Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE
@ 2026-08-04  1:47 Yuyang Huang
  2026-08-04  1:47 ` [PATCH net-next v5 01/10] ipv6: add ip6_del_rt_reason() Yuyang Huang
                   ` (10 more replies)
  0 siblings, 11 replies; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

When the kernel deletes an IPv6 route on its own, the RTM_DELROUTE
notification does not say why. User space cannot tell a route that
expired from one the router explicitly withdrew, yet the two call for
different reactions: an expired RA route means the router failed to
refresh it in time, which points at a misconfigured or unreliable
router and may warrant action such as disabling IPv6 on that network,
while a zero-lifetime withdrawal is normal, RFC-compliant operation.

This is a general problem for any consumer device running Linux,
especially on Wi-Fi networks, where multicast delivery is not
guaranteed (e.g. frames can be lost around DTIM for clients in power
save mode). The motivating case is Android: the userspace NetworkStack
process listens on RTMGRP_IPV6_ROUTE and today treats any loss of the
IPv6 default route as "router lost". To avoid the device repeatedly
gaining and losing IPv6 connectivity on a badly configured network,
when it detects the device is on a dual-stack network with working
IPv4 connectivity, it defensively clears accept_ra_defrtr and restarts
IPv6, so user space apps stop using broken global IPv6 connectivity
while link-local IPv6 keeps working. That reaction is wrong if the
route was withdrawn by a zero-lifetime RA (some ISPs do this
intentionally for reconfiguration) - with accept_ra_defrtr off, IPv6
never recovers once the router advertises again. It is the right
reaction if the route genuinely expired, since the router failed to
refresh it in time.

Fixing this in user space is not practical: RTM_NEWROUTE carries the
initial route lifetime (in rta_cacheinfo), but the kernel does not
resend it when a later RA refreshes the lifetime. So distinguishing
the cause of an RTM_DELROUTE from user space would mean opening a raw
socket, listening to RAs, and tracking lifetimes independently,
duplicating logic the kernel already has. Sending RTM_NEWROUTE on
every RA lifetime refresh was also considered, but that would be
spammy and is technically wrong, since a lifetime update does not add
a new route.

This series proposes RTA_DEL_REASON instead: it tells user space why
the route was deleted so it can react accordingly. In the Android
case, NetworkStack would defensively disable global IPv6 only on
RT_DEL_REASON_EXPIRED, and take no action on
RT_DEL_REASON_RA_WITHDRAWN, since that is RFC-compliant behavior.

Patches 1 to 6 add RTA_DEL_REASON and enum rt_del_reason to the
rtnetlink uAPI, thread the reason from the kernel-initiated IPv6
deletion paths down to the RTM_DELROUTE notification, and record the
cause: RT_DEL_REASON_EXPIRED for routes garbage collected after their
RTF_EXPIRES lifetime ran out, and RT_DEL_REASON_RA_WITHDRAWN for
default routes, prefix routes and RFC 4191 route information routes
withdrawn by Router Advertisements. Patches 1 to 5 are no-ops on the
wire; the attribute first appears in patch 6. The route addition path
is not touched.

Patches 7 to 9 extend the rt-route Netlink spec with the route
notifications and their multicast groups, split the newroute and
delroute request attribute lists out of the shared getroute reply
list, and add the new attribute and its enum.

Only kernel-initiated deletions that user space cannot otherwise
explain are attributed. User-requested deletions are self-explanatory
to the requester, so they carry no reason; the UAPI documents that
absence and RT_DEL_REASON_UNSPEC must be treated identically, which
keeps the door open for attributing more paths (nexthop removal
cascades, device removal) later.

Patch 10 adds selftests covering all three producer paths: a
GC-expired route, and a default route + PIO prefix route + RIO route
advertised and then withdrawn by hand-crafted RAs over a raw ICMPv6
socket (no external RA tool needed), plus a check that user-requested
deletions carry no attribute. The notifications are decoded with YNL,
which also exercises the rt-route spec additions.

Changes since v4:
- Rename the payload values to RT_DEL_REASON_*, so they do not read as
  attribute ids. The attribute id is still RTA_DEL_REASON.
- Document that the reason value space is family-agnostic.
- Drop the skip_notify argument from ip6_del_rt_reason(); all callers
  passed false, and skipping the notification discards the reason.
- Drop the unused !CONFIG_IPV6 stub for ip6_del_rt_reason().

Changes since v3:
- Split the single kernel patch into six, one logical step each, per
  review.
- Use the enum throughout instead of a plain u32.
- Add inet6_rt_del_notify() and call it from fib6_del_route(), so the
  route addition path is unchanged.
- Move the route notifications and their multicast groups to their own
  spec patch.
- Split the newroute and delroute request attribute lists out of the
  getroute reply list in a separate spec patch, so the attribute
  addition is a one-line diff.

Changes since v2:
- Fix the patch 1 commit message, which still said u8.
- Keep del-reason out of the newroute and delroute request attribute
  lists in the rt-route spec, since the kernel rejects it in requests.

Changes since v1:
- Expand the motivation with the Android use case, per review request.
- Widen RTA_DEL_REASON from u8 to u32, per Netlink uAPI convention.
- Convert dict_keys to a set before the set difference in the
  selftest, for clarity.

Yuyang Huang (10):
  ipv6: add ip6_del_rt_reason()
  ipv6: propagate the route deletion reason to fib6_del_route()
  ipv6: record the reason for kernel-initiated route deletions
  ipv6: add a deletion reason argument to rt6_fill_node()
  ipv6: expose the route deletion reason in RTM_DELROUTE
  ipv6: add inet6_rt_del_notify()
  netlink: specs: rt-route: add route notifications
  netlink: specs: rt-route: split out the request attribute list
  netlink: specs: rt-route: add the route deletion reason
  selftests: net: verify RTA_DEL_REASON on route deletion

 Documentation/netlink/specs/rt-route.yaml     |  68 ++++++-
 include/net/ip6_fib.h                         |   5 +-
 include/net/ip6_route.h                       |   2 +
 include/uapi/linux/rtnetlink.h                |  22 +++
 net/ipv6/addrconf.c                           |   3 +-
 net/ipv6/ip6_fib.c                            |  14 +-
 net/ipv6/ndisc.c                              |   4 +-
 net/ipv6/route.c                              |  75 ++++++--
 .../testing/selftests/net/lib/py/__init__.py  |   4 +-
 tools/testing/selftests/net/lib/py/ynl.py     |   7 +-
 tools/testing/selftests/net/rtnetlink.py      | 181 +++++++++++++++++-
 11 files changed, 349 insertions(+), 36 deletions(-)

--
2.43.0


^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH net-next v5 01/10] ipv6: add ip6_del_rt_reason()
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
@ 2026-08-04  1:47 ` Yuyang Huang
  2026-08-07 16:21   ` Ido Schimmel
  2026-08-04  1:47 ` [PATCH net-next v5 02/10] ipv6: propagate the route deletion reason to fib6_del_route() Yuyang Huang
                   ` (9 subsequent siblings)
  10 siblings, 1 reply; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

Add RTA_DEL_REASON and enum rt_del_reason to the rtnetlink uAPI, and
add ip6_del_rt_reason(), which takes the reason a route is being
deleted. It has no skip_notify argument: a caller that records a
deletion reason wants the notification that carries it.

The reason is unused for now. Subsequent patches propagate it to the
deletion path and report it on RTM_DELROUTE.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
 include/net/ip6_route.h        |  2 ++
 include/uapi/linux/rtnetlink.h | 22 ++++++++++++++++++++++
 net/ipv6/route.c               |  8 ++++++++
 3 files changed, 32 insertions(+)

diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 09ffe0f13ce7..7bcf0e5bd887 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -128,6 +128,8 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags,
 int ip6_ins_rt(struct net *net, struct fib6_info *f6i);
 #if IS_ENABLED(CONFIG_IPV6)
 int ip6_del_rt(struct net *net, struct fib6_info *f6i, bool skip_notify);
+int ip6_del_rt_reason(struct net *net, struct fib6_info *f6i,
+		      enum rt_del_reason del_reason);
 #else
 static inline int ip6_del_rt(struct net *net, struct fib6_info *f6i,
 			     bool skip_notify)
diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
index 27265fd31e5f..66cfc17ede0e 100644
--- a/include/uapi/linux/rtnetlink.h
+++ b/include/uapi/linux/rtnetlink.h
@@ -399,6 +399,7 @@ enum rtattr_type_t {
 	RTA_DPORT,
 	RTA_NH_ID,
 	RTA_FLOWLABEL,
+	RTA_DEL_REASON,
 	__RTA_MAX
 };
 
@@ -407,6 +408,27 @@ enum rtattr_type_t {
 #define RTM_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))))
 #define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg))
 
+/* RTA_DEL_REASON: why the kernel deleted the route. u32.
+ * Emitted only on RTM_DELROUTE notifications, and only when the deletion
+ * path records a cause. Absence means either an older kernel or a
+ * deletion path that does not (yet) record its cause - consumers must
+ * treat "absent" and "unspec" identically. New causes may be appended.
+ * Currently only IPv6 deletion paths record a cause.
+ *
+ * The value space is family-agnostic: a value must never be
+ * reinterpreted per address family. A cause that only one family can
+ * produce still gets its own value rather than reusing another
+ * family's.
+ */
+enum rt_del_reason {
+	RT_DEL_REASON_UNSPEC,		/* cause not recorded */
+	RT_DEL_REASON_EXPIRED,		/* RTF_EXPIRES lifetime ran out (GC) */
+	RT_DEL_REASON_RA_WITHDRAWN,	/* zero-lifetime RA / PIO / RIO */
+	__RT_DEL_REASON_MAX
+};
+
+#define RT_DEL_REASON_MAX (__RT_DEL_REASON_MAX - 1)
+
 /* RTM_MULTIPATH --- array of struct rtnexthop.
  *
  * "struct rtnexthop" describes all necessary nexthop information,
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index fc42d67e5822..ca374b056f5a 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -4004,6 +4004,14 @@ int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify)
 	return __ip6_del_rt(rt, &info);
 }
 
+int ip6_del_rt_reason(struct net *net, struct fib6_info *rt,
+		      enum rt_del_reason del_reason)
+{
+	struct nl_info info = { .nl_net = net };
+
+	return __ip6_del_rt(rt, &info);
+}
+
 static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)
 {
 	struct nl_info *info = &cfg->fc_nlinfo;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH net-next v5 02/10] ipv6: propagate the route deletion reason to fib6_del_route()
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
  2026-08-04  1:47 ` [PATCH net-next v5 01/10] ipv6: add ip6_del_rt_reason() Yuyang Huang
@ 2026-08-04  1:47 ` Yuyang Huang
  2026-08-07 16:22   ` Ido Schimmel
  2026-08-04  1:47 ` [PATCH net-next v5 03/10] ipv6: record the reason for kernel-initiated route deletions Yuyang Huang
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

Pass the deletion reason from ip6_del_rt_reason() down through
__ip6_del_rt(), fib6_del() and into fib6_del_route(). All existing
callers pass RT_DEL_REASON_UNSPEC.

fib6_del_route() ignores the reason until the notification path learns
to report it.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
 include/net/ip6_fib.h |  3 ++-
 net/ipv6/ip6_fib.c    | 12 +++++++-----
 net/ipv6/route.c      | 19 +++++++++++--------
 3 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index 9cd27e1b9b69..0ba5d3c56543 100644
--- a/include/net/ip6_fib.h
+++ b/include/net/ip6_fib.h
@@ -468,7 +468,8 @@ void fib6_clean_all_skip_notify(struct net *net,
 
 int fib6_add(struct fib6_node *root, struct fib6_info *rt,
 	     struct nl_info *info, struct netlink_ext_ack *extack);
-int fib6_del(struct fib6_info *rt, struct nl_info *info);
+int fib6_del(struct fib6_info *rt, struct nl_info *info,
+	     enum rt_del_reason del_reason);
 
 static inline
 void rt6_get_prefsrc(const struct rt6_info *rt, struct in6_addr *addr)
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index e9fc692d4f3b..1f6f6f33ded1 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -1967,7 +1967,8 @@ static struct fib6_node *fib6_repair_tree(struct net *net,
 }
 
 static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,
-			   struct fib6_info __rcu **rtp, struct nl_info *info)
+			   struct fib6_info __rcu **rtp, struct nl_info *info,
+			   enum rt_del_reason del_reason)
 {
 	struct fib6_info *leaf, *replace_rt = NULL;
 	struct fib6_walker *w;
@@ -2062,7 +2063,8 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,
 }
 
 /* Need to own table->tb6_lock */
-int fib6_del(struct fib6_info *rt, struct nl_info *info)
+int fib6_del(struct fib6_info *rt, struct nl_info *info,
+	     enum rt_del_reason del_reason)
 {
 	struct net *net = info->nl_net;
 	struct fib6_info __rcu **rtp;
@@ -2091,7 +2093,7 @@ int fib6_del(struct fib6_info *rt, struct nl_info *info)
 		if (rt == cur) {
 			if (fib6_requires_src(cur))
 				fib6_routes_require_src_dec(info->nl_net);
-			fib6_del_route(table, fn, rtp, info);
+			fib6_del_route(table, fn, rtp, info, del_reason);
 			return 0;
 		}
 		rtp_next = &cur->fib6_next;
@@ -2253,7 +2255,7 @@ static int fib6_clean_node(struct fib6_walker *w)
 		res = c->func(rt, c->arg);
 		if (res == -1) {
 			w->leaf = rt;
-			res = fib6_del(rt, &info);
+			res = fib6_del(rt, &info, RT_DEL_REASON_UNSPEC);
 			if (res) {
 #if RT6_DEBUG >= 2
 				pr_debug("%s: del failed: rt=%p@%p err=%d\n",
@@ -2401,7 +2403,7 @@ static void fib6_gc_table(struct net *net,
 
 	hlist_for_each_entry_safe(rt, n, &tb6->tb6_gc_hlist, gc_link)
 		if (fib6_age(rt, gc_args) == -1)
-			fib6_del(rt, &info);
+			fib6_del(rt, &info, RT_DEL_REASON_UNSPEC);
 }
 
 static void fib6_gc_all(struct net *net, struct fib6_gc_args *gc_args)
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index ca374b056f5a..252f1825a199 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -3973,7 +3973,8 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags,
 	return err;
 }
 
-static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info)
+static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info,
+			enum rt_del_reason del_reason)
 {
 	struct net *net = info->nl_net;
 	struct fib6_table *table;
@@ -3986,7 +3987,7 @@ static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info)
 
 	table = rt->fib6_table;
 	spin_lock_bh(&table->tb6_lock);
-	err = fib6_del(rt, info);
+	err = fib6_del(rt, info, del_reason);
 	spin_unlock_bh(&table->tb6_lock);
 
 out:
@@ -4001,7 +4002,7 @@ int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify)
 		.skip_notify = skip_notify
 	};
 
-	return __ip6_del_rt(rt, &info);
+	return __ip6_del_rt(rt, &info, RT_DEL_REASON_UNSPEC);
 }
 
 int ip6_del_rt_reason(struct net *net, struct fib6_info *rt,
@@ -4009,7 +4010,7 @@ int ip6_del_rt_reason(struct net *net, struct fib6_info *rt,
 {
 	struct nl_info info = { .nl_net = net };
 
-	return __ip6_del_rt(rt, &info);
+	return __ip6_del_rt(rt, &info, del_reason);
 }
 
 static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)
@@ -4072,13 +4073,13 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)
 		list_for_each_entry_safe(sibling, next_sibling,
 					 &rt->fib6_siblings,
 					 fib6_siblings) {
-			err = fib6_del(sibling, info);
+			err = fib6_del(sibling, info, RT_DEL_REASON_UNSPEC);
 			if (err)
 				goto out_unlock;
 		}
 	}
 
-	err = fib6_del(rt, info);
+	err = fib6_del(rt, info, RT_DEL_REASON_UNSPEC);
 out_unlock:
 	spin_unlock_bh(&table->tb6_lock);
 out_put:
@@ -4204,7 +4205,8 @@ static int ip6_route_del(struct fib6_config *cfg,
 				if (!fib6_info_hold_safe(rt))
 					continue;
 
-				err =  __ip6_del_rt(rt, &cfg->fc_nlinfo);
+				err = __ip6_del_rt(rt, &cfg->fc_nlinfo,
+						   RT_DEL_REASON_UNSPEC);
 				break;
 			}
 			if (cfg->fc_nh_id)
@@ -4223,7 +4225,8 @@ static int ip6_route_del(struct fib6_config *cfg,
 
 			/* if gateway was specified only delete the one hop */
 			if (cfg->fc_flags & RTF_GATEWAY)
-				err = __ip6_del_rt(rt, &cfg->fc_nlinfo);
+				err = __ip6_del_rt(rt, &cfg->fc_nlinfo,
+						   RT_DEL_REASON_UNSPEC);
 			else
 				err = __ip6_del_rt_siblings(rt, cfg);
 			break;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH net-next v5 03/10] ipv6: record the reason for kernel-initiated route deletions
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
  2026-08-04  1:47 ` [PATCH net-next v5 01/10] ipv6: add ip6_del_rt_reason() Yuyang Huang
  2026-08-04  1:47 ` [PATCH net-next v5 02/10] ipv6: propagate the route deletion reason to fib6_del_route() Yuyang Huang
