AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/30 18:59 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "IPV6",
    "IPV6_ROUTER_PREF",
    "IPV6_ROUTE_INFO"
  ],
  "FocusSymbols": [
    "addrconf_prefix_rcv",
    "rt6_route_rcv",
    "ip6_del_rt_reason",
    "rt6_dump_route",
    "inet6_rt_del_notify",
    "fib6_del",
    "fib6_run_gc",
    "ndisc_rcv"
  ],
  "Reasoning": "The patch introduces a new netlink attribute `RTA_DEL_REASON` for IPv6 routes to report the reason for route deletion (e.g., expired, RA withdrawn). It modifies core IPv6 routing functions, garbage collection, and Router Advertisement (RA) processing logic to plumb this new attribute through the route deletion paths and netlink notifications. This is a functional change in the networking subsystem that should be fuzzed to ensure correct handling of the new attribute and no regressions in route deletion.",
  "WorthFuzzing": true
}

1/1 2026/07/30 18:59 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit fb9ae7006c9970f124bd91d02f2be7adcdbd4be4\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Jul 30 18:59:56 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/netlink/specs/rt-route.yaml b/Documentation/netlink/specs/rt-route.yaml\nindex 33195db96746c..e37a1301ef1db 100644\n--- a/Documentation/netlink/specs/rt-route.yaml\n+++ b/Documentation/netlink/specs/rt-route.yaml\n@@ -78,6 +78,15 @@ definitions:\n       -\n         name: rta-used\n         type: u32\n+  -\n+    name: del-reason\n+    type: enum\n+    name-prefix: rta-del-reason-\n+    enum-name: rta-del-reason\n+    entries:\n+      - unspec\n+      - expired\n+      - ra-withdrawn\n \n attribute-sets:\n   -\n@@ -185,6 +194,10 @@ attribute-sets:\n         type: u32\n         byte-order: big-endian\n         display-hint: hex\n+      -\n+        name: del-reason\n+        type: u32\n+        enum: del-reason\n   -\n     name: metrics\n     name-prefix: rtax-\n@@ -299,6 +312,7 @@ operations:\n             - dport\n             - nh-id\n             - flowlabel\n+            - del-reason\n       dump:\n         request:\n           value: 26\n@@ -313,7 +327,38 @@ operations:\n       do:\n         request:\n           value: 24\n-          attributes: *all-route-attrs\n+          # del-reason is notification-only. It is above\n+          # strict_start_type in rtm_ipv6_policy, so the kernel rejects\n+          # it in requests.\n+          attributes: \u0026route-req-attrs\n+            - dst\n+            - src\n+            - iif\n+            - oif\n+            - gateway\n+            - priority\n+            - prefsrc\n+            - metrics\n+            - multipath\n+            - flow\n+            - cacheinfo\n+            - table\n+            - mark\n+            - mfc-stats\n+            - via\n+            - newdst\n+            - pref\n+            - encap-type\n+            - encap\n+            - expires\n+            - pad\n+            - uid\n+            - ttl-propagate\n+            - ip-proto\n+            - sport\n+            - dport\n+            - nh-id\n+            - flowlabel\n     -\n       name: delroute\n       doc: Delete an existing route\n@@ -321,4 +366,23 @@ operations:\n       do:\n         request:\n           value: 25\n-          attributes: *all-route-attrs\n+          attributes: *route-req-attrs\n+    -\n+      name: newroute-ntf\n+      doc: Notification about a created route.\n+      value: 24\n+      notify: getroute\n+    -\n+      name: delroute-ntf\n+      doc: Notification about a deleted route.\n+      value: 25\n+      notify: getroute\n+\n+mcast-groups:\n+  list:\n+    -\n+      name: rtnlgrp-ipv4-route\n+      value: 7\n+    -\n+      name: rtnlgrp-ipv6-route\n+      value: 11\ndiff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h\nindex 9cd27e1b9b692..c6dc23a42c118 100644\n--- a/include/net/ip6_fib.h\n+++ b/include/net/ip6_fib.h\n@@ -468,7 +468,8 @@ void fib6_clean_all_skip_notify(struct net *net,\n \n int fib6_add(struct fib6_node *root, struct fib6_info *rt,\n \t     struct nl_info *info, struct netlink_ext_ack *extack);\n-int fib6_del(struct fib6_info *rt, struct nl_info *info);\n+int fib6_del(struct fib6_info *rt, struct nl_info *info,\n+\t     enum rta_del_reason del_reason);\n \n static inline\n void rt6_get_prefsrc(const struct rt6_info *rt, struct in6_addr *addr)\n@@ -532,6 +533,8 @@ static inline void fib6_rt_update(struct net *net, struct fib6_info *rt,\n #endif\n void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,\n \t\t     unsigned int flags);\n+void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info,\n+\t\t\t enum rta_del_reason del_reason);\n \n void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args,\n \t\t\t unsigned long now);\ndiff --git a/include/net/ip6_route.h b/include/net/ip6_route.h\nindex 09ffe0f13ce75..92ad5a0d03a25 100644\n--- a/include/net/ip6_route.h\n+++ b/include/net/ip6_route.h\n@@ -128,12 +128,21 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags,\n int ip6_ins_rt(struct net *net, struct fib6_info *f6i);\n #if IS_ENABLED(CONFIG_IPV6)\n int ip6_del_rt(struct net *net, struct fib6_info *f6i, bool skip_notify);\n+int ip6_del_rt_reason(struct net *net, struct fib6_info *f6i, bool skip_notify,\n+\t\t      enum rta_del_reason del_reason);\n #else\n static inline int ip6_del_rt(struct net *net, struct fib6_info *f6i,\n \t\t\t     bool skip_notify)\n {\n \treturn -EAFNOSUPPORT;\n }\n+\n+static inline int ip6_del_rt_reason(struct net *net, struct fib6_info *f6i,\n+\t\t\t\t    bool skip_notify,\n+\t\t\t\t    enum rta_del_reason del_reason)\n+{\n+\treturn -EAFNOSUPPORT;\n+}\n #endif\n \n void rt6_flush_exceptions(struct fib6_info *f6i);\ndiff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h\nindex 27265fd31e5fc..fe00e624f3c3a 100644\n--- a/include/uapi/linux/rtnetlink.h\n+++ b/include/uapi/linux/rtnetlink.h\n@@ -399,6 +399,7 @@ enum rtattr_type_t {\n \tRTA_DPORT,\n \tRTA_NH_ID,\n \tRTA_FLOWLABEL,\n+\tRTA_DEL_REASON,\n \t__RTA_MAX\n };\n \n@@ -407,6 +408,22 @@ enum rtattr_type_t {\n #define RTM_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))))\n #define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg))\n \n+/* RTA_DEL_REASON: why the kernel deleted the route. u32.\n+ * Emitted only on RTM_DELROUTE notifications, and only when the deletion\n+ * path records a cause. Absence means either an older kernel or a\n+ * deletion path that does not (yet) record its cause - consumers must\n+ * treat \"absent\" and \"unspec\" identically. New causes may be appended.\n+ * Currently only IPv6 deletion paths record a cause.\n+ */\n+enum rta_del_reason {\n+\tRTA_DEL_REASON_UNSPEC,\t\t/* cause not recorded */\n+\tRTA_DEL_REASON_EXPIRED,\t\t/* RTF_EXPIRES lifetime ran out (GC) */\n+\tRTA_DEL_REASON_RA_WITHDRAWN,\t/* zero-lifetime RA / PIO / RIO */\n+\t__RTA_DEL_REASON_MAX\n+};\n+\n+#define RTA_DEL_REASON_MAX (__RTA_DEL_REASON_MAX - 1)\n+\n /* RTM_MULTIPATH --- array of struct rtnexthop.\n  *\n  * \"struct rtnexthop\" describes all necessary nexthop information,\ndiff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c\nindex f6fa2715b450b..0c3d38f69fc43 100644\n--- a/net/ipv6/addrconf.c\n+++ b/net/ipv6/addrconf.c\n@@ -2874,7 +2874,8 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)\n \t\tif (rt) {\n \t\t\t/* Autoconf prefix route */\n \t\t\tif (valid_lft == 0) {\n-\t\t\t\tip6_del_rt(net, rt, false);\n+\t\t\t\tip6_del_rt_reason(net, rt, false,\n+\t\t\t\t\t\t  RTA_DEL_REASON_RA_WITHDRAWN);\n \t\t\t\trt = NULL;\n \t\t\t} else {\n \t\t\t\ttable = rt-\u003efib6_table;\ndiff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c\nindex a130cdfaebfbe..159db10a846cc 100644\n--- a/net/ipv6/ip6_fib.c\n+++ b/net/ipv6/ip6_fib.c\n@@ -1966,7 +1966,8 @@ static struct fib6_node *fib6_repair_tree(struct net *net,\n }\n \n static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,\n-\t\t\t   struct fib6_info __rcu **rtp, struct nl_info *info)\n+\t\t\t   struct fib6_info __rcu **rtp, struct nl_info *info,\n+\t\t\t   enum rta_del_reason del_reason)\n {\n \tstruct fib6_info *leaf, *replace_rt = NULL;\n \tstruct fib6_walker *w;\n@@ -2055,13 +2056,14 @@ static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,\n \t\t\tcall_fib6_entry_notifiers_replace(net, replace_rt);\n \t}\n \tif (!info-\u003eskip_notify)\n-\t\tinet6_rt_notify(RTM_DELROUTE, rt, info, 0);\n+\t\tinet6_rt_del_notify(rt, info, del_reason);\n \n \tfib6_info_release(rt);\n }\n \n /* Need to own table-\u003etb6_lock */\n-int fib6_del(struct fib6_info *rt, struct nl_info *info)\n+int fib6_del(struct fib6_info *rt, struct nl_info *info,\n+\t     enum rta_del_reason del_reason)\n {\n \tstruct net *net = info-\u003enl_net;\n \tstruct fib6_info __rcu **rtp;\n@@ -2090,7 +2092,7 @@ int fib6_del(struct fib6_info *rt, struct nl_info *info)\n \t\tif (rt == cur) {\n \t\t\tif (fib6_requires_src(cur))\n \t\t\t\tfib6_routes_require_src_dec(info-\u003enl_net);\n-\t\t\tfib6_del_route(table, fn, rtp, info);\n+\t\t\tfib6_del_route(table, fn, rtp, info, del_reason);\n \t\t\treturn 0;\n \t\t}\n \t\trtp_next = \u0026cur-\u003efib6_next;\n@@ -2252,7 +2254,7 @@ static int fib6_clean_node(struct fib6_walker *w)\n \t\tres = c-\u003efunc(rt, c-\u003earg);\n \t\tif (res == -1) {\n \t\t\tw-\u003eleaf = rt;\n-\t\t\tres = fib6_del(rt, \u0026info);\n+\t\t\tres = fib6_del(rt, \u0026info, RTA_DEL_REASON_UNSPEC);\n \t\t\tif (res) {\n #if RT6_DEBUG \u003e= 2\n \t\t\t\tpr_debug(\"%s: del failed: rt=%p@%p err=%d\\n\",\n@@ -2400,7 +2402,7 @@ static void fib6_gc_table(struct net *net,\n \n \thlist_for_each_entry_safe(rt, n, \u0026tb6-\u003etb6_gc_hlist, gc_link)\n \t\tif (fib6_age(rt, gc_args) == -1)\n-\t\t\tfib6_del(rt, \u0026info);\n+\t\t\tfib6_del(rt, \u0026info, RTA_DEL_REASON_EXPIRED);\n }\n \n static void fib6_gc_all(struct net *net, struct fib6_gc_args *gc_args)\ndiff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c\nindex f867ec8d3d905..713db9089d5e1 100644\n--- a/net/ipv6/ndisc.c\n+++ b/net/ipv6/ndisc.c\n@@ -1361,7 +1361,9 @@ static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)\n \tdefrtr_usr_metric = in6_dev-\u003ecnf.ra_defrtr_metric;\n \t/* delete the route if lifetime is 0 or if metric needs change */\n \tif (rt \u0026\u0026 (lifetime == 0 || rt-\u003efib6_metric != defrtr_usr_metric)) {\n-\t\tip6_del_rt(net, rt, false);\n+\t\tip6_del_rt_reason(net, rt, false,\n+\t\t\t\t  lifetime == 0 ? RTA_DEL_REASON_RA_WITHDRAWN :\n+\t\t\t\t\t\t  RTA_DEL_REASON_UNSPEC);\n \t\trt = NULL;\n \t}\n \ndiff --git a/net/ipv6/route.c b/net/ipv6/route.c\nindex fc42d67e58226..94f9646d5ebc3 100644\n--- a/net/ipv6/route.c\n+++ b/net/ipv6/route.c\n@@ -111,7 +111,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,\n \t\t\t struct fib6_info *rt, struct dst_entry *dst,\n \t\t\t struct in6_addr *dest, struct in6_addr *src,\n \t\t\t int iif, int type, u32 portid, u32 seq,\n-\t\t\t unsigned int flags);\n+\t\t\t unsigned int flags, enum rta_del_reason del_reason);\n static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res,\n \t\t\t\t\t   const struct in6_addr *daddr,\n \t\t\t\t\t   const struct in6_addr *saddr);\n@@ -1020,7 +1020,7 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,\n \t\t\t\t\tgwaddr, dev);\n \n \tif (rt \u0026\u0026 !lifetime) {\n-\t\tip6_del_rt(net, rt, false);\n+\t\tip6_del_rt_reason(net, rt, false, RTA_DEL_REASON_RA_WITHDRAWN);\n \t\trt = NULL;\n \t}\n \n@@ -3973,7 +3973,8 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags,\n \treturn err;\n }\n \n-static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info)\n+static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info,\n+\t\t\tenum rta_del_reason del_reason)\n {\n \tstruct net *net = info-\u003enl_net;\n \tstruct fib6_table *table;\n@@ -3986,7 +3987,7 @@ static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info)\n \n \ttable = rt-\u003efib6_table;\n \tspin_lock_bh(\u0026table-\u003etb6_lock);\n-\terr = fib6_del(rt, info);\n+\terr = fib6_del(rt, info, del_reason);\n \tspin_unlock_bh(\u0026table-\u003etb6_lock);\n \n out:\n@@ -3994,14 +3995,20 @@ static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info)\n \treturn err;\n }\n \n-int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify)\n+int ip6_del_rt_reason(struct net *net, struct fib6_info *rt, bool skip_notify,\n+\t\t      enum rta_del_reason del_reason)\n {\n \tstruct nl_info info = {\n \t\t.nl_net = net,\n \t\t.skip_notify = skip_notify\n \t};\n \n-\treturn __ip6_del_rt(rt, \u0026info);\n+\treturn __ip6_del_rt(rt, \u0026info, del_reason);\n+}\n+\n+int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify)\n+{\n+\treturn ip6_del_rt_reason(net, rt, skip_notify, RTA_DEL_REASON_UNSPEC);\n }\n \n static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)\n@@ -4028,7 +4035,8 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)\n \n \t\t\tif (rt6_fill_node(net, skb, rt, NULL,\n \t\t\t\t\t  NULL, NULL, 0, RTM_DELROUTE,\n-\t\t\t\t\t  info-\u003eportid, seq, 0) \u003c 0) {\n+\t\t\t\t\t  info-\u003eportid, seq, 0,\n+\t\t\t\t\t  RTA_DEL_REASON_UNSPEC) \u003c 0) {\n \t\t\t\tkfree_skb(skb);\n \t\t\t\tskb = NULL;\n \t\t\t} else\n@@ -4064,13 +4072,13 @@ static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)\n \t\tlist_for_each_entry_safe(sibling, next_sibling,\n \t\t\t\t\t \u0026rt-\u003efib6_siblings,\n \t\t\t\t\t fib6_siblings) {\n-\t\t\terr = fib6_del(sibling, info);\n+\t\t\terr = fib6_del(sibling, info, RTA_DEL_REASON_UNSPEC);\n \t\t\tif (err)\n \t\t\t\tgoto out_unlock;\n \t\t}\n \t}\n \n-\terr = fib6_del(rt, info);\n+\terr = fib6_del(rt, info, RTA_DEL_REASON_UNSPEC);\n out_unlock:\n \tspin_unlock_bh(\u0026table-\u003etb6_lock);\n out_put:\n@@ -4196,7 +4204,8 @@ static int ip6_route_del(struct fib6_config *cfg,\n \t\t\t\tif (!fib6_info_hold_safe(rt))\n \t\t\t\t\tcontinue;\n \n-\t\t\t\terr =  __ip6_del_rt(rt, \u0026cfg-\u003efc_nlinfo);\n+\t\t\t\terr = __ip6_del_rt(rt, \u0026cfg-\u003efc_nlinfo,\n+\t\t\t\t\t\t   RTA_DEL_REASON_UNSPEC);\n \t\t\t\tbreak;\n \t\t\t}\n \t\t\tif (cfg-\u003efc_nh_id)\n@@ -4215,7 +4224,8 @@ static int ip6_route_del(struct fib6_config *cfg,\n \n \t\t\t/* if gateway was specified only delete the one hop */\n \t\t\tif (cfg-\u003efc_flags \u0026 RTF_GATEWAY)\n-\t\t\t\terr = __ip6_del_rt(rt, \u0026cfg-\u003efc_nlinfo);\n+\t\t\t\terr = __ip6_del_rt(rt, \u0026cfg-\u003efc_nlinfo,\n+\t\t\t\t\t\t   RTA_DEL_REASON_UNSPEC);\n \t\t\telse\n \t\t\t\terr = __ip6_del_rt_siblings(rt, cfg);\n \t\t\tbreak;\n@@ -5739,6 +5749,7 @@ static size_t rt6_nlmsg_size(struct fib6_info *f6i)\n \t       + nla_total_size(sizeof(struct rta_cacheinfo))\n \t       + nla_total_size(TCP_CA_NAME_MAX) /* RTAX_CC_ALGO */\n \t       + nla_total_size(1) /* RTA_PREF */\n+\t       + nla_total_size(4) /* RTA_DEL_REASON */\n \t       + nexthop_len;\n }\n \n@@ -5775,7 +5786,7 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,\n \t\t\t struct fib6_info *rt, struct dst_entry *dst,\n \t\t\t struct in6_addr *dest, struct in6_addr *src,\n \t\t\t int iif, int type, u32 portid, u32 seq,\n-\t\t\t unsigned int flags)\n+\t\t\t unsigned int flags, enum rta_del_reason del_reason)\n {\n \tstruct rt6_info *rt6 = dst_rt6_info(dst);\n \tstruct rt6key *rt6_dst, *rt6_src;\n@@ -5954,6 +5965,10 @@ static int rt6_fill_node(struct net *net, struct sk_buff *skb,\n \tif (rtnl_put_cacheinfo(skb, dst, 0, expires, dst ? dst-\u003eerror : 0) \u003c 0)\n \t\tgoto nla_put_failure;\n \n+\tif (del_reason != RTA_DEL_REASON_UNSPEC \u0026\u0026\n+\t    nla_put_u32(skb, RTA_DEL_REASON, del_reason))\n+\t\tgoto nla_put_failure;\n+\n \tif (nla_put_u8(skb, RTA_PREF, IPV6_EXTRACT_PREF(rt6_flags)))\n \t\tgoto nla_put_failure;\n \n@@ -6055,7 +6070,8 @@ static int rt6_nh_dump_exceptions(struct fib6_nh *nh, void *arg)\n \t\t\t\t\t    \u0026rt6_ex-\u003ert6i-\u003edst, NULL, NULL, 0,\n \t\t\t\t\t    RTM_NEWROUTE,\n \t\t\t\t\t    NETLINK_CB(dump-\u003ecb-\u003eskb).portid,\n-\t\t\t\t\t    dump-\u003ecb-\u003enlh-\u003enlmsg_seq, w-\u003eflags);\n+\t\t\t\t\t    dump-\u003ecb-\u003enlh-\u003enlmsg_seq, w-\u003eflags,\n+\t\t\t\t\t    RTA_DEL_REASON_UNSPEC);\n \t\t\tif (err)\n \t\t\t\treturn err;\n \n@@ -6103,7 +6119,8 @@ int rt6_dump_route(struct fib6_info *rt, void *p_arg, unsigned int skip)\n \t\t\tif (rt6_fill_node(net, arg-\u003eskb, rt, NULL, NULL, NULL,\n \t\t\t\t\t  0, RTM_NEWROUTE,\n \t\t\t\t\t  NETLINK_CB(arg-\u003ecb-\u003eskb).portid,\n-\t\t\t\t\t  arg-\u003ecb-\u003enlh-\u003enlmsg_seq, flags)) {\n+\t\t\t\t\t  arg-\u003ecb-\u003enlh-\u003enlmsg_seq, flags,\n+\t\t\t\t\t  RTA_DEL_REASON_UNSPEC)) {\n \t\t\t\treturn 0;\n \t\t\t}\n \t\t\tcount++;\n@@ -6336,12 +6353,14 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,\n \t\t\terr = rt6_fill_node(net, skb, from, NULL, NULL, NULL,\n \t\t\t\t\t    iif, RTM_NEWROUTE,\n \t\t\t\t\t    NETLINK_CB(in_skb).portid,\n-\t\t\t\t\t    nlh-\u003enlmsg_seq, 0);\n+\t\t\t\t\t    nlh-\u003enlmsg_seq, 0,\n+\t\t\t\t\t    RTA_DEL_REASON_UNSPEC);\n \t\telse\n \t\t\terr = rt6_fill_node(net, skb, from, dst, \u0026fl6.daddr,\n \t\t\t\t\t    \u0026fl6.saddr, iif, RTM_NEWROUTE,\n \t\t\t\t\t    NETLINK_CB(in_skb).portid,\n-\t\t\t\t\t    nlh-\u003enlmsg_seq, 0);\n+\t\t\t\t\t    nlh-\u003enlmsg_seq, 0,\n+\t\t\t\t\t    RTA_DEL_REASON_UNSPEC);\n \t} else {\n \t\terr = -ENETUNREACH;\n \t}\n@@ -6357,8 +6376,9 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,\n \treturn err;\n }\n \n-void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,\n-\t\t     unsigned int nlm_flags)\n+static void __inet6_rt_notify(int event, struct fib6_info *rt,\n+\t\t\t      struct nl_info *info, unsigned int nlm_flags,\n+\t\t\t      enum rta_del_reason del_reason)\n {\n \tstruct net *net = info-\u003enl_net;\n \tstruct sk_buff *skb;\n@@ -6377,7 +6397,7 @@ void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,\n \t\tgoto errout;\n \n \terr = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0,\n-\t\t\t    event, info-\u003eportid, seq, nlm_flags);\n+\t\t\t    event, info-\u003eportid, seq, nlm_flags, del_reason);\n \tif (err \u003c 0) {\n \t\tkfree_skb(skb);\n \t\t/* -EMSGSIZE implies needed space grew under us. */\n@@ -6398,6 +6418,18 @@ void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,\n \trtnl_set_sk_err(net, RTNLGRP_IPV6_ROUTE, err);\n }\n \n+void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,\n+\t\t     unsigned int nlm_flags)\n+{\n+\t__inet6_rt_notify(event, rt, info, nlm_flags, RTA_DEL_REASON_UNSPEC);\n+}\n+\n+void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info,\n+\t\t\t enum rta_del_reason del_reason)\n+{\n+\t__inet6_rt_notify(RTM_DELROUTE, rt, info, 0, del_reason);\n+}\n+\n void fib6_rt_update(struct net *net, struct fib6_info *rt,\n \t\t    struct nl_info *info)\n {\n@@ -6410,7 +6442,8 @@ void fib6_rt_update(struct net *net, struct fib6_info *rt,\n \t\tgoto errout;\n \n \terr = rt6_fill_node(net, skb, rt, NULL, NULL, NULL, 0,\n-\t\t\t    RTM_NEWROUTE, info-\u003eportid, seq, NLM_F_REPLACE);\n+\t\t\t    RTM_NEWROUTE, info-\u003eportid, seq, NLM_F_REPLACE,\n+\t\t\t    RTA_DEL_REASON_UNSPEC);\n \tif (err \u003c 0) {\n \t\t/* -EMSGSIZE implies BUG in rt6_nlmsg_size() */\n \t\tWARN_ON(err == -EMSGSIZE);\n@@ -6463,7 +6496,7 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i,\n \t}\n \n \terr = rt6_fill_node(net, skb, f6i, NULL, NULL, NULL, 0, RTM_NEWROUTE, 0,\n-\t\t\t    0, 0);\n+\t\t\t    0, 0, RTA_DEL_REASON_UNSPEC);\n \tif (err \u003c 0) {\n \t\t/* -EMSGSIZE implies BUG in rt6_nlmsg_size() */\n \t\tWARN_ON(err == -EMSGSIZE);\ndiff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py\nindex e58bdbdc58eea..34935886b6adf 100644\n--- a/tools/testing/selftests/net/lib/py/__init__.py\n+++ b/tools/testing/selftests/net/lib/py/__init__.py\n@@ -17,7 +17,7 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \\\n     wait_file, tool, tc\n from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids\n from .ynl import NlError, NlctrlFamily, YnlFamily, \\\n-    EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily\n+    EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily, RtnlRouteFamily\n from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink\n \n __all__ = [\"KSRC\",\n@@ -34,4 +34,4 @@ __all__ = [\"KSRC\",\n            \"NetdevSim\", \"NetdevSimDev\",\n            \"NetshaperFamily\", \"DevlinkFamily\", \"PSPFamily\", \"NlError\",\n            \"YnlFamily\", \"EthtoolFamily\", \"NetdevFamily\", \"RtnlFamily\",\n-           \"NlctrlFamily\", \"RtnlAddrFamily\", \"Netlink\"]\n+           \"NlctrlFamily\", \"RtnlAddrFamily\", \"RtnlRouteFamily\", \"Netlink\"]\ndiff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py\nindex 2e567062aa6cc..08deff756f29b 100644\n--- a/tools/testing/selftests/net/lib/py/ynl.py\n+++ b/tools/testing/selftests/net/lib/py/ynl.py\n@@ -29,7 +29,7 @@ except ModuleNotFoundError as e:\n \n __all__ = [\n     \"NlError\", \"NlPolicy\", \"Netlink\", \"YnlFamily\", \"SPEC_PATH\",\n-    \"EthtoolFamily\", \"RtnlFamily\", \"RtnlAddrFamily\",\n+    \"EthtoolFamily\", \"RtnlFamily\", \"RtnlAddrFamily\", \"RtnlRouteFamily\",\n     \"NetdevFamily\", \"NetshaperFamily\", \"NlctrlFamily\", \"DevlinkFamily\",\n     \"PSPFamily\",\n ]\n@@ -54,6 +54,11 @@ class RtnlAddrFamily(YnlFamily):\n         super().__init__((SPEC_PATH / Path('rt-addr.yaml')).as_posix(),\n                          schema='', recv_size=recv_size)\n \n+class RtnlRouteFamily(YnlFamily):\n+    def __init__(self, recv_size=0):\n+        super().__init__((SPEC_PATH / Path('rt-route.yaml')).as_posix(),\n+                         schema='', recv_size=recv_size)\n+\n class NetdevFamily(YnlFamily):\n     def __init__(self, recv_size=0):\n         super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(),\ndiff --git a/tools/testing/selftests/net/rtnetlink.py b/tools/testing/selftests/net/rtnetlink.py\nindex 0c67c7c00d841..f74ccd5c2d13a 100755\n--- a/tools/testing/selftests/net/rtnetlink.py\n+++ b/tools/testing/selftests/net/rtnetlink.py\n@@ -5,7 +5,8 @@ import socket\n import struct\n import time\n from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_eq, ksft_ge, ksft_true, KsftSkipEx\n-from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily\n+from lib.py import ksft_not_in, ksft_not_none\n+from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily, RtnlRouteFamily\n \n IPV4_ALL_HOSTS_MULTICAST = b'\\xe0\\x00\\x00\\x01'\n IPV4_TEST_MULTICAST = b'\\xef\\x01\\x01\\x01'\n@@ -134,8 +135,184 @@ def ipv4_devconf_notify() -\u003e None:\n     ksft_true(f\"inet {ifname} forwarding on\" in cmd_obj.stdout,\n               f\"No 'forwarding on' notificiation found for interface {ifname}\")\n \n+def _rtnl_route_subscribe(ns):\n+    with NetNSEnter(str(ns)):\n+        rtnl = RtnlRouteFamily()\n+    rtnl.ntf_subscribe(\"rtnlgrp-ipv6-route\")\n+    return rtnl\n+\n+\n+def _wait_route_ntf(rtnl, name, dst_len, dst=None, deadline=10):\n+    \"\"\"Return the attrs of the first matching notification, None on timeout.\"\"\"\n+\n+    for msg in rtnl.poll_ntf(duration=deadline):\n+        if msg['name'] != name:\n+            continue\n+        attrs = msg['msg']\n+        if attrs['rtm-dst-len'] != dst_len:\n+            continue\n+        if dst is not None and attrs.get('dst') != dst:\n+            continue\n+        return attrs\n+    return None\n+\n+\n+def _collect_route_ntfs(rtnl, name, want, deadline=10):\n+    \"\"\"Gather attrs of matching notifications, keyed by (dst_len, dst).\"\"\"\n+\n+    seen = {}\n+    for msg in rtnl.poll_ntf(duration=deadline):\n+        if msg['name'] != name:\n+            continue\n+        attrs = msg['msg']\n+        key = (attrs['rtm-dst-len'], attrs.get('dst'))\n+        if key in want:\n+            seen[key] = attrs\n+            if len(seen) == len(want):\n+                break\n+    return seen\n+\n+\n+def _write_ipv6_sysctl(name, value):\n+    with open(f\"/proc/sys/net/ipv6/{name}\", \"w\") as f:\n+        f.write(f\"{value}\\n\")\n+\n+\n+def ipv6_route_del_reason_expired() -\u003e None:\n+    \"\"\"An expired route reports RTA_DEL_REASON == expired.\"\"\"\n+\n+    with NetNS() as ns:\n+        rtnl = _rtnl_route_subscribe(ns)\n+        with NetNSEnter(str(ns)):\n+            _write_ipv6_sysctl(\"route/gc_interval\", 2)\n+        ip(\"link add name dummy1 type dummy\", ns=str(ns))\n+        ip(\"link set dev dummy1 up\", ns=str(ns))\n+        ip(\"-6 route add 2001:db8:2::/64 dev dummy1 expires 2\", ns=str(ns))\n+\n+        attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:2::',\n+                                deadline=15)\n+        ksft_not_none(attrs, \"no RTM_DELROUTE for the expired route\")\n+        if attrs is not None:\n+            ksft_eq(attrs.get('del-reason'), 'expired')\n+\n+\n+def _send_ra(sock, ifindex, lifetime, rio=None, pio=None):\n+    \"\"\"The kernel fills in the ICMPv6 checksum on raw ICMPv6 sockets.\"\"\"\n+\n+    # type, code, cksum, hop limit, flags, router lifetime,\n+    # reachable time, retrans timer\n+    ra = struct.pack('!BBHBBHII', 134, 0, 0, 64, 0, lifetime, 0, 0)\n+    if rio is not None:\n+        prefix, plen, rio_lifetime = rio\n+        # RFC 4191 route information option, /64 prefix (8 bytes)\n+        ra += struct.pack('!BBBBI', 24, 2, plen, 0, rio_lifetime)\n+        ra += socket.inet_pton(socket.AF_INET6, prefix)[:8]\n+    if pio is not None:\n+        prefix, plen, valid_lft = pio\n+        # RFC 4861 prefix information option, on-link only (L set, A clear)\n+        ra += struct.pack('!BBBBIII', 3, 4, plen, 0x80, valid_lft, 0, 0)\n+        ra += socket.inet_pton(socket.AF_INET6, prefix)\n+    sock.sendto(ra, ('ff02::1', 0, 0, ifindex))\n+\n+\n+def _ra_router_sock(ns_r, ifname):\n+    with NetNSEnter(str(ns_r)):\n+        sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW,\n+                             socket.IPPROTO_ICMPV6)\n+        sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 255)\n+        return sock, socket.if_nametoindex(ifname)\n+\n+\n+def _ra_advertise_routes(rtnl, sock, ifindex, want, **ra_opts):\n+    \"\"\"\n+    Sending fails with EADDRNOTAVAIL until the router's link-local\n+    address passes DAD, so retry.\n+    \"\"\"\n+\n+    seen = {}\n+    for _ in range(10):\n+        try:\n+            _send_ra(sock, ifindex, **ra_opts)\n+        except OSError:\n+            time.sleep(0.2)\n+            continue\n+        seen.update(_collect_route_ntfs(rtnl, 'newroute-ntf',\n+                                        want - set(seen.keys()), deadline=2))\n+        if len(seen) == len(want):\n+            break\n+    return seen\n+\n+\n+def ipv6_route_del_reason_ra_withdrawn() -\u003e None:\n+    \"\"\"\n+    Routes withdrawn by a zero-lifetime RA (router lifetime, RFC 4861\n+    PIO, RFC 4191 RIO) report RTA_DEL_REASON == ra-withdrawn.\n+    \"\"\"\n+\n+    # (rtm-dst-len, dst); the default route carries no RTA_DST\n+    routes = {(0, None), (64, '2001:db8:6::'), (64, '2001:db8:5::')}\n+\n+    with NetNS() as ns_h, NetNS() as ns_r:\n+        ip(f\"link add veth0 netns {ns_h} type veth peer name veth1 netns {ns_r}\")\n+        with NetNSEnter(str(ns_h)):\n+            _write_ipv6_sysctl(\"conf/veth0/accept_ra\", 2)\n+            _write_ipv6_sysctl(\"conf/veth0/forwarding\", 0)\n+            try:\n+                _write_ipv6_sysctl(\"conf/veth0/accept_ra_rt_info_max_plen\", 64)\n+            except FileNotFoundError:\n+                raise KsftSkipEx(\"no CONFIG_IPV6_ROUTE_INFO\")\n+        with NetNSEnter(str(ns_r)):\n+            # skip DAD so the router's link-local source is usable right away\n+            _write_ipv6_sysctl(\"conf/veth1/accept_dad\", 0)\n+        ip(\"link set dev veth0 up\", ns=str(ns_h))\n+        ip(\"link set dev veth1 up\", ns=str(ns_r))\n+\n+        rtnl = _rtnl_route_subscribe(ns_h)\n+        sock, ifindex = _ra_router_sock(ns_r, \"veth1\")\n+\n+        seen = _ra_advertise_routes(rtnl, sock, ifindex, routes,\n+                                    lifetime=1800,\n+                                    rio=('2001:db8:5::', 64, 600),\n+                                    pio=('2001:db8:6::', 64, 600))\n+        ksft_eq(set(seen), routes, \"not all RA routes were installed\")\n+        if set(seen) != routes:\n+            return\n+\n+        _send_ra(sock, ifindex, 0, rio=('2001:db8:5::', 64, 0),\n+                 pio=('2001:db8:6::', 64, 0))\n+        seen = _collect_route_ntfs(rtnl, 'delroute-ntf', routes)\n+        for key in routes:\n+            attrs = seen.get(key)\n+            ksft_not_none(attrs, f\"no RTM_DELROUTE for {key}\")\n+            if attrs is not None:\n+                ksft_eq(attrs.get('del-reason'), 'ra-withdrawn')\n+\n+\n+def ipv6_route_del_reason_absent() -\u003e None:\n+    \"\"\"\n+    A deletion path that records no cause (here a userspace request)\n+    must not carry RTA_DEL_REASON at all.\n+    \"\"\"\n+\n+    with NetNS() as ns:\n+        rtnl = _rtnl_route_subscribe(ns)\n+        ip(\"link add name dummy1 type dummy\", ns=str(ns))\n+        ip(\"link set dev dummy1 up\", ns=str(ns))\n+        ip(\"-6 route add 2001:db8:1::/64 dev dummy1\", ns=str(ns))\n+        ip(\"-6 route del 2001:db8:1::/64 dev dummy1\", ns=str(ns))\n+\n+        attrs = _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:1::')\n+        ksft_not_none(attrs, \"no RTM_DELROUTE for 2001:db8:1::/64\")\n+        if attrs is not None:\n+            ksft_not_in('del-reason', attrs,\n+                        \"user deletion must not carry del-reason\")\n+\n+\n def main() -\u003e None:\n-    ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify])\n+    ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify,\n+              ipv6_route_del_reason_expired,\n+              ipv6_route_del_reason_ra_withdrawn,\n+              ipv6_route_del_reason_absent])\n     ksft_exit()\n \n if __name__ == \"__main__\":\n"
}

