From nobody Fri Sep 25 08:45:38 2026 Received: from mail-pj2-f13.google.com (mail-pj2-f13.google.com [74.125.227.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A3AA043A7E4 for ; Tue, 15 Sep 2026 05:01:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.141 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789448500; cv=none; b=eqLErX55Xv48bdzYFWO7k531lc2TdRAAdxQRCFFTcFYjdWv4WlxqlxWSbNmTBQUAk7pF21PCm2sNdFX3SHSaoPw0+RNc4KdYTQnRv4nsLGL2QBB5f0LK+O75O8TeBuRVLNl0QlK32Dx2bjk9Bnf6k6gmllf+CQ6Zfjl3l+GihJI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789448500; c=relaxed/simple; bh=x1gejsAd6YWA5CBEc/ub3FzHb3dF1/qcv14ggelBuvA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=azzkhMzGJVvqlqyFfN99WFQZqXJ3uzj6HRJkeTQJkAca7SPcXFoEmayArNZVvPqGZfI57737gbWXI1a3SGVDhY3E+RA9Ib1nhQENJLi0ET4C+yn2wzUOdz6mmZcoOzMN2Da1HZfFflG7EEhEaSSu/j/9L3FgbkSB/IiRYk9UUUE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ODXyVOZX; arc=none smtp.client-ip=74.125.227.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ODXyVOZX" Received: by mail-pj2-f13.google.com with SMTP id 98e67ed59e1d1-396ccafb751so2836969a91.2 for ; Mon, 14 Sep 2026 22:01:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789448497; x=1790053297; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=qAZ/bCmEsYsB0LV/q1vRmEmrcb+izaYrj4LP1qy1vmw=; b=ODXyVOZX476O2LF2w9Y8f1RHyQIfPgCsAjNgWF600jwQS8GzP27wWYHOp7oddrhJOM ywfjjB9olLkBAUGluR3cB5GOSGoH/vh9jzwlEIyeagt9Rfh2xWslkaUulfHKVzDpP3nd NmsM56w07BucFsKak3j+v7W7VGkdtrVnnIrtjS/gWaQiHf/2dDKwwaxBmF7tL6W+oW6u gWFgKDycU+Fm7JHGyYk3Q4UNhIwO0MutwTeICSgQuQ9ekEfHi4S5NVna8Say4RqkX+/Y RWpslQRnc9CHAO/krmXw2+aMY69iLB/ZHNzrwFnCW+8WRlnbVfSAam+aCrtMet/fJexV gdBQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789448497; x=1790053297; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=qAZ/bCmEsYsB0LV/q1vRmEmrcb+izaYrj4LP1qy1vmw=; b=B1pZCArU4GH5XxB+xtvdr7FOEU4ROUwtIycA8Os0QoaLLaBDfpgYynfgOVanvwDm3s 0wWxAVSr6lbzOHkV29a2dD3qUbB02u5ALRsNQFrr4Hg79K0565Z1890vXr24t0nzVXyt QYe8qvg+BXG4Cxrk5hTaIgHTB2HPFWcpERJHstWv7Vz7pfAIfsOdWMqqpb6jDQml0cbe ufEmAp6rF5ni7qZqzNK64A4jkNC6dzpwMaCiK/njI27I40dOP2qLVnugx3uOm0Fw9owg yxEdQQwSFgRHWyfpTo3lFbuwxltbU9RT0QnJAtefn/v9A5P5bcwDD52vMXbf4gyRxfsI PK2g== X-Forwarded-Encrypted: i=1; AKwUvByuJwwwQE+PlTm8YLSphW5oQzA2IUgxIvZlLn31FjGCZATQEfQcghd2UfTv4sCxK2sGuxHmxMtUBItWSdM=@vger.kernel.org X-Gm-Message-State: AFuF++lkN4kbWMFQWUy5n3O9p4MeSUqwwwCFLEu7OpwUXJv3scdm1aHg KQu6X5FHYXNcub0MB3VFqtmXF3QfNq0SDXbFrjvKlSh2WwdZhZIMyW7zUFmiY6Nn X-Gm-Gg: AYBFou0YK7lbP31QulgrwvGc1TwU7CqKOauz5xf4ScxKY0wjCGEkGVmB2geXFW8tByt nhrP0YIXLFvBoDQ87tCT4lan9UwPz2+NHPKPJw/dtuVLdj45dG0PshVT7RwJ7C1EJZt0ZW49EYf +IBUVk9pwcqaYqB9fXBOsUjZv97qdE8mooKuipU14wVfE/ohuOThMiI1mKaMezGTb9i/5hMi8da w0kbiojqeZ21MzNpFTQ8vsxnT6diZUMUkuGYLyyySMTVZw/bCHgAQE5gRrZnTCsGN+gQXY7P0JG wvV4tw6Enl1PHJdw22RNem8cY/cNS1WLG2cH39lnJUFLKA8pIYfXa/u1P55krSypURxNhxs1dpc ExOWHKglzQY4b2fKPAfbXucxszBM5Dt9Hk7KwSjMlM5FxPmKLZvEqA3o2SWTCU8tqSffSfY6vGH TWy0eMWifNoBiblWNeld0kVLvRZe/p06xZ5PRb4OkalLfmcG4GdNPtrb7CisZElFsovSVOfRnht 7qlNUCDPBrz3tFo7emGGKkpvkVvvImBZz5jq+pvaHUHOwYt9PNWdMvBYzyTrs0++L9c9aDHJwcD ZYOH24yCt3WyNDQlT23Q X-Received: by 2002:a17:90b:4a0f:b0:381:6c5:3f63 with SMTP id 98e67ed59e1d1-39debf7613bmr9948084a91.6.1789448496872; Mon, 14 Sep 2026 22:01:36 -0700 (PDT) Received: from lawlee-vm0.d4y3nv5wwgfelhhopdxv1tqjld.dx.internal.cloudapp.net ([13.93.150.60]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-33ba4e51449sm26872358eec.5.2026.09.14.22.01.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 14 Sep 2026 22:01:36 -0700 (PDT) From: Lawrence Lee To: David Ahern , Ido Schimmel , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni Cc: Simon Horman , Randy Dunlap , netdev@vger.kernel.org, Arun Ajith S , Roopa Prabhu , Jaehee Park , Jonathan Corbet , Shuah Khan , Shuah Khan , linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, Alexander Aring , linux-wpan@vger.kernel.org, linux-bluetooth@vger.kernel.org Subject: [PATCH net-next v2 1/2] ipv6: update NUD_FAILED neighbors from NA messages Date: Tue, 15 Sep 2026 05:01:31 +0000 Message-ID: <6596966f734f3d416bfa83722f7a595149bcc3f8.1789448374.git.lfqlee314@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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. In the context of RFC 4861 which introduced NDP, both non-existent and FAILED entries are considered untracked since they do not have a valid neighbor cache entry. 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 Assisted-by: LLM Sashiko sparse Signed-off-by: Lawrence Lee --- Documentation/networking/ip-sysctl.rst | 28 ++++---- include/net/ndisc.h | 15 ++-- net/6lowpan/ndisc.c | 15 ++-- net/ipv6/ndisc.c | 94 +++++++++++++++++--------- 4 files changed, 96 insertions(+), 56 deletions(-) diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/network= ing/ip-sysctl.rst index 208f46967ee5..4cc57a6be99b 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -3223,18 +3223,19 @@ drop_unsolicited_na - BOOLEAN Default: 0 (disabled). =20 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: =20 - - 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. =20 - - 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. =20 This is as per router-side behavior documented in RFC9131. =20 @@ -3249,9 +3250,10 @@ accept_untracked_na - INTEGER used in conjunction with the ndisc_notify setting on the host to satisfy this prerequisite. =20 - - 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. =20 enhanced_dad - BOOLEAN Include a nonce option in the IPv6 neighbor solicitation messages used for diff --git a/include/net/ndisc.h b/include/net/ndisc.h index 96e3bb6e83af..7fb3f10eca6c 100644 --- a/include/net/ndisc.h +++ b/include/net/ndisc.h @@ -154,11 +154,13 @@ void __ndisc_fill_addr_option(struct sk_buff *skb, in= t type, const void *data, * option parser will take care about that option. * * void (*update)(const struct net_device *dev, struct neighbour *n, - * u32 flags, u8 icmp6_type, + * u32 flags, bool failed_recovery, u8 icmp6_type, * const struct ndisc_options *ndopts): * This function is called when IPv6 ndisc updates the neighbour cache * entry. Additional options which can be updated may be previously * parsed by parse_opts callback and accessible over ndopts parameter. + * failed_recovery indicates that ndisc accepted the packet to recover + * an entry observed in NUD_FAILED. * * int (*opt_addr_space)(const struct net_device *dev, u8 icmp6_type, * struct neighbour *neigh, u8 *ha_buf, @@ -197,7 +199,7 @@ struct ndisc_ops { struct nd_opt_hdr *nd_opt, struct ndisc_options *ndopts); void (*update)(const struct net_device *dev, struct neighbour *n, - u32 flags, u8 icmp6_type, + u32 flags, bool failed_recovery, u8 icmp6_type, const struct ndisc_options *ndopts); int (*opt_addr_space)(const struct net_device *dev, u8 icmp6_type, struct neighbour *neigh, u8 *ha_buf, @@ -227,12 +229,13 @@ static inline int ndisc_ops_parse_options(const struc= t net_device *dev, } =20 static inline void ndisc_ops_update(const struct net_device *dev, - struct neighbour *n, u32 flags, - u8 icmp6_type, - const struct ndisc_options *ndopts) + struct neighbour *n, u32 flags, + bool failed_recovery, u8 icmp6_type, + const struct ndisc_options *ndopts) { if (dev->ndisc_ops && dev->ndisc_ops->update) - dev->ndisc_ops->update(dev, n, flags, icmp6_type, ndopts); + dev->ndisc_ops->update(dev, n, flags, failed_recovery, + icmp6_type, ndopts); } =20 static inline int ndisc_ops_opt_addr_space(const struct net_device *dev, diff --git a/net/6lowpan/ndisc.c b/net/6lowpan/ndisc.c index 868d28583c0a..8fedfef93740 100644 --- a/net/6lowpan/ndisc.c +++ b/net/6lowpan/ndisc.c @@ -47,7 +47,8 @@ static int lowpan_ndisc_parse_options(const struct net_de= vice *dev, } } =20 -static void lowpan_ndisc_802154_update(struct neighbour *n, u32 flags, +static void lowpan_ndisc_802154_update(struct neighbour *n, + bool failed_recovery, u8 icmp6_type, const struct ndisc_options *ndopts) { @@ -87,20 +88,24 @@ static void lowpan_ndisc_802154_update(struct neighbour= *n, u32 flags, ieee802154_be16_to_le16(&neigh->short_addr, lladdr_short); if (!lowpan_802154_is_valid_src_short_addr(neigh->short_addr)) neigh->short_addr =3D cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC); + } else if (failed_recovery) { + neigh->short_addr =3D cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC); } write_unlock_bh(&n->lock); } =20 static void lowpan_ndisc_update(const struct net_device *dev, - struct neighbour *n, u32 flags, u8 icmp6_type, + struct neighbour *n, u32 flags, + bool failed_recovery, u8 icmp6_type, const struct ndisc_options *ndopts) { if (!lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154)) return; =20 - /* react on overrides only. TODO check if this is really right. */ - if (flags & NEIGH_UPDATE_F_OVERRIDE) - lowpan_ndisc_802154_update(n, flags, icmp6_type, ndopts); + /* React to overrides or accepted FAILED-entry recovery. */ + if ((flags & NEIGH_UPDATE_F_OVERRIDE) || failed_recovery) + lowpan_ndisc_802154_update(n, failed_recovery, icmp6_type, + ndopts); } =20 static int lowpan_ndisc_opt_addr_space(const struct net_device *dev, diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c index 90cd5d852569..84d70c09205a 100644 --- a/net/ipv6/ndisc.c +++ b/net/ipv6/ndisc.c @@ -778,13 +778,23 @@ static int pndisc_is_router(const void *pkey, return ret; } =20 +static void __ndisc_update(const struct net_device *dev, + struct neighbour *neigh, const u8 *lladdr, u8 new, + u32 flags, bool failed_recovery, u8 icmp6_type, + struct ndisc_options *ndopts) +{ + neigh_update(neigh, lladdr, new, flags, 0); + /* report ndisc ops about neighbour update */ + ndisc_ops_update(dev, neigh, flags, failed_recovery, icmp6_type, + ndopts); +} + void ndisc_update(const struct net_device *dev, struct neighbour *neigh, const u8 *lladdr, u8 new, u32 flags, u8 icmp6_type, struct ndisc_options *ndopts) { - neigh_update(neigh, lladdr, new, flags, 0); - /* report ndisc ops about neighbour update */ - ndisc_ops_update(dev, neigh, flags, icmp6_type, ndopts); + __ndisc_update(dev, neigh, lladdr, new, flags, false, icmp6_type, + ndopts); } =20 static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb) @@ -972,14 +982,18 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_b= uff *skb) =20 static int accept_untracked_na(struct inet6_dev *idev, struct in6_addr *sa= ddr) { + /* For any given neighbor IP address, consider it an untracked neighbo= r if + * it is absent from the neighbor cache or if it has a NUD_FAILED entr= y in + * the neighbor cache + */ switch (READ_ONCE(idev->cnf.accept_untracked_na)) { - case 0: /* Don't accept untracked na (absent in neighbor cache) */ + case 0: /* Reject NAs for untracked neighbours */ return 0; - case 1: /* Create new entries from na if currently untracked */ + case 1: /* Accept NAs for untracked neighbours */ return 1; - case 2: /* Create new entries from untracked na only if saddr is in the + case 2: /* Accept NAs for untracked neighbours 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: @@ -1001,6 +1015,9 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_b= uff *skb) struct neigh_table *tbl; struct neighbour *neigh; struct inet6_dev *idev; + bool neigh_failed =3D false; + bool neigh_untracked =3D false; + bool accept_untracked =3D false; u8 *lladdr =3D NULL; SKB_DR(reason); u8 new_state; @@ -1067,32 +1084,41 @@ static enum skb_drop_reason ndisc_recv_na(struct sk= _buff *skb) neigh =3D neigh_lookup(tbl, &msg->target, dev); =20 /* 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 =3D=3D all-routers-mcast) check. */ new_state =3D msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE; - if (!neigh && lladdr && idev && READ_ONCE(idev->cnf.forwarding)) { - if (accept_untracked_na(idev, saddr)) { - neigh =3D neigh_create(tbl, &msg->target, dev); - new_state =3D NUD_STALE; - } - } + neigh_failed =3D neigh && + (READ_ONCE(neigh->nud_state) & NUD_FAILED); + neigh_untracked =3D !neigh || neigh_failed; + if (neigh_untracked) { + accept_untracked =3D lladdr && idev && + READ_ONCE(idev->cnf.forwarding) && + accept_untracked_na(idev, saddr); + new_state =3D NUD_STALE; + } + if (!neigh && accept_untracked) + neigh =3D neigh_create(tbl, &msg->target, dev); =20 if (neigh && !IS_ERR(neigh)) { + u32 update_flags; u8 old_flags =3D neigh->flags; =20 - if (READ_ONCE(neigh->nud_state) & NUD_FAILED) + if (neigh_untracked && !accept_untracked) goto out; =20 /* @@ -1108,19 +1134,23 @@ static enum skb_drop_reason ndisc_recv_na(struct sk= _buff *skb) goto out; } =20 - ndisc_update(dev, neigh, lladdr, - new_state, - NEIGH_UPDATE_F_WEAK_OVERRIDE| - (msg->icmph.icmp6_override ? NEIGH_UPDATE_F_OVERRIDE : 0)| - NEIGH_UPDATE_F_OVERRIDE_ISROUTER| - (msg->icmph.icmp6_router ? NEIGH_UPDATE_F_ISROUTER : 0), - NDISC_NEIGHBOUR_ADVERTISEMENT, &ndopts); + update_flags =3D NEIGH_UPDATE_F_WEAK_OVERRIDE | + (msg->icmph.icmp6_override ? + NEIGH_UPDATE_F_OVERRIDE : 0) | + NEIGH_UPDATE_F_OVERRIDE_ISROUTER | + (msg->icmph.icmp6_router ? + NEIGH_UPDATE_F_ISROUTER : 0); + + __ndisc_update(dev, neigh, lladdr, + new_state, update_flags, neigh_failed, + NDISC_NEIGHBOUR_ADVERTISEMENT, &ndopts); =20 if ((old_flags & ~neigh->flags) & NTF_ROUTER) { /* * Change: router to host */ - rt6_clean_tohost(dev_net(dev), saddr); + rt6_clean_tohost(net, + neigh_failed ? &msg->target : saddr); } reason =3D SKB_CONSUMED; out: --=20 2.43.0 From nobody Fri Sep 25 08:45:38 2026 Received: from mail-oo2-f16.google.com (mail-oo2-f16.google.com [74.125.231.144]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D779C435508 for ; Tue, 15 Sep 2026 05:01:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.231.144 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789448503; cv=none; b=jtyQcyA0EBlDkSsosJP9bISM+qq8tgWka7KVPsGLMLJxZMx5Myby9JseZAVSQZZvPMFRTO8VoEuw0mUHJf9TNrSkL88QDyo64XyN4FnkX8t/4uiKzWp2gkX3hh1TNyzaO/goVSavoxIA+7Bgpot9NZrsprelIMsJg4mhsrcZ03s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789448503; c=relaxed/simple; bh=uCtUnL+DK0+Vcijpb0NbemyGhnyykjJ0ZTtE8mcmaSc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=d2cXlNILbbfbojiM51qfjNhm87ATzMp/Oeye6BJe+yUY1pS6T8HT5o4LjUNMrm3ZvJweWufldtqMSLejgYkA0+LFp7JBzvVVmW0sT9qznkutzVdJhShrXlVPRDwhU9AYYqdm+PXVr3OjWjlTWM3s0sVTsJK2kJ3JOIxTJ80s7Ns= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=mC0I0wzN; arc=none smtp.client-ip=74.125.231.144 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="mC0I0wzN" Received: by mail-oo2-f16.google.com with SMTP id 46e09a7af769-805bf8c2661so1885719a34.3 for ; Mon, 14 Sep 2026 22:01:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789448499; x=1790053299; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=e8F3JwitOM+HzKjUaxufpWmqRA5xpMbmjQIDcQNq5os=; b=mC0I0wzNejhewKPZ15ldKEsGHp6Rif7O3zEzRelpr+NJoIFF3OqAOw85MMzXtXOKc7 tivgimdw7rEuVvUSQOduChnJrw4BD/RW+KA+fJ0Nslz5oeBwg/E0jlreE+IGi1Q7JM3l /v6GysBSLqGl6GkQU1kbmpVrUFh9otDCy/cDuws+mrA5IghF4/k2M4jFkmGGQ8sFl/9/ /N3nlwq3NWEBBJOfVIebE/1luBv4f7ElNNTuWxsWg37SGwbpHaY76AEPPZureKjJvuGU pWMz7PB8uAOhXEqRHjqtd9m3kdH5QYMLfakY87BF/oHb2oHQNlg80tJpe0QBFfup+a/D ngoA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789448499; x=1790053299; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=e8F3JwitOM+HzKjUaxufpWmqRA5xpMbmjQIDcQNq5os=; b=cCne2XZhXtEDCJjFWmA8SRhutZ+8L9KsQwAcihUrB1HyNugjYAAlxpSBKsIdnJrjhA tqf8HJA3h61W48LVuI1wmTxsDr9OpEKwRPYaKvp1vGWeKhu7caov9BB3N9at05PMPLwT +nc+1j9Klc4NLulUEICPySx94N/c4QgBQoKlBma7H+v8sVlEt6jmXKXgC7a5hA4XBkK+ 5YLNAClCrcdyJrCjDH4MRJajRJ/HRv9UjI57weGbuUwzV05JhTX1nCb7Qe1hLt8Mvcsm xeBdqrIXNx7SGaB0OUzt1esItOEYSSvLfOczg8rkHbqIWxmR5zAbMWrRWjXJd0WfuHUn na3w== X-Forwarded-Encrypted: i=1; AKwUvBx+51lOve3hCAW0DIS3t5W4JBgALwE+kvM6TIjrDePufqKANtPBSqcGIk0EoCs+eVXADE+1OQoX9Jx6bHw=@vger.kernel.org X-Gm-Message-State: AFuF++li/2Lusp8gD/pedE9ep0f7pz0MjmOibrqnfQY9q/jxJCfdAbOO 8vsSLs3TK3y9OTO0WJfNjVeCJ/MBh6BaRXbzf0J039XKV+pp7LUycD5C X-Gm-Gg: AYBFou2/EH+WoalC5Jxv6eOVrIRplG7ZOsX7xxQwiCqVTULvuegJGsAEGSAEeoUIJA1 /lMUH6ekXBPH46AeQVaOaSCNRrWEK1iJO+2zycbiVkiIj0MOFx/0HZuWvB4qbezFNvNWfXaAjS3 1iITSz9zmCXfrBehYiAlCxgByXW4tnCOnzXTiUt4EQ6tTo2pu7v38rQ15tWkqH3HLVCcQMdlkv0 KArs3kFBn5uf6qECz5xYFztwZA57vRJ1Qg7qAnqmnafo878FpQUj4iaBoGgPoacXfYtcM/k0maP JNWpdUhRuaI8I0/dDd4a1/K+gLP1I4RQi4ngTrsS0Ff5oAu1vkrk1VJlneVbgex94TEMPBAk3xk qoWAyRvMk1zHBMwmxc5B10fiFcwXn0zUrKY2F5pe432Sp1sECj36f88W2qcgV/KSTzyl0q47JMK NHi8WvAepIfkATJ15gzyX8HJhL7xPZoWdtF4gAfeL/Khyzcxr3wYKzTJmT/ILYl4ItxVNl4Xq9L 1ppyypRQDOfxfchm6v8dVfWpLKv9pzi8T7ZLLyA+J0b+Ejtab/dTHqVTiWMBepIQaeIG6RQwL+N vSrLXl4H982A2JHg/U7J X-Received: by 2002:a05:6830:6405:b0:805:5e7e:9bf6 with SMTP id 46e09a7af769-80899f62264mr4009489a34.21.1789448498623; Mon, 14 Sep 2026 22:01:38 -0700 (PDT) Received: from lawlee-vm0.d4y3nv5wwgfelhhopdxv1tqjld.dx.internal.cloudapp.net ([13.93.150.60]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-33ba4e51449sm26872358eec.5.2026.09.14.22.01.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 14 Sep 2026 22:01:38 -0700 (PDT) From: Lawrence Lee To: David Ahern , Ido Schimmel , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni Cc: Simon Horman , Randy Dunlap , netdev@vger.kernel.org, Arun Ajith S , Roopa Prabhu , Jaehee Park , Jonathan Corbet , Shuah Khan , Shuah Khan , linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, Alexander Aring , linux-wpan@vger.kernel.org, linux-bluetooth@vger.kernel.org Subject: [PATCH net-next v2 2/2] selftests: net: test untracked NA recovery of FAILED neighbors Date: Tue, 15 Sep 2026 05:01:32 +0000 Message-ID: <3f33052571409614b953a3dc7778114c51c43669.1789448374.git.lfqlee314@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Seed a FAILED neighbor before triggering an unsolicited NA. Verify that the entry transitions to STALE only when drop_unsolicited_na is disabled, accept_untracked_na mode 1 or in-prefix mode 2 is enabled, and IPv6 forwarding is enabled. Verify that disabling each gate or using an out-of-prefix source with mode 2 keeps the entry in FAILED. Mark the seed externally learned and disable carrier-based eviction so the entry cannot be garbage-collected before the NA arrives. Require the marker after processing to prove that a recovered entry was updated in place instead of deleted and recreated. Check both the command that seeds the entry and its resulting state. Capture the verifier status before constructing the test description so the array assignment cannot mask a failure. Link: https://lore.kernel.org/r/20260813233344.445265-1-lfqlee314@gmail.com Assisted-by: LLM Sashiko sparse Signed-off-by: Lawrence Lee --- .../net/ndisc_unsolicited_na_test.sh | 124 +++++++++++++++--- 1 file changed, 105 insertions(+), 19 deletions(-) diff --git a/tools/testing/selftests/net/ndisc_unsolicited_na_test.sh b/too= ls/testing/selftests/net/ndisc_unsolicited_na_test.sh index 5db69dad0cfc..5f4f29f0ac4a 100755 --- a/tools/testing/selftests/net/ndisc_unsolicited_na_test.sh +++ b/tools/testing/selftests/net/ndisc_unsolicited_na_test.sh @@ -3,12 +3,18 @@ =20 # This test is for the accept_untracked_na feature to # enable RFC9131 behaviour. The following is the test-matrix. -# drop accept fwding behaviour -# ---- ------ ------ ---------------------------------------------- -# 1 X X Don't update NC -# 0 0 X Don't update NC -# 0 1 0 Don't update NC -# 0 1 1 Add a STALE NC entry +# state drop accept fwding behaviour +# ------ ---- ------ ------ -----------------------------------------= ----- +# absent 1 X X Don't update NC +# absent 0 0 X Don't update NC +# absent 0 1 0 Don't update NC +# absent 0 1 1 Add a STALE NC entry +# failed 1 X X Keep the NC entry in FAILED state +# failed 0 0 X Keep the NC entry in FAILED state +# failed 0 1 0 Keep the NC entry in FAILED state +# failed 0 1 1 Update the NC entry to STALE +# failed 0 2 1 Update the NC entry to STALE if in-network +# failed 0 2 1 Keep the NC entry FAILED if out-of-network =20 source lib.sh ret=3D0 @@ -20,7 +26,9 @@ HOST_INTF=3D"veth-host" ROUTER_INTF=3D"veth-router" =20 ROUTER_ADDR=3D"2000:20::1" -HOST_ADDR=3D"2000:20::2" +HOST_ADDR_IN_NETWORK=3D"2000:20::2" +HOST_ADDR_OUT_OF_NETWORK=3D"2000:21::2" +HOST_ADDR=3D"${HOST_ADDR_IN_NETWORK}" SUBNET_WIDTH=3D64 ROUTER_ADDR_WITH_MASK=3D"${ROUTER_ADDR}/${SUBNET_WIDTH}" HOST_ADDR_WITH_MASK=3D"${HOST_ADDR}/${SUBNET_WIDTH}" @@ -88,6 +96,8 @@ setup() ${ROUTER_CONF}.drop_unsolicited_na=3D${drop_unsolicited_na} ${IP_ROUTER_EXEC} sysctl -qw \ ${ROUTER_CONF}.accept_untracked_na=3D${accept_untracked_na} + ${IP_ROUTER_EXEC} sysctl -qw \ + ${ROUTER_CONF}.ndisc_evict_nocarrier=3D0 ${IP_ROUTER_EXEC} sysctl -qw ${ROUTER_CONF}.disable_ipv6=3D0 ${IP_ROUTER} addr add ${ROUTER_ADDR_WITH_MASK} dev ${ROUTER_INTF} =20 @@ -140,24 +150,77 @@ verify_ndisc() { local drop_unsolicited_na=3D$1 local accept_untracked_na=3D$2 local forwarding=3D$3 + local initial_state=3D${4:-absent} + local same_subnet=3D${5:-1} + local neigh_show_output + local expected_state + + if [ "${drop_unsolicited_na}" -eq 0 ] && + [ "${forwarding}" -eq 1 ]; then + case "${accept_untracked_na}" in + 1) + expected_state=3DSTALE + ;; + 2) + [ "${same_subnet}" -eq 1 ] && expected_state=3DSTALE + ;; + esac + fi + if [ -z "${expected_state}" ] && + [ "${initial_state}" =3D "failed" ]; then + expected_state=3DFAILED + fi =20 - neigh_show_output=3D$(${IP_ROUTER} neigh show \ - to ${HOST_ADDR} dev ${ROUTER_INTF} nud stale) - if [ ${drop_unsolicited_na} -eq 0 ] && \ - [ ${accept_untracked_na} -eq 1 ] && \ - [ ${forwarding} -eq 1 ]; then - # Neighbour entry expected to be present for 011 case - [[ ${neigh_show_output} ]] + if [ -n "${expected_state}" ]; then + neigh_show_output=3D$(${IP_ROUTER} neigh show \ + to "${HOST_ADDR}" dev "${ROUTER_INTF}") + if [[ " ${neigh_show_output} " !=3D \ + *" ${expected_state} "* ]]; then + return 1 + fi + if [ "${initial_state}" =3D "failed" ]; then + [[ "${neigh_show_output}" =3D=3D *"extern_learn"* ]] + fi else - # Neighbour entry expected to be absent for all other cases + neigh_show_output=3D$(${IP_ROUTER} neigh show \ + to "${HOST_ADDR}" dev "${ROUTER_INTF}") [[ -z ${neigh_show_output} ]] fi } =20 test_unsolicited_na_common() { + local same_subnet=3D${5:-1} + local neigh_show_output + + if [ "${same_subnet}" -eq 1 ]; then + HOST_ADDR=3D"${HOST_ADDR_IN_NETWORK}" + else + HOST_ADDR=3D"${HOST_ADDR_OUT_OF_NETWORK}" + fi + HOST_ADDR_WITH_MASK=3D"${HOST_ADDR}/${SUBNET_WIDTH}" + # Setup the test bed, but keep links down - setup $1 $2 $3 + setup "$1" "$2" "$3" + + if [ "${4:-absent}" =3D "failed" ]; then + if ! ${IP_ROUTER} neigh replace "${HOST_ADDR}" \ + dev "${ROUTER_INTF}" \ + nud failed extern_learn; then + echo "Unable to create NUD_FAILED neighbor entry" + return 1 + fi + neigh_show_output=3D$(${IP_ROUTER} neigh show \ + to "${HOST_ADDR}" dev "${ROUTER_INTF}") + if [[ " ${neigh_show_output} " !=3D *" FAILED "* ]]; then + echo "Unable to verify NUD_FAILED neighbor entry" + return 1 + fi + if [[ "${neigh_show_output}" !=3D *"extern_learn"* ]]; then + echo "Neighbor entry is not externally learned" + return 1 + fi + fi =20 # Bring the link up, wait for the NA, # and add a delay to ensure neighbour processing is done. @@ -165,22 +228,35 @@ test_unsolicited_na_common() start_tcpdump =20 # Verify the neighbour table - verify_ndisc $1 $2 $3 + verify_ndisc "$1" "$2" "$3" "$4" "${same_subnet}" =20 } =20 test_unsolicited_na_combination() { - test_unsolicited_na_common $1 $2 $3 + local initial_state=3D${4:-absent} + local same_subnet=3D${5:-1} + local rc + + test_unsolicited_na_common "$1" "$2" "$3" "${initial_state}" \ + "${same_subnet}" + rc=3D$? test_msg=3D("test_unsolicited_na: " "drop_unsolicited_na=3D$1 " "accept_untracked_na=3D$2 " "forwarding=3D$3") - log_test $? 0 "${test_msg[*]}" + if [ "${initial_state}" =3D "failed" ]; then + test_msg+=3D("initial_state=3Dfailed") + fi + if [ "$2" -eq 2 ]; then + test_msg+=3D("same_subnet=3D${same_subnet}") + fi + log_test "${rc}" 0 "${test_msg[*]}" cleanup } =20 test_unsolicited_na_combinations() { # Args: drop_unsolicited_na accept_untracked_na forwarding + # [initial_state] [same_subnet] =20 # Expect entry test_unsolicited_na_combination 0 1 1 @@ -193,6 +269,16 @@ test_unsolicited_na_combinations() { test_unsolicited_na_combination 1 0 1 test_unsolicited_na_combination 1 1 0 test_unsolicited_na_combination 1 1 1 + + # Expect FAILED entry to become STALE + test_unsolicited_na_combination 0 1 1 failed + test_unsolicited_na_combination 0 2 1 failed 1 + + # Expect FAILED entry to remain FAILED + test_unsolicited_na_combination 0 0 1 failed + test_unsolicited_na_combination 0 1 0 failed + test_unsolicited_na_combination 1 1 1 failed + test_unsolicited_na_combination 0 2 1 failed 0 } =20 ##########################################################################= ##### --=20 2.43.0