git: bac4760524a2 - main - netinet6: Fix ND link-layer address option layout for IPoIB

From: Pouria Mousavizadeh Tehrani <pouria_at_FreeBSD.org>
Date: Thu, 09 Jul 2026 18:20:34 UTC
The branch main has been updated by pouria:

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

commit bac4760524a2a15ce75e35251f0c9cdf31732f3f
Author:     Vinícius Ferrão <vinicius@ferrao.net.br>
AuthorDate: 2026-07-09 07:05:53 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-07-09 17:30:04 +0000

    netinet6: Fix ND link-layer address option layout for IPoIB
    
    RFC 4391 (IP over InfiniBand), section 9.3, lays the ND source/target
    link-layer address option out as type, length (3), two reserved zero
    octets, then the 20-octet IPoIB link-layer address.
    The ND code assumed the Ethernet layout (RFC 4861, section 4.6.1)
    everywhere and read/wrote the address directly after the option
    header, i.e. two octets early.
    The option-length sanity check computes 24 for both layouts for
    a 20-octet address, so the mismatch was silent.
    
    PR:             296585
    Reviewed by:    adrian, pouria
    Sponsored by:   VersatusHPC
    Differential Revision: https://reviews.freebsd.org/D58096
---
 sys/netinet6/icmp6.c   | 15 ++++++++++-----
 sys/netinet6/nd6.h     |  1 +
 sys/netinet6/nd6_nbr.c | 47 +++++++++++++++++++++++++++++++++++++----------
 sys/netinet6/nd6_rtr.c | 18 ++++++++++--------
 4 files changed, 58 insertions(+), 23 deletions(-)

diff --git a/sys/netinet6/icmp6.c b/sys/netinet6/icmp6.c
index 7fe18f4a4d2b..e92a433c21b1 100644
--- a/sys/netinet6/icmp6.c
+++ b/sys/netinet6/icmp6.c
@@ -2169,7 +2169,8 @@ icmp6_redirect_input(struct mbuf *m, int off)
 	union nd_opts ndopts;
 	char ip6buf[INET6_ADDRSTRLEN];
 	char *lladdr;
-	int icmp6len, is_onlink, is_router, lladdrlen;
+	int icmp6len, is_onlink, is_router;
+	u_int lladdr_pad, lladdrlen;
 
 	M_ASSERTPKTHDR(m);
 	KASSERT(m->m_pkthdr.rcvif != NULL, ("%s: no rcvif", __func__));
@@ -2296,16 +2297,17 @@ icmp6_redirect_input(struct mbuf *m, int off)
 
 	lladdr = NULL;
 	lladdrlen = 0;
+	lladdr_pad = nd6_lladdr_opt_pad(ifp);
 	if (ndopts.nd_opts_tgt_lladdr) {
-		lladdr = (char *)(ndopts.nd_opts_tgt_lladdr + 1);
+		lladdr = (char *)(ndopts.nd_opts_tgt_lladdr + 1) + lladdr_pad;
 		lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3;
 	}
 
 	if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) {
 		nd6log((LOG_INFO, "%s: lladdrlen mismatch for %s "
-		    "(if %d, icmp6 packet %d): %s\n",
+		    "(if %d, icmp6 packet %u): %s\n",
 		    __func__, ip6_sprintf(ip6buf, &redtgt6),
-		    ifp->if_addrlen, lladdrlen - 2,
+		    ifp->if_addrlen, lladdrlen - 2 - lladdr_pad,
 		    icmp6_redirect_diag(&src6, &reddst6, &redtgt6)));
 		goto bad;
 	}
