When LRP is configured with multiple IPs we could have MAC Bindings with IP addresses corresponding to any of the LRP subnets. However, the refresh mechanism would just choose the first IP as source. That could cause confusion for the switches along the way and it could also be dropped along the way. Make sure we always select the right IP. The linear lookup shouldn't cause any performance issues as in the practice there isn't too many IPs per LRP.
Reported-at: https://redhat.atlassian.net/browse/FDP-3784 Fixes: 1e4d4409f391 ("controller: Send ARP/ND for stale mac_bindings entries.") Assisted-by: Claude Opus 4.6, Claude Code Signed-off-by: Ales Musil <[email protected]> --- controller/mac-cache.c | 28 +++++++--- tests/ovn.at | 113 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 6 deletions(-) diff --git a/controller/mac-cache.c b/controller/mac-cache.c index bdf35eeb7..108d93f97 100644 --- a/controller/mac-cache.c +++ b/controller/mac-cache.c @@ -920,13 +920,29 @@ mac_binding_probe_stats_run(struct vector *stats_vec, uint64_t *req_delay, continue; } - bool is_mb_v4 = IN6_IS_ADDR_V4MAPPED(&mb->data.ip); - if ((is_mb_v4 && laddr.n_ipv4_addrs) - || (!is_mb_v4 && laddr.n_ipv6_addrs)) { - struct in6_addr local = - is_mb_v4 ? in6_addr_mapped_ipv4(laddr.ipv4_addrs[0].addr) - : laddr.ipv6_addrs[0].addr; + struct in6_addr local = in6addr_any; + if (IN6_IS_ADDR_V4MAPPED(&mb->data.ip)) { + ovs_be32 ip4 = in6_addr_get_mapped_ipv4(&mb->data.ip); + for (size_t i = 0; i < laddr.n_ipv4_addrs; i++) { + struct ipv4_netaddr address = laddr.ipv4_addrs[i]; + if (address.network == (ip4 & address.mask)) { + local = in6_addr_mapped_ipv4(address.addr); + break; + } + } + } else { + for (size_t i = 0; i < laddr.n_ipv6_addrs; i++) { + struct ipv6_netaddr address = laddr.ipv6_addrs[i]; + struct in6_addr neigh_prefix = + ipv6_addr_bitand(&mb->data.ip, &address.mask); + if (ipv6_addr_equals(&address.network, &neigh_prefix)) { + local = address.addr; + break; + } + } + } + if (!ipv6_addr_equals(&local, &in6addr_any)) { mac_binding_update_log("Sending ARP/ND request for active", &mb->data, true, threshold, stats->idle_age_ms, since_updated_ms); diff --git a/tests/ovn.at b/tests/ovn.at index 2914d0e64..cd849f4db 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -37258,6 +37258,119 @@ OVN_CLEANUP([hv1]) AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([MAC binding aging - probing multi-subnet source IP]) +AT_SKIP_IF([test $HAVE_SCAPY = no]) +ovn_start + +aging_th=10 +net_add n1 +sim_add hv1 +as hv1 +check ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovn-appctl -t ovn-controller vlog/set mac_cache:file:dbg pinctrl:file:dbg + +check ovn-nbctl \ + -- ls-add ls1 \ + -- lr-add lr \ + -- set logical_router lr options:mac_binding_age_threshold=$aging_th \ + -- lrp-add lr lr-ls1 00:00:00:00:10:00 10.10.10.1/24 42.42.42.1/24 \ + fd11::1/64 fd12::1/64 \ + -- lsp-add-router-port ls1 ls1-lr lr-ls1 \ + -- lsp-add ls1 vif1 \ + -- lsp-set-addresses vif1 "unknown" + +check ovs-vsctl \ + -- add-port br-int vif1 \ + -- set interface vif1 external-ids:iface-id=vif1 \ + options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap + +OVN_POPULATE_ARP +wait_for_ports_up +check ovn-nbctl --wait=hv sync + +# Wait for pinctrl thread to be connected. +OVS_WAIT_UNTIL([grep pinctrl hv1/ovn-controller.log | grep -q connected]) + +# Create MAC bindings in both IPv4 subnets. +send_garp hv1 vif1 2 00:00:00:00:10:1a ff:ff:ff:ff:ff:ff 10.10.10.100 10.10.10.100 +wait_row_count mac_binding 1 ip="10.10.10.100" logical_port="lr-ls1" + +send_garp hv1 vif1 2 00:00:00:00:10:1b ff:ff:ff:ff:ff:ff 42.42.42.253 42.42.42.253 +wait_row_count mac_binding 1 ip="42.42.42.253" logical_port="lr-ls1" + +# Create MAC bindings in both IPv6 subnets. +send_na hv1 vif1 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1 +wait_row_count mac_binding 1 ip=\"fd11::64\" logical_port=\"lr-ls1\" + +send_na hv1 vif1 00:00:00:00:10:1b 00:00:00:00:10:00 fd12::64 fd12::1 +wait_row_count mac_binding 1 ip=\"fd12::64\" logical_port=\"lr-ls1\" + +# Record UUIDs for all MAC bindings. +uuid_v4_1=$(fetch_column Mac_Binding _uuid ip=10.10.10.100) +uuid_v4_2=$(fetch_column Mac_Binding _uuid ip=42.42.42.253) +uuid_v6_1=$(fetch_column Mac_Binding _uuid ip=\"fd11::64\") +uuid_v6_2=$(fetch_column Mac_Binding _uuid ip=\"fd12::64\") + +# Send IPv4 and IPv6 UDP traffic to refresh entries in OFTABLE_MAC_BINDING. +send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:1a 42.42.42.253 10.10.10.100 +send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:1b 10.10.10.100 42.42.42.253 +send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:1a fd12::64 fd11::64 +send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:1b fd11::64 fd12::64 + +OVS_WAIT_UNTIL([$(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | \ + sed 's/reg15=0x.,metadata=0x./reg15=<cleared>,metadata=<cleared>/g' | \ + grep -q "reg0=0xa0a0a64,reg15=<cleared>,metadata=<cleared> actions=mod_dl_dst:00:00:00:00:10:1a")]) +OVS_WAIT_UNTIL([$(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_BINDING | \ + sed 's/reg15=0x.,metadata=0x./reg15=<cleared>,metadata=<cleared>/g' | \ + grep -q "reg0=0x2a2a2afd,reg15=<cleared>,metadata=<cleared> actions=mod_dl_dst:00:00:00:00:10:1b")]) + +# Wait until all entries in OFTABLE_MAC_CACHE_USE are stale. +OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | \ + awk '/nw_src=10.10.10.100/{print substr($6,10,1)}') -ge $((aging_th/2))]) +OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | \ + awk '/nw_src=42.42.42.253/{print substr($6,10,1)}') -ge $((aging_th/2))]) +OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | \ + awk '/ipv6_src=fd11::64/{print substr($6,10,1)}') -ge $((aging_th/2))]) +OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=OFTABLE_MAC_CACHE_USE | \ + awk '/ipv6_src=fd12::64/{print substr($6,10,1)}') -ge $((aging_th/2))]) + +# Send traffic to trigger probing for all entries. +send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:1a 42.42.42.253 10.10.10.100 +send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:1b 10.10.10.100 42.42.42.253 +send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:1a fd12::64 fd11::64 +send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:1b fd11::64 fd12::64 + +# Verify ARP probes use the correct source IPs from matching subnets. +# ARP for 10.10.10.100 must use source IP 10.10.10.1 (first subnet). +dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 10.10.10.1 10.10.10.100 00:00:00:00:00:00 > expected +# ARP for 42.42.42.253 must use source IP 42.42.42.1 (second subnet). +dump_arp 1 00:00:00:00:10:00 ff:ff:ff:ff:ff:ff 42.42.42.1 42.42.42.253 00:00:00:00:00:00 >> expected +OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected]) + +# Verify NS probes use the correct source IPs from matching subnets. +# NS for fd11::64 must use source IP fd11::1 (first IPv6 subnet). +dump_ns 33:33:ff:00:00:64 00:00:00:00:10:00 ff02::1:ff00:64 fd11::1 fd11::64 > expected_v6 +# NS for fd12::64 must use source IP fd12::1 (second IPv6 subnet). +dump_ns 33:33:ff:00:00:64 00:00:00:00:10:00 ff02::1:ff00:64 fd12::1 fd12::64 >> expected_v6 +OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_v6]) + +# Send ARP/NA replies and check MAC_Binding UUIDs remain consistent. +send_garp hv1 vif1 2 00:00:00:00:10:1a 00:00:00:00:10:00 10.10.10.100 10.10.10.1 +send_garp hv1 vif1 2 00:00:00:00:10:1b 00:00:00:00:10:00 42.42.42.253 42.42.42.1 +send_na hv1 vif1 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1 +send_na hv1 vif1 00:00:00:00:10:1b 00:00:00:00:10:00 fd12::64 fd12::1 + +AT_CHECK([test "$(fetch_column Mac_Binding _uuid ip=10.10.10.100)" = "$uuid_v4_1"]) +AT_CHECK([test "$(fetch_column Mac_Binding _uuid ip=42.42.42.253)" = "$uuid_v4_2"]) +AT_CHECK([test "$(fetch_column Mac_Binding _uuid ip=\"fd11::64\")" = "$uuid_v6_1"]) +AT_CHECK([test "$(fetch_column Mac_Binding _uuid ip=\"fd12::64\")" = "$uuid_v6_2"]) + +OVN_CLEANUP([hv1]) +AT_CLEANUP +]) + OVN_FOR_EACH_NORTHD([ AT_SETUP([MAC binding aging - probing distributed GW router]) AT_SKIP_IF([test $HAVE_SCAPY = no]) -- 2.54.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
