Dynamic routing was designed so BGP-learned routes are only accepted
when the nexthop lies in a subnet of the logical router port they are
learned on—right for plain IP routing.

With L3 EVPN (IP-VRF / type-5), routes can be learned on a port with a
nexthop outside any configured LRP subnet (indirect nexthop).

find_route_outport(..., force_out_port=false) then fails and the route
never reaches the pipeline.  Pass force_out_port=true for SB
Learned_Route parsing so that the routes can be handled properly.

Add multinode L3 EVPN case with a transit router (non-L2-adjacent
VTEPs).

Reported-at: https://redhat.atlassian.net/browse/FDP-3476
Fixes: 966ca1c919ce ("northd: Handle learned routes.")
Assisted-by: Cursor, with model: claude-4.6-opus-high
Signed-off-by: Han Zhou <[email protected]>
---
 northd/en-learned-route-sync.c |   2 +-
 tests/multinode.at             | 197 +++++++++++++++++++++++++++++++++
 tests/ovn-northd.at            |   7 +-
 3 files changed, 202 insertions(+), 4 deletions(-)

diff --git a/northd/en-learned-route-sync.c b/northd/en-learned-route-sync.c
index a1bfa13ae172..4f7a12a28fdc 100644
--- a/northd/en-learned-route-sync.c
+++ b/northd/en-learned-route-sync.c
@@ -189,7 +189,7 @@ parse_route_from_sbrec_route(struct hmap *parsed_routes_out,
     if (!find_route_outport(lr_ports, route->logical_port->logical_port,
                             "static route", route->ip_prefix, route->nexthop,
                             IN6_IS_ADDR_V4MAPPED(nexthop),
-                            false,
+                            true,
                             &out_port, &lrp_addr_s)) {
         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
         VLOG_WARN_RL(&rl, "could not find output port %s for learned route "
diff --git a/tests/multinode.at b/tests/multinode.at
index 6b9614126e26..bc1eb7a7c8e0 100644
--- a/tests/multinode.at
+++ b/tests/multinode.at
@@ -4580,6 +4580,203 @@ fe80::/64 dev lo-wl-10 proto kernel metric 256 pref 
medium])
 
 AT_CLEANUP
 
+AT_SETUP([ovn multinode bgp L3 EVPN with intermediate router - VTEPs not L2 
adjacent])
+check_fake_multinode_setup
+cleanup_multinode_resources
+CHECK_VRF()
+
+gw=ovn-gw-1
+vni=10
+ext_bgp_ip_gw1=42.10.10.11
+router_ext_ip=42.10.10.1
+ext_bgp_ip6_gw1=42:10:10::11
+ext_bgp_mac_gw1=00:00:01:00:00:$vni
+host_bgp_ip_gw1=42.10.20.12
+router_host_ip=42.10.20.1
+host_bgp_ip6_gw1=42:10:20::12
+host_bgp_mac_gw1=00:00:00:01:00:$vni
+nat_ip_gw1=42.10.20.13
+nat_ip6_gw1=42:10:20::13
+lb_ip_gw1=42.10.20.14
+lb_ip6_gw1=42:10:20::14
+ext_as=4200000100
+host_as=4210000000
+router_as=4290000000
+
+check m_as $gw ovs-vsctl add-br br-$gw
+on_exit "m_as $gw ovs-vsctl del-br br-$gw"
+check m_as $gw ip netns add router-ns
+on_exit "m_as $gw ip netns del router-ns"
+check m_as $gw ip link add host-to-r type veth peer name r-host
+on_exit "m_as $gw ip link del host-to-r"
+check m_as $gw ip link set r-host netns router-ns
+check m_as $gw ovs-vsctl add-port br-$gw host-to-r
+check m_as $gw ip link set host-to-r up
+check m_as $gw ip link set br-$gw up
+check m_as $gw ip netns exec router-ns ip link set lo up
+check m_as $gw ip netns exec router-ns ip addr add $router_host_ip/24 dev 
r-host
+check m_as $gw ip netns exec router-ns ip link set r-host up
+
+check m_as $gw ip netns add frr-ns
+on_exit "m_as $gw ip netns del frr-ns"
+check m_as $gw ip link add ext1 type veth peer name ext1-r
+check m_as $gw ip link set ext1 netns frr-ns
+check m_as $gw ip link set ext1-r netns router-ns
+check m_as $gw ip netns exec frr-ns ip link set lo up
+check m_as $gw ip netns exec frr-ns ip addr add $ext_bgp_ip_gw1/24 dev ext1
+check m_as $gw ip netns exec frr-ns ip link set ext1 up
+check m_as $gw ip netns exec router-ns ip addr add $router_ext_ip/24 dev ext1-r
+check m_as $gw ip netns exec router-ns ip link set ext1-r up
+check m_as $gw ip netns exec router-ns sysctl -qw net.ipv4.ip_forward=1
+
+check m_as $gw ovs-vsctl set open . 
external-ids:ovn-bridge-mappings=public:br-ex
+check m_as $gw ovs-vsctl set open . 
external-ids:ovn-evpn-local-ip=$host_bgp_ip_gw1
+check m_as $gw ovs-vsctl set open . external-ids:ovn-evpn-vxlan-ports=4789
+
+# FRR in frr-ns (external EVPN speaker) and router-ns (BGP EVPN transit 
between ext1-r and r-host).
+check m_as $gw sed -i 's/bgpd=no/bgpd=yes/g' /etc/frr/daemons
+check m_as $gw sed -i 's/StartLimitBurst=.*/StartLimitBurst=100/g' 
/usr/lib/systemd/system/frr.service
+check m_as $gw systemctl daemon-reload
+for ns in frr-ns router-ns; do
+    check m_as $gw mkdir -p /etc/frr/$ns
+    check m_as $gw cp -r /etc/frr/daemons /etc/frr/frr.conf /etc/frr/$ns/
+    check m_as $gw rm -f /etc/frr/$ns/vtysh.conf
+    check m_as $gw touch /etc/frr/$ns/vtysh.conf
+done
+check m_as $gw systemctl stop frr
+
+check m_as $gw mkdir -p /run/frr/frr-ns /run/frr/router-ns
+if m_as $gw getent passwd frr > /dev/null; then
+    check m_as $gw chown -R frr:frr /run/frr/frr-ns /run/frr/router-ns
+fi
+for ns in frr-ns router-ns; do
+    for init in /usr/libexec/frr/frrinit.sh /usr/lib/frr/frrinit.sh; do
+        if m_as $gw test -x "$init"; then
+            m_as $gw ip netns exec $ns "$init" start $ns
+            break
+        fi
+    done
+done
+on_exit "m_as $gw ip netns exec frr-ns /usr/libexec/frr/frrinit.sh stop frr-ns"
+on_exit "m_as $gw ip netns exec router-ns /usr/libexec/frr/frrinit.sh stop 
router-ns"
+check m_as $gw systemctl start frr
+m_as $gw vtysh -c 'configure' -c "no router bgp $router_as" -c 'exit'
+
+m_config_external_frr_router_l3 $gw $ext_as $ext_bgp_ip_gw1 $ext_bgp_ip_gw1/24 
$ext_bgp_ip6_gw1/64 $ext_bgp_mac_gw1 $vni
+
+router_flags=$(m_frr_ns_flags router-ns)
+echo "configure
+ip prefix-list accept-all seq 5 permit any
+ipv6 prefix-list accept-all seq 5 permit any
+router bgp $router_as
+ bgp router-id $router_ext_ip
+ no bgp ebgp-requires-policy
+ neighbor ext1-r interface remote-as $ext_as
+ neighbor ext1-r passive
+ neighbor r-host interface remote-as $host_as
+ address-family ipv4 unicast
+  neighbor ext1-r prefix-list accept-all in
+  neighbor r-host prefix-list accept-all in
+  redistribute connected
+ exit-address-family
+ address-family l2vpn evpn
+  neighbor ext1-r activate
+  neighbor r-host activate
+  advertise-all-vni
+ exit-address-family
+exit
+end
+" | podman exec -i $gw ip netns exec router-ns vtysh $router_flags
+
+check m_as $gw ip addr add $host_bgp_ip_gw1/24 dev br-$gw
+check m_as $gw ip link set br-$gw up
+
+check multinode_nbctl                                                          
    \
+    -- lr-add lr                                                               
    \
+      -- set logical_router lr options:dynamic-routing=true                    
    \
+                               options:dynamic-routing-vrf-id=$vni             
    \
+      -- lrp-add lr lr-gw1 $host_bgp_mac_gw1 $host_bgp_ip_gw1/24 
$host_bgp_ip6_gw1/64 \
+        -- lrp-set-gateway-chassis lr-gw1 $gw 10                               
   \
+        -- lrp-set-options lr-gw1 dynamic-routing-redistribute=nat,lb          
   \
+      -- lrp-add lr lr-int1 00:00:00:00:01:02 30.0.1.1/24 30:1::1/64           
    \
+        -- lrp-set-options lr-int1 dynamic-routing-redistribute=connected      
   \
+    -- ls-add ls                                                               
    \
+      -- lsp-add-localnet-port ls ls-ln public                                 
    \
+      -- lsp-add-router-port ls ls-lr-gw1 lr-gw1                               
    \
+    -- ls-add ls-int1                                                          
    \
+      -- lsp-add-router-port ls-int1 ls-int1-lr lr-int1
+check multinode_nbctl                                                          
    \
+    -- lsp-add ls-int1 w1                                                      
    \
+      -- lsp-set-addresses w1 "00:00:00:00:00:01 30.0.1.11 30:1::11"           
    \