@ 2026-08-04  1:47 ` Yuyang Huang
  2026-08-07 16:22   ` Ido Schimmel
  2026-08-04  1:47 ` [PATCH net-next v5 04/10] ipv6: add a deletion reason argument to rt6_fill_node() Yuyang Huang
                   ` (7 subsequent siblings)
  10 siblings, 1 reply; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

Record why the kernel deletes an IPv6 route on its own:

- RT_DEL_REASON_EXPIRED for routes reaped by the FIB6 garbage
  collector after their RTF_EXPIRES lifetime ran out.
- RT_DEL_REASON_RA_WITHDRAWN for default routes, prefix routes and
  RFC 4191 route information routes withdrawn by a zero-lifetime
  Router Advertisement.

Deleting a default route because its metric changed is not a
withdrawal, so it keeps RT_DEL_REASON_UNSPEC.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
 net/ipv6/addrconf.c | 3 ++-
 net/ipv6/ip6_fib.c  | 2 +-
 net/ipv6/ndisc.c    | 4 +++-
 net/ipv6/route.c    | 2 +-
 4 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index f6fa2715b450..9d89be7e0544 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -2874,7 +2874,8 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
 		if (rt) {
 			/* Autoconf prefix route */
 			if (valid_lft == 0) {
-				ip6_del_rt(net, rt, false);
+				ip6_del_rt_reason(net, rt,
+						  RT_DEL_REASON_RA_WITHDRAWN);
 				rt = NULL;
 			} else {
 				table = rt->fib6_table;
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index 1f6f6f33ded1..f8a9ec7fa616 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -2403,7 +2403,7 @@ static void fib6_gc_table(struct net *net,
 
 	hlist_for_each_entry_safe(rt, n, &tb6->tb6_gc_hlist, gc_link)
 		if (fib6_age(rt, gc_args) == -1)
-			fib6_del(rt, &info, RT_DEL_REASON_UNSPEC);
+			fib6_del(rt, &info, RT_DEL_REASON_EXPIRED);
 }
 
 static void fib6_gc_all(struct net *net, struct fib6_gc_args *gc_args)
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index fe36b3f51285..1111c4ebc61d 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -1361,7 +1361,9 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)
 	defrtr_usr_metric = in6_dev->cnf.ra_defrtr_metric;
 	/* delete the route if lifetime is 0 or if metric needs change */
 	if (rt && (lifetime == 0 || rt->fib6_metric != defrtr_usr_metric)) {
-		ip6_del_rt(net, rt, false);
+		ip6_del_rt_reason(net, rt,
+				  lifetime == 0 ? RT_DEL_REASON_RA_WITHDRAWN :
+						  RT_DEL_REASON_UNSPEC);
 		rt = NULL;
 	}
 
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 252f1825a199..bb14d7f7bad4 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -1020,7 +1020,7 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
 					gwaddr, dev);
 
 	if (rt && !lifetime) {
-		ip6_del_rt(net, rt, false);
+		ip6_del_rt_reason(net, rt, RT_DEL_REASON_RA_WITHDRAWN);
 		rt = NULL;
 	}
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH net-next v5 04/10] ipv6: add a deletion reason argument to rt6_fill_node()
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
                   ` (2 preceding siblings ...)
  2026-08-04  1:47 ` [PATCH net-next v5 03/10] ipv6: record the reason for kernel-initiated route deletions Yuyang Huang
@ 2026-08-04  1:47 ` Yuyang Huang
  2026-08-07 16:22   ` Ido Schimmel
  2026-08-04  1:47 ` [PATCH net-next v5 05/10] ipv6: expose the route deletion reason in RTM_DELROUTE Yuyang Huang
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

Add the deletion reason to rt6_fill_node() so that it can report it to
user space. All callers pass RT_DEL_REASON_UNSPEC for now.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
 net/ipv6/route.c | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index bb14d7f7bad4..6732e95bf7b3 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -111,7 +111,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
 			 struct fib6_info *rt, struct dst_entry *dst,
 			 struct in6_addr *dest, struct in6_addr *src,
 			 int iif, int type, u32 portid, u32 seq,
-			 unsigned int flags);
+			 unsigned int flags, enum rt_del_reason del_reason);
 static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res,
 					   const struct in6_addr *daddr,
 					   const struct in6_addr *saddr);
@@ -4037,7 +4037,8 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)
 
 			if (rt6_fill_node(net, skb, rt, NULL,
 					  NULL, NULL, 0, RTM_DELROUTE,
-					  info->portid, seq, 0) < 0) {
+					  info->portid, seq, 0,
+					  RT_DEL_REASON_UNSPEC) < 0) {
 				kfree_skb(skb);
 				skb = NULL;
 			} else
@@ -5786,7 +5787,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
 			 struct fib6_info *rt, struct dst_entry *dst,
 			 struct in6_addr *dest, struct in6_addr *src,
 			 int iif, int type, u32 portid, u32 seq,
-			 unsigned int flags)
+			 unsigned int flags, enum rt_del_reason del_reason)
 {
 	struct rt6_info *rt6 = dst_rt6_info(dst);
 	struct rt6key *rt6_dst, *rt6_src;
@@ -6066,7 +6067,8 @@ static int rt6_nh_dump_exceptions(struct fib6_nh *nh, void *arg)
 					    &rt6_ex->rt6i->dst, NULL, NULL, 0,
 					    RTM_NEWROUTE,
 					    NETLINK_CB(dump->cb->skb).portid,
-					    dump->cb->nlh->nlmsg_seq, w->flags);
+					    dump->cb->nlh->nlmsg_seq, w->flags,
+					    RT_DEL_REASON_UNSPEC);
 			if (err)
 				return err;
 
@@ -6114,7 +6116,8 @@ int rt6_dump_route(struct fib6_info *rt, void *p_arg, unsigned int skip)
 			if (rt6_fill_node(net, arg->skb, rt, NULL, NULL, NULL,
 					  0, RTM_NEWROUTE,
 					  NETLINK_CB(arg->cb->skb).portid,
-					  arg->cb->nlh->nlmsg_seq, flags)) {
+					  arg->cb->nlh->nlmsg_seq, flags,
+					  RT_DEL_REASON_UNSPEC)) {
 				return 0;
 			}
 			count++;
@@ -6347,12 +6350,14 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
 			err = rt6_fill_node(net, skb, from, NULL, NULL, NULL,
 					    iif, RTM_NEWROUTE,
 					    NETLINK_CB(in_skb).portid,
-					    nlh->nlmsg_seq, 0);
+					    nlh->nlmsg_seq, 0,
+					    RT_DEL_REASON_UNSPEC);
 		else
 			err = rt6_fill_node(net, skb, from, dst, &fl6.daddr,
 					    &fl6.saddr, iif, RTM_NEWROUTE,
 					    NETLINK_CB(in_skb).portid,
-					    nlh->nlmsg_seq, 0);
+					    nlh->nlmsg_seq, 0,
+					    RT_DEL_REASON_UNSPEC);
 	} else {
 		err = -ENETUNREACH;
 	}
@@ -6388,7 +6393,8 @@ void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,
 		goto errout;
 
 	err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0,
-			    event, info->portid, seq, nlm_flags);
+			    event, info->portid, seq, nlm_flags,
+			    RT_DEL_REASON_UNSPEC);
 	if (err < 0) {
 		kfree_skb(skb);
 		/* -EMSGSIZE implies needed space grew under us. */
@@ -6421,7 +6427,8 @@ void fib6_rt_update(struct net *net, struct fib6_info *rt,
 		goto errout;
 
 	err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0,
-			    RTM_NEWROUTE, info->portid, seq, NLM_F_REPLACE);
+			    RTM_NEWROUTE, info->portid, seq, NLM_F_REPLACE,
+			    RT_DEL_REASON_UNSPEC);
 	if (err < 0) {
 		/* -EMSGSIZE implies BUG in rt6_nlmsg_size() */
 		WARN_ON(err == -EMSGSIZE);
@@ -6474,7 +6481,7 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i,
 	}
 
 	err = rt6_fill_node(net, skb, f6i, NULL, NULL, NULL, 0, RTM_NEWROUTE, 0,
-			    0, 0);
+			    0, 0, RT_DEL_REASON_UNSPEC);
 	if (err < 0) {
 		/* -EMSGSIZE implies BUG in rt6_nlmsg_size() */
 		WARN_ON(err == -EMSGSIZE);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH net-next v5 05/10] ipv6: expose the route deletion reason in RTM_DELROUTE
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
                   ` (3 preceding siblings ...)
  2026-08-04  1:47 ` [PATCH net-next v5 04/10] ipv6: add a deletion reason argument to rt6_fill_node() Yuyang Huang
@ 2026-08-04  1:47 ` Yuyang Huang
  2026-08-07 16:23   ` Ido Schimmel
  2026-08-04  1:47 ` [PATCH net-next v5 06/10] ipv6: add inet6_rt_del_notify() Yuyang Huang
                   ` (5 subsequent siblings)
  10 siblings, 1 reply; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

Emit RTA_DEL_REASON from rt6_fill_node() when the deletion reason is
not RT_DEL_REASON_UNSPEC, and reserve room for it in
rt6_nlmsg_size().

Every caller still passes RT_DEL_REASON_UNSPEC.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
 net/ipv6/route.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 6732e95bf7b3..d3bf2dd209bd 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -5751,6 +5751,7 @@ static size_t rt6_nlmsg_size(struct fib6_info *f6i)
 	       + nla_total_size(sizeof(struct rta_cacheinfo))
 	       + nla_total_size(TCP_CA_NAME_MAX) /* RTAX_CC_ALGO */
 	       + nla_total_size(1) /* RTA_PREF */
+	       + nla_total_size(4) /* RTA_DEL_REASON */
 	       + nexthop_len;
 }
 
@@ -5966,6 +5967,10 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,
 	if (rtnl_put_cacheinfo(skb, dst, 0, expires, dst ? dst->error : 0) < 0)
 		goto nla_put_failure;
 
+	if (del_reason != RT_DEL_REASON_UNSPEC &&
+	    nla_put_u32(skb, RTA_DEL_REASON, del_reason))
+		goto nla_put_failure;
+
 	if (nla_put_u8(skb, RTA_PREF, IPV6_EXTRACT_PREF(rt6_flags)))
 		goto nla_put_failure;
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH net-next v5 06/10] ipv6: add inet6_rt_del_notify()
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
                   ` (4 preceding siblings ...)
  2026-08-04  1:47 ` [PATCH net-next v5 05/10] ipv6: expose the route deletion reason in RTM_DELROUTE Yuyang Huang
@ 2026-08-04  1:47 ` Yuyang Huang
  2026-08-07 16:23   ` Ido Schimmel
  2026-08-04  1:47 ` [PATCH net-next v5 07/10] netlink: specs: rt-route: add route notifications Yuyang Huang
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