@@ -2492,6 +2494,7 @@ icmp6_redirect_output(struct mbuf *m0, struct nhop_object *nh)
 	{
 		/* target lladdr option */
 		int len;
+		u_int pad;
 		struct nd_opt_hdr *nd_opt;
 		char *lladdr;
 
@@ -2499,6 +2502,7 @@ icmp6_redirect_output(struct mbuf *m0, struct nhop_object *nh)
 		if (ln == NULL)
 			goto nolladdropt;
 
+		pad = nd6_lladdr_opt_pad(ifp);
 		len = sizeof(*nd_opt) + ifp->if_addrlen;
 		len = (len + 7) & ~7;	/* round by 8 */
 		/* safety check */
@@ -2507,9 +2511,10 @@ icmp6_redirect_output(struct mbuf *m0, struct nhop_object *nh)
 
 		if (ln->la_flags & LLE_VALID) {
 			nd_opt = (struct nd_opt_hdr *)p;
+			memset(nd_opt, 0, len);
 			nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
 			nd_opt->nd_opt_len = len >> 3;
-			lladdr = (char *)(nd_opt + 1);
+			lladdr = (char *)(nd_opt + 1) + pad;
 			bcopy(ln->ll_addr, lladdr, ifp->if_addrlen);
 			p += len;
 		}
diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h
index 92b776b008b9..372d06b6574d 100644
--- a/sys/netinet6/nd6.h
+++ b/sys/netinet6/nd6.h
@@ -380,6 +380,7 @@ void nd6_ns_input(struct mbuf *, int, int);
 void nd6_ns_output(struct ifnet *, const struct in6_addr *,
 	const struct in6_addr *, const struct in6_addr *, uint8_t *);
 caddr_t nd6_ifptomac(struct ifnet *);
+u_int nd6_lladdr_opt_pad(struct ifnet *);
 void nd6_dad_init(void);
 void nd6_dad_start(struct ifaddr *, int);
 void nd6_dad_stop(struct ifaddr *);
diff --git a/sys/netinet6/nd6_nbr.c b/sys/netinet6/nd6_nbr.c
index c345e96f8d3f..0601b55a0f18 100644
--- a/sys/netinet6/nd6_nbr.c
+++ b/sys/netinet6/nd6_nbr.c
@@ -151,7 +151,8 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
 	union nd_opts ndopts;
 	char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
 	char *lladdr;
-	int lladdrlen, rflag, tentative, tlladdr;
+	int rflag, tentative, tlladdr;
+	u_int lladdr_pad, lladdrlen;
 	uint32_t dflags;
 
 	ifa = NULL;
@@ -240,8 +241,9 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
 
 	lladdr = NULL;
 	lladdrlen = 0;
+	lladdr_pad = nd6_lladdr_opt_pad(ifp);
 	if (ndopts.nd_opts_src_lladdr) {
-		lladdr = (char *)(ndopts.nd_opts_src_lladdr + 1);
+		lladdr = (char *)(ndopts.nd_opts_src_lladdr + 1) + lladdr_pad;
 		lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
 	}
 
@@ -302,9 +304,9 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
 
 	if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) {
 		nd6log((LOG_INFO, "nd6_ns_input: lladdrlen mismatch for %s "
-		    "(if %d, NS packet %d)\n",
+		    "(if %d, NS packet %u)\n",
 		    ip6_sprintf(ip6bufs, &taddr6),
-		    ifp->if_addrlen, lladdrlen - 2));
+		    ifp->if_addrlen, lladdrlen - 2 - lladdr_pad));
 		goto bad;
 	}
 