2/1 2026/07/30 18:59 agent patch-evaluator
1m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "IPV6",
    "IPV6_ROUTER_PREF",
    "IPV6_ROUTE_INFO"
  ],
  "FocusSymbols": [
    "addrconf_prefix_rcv",
    "rt6_route_rcv",
    "ip6_del_rt_reason",
    "rt6_dump_route",
    "inet6_rt_del_notify",
    "fib6_del",
    "fib6_run_gc",
    "ndisc_rcv"
  ],
  "Reasoning": "The patch introduces a new netlink attribute `RTA_DEL_REASON` for IPv6 routes to report the reason for route deletion (e.g., expired, RA withdrawn). It modifies core IPv6 routing functions, garbage collection, and Router Advertisement (RA) processing logic to plumb this new attribute through the route deletion paths and netlink notifications. This is a functional change in the networking subsystem that should be fuzzed to ensure correct handling of the new attribute and no regressions in route deletion.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit fb9ae7006c9970f124bd91d02f2be7adcdbd4be4
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Jul 30 18:59:56 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/netlink/specs/rt-route.yaml b/Documentation/netlink/specs/rt-route.yaml
index 33195db96746c..e37a1301ef1db 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: rta-del-reason-
+    enum-name: rta-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,7 +327,38 @@ operations:
       do:
         request:
           value: 24