Move the body of inet6_rt_notify() to __inet6_rt_notify() and give it
the deletion reason. inet6_rt_notify() keeps its prototype, so the
route addition path does not change.

Add inet6_rt_del_notify() and call it from fib6_del_route().
RTA_DEL_REASON now reaches user space on RTM_DELROUTE for routes the
kernel deleted on its own.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
 include/net/ip6_fib.h |  2 ++
 net/ipv6/ip6_fib.c    |  2 +-
 net/ipv6/route.c      | 20 ++++++++++++++++----
 3 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index 0ba5d3c56543..232289b439c3 100644
--- a/include/net/ip6_fib.h
+++ b/include/net/ip6_fib.h
@@ -533,6 +533,8 @@ static inline void fib6_rt_update(struct net *net, struct fib6_info *rt,
 #endif
 void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,
 		     unsigned int flags);
+void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info,
+			 enum rt_del_reason del_reason);
 
 void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args,
 			 unsigned long now);
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index f8a9ec7fa616..3e382ba1573e 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -2057,7 +2057,7 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,
 			call_fib6_entry_notifiers_replace(net, replace_rt);
 	}
 	if (!info->skip_notify)
-		inet6_rt_notify(RTM_DELROUTE, rt, info, 0);
+		inet6_rt_del_notify(rt, info, del_reason);
 
 	fib6_info_release(rt);
 }
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index d3bf2dd209bd..1704359d3960 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -6378,8 +6378,9 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
 	return err;
 }
 
-void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,
-		     unsigned int nlm_flags)
+static void __inet6_rt_notify(int event, struct fib6_info *rt,
+			      struct nl_info *info, unsigned int nlm_flags,
+			      enum rt_del_reason del_reason)
 {
 	struct net *net = info->nl_net;
 	struct sk_buff *skb;
@@ -6398,8 +6399,7 @@ void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,
 		goto errout;
 
 	err = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0,
-			    event, info->portid, seq, nlm_flags,
-			    RT_DEL_REASON_UNSPEC);
+			    event, info->portid, seq, nlm_flags, del_reason);
 	if (err < 0) {
 		kfree_skb(skb);
 		/* -EMSGSIZE implies needed space grew under us. */
@@ -6420,6 +6420,18 @@ void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,
 	rtnl_set_sk_err(net, RTNLGRP_IPV6_ROUTE, err);
 }
 
+void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,
+		     unsigned int nlm_flags)
+{
+	__inet6_rt_notify(event, rt, info, nlm_flags, RT_DEL_REASON_UNSPEC);
+}
+
+void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info,
+			 enum rt_del_reason del_reason)
+{
+	__inet6_rt_notify(RTM_DELROUTE, rt, info, 0, del_reason);
+}
+
 void fib6_rt_update(struct net *net, struct fib6_info *rt,
 		    struct nl_info *info)
 {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH net-next v5 07/10] netlink: specs: rt-route: add route notifications
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
                   ` (5 preceding siblings ...)
  2026-08-04  1:47 ` [PATCH net-next v5 06/10] ipv6: add inet6_rt_del_notify() Yuyang Huang
@ 2026-08-04  1:47 ` Yuyang Huang
  2026-08-04  1:47 ` [PATCH net-next v5 08/10] netlink: specs: rt-route: split out the request attribute list Yuyang Huang
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

Declare the RTM_NEWROUTE and RTM_DELROUTE notifications and the route
multicast groups, so that generated clients can subscribe to route
changes. Both notifications reuse the getroute reply attributes.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
 Documentation/netlink/specs/rt-route.yaml | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/Documentation/netlink/specs/rt-route.yaml b/Documentation/netlink/specs/rt-route.yaml
index 33195db96746..62b215614210 100644
--- a/Documentation/netlink/specs/rt-route.yaml
+++ b/Documentation/netlink/specs/rt-route.yaml
@@ -322,3 +322,22 @@ operations:
         request:
           value: 25
           attributes: *all-route-attrs
+    -
+      name: newroute-ntf
+      doc: Notification about a created route.
+      value: 24
+      notify: getroute
+    -
+      name: delroute-ntf
+      doc: Notification about a deleted route.
+      value: 25
+      notify: getroute
+
+mcast-groups:
+  list:
+    -
+      name: rtnlgrp-ipv4-route
+      value: 7
+    -
+      name: rtnlgrp-ipv6-route
+      value: 11
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH net-next v5 08/10] netlink: specs: rt-route: split out the request attribute list
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
                   ` (6 preceding siblings ...)
  2026-08-04  1:47 ` [PATCH net-next v5 07/10] netlink: specs: rt-route: add route notifications Yuyang Huang
@ 2026-08-04  1:47 ` Yuyang Huang
  2026-08-04  1:47 ` [PATCH net-next v5 09/10] netlink: specs: rt-route: add the route deletion reason Yuyang Huang
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

The newroute and delroute requests alias the same attribute list as the
getroute reply, but requests and replies do not carry the same
attributes. Give the requests their own list.

The two lists are identical today, so the generated code does not
change.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
 Documentation/netlink/specs/rt-route.yaml | 32 +++++++++++++++++++++--
 1 file changed, 30 insertions(+), 2 deletions(-)

diff --git a/Documentation/netlink/specs/rt-route.yaml b/Documentation/netlink/specs/rt-route.yaml
index 62b215614210..9f4a1a253676 100644
--- a/Documentation/netlink/specs/rt-route.yaml
+++ b/Documentation/netlink/specs/rt-route.yaml
@@ -313,7 +313,35 @@ operations:
       do:
         request:
           value: 24
