All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Lawrence Lee" <lfqlee314@gmail.com>
To: "Ido Schimmel" <idosch@nvidia.com>
Cc: "David Ahern" <dsahern@kernel.org>,
	"David S. Miller" <davem@davemloft.net>,
	"Eric Dumazet" <edumazet@google.com>,
	"Jakub Kicinski" <kuba@kernel.org>,
	"Paolo Abeni" <pabeni@redhat.com>,
	"Simon Horman" <horms@kernel.org>,
	"Randy Dunlap" <rdunlap@infradead.org>, <netdev@vger.kernel.org>,
	"Arun Ajith S" <aajith@arista.com>,
	"Roopa Prabhu" <roopa@nvidia.com>,
	"Jaehee Park" <jhpark1013@gmail.com>,
	"Jonathan Corbet" <corbet@lwn.net>,
	"Shuah Khan" <skhan@linuxfoundation.org>,
	"Shuah Khan" <shuah@kernel.org>, <linux-doc@vger.kernel.org>,
	<linux-kselftest@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	"Alexander Aring" <alex.aring@gmail.com>,
	<linux-wpan@vger.kernel.org>, <linux-bluetooth@vger.kernel.org>
Subject: Re: [PATCH net-next v2 1/2] ipv6: update NUD_FAILED neighbors from NA messages
Date: Wed, 16 Sep 2026 17:43:11 +0000	[thread overview]
Message-ID: <DLGXDIB879EO.IDZNAIRY0VFJ@gmail.com> (raw)
In-Reply-To: <20260916120202.GA935660@shredder>

On Wed Sep 16, 2026 at 12:02 PM UTC, Ido Schimmel wrote:
> On Tue, Sep 15, 2026 at 05:01:31AM +0000, Lawrence Lee wrote:
> > 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.
>
> RFC 4861 didn't introduce NDP (RFC 1970 did), so please omit this bit.
> But if you're going to mention RFC 4861, then cite 7.3.3 which says that
> "If address resolution fails, the entry SHOULD be deleted". The fact
> that Linux keeps it as FAILED is an implementation detail and treating
> it as untracked is correct from RFC perspective.
>

That's my mistake, I'll update this to mention that 4861 is just the 
most recent NDP standard and will cite 7.3.3.

> > 
> > 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 <lfqlee314@gmail.com>
> > ---
> >  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(-)
>
> The RFC was:
>
> 1 file changed, 11 insertions(+), 2 deletions(-)
>
> I'm not sure how this ballooned to this size...
>

Some of the additional size can be attributed to doc/comment changes, 
but a lot of it comes from changes I implemented to address issues found 
by the local Sashiko review I ran. It was definitely an oversight on my 
part to not mention these in either the commit message or comments, 
sorry about that.

> > 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, int 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 struct net_device *dev,
> >  }
> >  
> >  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);
> >  }
>
> All the changes in this file can be dropped. See below.
>

This is tied to the lowpan changes below.

> >  
> >  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_device *dev,
> >  	}
> >  }
> >  
> > -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 = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
> > +	} else if (failed_recovery) {
> > +		neigh->short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
> >  	}
> >  	write_unlock_bh(&n->lock);
> >  }
> >  
> >  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;
> >  
> > -	/* 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);
> >  }
> >  
> >  static int lowpan_ndisc_opt_addr_space(const struct net_device *dev,
>
> I'm not sure why you added these lowpan changes to the patch. They are
> not described in the commit message. Given that lowpan_ndisc_update()
> already has a TODO comment about only handling overrides, I suggest to
> ignore it. If needed, it can be modified in the future by someone who
> can explain the use case and test the change.
>

I added the lowpan changes after my local Sashiko review run identified 
an issue where a FAILED neighbor can retain an outdated private short 
address if it's moved to STALE by a non-override NA. Happy to drop all 
lowpan-related changes or update comments/commit message to reflect the 
changes, please let me know your preference.

> > 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;
> >  }
> >  
> > +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);
> >  }
>
> This hunk can be dropped.
>

Tied to the lowpan changes above.

> >  
> >  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_buff *skb)
> >  
> >  static int accept_untracked_na(struct inet6_dev *idev, struct in6_addr *saddr)
> >  {
> > +    /* For any given neighbor IP address, consider it an untracked neighbor if
> > +     * it is absent from the neighbor cache or if it has a NUD_FAILED entry in
> > +     * the neighbor cache
> > +     */
>
> Redundant given the comments in the caller and the sysctl documentation.
> Simply modify the existing comments below to mention FAILED case.
>