-          attributes: *all-route-attrs
+          # 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
+            - 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,4 +366,23 @@ operations:
       do:
         request:
           value: 25
-          attributes: *all-route-attrs
+          attributes: *route-req-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
diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index 9cd27e1b9b692..c6dc23a42c118 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 rta_del_reason del_reason);
 
 static inline
 void rt6_get_prefsrc(const struct rt6_info *rt, struct in6_addr *addr)
@@ -532,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 rta_del_reason del_reason);
 
 void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args,
 			 unsigned long now);
diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h
index 09ffe0f13ce75..92ad5a0d03a25 100644
--- a/include/net/ip6_route.h
+++ b/include/net/ip6_route.h
@@ -128,12 +128,21 @@ 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, bool skip_notify,
+		      enum rta_del_reason del_reason);
 #else
 static inline int ip6_del_rt(struct net *net, struct fib6_info *f6i,
 			     bool skip_notify)
 {
 	return -EAFNOSUPPORT;
 }
+
+static inline int ip6_del_rt_reason(struct net *net, struct fib6_info *f6i,
+				    bool skip_notify,
+				    enum rta_del_reason del_reason)
+{
+	return -EAFNOSUPPORT;
+}
 #endif
 
 void rt6_flush_exceptions(struct fib6_info *f6i);
diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h
index 27265fd31e5fc..fe00e624f3c3a 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,22 @@ 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.
+ */
+enum rta_del_reason {
+	RTA_DEL_REASON_UNSPEC,		/* cause not recorded */
+	RTA_DEL_REASON_EXPIRED,		/* RTF_EXPIRES lifetime ran out (GC) */
+	RTA_DEL_REASON_RA_WITHDRAWN,	/* zero-lifetime RA / PIO / RIO */
+	__RTA_DEL_REASON_MAX
+};
+
+#define RTA_DEL_REASON_MAX (__RTA_DEL_REASON_MAX - 1)
+
 /* RTM_MULTIPATH --- array of struct rtnexthop.
  *
  * "struct rtnexthop" describes all necessary nexthop information,
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index f6fa2715b450b..0c3d38f69fc43 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, false,
+						  RTA_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 a130cdfaebfbe..159db10a846cc 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -1966,7 +1966,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 rta_del_reason del_reason)
 {
 	struct fib6_info *leaf, *replace_rt = NULL;
 	struct fib6_walker *w;
@@ -2055,13 +2056,14 @@ 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);
 }
 
 /* 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 rta_del_reason del_reason)
 {
 	struct net *net = info->nl_net;
 	struct fib6_info __rcu **rtp;
@@ -2090,7 +2092,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;
@@ -2252,7 +2254,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, RTA_DEL_REASON_UNSPEC);
 			if (res) {
 #if RT6_DEBUG >= 2
 				pr_debug("%s: del failed: rt=%p@%p err=%d\n",
@@ -2400,7 +2402,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, RTA_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 f867ec8d3d905..713db9089d5e1 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, false,
+				  lifetime == 0 ? RTA_DEL_REASON_RA_WITHDRAWN :
+						  RTA_DEL_REASON_UNSPEC);
 		rt = NULL;
 	}
 
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index fc42d67e58226..94f9646d5ebc3 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 rta_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);
@@ -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, false, RTA_DEL_REASON_RA_WITHDRAWN);
 		rt = NULL;
 	}
 
@@ -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 rta_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:
@@ -3994,14 +3995,20 @@ static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info)
 	return err;
 }
 
-int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify)
+int ip6_del_rt_reason(struct net *net, struct fib6_info *rt, bool skip_notify,
+		      enum rta_del_reason del_reason)
 {
 	struct nl_info info = {
 		.nl_net = net,
 		.skip_notify = skip_notify
 	};
 
-	return __ip6_del_rt(rt, &info);
+	return __ip6_del_rt(rt, &info, del_reason);
+}
+
+int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify)
+{
+	return ip6_del_rt_reason(net, rt, skip_notify, RTA_DEL_REASON_UNSPEC);
 }
 
 static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg)
@@ -4028,7 +4035,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,
+					  RTA_DEL_REASON_UNSPEC) < 0) {
 				kfree_skb(skb);
 				skb = NULL;
 			} else
@@ -4064,13 +4072,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, RTA_DEL_REASON_UNSPEC);
 			if (err)
 				goto out_unlock;
 		}
 	}
 
-	err = fib6_del(rt, info);
+	err = fib6_del(rt, info, RTA_DEL_REASON_UNSPEC);
 out_unlock:
 	spin_unlock_bh(&table->tb6_lock);
 out_put:
@@ -4196,7 +4204,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,
+						   RTA_DEL_REASON_UNSPEC);
 				break;
 			}
 			if (cfg->fc_nh_id)
@@ -4215,7 +4224,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,
+						   RTA_DEL_REASON_UNSPEC);
 			else
 				err = __ip6_del_rt_siblings(rt, cfg);
 			break;
@@ -5739,6 +5749,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;
 }
 
@@ -5775,7 +5786,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 rta_del_reason del_reason)
 {
 	struct rt6_info *rt6 = dst_rt6_info(dst);
 	struct rt6key *rt6_dst, *rt6_src;
@@ -5954,6 +5965,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 != RTA_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;
 
@@ -6055,7 +6070,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,
+					    RTA_DEL_REASON_UNSPEC);
 			if (err)
 				return err;
 
@@ -6103,7 +6119,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,
+					  RTA_DEL_REASON_UNSPEC)) {
 				return 0;
 			}
 			count++;
@@ -6336,12 +6353,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,
+					    RTA_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,
+					    RTA_DEL_REASON_UNSPEC);
 	} else {
 		err = -ENETUNREACH;
 	}
@@ -6357,8 +6376,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 rta_del_reason del_reason)
 {
 	struct net *net = info->nl_net;
 	struct sk_buff *skb;
@@ -6377,7 +6397,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);
+			    event, info->portid, seq, nlm_flags, del_reason);
 	if (err < 0) {
 		kfree_skb(skb);
 		/* -EMSGSIZE implies needed space grew under us. */
