marker: serial-link (WAN) support via data_link_type -> uBridge linktype

Markers now work on serial links (Cisco HDLC / PPP / Frame Relay / ATM), not just Ethernet. A marker carries a data_link_type (default DLT_EN10MB); at the uBridge boundary it becomes the 'mark ... linktype <dlt>' keyword so the BPF compiles and the pcap is written with the matching link-layer.

- MarkerCreate / MarkerDefinitionCreate gain data_link_type (default DLT_EN10MB). Per-link it is create-only; definitions are updatable (a change re-fans-out).
- base_node._marker_linktype() normalizes the GNS3 DLT name (strip DLT_, uppercase, None for EN10MB). Single source is SerialPort.data_link_types, so Cisco PPP -> PPP_SERIAL (50), matching the capture path -- no second mapping table.
- _ubridge_add_marker_filter (generic) and the IOU marker loop append 'linktype <dlt>'.
- Definition fan-out branches on link_type in inherit_marker: Ethernet is always EN10MB; a serial link uses the definition's WAN encapsulation, or is SKIPPED when none was chosen (an EN10MB pcap on serial is undecodable). One definition covers a mixed topology.
- MCP marker_definition exposes data_link_type (None = not forwarded).
- No uBridge rebuild on a data_link_type change -- only that one marker's filter is swapped (delete + re-add), mirroring a BPF change; reset_packet_filters preserves sibling mark filters.

Requires the uBridge build with 'mark ... linktype' support.
This commit is contained in:
YueGuobin committed 2026-08-07 01:53:33 +08:00
1 parent 117f580cfd
commit 3322233658
11 files changed
+231 -29

No files matched your search

