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/[email protected]
Assisted-by: LLM Sashiko sparse
Signed-off-by: Lawrence Lee <[email protected]>
---
The existing 6LoWPAN override-only handling also applies to INCOMPLETE
entries and is intentionally left unchanged. The existing
rt6_clean_tohost() source-versus-target behavior applies to all neighbor
states and is also left unchanged.

 Documentation/networking/ip-sysctl.rst | 28 ++++++++-------
 net/ipv6/ndisc.c                       | 47 ++++++++++++++------------
 2 files changed, 41 insertions(+), 34 deletions(-)

diff --git a/Documentation/networking/ip-sysctl.rst 
b/Documentation/networking/ip-sysctl.rst
index f7af0286341c..685c84cf543d 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -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
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index 90cd5d852569..12d85d7f8234 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -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
-- 
2.43.0

Reply via email to