This was a really interesting question, so I spent some time researching it. It turns out the question has been asked previously, and this answer describes the issue:
The root cause is that, stp messages sent correctly from the bridge_slaves, but the rcv routine is only restricted to the init_ns in net/llc/llc_input.c line 166 (linux-source-5.15.0...)
It looks like the same conditional exists in current 6.1.x kernels; see e.g. here.
You can verify this is the problem by leaving your bridges in the global network namespace. I'm not familiar with GNS3 so I set up a test environment using the command line; I ended up with something like this:
In this diagram, sw1-br0
and sw2-br0
are bridge devices, sw1
and sw2
are network namespaces, and everything else are veth
devices. This is largely equivalent to your example (the two bridges are connected by a pair of links), but the bridges live in the global namespace. We attach a namespaced interface to each bridge so that we can test end-to-end connectivity.
I set everything up with this script:
#!/bin/sh
set -ex
for dev in 1 2; do
ns=sw$dev
# create namespace
ip netns add $ns
# create bridge device
ip link add $ns-br0 type bridge stp_state 1
ip link set $ns-br0 up
# create link from namespace to bridge
ip link add $ns-int type veth peer name $ns-ext
# configure internal device
ip link set netns $ns dev $ns-int
ip -n $ns link set up dev $ns-int
ip -n $ns addr add 100.64.10.$(( dev * 10))/24 dev $ns-int
# add external device to bridge
ip link set master $ns-br0 dev $ns-ext
ip link set up dev $ns-ext
done
# create links between bridge devices
for port in port0 port1; do
ip link add sw1-$port type veth peer name sw2-$port
ip link set master sw1-br0 sw1-$port
ip link set master sw2-br0 sw2-$port
done
When this script finishes running, the bridge-to-bridge links are all disabled, while the namespace-to-bridge links are up. This gives us:
$ bridge link | grep br0
5090: sw1-ext@if5091: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw1-br0 state forwarding priority 32 cost 2
5093: sw2-ext@if5094: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw2-br0 state forwarding priority 32 cost 2
5095: sw2-port0@sw1-port0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 master sw2-br0 state disabled priority 32 cost 2
5096: sw1-port0@sw2-port0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 master sw1-br0 state disabled priority 32 cost 2
5097: sw2-port1@sw1-port1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 master sw2-br0 state disabled priority 32 cost 2
5098: sw1-port1@sw2-port1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 master sw1-br0 state disabled priority 32 cost 2
If I bring up the port0
interface:
ip link set sw1-port0 up
ip link set sw2-port0 up
We eventually see:
5090: sw1-ext@if5091: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw1-br0 state forwarding priority 32 cost 2
5093: sw2-ext@if5094: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw2-br0 state forwarding priority 32 cost 2
5095: sw2-port0@sw1-port0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw2-br0 state forwarding priority 32 cost 2
5096: sw1-port0@sw2-port0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw1-br0 state forwarding priority 32 cost 2
5097: sw2-port1@sw1-port1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 master sw2-br0 state disabled priority 32 cost 2
5098: sw1-port1@sw2-port1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 master sw1-br0 state disabled priority 32 cost 2
And when I finally bring up the port1
link and wait for stp to converge:
ip link set sw1-port1 up
ip link set sw2-port1 up
We see:
5090: sw1-ext@if5091: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw1-br0 state forwarding priority 32 cost 2
5093: sw2-ext@if5094: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw2-br0 state forwarding priority 32 cost 2
5095: sw2-port0@sw1-port0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw2-br0 state forwarding priority 32 cost 2
5096: sw1-port0@sw2-port0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw1-br0 state forwarding priority 32 cost 2
5097: sw2-port1@sw1-port1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw2-br0 state blocking priority 32 cost 2
5098: sw1-port1@sw2-port1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master sw1-br0 state forwarding priority 32 cost 2
Here you can see that the bridges have successfully detected a loop and marked one of the ports as blocking
.
We can verify connectivity between the two namespaces:
# ip netns exec sw1 ping -c2 100.64.10.20
PING 100.64.10.20 (100.64.10.20) 56(84) bytes of data.
64 bytes from 100.64.10.20: icmp_seq=1 ttl=64 time=0.052 ms
64 bytes from 100.64.10.20: icmp_seq=2 ttl=64 time=0.067 ms
--- 100.64.10.20 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1004ms
rtt min/avg/max/mdev = 0.052/0.059/0.067/0.007 ms
And by running tcpdump
on any of the links we can verify that there is no loop.