From fee1fc1d5a5475f5516d406a03e443348cd0f06c Mon Sep 17 00:00:00 2001 From: Danielle Ratson Date: Wed, 29 Apr 2026 09:24:04 +0300 Subject: [PATCH 1/2] bridge: Do not suppress ARP probes and DAD NS unconditionally When neighbor suppression is enabled on a VXLAN port, the bridge is expected to reply to ARP/NS messages on behalf of remote hosts when both FDB and neighbor entries exist. This allows the bridge to suppress flooding of these messages to the VXLAN overlay. According to RFC 9161 ("Operational Aspects of Proxy ARP/ND in Ethernet Virtual Private Networks"): "A PE SHOULD reply to broadcast/multicast address resolution messages, i.e., ARP Requests, ARP probes, NS messages, as well as DAD NS messages. An ARP probe is an ARP Request constructed with an all-zero sender IP address that may be used by hosts for IPv4 Address Conflict Detection as specified in [RFC5227]". However, the current implementation unconditionally suppresses ARP probes and DAD Neighbor Solicitations, which breaks Duplicate Address Detection (DAD) over EVPN. For DAD to work correctly over the VXLAN fabric: - When the bridge does not know the answer: flood the probe/DAD packet to allow remote VTEPs to respond. - When the bridge knows the answer: reply to indicate the address is in use. Fix by adjusting the early suppression checks to exclude ARP probes and DAD NS from unconditional suppression. When replying to a DAD NS, br_nd_send() is adjusted to set the NA destination to the all-nodes multicast address (ff02::1) and clear the Solicited flag, in accordance with RFC 4861 section 7.2.4. Reviewed-by: Ido Schimmel Signed-off-by: Danielle Ratson Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260429062405.1386417-2-danieller@nvidia.com Signed-off-by: Jakub Kicinski --- net/bridge/br_arp_nd_proxy.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c index deb1ab1f24b0..3205346f298c 100644 --- a/net/bridge/br_arp_nd_proxy.c +++ b/net/bridge/br_arp_nd_proxy.c @@ -164,7 +164,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, return; if (parp->ar_op != htons(ARPOP_RREQUEST) && parp->ar_op != htons(ARPOP_RREPLY) && - (ipv4_is_zeronet(sip) || sip == tip)) { + sip == tip) { /* prevent flooding to neigh suppress ports */ BR_INPUT_SKB_CB(skb)->proxyarp_replied = 1; return; @@ -262,6 +262,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, int ns_olen; int i, len; u8 *daddr; + bool dad; u16 pvid; if (!dev || skb_linearize(request)) @@ -300,8 +301,13 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, } } + dad = ipv6_addr_any(&ipv6_hdr(request)->saddr); + /* Ethernet header */ - ether_addr_copy(eth_hdr(reply)->h_dest, daddr); + if (dad) + ipv6_eth_mc_map(&in6addr_linklocal_allnodes, eth_hdr(reply)->h_dest); + else + ether_addr_copy(eth_hdr(reply)->h_dest, daddr); ether_addr_copy(eth_hdr(reply)->h_source, n->ha); eth_hdr(reply)->h_proto = htons(ETH_P_IPV6); reply->protocol = htons(ETH_P_IPV6); @@ -317,7 +323,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, pip6->priority = ipv6_hdr(request)->priority; pip6->nexthdr = IPPROTO_ICMPV6; pip6->hop_limit = 255; - pip6->daddr = ipv6_hdr(request)->saddr; + pip6->daddr = dad ? in6addr_linklocal_allnodes : ipv6_hdr(request)->saddr; pip6->saddr = *(struct in6_addr *)n->primary_key; skb_pull(reply, sizeof(struct ipv6hdr)); @@ -330,7 +336,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p, na->icmph.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT; na->icmph.icmp6_router = (n->flags & NTF_ROUTER) ? 1 : 0; na->icmph.icmp6_override = 1; - na->icmph.icmp6_solicited = 1; + na->icmph.icmp6_solicited = dad ? 0 : 1; na->target = ns->target; ether_addr_copy(&na->opt[2], n->ha); na->opt[0] = ND_OPT_TARGET_LL_ADDR; @@ -435,7 +441,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br, saddr = &iphdr->saddr; daddr = &iphdr->daddr; - if (ipv6_addr_any(saddr) || !ipv6_addr_cmp(saddr, daddr)) { + if (!ipv6_addr_cmp(saddr, daddr)) { /* prevent flooding to neigh suppress ports */ BR_INPUT_SKB_CB(skb)->proxyarp_replied = 1; return; From a3f88d89f698743a8cd91fb43f997e2d292a168d Mon Sep 17 00:00:00 2001 From: Danielle Ratson Date: Wed, 29 Apr 2026 09:24:05 +0300 Subject: [PATCH 2/2] selftests: net: Add tests for ARP probe and DAD NS handling Add test cases to verify that ARP probes and DAD Neighbor Solicitations are handled correctly by the bridge neighbor suppression feature. When neighbor suppression is enabled on a bridge VXLAN port, the bridge should reply to ARP/NS messages on behalf of remote hosts when both FDB and neighbor entries exist, and the answer is known. However, when either the FDB or the neighbor exists, ARP probes / DAD NS should be treated like regular ARP requests / NS and flood to VXLAN. Add two new test functions: neigh_suppress_arp_probe(): Tests ARP probe handling by triggering duplicate address detection using arping -D. Verifies that probes are flooded when the bridge doesn't know the answer, and suppressed when FDB and neighbor entries exist. neigh_suppress_dad_ns(): Tests DAD NS handling by constructing DAD NS packets using mausezahn and verifies correct flooding/suppression behavior. Before the previous patch: $ ./test_bridge_neigh_suppress.sh -t "neigh_suppress_arp_probe neigh_suppress_dad_ns" Per-port ARP probe suppression ------------------------------ TEST: ARP probe suppression [ OK ] TEST: "neigh_suppress" is on [ OK ] TEST: ARP probe suppression [FAIL] TEST: FDB and neighbor entry installation [ OK ] TEST: arping [FAIL] TEST: ARP probe suppression [FAIL] TEST: neighbor removal [ OK ] TEST: ARP probe suppression [FAIL] TEST: "neigh_suppress" is off [ OK ] TEST: ARP probe suppression [FAIL] Per-port DAD NS suppression --------------------------- TEST: DAD NS suppression [ OK ] TEST: "neigh_suppress" is on [ OK ] TEST: DAD NS suppression [FAIL] TEST: FDB and neighbor entry installation [ OK ] TEST: DAD NS suppression [FAIL] TEST: neighbor removal [ OK ] TEST: DAD NS suppression [FAIL] TEST: DAD NS proxy NA reply [FAIL] TEST: "neigh_suppress" is off [ OK ] TEST: DAD NS suppression [FAIL] Tests passed: 10 Tests failed: 10 After the previous patch: $ ./test_bridge_neigh_suppress.sh -t "neigh_suppress_arp_probe neigh_suppress_dad_ns" Per-port ARP probe suppression ------------------------------ TEST: ARP probe suppression [ OK ] TEST: "neigh_suppress" is on [ OK ] TEST: ARP probe suppression [ OK ] TEST: FDB and neighbor entry installation [ OK ] TEST: arping [ OK ] TEST: ARP probe suppression [ OK ] TEST: neighbor removal [ OK ] TEST: ARP probe suppression [ OK ] TEST: "neigh_suppress" is off [ OK ] TEST: ARP probe suppression [ OK ] Per-port DAD NS suppression --------------------------- TEST: DAD NS suppression [ OK ] TEST: "neigh_suppress" is on [ OK ] TEST: DAD NS suppression [ OK ] TEST: FDB and neighbor entry installation [ OK ] TEST: DAD NS suppression [ OK ] TEST: neighbor removal [ OK ] TEST: DAD NS suppression [ OK ] TEST: DAD NS proxy NA reply [ OK ] TEST: "neigh_suppress" is off [ OK ] TEST: DAD NS suppression [ OK ] Tests passed: 20 Tests failed: 0 Signed-off-by: Danielle Ratson Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260429062405.1386417-3-danieller@nvidia.com Signed-off-by: Jakub Kicinski --- .../net/test_bridge_neigh_suppress.sh | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/tools/testing/selftests/net/test_bridge_neigh_suppress.sh b/tools/testing/selftests/net/test_bridge_neigh_suppress.sh index 9067197c9055..4bc92078e173 100755 --- a/tools/testing/selftests/net/test_bridge_neigh_suppress.sh +++ b/tools/testing/selftests/net/test_bridge_neigh_suppress.sh @@ -56,6 +56,8 @@ TESTS=" neigh_suppress_uc_ns neigh_vlan_suppress_arp neigh_vlan_suppress_ns + neigh_suppress_arp_probe + neigh_suppress_dad_ns " VERBOSE=0 PAUSE_ON_FAIL=no @@ -875,6 +877,130 @@ neigh_vlan_suppress_ns() log_test $? 0 "NS suppression (VLAN $vid2)" } +neigh_suppress_arp_probe() +{ + local vid=10 + local tip=192.0.2.2 + local h2_mac + + echo + echo "Per-port ARP probe suppression" + echo "------------------------------" + + run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact" + run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto 0x0806 flower indev swp1 arp_tip $tip arp_sip 0.0.0.0 arp_op request action pass" + + # Initial state - check that ARP probes are not suppressed. + run_cmd "ip netns exec $h1 arping -D -q -c 1 -w 5 -I eth0.$vid $tip" + tc_check_packets "$sw1" "dev vx0 egress" 101 1 + log_test $? 0 "ARP probe suppression" + + # Enable neighbor suppression and check that nothing changes. + run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on" + run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\"" + log_test $? 0 "\"neigh_suppress\" is on" + + run_cmd "ip netns exec $h1 arping -D -q -c 1 -w 5 -I eth0.$vid $tip" + tc_check_packets "$sw1" "dev vx0 egress" 101 2 + log_test $? 0 "ARP probe suppression" + + # Install FDB and a neighbor and check that ARP probes are suppressed. + h2_mac=$(ip -n "$h2" -j -p link show eth0."$vid" | jq -r '.[]["address"]') + run_cmd "bridge -n $sw1 fdb replace $h2_mac dev vx0 master static vlan $vid" + run_cmd "ip -n $sw1 neigh replace $tip lladdr $h2_mac nud permanent dev br0.$vid" + log_test $? 0 "FDB and neighbor entry installation" + + run_cmd "ip netns exec $h1 arping -D -q -c 1 -w 5 -I eth0.$vid $tip" + log_test $? 1 "arping" + tc_check_packets "$sw1" "dev vx0 egress" 101 2 + log_test $? 0 "ARP probe suppression" + + # Remove the neighbor entry and check that ARP probes are not suppressed. + run_cmd "ip -n $sw1 neigh del $tip dev br0.$vid" + log_test $? 0 "neighbor removal" + + run_cmd "ip netns exec $h1 arping -D -q -c 1 -w 5 -I eth0.$vid $tip" + tc_check_packets "$sw1" "dev vx0 egress" 101 3 + log_test $? 0 "ARP probe suppression" + + # Disable neighbor suppression. + run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress off" + run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress off\"" + log_test $? 0 "\"neigh_suppress\" is off" + + run_cmd "ip netns exec $h1 arping -D -q -c 1 -w 5 -I eth0.$vid $tip" + tc_check_packets "$sw1" "dev vx0 egress" 101 4 + log_test $? 0 "ARP probe suppression" +} + +neigh_suppress_dad_ns() +{ + local vid=10 + local tip=2001:db8:1::99 + local mcast=ff02::1:ff00:99 + local dmac=33:33:ff:00:00:99 + local full_tip=20:01:0d:b8:00:01:00:00:00:00:00:00:00:00:00:99 + local csum="4b:bc" + local smac + local tmac + + echo + echo "Per-port DAD NS suppression" + echo "---------------------------" + + smac=$(ip -n "$h1" -j -p link show eth0."$vid" | jq -r '.[]["address"]') + + run_cmd "tc -n $sw1 qdisc replace dev vx0 clsact" + run_cmd "tc -n $sw1 filter replace dev vx0 egress pref 1 handle 101 proto ipv6 flower indev swp1 ip_proto icmpv6 dst_ip $mcast src_ip :: type 135 code 0 action pass" + + # Initial state - check that DAD NS are not suppressed. + run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A :: -B $mcast -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum" "$full_tip") -q" + tc_check_packets "$sw1" "dev vx0 egress" 101 1 + log_test $? 0 "DAD NS suppression" + + # Enable neighbor suppression and check that nothing changes. + run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress on" + run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress on\"" + log_test $? 0 "\"neigh_suppress\" is on" + + run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A :: -B $mcast -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum" "$full_tip") -q" + tc_check_packets "$sw1" "dev vx0 egress" 101 2 + log_test $? 0 "DAD NS suppression" + + # Install FDB and a neighbor and check that DAD NS are suppressed + # and that a proxy NA is sent back to h1. + tmac=$(ip -n "$h2" -j -p link show eth0."$vid" | jq -r '.[]["address"]') + run_cmd "bridge -n $sw1 fdb replace $tmac dev vx0 master static vlan $vid" + run_cmd "ip -n $sw1 -6 neigh replace $tip lladdr $tmac nud permanent dev br0.$vid" + log_test $? 0 "FDB and neighbor entry installation" + + run_cmd "tc -n $h1 qdisc replace dev eth0.$vid clsact" + run_cmd "tc -n $h1 filter replace dev eth0.$vid ingress pref 1 handle 101 proto ipv6 flower ip_proto icmpv6 dst_ip ff02::1 src_ip $tip type 136 code 0 action pass" + + run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A :: -B $mcast -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum" "$full_tip") -q" + tc_check_packets "$sw1" "dev vx0 egress" 101 2 + log_test $? 0 "DAD NS suppression" + tc_check_packets "$h1" "dev eth0.$vid ingress" 101 1 + log_test $? 0 "DAD NS proxy NA reply" + + # Remove the neighbor entry and check that DAD NS are not suppressed. + run_cmd "ip -n $sw1 -6 neigh del $tip dev br0.$vid" + log_test $? 0 "neighbor removal" + + run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A :: -B $mcast -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum" "$full_tip") -q" + tc_check_packets "$sw1" "dev vx0 egress" 101 3 + log_test $? 0 "DAD NS suppression" + + # Disable neighbor suppression. + run_cmd "bridge -n $sw1 link set dev vx0 neigh_suppress off" + run_cmd "bridge -n $sw1 -d link show dev vx0 | grep \"neigh_suppress off\"" + log_test $? 0 "\"neigh_suppress\" is off" + + run_cmd "ip netns exec $h1 mausezahn -6 eth0.$vid -c 1 -a $smac -b $dmac -A :: -B $mcast -t ip hop=255,next=58,payload=$(icmpv6_header_get "$csum" "$full_tip") -q" + tc_check_packets "$sw1" "dev vx0 egress" 101 4 + log_test $? 0 "DAD NS suppression" +} + ################################################################################ # Usage