feat(docker): traffic-insight markers on kernel-datapath links via AF_PACKET

Markers no longer disqualify the kernel datapath. They ride the NIO like
on the relay path and attach to the veth host end through uBridge's
marker add_kernel/delete_kernel/enable_kernel commands (AF_PACKET taps,
same BPF/direction/signal semantics as the relay mark filter).

- base_node: marker anchors are now polymorphic (relay bridge name or
  kernel veth host interface); the reconcile loop is datapath-agnostic
  and DockerVM overrides the add/delete/enable primitives to translate
  the commands. Marker-name validation extracted for both add paths.
- docker_vm: kernel NIO connect/update apply markers on the veth anchor;
  link deletion tears markers down explicitly (the veth survives the
  link, unlike a relay bridge whose death drops its filters).
- controller: eligibility and start_marker no longer exclude markers or
  project-level marker definitions; the kernel NIO carries the capture
  node's marker specs.
- NIO creation accepts markers on kernel NIOs (filters still rejected).
This commit is contained in:
YueGuobin committed 2026-10-06 00:21:07 +08:00
1 parent f6b869be63
commit 61020d3bbc
8 files changed
+378 -69

No files matched your search

+9 -17
View File
@@ -135,9 +135,9 @@ class UDPLink(Link):
"""
Whether this link can be wired on the kernel datapath (veth pairs
enslaved into a per-link Linux bridge) instead of the uBridge UDP
relay. The kernel path has no userspace relay, so filters and
markers — which live in the relay — disqualify it (capture is
served separately by uBridge's AF_PACKET module).
relay. The kernel path has no userspace relay, so filters — which
live in the relay — disqualify it. Markers and capture are served
by uBridge's AF_PACKET modules on the veth host end.
It is host-local (same compute) and requires the adapter interfaces
to be created as veths at container start, hence stopped nodes only.
"""
@@ -150,12 +150,6 @@ class UDPLink(Link):
return False
if self.get_active_filters():
return False
if self._markers:
return False
# Project-level marker definitions are inherited by every link right
# after creation (apply_defs_to_new_link) and need the relay datapath.
if getattr(self._project, "_marker_definitions", None):
return False
# Running nodes already have relay TAPs wired into uBridge; the
# kernel path needs the veth variant created at container start.
if node1.status != "stopped" or node2.status != "stopped":
@@ -195,10 +189,14 @@ class UDPLink(Link):
# both ends compute it independently (no coordinator) and crash
# leftovers sweep deterministically; 11 hex chars fill IFNAMSIZ
# (15), pushing name-collision probability to irrelevance.
# Markers ride the NIO like on the relay datapath, routed by
# capture node; they attach to the veth host end via uBridge's
# AF_PACKET marker module instead of a relay `mark` filter.
bridge_name = "gns3" + self._id.replace("-", "")[:11]
node1_markers, node2_markers = self._get_node_markers(node1, node2)
self._link_data = [
{"type": "nio_bridge", "bridge": bridge_name, "filters": {}, "markers": {}, "suspend": self._suspended},
{"type": "nio_bridge", "bridge": bridge_name, "filters": {}, "markers": {}, "suspend": self._suspended},
{"type": "nio_bridge", "bridge": bridge_name, "filters": {}, "markers": node1_markers, "suspend": self._suspended},
{"type": "nio_bridge", "bridge": bridge_name, "filters": {}, "markers": node2_markers, "suspend": self._suspended},
]
return [
(node1, adapter_number1, port_number1, self._link_data[0]),
@@ -542,12 +540,6 @@ class UDPLink(Link):
if name in self._markers:
raise ControllerError(f"Marker '{name}' already exists on link {self._id}")
if self.kernel_datapath:
raise ControllerError(
"Markers are not supported on kernel-datapath links (no uBridge relay in the "
"forwarding path); delete and recreate the link to use markers"
)
# Validate the BPF only for private per-link markers. An inherited copy
# (``inherited_from`` set) fans out from a definition whose BPF was
# already validated once at create/update (and on project load), so