On Thu, Aug 27, 2026 at 9:44 PM Jacob Tanenbaum <[email protected]> wrote:

>
>
> On Thu, Aug 27, 2026 at 9:29 AM Ales Musil via dev <
> [email protected]> wrote:
>
>> On Thu, Aug 27, 2026 at 3:12 PM Ales Musil <[email protected]> wrote:
>>
>> > The re-ARP probes were using unicast to check if the host is still
>> > alive to prevent the entry from aging out. The unicast works fine
>> > and prevents unnecessary floods, however, there is a case when the
>> > MAC address could have been changed without OVN learning about that.
>> > In that case the unicast won't be ever responded to and the only way
>> > to refresh that entry is to wait for it to age out. Send broadcast
>> > after two unicast attempts with the timing that gives us usually
>> > 2 unicast probes and 2 broadcast, we will age out if neither of them
>> > is responded to.
>> >
>> > Fixes: 59c7361e2613 ("pinctrl: Use unicast for MAC binding ARP probe.")
>> > Reported-at: https://redhat.atlassian.net/browse/FDP-4224
>> > Assisted-by: Claude Opus 4.6, OpenCode
>> > Signed-off-by: Ales Musil <[email protected]>
>> > ---
>> >
>>
>> Ah I noticed a few wrong numbers in the comments.
>> I'll fix that in v2 or during the merge, whichever comes first.
>>
>>
>> >  controller/mac-cache.c |  11 ++++-
>> >  controller/mac-cache.h |   2 +
>> >  tests/ovn.at           | 102 +++++++++++++++++++++++++++++++++++++++++
>> >  3 files changed, 114 insertions(+), 1 deletion(-)
>> >
>> > diff --git a/controller/mac-cache.c b/controller/mac-cache.c
>> > index 8f100a29d..39ab13754 100644
>> > --- a/controller/mac-cache.c
>> > +++ b/controller/mac-cache.c
>> > @@ -34,6 +34,7 @@ VLOG_DEFINE_THIS_MODULE(mac_cache);
>> >  #define BUFFER_QUEUE_DEPTH          4
>> >  #define BUFFERED_PACKETS_TIMEOUT_MS 10000
>> >  #define BUFFERED_PACKETS_LOOKUP_MS  100
>> > +#define ARP_BROADCAST_THRESHOLD     2
>>
>
> Nit: Is the name a little misleading? this threshold applies to both ARP
> and ND.
>

Fair, updated in v2.


