Re: [RFC net-next] ipv6: update NUD_FAILED neighbors from NA messages
From: Lawrence Lee
Date: Tue Aug 25 2026 - 14:08:04 EST
On Tue Aug 25, 2026 at 11:52 AM UTC, Ido Schimmel wrote:
> On Thu, Aug 13, 2026 at 11:33:44PM +0000, Lawrence Lee wrote:
> > I noticed an inconsistency between IPv4 and IPv6 in how the kernel handles
> > neighbor advertisements/ARP replies for NUD_FAILED neighbor entries
> > and would like some guidance/input from maintainers.
> >
> > For an existing IPv4 neighbor that is in the NUD_FAILED state, receiving an
> > ARP reply for the neighbor IP will update the entry in the kernel to
> > either NUD_STALE or NUD_REACHABLE depending on if the reply is unicast or
> > broadcast.
> >
> > For an existing IPv6 neighbor that is in the NUD_FAILED state, receiving a
> > neighbor advertisement (NA) for the neighbor IP does nothing as
> > ndisc_recv_na() explicitly ignores NUD_FAILED neighbor entries:
> >
> > if (READ_ONCE(neigh->nud_state) & NUD_FAILED)
> > goto out;
> >
> > This check was added by commit titled "[IPV6] Don't update FAILED
> > entries on receipt of NAs." (Hideaki Yoshifuji, 2005-01-16; pre-git, in
> > mainline since v2.6.12-rc2) with the justification "As NAs do not create
> > new entries (RFC2461 7.2.5), NA should not change state of FAILED entries."
> >
> > However, RFC9131 introduced a method for NAs to create new neighbor entries
> > (implemented as `accept_unsolicited_na` and later renamed to
> > `accept_untracked_na`), which means the original justification for ignoring
> > NAs for NUD_FAILED neighbor entries is no longer 100% correct. I think to
> > remain logically consistent, it makes sense to allow NAs to update
> > NUD_FAILED entries anytime we allow creating new entries with
> > `accept_untracked_na`.
> >
> > I realize that RFC9131 section 4.2 states the following:
> >
> > ... routers create a new Neighbor Cache entry upon
> > receiving an unsolicited Neighbor Advertisement for an address that
> > does not already have a Neighbor Cache entry. These changes do not
> > modify the router behavior specified in [RFC4861] for the scenario
> > when the corresponding Neighbor Cache entry already exists.
> >
> > However, I would argue that since NUD_FAILED is purely a kernel construct
> > and has no equivalent state defined in RFC4861 section 7.3.2, a neighbor in
> > state NUD_FAILED does not actually have a valid Neighbor Cache entry as
> > defined by RFC4861 and should be treated as if the neighbor entry doesn't
> > exist; therefore NUD_FAILED neighbors does fall within the scope of
> > RFC9131.
> >
> > The motivation for this question comes from my work on SONiC, a network OS
> > which is built on top of Debian and runs on switching hardware. We have
> > encountered an issue where the switch receives traffic for an IPv6 neighbor
> > before that neighbor is resolvable, which leads to the kernel neighbor
> > being set to NUD_FAILED. When the IPv6 neighbor becomes ready to receive
> > traffic, it sends an unsolicited NA to the switch which gets ignored
> > because the kernel neighbor is NUD_FAILED. Subsequent traffic destined to
> > this neighbor stays entirely within the switch ASIC and isn't visible to
> > the kernel, so there's no stimulus for the kernel to send neighbor
> > solicitations; as a result, the neighbor entry stays unresolved and traffic
> > to the neighbor is dropped.
>
> Why "Subsequent traffic destined to this neighbor stays entirely within
> the switch ASIC and isn't visible to the kernel"? If the neighbour is
> unresolved and you're relying on the kernel to perform the resolution,
> then you should trap these packets and inject them to the kernel's Rx
> path. This should provide "stimulus for the kernel to send neighbor
> solicitations".
Normally, this is what happens. However, I am working on a scenario
where we have two switches providing connectivity to a single rack of
servers to provide increased redundancy. Each server is connected to
both switches using a single cable with 3 ends, and for each server one
switch is designated as the 'active' switch and the other as 'standby'
at any given time. When we have a FAILED neighbor on one switch, we
cannot determine which specific server/switch interface that neighbor
should be associated with. For any traffic destined to the FAILED
neighbor IP, we route it to the peer switch to maximize the chance that
packets can be forwarded successfully (with the hope that the peer is
able to resolve the neighbor entry). The routing to the peer takes place
entirely within the ASIC, which is why we cannot rely on the kernel to
resolve the neighbor in this particular scenario. There is a publicly
available HLD on this feature if you are interested:
https://github.com/sonic-net/SONiC/blob/master/doc/dualtor/dualtor_active_standby_hld.md#6352-neighbor-miss-due-to-one-side-link-down
When we see this particular issue, the traffic is landing on the
'active' switch for this particular neighbor IP/server, the switch just
doesn't know it yet since the neighbor isn't resolved. The packets get
routed to the peer switch, which is 'standby' for the same server. On
the peer, these packets will get trapped to the kernel but the peer
still cannot resolve the neighbor since it is in 'standby' mode for the
associated interface/server (this is a constraint of the physical cable
that is used to connect the server to both switches, the cable drops
traffic from the standby switch).
The routing to the peer was originally intended to still allow
forwarding when a physical link/cabling issue prevents one switch from
resolving a neighbor entry. It just has the unfortunate effect of
preventing the switch from ever resolving the neighbor entry if we get
traffic for a neighbor IP before it's ready/resolvable.
> If you can't do this for some reason (please explain why), then you can
> either:
>
> 1. Trigger the resolution from user space via NTF_USE.
The switch unfortunately has no mechanism to determine when any neighbor
becomes ready and resolvable so I'm not sure that this is feasible.
SONiC does already have a mechanism to retry resolution of FAILED
neighbors but since it only runs periodically, it's not always
performant enough for every situation. We are hesitant to increase the
retry frequency due to concerns about CPU load.
> 2. Configure offloaded neighbours with NTF_EXT_MANAGED so that the
> kernel will periodically probe them and keep them reachable when
> possible.
I believe NTF_EXT_MANAGED neighbors would have similar drawbacks to the
existing resolution retry mechanism in SONiC. Retrying too frequently
risks excessive CPU load especially with many neighbor entries in the
kernel, but reducing the frequency means longer periods of time where
traffic cannot be forwarded.
> > The main questions I'd like to pose:
> >
> > 1. When RFC9131 was implemented (`accept_untracked_na`), was an intentional
> > choice made to not update the handling of NUD_FAILED neighbors? I
> > searched through the discussions for all three commits relevant to this
> > feature but did not find any mention of NUD_FAILED handling:
> > commit f9a2fb73318e ("net/ipv6: Introduce accept_unsolicited_na knob to implement router-side changes for RFC9131")
> > commit 3e0b8f529c10 ("net/ipv6: Expand and rename accept_unsolicited_na to accept_untracked_na")
> > commit aaa5f515b16b ("net: ipv6: new accept_untracked_na option to accept na only if in-network")
>
> AFAIK it wasn't an intentional decision to not update NUD_FAILED
> neighbours.
>
> >
> > 2. Should unsolicited NAs be allowed to update NUD_FAILED neighbors when
> > `accept_untracked_na` is enabled (this would align with existing
> > IPv4/ARP behavior that allows ARP replies to update NUD_FAILED
> > neighbors).
>
> Looks fine, but I suggest first evaluating the alternatives above. They
> don't require any kernel changes. Besides, dropping traffic to an
> unresolved neighbour instead of trapping to the CPU doesn't make a lot
> of sense.
Unfortunately some of the platforms that we need to support have fairly
weak CPUs. If we tune the alternatives to minimize the amount of time
where the neighbor is unresolved and traffic gets dropped, there is a
risk of excessive CPU usage which could in turn cause other issues.
> Also, note that "align with existing IPv4/ARP behavior" is not accurate:
> arp_process() updates an existing NUD_FAILED entry unconditionally and
> it can transition from NUD_FAILED to NUD_REACHABLE. So, your patch is
> strictly more restrictive than IPv4 rather than aligned with it.
Sorry I should have been more specific before. Maybe a better way to
phrase it is that this would be analogous to IPv4 in the sense that it
allows NUD_FAILED neighbors to change states upon receipt of a NA, but
by using NUD_STALE it remains consistent with RFC9131 Section 6.1.1.