From 95824b18611163a6d7d7581c3ab32f533b078d82 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Tue, 4 Aug 2026 22:19:43 +0800 Subject: [PATCH] marker: incremental apply, clear bridge map on uBridge stop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _ubridge_apply_markers now installs only markers not already on the bridge (uBridge's reset_packet_filters preserves mark filters), so an NIO update no longer re-adds — and reopens — sibling markers' pcaps. _stop_ubridge clears _marker_filter_bridges so a node restart re-installs everything (the map would otherwise keep stale entries pointing at a fresh, empty uBridge). --- gns3server/compute/base_node.py | 23 +++++++++++++++++------ tests/compute/test_base_node.py | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index 7cdd15fce..fca52ce31 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -990,6 +990,10 @@ class BaseNode: log.info(f"Stopping uBridge hypervisor at {self._ubridge_hypervisor.endpoint}") await self._ubridge_hypervisor.stop() self._ubridge_hypervisor = None + # uBridge is gone, so every marker filter (and its in-bridge state) is + # gone too — clear the map so the next apply re-installs them all rather + # than skipping them as "already installed". + self._marker_filter_bridges.clear() async def add_ubridge_udp_connection(self, bridge_name, source_nio, destination_nio): """ @@ -1187,11 +1191,13 @@ class BaseNode: async def _ubridge_apply_markers(self, bridge_name, nio): """ - (Re-)apply every traffic-insight marker carried by *nio* to the uBridge - bridge *bridge_name*. Called from ``add_ubridge_udp_connection`` (bridge - creation / node restart) and ``update_ubridge_udp_connection`` (NIO update - — the preceding ``_ubridge_apply_filters`` has already issued - ``reset_packet_filters``, so we must re-add markers to survive the reset). + Install the traffic-insight markers carried by *nio* onto bridge + *bridge_name* that aren't already there. uBridge's ``reset_packet_filters`` + preserves mark filters (contract), so on an NIO update we add only the new + ones — re-adding an existing marker would either duplicate it or + close/reopen its pcap. Called from ``add_ubridge_udp_connection`` (fresh + bridge, empty map → installs all) and ``update_ubridge_udp_connection`` + (incremental). """ from gns3server.compute.marker.marker_manager import MarkerManager @@ -1202,9 +1208,14 @@ class BaseNode: manager = MarkerManager.instance() markers_dir = self.project.markers_working_directory() for name, spec in markers.items(): + link_id = spec.get("link_id", "") + # Incremental: skip markers already on this bridge. uBridge keeps mark + # filters across reset_packet_filters, so re-adding would duplicate (or + # reopen the pcap). A fresh bridge has an empty map → installs all. + if (name, link_id) in self._marker_filter_bridges: + continue bpf = spec.get("bpf", "") tag = spec.get("tag") - link_id = spec.get("link_id", "") pcap_path = os.path.join( markers_dir, f"{self._id}_{link_id}_{name}.pcap" ) diff --git a/tests/compute/test_base_node.py b/tests/compute/test_base_node.py index b1393e25f..2935e990b 100644 --- a/tests/compute/test_base_node.py +++ b/tests/compute/test_base_node.py @@ -289,3 +289,29 @@ async def test_rebuild_marker_filter_delete_then_add(compute_project, manager): assert any("delete_packet_filter VPCS-10 m" in c for c in cmds) assert any("add_packet_filter VPCS-10 m mark" in c and "tcp" in c for c in cmds) assert any("enable_packet_filter VPCS-10 m off" in c for c in cmds) + + +@pytest.mark.asyncio +async def test_apply_markers_skips_already_installed(compute_project, manager): + # Incremental apply: a marker already in _marker_filter_bridges is not + # re-added (uBridge keeps it across reset), so its pcap isn't reopened. + node = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager) + node._ubridge_send = AsyncioMagicMock() + node._marker_filter_bridges["m", "L1"] = "VPCS-10" # already installed + nio = NIOUDP(1234, "127.0.0.1", 4321) + nio.markers = {"m": {"bpf": "icmp", "tag": None, "link_id": "L1", "direction": None, "enabled": True}} + with patch("gns3server.compute.marker.marker_manager.MarkerManager") as mm: + mm.instance.return_value.register = MagicMock() + await node._ubridge_apply_markers("VPCS-10", nio) + cmds = [c.args[0] for c in node._ubridge_send.call_args_list] + assert not any("add_packet_filter" in c for c in cmds) # skipped, not re-added + + +@pytest.mark.asyncio +async def test_stop_ubridge_clears_marker_bridges(compute_project, manager): + # uBridge stopping drops every marker filter — the map must clear so the next + # apply re-installs them instead of skipping as "already installed". + node = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager) + node._marker_filter_bridges["m", "L1"] = "VPCS-10" + await node._stop_ubridge() + assert node._marker_filter_bridges == {}