git: c4b6df9a7cc9 - stable/15 - netinet6: Fix ND link-layer address option layout for IPoIB (stable/15)
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 02 Nov 2026 15:49:29 UTC
The branch stable/15 has been updated by adrian:
URL: https://cgit.FreeBSD.org/src/commit/?id=c4b6df9a7cc977cbe842b4fa9499af15b48a1b88
commit c4b6df9a7cc977cbe842b4fa9499af15b48a1b88
Author: Vinícius Ferrão <vinicius@ferrao.net.br>
AuthorDate: 2026-11-02 15:45:03 +0000
Commit: Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2026-11-02 15:46:42 +0000
netinet6: Fix ND link-layer address option layout for IPoIB (stable/15)
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.
Against a conformant peer (e.g. Linux) this broke IPv6 over IPoIB in both
directions: the address parsed from a received NS/NA option was shifted by two
bytes, so the cached link-layer address embedded a GID that does not exist on
the fabric and replies were sent nowhere, and the options we built carried our
address at the wrong offset for the peer. IPv4/ARP was unaffected. This blocked
everything IPv6 over IPoIB against Linux, including rdma_cm's IP-to-GID
resolution and therefore NFS-over-RDMA-over-InfiniBand with IPv6.
Add nd6_lladdr_opt_pad(), the analog of Linux's ndisc_addr_option_pad(): the
number of reserved octets between the option header and the address (2 for
IFT_INFINIBAND and IFT_INFINIBANDLAG, 0 otherwise). Apply it at the option parse
sites (nd6_ns_input(), nd6_na_input(), nd6_rs_input(), nd6_ra_input(),
icmp6_redirect_input()) and the option build sites (nd6_ns_output_fib(),
nd6_na_output_fib(), icmp6_redirect_output()). Zero the redirect target option
before filling it; previously its trailing pad bytes were left uninitialized on
links with 20-octet addresses. No functional change for link types without
reserved octets.
This is a direct commit to stable/15, equivalent to main commit
bac4760524a2a15ce75e35251f0c9cdf31732f3f (D58096). It could not be cherry-picked
cleanly: the neighbor-discovery code diverged on main (the router advertisement
parse was refactored into helpers), so the same fix is reimplemented against the
stable/15 sources.
PR: 296585
Sponsored by: VersatusHPC
Reviewed by: pouria
Differential Revision: https://reviews.freebsd.org/D58132
---
sys/netinet6/icmp6.c | 9 +++++++--
sys/netinet6/nd6.h | 1 +
sys/netinet6/nd6_nbr.c | 35 +++++++++++++++++++++++++++++++----
sys/netinet6/nd6_rtr.c | 7 +++++--
4 files changed, 44 insertions(+), 8 deletions(-)
diff --git a/sys/netinet6/icmp6.c b/sys/netinet6/icmp6.c
index ec2e5a22aa33..a2459bc82f23 100644
--- a/sys/netinet6/icmp6.c
+++ b/sys/netinet6/icmp6.c
@@ -2170,6 +2170,7 @@ icmp6_redirect_input(struct mbuf *m, int off)
char ip6buf[INET6_ADDRSTRLEN];
char *lladdr;
int icmp6len, is_onlink, is_router, lladdrlen;
+ u_int lladdr_pad;
M_ASSERTPKTHDR(m);
KASSERT(m->m_pkthdr.rcvif != NULL, ("%s: no rcvif", __func__));
@@ -2296,8 +2297,9 @@ 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;
}
@@ -2490,6 +2492,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;
@@ -2497,6 +2500,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 */
@@ -2505,9 +2509,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 e484c709e29a..566b92fc783f 100644
--- a/sys/netinet6/nd6.h
+++ b/sys/netinet6/nd6.h
@@ -390,6 +390,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 29151b29a071..d79bb17196a4 100644
--- a/sys/netinet6/nd6_nbr.c
+++ b/sys/netinet6/nd6_nbr.c
@@ -139,6 +139,7 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
char *lladdr;
int anycast, lladdrlen, proxy, rflag, tentative, tlladdr;
+ u_int lladdr_pad;
ifa = NULL;
@@ -226,8 +227,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;
}
@@ -559,6 +561,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)
@@ -568,6 +571,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;
@@ -577,7 +581,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);
}
}
/*
@@ -662,6 +667,7 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len)
size_t linkhdrsize;
int flags, is_override, is_router, is_solicited;
int lladdr_off, lladdrlen, checklink;
+ u_int lladdr_pad;
bool flush_holdchain = false;
NET_EPOCH_ASSERT();
@@ -730,8 +736,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;
}
@@ -1072,6 +1079,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);
@@ -1084,7 +1092,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;
@@ -1141,6 +1149,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 4735685a61cd..c497fc9fa6f1 100644
--- a/sys/netinet6/nd6_rtr.c
+++ b/sys/netinet6/nd6_rtr.c
@@ -169,6 +169,7 @@ nd6_rs_input(struct mbuf *m, int off, int icmp6len)
char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
char *lladdr;
int lladdrlen;
+ u_int lladdr_pad;
ifp = m->m_pkthdr.rcvif;
@@ -223,8 +224,9 @@ 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;
}
@@ -577,9 +579,10 @@ nd6_ra_input(struct mbuf *m, int off, int icmp6len)
{
char *lladdr = NULL;
int lladdrlen = 0;
+ u_int 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;
}