Ports that do not participate in STP or RSTP while they are enabled
on a bridge can easily become not floodable, because the bundle update
checks the STP/RSTP config on the bridge and not on the port and these
ports have their default forwarding disabled state.  So, every time
a bundle update runs, all bundles that do not participate in STP/RSTP
are marked as not floodable, this means all the internal ports,
including the bridge port, become non-floodable, as they do not
participate by design.

The issue was introduced quite some time ago, but became much easier
to hit since the fix that made bundle update run on every
reconfiguration to make sure the floodable flags are updated.

Reported-at: 
https://mail.openvswitch.org/pipermail/ovs-discuss/2026-September/054022.html
Reported-by: Svenne Krap <[email protected]>
Fixes: 4b5f19962adc ("stp,rstp: disable learning and forwarding in STP/RSTP 
disabled state.")
Fixes: 332ca1dad732 ("ofproto-dpif: Fix bundle floodable flag when disabling 
STP/RSTP.")
Signed-off-by: Ilya Maximets <[email protected]>
---
 ofproto/ofproto-dpif.c |  8 ++--
 tests/ofproto-dpif.at  | 92 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 96 insertions(+), 4 deletions(-)

diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 0f0f71d14..443de8708 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -3376,8 +3376,8 @@ bundle_update(struct ofbundle *bundle)
     LIST_FOR_EACH (port, bundle_node, &bundle->ports) {
         if (port->up.pp.config & OFPUTIL_PC_NO_FLOOD
             || netdev_get_pt_mode(port->up.netdev) == NETDEV_PT_LEGACY_L3
-            || (bundle->ofproto->stp && !stp_forward_in_state(port->stp_state))
-            || (bundle->ofproto->rstp && 
!rstp_forward_in_state(port->rstp_state))) {
+            || (port->stp_port && !stp_forward_in_state(port->stp_state))
+            || (port->rstp_port && !rstp_forward_in_state(port->rstp_state))) {
             bundle->floodable = false;
             break;
         }
@@ -3425,8 +3425,8 @@ bundle_add_port(struct ofbundle *bundle, ofp_port_t 
ofp_port,
         ovs_list_push_back(&bundle->ports, &port->bundle_node);
         if (port->up.pp.config & OFPUTIL_PC_NO_FLOOD
             || netdev_get_pt_mode(port->up.netdev) == NETDEV_PT_LEGACY_L3
-            || (bundle->ofproto->stp && !stp_forward_in_state(port->stp_state))
-            || (bundle->ofproto->rstp && 
!rstp_forward_in_state(port->rstp_state))) {
+            || (port->stp_port && !stp_forward_in_state(port->stp_state))
+            || (port->rstp_port && !rstp_forward_in_state(port->rstp_state))) {
             bundle->floodable = false;
         }
     }
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index efb36e058..58f395ab3 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -14037,3 +14037,95 @@ Datapath actions: 100,1
 
 OVS_VSWITCHD_STOP
 AT_CLEANUP
+
+AT_SETUP([ofproto-dpif - bundle floodable flag on internal port - STP])
+AT_KEYWORDS([stp bundle floodable])
+OVS_VSWITCHD_START
+
+add_of_ports br0 1 2 3
+AT_CHECK([ovs-vsctl set interface p1 type=internal])
+
+AT_CHECK([ovs-vsctl set bridge br0 stp_enable=true])
+AT_CHECK([ovs-ofctl add-flow br0 action=NORMAL])
+
+AT_CHECK([ovs-appctl time/stop])
+
+# Give time for STP to synchronize.
+AT_CHECK([ovs-appctl time/warp 30000 3000], [0], [ignore])
+
+dnl Verify we are in forwarding state.
+AT_CHECK([ovs-appctl stp/show br0 | grep "p2.*forwarding"], [0], [ignore])
+AT_CHECK([ovs-appctl stp/show br0 | grep "p3.*forwarding"], [0], [ignore])
+
+dnl Send a broadcast packet on p2, verify it floods to the bridge port, the
+dnl internal p1 and the dummy p3.
+AT_CHECK([ovs-appctl ofproto/trace br0 \
+          'in_port=p2,eth_src=00:00:00:00:00:01,eth_dst=ff:ff:ff:ff:ff:ff' \
+          -generate --names], [0], [stdout])
+AT_CHECK([tail -1 stdout | sed 's/Datapath actions: //' \
+            | tr "," "\n" | sort | tr "\n" ","], [0], [br0,p1,p3,])
+
+dnl Delete p3 to trigger reconfiguration.
+AT_CHECK([ovs-vsctl del-port p3])
+
+dnl Verify we are still in forwarding state.
+AT_CHECK([ovs-appctl time/warp 30000 3000], [0], [ignore])
+AT_CHECK([ovs-appctl stp/show br0 | grep "p2.*forwarding"], [0], [ignore])
+
+dnl Send a broadcast packet on p2, verify it still floods to both the bridge
+dnl port and the p1.
+AT_CHECK([ovs-appctl ofproto/trace br0 \
+          'in_port=p2,eth_src=00:00:00:00:00:01,eth_dst=ff:ff:ff:ff:ff:ff' \
+          -generate --names], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0], [dnl
+Datapath actions: br0,p1
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif - bundle floodable flag on internal port - RSTP])
+AT_KEYWORDS([rstp bundle floodable])
+OVS_VSWITCHD_START
+
+add_of_ports br0 1 2 3
+AT_CHECK([ovs-vsctl set interface p1 type=internal])
+
+AT_CHECK([ovs-vsctl set bridge br0 rstp_enable=true])
+AT_CHECK([ovs-ofctl add-flow br0 action=NORMAL])
+
+AT_CHECK([ovs-appctl time/stop])
+
+# Give time for RSTP to synchronize.
+AT_CHECK([ovs-appctl time/warp 5000 500], [0], [ignore])
+
+dnl Verify we are in forwarding state.
+AT_CHECK([ovs-appctl rstp/show br0 | grep "p2.*Designated"], [0], [ignore])
+AT_CHECK([ovs-appctl rstp/show br0 | grep "p3.*Designated"], [0], [ignore])
+
+dnl Send a broadcast packet on p2, verify it floods to the bridge port, the
+dnl internal p1 and the dummy p3.
+AT_CHECK([ovs-appctl ofproto/trace br0 \
+          'in_port=p2,eth_src=00:00:00:00:00:01,eth_dst=ff:ff:ff:ff:ff:ff' \
+          -generate --names], [0], [stdout])
+AT_CHECK([tail -1 stdout | sed 's/Datapath actions: //' \
+            | tr "," "\n" | sort | tr "\n" ","], [0], [br0,p1,p3,])
+
+dnl Delete p3 to trigger reconfiguration.
+AT_CHECK([ovs-vsctl del-port p3])
+
+dnl Verify we are still in forwarding state.
+AT_CHECK([ovs-appctl time/warp 5000 500], [0], [ignore])
+AT_CHECK([ovs-appctl rstp/show br0 | grep "p2.*Designated"], [0], [ignore])
+
+dnl Send a broadcast packet on p2, verify it still floods to both the bridge
+dnl port and the p1.
+AT_CHECK([ovs-appctl ofproto/trace br0 \
+          'in_port=p2,eth_src=00:00:00:00:00:01,eth_dst=ff:ff:ff:ff:ff:ff' \
+          -generate --names], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0], [dnl
+Datapath actions: br0,p1
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
-- 
2.55.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to