+    -- lr-nat-add lr dnat_and_snat $nat_ip_gw1 30.0.1.11 w1 00:00:00:00:01:11  
    \
+    -- lr-nat-add lr dnat_and_snat $nat_ip6_gw1 30:1::11 w1 00:00:00:00:01:11  
    \
+    -- lb-add lb1 $lb_ip_gw1 30.0.1.11                                         
   \
+    -- lb-add lb2 $lb_ip6_gw1 30:1::11                                         
   \
+    -- lr-lb-add lr lb1                                                        
    \
+    -- lr-lb-add lr lb2
+check m_as $gw /data/create_fake_vm.sh w1 w1 \
+    00:00:00:00:00:01 1500 30.0.1.11 24 30.0.1.1 30:1::11/64 30:1::1
+m_wait_for_ports_up
+check multinode_nbctl --wait=hv sync
+check multinode_nbctl set logical_switch ls                                    
    \
+    other_config:dynamic-routing-vni=$vni                                      
    \
+    other_config:dynamic-routing-bridge-ifname=br-$vni                         
    \
+    other_config:dynamic-routing-vxlan-ifname=vxlan-$vni                       
  \
+    other_config:dynamic-routing-advertise-ifname=lo-$vni                      
  \
+    other_config:dynamic-routing-redistribute=fdb,ip
+check multinode_nbctl --wait=hv sync
+check m_as $gw ovs-ofctl add-flow br-$gw 
"priority=100,ip,nw_dst=$host_bgp_ip_gw1,udp,tp_dst=4789,actions=LOCAL"
+OVS_WAIT_UNTIL([m_as ovn-gw-1 test -d /sys/class/net/vxlan_sys_4789])
+m_as $gw vtysh -c 'configure' -c 'no router bgp 4290000000' -c 'no router bgp 
4210000000' -c 'exit'
+m_config_host_frr_router_l3 $gw $host_as $host_bgp_ip_gw1 $host_bgp_ip_gw1/24 
$host_bgp_mac_gw1 $vni
+OVS_WAIT_UNTIL([m_as ovn-gw-1 vtysh -c 'show bgp neighbors' | grep -qE 
'Connections established 1'])
+
+check m_as $gw ip link add name lo-wl-$vni type dummy
+on_exit "m_as $gw ip link del lo-wl-$vni"
+check m_as $gw ip link set lo-wl-$vni address 00:01:01:01:01:$vni
+check m_as $gw ip link set lo-wl-$vni master vrf-$vni
+check m_as $gw ip addr add dev lo-wl-$vni 77.77.1.$vni/32
+check m_as $gw ip -6 addr add dev lo-wl-$vni 77:77::1:$vni/64 nodad
+check m_as $gw ip link set lo-wl-$vni up
+
+# Extra fabric in frr-ns vrf: 99.99.99.99/32, 88.88.0.0/24 + fabric-wl-ns 
workload.
+check m_as $gw ip netns exec frr-ns ip link add lo-99 type dummy
+on_exit "m_as $gw ip netns exec frr-ns ip link del lo-99"
+check m_as $gw ip netns exec frr-ns ip link set lo-99 master vrf-$vni
+check m_as $gw ip netns exec frr-ns ip addr add 99.99.99.99/32 dev lo-99
+check m_as $gw ip netns exec frr-ns ip link set lo-99 up
+check m_as $gw ip netns exec frr-ns sysctl -qw net.ipv4.ip_forward=1
+check m_as $gw ip netns add fabric-wl-ns
+on_exit "m_as $gw ip netns del fabric-wl-ns"
+check m_as $gw ip netns exec frr-ns ip link add br-fabric-wl type bridge
+check m_as $gw ip netns exec frr-ns ip link set br-fabric-wl master vrf-$vni
+check m_as $gw ip netns exec frr-ns ip addr add 88.88.0.1/24 dev br-fabric-wl
+check m_as $gw ip netns exec frr-ns ip link set br-fabric-wl up
+check m_as $gw ip netns exec frr-ns ip link add fabric-wl type veth peer name 
fabric-wl-r
+check m_as $gw ip netns exec frr-ns ip link set fabric-wl-r master br-fabric-wl
+check m_as $gw ip netns exec frr-ns ip link set fabric-wl-r up
+check m_as $gw ip netns exec frr-ns ip link set fabric-wl netns fabric-wl-ns
+check m_as $gw ip netns exec fabric-wl-ns ip link set lo up
+check m_as $gw ip netns exec fabric-wl-ns ip addr add 88.88.0.10/24 dev 
fabric-wl
+check m_as $gw ip netns exec fabric-wl-ns ip link set fabric-wl up
+check m_as $gw ip netns exec fabric-wl-ns ip route add default via 88.88.0.1
+
+OVS_WAIT_FOR_OUTPUT_UNQUOTED([m_as ovn-gw-1 ovn-appctl evpn/remote-vtep-list | 
sort], [0], [dnl
+IP: $ext_bgp_ip_gw1, port: 4789, vni: 10
+])
+
+m_wait_row_count Learned_Route 1 ip_prefix=88.88.0.0/24
+
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec frr-ns ip route show vrf vrf-$vni 
| grep -q 30.0.1])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec frr-ns ip vrf exec vrf-$vni ping   
 -c1 30.0.1.11])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec frr-ns ip vrf exec vrf-$vni ping 
