fix: resolve the code-review findings on the kernel datapath

Compute:
- Track the anchor that owns uBridge's single AF_PACKET capture slot: a
  second kernel port's capture is refused with a clear error (its flag
  rolled back), a failed start no longer wedges the port, and only the
  owning port's stop issues the process-wide `capture stop_kernel`
  (docker/qemu/iou/dynamips/switch, via the mixin's slot helpers).
- Unwind failed starts instead of leaking uBridge and the anchor TAPs:
  QEMU's stop body runs under try/finally, QEMU and IOU start the datapath
  inside the launch try, Dynamips undoes a half-attached port (the retry
  used to be short-circuited into a silent black hole), and Docker deletes
  the veth pair when the namespace move fails.
- Restore the relay filter guard on IOU (kernel-only filters raise
  NodeError, an uncompilable BPF line degrades to a warning), validate the
  eBPF/quota/window/bpf filter shapes before touching the bridge (short or
  non-numeric values surfaced as 500s after partial state), and refuse a
  second NIO on an occupied Docker/QEMU port (the guard IOU and Dynamips
  already had).
- Probe fixes: `_probe_bridge_tap` survives a failing uBridge start, and
  the probe caches key on ctime too, so a chmod/setcap remedy re-probes.

Controller:
- Parse GNS3_UNIX_SOCKET_NIO like the compute does (line-anchored, truthy
  value) and mirror the compute's class selection in kernel_anchor_type.
- Read partial capability reports defensively; make BridgeNIO.bridge
  required-but-nullable so an omitted key is a 422 rather than a KeyError.
- Strip the packet_loss correlation slot even when it is zero: the relay
  filter takes exactly one argument and rejected `packet_loss 10 0`.

Ethernet switch: skip ports whose absorbed anchor does not exist yet (the
join is deferred by design) and roll the ports_mapping back when the VLAN
reconcile fails, so a retry still diffs against the old mapping.

Agent: reject device config entries without a device_name instead of
silently dropping them (a malformed batch used to execute partially while
reporting success).

Also split the netem builder to clear C901 and drop the two per-file ruff
ignores the PR had added, keeping the remove-only policy.
This commit is contained in:
YueGuobin committed 2026-10-06 19:55:21 +08:00
1 parent 87ca2138e3
commit c3c025ab2a
21 files changed
+654 -274

No files matched your search

+7 -3
View File
@@ -803,14 +803,18 @@ class Link:
if not reported:
return True
for caps in reported:
# Field-wise .get: the controller stores the compute's raw
# capabilities verbatim, so a divergent or hand-crafted payload
# with a partial ubridge_tc dict must read as "not supported"
# rather than raising a KeyError into the API response.
if filter_type in FILTER_EBPF_MODES:
if FILTER_EBPF_MODES[filter_type] not in caps["ebpf_modes"]:
if FILTER_EBPF_MODES[filter_type] not in (caps.get("ebpf_modes") or []):
return False
elif filter_type == "bpf":
if not caps["cbpf"]:
if not caps.get("cbpf"):
return False
elif filter_type in FILTER_NETEM_KEYWORDS:
if FILTER_NETEM_KEYWORDS[filter_type] not in caps["netem"]:
if FILTER_NETEM_KEYWORDS[filter_type] not in (caps.get("netem") or []):
return False
# the remaining types (delay, packet_loss, corrupt) run on any
# build that reports tc capabilities at all
+11 -1
View File
@@ -70,7 +70,17 @@ def _is_unix_socket_docker(node):
"""
environment = (node.properties or {}).get("environment") or ""
return "GNS3_UNIX_SOCKET_NIO" in environment and not is_iol_runner_environment(environment)
if is_iol_runner_environment(environment):
return False
for line in environment.splitlines():
line = line.strip().rstrip(",")
if line.startswith("GNS3_UNIX_SOCKET_NIO="):
# Mirror the compute's parsing exactly (VendorDockerVM): the
# marker must start the line, and only a truthy value enables
# the unix-socket wiring — with 0/false/no the container wires
# adapters the standard way and can carry a kernel link.
return line.split("=", 1)[1].strip().lower() in ("1", "true", "yes")
return False
class UDPLink(Link):