+14
View File
@@ -122,13 +122,27 @@ class Link:
with a per-link private marker of the same name. It carries an
``inherited_from`` back-reference that (a) guards against per-link
edits and (b) lets the project sync changes to every copy at once.
The pcap link-layer follows the link type: Ethernet is always EN10MB.
A serial link needs the definition's WAN encapsulation (HDLC / PPP /
Frame Relay); if none was chosen the serial link is skipped — an EN10MB
pcap on a serial link is undecodable.
"""
def_data_link_type = marker_def.get("data_link_type", "DLT_EN10MB")
if self._link_type == "serial":
if def_data_link_type.upper() == "DLT_EN10MB":
return # definition is Ethernet-only; skip this serial link
data_link_type = def_data_link_type
else:
data_link_type = "DLT_EN10MB"
await self.start_marker(
name=f"global-{def_name}",
bpf=marker_def["bpf"],
tag=marker_def.get("tag"),
direction=marker_def.get("direction"),
data_link_type=data_link_type,
color=marker_def.get("color"),
highlight_duration=marker_def.get("highlight_duration"),
enabled=not marker_def.get("paused", False),
+29 -11
View File
@@ -1000,7 +1000,7 @@ class Project:
"For a capture-node-relative direction on a single link, use a per-link marker."
)
async def create_marker_definition(self, name, bpf, tag=None, direction=None, color=None, highlight_duration=None):
async def create_marker_definition(self, name, bpf, tag=None, direction=None, color=None, highlight_duration=None, data_link_type="DLT_EN10MB"):
"""
Create a project-level marker definition and fan out to every existing
link that has a capable node. Links without a capable node are silently
@@ -1014,12 +1014,12 @@ class Project:
self._validate_marker_definition_bpf(name, bpf)
self._validate_marker_definition_direction(name, direction)
self._marker_definitions[name] = {"bpf": bpf, "tag": tag, "direction": direction, "color": color, "highlight_duration": highlight_duration, "paused": False}
self._marker_definitions[name] = {"bpf": bpf, "tag": tag, "direction": direction, "color": color, "highlight_duration": highlight_duration, "data_link_type": data_link_type, "paused": False}
await self._apply_def_to_all_links(name)
self.dump()
self.emit_notification("project.updated", self.asdict())
async def update_marker_definition(self, name, bpf=None, tag=None, direction=_UNSET, color=None, highlight_duration=None):
async def update_marker_definition(self, name, bpf=None, tag=None, direction=_UNSET, color=None, highlight_duration=None, data_link_type=_UNSET):
"""
Update a marker definition and sync every inherited copy on every link.
"""
@@ -1042,15 +1042,33 @@ class Project:
if direction is not _UNSET:
self._validate_marker_definition_direction(name, direction)
d["direction"] = direction # None = clear back to both directions
if data_link_type is not _UNSET:
d["data_link_type"] = data_link_type
# Sync: update every inherited copy across all links.
for link in list(self._links.values()):
marker_name = f"global-{name}"
if marker_name in link.markers and link.markers[marker_name].get("inherited_from") == name:
await link.update_marker(
marker_name, bpf=d["bpf"], tag=d.get("tag"), direction=d.get("direction"), color=d.get("color"),
highlight_duration=d.get("highlight_duration"), inherited=True
)
if data_link_type is not _UNSET:
# data_link_type decides which links host an inherited copy (serial
# links are skipped unless a WAN encapsulation is chosen), so a change
# needs a full re-fan-out: drop every copy, then re-apply.
for link in list(self._links.values()):
marker_name = f"global-{name}"
if marker_name in link.markers and link.markers[marker_name].get("inherited_from") == name:
try:
await link.stop_marker(marker_name, inherited=True)
except ControllerError:
log.warning(
"Failed to remove inherited marker %s from link %s",
marker_name, link.id
)
await self._apply_def_to_all_links(name)
else:
# Sync: update every inherited copy across all links.
for link in list(self._links.values()):
marker_name = f"global-{name}"
if marker_name in link.markers and link.markers[marker_name].get("inherited_from") == name:
await link.update_marker(
marker_name, bpf=d["bpf"], tag=d.get("tag"), direction=d.get("direction"), color=d.get("color"),
highlight_duration=d.get("highlight_duration"), inherited=True
)
self.dump()
self.emit_notification("project.updated", self.asdict())
+11 -7
View File
@@ -57,15 +57,18 @@ class UDPLink(Link):
def _markers_for_node(self, node):
"""
Marker specs (name -> {bpf, tag, link_id, direction, enabled}) for the
markers whose capture side is ``node``. Routed by capture_node_id so a
marker only rides the NIO of the node whose uBridge will host it. A
disabled marker is included (installed then turned ``off`` at uBridge,
not dropped) so the UI can toggle it instantly without an NIO rebuild.
Marker specs (name -> {bpf, tag, link_id, direction, data_link_type,
enabled}) for the markers whose capture side is ``node``. Routed by
capture_node_id so a marker only rides the NIO of the node whose uBridge
will host it. A disabled marker is included (installed then turned
``off`` at uBridge, not dropped) so the UI can toggle it instantly
without an NIO rebuild.
"""
return {
name: {"bpf": m["bpf"], "tag": m.get("tag"), "link_id": self._id,
"direction": m.get("direction"), "enabled": m.get("enabled", True)}
"direction": m.get("direction"),
"data_link_type": m.get("data_link_type", "DLT_EN10MB"),
"enabled": m.get("enabled", True)}
for name, m in self._markers.items()
if m.get("capture_node_id") == node.id
}
@@ -350,7 +353,7 @@ class UDPLink(Link):
# explicitly deletes a marker via the REST API, and a marker is torn
# down automatically only when its link is deleted.
async def start_marker(self, name, bpf, tag=None, direction=None, capture_node_id=None, color=None, highlight_duration=None, enabled=True, inherited_from=None):
async def start_marker(self, name, bpf, tag=None, direction=None, data_link_type="DLT_EN10MB", capture_node_id=None, color=None, highlight_duration=None, enabled=True, inherited_from=None):
"""
Attach a traffic-insight marker to this link.
@@ -401,6 +404,7 @@ class UDPLink(Link):
"highlight_duration": highlight_duration,
"capture_node_id": marker_side["node"].id,
"direction": direction,
"data_link_type": data_link_type,
}
if inherited_from:
marker_entry["inherited_from"] = inherited_from