Bug 296585 - netinet6: IPv6 Neighbor Discovery broken over IPoIB
Summary: netinet6: IPv6 Neighbor Discovery broken over IPoIB
Status: In Progress
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: 16.0-CURRENT
Hardware: Any Any
: --- Affects Some People
Assignee: Pouria Mousavizadeh Tehrani
URL: https://reviews.freebsd.org/D58096
Keywords:
Depends on:
Blocks:
 
Reported: 2026-07-08 01:10 UTC by Vinícius Ferrão
Modified: 2026-07-09 18:21 UTC (History)
1 user (show)

See Also:


Attachments
Proposed patch fixing the issue (12.98 KB, application/mbox)
2026-07-08 01:10 UTC, Vinícius Ferrão
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Vinícius Ferrão 2026-07-08 01:10:19 UTC
Created attachment 272580 [details]
Proposed patch fixing the issue

IPv6 does not work over IPoIB between FreeBSD and Linux. On an InfiniBand
fabric with IPoIB interfaces up on both sides, ping6 from a Linux host
(AlmaLinux 10, kernel 6.x) to a FreeBSD host gets 100% packet loss and the
Linux neighbor entry for the FreeBSD address stays FAILED. IPv4 ARP on the
same interfaces works fine. Since IPv6 neighbor resolution never completes,
everything above it is dead too: rdma_cm cannot resolve IPv6 addresses to
GIDs, so NFS-over-RDMA-over-InfiniBand is unusable with IPv6.

The problem is in how FreeBSD builds and parses the ND link-layer address
option on IPoIB. IPoIB uses 20-byte hardware addresses, and RFC 4391
(section 9.3) says the option must be: Type (1 byte), Length (1 byte) = 3,
then two zero octets, then the 20-byte address ("two octets of zero MUST be
prepended to fill the total option length of 24 octets"). Linux does this
(net/ipv6/ndisc.c, ndisc_addr_option_pad() returns 2 for
ARPHRD_INFINIBAND).

FreeBSD assumes the address always starts right after the 2-byte option
header. That is correct for Ethernet (RFC 4861, 4.6.1), wrong for IPoIB.
The assumption is baked into both sides of the code:

- parse: nd6_ns_input(), nd6_na_input() (sys/netinet6/nd6_nbr.c),
  nd6_rs_input(), nd6_ra_opt_src_lladdr() (sys/netinet6/nd6_rtr.c),
  icmp6_redirect_input() (sys/netinet6/icmp6.c): all
  `lladdr = (char *)(opt + 1);`
- build: nd6_ns_output_fib(), nd6_na_output_fib() (nd6_nbr.c),
  icmp6_redirect_output() (icmp6.c): all `bcopy(mac, opt + 1, if_addrlen)`,
  which leaves the pad at the end of the option instead of the start.

Nothing catches this. The option-length sanity check
`((if_addrlen + 2 + 7) & ~7) != lladdrlen` computes 24 for
if_addrlen = 20 under either layout, so the bad options pass silently.

On the wire (tcpdump -XX, FreeBSD 16.0-CURRENT server, Linux client) the
failure looks like this:

1. The Linux NS carries the client's address at option offset 4, after the
   reserved field. FreeBSD reads offset 2 and caches a 2-byte-shifted
   garbage lladdr; the GID embedded in it does not exist on the fabric.
2. FreeBSD then sends its solicited NA (and its own unicast NS probes) to
   that nonexistent IPoIB destination. They never arrive: the client counts
   0 inbound packets, its neighbor entry stays FAILED, and FreeBSD's ndp
   never learns the client either.
3. FreeBSD's own NA/NS options carry its address at offset 2 with the pad
   trailing, so even delivered options would be mis-parsed by a conformant
   receiver. Both directions are broken.

FreeBSD-to-FreeBSD IPv6-over-IPoIB appears to work only because both ends
share the same non-conformant layout. The bug shows against any conformant
stack.

Instructions on how to reproduce:

    freebsd# ifconfig ib0 inet6 fc00:10:10::1/64 alias
    linux# ip -6 addr add fc00:10:10::2/64 dev ib0
    linux# ping6 -c3 fc00:10:10::1        # 100% loss
    linux# ip -6 neigh show dev ib0       # fc00:10:10::1 FAILED
    freebsd# tcpdump -ni ib0 -e -vv -XX icmp6
    # inbound NS option: 01 03 00 00 <20-byte addr>   (RFC 4391 layout)
    # outbound NA option: 02 03 <20-byte addr> 00 00  (address at wrong offset)

Proposed patch:

Patch attached (against main). It adds nd6_lladdr_opt_pad(ifp), the analog
of Linux's ndisc_addr_option_pad(): returns 2 for IFT_INFINIBAND /
IFT_INFINIBANDLAG, 0 otherwise. The five parse sites read the address at
(opt + 1) + pad, and the three build sites size the option as
hdr + pad + addrlen, zero it, and copy the address at (opt + 1) + pad.
While here: icmp6_redirect_output() previously did not zero the option
area, which leaked 2 uninitialized mbuf bytes in the trailing pad on
20-byte-addrlen links; the patch fixes that too. No functional change for
pad == 0 link types (Ethernet etc.).
Comment 1 commit-hook freebsd_committer freebsd_triage 2026-07-09 18:21:04 UTC
A commit in branch main references this bug:

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(-)