@@ -582,6 +584,7 @@ nd6_ns_output_fib(struct ifnet *ifp, const struct in6_addr *saddr6,
 		struct nd_opt_hdr *nd_opt;
 		char *mac;
 		int optlen;
+		u_int pad;
 
 		mac = NULL;
 		if (ifp->if_carp)
@@ -591,6 +594,7 @@ nd6_ns_output_fib(struct ifnet *ifp, const struct in6_addr *saddr6,
 
 		if (mac != NULL) {
 			nd_opt = (struct nd_opt_hdr *)(nd_ns + 1);
+			pad = nd6_lladdr_opt_pad(ifp);
 			optlen = sizeof(struct nd_opt_hdr) + ifp->if_addrlen;
 			/* 8 byte alignments... */
 			optlen = (optlen + 7) & ~7;
@@ -600,7 +604,8 @@ nd6_ns_output_fib(struct ifnet *ifp, const struct in6_addr *saddr6,
 			bzero(nd_opt, optlen);
 			nd_opt->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
 			nd_opt->nd_opt_len = optlen >> 3;
-			bcopy(mac, nd_opt + 1, ifp->if_addrlen);
+			memcpy((char *)(nd_opt + 1) + pad, mac,
+			    ifp->if_addrlen);
 		}
 	}
 	/*
@@ -680,7 +685,8 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len)
 	char *lladdr;
 	size_t linkhdrsize;
 	int flags, is_override, is_router, is_solicited;
-	int lladdr_off, lladdrlen, checklink;
+	int lladdr_off, checklink;
+	u_int lladdr_pad, lladdrlen;
 	bool flush_holdchain = false;
 
 	NET_EPOCH_ASSERT();
@@ -749,8 +755,9 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len)
 
 	lladdr = NULL;
 	lladdrlen = 0;
+	lladdr_pad = nd6_lladdr_opt_pad(ifp);
 	if (ndopts.nd_opts_tgt_lladdr) {
-		lladdr = (char *)(ndopts.nd_opts_tgt_lladdr + 1);
+		lladdr = (char *)(ndopts.nd_opts_tgt_lladdr + 1) + lladdr_pad;
 		lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3;
 	}
 
@@ -795,8 +802,8 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len)
 
 	if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) {
 		nd6log((LOG_INFO, "nd6_na_input: lladdrlen mismatch for %s "
-		    "(if %d, NA packet %d)\n", ip6_sprintf(ip6bufs, &taddr6),
-		    ifp->if_addrlen, lladdrlen - 2));
+		    "(if %d, NA packet %u)\n", ip6_sprintf(ip6bufs, &taddr6),
+		    ifp->if_addrlen, lladdrlen - 2 - lladdr_pad));
 		goto bad;
 	}
 
@@ -1087,6 +1094,7 @@ nd6_na_output_fib(struct ifnet *ifp, const struct in6_addr *daddr6_0,
 		}
 	}
 	if ((tlladdr & ND6_NA_OPT_LLA) && mac != NULL) {
+		u_int pad = nd6_lladdr_opt_pad(ifp);
 		int optlen = sizeof(struct nd_opt_hdr) + ifp->if_addrlen;
 		struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd_na + 1);
 
@@ -1099,7 +1107,7 @@ nd6_na_output_fib(struct ifnet *ifp, const struct in6_addr *daddr6_0,
 		bzero((caddr_t)nd_opt, optlen);
 		nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
 		nd_opt->nd_opt_len = optlen >> 3;
-		bcopy(mac, (caddr_t)(nd_opt + 1), ifp->if_addrlen);
+		memcpy((char *)(nd_opt + 1) + pad, mac, ifp->if_addrlen);
 	} else
 		flags &= ~ND_NA_FLAG_OVERRIDE;
 
@@ -1156,6 +1164,25 @@ nd6_ifptomac(struct ifnet *ifp)
 	}
 }
 
+/*
+ * Number of reserved octets between the header of an ND link-layer
+ * address option and the link-layer address itself.  On IPoIB links
+ * two zero octets are prepended to the 20-octet link-layer address
+ * to fill the 24-octet option (RFC 4391, section 9.3); on all other
+ * link types the address immediately follows the option header.
+ */
+u_int
+nd6_lladdr_opt_pad(struct ifnet *ifp)
+{
+	switch (ifp->if_type) {
+	case IFT_INFINIBAND:
+	case IFT_INFINIBANDLAG:
+		return (2);
+	default:
+		return (0);
+	}
+}
+
 struct dadq {
 	TAILQ_ENTRY(dadq) dad_list;
 	struct ifaddr *dad_ifa;
diff --git a/sys/netinet6/nd6_rtr.c b/sys/netinet6/nd6_rtr.c
index 637f00bef2c9..167f3704f02d 100644
--- a/sys/netinet6/nd6_rtr.c
+++ b/sys/netinet6/nd6_rtr.c
@@ -171,7 +171,7 @@ nd6_rs_input(struct mbuf *m, int off, int icmp6len)
 	union nd_opts ndopts;
 	char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
 	char *lladdr;
-	int lladdrlen;
+	u_int lladdr_pad, lladdrlen;
 
 	ifp = m->m_pkthdr.rcvif;
 
@@ -226,16 +226,17 @@ nd6_rs_input(struct mbuf *m, int off, int icmp6len)
 
 	lladdr = NULL;
 	lladdrlen = 0;
+	lladdr_pad = nd6_lladdr_opt_pad(ifp);
 	if (ndopts.nd_opts_src_lladdr) {
-		lladdr = (char *)(ndopts.nd_opts_src_lladdr + 1);
+		lladdr = (char *)(ndopts.nd_opts_src_lladdr + 1) + lladdr_pad;
 		lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
 	}
 
 	if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) {
 		nd6log((LOG_INFO,
-		    "%s: lladdrlen mismatch for %s (if %d, RS packet %d)\n",
+		    "%s: lladdrlen mismatch for %s (if %d, RS packet %u)\n",
 		    __func__, ip6_sprintf(ip6bufs, &saddr6),
-		    ifp->if_addrlen, lladdrlen - 2));
+		    ifp->if_addrlen, lladdrlen - 2 - lladdr_pad));
 		goto bad;
 	}
 
@@ -467,18 +468,19 @@ nd6_ra_opt_src_lladdr(struct nd_opt_hdr *opthdr, struct ifnet *ifp,
 {
 	char ip6bufs[INET6_ADDRSTRLEN];
 	char *lladdr = NULL;
-	int lladdrlen = 0;
+	u_int lladdr_pad = nd6_lladdr_opt_pad(ifp);
+	u_int lladdrlen = 0;
 
 	if (opthdr != NULL) {
-		lladdr = (char *)(opthdr + 1);
+		lladdr = (char *)(opthdr + 1) + lladdr_pad;
 		lladdrlen = opthdr->nd_opt_len << 3;
 	}
 
 	if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) {
 		nd6log((LOG_INFO,
-		    "%s: lladdrlen mismatch for %s (if %d, RA packet %d)\n",
+		    "%s: lladdrlen mismatch for %s (if %d, RA packet %u)\n",
 		    __func__, ip6_sprintf(ip6bufs, &saddr6),
-		    ifp->if_addrlen, lladdrlen - 2));
+		    ifp->if_addrlen, lladdrlen - 2 - lladdr_pad));
 		return (-1);
 	}