> -----Original Message-----
> From: Danielle Ratson <[email protected]>
> Sent: Wednesday, 29 April 2026 9:24
> To: [email protected]
> Cc: [email protected]; Ido Schimmel <[email protected]>;
> [email protected]; [email protected]; [email protected];
> [email protected]; [email protected]; [email protected];
> [email protected]; [email protected]; linux-
> [email protected]; Danielle Ratson <[email protected]>
> Subject: [PATCH net-next 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 <[email protected]>
> Signed-off-by: Danielle Ratson <[email protected]>
> ---
> 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;
Hi,
Sashiko wrote:
> Should the override flag also be conditionally cleared when responding to a
> DAD solicitation?
> According to RFC 4861 section 7.2.4, the Override flag should not be set
> when responding to DAD solicitations (where the source address is
> unspecified).
The NA sent in response to a DAD NS is an unsolicited advertisement- the
Solicited flag is explicitly cleared for this case:
na->icmph.icmp6_solicited = dad ? 0 : 1;
RFC 4861 section 4.4 defines when the Override flag should be set:
"It SHOULD NOT be set in solicited advertisements for anycast addresses and in
solicited proxy advertisements. It SHOULD be set in other solicited
advertisements and in unsolicited advertisements."
Since a DAD response is an unsolicited advertisement, Override SHOULD be set,
so leaving icmp6_override = 1 unconditionally is correct.
This is also consistent with how the Linux host stack behaves. A regular Linux
host responding to a DAD probe sends:
12:08:13.391138 5e:79:d1:22:89:61 > 33:33:ff:00:00:02, ethertype IPv6 (0x86dd),
length 86: (hlim 255, next-header ICMPv6 (58), payload length 32) :: >
ff02::1:ff00:2: [icmp6 sum ok] ICMP6, neighbor solicitation, length 32, who has
2001:db8:1::2
unknown option (14), length 8 (1):
0x0000: d73f 45b7 5343
12:08:13.392867 76:93:dd:a0:c7:7d > 33:33:00:00:00:01, ethertype IPv6 (0x86dd),
length 86: (hlim 255, next-header ICMPv6 (58), payload length 32) 2001:db8:1::2
> ff02::1: [icmp6 sum ok] ICMP6, neighbor advertisement, length 32, tgt is
2001:db8:1::2, Flags [override]
destination link-address option (2), length 8 (1):
76:93:dd:a0:c7:7d
0x0000: 7693 dda0 c77d
The NA has Override set but no Solicited flag, consistent with RFC 4861 section
4.4.