refactor(builtin): use deterministic bridge name from switch UUID

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: <bridge>-<port> (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).
This commit is contained in:
YueGuobin 2026-07-18 01:18:32 +08:00
parent aabbee3a04
commit 18a123e0b9
No known key found for this signature in database
2 changed files with 13 additions and 13 deletions

View File

@ -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<i>`` (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: ``<bridge>-<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')

View File

@ -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'),