mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 20:40:13 +03:00
fix(marker): per-link attribution via link field in MARK signals
A uBridge MARK signal carries only node= and filter= (no bridge/link field), so when one node is the capture side for several links that share a marker name (always the case for global-{name} definitions on a multi-interface node) the signals were indistinguishable and the (node, filter) registry collapsed them to a single link.
The mark filter is now stamped with its link id (mark <bpf> ... link <link_id>); uBridge echoes it verbatim (link=<link_id>) and the listener uses the signal's link= as the authoritative link_id of the marker.match event, falling back to the registry only for legacy signals without it. base_node and iou apply paths pass link_id; covered by two new listener tests.
This commit is contained in:
parent
27356da62d
commit
794bfef450
@ -1073,7 +1073,7 @@ class BaseNode:
|
||||
)
|
||||
i += 1
|
||||
|
||||
async def _ubridge_add_marker_filter(self, bridge_name, name, bpf, pcap_path, tag=None):
|
||||
async def _ubridge_add_marker_filter(self, bridge_name, name, bpf, pcap_path, tag=None, link_id=None):
|
||||
"""
|
||||
Attach a `mark` packet filter to a uBridge bridge for traffic insight.
|
||||
|
||||
@ -1104,6 +1104,11 @@ class BaseNode:
|
||||
)
|
||||
if tag is not None:
|
||||
cmd += f" tag {tag}"
|
||||
# Per-link attribution (contract §3.2): when one ubridge bridge serves
|
||||
# several GNS3 links (e.g. IOU's per-node bridge), bridge+filter collide,
|
||||
# so the link id is the only way to tell signals — and pcap files — apart.
|
||||
if link_id:
|
||||
cmd += f" link {link_id}"
|
||||
cmd += ' pcap "{path}"'.format(path=pcap_path)
|
||||
# Let BPF compile errors propagate — the marker is the user's intent, so a
|
||||
# bad expression must surface instead of being silently dropped.
|
||||
@ -1133,7 +1138,7 @@ class BaseNode:
|
||||
markers_dir, f"{self._id}_{link_id}_{name}.pcap"
|
||||
)
|
||||
try:
|
||||
await self._ubridge_add_marker_filter(bridge_name, name, bpf, pcap_path, tag)
|
||||
await self._ubridge_add_marker_filter(bridge_name, name, bpf, pcap_path, tag, link_id)
|
||||
except UbridgeError as e:
|
||||
# Swallow BPF compile errors (warn + skip) so a single bad
|
||||
# expression can't break link creation / node restart — mirrors
|
||||
|
||||
@ -1137,6 +1137,11 @@ class IOUVM(BaseNode):
|
||||
)
|
||||
if tag is not None:
|
||||
cmd += f" tag {tag}"
|
||||
# IOU uses one per-node IOL-BRIDGE for every link, so bridge+filter
|
||||
# are identical across this node's links — `link` is the only way the
|
||||
# controller can tell their signals apart (contract §3.2).
|
||||
if link_id:
|
||||
cmd += f" link {link_id}"
|
||||
cmd += ' pcap "{path}"'.format(path=pcap_path)
|
||||
try:
|
||||
await self._ubridge_send(cmd)
|
||||
|
||||
@ -79,6 +79,7 @@ class MarkerListener(asyncio.DatagramProtocol):
|
||||
return
|
||||
|
||||
# "-" means the field was unset on the ubridge side (see contract §3.3).
|
||||
link = kv.get("link")
|
||||
tag = kv.get("tag")
|
||||
length = kv.get("len")
|
||||
|
||||
@ -89,10 +90,16 @@ class MarkerListener(asyncio.DatagramProtocol):
|
||||
)
|
||||
return
|
||||
|
||||
# `link=` is the authoritative per-link id (opaque, set by gns3server at
|
||||
# filter install time). It disambiguates signals that share a node+filter
|
||||
# across several links; fall back to the registry's link only for legacy
|
||||
# signals that carry no `link=`.
|
||||
signal_link = link if link and link != "-" else None
|
||||
|
||||
event = {
|
||||
"project_id": project_id,
|
||||
"node_id": node_id,
|
||||
"link_id": link_id,
|
||||
"link_id": signal_link or link_id,
|
||||
"filter": filter_name,
|
||||
# Prefer the value carried in the signal; fall back to the one we registered.
|
||||
"tag": tag if tag and tag != "-" else registered_tag,
|
||||
|
||||
@ -161,6 +161,28 @@ class TestMarkerListener:
|
||||
lis.datagram_received(b"MARK 2.0 node=n filter=f tag=- len=20\n", None)
|
||||
assert fmgr.events[0][1]["tag"] == 42
|
||||
|
||||
def test_link_in_signal_overrides_registry_link(self):
|
||||
# Per-link attribution (contract §3.2/§3.3): the signal's `link=` is
|
||||
# authoritative and must disambiguate links sharing a node+filter —
|
||||
# e.g. several links on one IOU node under the same global marker name.
|
||||
fmgr = FakeMarkerManager()
|
||||
fmgr.register("p", "n", "f", "registry-link", tag=1)
|
||||
lis = MarkerListener(fmgr)
|
||||
lis.connection_made(None)
|
||||
lis.datagram_received(
|
||||
b"MARK 3.0 node=n filter=f link=signal-link tag=1 len=42\n", None
|
||||
)
|
||||
assert fmgr.events[0][1]["link_id"] == "signal-link"
|
||||
|
||||
def test_link_dash_falls_back_to_registry_link(self):
|
||||
# Legacy signals that carry no link fall back to the registry's link_id.
|
||||
fmgr = FakeMarkerManager()
|
||||
fmgr.register("p", "n", "f", "registry-link", tag=1)
|
||||
lis = MarkerListener(fmgr)
|
||||
lis.connection_made(None)
|
||||
lis.datagram_received(b"MARK 3.0 node=n filter=f link=- tag=1 len=42\n", None)
|
||||
assert fmgr.events[0][1]["link_id"] == "registry-link"
|
||||
|
||||
def test_exception_does_not_kill_listener(self):
|
||||
fmgr = FakeMarkerManager()
|
||||
lis = MarkerListener(fmgr)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user