feat(docker): wire Docker-to-Docker links through a kernel bridge

Eligible links (both endpoints Docker, same compute, no filters/markers,
stopped nodes, no unix-socket NIO containers) are wired as veth pairs
enslaved into a per-link Linux kernel bridge (gns3 + 11 hex of the link
id, filling IFNAMSIZ) instead of the uBridge UDP relay, so frames flow
entirely in the kernel.

- NIOBridge NIO type: the controller decides the datapath in
  UDPLink._prepare and both computes enslave their deterministic gv*/gc*
  veth pair via uBridge's docker/brctl modules
- carrier/suspend = host-end admin state; both endpoints race-safely
  create/delete the bridge (EEXIST/EBUSY/ENOENT tolerated)
- veth adapters can still attach relay (UDP) links on a running node:
  lazy relay bridge over AF_PACKET on the host end (add_nio_ethernet)
- filters/markers/capture are rejected with explicit errors on this
  datapath (they live in the uBridge relay); vendor unix-socket
  containers are excluded; Server.enable_kernel_datapath is the
  kill-switch (default on)
- host-end veths survive container death and are removed on stop plus
  swept by name on the next start (crash/stale recovery)
This commit is contained in:
YueGuobin committed 2026-10-06 00:21:07 +08:00
1 parent 65c858d455
commit 37d7b9121e
13 files changed
+1031 -21

No files matched your search

@@ -36,6 +36,7 @@ import tempfile
from gns3server.compute.docker.docker_error import DockerError, DockerHttp304Error, DockerHttp404Error
from gns3server.compute.docker.docker_vm import DockerVM
from gns3server.compute.nios.nio_bridge import NIOBridge
from gns3server.utils.asyncio import wait_for_file_creation
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
@@ -415,6 +416,12 @@ class VendorDockerVM(DockerVM):
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: