Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 3 Oct 2025 08:02:25 GMT
From:      "Andrey V. Elsukov" <ae@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 716acd9367df - main - carp6: revise the generation of ND6 NA
Message-ID:  <202510030802.59382PmL063849@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by ae:

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

commit 716acd9367df49d44f29eeb783706025f3a04c65
Author:     Andrey V. Elsukov <ae@FreeBSD.org>
AuthorDate: 2025-10-03 07:57:44 +0000
Commit:     Andrey V. Elsukov <ae@FreeBSD.org>
CommitDate: 2025-10-03 07:57:44 +0000

    carp6: revise the generation of ND6 NA
    
    * use ND_NA_FLAG_ROUTER flag in carp_send_na() when we work as router.
    * use in6addr_any as destination address for nd6_na_output(), then it
      will use ipv6-all-nodes multicast address.
    * add in6_selectsrc_nbr() function that accepts additional argument
      ip6_moptions. Use this function from ND6 code to avoid cases when
      nd6_na_output/nd6_ns_output can not find source address for
      multicast destinations.
    * add some comments from RFC2461 for better understanding.
    * use tlladdr argument as flags and use ND6_NA_OPT_LLA when we need
      to add target link-layer address option, and ND6_NA_CARP_MASTER when
      we know that target address is CARP master. Then we can prepare
      correct CARP's mac address if target address is CARP master.
    * move blocks of code where multicast options is initialized and
      use it when destination address is multicast.
    
    Reviewed by:    kp
    Obtained from:  Yandex LLC
    MFC after:      2 weeks
    Sponsored by:   Yandex LLC
    Differential Revision: https://reviews.freebsd.org/D52825
---
 sys/netinet/ip_carp.c  |  27 ++++++++---
 sys/netinet6/in6_src.c |  54 ++++++++++++++++------
 sys/netinet6/ip6_var.h |   2 +
 sys/netinet6/nd6.h     |   4 ++
 sys/netinet6/nd6_nbr.c | 121 ++++++++++++++++++++++---------------------------
 5 files changed, 119 insertions(+), 89 deletions(-)

diff --git a/sys/netinet/ip_carp.c b/sys/netinet/ip_carp.c
index d3d7957cf087..4f553b9aac5e 100644
--- a/sys/netinet/ip_carp.c
+++ b/sys/netinet/ip_carp.c
@@ -1640,18 +1640,31 @@ carp_iamatch(struct ifaddr *ifa, uint8_t **enaddr)
 static void
 carp_send_na(struct carp_softc *sc)
 {
-	static struct in6_addr mcast = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
 	struct ifaddr *ifa;
-	struct in6_addr *in6;
+	int flags;
 
+	/*
+	 * Sending Unsolicited Neighbor Advertisements
+	 *
+	 * If the node is a router, we MUST set the Router flag to one.
+	 * We set Override flag to one and send link-layer address option,
+	 * thus neighboring nodes will install the new link-layer address.
+	 */
+	flags = ND_NA_FLAG_OVERRIDE;
+	if (V_ip6_forwarding)
+		flags |= ND_NA_FLAG_ROUTER;
 	CARP_FOREACH_IFA(sc, ifa) {
 		if (ifa->ifa_addr->sa_family != AF_INET6)
 			continue;
-
-		in6 = IFA_IN6(ifa);
-		nd6_na_output(sc->sc_carpdev, &mcast, in6,
-		    ND_NA_FLAG_OVERRIDE, 1, NULL);
-		DELAY(1000);	/* XXX */
+		/*
+		 * We use unspecified address as destination here to avoid
+		 * scope initialization for each call.
+		 * nd6_na_output() will use all nodes multicast address if
+		 * destinaion address is unspecified.
+		 */
+		nd6_na_output(sc->sc_carpdev, &in6addr_any, IFA_IN6(ifa),
+		    flags, ND6_NA_OPT_LLA | ND6_NA_CARP_MASTER, NULL);
+		DELAY(1000);	/* RetransTimer */
 	}
 }
 
diff --git a/sys/netinet6/in6_src.c b/sys/netinet6/in6_src.c
index dd6864482b3c..3e55c6e5fc05 100644
--- a/sys/netinet6/in6_src.c
+++ b/sys/netinet6/in6_src.c
@@ -132,8 +132,8 @@ static int in6_selectif(struct sockaddr_in6 *, struct ip6_pktopts *,
 	struct ip6_moptions *, struct ifnet **,
 	struct ifnet *, u_int);
 static int in6_selectsrc(uint32_t, struct sockaddr_in6 *,
-	struct ip6_pktopts *, struct inpcb *, struct ucred *,
-	struct ifnet **, struct in6_addr *);
+	struct ip6_pktopts *, struct ip6_moptions *, struct inpcb *,
+	struct ucred *, struct ifnet **, struct in6_addr *);
 
 static struct in6_addrpolicy *lookup_addrsel_policy(struct sockaddr_in6 *);
 
@@ -173,8 +173,8 @@ static struct in6_addrpolicy *match_addrsel_policy(struct sockaddr_in6 *);
 
 static int
 in6_selectsrc(uint32_t fibnum, struct sockaddr_in6 *dstsock,
-    struct ip6_pktopts *opts, struct inpcb *inp, struct ucred *cred,
-    struct ifnet **ifpp, struct in6_addr *srcp)
+    struct ip6_pktopts *opts, struct ip6_moptions *mopts, struct inpcb *inp,
+    struct ucred *cred, struct ifnet **ifpp, struct in6_addr *srcp)
 {
 	struct rm_priotracker in6_ifa_tracker;
 	struct in6_addr dst, tmp;
@@ -186,7 +186,6 @@ in6_selectsrc(uint32_t fibnum, struct sockaddr_in6 *dstsock,
 	u_int32_t odstzone;
 	int prefer_tempaddr;
 	int error;
-	struct ip6_moptions *mopts;
 
 	NET_EPOCH_ASSERT();
 	KASSERT(srcp != NULL, ("%s: srcp is NULL", __func__));
@@ -205,13 +204,6 @@ in6_selectsrc(uint32_t fibnum, struct sockaddr_in6 *dstsock,
 		*ifpp = NULL;
 	}
 
-	if (inp != NULL) {
-		INP_LOCK_ASSERT(inp);
-		mopts = inp->in6p_moptions;
-	} else {
-		mopts = NULL;
-	}
-
 	/*
 	 * If the source address is explicitly specified by the caller,
 	 * check if the requested source address is indeed a unicast address
@@ -552,10 +544,13 @@ in6_selectsrc_socket(struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts,
 	uint32_t fibnum;
 	int error;
 
+	INP_LOCK_ASSERT(inp);
+
 	fibnum = inp->inp_inc.inc_fibnum;
 	retifp = NULL;
 
-	error = in6_selectsrc(fibnum, dstsock, opts, inp, cred, &retifp, srcp);
+	error = in6_selectsrc(fibnum, dstsock, opts, inp->in6p_moptions,
+	    inp, cred, &retifp, srcp);
 	if (error != 0)
 		return (error);
 
@@ -583,7 +578,7 @@ in6_selectsrc_socket(struct sockaddr_in6 *dstsock, struct ip6_pktopts *opts,
  * Stores selected address to @srcp.
  * Returns 0 on success.
  *
- * Used by non-socket based consumers (ND code mostly)
+ * Used by non-socket based consumers
  */
 int
 in6_selectsrc_addr(uint32_t fibnum, const struct in6_addr *dst,
@@ -602,13 +597,42 @@ in6_selectsrc_addr(uint32_t fibnum, const struct in6_addr *dst,
 	dst_sa.sin6_scope_id = scopeid;
 	sa6_embedscope(&dst_sa, 0);
 
-	error = in6_selectsrc(fibnum, &dst_sa, NULL, NULL, NULL, &retifp, srcp);
+	error = in6_selectsrc(fibnum, &dst_sa, NULL, NULL,
+	    NULL, NULL, &retifp, srcp);
 	if (hlim != NULL)
 		*hlim = in6_selecthlim(NULL, retifp);
 
 	return (error);
 }
 
+/*
+ * Select source address based on @fibnum, @dst and @mopts.
+ * Stores selected address to @srcp.
+ * Returns 0 on success.
+ *
+ * Used by non-socket based consumers (ND code mostly)
+ */
+int
+in6_selectsrc_nbr(uint32_t fibnum, const struct in6_addr *dst,
+    struct ip6_moptions *mopts, struct ifnet *ifp, struct in6_addr *srcp)
+{
+	struct sockaddr_in6 dst_sa;
+	struct ifnet *retifp;
+	int error;
+
+	retifp = ifp;
+	bzero(&dst_sa, sizeof(dst_sa));
+	dst_sa.sin6_family = AF_INET6;
+	dst_sa.sin6_len = sizeof(dst_sa);
+	dst_sa.sin6_addr = *dst;
+	dst_sa.sin6_scope_id = ntohs(in6_getscope(dst));
+	sa6_embedscope(&dst_sa, 0);
+
+	error = in6_selectsrc(fibnum, &dst_sa, NULL, mopts,
+	    NULL, NULL, &retifp, srcp);
+	return (error);
+}
+
 static struct nhop_object *
 cache_route(uint32_t fibnum, const struct sockaddr_in6 *dst, struct route_in6 *ro,
     uint32_t flowid)
diff --git a/sys/netinet6/ip6_var.h b/sys/netinet6/ip6_var.h
index e1a4e8678ebb..c28bfa5a9d08 100644
--- a/sys/netinet6/ip6_var.h
+++ b/sys/netinet6/ip6_var.h
@@ -440,6 +440,8 @@ int	in6_selectsrc_socket(struct sockaddr_in6 *, struct ip6_pktopts *,
     struct inpcb *, struct ucred *, int, struct in6_addr *, int *);
 int	in6_selectsrc_addr(uint32_t, const struct in6_addr *,
     uint32_t, struct ifnet *, struct in6_addr *, int *);
+int	in6_selectsrc_nbr(uint32_t, const struct in6_addr *,
+    struct ip6_moptions *, struct ifnet *, struct in6_addr *);
 int in6_selectroute(struct sockaddr_in6 *, struct ip6_pktopts *,
 	struct ip6_moptions *, struct route_in6 *, struct ifnet **,
 	struct nhop_object **, u_int, uint32_t);
diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h
index 5fe027ac5e7c..e484c709e29a 100644
--- a/sys/netinet6/nd6.h
+++ b/sys/netinet6/nd6.h
@@ -171,6 +171,10 @@ struct	in6_ndifreq {
 #define NDPRF_ONLINK		0x1
 #define NDPRF_DETACHED		0x2
 
+/* ND6 NA output flags */
+#define	ND6_NA_OPT_LLA		0x01
+#define	ND6_NA_CARP_MASTER	0x02
+
 /* protocol constants */
 #define MAX_RTR_SOLICITATION_DELAY	1	/* 1sec */
 #define RTR_SOLICITATION_INTERVAL	4	/* 4sec */
diff --git a/sys/netinet6/nd6_nbr.c b/sys/netinet6/nd6_nbr.c
index cc17b4e1a402..e2db192bca15 100644
--- a/sys/netinet6/nd6_nbr.c
+++ b/sys/netinet6/nd6_nbr.c
@@ -245,10 +245,9 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
 	 * In implementation, we add target link-layer address by default.
 	 * We do not add one in MUST NOT cases.
 	 */
-	if (!IN6_IS_ADDR_MULTICAST(&daddr6))
-		tlladdr = 0;
-	else
-		tlladdr = 1;
+	tlladdr = 0;
+	if (IN6_IS_ADDR_MULTICAST(&daddr6))
+		tlladdr |= ND6_NA_OPT_LLA;
 
 	/*
 	 * Target address (taddr6) must be either:
@@ -257,9 +256,11 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
 	 * (3) "tentative" address on which DAD is being performed.
 	 */
 	/* (1) and (3) check. */
-	if (ifp->if_carp)
+	if (ifp->if_carp) {
 		ifa = (*carp_iamatch6_p)(ifp, &taddr6);
-	else
+		if (ifa != NULL)
+			tlladdr |= ND6_NA_CARP_MASTER;
+	} else
 		ifa = (struct ifaddr *)in6ifa_ifpwithaddr(ifp, &taddr6);
 
 	/* (2) check. */
@@ -322,33 +323,29 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
 		goto freeit;
 	}
 
+	/*
+	 * If the Target Address is either an anycast address or a unicast
+	 * address for which the node is providing proxy service, or the Target
+	 * Link-Layer Address option is not included, the Override flag SHOULD
+	 * be set to zero.  Otherwise, the Override flag SHOULD be set to one.
+	 */
+	if (anycast == 0 && proxy == 0 && (tlladdr & ND6_NA_OPT_LLA) != 0)
+		rflag |= ND_NA_FLAG_OVERRIDE;
 	/*
 	 * If the source address is unspecified address, entries must not
 	 * be created or updated.
-	 * It looks that sender is performing DAD.  Output NA toward
-	 * all-node multicast address, to tell the sender that I'm using
-	 * the address.
+	 * It looks that sender is performing DAD. nd6_na_output() will
+	 * send NA toward all-node multicast address, to tell the sender
+	 * that I'm using the address.
 	 * S bit ("solicited") must be zero.
 	 */
-	if (IN6_IS_ADDR_UNSPECIFIED(&saddr6)) {
-		struct in6_addr in6_all;
-
-		in6_all = in6addr_linklocal_allnodes;
-		if (in6_setscope(&in6_all, ifp, NULL) != 0)
-			goto bad;
-		nd6_na_output_fib(ifp, &in6_all, &taddr6,
-		    ((anycast || proxy || !tlladdr) ? 0 : ND_NA_FLAG_OVERRIDE) |
-		    rflag, tlladdr, proxy ? (struct sockaddr *)&proxydl : NULL,
-		    M_GETFIB(m));
-		goto freeit;
+	if (!IN6_IS_ADDR_UNSPECIFIED(&saddr6)) {
+		nd6_cache_lladdr(ifp, &saddr6, lladdr, lladdrlen,
+		    ND_NEIGHBOR_SOLICIT, 0);
+		rflag |= ND_NA_FLAG_SOLICITED;
 	}
 
-	nd6_cache_lladdr(ifp, &saddr6, lladdr, lladdrlen,
-	    ND_NEIGHBOR_SOLICIT, 0);
-
-	nd6_na_output_fib(ifp, &saddr6, &taddr6,
-	    ((anycast || proxy || !tlladdr) ? 0 : ND_NA_FLAG_OVERRIDE) |
-	    rflag | ND_NA_FLAG_SOLICITED, tlladdr,
+	nd6_na_output_fib(ifp, &saddr6, &taddr6, rflag, tlladdr,
 	    proxy ? (struct sockaddr *)&proxydl : NULL, M_GETFIB(m));
  freeit:
 	if (ifa != NULL)
@@ -440,13 +437,6 @@ nd6_ns_output_fib(struct ifnet *ifp, const struct in6_addr *saddr6,
 		return;
 	M_SETFIB(m, fibnum);
 
-	if (daddr6 == NULL || IN6_IS_ADDR_MULTICAST(daddr6)) {
-		m->m_flags |= M_MCAST;
-		im6o.im6o_multicast_ifp = ifp;
-		im6o.im6o_multicast_hlim = 255;
-		im6o.im6o_multicast_loop = 0;
-	}
-
 	icmp6len = sizeof(*nd_ns);
 	m->m_pkthdr.len = m->m_len = sizeof(*ip6) + icmp6len;
 	m->m_data += max_linkhdr;	/* or M_ALIGN() equivalent? */
@@ -471,6 +461,12 @@ nd6_ns_output_fib(struct ifnet *ifp, const struct in6_addr *saddr6,
 		if (in6_setscope(&ip6->ip6_dst, ifp, NULL) != 0)
 			goto bad;
 	}
+	if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+		m->m_flags |= M_MCAST;
+		im6o.im6o_multicast_ifp = ifp;
+		im6o.im6o_multicast_hlim = 255;
+		im6o.im6o_multicast_loop = 0;
+	}
 	if (nonce == NULL) {
 		char ip6buf[INET6_ADDRSTRLEN];
 		struct ifaddr *ifa = NULL;
@@ -492,20 +488,16 @@ nd6_ns_output_fib(struct ifnet *ifp, const struct in6_addr *saddr6,
 			ifa = (struct ifaddr *)in6ifa_ifpwithaddr(ifp, saddr6);
 		if (ifa == NULL) {
 			int error;
-			struct in6_addr dst6, src6;
-			uint32_t scopeid;
 
-			in6_splitscope(&ip6->ip6_dst, &dst6, &scopeid);
-			error = in6_selectsrc_addr(fibnum, &dst6,
-			    scopeid, ifp, &src6, NULL);
+			error = in6_selectsrc_nbr(fibnum, &ip6->ip6_dst, &im6o,
+			    ifp, &ip6->ip6_src);
 			if (error) {
 				nd6log((LOG_DEBUG, "%s: source can't be "
 				    "determined: dst=%s, error=%d\n", __func__,
-				    ip6_sprintf(ip6buf, &dst6),
+				    ip6_sprintf(ip6buf, &ip6->ip6_dst),
 				    error));
 				goto bad;
 			}
-			ip6->ip6_src = src6;
 		} else
 			ip6->ip6_src = *saddr6;
 
@@ -968,7 +960,9 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len)
  * - proxy advertisement delay rule (RFC2461 7.2.8, last paragraph, SHOULD)
  * - anycast advertisement delay rule (RFC2461 7.2.7, SHOULD)
  *
- * tlladdr - 1 if include target link-layer address
+ * tlladdr:
+ * - 0x01 if include target link-layer address
+ * - 0x02 if target address is CARP MASTER
  * sdl0 - sockaddr_dl (= proxy NA) or NULL
  */
 static void
@@ -981,8 +975,7 @@ nd6_na_output_fib(struct ifnet *ifp, const struct in6_addr *daddr6_0,
 	struct ip6_hdr *ip6;
 	struct nd_neighbor_advert *nd_na;
 	struct ip6_moptions im6o;
-	struct in6_addr daddr6, dst6, src6;
-	uint32_t scopeid;
+	struct in6_addr daddr6;
 
 	NET_EPOCH_ASSERT();
 
@@ -1006,13 +999,6 @@ nd6_na_output_fib(struct ifnet *ifp, const struct in6_addr *daddr6_0,
 		return;
 	M_SETFIB(m, fibnum);
 
-	if (IN6_IS_ADDR_MULTICAST(&daddr6)) {
-		m->m_flags |= M_MCAST;
-		im6o.im6o_multicast_ifp = ifp;
-		im6o.im6o_multicast_hlim = 255;
-		im6o.im6o_multicast_loop = 0;
-	}
-
 	icmp6len = sizeof(*nd_na);
 	m->m_pkthdr.len = m->m_len = sizeof(struct ip6_hdr) + icmp6len;
 	m->m_data += max_linkhdr;	/* or M_ALIGN() equivalent? */
@@ -1024,26 +1010,24 @@ nd6_na_output_fib(struct ifnet *ifp, const struct in6_addr *daddr6_0,
 	ip6->ip6_vfc |= IPV6_VERSION;
 	ip6->ip6_nxt = IPPROTO_ICMPV6;
 	ip6->ip6_hlim = 255;
+
 	if (IN6_IS_ADDR_UNSPECIFIED(&daddr6)) {
 		/* reply to DAD */
-		daddr6.s6_addr16[0] = IPV6_ADDR_INT16_MLL;
-		daddr6.s6_addr16[1] = 0;
-		daddr6.s6_addr32[1] = 0;
-		daddr6.s6_addr32[2] = 0;
-		daddr6.s6_addr32[3] = IPV6_ADDR_INT32_ONE;
+		daddr6 = in6addr_linklocal_allnodes;
 		if (in6_setscope(&daddr6, ifp, NULL))
 			goto bad;
 
 		flags &= ~ND_NA_FLAG_SOLICITED;
 	}
-	ip6->ip6_dst = daddr6;
+	if (IN6_IS_ADDR_MULTICAST(&daddr6)) {
+		m->m_flags |= M_MCAST;
+		im6o.im6o_multicast_ifp = ifp;
+		im6o.im6o_multicast_hlim = 255;
+		im6o.im6o_multicast_loop = 0;
+	}
 
-	/*
-	 * Select a source whose scope is the same as that of the dest.
-	 */
-	in6_splitscope(&daddr6, &dst6, &scopeid);
-	error = in6_selectsrc_addr(fibnum, &dst6,
-	    scopeid, ifp, &src6, NULL);
+	ip6->ip6_dst = daddr6;
+	error = in6_selectsrc_nbr(fibnum, &daddr6, &im6o, ifp, &ip6->ip6_src);
 	if (error) {
 		char ip6buf[INET6_ADDRSTRLEN];
 		nd6log((LOG_DEBUG, "nd6_na_output: source can't be "
@@ -1051,28 +1035,31 @@ nd6_na_output_fib(struct ifnet *ifp, const struct in6_addr *daddr6_0,
 		    ip6_sprintf(ip6buf, &daddr6), error));
 		goto bad;
 	}
-	ip6->ip6_src = src6;
 	nd_na = (struct nd_neighbor_advert *)(ip6 + 1);
 	nd_na->nd_na_type = ND_NEIGHBOR_ADVERT;
 	nd_na->nd_na_code = 0;
 	nd_na->nd_na_target = *taddr6;
 	in6_clearscope(&nd_na->nd_na_target); /* XXX */
 
+	/*
+	 * If we respond from CARP address, we need to prepare mac address
+	 * for carp_output().
+	 */
+	if (ifp->if_carp && (tlladdr & ND6_NA_CARP_MASTER))
+		mac = (*carp_macmatch6_p)(ifp, m, taddr6);
 	/*
 	 * "tlladdr" indicates NS's condition for adding tlladdr or not.
 	 * see nd6_ns_input() for details.
 	 * Basically, if NS packet is sent to unicast/anycast addr,
 	 * target lladdr option SHOULD NOT be included.
 	 */
-	if (tlladdr) {
+	if (tlladdr & ND6_NA_OPT_LLA) {
 		/*
 		 * sdl0 != NULL indicates proxy NA.  If we do proxy, use
 		 * lladdr in sdl0.  If we are not proxying (sending NA for
 		 * my address) use lladdr configured for the interface.
 		 */
 		if (sdl0 == NULL) {
-			if (ifp->if_carp)
-				mac = (*carp_macmatch6_p)(ifp, m, taddr6);
 			if (mac == NULL)
 				mac = nd6_ifptomac(ifp);
 		} else if (sdl0->sa_family == AF_LINK) {
@@ -1082,7 +1069,7 @@ nd6_na_output_fib(struct ifnet *ifp, const struct in6_addr *daddr6_0,
 				mac = LLADDR(sdl);
 		}
 	}
-	if (tlladdr && mac) {
+	if ((tlladdr & ND6_NA_OPT_LLA) && mac != NULL) {
 		int optlen = sizeof(struct nd_opt_hdr) + ifp->if_addrlen;
 		struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd_na + 1);
 



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202510030802.59382PmL063849>