@@ -6398,6 +6418,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, RTA_DEL_REASON_UNSPEC);
+}
+
+void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info,
+			 enum rta_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)
 {
@@ -6410,7 +6442,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,
+			    RTA_DEL_REASON_UNSPEC);
 	if (err < 0) {
 		/* -EMSGSIZE implies BUG in rt6_nlmsg_size() */
 		WARN_ON(err == -EMSGSIZE);
@@ -6463,7 +6496,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, RTA_DEL_REASON_UNSPEC);
 	if (err < 0) {
 		/* -EMSGSIZE implies BUG in rt6_nlmsg_size() */
 		WARN_ON(err == -EMSGSIZE);
diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index e58bdbdc58eea..34935886b6adf 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 2e567062aa6cc..08deff756f29b 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 0c67c7c00d841..f74ccd5c2d13a 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__":

3/2 2026/07/30 18:59 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12996
							output: 26
							thoughts: 695
						

Thoughts:
**Fuzzing Target Identification: IPv6 Route Deletion Patch**

Okay, so I've got this patch here, and my initial assessment is that this warrants a fuzzing campaign. It introduces a new netlink attribute, `RTA_DEL_REASON`, specifically for IPv6 routes, and that's a functional change, no doubt. The kernel folks are starting to pass some more information.

The patch itself modifies several core functions like `fib6_del` and `ip6_del_rt` to accept a `del_reason` parameter, which is huge and where most of the work is going to be. It then cascades outward to update the notification mechanisms, `rt6_fill_node` and `inet6_rt_notify` so that they actually use this new attribute. They're passing a delete reason now, and the conditions are defined.

Essentially, they're setting the delete reason based on the cause. Expired routes during garbage collection get `RTA_DEL_REASON_EXPIRED`. RA withdrawals get `RTA_DEL_REASON_RA_WITHDRAWN`. Everything else defaults to `RTA_DEL_REASON_UNSPEC`.

So, the key here is not just that it's a new attribute, but that it's changing the behavior of the IPv6 routing logic. This is definitely fuzz-worthy.

Now, let's identify the actual functions we need to target. `fib6_del` is a critical function, but as it's internal, we'll have to find the entry points.

The trigger points for fuzzing are, generally:
*   Netlink route deletion (where we'll be dealing with `RTA_DEL_REASON_UNSPEC` mostly, though it will exercise the paths)
*   IPv6 route garbage collection, via `fib6_gc_table`, and also, probably `fib6_run_gc`
*   RA processing: `addrconf_prefix_rcv`, `ndisc_router_discovery`, `rt6_route_rcv`. This looks juicy.
*   Netlink route dump/get, but it's not the primary focus.

The modified functions are: `addrconf_prefix_rcv`, `ndisc_router_discovery`, `rt6_route_rcv`, `fib6_del`, `ip6_del_rt_reason`, `rt6_fill_node`, `inet6_rt_notify`, and `inet6_rt_del_notify`.

So, the focus symbols for fuzzing will be:
*   `addrconf_prefix_rcv`
*   `ndisc_router_discovery`
*   `rt6_route_rcv`
*   `fib6_gc_table` (and let's check for `fib6_run_gc` as well).
*   `inet6_rtm_delroute` is an interesting one because it triggers `ip6_route_del` which calls `__ip6_del_rt`.

I will double-check whether `fib6_gc_table` is static, or can be called directly, and whether `rt6_route_rcv` is static.



4/2 2026/07/30 19:00 tool grepper
0m Args:
{
  "Expression": "^(int|void).* rt6_route_rcv"
}

Results:
{
  "Output": "include/net/ip6_route.h=208=void rt6_purge_dflt_routers(struct net *net);\ninclude/net/ip6_route.h-209-\ninclude/net/ip6_route.h:210:int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,\ninclude/net/ip6_route.h-211-\t\t  const struct in6_addr *gwaddr);\n--\nnet/ipv6/route.c=963=static bool rt6_is_gw_or_nonexthop(const struct fib6_result *res)\n--\nnet/ipv6/route.c-969-#ifdef CONFIG_IPV6_ROUTE_INFO\nnet/ipv6/route.c:970:int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,\nnet/ipv6/route.c-971-\t\t  const struct in6_addr *gwaddr)\n"
}

5/2 2026/07/30 19:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14670
							output: 27
							thoughts: 46
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Routing Table and Neighbor Discovery Review**

Alright, let's see, `rt6_route_rcv` - that's definitely a non-static function, meaning it's externally visible and potentially called from other parts of the kernel or even modules. I need to keep that in mind when I'm assessing its impact on other modules and the overall system. And `addrconf_prefix_rcv`...same deal. Non-static, so the same considerations apply. Okay, nothing unexpected there, but important to remember.

Now, `ndisc_router_discovery`...that one is ringing a bell. Is that static or not? I *think* it might be, but I should verify that. It's crucial, because a static function implies its scope is limited, meaning it's primarily used within the defining source file, right? If it's static, then changes to it are less likely to ripple out and have wide-ranging consequences across the entire kernel or affect a module, so it's probably safe and will remain largely the same. But I'll double-check the definition just to be sure. It's always best to have a good understanding of any function you're working with.



6/2 2026/07/30 19:00 tool grepper
0m Args:
{
  "Expression": "^(int|void|static).* ndisc_router_discovery"
}

Results:
{
  "Output": "net/ipv6/ndisc.c=1182=static void ndisc_ra_useropt(struct sk_buff *ra, struct nd_opt_hdr *opt)\n--\nnet/ipv6/ndisc.c-1230-\nnet/ipv6/ndisc.c:1231:static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)\nnet/ipv6/ndisc.c-1232-{\n"
}

7/2 2026/07/30 19:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15149
							output: 24
							thoughts: 34
						

Thoughts:
Okay, here's my interpretation of those thoughts, presented as a first-person expert summary:

**Analyzing the Network Discovery Process**

Alright, so `ndisc_router_discovery` – it's static, right? That means its behavior is pretty well-defined, and we're not dealing with some dynamically allocated, complex beast. That's good to know. The entry point, the first place where things kick off, is `ndisc_rcv`. So, that's where I need to focus my attention. It’s the starting gun for the whole process. Let's dig into `ndisc_rcv` and see what's happening there. That's the key to understanding the initial discovery logic.



8/2 2026/07/30 19:00 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "^(int|void).* ndisc_rcv"
}

Results:
{
  "error": "no matches"
}

9/2 2026/07/30 19:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15377
							output: 19
							thoughts: 22
						

Thoughts:
Here's my take on those thoughts:

**Navigating the IPv6 Neighborhood Discovery Code**

Okay, so I'm trying to wrap my head around this Neighborhood Discovery stuff within the IPv6 stack. I need to figure out where `ndisc_rcv` resides. It's often easy to miss crucial details within a large codebase. My initial search didn't yield an immediate answer, so I need to go back and refine my search to find where `ndisc_rcv` is located. Let's see... Ah-ha! It looks like it's tucked away in `net/ipv6/ndisc.c`. That's precisely where I'd expect to find the core IPv6 neighbor discovery logic. Good. Now that I know the location, I can start to dig into the function itself and its surrounding context.



10/2 2026/07/30 19:00 tool grepper
0m Args:
{
  "Expression": "ndisc_rcv"
}

Results:
{
  "Output": "include/net/ndisc.h=404=void ndisc_cleanup(void);\ninclude/net/ndisc.h-405-\ninclude/net/ndisc.h:406:enum skb_drop_reason ndisc_rcv(struct sk_buff *skb);\ninclude/net/ndisc.h-407-\n--\nnet/ipv6/icmp.c=1101=static int icmpv6_rcv(struct sk_buff *skb)\n--\nnet/ipv6/icmp.c-1192-\tcase NDISC_REDIRECT:\nnet/ipv6/icmp.c:1193:\t\treason = ndisc_rcv(skb);\nnet/ipv6/icmp.c-1194-\t\tbreak;\n--\nnet/ipv6/ndisc.c=1791=static bool ndisc_suppress_frag_ndisc(struct sk_buff *skb)\n--\nnet/ipv6/ndisc.c-1804-\nnet/ipv6/ndisc.c:1805:enum skb_drop_reason ndisc_rcv(struct sk_buff *skb)\nnet/ipv6/ndisc.c-1806-{\n"
}

11/2 2026/07/30 19:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15908
							output: 26
							thoughts: 27
						

Thoughts:
Okay, here's my summary, presented as a stream of thought:

**Checking the Static Declaration of Relevant Functions**

Alright, let's see. My initial hunch was correct: `ndisc_rcv` is indeed *not* a static function. That checks out. But, I need to be thorough. Now, the question is, what about `addrconf_prefix_rcv`? I need to quickly ascertain the declaration status of that one as well to get the full picture. It's important to cross-reference these to confirm my hypothesis.



12/2 2026/07/30 19:00 tool grepper
0m Args:
{
  "Expression": "^(int|void).* addrconf_prefix_rcv"
}

Results:
{
  "Output": "include/net/addrconf.h=153=void addrconf_add_linklocal(struct inet6_dev *idev,\n--\ninclude/net/addrconf.h-155-\ninclude/net/addrconf.h:156:int addrconf_prefix_rcv_add_addr(struct net *net, struct net_device *dev,\ninclude/net/addrconf.h-157-\t\t\t\t const struct prefix_info *pinfo,\n--\ninclude/net/addrconf.h=282=static inline bool ipv6_is_mld(struct sk_buff *skb, int nexthdr, int offset)\n--\ninclude/net/addrconf.h-303-\ninclude/net/addrconf.h:304:void addrconf_prefix_rcv(struct net_device *dev,\ninclude/net/addrconf.h-305-\t\t\t u8 *opt, int len, bool sllao);\n--\nnet/ipv6/addrconf.c=2689=static bool is_addr_mode_generate_stable(struct inet6_dev *idev)\n--\nnet/ipv6/addrconf.c-2694-\nnet/ipv6/addrconf.c:2695:int addrconf_prefix_rcv_add_addr(struct net *net, struct net_device *dev,\nnet/ipv6/addrconf.c-2696-\t\t\t\t const struct prefix_info *pinfo,\n--\nnet/ipv6/addrconf.c=2796=EXPORT_SYMBOL_GPL(addrconf_prefix_rcv_add_addr);\nnet/ipv6/addrconf.c-2797-\nnet/ipv6/addrconf.c:2798:void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)\nnet/ipv6/addrconf.c-2799-{\n"
}

13/2 2026/07/30 19:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16557
							output: 25
							thoughts: 37
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating Potential IPV6 Routing Issues**

Alright, let's see, I'm digging into something related to IPv6 routing... specifically, I'm looking at `addrconf_prefix_rcv`. The immediate observation is that it's *not* declared as static, which raises a flag. Hmm, that's not what I'd expect given its role, potentially indicating broader scope than might be intended. I need to keep that in mind, this could be important down the line.

Now, my mind shifts to other related functions. Let me check the impact on `ip6_del_rt` and, specifically, `ip6_del_rt_reason`. I'm wondering if there's any interconnectedness between these functions regarding their behavior regarding route deletion and reason. Is there any potential for unintended side-effects or race conditions between them? I need to analyze how data flows in this space to understand if `addrconf_prefix_rcv` is playing nice with them. Perhaps the problem isn't isolated, and they relate.



14/2 2026/07/30 19:00 tool grepper
0m Args:
{
  "Expression": "^(int|void).* ip6_del_rt"
}

Results:
{
  "Output": "include/net/ip6_route.h=128=int ip6_ins_rt(struct net *net, struct fib6_info *f6i);\ninclude/net/ip6_route.h-129-#if IS_ENABLED(CONFIG_IPV6)\ninclude/net/ip6_route.h:130:int ip6_del_rt(struct net *net, struct fib6_info *f6i, bool skip_notify);\ninclude/net/ip6_route.h:131:int ip6_del_rt_reason(struct net *net, struct fib6_info *f6i, bool skip_notify,\ninclude/net/ip6_route.h-132-\t\t      enum rta_del_reason del_reason);\n--\nnet/ipv6/route.c=3976=static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info,\n--\nnet/ipv6/route.c-3997-\nnet/ipv6/route.c:3998:int ip6_del_rt_reason(struct net *net, struct fib6_info *rt, bool skip_notify,\nnet/ipv6/route.c-3999-\t\t      enum rta_del_reason del_reason)\n--\nnet/ipv6/route.c-4008-\nnet/ipv6/route.c:4009:int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify)\nnet/ipv6/route.c-4010-{\n"
}

15/2 2026/07/30 19:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17263
							output: 27
							thoughts: 38
						

Thoughts:
Okay, here's my interpretation of those thoughts, summarized from my perspective as someone familiar with network routing:

**Analyzing IPv6 Route Deletion Functions**

Alright, let's see, `ip6_del_rt` and `ip6_del_rt_reason` are *not* declared as static. This immediately grabs my attention. I need to understand the implications of this. It suggests that these functions might be exposed, perhaps used by other modules or parts of the kernel outside of the immediate routing table management. So, I need to check the codebase for usage or call sites. More context is required.

But that makes me think, what about the RTM_DELROUTE handler for IPv6? My understanding is, `inet6_rtm_delroute` is the key function called when a route is deleted via the routing message API. Are `ip6_del_rt` and `ip6_del_rt_reason` called *within* that function, or are they called from elsewhere? Is there an interaction that I need to be aware of? I'll need to trace the calling hierarchy. This looks like a potentially critical section of the IPv6 routing implementation to investigate.



16/2 2026/07/30 19:00 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "^(int|void).* inet6_rtm_delroute"
}

Results:
{
  "error": "no matches"
}

17/2 2026/07/30 19:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17593
							output: 22
							thoughts: 27
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**IPv6 Route Deletion - A Quick Check**

Hmm, so they're talking about removing a route in IPv6. Let me just make sure I have the function name correct. Ah, yes, that rings a bell. It looks like we're zeroing in on `inet6_rtm_delroute`. Good. Now I need to remember exactly where that lives... Ah, yes, `net/ipv6/route.c`. Of course. That's where all the IPv6 routing logic resides. Makes perfect sense. This sounds like it could be a relatively straightforward operation, or potentially a can of worms, depending on what else is going on in that function. I'll need to remember to check the adjacent functions in that file, as well as the related data structures to determine what might cause any complexity.



18/2 2026/07/30 19:00 tool grepper
0m Args:
{
  "Expression": "inet6_rtm_delroute"
}

Results:
{
  "Output": "net/ipv6/route.c=5602=static int ip6_route_multipath_del(struct fib6_config *cfg,\n--\nnet/ipv6/route.c-5641-\nnet/ipv6/route.c:5642:static int inet6_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh,\nnet/ipv6/route.c-5643-\t\t\t      struct netlink_ext_ack *extack)\n--\nnet/ipv6/route.c=6917=static const struct rtnl_msg_handler ip6_route_rtnl_msg_handlers[] __initconst_or_module = {\n--\nnet/ipv6/route.c-6920-\t{.owner = THIS_MODULE, .protocol = PF_INET6, .msgtype = RTM_DELROUTE,\nnet/ipv6/route.c:6921:\t .doit = inet6_rtm_delroute, .flags = RTNL_FLAG_DOIT_UNLOCKED},\nnet/ipv6/route.c-6922-\t{.owner = THIS_MODULE, .protocol = PF_INET6, .msgtype = RTM_GETROUTE,\n"
}

19/2 2026/07/30 19:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18146
							output: 22
							thoughts: 27
						

Thoughts:
Here's a summary of my thinking process, assuming I'm an expert:

**Investigating IPv6 Route Management Functionality**

Okay, so I was looking at the IPv6 route management code. I noticed `inet6_rtm_delroute` is declared as static. Interesting. That suggests it's likely only intended for internal use within this specific module. It's not designed to be directly called from outside.

That then got me thinking... what about its counterpart, `inet6_rtm_getroute`? Is it also static, or is it exposed in a way that allows external access? If `delroute` is static, it gives me clues about how the maintainers are thinking about network configuration and access. Is there a consistent pattern across functions? Or is it a special case? I need to go and check the definition of `inet6_rtm_getroute` and see what the story is there. This will give me a better understanding of the design philosophy surrounding IPv6 routing table manipulation, especially around the API surface. Is there something special or unique about `getroute` or `delroute`? Time to dig in!



20/2 2026/07/30 19:00 tool grepper
0m Args:
{
  "Expression": "inet6_rtm_getroute"
}

Results:
{
  "Output": "net/ipv6/route.c=6155=static int inet6_rtm_valid_getroute_req(struct sk_buff *skb,\n--\nnet/ipv6/route.c-6229-\nnet/ipv6/route.c:6230:static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,\nnet/ipv6/route.c-6231-\t\t\t      struct netlink_ext_ack *extack)\n--\nnet/ipv6/route.c=6917=static const struct rtnl_msg_handler ip6_route_rtnl_msg_handlers[] __initconst_or_module = {\n--\nnet/ipv6/route.c-6922-\t{.owner = THIS_MODULE, .protocol = PF_INET6, .msgtype = RTM_GETROUTE,\nnet/ipv6/route.c:6923:\t .doit = inet6_rtm_getroute, .flags = RTNL_FLAG_DOIT_UNLOCKED},\nnet/ipv6/route.c-6924-};\n"
}

21/2 2026/07/30 19:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18729
							output: 25
							thoughts: 25
						

Thoughts:
Here's a summary of my immediate thought process:

**Network Routing Inquiry**

Okay, so I'm looking at this routing code and I see `inet6_rtm_getroute` is declared as static. Interesting. My initial thought is to quickly consider the implications of this static declaration in the context of the routing functions, and I wonder if I can trace the functionality that will be impacted.

Does this have any impact on related functions in the routing process? Let me think. My mind jumps to another function: how is `rt6_dump_route` handling this? I need to understand how the static declaration of `inet6_rtm_getroute` ties into functions called to dump the routing tables. I'm wondering if this staticness has any implications on dumping the routing data, or if this function is actually a more granular component of a more broad set of functions. I should examine the relationship between these two to be sure.



22/2 2026/07/30 19:01 tool grepper
0m Args:
{
  "Expression": "^(int|void).* rt6_dump_route"
}

Results:
{
  "Output": "include/net/ip6_route.h=223=struct rt6_rtnl_dump_arg {\n--\ninclude/net/ip6_route.h-229-\ninclude/net/ip6_route.h:230:int rt6_dump_route(struct fib6_info *f6i, void *p_arg, unsigned int skip);\ninclude/net/ip6_route.h-231-void rt6_mtu_change(struct net_device *dev, unsigned int mtu);\n--\nnet/ipv6/route.c=6034=static int rt6_nh_dump_exceptions(struct fib6_nh *nh, void *arg)\n--\nnet/ipv6/route.c-6086-/* Return -1 if done with node, number of handled routes on partial dump */\nnet/ipv6/route.c:6087:int rt6_dump_route(struct fib6_info *rt, void *p_arg, unsigned int skip)\nnet/ipv6/route.c-6088-{\n"
}

23/2 2026/07/30 19:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19245
							output: 27
							thoughts: 36
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating IPv6 Route Notifications**

Hmm, so `rt6_dump_route` isn't static. That's good to keep in mind, I might need to consider its usage in the broader context of the routing table implementation. Let's see... what about the notifications? Specifically, `inet6_rt_notify` and `inet6_rt_del_notify`? Are there nuances to their operation that affect how route modifications are signaled, and how those notifications interact with whatever mechanisms use those functions? It is important to know if these functions operate the same as they do in IPv4 to accurately analyze and modify the code. I need to refresh my understanding of how those notification routines are used. A deeper dive might reveal how we're handling route additions and deletions in the IPv6 routing context, and if there are any subtle interactions or potential race conditions I need to be aware of.



24/2 2026/07/30 19:01 tool grepper
0m Args:
{
  "Expression": "^(int|void).* inet6_rt_del_notify"
}

Results:
{
  "Output": "include/net/ip6_fib.h=534=void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,\ninclude/net/ip6_fib.h-535-\t\t     unsigned int flags);\ninclude/net/ip6_fib.h:536:void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info,\ninclude/net/ip6_fib.h-537-\t\t\t enum rta_del_reason del_reason);\n--\nnet/ipv6/route.c=6421=void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,\n--\nnet/ipv6/route.c-6426-\nnet/ipv6/route.c:6427:void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info,\nnet/ipv6/route.c-6428-\t\t\t enum rta_del_reason del_reason)\n"
}

25/2 2026/07/30 19:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19789
							output: 23
							thoughts: 27
						

Thoughts:
Here's my summary, as though I'm thinking through this:

**Analyzing the Function Declarations**

Alright, let's see... `inet6_rt_del_notify` and `fib6_del` are both declared as non-static functions. That's immediately interesting, because it implies they're intended for use outside of their immediate implementation file. We're looking at functions designed for wider linkage, potentially exported or called from other modules or subsystems. That design choice suggests their functionality is core and possibly part of an API for IPv6 route management. I need to keep this in mind as I assess the broader architecture. It indicates a level of coupling and interdependence that will influence how I approach refactoring, optimization, or security assessments. The *why* is the key question.



26/2 2026/07/30 19:01 tool grepper
0m Args:
{
  "Expression": "^(int|void).* fib6_del"
}

Results:
{
  "Output": "include/net/ip6_fib.h=469=int fib6_add(struct fib6_node *root, struct fib6_info *rt,\ninclude/net/ip6_fib.h-470-\t     struct nl_info *info, struct netlink_ext_ack *extack);\ninclude/net/ip6_fib.h:471:int fib6_del(struct fib6_info *rt, struct nl_info *info,\ninclude/net/ip6_fib.h-472-\t     enum rta_del_reason del_reason);\n--\nnet/ipv6/ip6_fib.c=1968=static void fib6_del_route(struct fib6_table *table, struct fib6_node *fn,\n--\nnet/ipv6/ip6_fib.c-2064-/* Need to own table-\u003etb6_lock */\nnet/ipv6/ip6_fib.c:2065:int fib6_del(struct fib6_info *rt, struct nl_info *info,\nnet/ipv6/ip6_fib.c-2066-\t     enum rta_del_reason del_reason)\n"
}

27/2 2026/07/30 19:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20304
							output: 22
							thoughts: 164
						

Thoughts:
**Analyzing IPv6 Routing Functionality**

Okay, so I'm looking at this list: `addrconf_prefix_rcv`, `rt6_route_rcv`, `ip6_del_rt_reason`, `ip6_del_rt`, `rt6_dump_route`, `inet6_rt_del_notify`, and `fib6_del`. These seem to be core functions related to IPv6 routing, and I need to understand the dependencies and configuration requirements for them.

First, `CONFIG_IPV6` is a given. I mean, we're talking about IPv6, so that's a baseline. The test script also mentions `CONFIG_IPV6_ROUTE_INFO`, and there's a `KsftSkipEx` exception that raises if it's not found, which suggests it's crucial or at least part of the intended test setup. I'll need to double-check the `net/ipv6/Kconfig` file in the kernel source to understand exactly what `IPV6_ROUTE_INFO` controls and how it impacts these functions. This will help me determine the exact context and conditions under which these functions are being tested and used.



28/2 2026/07/30 19:01 tool grepper
0m Args:
{
  "Expression": "config IPV6_ROUTE_INFO"
}

Results:
{
  "Output": "net/ipv6/Kconfig=21=config IPV6_ROUTER_PREF\n--\nnet/ipv6/Kconfig-30-\nnet/ipv6/Kconfig:31:config IPV6_ROUTE_INFO\nnet/ipv6/Kconfig-32-\tbool \"IPv6: Route Information (RFC 4191) support\"\n"
}

29/2 2026/07/30 19:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20841
							output: 25
							thoughts: 39
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Configuration Considerations: IPv6 Routing**

Alright, let's see... *yes, I confirm that `IPV6_ROUTE_INFO` is a perfectly valid configuration option.* No surprises there. Of course, when dealing with IPv6 routing, you're going to use this. Now, let me think through the implications a bit deeper... ah, and it's crystal clear that `IPV6_ROUTER_PREF` is inherently linked to `IPV6_ROUTE_INFO`, isn't it? It acts as a dependency. You *need* that preference information to properly utilize the route info; it's practically inseparable for correct behavior. Gotta make sure those are correctly configured together.



30/2 2026/07/30 19:01 tool grepper
0m Args:
{
  "Expression": "depends on.*IPV6_ROUTER_PREF"
}

Results:
{
  "Output": "net/ipv6/Kconfig=31=config IPV6_ROUTE_INFO\nnet/ipv6/Kconfig-32-\tbool \"IPv6: Route Information (RFC 4191) support\"\nnet/ipv6/Kconfig:33:\tdepends on IPV6_ROUTER_PREF\nnet/ipv6/Kconfig-34-\thelp\n"
}

31/2 2026/07/30 19:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21172
							output: 24
							thoughts: 302
						

Thoughts:
**My Analysis of IPv6 Router Preference Configuration**

Okay, so I'm thinking we definitely need `IPV6_ROUTER_PREF` and `IPV6_ROUTE_INFO` enabled for this setup. `IPV6_MULTIPLE_TABLES` could potentially come into play too, but it might not be a hard requirement at this stage. I'll check if just `CONFIG_IPV6` is sufficient for the core functions – yep, that seems to cover the basics. However, to actually hit the code path that handles Route Information Options (RIOs) when the RA is withdrawn, we absolutely need `IPV6_ROUTE_INFO`.

Let's dive into that test script. Aha, here it is:

```python
        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")
```

The script tries to set `accept_ra_rt_info_max_plen`, and if that fails with a `FileNotFoundError`, it skips the test because `CONFIG_IPV6_ROUTE_INFO` is missing. Makes perfect sense. So, yes, `IPV6_ROUTE_INFO` is required for this specific configuration related to `accept_ra_rt_info_max_plen`.

Let me quickly cross-reference that with the `ndisc_router_discovery` and `rt6_route_rcv` code. Yep, looking at `rt6_route_rcv`, it's conditionally compiled under `CONFIG_IPV6_ROUTE_INFO`. That confirms the dependency again.



32/2 2026/07/30 19:01 tool grepper
0m Args:
{
  "Expression": "#ifdef CONFIG_IPV6_ROUTE_INFO"
}

Results:
{
  "Output": "include/linux/ipv6.h=13=struct ipv6_devconf {\n--\ninclude/linux/ipv6.h-51-\t__s32\t\trtr_probe_interval;\ninclude/linux/ipv6.h:52:#ifdef CONFIG_IPV6_ROUTE_INFO\ninclude/linux/ipv6.h-53-\t__s32\t\taccept_ra_rt_info_min_plen;\n--\ninclude/net/ndisc.h=100=struct ndisc_options {\ninclude/net/ndisc.h-101-\tstruct nd_opt_hdr *nd_opt_array[__ND_OPT_ARRAY_MAX];\ninclude/net/ndisc.h:102:#ifdef CONFIG_IPV6_ROUTE_INFO\ninclude/net/ndisc.h-103-\tstruct nd_opt_hdr *nd_opts_ri;\n--\nnet/ipv6/addrconf.c=181=static struct ipv6_devconf ipv6_devconf __read_mostly = {\n--\nnet/ipv6/addrconf.c-211-\t.rtr_probe_interval\t= 60 * HZ,\nnet/ipv6/addrconf.c:212:#ifdef CONFIG_IPV6_ROUTE_INFO\nnet/ipv6/addrconf.c-213-\t.accept_ra_rt_info_min_plen = 0,\n--\nnet/ipv6/addrconf.c=246=static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {\n--\nnet/ipv6/addrconf.c-276-\t.rtr_probe_interval\t= 60 * HZ,\nnet/ipv6/addrconf.c:277:#ifdef CONFIG_IPV6_ROUTE_INFO\nnet/ipv6/addrconf.c-278-\t.accept_ra_rt_info_min_plen = 0,\n--\nnet/ipv6/addrconf.c=5664=static void ipv6_store_devconf(const struct ipv6_devconf *cnf,\n--\nnet/ipv6/addrconf.c-5703-\t\tjiffies_to_msecs(READ_ONCE(cnf-\u003ertr_probe_interval));\nnet/ipv6/addrconf.c:5704:#ifdef CONFIG_IPV6_ROUTE_INFO\nnet/ipv6/addrconf.c-5705-\tarray[DEVCONF_ACCEPT_RA_RT_INFO_MIN_PLEN] =\n--\nnet/ipv6/addrconf.c=6849=static const struct ctl_table addrconf_sysctl[] = {\n--\nnet/ipv6/addrconf.c-7070-\t},\nnet/ipv6/addrconf.c:7071:#ifdef CONFIG_IPV6_ROUTE_INFO\nnet/ipv6/addrconf.c-7072-\t{\n--\nnet/ipv6/ndisc.c=220=struct ndisc_options *ndisc_parse_options(const struct net_device *dev,\n--\nnet/ipv6/ndisc.c-256-\t\t\tbreak;\nnet/ipv6/ndisc.c:257:#ifdef CONFIG_IPV6_ROUTE_INFO\nnet/ipv6/ndisc.c-258-\t\tcase ND_OPT_ROUTE_INFO:\n--\nnet/ipv6/ndisc.c=1231=static enum skb_drop_reason ndisc_router_discovery(struct sk_buff *skb)\n--\nnet/ipv6/ndisc.c-1496-\nnet/ipv6/ndisc.c:1497:#ifdef CONFIG_IPV6_ROUTE_INFO\nnet/ipv6/ndisc.c-1498-\tif (!READ_ONCE(in6_dev-\u003ecnf.accept_ra_from_local) \u0026\u0026\n--\nnet/ipv6/route.c=115=static struct rt6_info *rt6_find_cached_rt(const struct fib6_result *res,\n--\nnet/ipv6/route.c-118-\nnet/ipv6/route.c:119:#ifdef CONFIG_IPV6_ROUTE_INFO\nnet/ipv6/route.c-120-static struct fib6_info *rt6_add_route_info(struct net *net,\n--\nnet/ipv6/route.c=963=static bool rt6_is_gw_or_nonexthop(const struct fib6_result *res)\n--\nnet/ipv6/route.c-968-\nnet/ipv6/route.c:969:#ifdef CONFIG_IPV6_ROUTE_INFO\nnet/ipv6/route.c-970-int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,\n--\nnet/ipv6/route.c=4239=static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_buff *skb)\n--\nnet/ipv6/route.c-4381-\nnet/ipv6/route.c:4382:#ifdef CONFIG_IPV6_ROUTE_INFO\nnet/ipv6/route.c-4383-static struct fib6_info *rt6_get_route_info(struct net *net,\n"
}

33/2 2026/07/30 19:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23234
							output: 25
							thoughts: 684
						

Thoughts:
**Analyzing IPv6 Routing Symbol Visibility**

Okay, so I'm diving into this IPv6 routing code, and the first thing I notice is that `rt6_route_rcv` is indeed controlled by `CONFIG_IPV6_ROUTE_INFO`. That's a key piece of information, meaning we absolutely need to include `IPV6_ROUTE_INFO` and `IPV6_ROUTER_PREF` in `EnableConfigs`, along with the base `IPV6` configuration, of course.

Next, let's look at the symbols we need to consider. I've got: `addrconf_prefix_rcv`, `rt6_route_rcv`, `ip6_del_rt_reason`, `ip6_del_rt`, `rt6_dump_route`, `inet6_rt_del_notify`, and `fib6_del`.

Right, now, wait a second... `ip6_del_rt_reason` and `ip6_del_rt` are just wrappers for `__ip6_del_rt` within `net/ipv6/route.c`. Okay, let's check if `__ip6_del_rt` is static. Yes, it's static, which means `ip6_del_rt_reason` and `ip6_del_rt` are fine to include here. Good. Then `fib6_del` is in `net/ipv6/ip6_fib.c`, `inet6_rt_del_notify` and `rt6_dump_route` are in `net/ipv6/route.c`.

Let's double-check `fib6_del`'s visibility. It's declared in `include/net/ip6_fib.h` and defined in `net/ipv6/ip6_fib.c`:

```c
int fib6_del(struct fib6_info *rt, struct nl_info *info,
	     enum rta_del_reason del_reason)
```

So it is non-static. Makes sense.

Now, let's go function by function.

`addrconf_prefix_rcv` is non-static.
```c
void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
```
`rt6_route_rcv` is non-static:
```c
int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
		  const struct in6_addr *gwaddr)
```
`rt6_dump_route` is non-static:
```c
int rt6_dump_route(struct fib6_info *rt, void *p_arg, unsigned int skip)
```
`inet6_rt_del_notify` is non-static:
```c
void inet6_rt_del_notify(struct fib6_info *rt, struct nl_info *info,
			 enum rta_del_reason del_reason)
```

Are there other function calls related to this?
`fib6_gc_table` is static, but it is called from `fib6_run_gc` which is non-static. Makes sense, that's what I expected.



34/2 2026/07/30 19:01 tool grepper
0m Args:
{
  "Expression": "^(int|void).* fib6_run_gc"
}

Results:
{
  "Output": "include/net/ip6_fib.h=539=void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args,\ninclude/net/ip6_fib.h-540-\t\t\t unsigned long now);\ninclude/net/ip6_fib.h:541:void fib6_run_gc(unsigned long expires, struct net *net, bool force);\ninclude/net/ip6_fib.h-542-void fib6_gc_cleanup(void);\n--\nnet/ipv6/ip6_fib.c=2408=static void fib6_gc_all(struct net *net, struct fib6_gc_args *gc_args)\n--\nnet/ipv6/ip6_fib.c-2427-\nnet/ipv6/ip6_fib.c:2428:void fib6_run_gc(unsigned long expires, struct net *net, bool force)\nnet/ipv6/ip6_fib.c-2429-{\n"
}

35/2 2026/07/30 19:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24930
							output: 231
							thoughts: 668
						

Thoughts:
**My Analysis of the IPv6 Route Deletion Patch**

Okay, let's break this down. This patch introduces `RTA_DEL_REASON`, a new netlink attribute for IPv6 routes. This attribute will indicate the reason behind a route's deletion – things like expiration or withdrawal from a Router Advertisement (RA). The primary focus of this change is to modify the IPv6 routing functions, garbage collection mechanisms, and RA processing to incorporate this new attribute into route deletion paths and netlink notifications. This is a solid, functional change within the networking subsystem, and it necessitates careful fuzzing to confirm correct attribute handling and prevent any regression in route deletion functionality.

I've zeroed in on the key symbols this patch touches. The `fib6_run_gc` is non-static, and it's important because it triggers garbage collection and now incorporates this new `RTA_DEL_REASON_EXPIRED` reason. Then, the entry point for `ndisc_router_discovery`, `ndisc_rcv`, is also non-static, which is crucial.

Here's my refined list of the primary functions and data structures that will be affected by this change:

*   `addrconf_prefix_rcv`: This will be involved.
*   `rt6_route_rcv`: This will be involved.
*   `ip6_del_rt_reason`: This is crucial and will likely be the primary symbol to fuzz.
*   `rt6_dump_route`: Important for netlink dumps.
*   `inet6_rt_del_notify`: Involved in notifications.
*   `fib6_del`: This is modified by the patch.
*   `fib6_run_gc`: We're incorporating the reason here.
*   `ndisc_rcv`: Key as the entry point for router discovery.

`ip6_del_rt` is just a wrapper, so including `ip6_del_rt_reason` covers it. I've double-checked that `rt6_dump_route` is correct for netlink dumps. `inet6_rtm_getroute` is static, so focusing on `rt6_dump_route` for route dumps makes sense.

For the configuration options, the patch touches `fib6_del`, which is independent of multiple tables, thus only `IPV6`, `IPV6_ROUTER_PREF`, and `IPV6_ROUTE_INFO` are crucial. `IPV6` should be sufficient for testing the overall effects of the patch since it modifies `fib6_del`, which is used regardless of multiple tables.

I'm ready to use the `set-results` tool and provide my findings.



36/2 2026/07/30 19:01 tool set-results
0m Args:
{
  "EnableConfigs": [
    "IPV6",
    "IPV6_ROUTER_PREF",
    "IPV6_ROUTE_INFO"
  ],
  "FocusSymbols": [
    "addrconf_prefix_rcv",
    "rt6_route_rcv",
    "ip6_del_rt_reason",
    "rt6_dump_route",
    "inet6_rt_del_notify",
    "fib6_del",
    "fib6_run_gc",
    "ndisc_rcv"
  ],
  "Reasoning": "The patch introduces a new netlink attribute `RTA_DEL_REASON` for IPv6 routes to report the reason for route deletion (e.g., expired, RA withdrawn). It modifies core IPv6 routing functions, garbage collection, and Router Advertisement (RA) processing logic to plumb this new attribute through the route deletion paths and netlink notifications. This is a functional change in the networking subsystem that should be fuzzed to ensure correct handling of the new attribute and no regressions in route deletion.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "IPV6",
    "IPV6_ROUTER_PREF",
    "IPV6_ROUTE_INFO"
  ],
  "FocusSymbols": [
    "addrconf_prefix_rcv",
    "rt6_route_rcv",
    "ip6_del_rt_reason",
    "rt6_dump_route",
    "inet6_rt_del_notify",
    "fib6_del",
    "fib6_run_gc",
    "ndisc_rcv"
  ],
  "Reasoning": "The patch introduces a new netlink attribute `RTA_DEL_REASON` for IPv6 routes to report the reason for route deletion (e.g., expired, RA withdrawn). It modifies core IPv6 routing functions, garbage collection, and Router Advertisement (RA) processing logic to plumb this new attribute through the route deletion paths and netlink notifications. This is a functional change in the networking subsystem that should be fuzzed to ensure correct handling of the new attribute and no regressions in route deletion.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)