mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
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:
21 files changed
+654
-274
No files matched your search
@@ -128,11 +128,12 @@ class DockerKernelDatapathMixin(KernelDatapathMixin):
|
||||
await self._ubridge_send(f"docker move_to_ns {guest_ifc} {self._namespace} {ifname}")
|
||||
except UbridgeError as e:
|
||||
raise UbridgeNamespaceError(e)
|
||||
except UbridgeNamespaceError:
|
||||
raise
|
||||
except Exception:
|
||||
# Don't leave a half-created pair behind (the next start would
|
||||
# fail on create_veth EEXIST until the stale sweep runs).
|
||||
# fail on create_veth EEXIST until the stale sweep runs). The
|
||||
# namespace failure cleans up the same way — the bare raise
|
||||
# preserves its type for the caller's UbridgeNamespaceError
|
||||
# handling (docker_vm.start converts it into a failed start).
|
||||
with contextlib.suppress(UbridgeError):
|
||||
await self._ubridge_send(f'docker delete_veth "{host_ifc}"')
|
||||
raise
|
||||
|
||||
@@ -1869,6 +1869,14 @@ class DockerVM(DockerKernelDatapathMixin, BaseNode):
|
||||
f"Port {port_number} doesn't exist on adapter {adapter_number} of Docker container '{self.name}'"
|
||||
)
|
||||
|
||||
# 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).
|
||||
if adapter.get_nio(port_number) is not None:
|
||||
raise DockerError(
|
||||
f"Port {port_number} on adapter {adapter_number} of Docker container '{self.name}' already has a link"
|
||||
)
|
||||
|
||||
if self.status == "started" and self.ubridge:
|
||||
await self._connect_nio(adapter_number, nio, port_number)
|
||||
await self._set_adapter_carrier(adapter_number, not nio.suspend, port_number)
|
||||
@@ -1999,11 +2007,18 @@ class DockerVM(DockerKernelDatapathMixin, BaseNode):
|
||||
if isinstance(nio, NIOBridge):
|
||||
# Kernel datapath: no relay bridge exists — capture via uBridge's
|
||||
# AF_PACKET module bound to the anchor (a veth host end for the
|
||||
# standard container, a persistent TAP for an IOL runner).
|
||||
# Single capture per uBridge process: a concurrent second one
|
||||
# returns EALREADY.
|
||||
# standard container, a persistent TAP for an IOL runner). One
|
||||
# capture per uBridge process: the slot is claimed per anchor,
|
||||
# and a failed start rolls the port's capturing flag back so the
|
||||
# port cannot later stop the winner's capture.
|
||||
host_ifc = self._kernel_host_ifc(adapter_number, port_number)
|
||||
await self._ubridge_send(f'capture start_kernel {host_ifc} "{output_file}"')
|
||||
try:
|
||||
self._reserve_kernel_capture(host_ifc)
|
||||
await self._ubridge_send(f'capture start_kernel {host_ifc} "{output_file}"')
|
||||
except Exception:
|
||||
self._release_kernel_capture(host_ifc)
|
||||
nio.stop_packet_capture()
|
||||
raise
|
||||
return
|
||||
# Relay datapath (even though the adapter interface is a veth, the
|
||||
# traffic flows through the uBridge bridge — key on the NIO type,
|
||||
@@ -2023,8 +2038,13 @@ class DockerVM(DockerKernelDatapathMixin, BaseNode):
|
||||
raise DockerError("Cannot stop the packet capture: uBridge is not running")
|
||||
nio = self._ethernet_adapters[adapter_number].get_nio(port_number)
|
||||
if isinstance(nio, NIOBridge):
|
||||
# Idempotent on the uBridge side (no active capture is OK).
|
||||
await self._ubridge_send("capture stop_kernel")
|
||||
# Idempotent on the uBridge side (no active capture is OK), but
|
||||
# process-wide and argument-less: only the port owning the slot
|
||||
# may stop it, or a second port's stop would kill this capture.
|
||||
host_ifc = self._kernel_host_ifc(adapter_number, port_number)
|
||||
if self._kernel_capture_owned_by(host_ifc):
|
||||
await self._ubridge_send("capture stop_kernel")
|
||||
self._release_kernel_capture(host_ifc)
|
||||
return
|
||||
bridge_name = self._bridge_name(adapter_number, port_number)
|
||||
await self._ubridge_send(f"bridge stop_capture {bridge_name}")
|
||||
|
||||
Reference in new issue
Block a user