fix(compute): refuse a duplicate port NIO and wire before bookkeeping

The compute-side backstop of the controller's duplicate-port guard
(keep-port-links + live-port bind made the controller guard reliable;
this is the layer under it). IOU and Dynamips bound the adapter NIO
before the uBridge/hypervisor attach: a failed attach left the port's
binding replaced — the link that still owned the port lost its handle
and its host state leaked (the IOU EBUSY incident's 8 orphan TAPs).
Dynamips was worse: its wiring idempotence check silently skipped the
attach on a second binding and rebound only the bookkeeping. A port
that already carries an NIO is now refused, and both types bind the
NIO only after the wiring succeeded.
This commit is contained in:
YueGuobin committed 2026-10-06 00:21:10 +08:00
1 parent 6839b279be
commit 6e36b58a5d
4 files changed
+125 -17

No files matched your search

+15 -1
View File
@@ -1312,12 +1312,26 @@ class Router(KernelDatapathMixin, BaseNode):
if not adapter.port_exists(port_number):
raise DynamipsError(f"Port {port_number} does not exist on adapter {adapter}")
# The compute-side backstop of the controller's duplicate-port guard:
# a port carries at most one NIO, and overwriting the one a link
# still owns orphans that link's teardown (its host state leaks).
# Refusing also keeps the kernel wiring's own idempotence check from
# silently skipping the attach while rebinding the bookkeeping.
if adapter.get_nio(port_number) is not None:
raise DynamipsError(
"Port {port_number} on slot {slot_number} of router '{name}' already has a link".format(
name=self._name, slot_number=slot_number, port_number=port_number
)
)
if isinstance(nio, NIOBridge):
# Kernel datapath: the port's anchor TAP is opened in the
# hypervisor and enslaved into the NIO's per-link bridge (the
# wiring is deferred to the node's start when no anchors exist).
adapter.add_nio(port_number, nio)
# Wire before bookkeeping: an attach that fails mid-way leaves
# the port unbound instead of half-wired.
await self._attach_kernel_nio(slot_number, port_number, nio)
adapter.add_nio(port_number, nio)
log.debug(
'Router "{name}" [{id}]: {nio} bound to port {slot_number}/{port_number}'.format(
name=self._name, id=self._id, nio=nio, slot_number=slot_number, port_number=port_number