git: f37fbe30f559 - main - ndp: implement delayed anycast and proxy NA

From: Pouria Mousavizadeh Tehrani <pouria_at_FreeBSD.org>
Date: Mon, 09 Mar 2026 19:05:56 UTC
The branch main has been updated by pouria:

URL: https://cgit.FreeBSD.org/src/commit/?id=f37fbe30f559acfb269f67d3efe59569878a3ee1

commit f37fbe30f559acfb269f67d3efe59569878a3ee1
Author:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
AuthorDate: 2026-03-09 17:00:15 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-03-09 19:00:49 +0000

    ndp: implement delayed anycast and proxy NA
    
    Reviewed by: bms
    Differential Revision: https://reviews.freebsd.org/D55141
---
 sys/netinet6/nd6.h     |  3 ++
 sys/netinet6/nd6_nbr.c | 78 ++++++++++++++++++++++++++++++++++----------------
 2 files changed, 57 insertions(+), 24 deletions(-)

diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h
index eca9f6262568..d9fccce33980 100644
--- a/sys/netinet6/nd6.h
+++ b/sys/netinet6/nd6.h
@@ -155,11 +155,13 @@ struct	in6_ndifreq {
 /* ND6 queue flags */
 #define	ND6_QUEUE_FLAG_NEWGUA	0x01	/* new global unicast address event */
 #define	ND6_QUEUE_FLAG_LLADDR	0x02	/* link-layer address change event */
+#define	ND6_QUEUE_FLAG_ANYCAST	0x04	/* delay NA for anycast or proxy address */
 
 /* protocol constants */
 #define MAX_RTR_SOLICITATION_DELAY	1	/* 1sec */
 #define RTR_SOLICITATION_INTERVAL	4	/* 4sec */
 #define MAX_RTR_SOLICITATIONS		3
+#define MAX_ANYCAST_DELAY_TIME		1	/* 1sec */
 
 #define ND6_INFINITE_LIFETIME		0xffffffff
 
@@ -373,6 +375,7 @@ void nd6_dad_start(struct ifaddr *, int);
 void nd6_dad_stop(struct ifaddr *);
 void nd6_grand_start(struct ifaddr *, uint32_t);
 void nd6_queue_stop(struct ifaddr *);
+void nd6_delayed_na_start(struct ifaddr *, struct in6_addr *, u_int, uint32_t);
 
 /* nd6_rtr.c */
 void nd6_rs_input(struct mbuf *, int, int);
diff --git a/sys/netinet6/nd6_nbr.c b/sys/netinet6/nd6_nbr.c
index 6a1f29c31eed..0a34e0e3628e 100644
--- a/sys/netinet6/nd6_nbr.c
+++ b/sys/netinet6/nd6_nbr.c
@@ -124,6 +124,7 @@ SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ND6_ONLINKNSRFC4861,
 struct nd_queue {
 	TAILQ_ENTRY(nd_queue) ndq_list;
 	struct ifaddr	*ndq_ifa;
+	struct in6_addr	ndq_daddr;
 	uint32_t	ndq_flags;
 	struct callout	ndq_callout;
 };
@@ -355,8 +356,17 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
 		rflag |= ND_NA_FLAG_SOLICITED;
 	}
 
-	nd6_na_output_fib(ifp, &saddr6, &taddr6, rflag, tlladdr,
-	    proxy ? (struct sockaddr *)&proxydl : NULL, M_GETFIB(m));
+	/*
+	 * RFC 4861, anycast or proxy NA sent in response to a NS SHOULD
+	 * be delayed by a random time between 0 and MAX_ANYCAST_DELAY_TIME
+	 * to reduce the probability of network congestion.
+	 */
+	if (anycast == 0 && proxy == 0)
+		nd6_na_output_fib(ifp, &saddr6, &taddr6, rflag, tlladdr,
+		    proxy ? (struct sockaddr *)&proxydl : NULL, M_GETFIB(m));
+	else
+		nd6_delayed_na_start(ifa, &saddr6, arc4random() %
+		    (MAX_ANYCAST_DELAY_TIME * hz), ND6_QUEUE_FLAG_ANYCAST);
  freeit:
 	if (ifa != NULL)
 		ifa_free(ifa);
@@ -648,10 +658,6 @@ nd6_ns_output(struct ifnet *ifp, const struct in6_addr *saddr6,
  *
  * Based on RFC 2461
  * Based on RFC 2462 (duplicate address detection)
- *
- * the following items are not implemented yet:
- * - proxy advertisement delay rule (RFC2461 7.2.8, last paragraph, SHOULD)
- * - anycast advertisement delay rule (RFC2461 7.2.7, SHOULD)
  */
 void
 nd6_na_input(struct mbuf *m, int off, int icmp6len)
@@ -966,10 +972,6 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len)
  *
  * Based on RFC 2461
  *
- * the following items are not implemented yet:
- * - proxy advertisement delay rule (RFC2461 7.2.8, last paragraph, SHOULD)
- * - anycast advertisement delay rule (RFC2461 7.2.7, SHOULD)
- *
  * tlladdr:
  * - 0x01 if include target link-layer address
  * - 0x02 if target address is CARP MASTER
@@ -1669,7 +1671,6 @@ nd6_queue_timer(void *arg)
 	struct ifaddr *ifa = ndq->ndq_ifa;
 	struct ifnet *ifp = ifa->ifa_ifp;
 	struct in6_ifextra *ext = ifp->if_inet6;
-	struct in6_addr taddr6 = IN6ADDR_ANY_INIT;
 	struct epoch_tracker et;
 	int delay, tlladdr;
 	u_long flags;
@@ -1685,12 +1686,10 @@ nd6_queue_timer(void *arg)
 		flags &= ~ND_NA_FLAG_ROUTER;
 
 	/*
-	 * RFC 9131 Section 6.1.2: if new global address added,
-	 * use the all-routers multicast address.
-	 * If the address is preferred, then the Override flag SHOULD NOT be set.
+	 * RFC 9131 Section 6.1.2: If the address is preferred,
+	 * then the Override flag SHOULD NOT be set.
 	 */
 	if ((ndq->ndq_flags & ND6_QUEUE_FLAG_NEWGUA) != 0) {
-		taddr6 = in6addr_linklocal_allrouters;
 		/*
 		 * XXX: If the address is in the Optimistic state,
 		 * then the Override flag MUST NOT be set.
@@ -1700,29 +1699,31 @@ nd6_queue_timer(void *arg)
 			flags |= ND_NA_FLAG_OVERRIDE;
 	}
 	/*
-	 * RFC 4891 Section 7.2.6: if link-layer address changed,
-	 * use the all-nodes multicast address.
+	 * RFC 4861 Section 7.2.6: if link-layer address changed,
 	 * The Override flag MAY be set to either zero or one.
 	 */
-	if ((ndq->ndq_flags & ND6_QUEUE_FLAG_LLADDR) != 0) {
-		taddr6 = in6addr_linklocal_allnodes;
+	if ((ndq->ndq_flags & ND6_QUEUE_FLAG_LLADDR) != 0)
 		flags |= ND_NA_FLAG_OVERRIDE;
-	}
+	/* anycast advertisement delay rule (RFC 4861 7.2.7, SHOULD) */
+	if ((ndq->ndq_flags & ND6_QUEUE_FLAG_ANYCAST) != 0)
+		flags |= ND_NA_FLAG_SOLICITED;
 
 	/* Wait at least a RetransTimer before removing from queue */
 	delay = ext->nd_retrans * hz / 1000;
 	callout_reset(&ndq->ndq_callout, delay, nd6_queue_rel, ndq);
 	IF_ADDR_WUNLOCK(ifp);
 
-	if (__predict_true(in6_setscope(&taddr6, ifp, NULL) == 0))
-		nd6_na_output_fib(ifp, &taddr6, IFA_IN6(ifa), flags, tlladdr, NULL, ifp->if_fib);
+	if (__predict_true(in6_setscope(&ndq->ndq_daddr, ifp, NULL) == 0))
+		nd6_na_output_fib(ifp, &ndq->ndq_daddr, IFA_IN6(ifa), flags, tlladdr,
+		    NULL, ifp->if_fib);
 
 	NET_EPOCH_EXIT(et);
 	CURVNET_RESTORE();
 }
 
 static void
-nd6_queue_add(struct ifaddr *ifa, int delay, uint32_t flags)
+nd6_queue_add(struct ifaddr *ifa, struct in6_addr *daddr,
+    int delay, uint32_t flags)
 {
 	struct nd_queue *ndq;
 	struct ifnet *ifp = ifa->ifa_ifp;
@@ -1749,6 +1750,7 @@ nd6_queue_add(struct ifaddr *ifa, int delay, uint32_t flags)
 
 	ndq->ndq_ifa = ifa;
 	ifa_ref(ndq->ndq_ifa);
+	memcpy(&ndq->ndq_daddr, daddr, sizeof(struct in6_addr));
 	ndq->ndq_flags = flags;
 
 	TAILQ_INSERT_TAIL(&ext->nd_queue, ndq, ndq_list);
@@ -1765,6 +1767,7 @@ nd6_grand_start(struct ifaddr *ifa, uint32_t flags)
 {
 	struct nd_queue *ndq;
 	struct in6_ifextra *ext = ifa->ifa_ifp->if_inet6;
+	struct in6_addr daddr = IN6ADDR_ANY_INIT;
 	int delay, count = 0;
 
 	NET_EPOCH_ASSERT();
@@ -1794,8 +1797,22 @@ nd6_grand_start(struct ifaddr *ifa, uint32_t flags)
 			return;
 	}
 
+	/*
+	 * RFC 9131 Section 6.1.2: if new global address added,
+	 * use the all-routers multicast address.
+	 */
+	if ((flags & ND6_QUEUE_FLAG_NEWGUA) != 0)
+		daddr = in6addr_linklocal_allrouters;
+
+	/*
+	 * RFC 4861 Section 7.2.6: if link-layer address changed,
+	 * use the all-nodes multicast address.
+	 */
+	if ((flags & ND6_QUEUE_FLAG_LLADDR) != 0)
+		daddr = in6addr_linklocal_allnodes;
+
 	delay = ext->nd_retrans * hz / 1000;
-	nd6_queue_add(ifa, count * delay, flags);
+	nd6_queue_add(ifa, &daddr, count * delay, flags);
 }
 
 /*
@@ -1817,3 +1834,16 @@ nd6_queue_stop(struct ifaddr *ifa)
 	}
 	IF_ADDR_WUNLOCK(ifa->ifa_ifp);
 }
+
+/*
+ * Send delayed NA for specified address.
+ * Called by nd6_ns_input for anycast or proxy NA
+ */
+void
+nd6_delayed_na_start(struct ifaddr *ifa, struct in6_addr *daddr,
+    u_int delay, uint32_t flags)
+{
+
+	NET_EPOCH_ASSERT();
+	nd6_queue_add(ifa, daddr, delay, flags);
+}