2

I've been doing some work with ARP/NDP and discovered that ndisc6 doesn't update the neighbor table (unlike arping).

arping example: 192.168.0.14 is configured on a device connected to a VLAN member so we are able to resolve its address.

root@ubuntu:/# ip neigh show 192.168.0.14
root@ubuntu:/# arping 192.168.0.14 -i Vlan1000
ARPING 192.168.0.14
60 bytes from ca:6c:4c:92:85:1c (192.168.0.14): index=0 time=38.088 msec
60 bytes from ca:6c:4c:92:85:1c (192.168.0.14): index=1 time=84.431 msec
60 bytes from ca:6c:4c:92:85:1c (192.168.0.14): index=2 time=119.154 msec
60 bytes from ca:6c:4c:92:85:1c (192.168.0.14): index=3 time=53.739 msec
^C
--- 192.168.0.14 statistics ---
4 packets transmitted, 4 packets received,   0% unanswered (0 extra)
rtt min/avg/max/std-dev = 38.088/73.853/119.154/31.015 ms
root@ubuntu:/# ip neigh show 192.168.0.14
192.168.0.14 dev Vlan1000 lladdr ca:6c:4c:92:85:1c REACHABLE

As you can see, arping is able to add a new entry into the kernel neighbor table as shown by ip neigh

For the analogous IPv6 example, ndisc6 cannot accomplish this:

root@ubuntu:/# ip neigh show fc02:1000::3
root@ubuntu:/# ndisc6 fc02:1000::3 Vlan1000
Soliciting fc02:1000::3 (fc02:1000::3) on Vlan1000...
Target link-layer address: CA:58:C2:5F:7C:02
 from fc02:1000::3
root@ubuntu:/# ip neigh show fc02:1000::3
root@ubuntu:/#

fc02:1000::3 is a similarly valid IP address configured on a device connected to a VLAN member. Despite receiving a valid neighbor advertisement message, the kernel neighbor table is not update as shown by the empty output from ip neigh

I have also found a reference to someone else seeing the same issue in the comments to this answer but without any explanation as to why.

2 Answers2

1

Kernel is the place where neighbor accounting is kept. So if an userspace tool generates an ARP (for IPv4) query or an NDP query (for IPv6) by using raw sockets (AF_PACKET for ARP and AF_INET6, SOCK_RAW for NDP) the reply doesn't matter to the kernel...

... except Linux kernel has an IPv4 ARP toggle to have it trust received ARP traffic even if not solicited. So the ARP reply to the arping command would then make kernel add an ARP entry, but there is no such toggle for IPv6.

I admit there is also a difference in behavior on Linux when using the generic multi-OS arping tool instead of iputils-arping: in the former case the ARP entry is created nevertheless "for some reason".


Anyway, this doesn't happen with IPv6 but it's quite easy to work around: create the entry first, update it later with ndisc6 since the kernel will have an entry and will process the reply. The best state type to use is stale: considered valid but unreliable.

To avoid some unnecessary disruption, ip neigh change is tried first without providing a MAC address so a former probably valid entry is kept, and if it fails because there was no entry or it was possibly in failed state (for these cases the MAC address is mandatory), use ip neigh replace (rather than ip neigh add to avoid a race condition between the two commands) and provide a fake local MAC address.

For OP's case:

ip -6 neigh change fc02:1000::3 nud stale dev Vlan1000 ||
    ip -6 neigh replace fc02:1000::3 lladdr 02:00:00:00:00:00 nud stale dev Vlan1000
ndisc6 fc02:1000::3 Vlan1000

Now this should give a correct entry:

ip -6 neigh show fc02:1000::3
A.B
  • 13,968
1

This behavior is by design and is not a problem with ndisc6 specifically nor is it an issue with kernel/userspace separation. According to RFC 4861 (NDP)

When a valid Neighbor Advertisement is received (either solicited or unsolicited), the Neighbor Cache is searched for the target's entry. If no entry exists, the advertisement SHOULD be silently discarded. There is no need to create an entry if none exists, since the recipient has apparently not initiated any communication with the target.

In my case, because I had not tried to communicate with the target IPv6 address prior to running ndisc6, the NA message received by the kernel was ignored. Newer versions of the Linux kernel include the accept_untracked_na parameter which allows this behavior to be adjusted:

Define behavior for accepting neighbor advertisements from devices that are absent in the neighbor cache:
0 - (default) Do not accept unsolicited and untracked neighbor advertisements.
1 - Add a new neighbor cache entry in STALE state for routers on receiving a neighbor advertisement (either solicited or unsolicited) with target link-layer address option specified if no neighbor entry is already present for the advertised IPv6 address. Without this knob, NAs received for untracked addresses (absent in neighbor cache) are silently ignored.
This is as per router-side behavior documented in RFC9131.
This has lower precedence than drop_unsolicited_na.
This will optimize the return path for the initial off-link communication that is initiated by a directly connected host, by ensuring that the first-hop router which turns on this setting doesn’t have to buffer the initial return packets to do neighbor-solicitation. The prerequisite is that the host is configured to send unsolicited neighbor advertisements on interface bringup. This setting should be used in conjunction with the ndisc_notify setting on the host to satisfy this prerequisite.
2 - Extend option (1) to add a new neighbor cache entry only if the source IP address is in the same subnet as an address configured on the interface that received the neighbor advertisement.