On Tue, May 26, 2026 at 3:24 PM Lorenzo Bianconi <
[email protected]> wrote:

>
>
> 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]>
>>
>
>
> Acked-by: Lorenzo Bianconi <[email protected]>
>
>
>> ---
>> v2: Rebase on top of latest main.
>>     Update the test according to suggestion from Dumitru.
>>     Replace AT_CHECK with check_column.
>> ---
>>  controller/mac-cache.c |  28 +++++++---
>>  tests/ovn.at           | 117 +++++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 139 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 0c3d4199b..a2a971034 100644
>> --- a/tests/ovn.at
>> +++ b/tests/ovn.at
>> @@ -37250,6 +37250,123 @@ 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.
>> +# Use different src MACs than the ones from GARP/NA to avoid resetting
>> +# OFTABLE_MAC_CACHE_USE idle_age (which would keep the timestamp fresh
>> +# and suppress probing via the cooldown mechanism).
>> +send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 42.42.42.253
>> 10.10.10.100
>> +send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2b 10.10.10.100
>> 42.42.42.253
>> +send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a fd12::64 fd11::64
>> +send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2b 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
>> <http://10.10.10.100/%7Bprint> 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
>> <http://42.42.42.253/%7Bprint> 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.
>> +# Again use different src MACs to only hit OFTABLE_MAC_BINDING (not
>> MAC_CACHE_USE).
>> +send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 42.42.42.253
>> 10.10.10.100
>> +send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2b 10.10.10.100
>> 42.42.42.253
>> +send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a fd12::64 fd11::64
>> +send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2b 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
>> +
>> +check_column "$uuid_v4_1" Mac_Binding _uuid ip=10.10.10.100
>> +check_column "$uuid_v4_2" Mac_Binding _uuid ip=42.42.42.253
>> +check_column "$uuid_v6_1" Mac_Binding _uuid ip=\"fd11::64\"
>> +check_column "$uuid_v6_2" Mac_Binding _uuid ip=\"fd12::64\"
>> +
>> +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
>>
>>
Thank you Dumitru and Lorenzo,
applied to main and backported all the way down to 24.03.

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

Reply via email to