From 18a123e0b96a6a71f681ec705ac1f658d5d03175 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sat, 18 Jul 2026 01:18:32 +0800 Subject: [PATCH] refactor(builtin): use deterministic bridge name from switch UUID MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace psutil.net_if_addrs() scan with a deterministic name derived from the switch's node UUID: gns3 + first 6 hex chars (10 chars, fits kernel IFNAMSIZ limit of 15). Taps: - (12-13 chars). Crash recovery: brctl delete the bridge first (best-effort), then create fresh. Stale bridges from abnormal gns3server shutdown are automatically reclaimed on the next start — no EEXIST or leaked interfaces. Remove _free_iface helper and psutil import (no longer needed). --- .../compute/builtin/nodes/ethernet_switch.py | 25 +++++++++---------- .../compute/test_ethernet_switch_nodes.py | 1 + 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/gns3server/compute/builtin/nodes/ethernet_switch.py b/gns3server/compute/builtin/nodes/ethernet_switch.py index 71ab22977..a46366dd0 100644 --- a/gns3server/compute/builtin/nodes/ethernet_switch.py +++ b/gns3server/compute/builtin/nodes/ethernet_switch.py @@ -37,8 +37,6 @@ be the switch). ESW ``access``/``dot1q``/``qinq`` port modes are composed from the ``brctl`` VLAN primitives here -- see ``_apply_port_vlan``. """ -import psutil - from ...base_node import BaseNode from ...nios.nio_udp import NIOUDP from ...error import NodeError @@ -116,16 +114,6 @@ class EthernetSwitch(BaseNode): """Name of the per-port uBridge relay bridge (not a kernel interface).""" return f"{self._id}-{port_number}" - @staticmethod - def _free_iface(prefix): - """First free kernel interface name ``prefix`` (kernel names are <=15 chars).""" - existing = psutil.net_if_addrs() - for i in range(4096): - name = f"{prefix}{i}" - if name not in existing and len(name) <= 15: - return name - raise NodeError(f"Could not allocate a free interface name with prefix '{prefix}'") - def _tap_name(self, port_number): """Kernel TAP name for a port: ``-`` (host-unique via the bridge).""" return f"{self._bridge_name}-{port_number}" @@ -221,11 +209,22 @@ class EthernetSwitch(BaseNode): """ Creates the per-node kernel bridge once and enables VLAN filtering. Applies the bridge-level QinQ ethertype if any port needs it. + + The bridge name is deterministic: ``gns3`` + the first 6 hex chars of + this switch's UUID (kernel interface names are ≤ 15 chars). A stale + bridge from a previous crash is deleted first so ``brctl create`` never + hits EEXIST. """ if self._bridge_created: return - self._bridge_name = self._free_iface("gns3br") + # deterministic short name — 10 chars, always fits the 15-char kernel cap + self._bridge_name = "gns3" + self._id.replace("-", "")[:6] + # crash recovery: best-effort delete any leftover bridge + try: + await self._ubridge_send(f'brctl delete "{self._bridge_name}"') + except UbridgeError: + pass # not found = nothing to clean await self._ubridge_send(f'brctl create "{self._bridge_name}"') # ``brctl create`` leaves the bridge DOWN; bring it UP so it forwards. await self._ubridge_send(f'link set "{self._bridge_name}" up') diff --git a/tests/api/routes/compute/test_ethernet_switch_nodes.py b/tests/api/routes/compute/test_ethernet_switch_nodes.py index 40ac32ad0..16677686b 100644 --- a/tests/api/routes/compute/test_ethernet_switch_nodes.py +++ b/tests/api/routes/compute/test_ethernet_switch_nodes.py @@ -84,6 +84,7 @@ class TestEthernetSwitchNodesRoutes: node = compute_project.get_node(response.json()["node_id"]) br = node._bridge_name node._ubridge_send.assert_has_calls([ + call(f'brctl delete "{br}"'), call(f'brctl create "{br}"'), call(f'link set "{br}" up'), call(f'brctl vlanfiltering "{br}" on'),