Will update.

> >  	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_buff *skb)
> >  	struct neigh_table *tbl;
> >  	struct neighbour *neigh;
> >  	struct inet6_dev *idev;
> > +	bool neigh_failed = false;
> > +	bool neigh_untracked = false;
> > +	bool accept_untracked = false;
>
> Try to maintain reverse xmas tree:
>
> https://docs.kernel.org/next/process/maintainer-netdev.html#local-variable-ordering-reverse-xmas-tree-rcs
>

Will fix.

> >  	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;
> > -		}
> > -	}
> > +	neigh_failed = neigh &&
> > +		       (READ_ONCE(neigh->nud_state) & NUD_FAILED);
> > +	neigh_untracked = !neigh || neigh_failed;
> > +	if (neigh_untracked) {
> > +		accept_untracked = lladdr && idev &&
> > +				   READ_ONCE(idev->cnf.forwarding) &&
> > +				   accept_untracked_na(idev, saddr);
> > +		new_state = NUD_STALE;
> > +	}
> > +	if (!neigh && accept_untracked)
> > +		neigh = neigh_create(tbl, &msg->target, dev);
> >  
> >  	if (neigh && !IS_ERR(neigh)) {
> > +		u32 update_flags;
> >  		u8 old_flags = neigh->flags;
> >  
> > -		if (READ_ONCE(neigh->nud_state) & NUD_FAILED)
> > +		if (neigh_untracked && !accept_untracked)
> >  			goto out;
> >  
> >  		/*
>
> This can be simplified to:
>
> diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
> index 90cd5d852569..7d114bdb263e 100644
> --- a/net/ipv6/ndisc.c
> +++ b/net/ipv6/ndisc.c
> @@ -1082,19 +1082,21 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)
>  	 * 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
>

Was originally unsure if I should modify the existing logical structure.  
Thanks for the suggestion, will implement this. Is it appropriate to 
credit you with a commit tag?

> >  
> >  		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);
> >  		}
>
> What is the reason for this change? It's also not explained in the
> commit message and I suspect it's not needed.

This was added in response to another Sashiko local review finding.  
Let's say we have some FAILED neighbor T with NTF_ROUTER set. If we get 
an NA from source address S with target address T and with the Router 
bit clear, existing kernel code will cleanup routes with gateway S, but 
IMO we should clean routes with gateway T instead since that is the 
neighbor which was updated by the NA. I can either update 
comments/commit message to reflect this or remove the change entirely, 
please let me know your preference.


  reply	other threads:[~2026-09-16 17:43 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-15  5:01 [PATCH net-next v2 0/2] ipv6: update NUD_FAILED neighbors from NA messages Lawrence Lee
2026-09-15  5:01 ` [PATCH net-next v2 1/2] " Lawrence Lee
2026-09-15  6:58   ` bluez.test.bot
2026-09-16 12:02   ` [PATCH net-next v2 1/2] " Ido Schimmel
2026-09-16 17:43     ` Lawrence Lee [this message]
2026-09-17 14:37       ` Ido Schimmel
2026-09-17 20:11         ` Lawrence Lee
2026-09-17 17:02   ` netdev-bot+sashiko
2026-09-17 20:26     ` Lawrence Lee
2026-09-15  5:01 ` [PATCH net-next v2 2/2] selftests: net: test untracked NA recovery of FAILED neighbors Lawrence Lee
2026-09-17 17:02   ` netdev-bot+sashiko
2026-09-18  5:52     ` Lawrence Lee
2026-09-15  5:26 ` [PATCH net-next v2 0/2] ipv6: update NUD_FAILED neighbors from NA messages Randy Dunlap
2026-09-15 15:54   ` Lawrence Lee

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=DLGXDIB879EO.IDZNAIRY0VFJ@gmail.com \
    --to=lfqlee314@gmail.com \
    --cc=aajith@arista.com \
    --cc=alex.aring@gmail.com \
    --cc=corbet@lwn.net \
    --cc=davem@davemloft.net \
    --cc=dsahern@kernel.org \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=idosch@nvidia.com \
    --cc=jhpark1013@gmail.com \
    --cc=kuba@kernel.org \
    --cc=linux-bluetooth@vger.kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-wpan@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=rdunlap@infradead.org \
    --cc=roopa@nvidia.com \
    --cc=shuah@kernel.org \
    --cc=skhan@linuxfoundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.