From e5f65a510a983eac387a2899a65697b31fee9aa2 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Thu, 1 Oct 2026 11:55:20 +0800 Subject: [PATCH] feat(controller): offer Dynamips the kernel datapath MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Dynamips Ethernet port can anchor a kernel link whenever its compute's uBridge has the tap module — the anchors are uBridge-created TAPs the hypervisor merely opens, the same capability QEMU gates on, so no Dynamips-specific capability is needed. Serial, ATM and POS ports are excluded per port by the existing link-type check (the controller's port matrix decides), keeping them on the relay whatever the capabilities. Eligibility tests cover the mixed pairs (docker/qemu/iou endpoints on the same compute), the strict tap-capability requirement, and the serial exclusion. --- gns3server/controller/udp_link.py | 14 +++- tests/controller/test_kernel_datapath_link.py | 64 +++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/gns3server/controller/udp_link.py b/gns3server/controller/udp_link.py index 416bb8050..abe1a7f78 100644 --- a/gns3server/controller/udp_link.py +++ b/gns3server/controller/udp_link.py @@ -182,9 +182,12 @@ class UDPLink(Link): datapath and cannot carry a kernel link at all. IOU's Ethernet bays anchor on persistent TAPs too, bound to its IOL fabric (``iol_bridge add_nio_tap``) — a separate capability, since it can - be present or absent independently of the tap module. A kernel link - is an Ethernet segment, so non-Ethernet ports (IOU serial) stay on - the relay whatever the node's capabilities. + be present or absent independently of the tap module. Dynamips + Ethernet slot ports anchor on persistent TAPs the hypervisor opens + (the tap module again — the Dynamips binary itself creates nothing), + so it gates like QEMU. A kernel link is an Ethernet segment, so + non-Ethernet ports (IOU serial, Dynamips serial/ATM/POS adapters) + stay on the relay whatever the node's capabilities. """ if not Config.instance().settings.Server.enable_kernel_datapath: @@ -214,6 +217,11 @@ class UDPLink(Link): return capabilities.get("ubridge_tap") is True if node.node_type == "iou": return capabilities.get("ubridge_iol_tap") is True + if node.node_type == "dynamips": + # The anchors are uBridge-created TAPs the Dynamips hypervisor + # merely opens (nio create_tap) — the same tap-module capability + # QEMU needs, not a Dynamips-specific one. + return capabilities.get("ubridge_tap") is True return False async def _prepare(self): diff --git a/tests/controller/test_kernel_datapath_link.py b/tests/controller/test_kernel_datapath_link.py index b61d5a644..5542783e9 100644 --- a/tests/controller/test_kernel_datapath_link.py +++ b/tests/controller/test_kernel_datapath_link.py @@ -169,6 +169,70 @@ async def test_iou_eligibility_is_its_own_capability(project): assert link._kernel_datapath_eligible(node1, node2) is False +@pytest.mark.asyncio +async def test_kernel_datapath_eligible_for_dynamips_with_tap_support(project): + """ + Dynamips Ethernet slot ports anchor on persistent TAPs the hypervisor + opens (nio create_tap on a uBridge-created device), so they gate on the + tap module like QEMU — no Dynamips-specific capability — and mix with + docker/qemu/iou endpoints on the same compute. + """ + + compute = MagicMock() + compute.id = "compute-1" + compute.capabilities = {"ubridge_tap": True, "ubridge_iol_tap": True} + node1 = _node(project, compute, "r1", node_type="dynamips") + node2 = _node(project, compute, "r2", node_type="dynamips") + link = await _link(project, node1, node2) + assert link._kernel_datapath_eligible(node1, node2) is True + + for peer in ( + _node(project, compute, "docker1"), + _node(project, compute, "qemu1", node_type="qemu"), + _node(project, compute, "iou1", node_type="iou"), + ): + link = await _link(project, node1, peer) + assert link._kernel_datapath_eligible(node1, peer) is True + + +@pytest.mark.asyncio +async def test_dynamips_eligibility_follows_the_tap_capability(project): + """ + A compute whose uBridge cannot create persistent TAPs keeps Dynamips on + the relay (the hypervisor would have nothing to open), and so does an + unreported/failed probe. + """ + + node2 = _node(project, _tap_capable_compute(), "docker1") + for tap_support in (None, False): + compute = _tap_capable_compute(tap_support=tap_support) + node1 = _node(project, compute, "r1", node_type="dynamips") + link = await _link(project, node1, node2) + assert link._kernel_datapath_eligible(node1, node2) is False + + +@pytest.mark.asyncio +async def test_kernel_datapath_not_eligible_for_dynamips_serial_ports(project): + """ + A Dynamips serial port (NM-4T, WIC-2T, ...) stays on the relay whatever + the compute's capabilities — the same per-port exclusion as IOU serial, + decided by the controller's port matrix. + """ + + from gns3server.controller.ports.serial_port import SerialPort + + compute = MagicMock() + compute.id = "compute-1" + compute.capabilities = {"ubridge_tap": True} + + node1 = _node(project, compute, "r1", node_type="dynamips") + node1._ports = [SerialPort("Serial0/0", 0, 0, 0)] + node2 = _node(project, compute, "r2", node_type="dynamips") + node2._ports = [SerialPort("Serial0/0", 0, 0, 0)] + link = await _link(project, node1, node2) + assert link._kernel_datapath_eligible(node1, node2) is False + + @pytest.mark.asyncio async def test_kernel_datapath_not_eligible_for_serial_ports(project): """