-6 -c1 30:1::11])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec frr-ns ip vrf exec vrf-$vni ping   
 -c1 $nat_ip_gw1])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec frr-ns ip vrf exec vrf-$vni ping   
 -c1 $lb_ip_gw1])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec fabric-wl-ns ping -c1 30.0.1.11])
+OVS_WAIT_UNTIL([m_as ovn-gw-1 ip netns exec frr-ns ip vrf exec vrf-$vni ping 
-I 99.99.99.99 -c1 30.0.1.11])
+
+AT_CLEANUP
+
 AT_SETUP([redirect-bridged to non-gw destination switch port])
 
 check_fake_multinode_setup
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 2ccf7a196999..3e7a6f7f8085 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -15730,7 +15730,7 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | 
ovn_strip_lflows], [0], [dnl
 ])
 
 # Learn a route to 2001:db8:2::/64 via 2001:db8:ffff::20 learned on lr0-sw1.
-# This is not reachable so will not produce a lflow.
+# Nexthop is off-LRP; northd emits a flow using the first IPv6 on lr0-sw1.
 check_uuid ovn-sbctl create Learned_Route \
     datapath=$datapath                    \
     logical_port=$sw1                     \
@@ -15746,6 +15746,7 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | 
ovn_strip_lflows], [0], [dnl
   table=??(lr_in_ip_routing   ), priority=194  , match=(reg7 == 0 && ip4.dst 
== 172.16.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 10.0.0.11; reg5 
= 10.0.0.1; eth.src = 00:00:00:00:ff:01; outport = "lr0-sw0"; flags.loopback = 
1; reg9[[9]] = 1; next;)
   table=??(lr_in_ip_routing   ), priority=196  , match=(reg7 == 0 && ip4.dst 
== 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 10.0.0.10; reg5 
= 10.0.0.1; eth.src = 00:00:00:00:ff:01; outport = "lr0-sw0"; flags.loopback = 
1; reg9[[9]] = 1; next;)
   table=??(lr_in_ip_routing   ), priority=198  , match=(ip4.dst == 
10.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg5 = 
10.0.0.1; eth.src = 00:00:00:00:ff:01; outport = "lr0-sw0"; flags.loopback = 1; 
reg9[[9]] = 1; next;)
+  table=??(lr_in_ip_routing   ), priority=514  , match=(reg7 == 0 && ip6.dst 
== 2001:db8:2::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = 
2001:db8:ffff::20; xxreg1 = 2001:db8::1; eth.src = 00:00:00:00:ff:02; outport = 
"lr0-sw1"; flags.loopback = 1; reg9[[9]] = 0; next;)
   table=??(lr_in_ip_routing   ), priority=516  , match=(reg7 == 0 && ip6.dst 
== 2001:db8:1::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = 
2001:db8::10; xxreg1 = 2001:db8::1; eth.src = 00:00:00:00:ff:02; outport = 
"lr0-sw1"; flags.loopback = 1; reg9[[9]] = 0; next;)
   table=??(lr_in_ip_routing   ), priority=518  , match=(inport == "lr0-sw0" && 
ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; 
xxreg1 = fe80::200:ff:fe00:ff01; eth.src = 00:00:00:00:ff:01; outport = 
"lr0-sw0"; flags.loopback = 1; reg9[[9]] = 0; next;)
   table=??(lr_in_ip_routing   ), priority=518  , match=(inport == "lr0-sw1" && 
ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; 
xxreg1 = fe80::200:ff:fe00:ff02; eth.src = 00:00:00:00:ff:02; outport = 
"lr0-sw1"; flags.loopback = 1; reg9[[9]] = 0; next;)
@@ -15753,8 +15754,8 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | 
ovn_strip_lflows], [0], [dnl
 ])
 
 # If we now add 2001:db8:ffff::1/64 as an additional network to lr0-sw1 we
-# will get another connected route and the previous received route will be
-# active.
+# will get another connected route and the previous logical flow for the
+# route will be updated with this new source IP.
 check ovn-nbctl --wait=sb set Logical_Router_Port lr0-sw1 \
     networks="\"2001:db8::1/64\" \"2001:db8:ffff::1/64\""
 check_row_count Advertised_Route 5
-- 
2.38.1

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

Reply via email to