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]>
---
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
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
+# 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
+# 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).
+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