ipv6: update NUD_FAILED neighbors from NA messages

Transition a FAILED neighbor entry to STALE upon receipt of an NA
message on routers when accept_untracked_na is enabled. This extends the
RFC 9131 accept_untracked_na behavior so that FAILED entries are treated
the same as non-existent entries.

RFC 4861 section 7.3.3 says that an entry should be deleted when address
resolution fails. Linux instead retains the entry in NUD_FAILED, so
treating it as untracked is consistent with the protocol model.

Trying to resolve FAILED neighbors via periodic probing (e.g. using
NTF_EXT_MANAGED) is more work compared to this approach which uses
information in NAs that the kernel may already be receiving. Note that
because this behavior in IPv6 is dependent on the accept_untracked_na
sysctl setting, this approach is more conservative than IPv4 which
transitions FAILED neighbors to STALE by default upon receiving GARPs.

Link: https://lore.kernel.org/r/20260813233344.445265-1-lfqlee314@gmail.com
Signed-off-by: Lawrence Lee <lfqlee314@gmail.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Link: https://patch.msgid.link/6be400e601f4ffa59e47ddb5daa33ecdd85dd88b.1790127207.git.lfqlee314@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Lawrence Lee 2026-09-23 01:34:18 +00:00 committed by Jakub Kicinski
parent 7ab4bbdca9
commit 24717a02ae
2 changed files with 41 additions and 34 deletions

View File

@ -3225,18 +3225,19 @@ drop_unsolicited_na - BOOLEAN
Default: 0 (disabled).
accept_untracked_na - INTEGER
Define behavior for accepting neighbor advertisements from devices that
are absent in the neighbor cache:
Define behavior for accepting neighbor advertisements for IPv6 addresses
that are absent from the neighbor cache or whose entries are in FAILED
state:
- 0 - (default) Do not accept unsolicited and untracked neighbor
advertisements.
- 0 - (default) Do not create new neighbor cache entries or update
FAILED entries from 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.
- 1 - For routers, add a new neighbor cache entry or update an existing
FAILED entry to STALE upon receiving a neighbor advertisement (either
solicited or unsolicited) with the target link-layer address option
specified. Without this knob, NAs received for untracked addresses
(absent from the neighbor cache or in FAILED state) are silently
ignored.
This is as per router-side behavior documented in RFC9131.
@ -3251,9 +3252,10 @@ accept_untracked_na - INTEGER
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.
- 2 - Extend option (1) to add a new neighbor cache entry or update a
FAILED entry only if the source IP address is in the same subnet as
an address configured on the interface that received the neighbor
advertisement.
enhanced_dad - BOOLEAN
Include a nonce option in the IPv6 neighbor solicitation messages used for

View File

@ -973,13 +973,13 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)
static int accept_untracked_na(struct inet6_dev *idev, struct in6_addr *saddr)
{
switch (READ_ONCE(idev->cnf.accept_untracked_na)) {
case 0: /* Don't accept untracked na (absent in neighbor cache) */
case 0: /* Don't accept untracked NA (absent or FAILED) */
return 0;
case 1: /* Create new entries from na if currently untracked */
case 1: /* Create new or update FAILED entries from NA */
return 1;
case 2: /* Create new entries from untracked na only if saddr is in the
case 2: /* Create new or update FAILED entries only if saddr is in the
* same subnet as an address configured on the interface that
* received the na
* received the NA
*/
return !!ipv6_chk_prefix(saddr, idev->dev);
default:
@ -1067,34 +1067,39 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
neigh = neigh_lookup(tbl, &msg->target, dev);
/* RFC 9131 updates original Neighbour Discovery RFC 4861.
* NAs with Target LL Address option without a corresponding
* entry in the neighbour cache can now create a STALE neighbour
* cache entry on routers.
* NAs with Target LL Address option can now create a STALE neighbor
* cache entry on routers if the NA does not have a corresponding entry
* in the neighbour cache or has a corresponding FAILED entry.
*
* entry accept fwding solicited behaviour
* ------- ------ ------ --------- ----------------------
* present X X 0 Set state to STALE
* present X X 1 Set state to REACHABLE
* absent 0 X X Do nothing
* absent 1 0 X Do nothing
* absent 1 1 X Add a new STALE entry
* entry accept fwding solicited behaviour
* ----------- ------ ------ --------- ----------------------
* non-FAILED X X 0 Set state to STALE
* non-FAILED X X 1 Set state to REACHABLE
* FAILED 0 X X Do nothing
* FAILED 1 0 X Do nothing
* FAILED 1 1 X Set state to STALE
* absent 0 X X Do nothing
* absent 1 0 X Do nothing
* absent 1 1 X Add a new STALE entry
*
* Note that we don't do a (daddr == all-routers-mcast) check.
*/
new_state = msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE;
if (!neigh && lladdr && idev && READ_ONCE(idev->cnf.forwarding)) {
if (accept_untracked_na(idev, saddr)) {
neigh = neigh_create(tbl, &msg->target, dev);
new_state = NUD_STALE;
if (!neigh || (READ_ONCE(neigh->nud_state) & NUD_FAILED)) {
if (!lladdr || !idev || !READ_ONCE(idev->cnf.forwarding) ||
!accept_untracked_na(idev, saddr)) {
if (neigh)
neigh_release(neigh);
return reason;
}
if (!neigh)
neigh = neigh_create(tbl, &msg->target, dev);
new_state = NUD_STALE;
}
if (neigh && !IS_ERR(neigh)) {
u8 old_flags = neigh->flags;
if (READ_ONCE(neigh->nud_state) & NUD_FAILED)
goto out;
/*
* Don't update the neighbor cache entry on a proxy NA from
* ourselves because either the proxied node is off link or it