feat(iol): anchor IOL runner containers on persistent TAPs

IOL runner containers (IOLDockerVM, GNS3_IOL_RUNNER) had no kernel
datapath: their guest leg is the runner's AF_UNIX socket pairs, so every
link rode the uBridge relay. Their Ethernet bays now own persistent TAP
anchors (gx names, the iol_docker key of the shared naming contract —
born at node start like IOU's, before any link, so the switch fast
path's deferred join can never miss them), and the per-port bridge's
topology leg swaps between UDP (relay) and the anchor (kernel link)
through the frozen stop -> delete -> add -> start contract — the unix
binding never re-binds across a swap.

- iol_docker_vm: KernelDatapathMixin hooks, the anchor lifecycle
  (_prepare_tap_datapath at _start_ubridge: probe ubridge_bridge_tap +
  ubridge_tap, sweep leftover, tap create born down), the kernel attach
  (ensure-then-add — tap create is strictly create-only, IFF_TUN_EXCL,
  so the ensure checks /sys/class/net first: an unconditional create
  answers EBUSY on the device the start loop just made), the release
  (port bridge stop + delete_nio_tap before the shared teardown) and the
  stop order the fd holders dictate (port bridges first, then tap
  delete, then the per-link bridges — all before the hypervisor stops).
- vendor_docker_vm: the port-bridge creation factored into
  _ensure_unix_port_bridge (idempotent on the registered name), keeping
  the NIOBridge rejection for the generic unix-socket containers.
- docker_vm: the three _kernel_veths lookups go through the
  _kernel_host_ifc hook — behavior-identical for DockerVM, and it is
  what lets the subclass route without overriding the binding methods.
- kernel_anchor: the iol_docker key (gx) and kernel_anchor_type, the
  controller-side mapping for naming an IOL runner's anchor on the
  switch fast path (the marker check stays inline: this module sits
  below everything and must not import the controller package).
This commit is contained in:
YueGuobin committed 2026-10-06 00:21:09 +08:00
1 parent c20125c180
commit 4b4a0fc35f
7 files changed
+773 -31

No files matched your search

+24 -13
View File
@@ -395,7 +395,28 @@ class VendorDockerVM(DockerVM):
Override: with GNS3_UNIX_SOCKET_NIO, bridge the adapter port through
the image's AF_UNIX datagram socket pair (raw Ethernet frames)
instead of a TAP interface moved into the container's network
namespace.
namespace — see ``_ensure_unix_port_bridge`` for the wiring.
"""
if not self._unix_socket_nio:
return await super()._add_ubridge_connection(nio, adapter_number, port_number)
if isinstance(nio, NIOBridge):
raise DockerError(
f"Container '{self._name}' bridges its adapters through AF_UNIX sockets; "
"kernel-datapath links are not supported for this container"
)
await self._ensure_unix_port_bridge(adapter_number, port_number)
if nio:
await self._connect_nio(adapter_number, nio, port_number)
async def _ensure_unix_port_bridge(self, adapter_number, port_number):
"""
The adapter port's uBridge bridge with its unix-socket NIO — the
guest leg every link topology rides on. Idempotent: an already
created bridge (this start wired it) is left alone.
Ports are addressed flat across adapters: interface index = adapter
number × ports-per-adapter + port number (single-port adapters
@@ -413,15 +434,6 @@ class VendorDockerVM(DockerVM):
uses.
"""
if not self._unix_socket_nio:
return await super()._add_ubridge_connection(nio, adapter_number, port_number)
if isinstance(nio, NIOBridge):
raise DockerError(
f"Container '{self._name}' bridges its adapters through AF_UNIX sockets; "
"kernel-datapath links are not supported for this container"
)
try:
adapter = self._ethernet_adapters[adapter_number]
except IndexError:
@@ -429,6 +441,8 @@ class VendorDockerVM(DockerVM):
interface_number = adapter_number * adapter.interfaces + port_number
bridge_name = self._bridge_name(adapter_number, port_number)
if bridge_name in self._bridges:
return
try:
await self._ubridge_send(f"bridge create {bridge_name}")
self._bridges.add(bridge_name)
@@ -470,9 +484,6 @@ class VendorDockerVM(DockerVM):
remote_sock,
)
if nio:
await self._connect_nio(adapter_number, nio, port_number)
async def _set_adapter_carrier(self, adapter_number, connected, port_number=0):
"""
Override: with GNS3_UNIX_SOCKET_NIO the bridges carry only unix and