From aabbee3a04edd0654283134568e8ca7081d64f19 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sat, 18 Jul 2026 01:11:25 +0800 Subject: [PATCH] fix(builtin): bring kernel bridge UP after creation brctl create leaves the bridge administratively DOWN. Add link set up so the bridge actually forwards frames between enslaved ports. Also record Docker iptables FORWARD DROP pitfall in project memory. --- .claude/memory/MEMORY.md | 1 + .../memory/docker-iptables-forward-bridge.md | 38 +++++++++++++++++++ .../compute/builtin/nodes/ethernet_switch.py | 2 + .../compute/test_ethernet_switch_nodes.py | 1 + 4 files changed, 42 insertions(+) create mode 100644 .claude/memory/docker-iptables-forward-bridge.md diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index 7956d4d04..dbc0f3b8d 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -24,6 +24,7 @@ ### uBridge Permission - **[uBridge Permission Issue](./gns3-ubridge-permission.md)** - Docker containers fail to start due to missing CAP_NET_ADMIN/CAP_NET_RAW capabilities on uBridge +- **[Docker iptables FORWARD blocks bridge](./docker-iptables-forward-bridge.md)** - Docker sets FORWARD chain to DROP, blocking kernel bridge forwarding; `sudo iptables -P FORWARD ACCEPT` to fix ### Docker Container Stop Delay - **[Docker Container Stop Delay](./docker-container-stop-delay.md)** - Some containers take ~5s to stop because they don't handle SIGTERM (AlpiNet, OstinatoWireshark) diff --git a/.claude/memory/docker-iptables-forward-bridge.md b/.claude/memory/docker-iptables-forward-bridge.md new file mode 100644 index 000000000..2716fe7ab --- /dev/null +++ b/.claude/memory/docker-iptables-forward-bridge.md @@ -0,0 +1,38 @@ +--- +name: docker-iptables-forward-bridge +description: Docker iptables FORWARD DROP blocks kernel bridge forwarding, fix and symptoms +metadata: + type: reference +--- + +Docker sets the iptables `FORWARD` chain default policy to `DROP` when the +Docker daemon starts. This blocks **all** forwarded traffic through Linux +kernel bridges on the host — including `gns3br{N}` bridges created by the +builtin Ethernet Switch (ubridge `brctl`). + +## Symptoms + +- Nodes connected to the switch can send frames into the bridge (visible in + `tcpdump -i gns3br{N}`) but never receive forwarded unicast frames. +- `bridge fdb show` may fail to learn MAC addresses (frames dropped before + the bridge learning path). +- ARP and multicast/broadcast may appear to work because they flood, but + unicast replies never reach the destination. +- OSPF Hello / CDP visible on both sides but ICMP echo reply never returns. +- `ubridge bridge get_stats` shows symmetric IN/OUT counts (relay is fine), + `bridge fdb show` shows learned MACs, `bridge link show` shows `state forwarding` + on all ports — yet unicast still doesn't work. + +## Fix + +Run once per host boot, or make persistent via iptables-persistent / firewall config: + +```bash +sudo iptables -P FORWARD ACCEPT +``` + +## Related + +- [[ethernet-switch-ubridge-brctl-migration]] — the kernel bridge that hits this +- [[gns3-server-linux-only]] — datapath constraint +- [[gns3-ubridge-permission]] — another host-level prerequisite (CAP_NET_ADMIN) diff --git a/gns3server/compute/builtin/nodes/ethernet_switch.py b/gns3server/compute/builtin/nodes/ethernet_switch.py index 3f1fd263a..71ab22977 100644 --- a/gns3server/compute/builtin/nodes/ethernet_switch.py +++ b/gns3server/compute/builtin/nodes/ethernet_switch.py @@ -227,6 +227,8 @@ class EthernetSwitch(BaseNode): return self._bridge_name = self._free_iface("gns3br") 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') await self._ubridge_send(f'brctl vlanfiltering "{self._bridge_name}" on') self._bridge_created = True await self._apply_bridge_proto_if_needed() diff --git a/tests/api/routes/compute/test_ethernet_switch_nodes.py b/tests/api/routes/compute/test_ethernet_switch_nodes.py index 52154c2ae..40ac32ad0 100644 --- a/tests/api/routes/compute/test_ethernet_switch_nodes.py +++ b/tests/api/routes/compute/test_ethernet_switch_nodes.py @@ -85,6 +85,7 @@ class TestEthernetSwitchNodesRoutes: br = node._bridge_name node._ubridge_send.assert_has_calls([ call(f'brctl create "{br}"'), + call(f'link set "{br}" up'), call(f'brctl vlanfiltering "{br}" on'), ])