-          attributes: *all-route-attrs
+          attributes: &route-req-attrs
+            - dst
+            - src
+            - iif
+            - oif
+            - gateway
+            - priority
+            - prefsrc
+            - metrics
+            - multipath
+            - flow
+            - cacheinfo
+            - table
+            - mark
+            - mfc-stats
+            - via
+            - newdst
+            - pref
+            - encap-type
+            - encap
+            - expires
+            - pad
+            - uid
+            - ttl-propagate
+            - ip-proto
+            - sport
+            - dport
+            - nh-id
+            - flowlabel
     -
       name: delroute
       doc: Delete an existing route
@@ -321,7 +349,7 @@ operations:
       do:
         request:
           value: 25
-          attributes: *all-route-attrs
+          attributes: *route-req-attrs
     -
       name: newroute-ntf
       doc: Notification about a created route.
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH net-next v5 09/10] netlink: specs: rt-route: add the route deletion reason
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
                   ` (7 preceding siblings ...)
  2026-08-04  1:47 ` [PATCH net-next v5 08/10] netlink: specs: rt-route: split out the request attribute list Yuyang Huang
@ 2026-08-04  1:47 ` Yuyang Huang
  2026-08-04  1:47 ` [PATCH net-next v5 10/10] selftests: net: verify RTA_DEL_REASON on route deletion Yuyang Huang
  2026-08-05 10:41 ` [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
  10 siblings, 0 replies; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

Add the del-reason attribute and its enum to the route attribute set,
and to the getroute reply, which the route notifications reuse.

The attribute is absent from the newroute and delroute request lists.
RTA_DEL_REASON is above strict_start_type in rtm_ipv6_policy, so
encoding it in a request is rejected with -EINVAL.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
 Documentation/netlink/specs/rt-route.yaml | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/Documentation/netlink/specs/rt-route.yaml b/Documentation/netlink/specs/rt-route.yaml
index 9f4a1a253676..f14532798640 100644
--- a/Documentation/netlink/specs/rt-route.yaml
+++ b/Documentation/netlink/specs/rt-route.yaml
@@ -78,6 +78,15 @@ definitions:
       -
         name: rta-used
         type: u32
+  -
+    name: del-reason
+    type: enum
+    name-prefix: rt-del-reason-
+    enum-name: rt-del-reason
+    entries:
+      - unspec
+      - expired
+      - ra-withdrawn
 
 attribute-sets:
   -
@@ -185,6 +194,10 @@ attribute-sets:
         type: u32
         byte-order: big-endian
         display-hint: hex
+      -
+        name: del-reason
+        type: u32
+        enum: del-reason
   -
     name: metrics
     name-prefix: rtax-
@@ -299,6 +312,7 @@ operations:
             - dport
             - nh-id
             - flowlabel
+            - del-reason
       dump:
         request:
           value: 26
@@ -313,6 +327,9 @@ operations:
       do:
         request:
           value: 24
+          # del-reason is notification-only. It is above
+          # strict_start_type in rtm_ipv6_policy, so the kernel rejects
+          # it in requests.
           attributes: &route-req-attrs
             - dst
             - src
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH net-next v5 10/10] selftests: net: verify RTA_DEL_REASON on route deletion
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
                   ` (8 preceding siblings ...)
  2026-08-04  1:47 ` [PATCH net-next v5 09/10] netlink: specs: rt-route: add the route deletion reason Yuyang Huang
@ 2026-08-04  1:47 ` Yuyang Huang
  2026-08-05 10:41 ` [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
  10 siblings, 0 replies; 22+ messages in thread
From: Yuyang Huang @ 2026-08-04  1:47 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

Extend rtnetlink.py to check the reason reported in RTM_DELROUTE:

- expired: route with a 2s lifetime collected by the fib6 GC
  (gc_interval lowered like fib_tests.sh fib6_gc_test does);
- ra-withdrawn: a single RA advertises a default route (router
  lifetime), an on-link prefix route (RFC 4861 prefix information
  option) and a route information option route (RFC 4191), then a
  second RA withdraws all three with zero lifetimes; the RAs are
  crafted over a raw ICMPv6 socket so the test does not depend on an
  external RA tool;
- absence: a userspace deletion request records no cause and must not
  carry the attribute at all.

Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
---
 .../testing/selftests/net/lib/py/__init__.py  |   4 +-
 tools/testing/selftests/net/lib/py/ynl.py     |   7 +-
 tools/testing/selftests/net/rtnetlink.py      | 181 +++++++++++++++++-
 3 files changed, 187 insertions(+), 5 deletions(-)

diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index e58bdbdc58ee..34935886b6ad 100644
--- a/tools/testing/selftests/net/lib/py/__init__.py
+++ b/tools/testing/selftests/net/lib/py/__init__.py
@@ -17,7 +17,7 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \
     wait_file, tool, tc
 from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids
 from .ynl import NlError, NlctrlFamily, YnlFamily, \
-    EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily
+    EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily, RtnlRouteFamily
 from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink
 
 __all__ = ["KSRC",
@@ -34,4 +34,4 @@ __all__ = ["KSRC",
            "NetdevSim", "NetdevSimDev",
            "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError",
            "YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily",
-           "NlctrlFamily", "RtnlAddrFamily", "Netlink"]
+           "NlctrlFamily", "RtnlAddrFamily", "RtnlRouteFamily", "Netlink"]
diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py
index 2e567062aa6c..08deff756f29 100644
--- a/tools/testing/selftests/net/lib/py/ynl.py
+++ b/tools/testing/selftests/net/lib/py/ynl.py
@@ -29,7 +29,7 @@ except ModuleNotFoundError as e:
 
 __all__ = [
     "NlError", "NlPolicy", "Netlink", "YnlFamily", "SPEC_PATH",
-    "EthtoolFamily", "RtnlFamily", "RtnlAddrFamily",
+    "EthtoolFamily", "RtnlFamily", "RtnlAddrFamily", "RtnlRouteFamily",
     "NetdevFamily", "NetshaperFamily", "NlctrlFamily", "DevlinkFamily",
     "PSPFamily",
 ]
@@ -54,6 +54,11 @@ class RtnlAddrFamily(YnlFamily):
         super().__init__((SPEC_PATH / Path('rt-addr.yaml')).as_posix(),
                          schema='', recv_size=recv_size)
 
+class RtnlRouteFamily(YnlFamily):
+    def __init__(self, recv_size=0):
+        super().__init__((SPEC_PATH / Path('rt-route.yaml')).as_posix(),
+                         schema='', recv_size=recv_size)
+
 class NetdevFamily(YnlFamily):
     def __init__(self, recv_size=0):
         super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(),
diff --git a/tools/testing/selftests/net/rtnetlink.py b/tools/testing/selftests/net/rtnetlink.py
index 0c67c7c00d84..f74ccd5c2d13 100755
--- a/tools/testing/selftests/net/rtnetlink.py
+++ b/tools/testing/selftests/net/rtnetlink.py
@@ -5,7 +5,8 @@ import socket
 import struct
 import time
 from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_eq, ksft_ge, ksft_true, KsftSkipEx
-from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily
+from lib.py import ksft_not_in, ksft_not_none
+from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily, RtnlRouteFamily
 
 IPV4_ALL_HOSTS_MULTICAST = b'\xe0\x00\x00\x01'
 IPV4_TEST_MULTICAST = b'\xef\x01\x01\x01'
@@ -134,8 +135,184 @@ def ipv4_devconf_notify() -> None:
     ksft_true(f"inet {ifname} forwarding on" in cmd_obj.stdout,
               f"No 'forwarding on' notificiation found for interface {ifname}")
 
+def _rtnl_route_subscribe(ns):
+    with NetNSEnter(str(ns)):
+        rtnl = RtnlRouteFamily()
+    rtnl.ntf_subscribe("rtnlgrp-ipv6-route")
+    return rtnl
+
+
+def _wait_route_ntf(rtnl, name, dst_len, dst=None, deadline=10):
+    """Return the attrs of the first matching notification, None on timeout."""
+
+    for msg in rtnl.poll_ntf(duration=deadline):
+        if msg['name'] != name:
+            continue
+        attrs = msg['msg']
+        if attrs['rtm-dst-len'] != dst_len:
+            continue
+        if dst is not None and attrs.get('dst') != dst:
+            continue
+        return attrs
+    return None
+
+
+def _collect_route_ntfs(rtnl, name, want, deadline=10):
+    """Gather attrs of matching notifications, keyed by (dst_len, dst)."""
+
+    seen = {}
+    for msg in rtnl.poll_ntf(duration=deadline):
+        if msg['name'] != name:
+            continue
+        attrs = msg['msg']
+        key = (attrs['rtm-dst-len'], attrs.get('dst'))
+        if key in want:
+            seen[key] = attrs
+            if len(seen) == len(want):
+                break
+    return seen
+
+
+def _write_ipv6_sysctl(name, value):
+    with open(f"/proc/sys/net/ipv6/{name}", "w") as f:
+        f.write(f"{value}\n")
+
+
+def ipv6_route_del_reason_expired() -> None:
+    """An expired route reports RTA_DEL_REASON == expired."""
+
+    with NetNS() as ns:
+        rtnl = _rtnl_route_subscribe(ns)
+        with NetNSEnter(str(ns)):
+            _write_ipv6_sysctl("route/gc_interval", 2)
+        ip("link add name dummy1 type dummy", ns=str(ns))
+        ip("link set dev dummy1 up", ns=str(ns))
+        ip("-6 route add 2001:db8:2::/64 dev dummy1 expires 2", ns=str(ns))
+
+        attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:2::',
+                                deadline=15)
+        ksft_not_none(attrs, "no RTM_DELROUTE for the expired route")
+        if attrs is not None:
+            ksft_eq(attrs.get('del-reason'), 'expired')
+
+
+def _send_ra(sock, ifindex, lifetime, rio=None, pio=None):
+    """The kernel fills in the ICMPv6 checksum on raw ICMPv6 sockets."""
+
+    # type, code, cksum, hop limit, flags, router lifetime,
+    # reachable time, retrans timer
+    ra = struct.pack('!BBHBBHII', 134, 0, 0, 64, 0, lifetime, 0, 0)
+    if rio is not None:
+        prefix, plen, rio_lifetime = rio
+        # RFC 4191 route information option, /64 prefix (8 bytes)
+        ra += struct.pack('!BBBBI', 24, 2, plen, 0, rio_lifetime)
+        ra += socket.inet_pton(socket.AF_INET6, prefix)[:8]
+    if pio is not None:
+        prefix, plen, valid_lft = pio
+        # RFC 4861 prefix information option, on-link only (L set, A clear)
+        ra += struct.pack('!BBBBIII', 3, 4, plen, 0x80, valid_lft, 0, 0)
+        ra += socket.inet_pton(socket.AF_INET6, prefix)
+    sock.sendto(ra, ('ff02::1', 0, 0, ifindex))
+
+
+def _ra_router_sock(ns_r, ifname):
+    with NetNSEnter(str(ns_r)):
+        sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW,
+                             socket.IPPROTO_ICMPV6)
+        sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 255)
+        return sock, socket.if_nametoindex(ifname)
+
+
+def _ra_advertise_routes(rtnl, sock, ifindex, want, **ra_opts):
+    """
+    Sending fails with EADDRNOTAVAIL until the router's link-local
+    address passes DAD, so retry.
+    """
+
+    seen = {}
+    for _ in range(10):
+        try:
+            _send_ra(sock, ifindex, **ra_opts)
+        except OSError:
+            time.sleep(0.2)
+            continue
+        seen.update(_collect_route_ntfs(rtnl, 'newroute-ntf',
+                                        want - set(seen.keys()), deadline=2))
+        if len(seen) == len(want):
+            break
+    return seen
+
+
+def ipv6_route_del_reason_ra_withdrawn() -> None:
+    """
+    Routes withdrawn by a zero-lifetime RA (router lifetime, RFC 4861
+    PIO, RFC 4191 RIO) report RTA_DEL_REASON == ra-withdrawn.
+    """
+
+    # (rtm-dst-len, dst); the default route carries no RTA_DST
+    routes = {(0, None), (64, '2001:db8:6::'), (64, '2001:db8:5::')}
+
+    with NetNS() as ns_h, NetNS() as ns_r:
+        ip(f"link add veth0 netns {ns_h} type veth peer name veth1 netns {ns_r}")
+        with NetNSEnter(str(ns_h)):
+            _write_ipv6_sysctl("conf/veth0/accept_ra", 2)
+            _write_ipv6_sysctl("conf/veth0/forwarding", 0)
+            try:
+                _write_ipv6_sysctl("conf/veth0/accept_ra_rt_info_max_plen", 64)
+            except FileNotFoundError:
+                raise KsftSkipEx("no CONFIG_IPV6_ROUTE_INFO")
+        with NetNSEnter(str(ns_r)):
+            # skip DAD so the router's link-local source is usable right away
+            _write_ipv6_sysctl("conf/veth1/accept_dad", 0)
+        ip("link set dev veth0 up", ns=str(ns_h))
+        ip("link set dev veth1 up", ns=str(ns_r))
+
+        rtnl = _rtnl_route_subscribe(ns_h)
+        sock, ifindex = _ra_router_sock(ns_r, "veth1")
+
+        seen = _ra_advertise_routes(rtnl, sock, ifindex, routes,
+                                    lifetime=1800,
+                                    rio=('2001:db8:5::', 64, 600),
+                                    pio=('2001:db8:6::', 64, 600))
+        ksft_eq(set(seen), routes, "not all RA routes were installed")
+        if set(seen) != routes:
+            return
+
+        _send_ra(sock, ifindex, 0, rio=('2001:db8:5::', 64, 0),
+                 pio=('2001:db8:6::', 64, 0))
+        seen = _collect_route_ntfs(rtnl, 'delroute-ntf', routes)
+        for key in routes:
+            attrs = seen.get(key)
+            ksft_not_none(attrs, f"no RTM_DELROUTE for {key}")
+            if attrs is not None:
+                ksft_eq(attrs.get('del-reason'), 'ra-withdrawn')
+
+
+def ipv6_route_del_reason_absent() -> None:
+    """
+    A deletion path that records no cause (here a userspace request)
+    must not carry RTA_DEL_REASON at all.
+    """
+
+    with NetNS() as ns:
+        rtnl = _rtnl_route_subscribe(ns)
+        ip("link add name dummy1 type dummy", ns=str(ns))
+        ip("link set dev dummy1 up", ns=str(ns))
+        ip("-6 route add 2001:db8:1::/64 dev dummy1", ns=str(ns))
+        ip("-6 route del 2001:db8:1::/64 dev dummy1", ns=str(ns))
+
+        attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:1::')
+        ksft_not_none(attrs, "no RTM_DELROUTE for 2001:db8:1::/64")
+        if attrs is not None:
+            ksft_not_in('del-reason', attrs,
+                        "user deletion must not carry del-reason")
+
+
 def main() -> None:
-    ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify])
+    ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify,
+              ipv6_route_del_reason_expired,
+              ipv6_route_del_reason_ra_withdrawn,
+              ipv6_route_del_reason_absent])
     ksft_exit()
 
 if __name__ == "__main__":
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE
  2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
                   ` (9 preceding siblings ...)
  2026-08-04  1:47 ` [PATCH net-next v5 10/10] selftests: net: verify RTA_DEL_REASON on route deletion Yuyang Huang
@ 2026-08-05 10:41 ` Yuyang Huang
  2026-08-07 10:11   ` Yuyang Huang
  10 siblings, 1 reply; 22+ messages in thread
From: Yuyang Huang @ 2026-08-05 10:41 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

I checked the sashiko's comment:
https://patchwork.kernel.org/project/netdevbpf/patch/20260804014714.4362-10-sigefriedhyy@gmail.com/

> Should this new UAPI attribute also be added to the Netlink YAML
> specification?

> Is this new attribute missing from the netlink YAML
> specification?
> should this new attribute be documented in
> Documentation/netlink/specs/rt-route.yaml

> Is the del-reason (RTA_DEL_REASON) attribute missing from the YAML
> specification?

> Is there a missing patch in this series to actually report the
> deletion reason?

> Does this del_reason actually get passed from the deletion path?

These are answered by later patches in the same series.

> This is a pre-existing issue, but could this RTM_NEWROUTE be emitted
> out of order with respect to an RTM_DELROUTE?

Not related to the current series but might be worth sending a follow
up separately.

> Should this new enum and its entries include doc properties to
> describe the specific deletion reasons for the uAPI documentation?

Agreed, but it is a nit. If I need to send out patch series 6 due to
adjusting later review comments, I will include the fix for this
comment as well.

> Is RTA_DEL_REASON missing from rtm_ipv6_policy and rtm_ipv4_policy?
> [...] Should it be added to the policies as NLA_IGNORE or
> NLA_REJECT?
> Will the new RTA_DEL_REASON attribute be rejected if user space
> echoes it back? [...] causing an ABI breakage.

The comment seems wrong. The attribute is kernel to user space
notification-only, user space code should never set it.


Thanks,

Yuyang

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE
  2026-08-05 10:41 ` [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
@ 2026-08-07 10:11   ` Yuyang Huang
  2026-08-07 15:52     ` Ido Schimmel
  0 siblings, 1 reply; 22+ messages in thread
From: Yuyang Huang @ 2026-08-07 10:11 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Ido Schimmel, Jakub Kicinski,
	Nimrod Oren, Paolo Abeni, Shuah Khan, Simon Horman,
	Stanislav Fomichev, Willem de Bruijn, linux-kernel,
	linux-kselftest, netdev

Reply to comment in the other sashiko run:

https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260804014714.4362-1-sigefriedhyy%40gmail.com

> Should the netlink socket returned here be closed by the callers?
> [...] Would a defer(rtnl.close) [...] make teardown explicit?

> Same question for this raw ICMPv6 socket [...] Would a
> defer(sock.close) here be preferable?

> is the stated reason for the retry loop accurate? [...]
> addrconf_dad_begin() takes the early branch and never sends a DAD
> probe at all [...] Could the docstring describe that tentative
> window instead?

The comments above look valid, but I do not think they affect the
correctness of the test code, so it does not seem worth sending a v6
just to fix them. If a v6 is needed for other reasons, I will fix
them there.

> Should the #else branch also get a stub for ip6_del_rt_reason()?

The ip6_del_rt() stub exists only because __remove_nexthop_fib() in
net/ipv4/nexthop.c is obj-y and calls it with CONFIG_IPV6=n. The new
helper has no caller outside net/ipv6/, so a stub for it would be
dead code. I would rather add one when a caller needs it.

Thanks,

Yuyang

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE
  2026-08-07 10:11   ` Yuyang Huang
@ 2026-08-07 15:52     ` Ido Schimmel
  2026-08-08  0:36       ` Yuyang Huang
  0 siblings, 1 reply; 22+ messages in thread
From: Ido Schimmel @ 2026-08-07 15:52 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Jakub Kicinski, Nimrod Oren,
	Paolo Abeni, Shuah Khan, Simon Horman, Stanislav Fomichev,
	Willem de Bruijn, linux-kernel, linux-kselftest, netdev

On Fri, Aug 07, 2026 at 07:11:29PM +0900, Yuyang Huang wrote:
> Reply to comment in the other sashiko run:
> 
> https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260804014714.4362-1-sigefriedhyy%40gmail.com

[...]

> > Should the #else branch also get a stub for ip6_del_rt_reason()?
> 
> The ip6_del_rt() stub exists only because __remove_nexthop_fib() in
> net/ipv4/nexthop.c is obj-y and calls it with CONFIG_IPV6=n. The new
> helper has no caller outside net/ipv6/, so a stub for it would be
> dead code. I would rather add one when a caller needs it.

But why put it under '#if IS_ENABLED(CONFIG_IPV6)' in the first place?
Just move it above, next to ip6_ins_rt()...

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 01/10] ipv6: add ip6_del_rt_reason()
  2026-08-04  1:47 ` [PATCH net-next v5 01/10] ipv6: add ip6_del_rt_reason() Yuyang Huang
@ 2026-08-07 16:21   ` Ido Schimmel
  2026-08-08  0:37     ` Yuyang Huang
  0 siblings, 1 reply; 22+ messages in thread
From: Ido Schimmel @ 2026-08-07 16:21 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Jakub Kicinski, Nimrod Oren,
	Paolo Abeni, Shuah Khan, Simon Horman, Stanislav Fomichev,
	Willem de Bruijn, linux-kernel, linux-kselftest, netdev

On Tue, Aug 04, 2026 at 10:47:05AM +0900, Yuyang Huang wrote:
> Add RTA_DEL_REASON and enum rt_del_reason to the rtnetlink uAPI, and
> add ip6_del_rt_reason(), which takes the reason a route is being
> deleted. It has no skip_notify argument: a caller that records a
> deletion reason wants the notification that carries it.
> 
> The reason is unused for now. Subsequent patches propagate it to the
> deletion path and report it on RTM_DELROUTE.
> 
> Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>
> ---
>  include/net/ip6_route.h        |  2 ++
>  include/uapi/linux/rtnetlink.h | 22 ++++++++++++++++++++++
>  net/ipv6/route.c               |  8 ++++++++
>  3 files changed, 32 insertions(+)
> 
> diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
> index 09ffe0f13ce7..7bcf0e5bd887 100644
> --- a/include/net/ip6_route.h
> +++ b/include/net/ip6_route.h
> @@ -128,6 +128,8 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags,
>  int ip6_ins_rt(struct net *net, struct fib6_info *f6i);
>  #if IS_ENABLED(CONFIG_IPV6)
>  int ip6_del_rt(struct net *net, struct fib6_info *f6i, bool skip_notify);
> +int ip6_del_rt_reason(struct net *net, struct fib6_info *f6i,
> +		      enum rt_del_reason del_reason);

Since the function is only invoked by IPv6 code there is no reason to
put it inside the guard and it would also suppress the comment from
Sashiko. You can place it near ip6_ins_rt().

>  #else
>  static inline int ip6_del_rt(struct net *net, struct fib6_info *f6i,
>  			     bool skip_notify)
> diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
> index 27265fd31e5f..66cfc17ede0e 100644
> --- a/include/uapi/linux/rtnetlink.h
> +++ b/include/uapi/linux/rtnetlink.h
> @@ -399,6 +399,7 @@ enum rtattr_type_t {
>  	RTA_DPORT,
>  	RTA_NH_ID,
>  	RTA_FLOWLABEL,
> +	RTA_DEL_REASON,
>  	__RTA_MAX
>  };
>  
> @@ -407,6 +408,27 @@ enum rtattr_type_t {
>  #define RTM_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))))
>  #define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg))
>  
> +/* RTA_DEL_REASON: why the kernel deleted the route. u32.
> + * Emitted only on RTM_DELROUTE notifications, and only when the deletion
> + * path records a cause. Absence means either an older kernel or a
> + * deletion path that does not (yet) record its cause - consumers must
> + * treat "absent" and "unspec" identically. New causes may be appended.
> + * Currently only IPv6 deletion paths record a cause.

Please add a note that this attribute is rejected by the kernel in
requests, like you did in patch #9.

> + *
> + * The value space is family-agnostic: a value must never be
> + * reinterpreted per address family. A cause that only one family can
> + * produce still gets its own value rather than reusing another
> + * family's.
> + */
> +enum rt_del_reason {
> +	RT_DEL_REASON_UNSPEC,		/* cause not recorded */
> +	RT_DEL_REASON_EXPIRED,		/* RTF_EXPIRES lifetime ran out (GC) */
> +	RT_DEL_REASON_RA_WITHDRAWN,	/* zero-lifetime RA / PIO / RIO */
> +	__RT_DEL_REASON_MAX
> +};
> +
> +#define RT_DEL_REASON_MAX (__RT_DEL_REASON_MAX - 1)
> +
>  /* RTM_MULTIPATH --- array of struct rtnexthop.
>   *
>   * "struct rtnexthop" describes all necessary nexthop information,
> diff --git a/net/ipv6/route.c b/net/ipv6/route.c
> index fc42d67e5822..ca374b056f5a 100644
> --- a/net/ipv6/route.c
> +++ b/net/ipv6/route.c
> @@ -4004,6 +4004,14 @@ int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify)
>  	return __ip6_del_rt(rt, &info);
>  }
>  
> +int ip6_del_rt_reason(struct net *net, struct fib6_info *rt,
> +		      enum rt_del_reason del_reason)
> +{
> +	struct nl_info info = { .nl_net = net };
> +
> +	return __ip6_del_rt(rt, &info);
> +}
> +
>  static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)
>  {
>  	struct nl_info *info = &cfg->fc_nlinfo;
> -- 
> 2.43.0
> 

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 02/10] ipv6: propagate the route deletion reason to fib6_del_route()
  2026-08-04  1:47 ` [PATCH net-next v5 02/10] ipv6: propagate the route deletion reason to fib6_del_route() Yuyang Huang
@ 2026-08-07 16:22   ` Ido Schimmel
  0 siblings, 0 replies; 22+ messages in thread
From: Ido Schimmel @ 2026-08-07 16:22 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Jakub Kicinski, Nimrod Oren,
	Paolo Abeni, Shuah Khan, Simon Horman, Stanislav Fomichev,
	Willem de Bruijn, linux-kernel, linux-kselftest, netdev

On Tue, Aug 04, 2026 at 10:47:06AM +0900, Yuyang Huang wrote:
> Pass the deletion reason from ip6_del_rt_reason() down through
> __ip6_del_rt(), fib6_del() and into fib6_del_route(). All existing
> callers pass RT_DEL_REASON_UNSPEC.
> 
> fib6_del_route() ignores the reason until the notification path learns
> to report it.
> 
> Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 03/10] ipv6: record the reason for kernel-initiated route deletions
  2026-08-04  1:47 ` [PATCH net-next v5 03/10] ipv6: record the reason for kernel-initiated route deletions Yuyang Huang
@ 2026-08-07 16:22   ` Ido Schimmel
  0 siblings, 0 replies; 22+ messages in thread
From: Ido Schimmel @ 2026-08-07 16:22 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Jakub Kicinski, Nimrod Oren,
	Paolo Abeni, Shuah Khan, Simon Horman, Stanislav Fomichev,
	Willem de Bruijn, linux-kernel, linux-kselftest, netdev

On Tue, Aug 04, 2026 at 10:47:07AM +0900, Yuyang Huang wrote:
> Record why the kernel deletes an IPv6 route on its own:
> 
> - RT_DEL_REASON_EXPIRED for routes reaped by the FIB6 garbage
>   collector after their RTF_EXPIRES lifetime ran out.
> - RT_DEL_REASON_RA_WITHDRAWN for default routes, prefix routes and
>   RFC 4191 route information routes withdrawn by a zero-lifetime
>   Router Advertisement.
> 
> Deleting a default route because its metric changed is not a
> withdrawal, so it keeps RT_DEL_REASON_UNSPEC.
> 
> Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 04/10] ipv6: add a deletion reason argument to rt6_fill_node()
  2026-08-04  1:47 ` [PATCH net-next v5 04/10] ipv6: add a deletion reason argument to rt6_fill_node() Yuyang Huang
@ 2026-08-07 16:22   ` Ido Schimmel
  0 siblings, 0 replies; 22+ messages in thread
From: Ido Schimmel @ 2026-08-07 16:22 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Jakub Kicinski, Nimrod Oren,
	Paolo Abeni, Shuah Khan, Simon Horman, Stanislav Fomichev,
	Willem de Bruijn, linux-kernel, linux-kselftest, netdev

On Tue, Aug 04, 2026 at 10:47:08AM +0900, Yuyang Huang wrote:
> Add the deletion reason to rt6_fill_node() so that it can report it to
> user space. All callers pass RT_DEL_REASON_UNSPEC for now.
> 
> Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 05/10] ipv6: expose the route deletion reason in RTM_DELROUTE
  2026-08-04  1:47 ` [PATCH net-next v5 05/10] ipv6: expose the route deletion reason in RTM_DELROUTE Yuyang Huang
@ 2026-08-07 16:23   ` Ido Schimmel
  0 siblings, 0 replies; 22+ messages in thread
From: Ido Schimmel @ 2026-08-07 16:23 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Jakub Kicinski, Nimrod Oren,
	Paolo Abeni, Shuah Khan, Simon Horman, Stanislav Fomichev,
	Willem de Bruijn, linux-kernel, linux-kselftest, netdev

On Tue, Aug 04, 2026 at 10:47:09AM +0900, Yuyang Huang wrote:
> Emit RTA_DEL_REASON from rt6_fill_node() when the deletion reason is
> not RT_DEL_REASON_UNSPEC, and reserve room for it in
> rt6_nlmsg_size().
> 
> Every caller still passes RT_DEL_REASON_UNSPEC.
> 
> Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 06/10] ipv6: add inet6_rt_del_notify()
  2026-08-04  1:47 ` [PATCH net-next v5 06/10] ipv6: add inet6_rt_del_notify() Yuyang Huang
@ 2026-08-07 16:23   ` Ido Schimmel
  0 siblings, 0 replies; 22+ messages in thread
From: Ido Schimmel @ 2026-08-07 16:23 UTC (permalink / raw)
  To: Yuyang Huang
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Jakub Kicinski, Nimrod Oren,
	Paolo Abeni, Shuah Khan, Simon Horman, Stanislav Fomichev,
	Willem de Bruijn, linux-kernel, linux-kselftest, netdev

On Tue, Aug 04, 2026 at 10:47:10AM +0900, Yuyang Huang wrote:
> Move the body of inet6_rt_notify() to __inet6_rt_notify() and give it
> the deletion reason. inet6_rt_notify() keeps its prototype, so the
> route addition path does not change.
> 
> Add inet6_rt_del_notify() and call it from fib6_del_route().
> RTA_DEL_REASON now reaches user space on RTM_DELROUTE for routes the
> kernel deleted on its own.
> 
> Signed-off-by: Yuyang Huang <sigefriedhyy@gmail.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE
  2026-08-07 15:52     ` Ido Schimmel
@ 2026-08-08  0:36       ` Yuyang Huang
  0 siblings, 0 replies; 22+ messages in thread
From: Yuyang Huang @ 2026-08-08  0:36 UTC (permalink / raw)
  To: Ido Schimmel
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Jakub Kicinski, Nimrod Oren,
	Paolo Abeni, Shuah Khan, Simon Horman, Stanislav Fomichev,
	Willem de Bruijn, linux-kernel, linux-kselftest, netdev

On Sat, Aug 8, 2026 at 12:52 AM Ido Schimmel <idosch@nvidia.com> wrote:

> But why put it under '#if IS_ENABLED(CONFIG_IPV6)' in the first place?
> Just move it above, next to ip6_ins_rt()...

Thanks for the comment, will fix it in patchset v6.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH net-next v5 01/10] ipv6: add ip6_del_rt_reason()
  2026-08-07 16:21   ` Ido Schimmel
@ 2026-08-08  0:37     ` Yuyang Huang
  0 siblings, 0 replies; 22+ messages in thread
From: Yuyang Huang @ 2026-08-08  0:37 UTC (permalink / raw)
  To: Ido Schimmel
  Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
	David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
	Eric Dumazet, Gal Pressman, Jakub Kicinski, Nimrod Oren,
	Paolo Abeni, Shuah Khan, Simon Horman, Stanislav Fomichev,
	Willem de Bruijn, linux-kernel, linux-kselftest, netdev

On Sat, Aug 8, 2026 at 1:22 AM Ido Schimmel <idosch@nvidia.com> wrote:

> Since the function is only invoked by IPv6 code there is no reason to
> put it inside the guard and it would also suppress the comment from
> Sashiko. You can place it near ip6_ins_rt().

> Please add a note that this attribute is rejected by the kernel in
> requests, like you did in patch #9.
>

Will fix them in patchset v6.

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2026-08-08  0:37 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-04  1:47 [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
2026-08-04  1:47 ` [PATCH net-next v5 01/10] ipv6: add ip6_del_rt_reason() Yuyang Huang
2026-08-07 16:21   ` Ido Schimmel
2026-08-08  0:37     ` Yuyang Huang
2026-08-04  1:47 ` [PATCH net-next v5 02/10] ipv6: propagate the route deletion reason to fib6_del_route() Yuyang Huang
2026-08-07 16:22   ` Ido Schimmel
2026-08-04  1:47 ` [PATCH net-next v5 03/10] ipv6: record the reason for kernel-initiated route deletions Yuyang Huang
2026-08-07 16:22   ` Ido Schimmel
2026-08-04  1:47 ` [PATCH net-next v5 04/10] ipv6: add a deletion reason argument to rt6_fill_node() Yuyang Huang
2026-08-07 16:22   ` Ido Schimmel
2026-08-04  1:47 ` [PATCH net-next v5 05/10] ipv6: expose the route deletion reason in RTM_DELROUTE Yuyang Huang
2026-08-07 16:23   ` Ido Schimmel
2026-08-04  1:47 ` [PATCH net-next v5 06/10] ipv6: add inet6_rt_del_notify() Yuyang Huang
2026-08-07 16:23   ` Ido Schimmel
2026-08-04  1:47 ` [PATCH net-next v5 07/10] netlink: specs: rt-route: add route notifications Yuyang Huang
2026-08-04  1:47 ` [PATCH net-next v5 08/10] netlink: specs: rt-route: split out the request attribute list Yuyang Huang
2026-08-04  1:47 ` [PATCH net-next v5 09/10] netlink: specs: rt-route: add the route deletion reason Yuyang Huang
2026-08-04  1:47 ` [PATCH net-next v5 10/10] selftests: net: verify RTA_DEL_REASON on route deletion Yuyang Huang
2026-08-05 10:41 ` [PATCH net-next v5 00/10] ipv6: report why a route was deleted in RTM_DELROUTE Yuyang Huang
2026-08-07 10:11   ` Yuyang Huang
2026-08-07 15:52     ` Ido Schimmel
2026-08-08  0:36       ` Yuyang Huang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox