mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 14:01: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
@@ -1065,6 +1065,10 @@ class BaseNode:
|
||||
# than skipping them as "already installed".
|
||||
self._marker_filter_bridges.clear()
|
||||
self._marker_specs.clear()
|
||||
# ...and so is the kernel capture, uBridge's single AF_PACKET slot
|
||||
# (meaningful for the kernel-datapath node types; see
|
||||
# KernelDatapathMixin._reserve_kernel_capture).
|
||||
self._kernel_capture_ifc = None
|
||||
|
||||
async def add_ubridge_udp_connection(self, bridge_name, source_nio, destination_nio):
|
||||
"""
|
||||
@@ -1116,9 +1120,18 @@ class BaseNode:
|
||||
:param filters: Array of filter dictionary
|
||||
"""
|
||||
|
||||
# The netem-extension filters (rate, reorder, gemodel…) only exist on
|
||||
# the kernel datapath. The controller keeps them off relay links;
|
||||
# this is the second guard for direct compute API use.
|
||||
self._guard_kernel_only_filters(filters)
|
||||
await self._ubridge_send("bridge reset_packet_filters " + bridge_name)
|
||||
for packet_filter in self._build_filter_list(filters):
|
||||
await self._ubridge_add_packet_filter(f"bridge add_packet_filter {bridge_name} {packet_filter}")
|
||||
|
||||
def _guard_kernel_only_filters(self, filters):
|
||||
"""
|
||||
The netem-extension filters (rate, reorder, gemodel…) only exist on
|
||||
the kernel datapath. The controller keeps them off relay links; this
|
||||
is the second guard for direct compute API use.
|
||||
"""
|
||||
|
||||
from gns3server.utils.packet_filter_validation import kernel_only_features
|
||||
|
||||
kernel_only = kernel_only_features(filters)
|
||||
@@ -1127,19 +1140,25 @@ class BaseNode:
|
||||
"Packet filter(s) {} only run on a kernel-datapath link (tc netem on the "
|
||||
"veth host end); the uBridge relay has no equivalent".format(", ".join(sorted(kernel_only)))
|
||||
)
|
||||
await self._ubridge_send("bridge reset_packet_filters " + bridge_name)
|
||||
for packet_filter in self._build_filter_list(filters):
|
||||
cmd = f"bridge add_packet_filter {bridge_name} {packet_filter}"
|
||||
try:
|
||||
await self._ubridge_send(cmd)
|
||||
except UbridgeError as e:
|
||||
match = re.search(r"Cannot compile filter '(.*)': syntax error", str(e))
|
||||
if match:
|
||||
message = f"Warning: ignoring BPF packet filter '{self.name}' due to syntax error: {match.group(1)}"
|
||||
log.warning(message)
|
||||
self.project.emit("log.warning", {"message": message})
|
||||
else:
|
||||
raise
|
||||
|
||||
async def _ubridge_add_packet_filter(self, cmd):
|
||||
"""
|
||||
Send one relay filter command, degrading a BPF expression that no
|
||||
longer compiles on this uBridge's libpcap to a warning instead of
|
||||
breaking the link (the controller already validated the syntax with
|
||||
tcpdump at create/update time).
|
||||
"""
|
||||
|
||||
try:
|
||||
await self._ubridge_send(cmd)
|
||||
except UbridgeError as e:
|
||||
match = re.search(r"Cannot compile filter '(.*)': syntax error", str(e))
|
||||
if match:
|
||||
message = f"Warning: ignoring BPF packet filter '{self.name}' due to syntax error: {match.group(1)}"
|
||||
log.warning(message)
|
||||
self.project.emit("log.warning", {"message": message})
|
||||
else:
|
||||
raise
|
||||
|
||||
def _build_filter_list(self, filters):
|
||||
"""
|
||||
|
||||
Reference in new issue
Block a user