physical_handle_flows_for_lport() handles the forward
dependency (PATCH/EXTERNAL/L3GATEWAY -> localnet) but
not the reverse: when a localnet port is added or
updated, chassisredirect ports on peer router datapaths
whose bridged redirect flows depend on that localnet
port (via put_remote_port_redirect_bridged() ->
get_localnet_port()) are not re-evaluated.
This causes the CR bridged redirect flow in
OFTABLE_LOCAL_OUTPUT to be permanently missing when the
localnet port binding is processed incrementally without
a full recompute.
Fix this by iterating peer router datapaths when a
localnet port is processed and re-evaluating any CR port
bindings found there.
Add a test that deterministically exposes the bug by
pre-creating the OVS patch port so that non_vif_data
does not change when the localnet port binding arrives,
forcing pflow_output to use the incremental path.
Fixes: 3ae8470edc64 ("I-P: Handle runtime data changes for pflow_output
engine.")
Assisted-by: Claude Opus 4.6, Claude Code
Signed-off-by: Dumitru Ceara <[email protected]>
---
NOTE:
- on 25.03 this patch would need some minor changes:
https://github.com/dceara/ovn/commit/54089ca
- that would allow us to also backport b408eedf6d9d
("ovn-controller: Skip type-update check for new port bindings.")
to 25.03
- more context here:
https://mail.openvswitch.org/pipermail/ovs-dev/2026-May/432414.html
---
controller/physical.c | 18 ++++++++
tests/ovn-controller.at | 92 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 110 insertions(+)
diff --git a/controller/physical.c b/controller/physical.c
index 185b798b93..42b42e948b 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -3799,6 +3799,24 @@ physical_handle_flows_for_lport(const struct
sbrec_port_binding *pb,
}
}
+ /* Chassisredirect ports on peer router datapaths may have bridged
+ * redirect flows that depend on this localnet port
+ * (put_remote_port_redirect_bridged() calls get_localnet_port()).
+ * Re-evaluate those CR ports. */
+ if (type == LP_LOCALNET && !removed && ldp) {
+ const struct peer_ports *pp;
+ VECTOR_FOR_EACH_PTR (&ldp->peer_ports, pp) {
+ const struct sbrec_port_binding *cr_pb =
+ lport_get_cr_port(p_ctx->sbrec_port_binding_by_name,
+ pp->remote, NULL);
+ if (cr_pb) {
+ ofctrl_remove_flows(flow_table, &cr_pb->header_.uuid);
+ physical_eval_port_binding(p_ctx, cr_pb, LP_CHASSISREDIRECT,
+ flow_table);
+ }
+ }
+ }
+
if (sbrec_port_binding_is_updated(
pb, SBREC_PORT_BINDING_COL_ADDITIONAL_CHASSIS) || removed) {
physical_multichassis_reprocess(pb, p_ctx, flow_table);
diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
index f9f64d691c..d4d666b699 100644
--- a/tests/ovn-controller.at
+++ b/tests/ovn-controller.at
@@ -1019,6 +1019,98 @@ OVN_CLEANUP([hv1])
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn-controller - localnet port change and chassisredirect bridged
redirect])
+AT_KEYWORDS([ovn-localnet-cr-bridged])
+
+ovn_start
+
+net_add n1
+
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ovn_attach n1 br-phys 192.168.0.1
+
+sim_add hv2
+as hv2
+check ovs-vsctl add-br br-phys
+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ovn_attach n1 br-phys 192.168.0.2
+
+dnl Create the full topology with localnet ports, router,
+dnl gateway chassis, and bridged redirect. Bind VIFs so the
+dnl datapaths are local.
+check ovn-nbctl
\
+ -- ls-add ls1
\
+ -- lsp-add-localnet-port ls1 ln1 phys
\
+ -- set logical_switch_port ln1 tag_request=101
\
+ -- lsp-add ls1 lp1
\
+ -- lsp-set-addresses lp1 "00:00:00:00:00:01 192.168.1.10"
\
+ -- lsp-add ls1 lp2
\
+ -- lsp-set-addresses lp2 "00:00:00:00:00:02 192.168.1.11"
\
+ -- lsp-add-router-port ls1 ls1-to-router router-to-ls1
\
+ -- ls-add ls-underlay
\
+ -- lsp-add-localnet-port ls-underlay ln-underlay phys
\
+ -- set logical_switch_port ln-underlay tag_request=1000
\
+ -- lsp-add-router-port ls-underlay underlay-to-router router-to-underlay
\
+ -- lr-add lr1
\
+ -- lrp-add lr1 router-to-ls1 00:00:01:01:02:03 192.168.1.1/24
\
+ -- lrp-add lr1 router-to-underlay 00:00:01:01:02:07 172.31.0.1/24
\
+ -- lrp-set-gateway-chassis router-to-underlay hv1
\
+ -- lrp-set-redirect-type router-to-underlay bridged
+
+check as hv1 ovs-vsctl add-port br-int vif0 \
+ -- set Interface vif0 external_ids:iface-id=lp1
+check as hv2 ovs-vsctl add-port br-int vif1 \
+ -- set Interface vif1 external_ids:iface-id=lp2
+
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+dnl Verify initial state: hv2 has the CR bridged redirect flow.
+router_dp_key=$(printf "%x" $(fetch_column datapath tunnel_key
external_ids:name=lr1))
+cr_key=$(printf "%x" $(fetch_column port_binding tunnel_key
logical_port=cr-router-to-underlay))
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_LOCAL_OUTPUT | grep
-q "reg15=0x${cr_key},metadata=0x${router_dp_key}"])
+
+dnl Delete the localnet port on ls-underlay.
+check ovn-nbctl --wait=hv lsp-del ln-underlay
+AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_LOCAL_OUTPUT | grep
"reg15=0x${cr_key},metadata=0x${router_dp_key}"], [1])
+
+dnl Pre-create the patch port that the new localnet port
+dnl would need. This way, when the localnet port binding arrives,
+dnl patch_run() sees the patch port already exists and non_vif_data
+dnl does NOT change, forcing pflow_output to handle the
+dnl change incrementally instead of recomputing.
+check as hv2 ovs-vsctl \
+ -- add-port br-int patch-br-int-to-ln-underlay \
+ -- set Interface patch-br-int-to-ln-underlay \
+ type=patch options:peer=patch-ln-underlay-to-br-int
+check as hv2 ovs-vsctl \
+ -- add-port br-phys patch-ln-underlay-to-br-int \
+ -- set Interface patch-ln-underlay-to-br-int \
+ type=patch options:peer=patch-br-int-to-ln-underlay
+check ovn-nbctl --wait=hv sync
+
+dnl Re-add the localnet port. The patch port already exists,
+dnl so non_vif_data should not change, and pflow_output should
+dnl be handled incrementally.
+check as hv2 ovn-appctl -t ovn-controller inc-engine/clear-stats
+check ovn-nbctl --wait=hv \
+ -- lsp-add-localnet-port ls-underlay ln-underlay phys \
+ -- set logical_switch_port ln-underlay tag_request=1000
+
+dnl Verify pflow_output was NOT recomputed.
+check_controller_engine_stats hv2 pflow_output norecompute compute
+
+dnl Verify the CR bridged redirect flow is back.
+OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=OFTABLE_LOCAL_OUTPUT
| grep -q "reg15=0x${cr_key},metadata=0x${router_dp_key}"])
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
+])
+
AT_SETUP([ovn-controller - I-P for address set update: no conjunction])
AT_KEYWORDS([as-i-p])
--
2.54.0
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev