The branch main has been updated by ae:

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

commit 716acd9367df49d44f29eeb783706025f3a04c65
Author:     Andrey V. Elsukov <a...@freebsd.org>
AuthorDate: 2025-10-03 07:57:44 +0000
Commit:     Andrey V. Elsukov <a...@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);
 

Reply via email to