>
>
>> >
>> >  static uint32_t
>> >  mac_binding_data_hash(const struct mac_binding_data *mb_data);
>> > @@ -178,6 +179,7 @@ mac_binding_add(struct hmap *map, struct
>> > mac_binding_data mb_data,
>> >      mb->data = mb_data;
>> >      mb->sbrec = smb;
>> >      mb->timestamp = timestamp;
>> > +    mb->arp_attempts = 0;
>> >      mac_binding_update_log("Added", &mb_data, false, NULL, 0, 0);
>> >  }
>> >
>> > @@ -908,6 +910,7 @@ mac_binding_probe_stats_run(struct vector
>> *stats_vec,
>> > uint64_t *req_delay,
>> >                      "Not sending ARP/ND request for recently updated",
>> >                      &mb->data, true, threshold, stats->idle_age_ms,
>> >                      since_updated_ms);
>> > +            mb->arp_attempts = 0;
>> >              continue;
>> >          }
>> >
>> > @@ -954,6 +957,11 @@ mac_binding_probe_stats_run(struct vector
>> *stats_vec,
>> > uint64_t *req_delay,
>> >          }
>> >
>> >          if (!ipv6_addr_equals(&local, &in6addr_any)) {
>> > +            struct eth_addr eth_dst =
>> > +                mb->arp_attempts < ARP_BROADCAST_THRESHOLD
>> > +                ? mb->data.mac
>> > +                : eth_addr_zero;
>> > +
>> >              mac_binding_update_log("Sending ARP/ND request for active",
>> >                                     &mb->data, true, threshold,
>> >                                     stats->idle_age_ms,
>> since_updated_ms);
>> > @@ -961,9 +969,10 @@ mac_binding_probe_stats_run(struct vector
>> *stats_vec,
>> > uint64_t *req_delay,
>> >              send_self_originated_neigh_packet(probe_data->swconn,
>> >
>> sbrec->datapath->tunnel_key,
>> >                                                pb->tunnel_key, laddr.ea,
>> > -                                              mb->data.mac, &local,
>> > +                                              eth_dst, &local,
>> >                                                &mb->data.ip,
>> >                                                OFTABLE_LOCAL_OUTPUT);
>> > +            mb->arp_attempts++;
>> >          }
>> >
>> >          destroy_lport_addresses(&laddr);
>> > diff --git a/controller/mac-cache.h b/controller/mac-cache.h
>> > index 8abea60c7..bf9afaf3a 100644
>> > --- a/controller/mac-cache.h
>> > +++ b/controller/mac-cache.h
>> > @@ -78,6 +78,8 @@ struct mac_binding {
>> >      const struct sbrec_mac_binding *sbrec;
>> >      /* User specified timestamp (in ms) */
>> >      long long timestamp;
>> > +    /* Number of re-ARP attempts for given entry. */
>> > +    size_t arp_attempts;
>> >  };
>> >
>> >  struct fdb_data {
>> > diff --git a/tests/ovn.at b/tests/ovn.at
>> > index a88a077c6..e169fc607 100644
>> > --- a/tests/ovn.at
>> > +++ b/tests/ovn.at
>> > @@ -37563,6 +37563,108 @@ OVN_CLEANUP([hv1])
>> >  AT_CLEANUP
>> >  ])
>> >
>> > +OVN_FOR_EACH_NORTHD([
>> > +AT_SETUP([MAC binding aging - probing unicast to broadcast transition])
>> > +CHECK_SCAPY
>> > +ovn_start
>> > +
>> > +aging_th=5
>> > +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 one IPv4 and one IPv6 MAC binding.
>> > +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_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\"
>> > +
>> > +# The first 3 probes are unicast (arp_attempts 0-2); the entry then
>> falls
>> >
>>
>> Should be 2 and (0-1).
>>
>>
>> > +# back to broadcast ARP / multicast NS.
>> > +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_bcast
>> > +OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_bcast])
>> > +
>> > +dump_ns 33:33:ff:00:00:64 00:00:00:00:10:00 ff02::1:ff00:64 fd11::1
>> > fd11::64 > expected_mcast
>> > +OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_mcast])
>> > +
>> > +dump_arp 1 00:00:00:00:10:00 00:00:00:00:10:1a 10.10.10.1 10.10.10.100
>> > 00:00:00:00:10:1a > expected_ucast_v4
>> > +OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_ucast_v4])
>> > +
>> > +dump_ns 00:00:00:00:10:1a 00:00:00:00:10:00 fd11::64 fd11::1 fd11::64 >
>> > expected_ucast_v6
>> > +OVN_CHECK_PACKETS_CONTAIN([hv1/vif1-tx.pcap], [expected_ucast_v6])
>> > +
>> > +# Verify the reset-to-zero mechanism using a distinct pair of
>> neighbours
>> > +# kept alive (never re-created) for the whole check.  A single entry
>> emits
>> > +# at most ARP_BROADCAST_THRESHOLD (3) unicast probes before falling
>> back
>> > to
>> >
>>
>> Should be (2).
>>
>>
>> > +# broadcast, so a 4th unicast probe proves arp_attempts was reset.
>> > +send_garp hv1 vif1 2 00:00:00:00:10:1b ff:ff:ff:ff:ff:ff 10.10.10.101
>> > 10.10.10.101
>> > +wait_row_count mac_binding 1 ip="10.10.10.101" logical_port="lr-ls1"
>> > +v4_uuid=$(fetch_column Mac_Binding _uuid ip=10.10.10.101)
>> > +
>> > +send_na hv1 vif1 00:00:00:00:10:1b 00:00:00:00:10:00 fd11::65 fd11::1
>> > +wait_row_count mac_binding 1 ip=\"fd11::65\" logical_port=\"lr-ls1\"
>> > +v6_uuid=$(fetch_column Mac_Binding _uuid ip=\"fd11::65\")
>> > +
>> > +# Keep the entries active (Tx towards them) and let a couple of unicast
>> > +# probes go out (arp_attempts reaches 2, below the broadcast
>> threshold).
>> >
>>
>> Should be 1.
>>
>>
>> > +send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 10.10.10.101
>> > 42.42.42.100
>> > +send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a fd11::65
>> fd12::100
>> > +OVS_WAIT_UNTIL([test $(grep -c "Sending ARP/ND.*ip: 10.10.10.101"
>> > hv1/ovn-controller.log) -ge 2])
>> > +OVS_WAIT_UNTIL([test $(grep -c "Sending ARP/ND.*ip: fd11::65"
>> > hv1/ovn-controller.log) -ge 2])
>> > +
>> > +# The neighbours answer, refreshing the rows in place (resetting
>> > +# arp_attempts).  Confirm the timestamps advanced.
>> > +v4_ts=$(fetch_column Mac_Binding timestamp ip=10.10.10.101)
>> > +v6_ts=$(fetch_column Mac_Binding timestamp ip=\"fd11::65\")
>> > +send_garp hv1 vif1 2 00:00:00:00:10:1b 00:00:00:00:10:00 10.10.10.101
>> > 10.10.10.1
>> > +send_na hv1 vif1 00:00:00:00:10:1b 00:00:00:00:10:00 fd11::65 fd11::1
>> > +OVS_WAIT_UNTIL([test $(fetch_column Mac_Binding timestamp
>> > ip=10.10.10.101) -gt $v4_ts])
>> > +OVS_WAIT_UNTIL([test $(fetch_column Mac_Binding timestamp
>> > ip=\"fd11::65\") -gt $v6_ts])
>> > +
>> > +# Probing must restart from unicast: wait for a 4th unicast ARP/NS
>> probe
>> > +# while the rows are still the original ones (same UUID, never
>> > re-created).
>> > +dump_arp 1 00:00:00:00:10:00 00:00:00:00:10:1b 10.10.10.1 10.10.10.101
>> > 00:00:00:00:10:1b > ucast_v4.pkt
>> > +dump_ns 00:00:00:00:10:1b 00:00:00:00:10:00 fd11::65 fd11::1 fd11::65 >
>> > ucast_v6.pkt
>> > +OVS_WAIT_UNTIL([
>> > +    send_udp hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a 10.10.10.101
>> > 42.42.42.100
>> > +    send_udp6 hv1 vif1 00:00:00:00:10:00 00:00:00:00:10:2a fd11::65
>> > fd12::100
>> > +    test "$(fetch_column Mac_Binding _uuid ip=10.10.10.101)" =
>> "$v4_uuid"
>> > && \
>> > +    test "$(fetch_column Mac_Binding _uuid ip=\"fd11::65\")" =
>> "$v6_uuid"
>> > && \
>> > +    test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in"
>> hv1/vif1-tx.pcap
>> > | \
>> > +           grep -Fc "$(cat ucast_v4.pkt)") -ge 4 && \
>> > +    test $($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in"
>> hv1/vif1-tx.pcap
>> > | \
>> > +           grep -Fc "$(cat ucast_v6.pkt)") -ge 4])
>> > +
>> > +OVN_CLEANUP([hv1])
>> > +AT_CLEANUP
>> > +])
>> > +
>> >  OVN_FOR_EACH_NORTHD([
>> >  AT_SETUP([MAC binding aging - probing distributed GW router])
>> >  CHECK_SCAPY
>> > --
>> > 2.55.0
>> >
>> >
>> _______________________________________________
>> dev mailing list
>> [email protected]
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
>>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to