From nobody Fri Jul 24 21:52:35 2026 Received: from mail-pj1-f43.google.com (mail-pj1-f43.google.com [209.85.216.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 959FF36897C for ; Thu, 23 Jul 2026 10:44:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784803464; cv=none; b=VqpRx2PAwF5JwwGuG1PW0e90p+X0Uu5aEuPegi8EM6ggmgl7Tad3FpYwoxSeIftEjtZkSs4MOte8Z5TnpVBm7eSGhje51yYJJnyC42sGDiNcdm7X8kY5mfo4DWBDwwa3SFTxlFrPHpJqqSGU+Um60wfrAC/nObwopEbszYWDGQw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784803464; c=relaxed/simple; bh=oovsr2na6ByECQ79v2yVYUdKbr0hEqCkiDAT1w6sVJg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Vce8gTTdnyncYwQJYaeexowG56BHniJ/xm9k3iOQrra1bznsFxTF87glspRQ31KFgKjLr59T4bk/jLyyHQ830DNqmYTwfqIMgGAsHu/ZuuFZKCIS2HEz+OpNHKkDwU6m7sW2rl8IOmmJUOG0Y4uF6fFAMazdgVs+bFrlYhx9tKo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=dPSIi1xq; arc=none smtp.client-ip=209.85.216.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="dPSIi1xq" Received: by mail-pj1-f43.google.com with SMTP id 98e67ed59e1d1-38e041ea211so459037a91.0 for ; Thu, 23 Jul 2026 03:44:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784803460; x=1785408260; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:sender:from:to:cc:subject:date :message-id:reply-to:content-type; bh=HQDE0WLryyqfn2W/fjBps5PNAUIYDVKYIDit1fnBt9g=; b=dPSIi1xqx6CbtefTYjMl0lfkKQwsuJKZC4XbaphqnQ5JB0xa8m0iZihQ1sl8rC/xPX YhtGu7NZDJ+Igs2FPzaqiJM5AIhK50WenXnqekE5I2x3B0BkoB9HXspBnQlesiokyZ+w Ja7AO76KQEKwLZOpr9MT/jOBFYflNdxvVQmCR7docf5/J0fuylrok+cAYx9wLb7HJxZd hR1ZzKLjKopps7M6A/jwfJ4a6Kty0V+6lwDXxx0ZIc9DgB6MUUFwOyqXwCreVtHD94Hw o5Y7Levz/QnneyR5O0P8WUtmfA+kc3XWVvJamnc29HYldfEhLcRfV5xpWv2FeHeNC2BS cU1Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784803460; x=1785408260; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:sender:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=HQDE0WLryyqfn2W/fjBps5PNAUIYDVKYIDit1fnBt9g=; b=sdImjt3z/ekjHHfmMHGeVHwf/kKEnMJxce2KO+B/FR5PUqw+vLlPTsaK9qzi0yBEqf vKnfB21ZNYc2Mo0JmQtFGQ7s0OVRm18sYe7XIdLJ/alzYmTHAkRDyrHqxIW38aHFHyQI 4RL5hWuUIeT5EATz32jL48OigAF8Doj/eIFS4HWwmFrjQk5Y//a+8MBY5cfPHW/o1AHL NjoSVOix0PJ7N1hQkwHvg0+v3THFvwKnlQD2VGaWnWrn7mfF5mPucgkEGup81W0msykv BCHNDIYHybANjjYlE/4Uep/1D9r3sl7oLjz8oh/YwhIKPa62iRdsAQpISVAkVEKWAeDV 4bog== X-Forwarded-Encrypted: i=1; AHgh+RraI5bRSFkS+RUF9m5ZIL+NpnEcJBr6uRkqH07RLeBZawGbrHCOXPp/EYzMEeKX2d2wsQVP6TtqITXzLFQ=@vger.kernel.org X-Gm-Message-State: AOJu0YwKsj6mzF52EX0lXTACHgCkExQPhyXBVKMs+QtO3IVBefKr3q/B yB8GsmwMJnwOqZbjKzBi3fGL7IMRfRdvSnxHPT47IT4W6+ALKbRtrR3f X-Gm-Gg: AR+sD113bmLWqQ5Wjh0zEixo7LQVqoivCjgi/Z5aDnaNM6EskORPeS5fOPZekC57H4r hOfkHebgbE6lRcSO44tjyiLOHeU/+dsFm7PayKKjf+qPR4/3CNXu6/ImmKBvIQ8j2JBVvKd3Evt O7rxcy/+zRyTMOirppssMXmbwEojnhEs2K/wkNQ5pf/Dju5OCXXJFxmKn4oOT1GSmAhHSGKsRsq rb5ZxEA35ZC8sHvd+7NpC99lgO7nwHRdW0+3qojNeHsJhm3Ky6PIHipCaU3QVNpi2pD5BgBDM81 bonyXuUPkbqMn3tc0Hk1dJoketJ783FDKjnTyUnLFOTOODaNw40ovwTgNnwhCd08WAFXLTUFkCU 0JQj0WOY68KtiluGCD6jgSceA63fGMbh6azMGfLAUW9yigjP2BklotSdAbK1o0jHlkaUOkb0TPZ VYYQTVnab9zhPj5Tmkxy057lHAQm7qtZe9t6SsWtpH0W//dSiaVioSrLHO2vZ0nQ== X-Received: by 2002:a17:90b:1a8a:b0:38e:bbf1:de3f with SMTP id 98e67ed59e1d1-38ec697957fmr2371843a91.12.1784803459343; Thu, 23 Jul 2026 03:44:19 -0700 (PDT) Received: from m-upc-A520M-HDV.lan ([2400:2410:3f60:500:86fb:a344:1a3d:eba0]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38ea59c9b5asm1210262a91.3.2026.07.23.03.44.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Jul 2026 03:44:18 -0700 (PDT) Sender: Yuyang Huang From: Yuyang Huang To: Yuyang Huang Cc: "David S. Miller" , Bobby Eshleman , Chris J Arges , Daniel Zahka , David Ahern , David Wei , Dimitri Daskalakis , Donald Hunter , Eric Dumazet , Gal Pressman , Ido Schimmel , Jakub Kicinski , Paolo Abeni , Shuah Khan , Simon Horman , Stanislav Fomichev , Willem de Bruijn , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, netdev@vger.kernel.org Subject: [PATCH net-next v2 1/2] ipv6: report why a route was deleted in RTM_DELROUTE Date: Thu, 23 Jul 2026 19:44:02 +0900 Message-ID: <20260723104403.7970-2-sigefriedhyy@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260723104403.7970-1-sigefriedhyy@gmail.com> References: <20260723104403.7970-1-sigefriedhyy@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add RTA_DEL_REASON (u8) to RTM_DELROUTE notifications to report the cause of kernel-initiated IPv6 route deletions, so user space can tell routes that expired from routes explicitly withdrawn by Router Advertisements. The two causes call for different treatment in user space. An expired RA route means the router failed to refresh it before its lifetime ran out, which points at a misconfigured or unreliable router; user space may want to react, for example by disabling IPv6 on that network entirely. An RA withdrawal (zero lifetime) is valid, RFC-compliant operation and needs no such recovery. Today both surface as an identical RTM_DELROUTE. Plumb the deletion reason through the IPv6 FIB and notification paths: - Update `fib6_del`, `fib6_del_route`, and `inet6_rt_notify` signatures to explicitly pass the `del_reason` (u8) down the call chain. - Update `rt6_fill_node()` to emit `RTA_DEL_REASON` attribute when the reason is not UNSPEC. - Record the cause in the kernel-initiated IPv6 deletion paths: - RTA_DEL_REASON_EXPIRED: for routes cleaned up by the FIB6 garbage collector. - RTA_DEL_REASON_RA_WITHDRAWN: for default routes, prefix routes, or route information option routes withdrawn by Router Advertisements. - Update the `rt-route` Netlink YAML spec to define the new attribute, notifications, and multicast groups. Signed-off-by: Yuyang Huang --- Documentation/netlink/specs/rt-route.yaml | 33 ++++++++++++ include/net/ip6_fib.h | 4 +- include/net/ip6_route.h | 2 + include/uapi/linux/rtnetlink.h | 17 ++++++ net/ipv6/addrconf.c | 3 +- net/ipv6/ip6_fib.c | 19 ++++--- net/ipv6/ndisc.c | 7 ++- net/ipv6/route.c | 66 +++++++++++++++-------- 8 files changed, 116 insertions(+), 35 deletions(-) diff --git a/Documentation/netlink/specs/rt-route.yaml b/Documentation/netl= ink/specs/rt-route.yaml index 33195db96746..234f502fb35d 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 =20 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 @@ -322,3 +336,22 @@ operations: request: value: 25 attributes: *all-route-attrs + - + name: newroute-ntf + doc: Notification about a created route. + value: 24 + notify: getroute + - + name: delroute-ntf + doc: Notification about a deleted route. + value: 25 + notify: getroute + +mcast-groups: + list: + - + name: rtnlgrp-ipv4-route + value: 7 + - + name: rtnlgrp-ipv6-route + value: 11 diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h index 9cd27e1b9b69..7ca0e2124e79 100644 --- a/include/net/ip6_fib.h +++ b/include/net/ip6_fib.h @@ -468,7 +468,7 @@ void fib6_clean_all_skip_notify(struct net *net, =20 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, u32 del_reason); =20 static inline void rt6_get_prefsrc(const struct rt6_info *rt, struct in6_addr *addr) @@ -531,7 +531,7 @@ static inline void fib6_rt_update(struct net *net, stru= ct fib6_info *rt, } #endif void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, - unsigned int flags); + unsigned int flags, u32 del_reason); =20 void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_arg= s, unsigned long now); diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index 09ffe0f13ce7..98f1684fe378 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -128,6 +128,8 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_fl= ags, 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_no= tify, + u32 del_reason); #else static inline int ip6_del_rt(struct net *net, struct fib6_info *f6i, bool skip_notify) diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h index 27265fd31e5f..fe00e624f3c3 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 }; =20 @@ -407,6 +408,22 @@ enum rtattr_type_t { #define RTM_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(st= ruct rtmsg)))) #define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg)) =20 +/* 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 f1fe9ede1edb..1ca9d82b5721 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 =3D=3D 0) { - ip6_del_rt(net, rt, false); + ip6_del_rt_reason(net, rt, false, + RTA_DEL_REASON_RA_WITHDRAWN); rt =3D NULL; } else { table =3D rt->fib6_table; diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index a130cdfaebfb..32b9a00956a0 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -1286,7 +1286,8 @@ static int fib6_add_rt2node(struct fib6_node *fn, str= uct fib6_info *rt, rcu_assign_pointer(rt->fib6_node, fn); rcu_assign_pointer(*ins, rt); if (!info->skip_notify) - inet6_rt_notify(RTM_NEWROUTE, rt, info, nlflags); + inet6_rt_notify(RTM_NEWROUTE, rt, info, nlflags, + RTA_DEL_REASON_UNSPEC); info->nl_net->ipv6.rt6_stats->fib_rt_entries++; =20 if (!(fn->fn_flags & RTN_RTINFO)) { @@ -1317,7 +1318,8 @@ static int fib6_add_rt2node(struct fib6_node *fn, str= uct fib6_info *rt, rt->fib6_next =3D iter->fib6_next; rcu_assign_pointer(*ins, rt); if (!info->skip_notify) - inet6_rt_notify(RTM_NEWROUTE, rt, info, NLM_F_REPLACE); + inet6_rt_notify(RTM_NEWROUTE, rt, info, NLM_F_REPLACE, + RTA_DEL_REASON_UNSPEC); if (!(fn->fn_flags & RTN_RTINFO)) { info->nl_net->ipv6.rt6_stats->fib_route_nodes++; fn->fn_flags |=3D RTN_RTINFO; @@ -1966,7 +1968,8 @@ static struct fib6_node *fib6_repair_tree(struct net = *net, } =20 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, + u32 del_reason) { struct fib6_info *leaf, *replace_rt =3D NULL; struct fib6_walker *w; @@ -2055,13 +2058,13 @@ 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_notify(RTM_DELROUTE, rt, info, 0, del_reason); =20 fib6_info_release(rt); } =20 /* 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, u32 del_reason) { struct net *net =3D info->nl_net; struct fib6_info __rcu **rtp; @@ -2090,7 +2093,7 @@ int fib6_del(struct fib6_info *rt, struct nl_info *in= fo) if (rt =3D=3D 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 =3D &cur->fib6_next; @@ -2252,7 +2255,7 @@ static int fib6_clean_node(struct fib6_walker *w) res =3D c->func(rt, c->arg); if (res =3D=3D -1) { w->leaf =3D rt; - res =3D fib6_del(rt, &info); + res =3D fib6_del(rt, &info, RTA_DEL_REASON_UNSPEC); if (res) { #if RT6_DEBUG >=3D 2 pr_debug("%s: del failed: rt=3D%p@%p err=3D%d\n", @@ -2400,7 +2403,7 @@ static void fib6_gc_table(struct net *net, =20 hlist_for_each_entry_safe(rt, n, &tb6->tb6_gc_hlist, gc_link) if (fib6_age(rt, gc_args) =3D=3D -1) - fib6_del(rt, &info); + fib6_del(rt, &info, RTA_DEL_REASON_EXPIRED); } =20 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 f867ec8d3d90..c3537fc0ec63 100644 --- a/net/ipv6/ndisc.c +++ b/net/ipv6/ndisc.c @@ -1361,7 +1361,9 @@ static enum skb_drop_reason ndisc_router_discovery(st= ruct sk_buff *skb) defrtr_usr_metric =3D in6_dev->cnf.ra_defrtr_metric; /* delete the route if lifetime is 0 or if metric needs change */ if (rt && (lifetime =3D=3D 0 || rt->fib6_metric !=3D defrtr_usr_metric)) { - ip6_del_rt(net, rt, false); + ip6_del_rt_reason(net, rt, false, + lifetime =3D=3D 0 ? RTA_DEL_REASON_RA_WITHDRAWN : + RTA_DEL_REASON_UNSPEC); rt =3D NULL; } =20 @@ -1396,7 +1398,8 @@ static enum skb_drop_reason ndisc_router_discovery(st= ruct sk_buff *skb) .nl_net =3D net, }; rt->fib6_flags =3D (rt->fib6_flags & ~RTF_PREF_MASK) | RTF_PREF(pref); - inet6_rt_notify(RTM_NEWROUTE, rt, &nlinfo, NLM_F_REPLACE); + inet6_rt_notify(RTM_NEWROUTE, rt, &nlinfo, NLM_F_REPLACE, + RTA_DEL_REASON_UNSPEC); } =20 if (rt) { diff --git a/net/ipv6/route.c b/net/ipv6/route.c index a1301334da48..e8ed95dc5665 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_buf= f *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, u32 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, in= t len, gwaddr, dev); =20 if (rt && !lifetime) { - ip6_del_rt(net, rt, false); + ip6_del_rt_reason(net, rt, false, RTA_DEL_REASON_RA_WITHDRAWN); rt =3D NULL; } =20 @@ -3973,7 +3973,8 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_= flags, return err; } =20 -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, + u32 del_reason) { struct net *net =3D info->nl_net; struct fib6_table *table; @@ -3986,7 +3987,7 @@ static int __ip6_del_rt(struct fib6_info *rt, struct = nl_info *info) =20 table =3D rt->fib6_table; spin_lock_bh(&table->tb6_lock); - err =3D fib6_del(rt, info); + err =3D fib6_del(rt, info, del_reason); spin_unlock_bh(&table->tb6_lock); =20 out: @@ -3994,14 +3995,20 @@ static int __ip6_del_rt(struct fib6_info *rt, struc= t nl_info *info) return err; } =20 -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_not= ify, + u32 del_reason) { struct nl_info info =3D { .nl_net =3D net, - .skip_notify =3D skip_notify + .skip_notify =3D skip_notify, }; =20 - 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); } =20 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) =20 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 =3D 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 =3D fib6_del(sibling, info); + err =3D fib6_del(sibling, info, RTA_DEL_REASON_UNSPEC); if (err) goto out_unlock; } } =20 - err =3D fib6_del(rt, info); + err =3D 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; =20 - err =3D __ip6_del_rt(rt, &cfg->fc_nlinfo); + err =3D __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, =20 /* if gateway was specified only delete the one hop */ if (cfg->fc_flags & RTF_GATEWAY) - err =3D __ip6_del_rt(rt, &cfg->fc_nlinfo); + err =3D __ip6_del_rt(rt, &cfg->fc_nlinfo, + RTA_DEL_REASON_UNSPEC); else err =3D __ip6_del_rt_siblings(rt, cfg); break; @@ -5381,7 +5391,8 @@ static void ip6_route_mpath_notify(struct fib6_info *= rt, } =20 if (rt) - inet6_rt_notify(RTM_NEWROUTE, rt, info, nlflags); + inet6_rt_notify(RTM_NEWROUTE, rt, info, nlflags, + RTA_DEL_REASON_UNSPEC); =20 rcu_read_unlock(); } @@ -5739,6 +5750,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; } =20 @@ -5775,7 +5787,7 @@ static int rt6_fill_node(struct net *net, struct sk_b= uff *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, u32 del_reason) { struct rt6_info *rt6 =3D dst_rt6_info(dst); struct rt6key *rt6_dst, *rt6_src; @@ -5954,6 +5966,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; =20 + if (type =3D=3D RTM_DELROUTE && del_reason !=3D 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; =20 @@ -6055,7 +6071,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; =20 @@ -6103,7 +6120,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 +6354,14 @@ static int inet6_rtm_getroute(struct sk_buff *in_sk= b, struct nlmsghdr *nlh, err =3D 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 =3D 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 =3D -ENETUNREACH; } @@ -6358,7 +6378,7 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb,= struct nlmsghdr *nlh, } =20 void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info, - unsigned int nlm_flags) + unsigned int nlm_flags, u32 del_reason) { struct net *net =3D info->nl_net; struct sk_buff *skb; @@ -6377,7 +6397,8 @@ void inet6_rt_notify(int event, struct fib6_info *rt,= struct nl_info *info, goto errout; =20 err =3D 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. */ @@ -6410,7 +6431,8 @@ void fib6_rt_update(struct net *net, struct fib6_info= *rt, goto errout; =20 err =3D 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 =3D=3D -EMSGSIZE); @@ -6463,7 +6485,7 @@ void fib6_info_hw_flags_set(struct net *net, struct f= ib6_info *f6i, } =20 err =3D 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 =3D=3D -EMSGSIZE); --=20 2.43.0 From nobody Fri Jul 24 21:52:35 2026 Received: from mail-pj1-f44.google.com (mail-pj1-f44.google.com [209.85.216.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 13AB343CE7D for ; Thu, 23 Jul 2026 10:44:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784803467; cv=none; b=Vf8NnNaG1ASnGvUCep4RurSlWhAgmCNVeiXXRhhperBARx6z/VGk7QIhMfo9zKJG6GwpB3ZE4icmT9B5GcyXCH5vS+2Ps+rUPVPsc/M72P0Bjno35eonU5fls1hp455kgZ7pIoQqLx9TF0x+qgk0B/rJdBxuvq23O9vBdBPuGJY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784803467; c=relaxed/simple; bh=O1CQFr8gL/tx0nZELxNaajoTVr4qy8kYNkp4eOn+3D8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Cvlf8aiNSIfD6tja3oqcs5DX++3p9B0HIZOKX9wgFb3t2xeo9ZZ6bjXfG2/yADFkU+0wWMRJmKIRwQbMmu0pkhGZGWOGIc9+80vbeRb+UVEi0CDUVvk74YscL03dHHUwjzz4fmqa4k7Cse5H7uzBIwk9rsTPMPYV4fDbM5fgxXk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=rL8wwaBE; arc=none smtp.client-ip=209.85.216.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="rL8wwaBE" Received: by mail-pj1-f44.google.com with SMTP id 98e67ed59e1d1-385ea3ce80dso444563a91.2 for ; Thu, 23 Jul 2026 03:44:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784803464; x=1785408264; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:sender:from:to:cc:subject:date :message-id:reply-to:content-type; bh=IfEzsYijpMni+dHKVnbyAeGeNkrEFny9SRjpkXNKFps=; b=rL8wwaBErqWCGZxPn87TGlJCCe5C1WzTi/w/O8WhMEUTjZyNYewUZP/GZFgRyRR6eD UmVGd/6XBwm44os7+1FZEhyF0dKUm4qKild/Ks9/6vSv69aVrsN6jgnuDFVRMNqgEbh1 8SNg4D54zjVFYVE7BT/EK62uNX8ovSDlDGu3AIkfX9a1RByfbOkjG1p7jcHJoE5wk8jK 92Y7VLwp+OAeV7dC+Ozc8ul/QrpWcr6WjvmxHZ4aSp3ESHMwJPOOqwruOY6VzwQ6Gh8O MWc74MCykhK2ZtWJvIoGx1EH9hOjjmUJsS9ghqf62ETZbmmMP6/tpUiAaRXi1sgpqFjk KYNg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784803464; x=1785408264; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:sender:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=IfEzsYijpMni+dHKVnbyAeGeNkrEFny9SRjpkXNKFps=; b=IxcRXtF/XiJLwa2Fr3Gp1LVs2V2AnkVsgJsWJyIN6kkGLNZ6Yw44/U382zX5JkmANV 56ydcjMXBVCczFR5i71F/yLHFgEMBHR8u8zmLobw+F6SnINhok5SXKsKc/GTr6oU8aFo nlgMfO4LifTx9gLK3achdgfXW224wrqYGrKa52nJ6SdPkMg3PU8mQcMv1q8e38Fi5XHe 685hpSPhovMcnpl1fOM7fqRh+Yse42NxeYNzlf/jxqJCxNMPwj56McWMCIdecDEi6UkM g3+O2k5y5fJq8a5uhQjjtjT8UlRDbZ/S96jS5UR+ic5K10FGFV3EhOunO+o1aGJBdnkE W3tA== X-Forwarded-Encrypted: i=1; AHgh+RqgVMXTs0d0RVWfl5x/faFvtjh1fiiN8i9r2cGcDWxwv4tLTcVIx0oZdFlC6a+gOqJAoFqV9hCVUCUnl0Q=@vger.kernel.org X-Gm-Message-State: AOJu0YwIEfFLd9YQod/n4vkfywleLwZHDy15o8Bdv/cU3eNeo+/gOULj uksxt/j+10iUK7W2ij+yCXsmI1ToiSAfxyUldM/6agJLOPwevO0vLNHb X-Gm-Gg: AR+sD11wSZb7iBWPYOBAvEkvqB04fP688JWkAj73GQW1Q4hySO9IBmmJ6KbgegJCwDd 5VRaOJ0p76Ac2WlrHFHRZOLxUUKHf6h92vilBWJHWBuQCGoTBs0G3kyEunb1/xaEHYee/Un5IhI PC5d1uvZK2xw8XVy8rBJ0HkjImraWMntuAS+70YkeV+wUa03mzGEJwrlPn8eBilOblr/eflXeXu h3tcuwgbMBDgD70uokdgYBbbkojV4erbCCtD9iA0s4N8jFJOJOPN7sfLHMHnKQPgTNJzbmX1KH+ rFsvxS64E7uilK9Quexn9IfsRT+7OayRKeD5DEbfJC+wccI/KUwraLa2F6t53pVWdxhEl2qBD6L gBiYGZuTT0+yiYmwY+ybBZsI+KSW9U5lgrsngUUll9alChRjpzSFoNctcHjOBfUT3mEFAEMaB9+ b3s7RIXQEXtAMH2HertyAEN0G0i5OVFi0RFh+/qFBUySQCPh8AakA= X-Received: by 2002:a17:90b:2f06:b0:389:8f6c:4d70 with SMTP id 98e67ed59e1d1-38ec6612dd9mr2791509a91.28.1784803463922; Thu, 23 Jul 2026 03:44:23 -0700 (PDT) Received: from m-upc-A520M-HDV.lan ([2400:2410:3f60:500:86fb:a344:1a3d:eba0]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-38ea59c9b5asm1210262a91.3.2026.07.23.03.44.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Jul 2026 03:44:23 -0700 (PDT) Sender: Yuyang Huang From: Yuyang Huang To: Yuyang Huang Cc: "David S. Miller" , Bobby Eshleman , Chris J Arges , Daniel Zahka , David Ahern , David Wei , Dimitri Daskalakis , Donald Hunter , Eric Dumazet , Gal Pressman , Ido Schimmel , Jakub Kicinski , Paolo Abeni , Shuah Khan , Simon Horman , Stanislav Fomichev , Willem de Bruijn , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, netdev@vger.kernel.org Subject: [PATCH net-next v2 2/2] selftests: net: verify RTA_DEL_REASON on route deletion Date: Thu, 23 Jul 2026 19:44:03 +0900 Message-ID: <20260723104403.7970-3-sigefriedhyy@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260723104403.7970-1-sigefriedhyy@gmail.com> References: <20260723104403.7970-1-sigefriedhyy@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Extend rtnetlink.py to check the reason reported in RTM_DELROUTE: - expired: route with a 2s lifetime collected by the fib6 GC (gc_interval lowered like fib_tests.sh fib6_gc_test does); - ra-withdrawn: a single RA advertises a default route (router lifetime), an on-link prefix route (RFC 4861 prefix information option) and a route information option route (RFC 4191), then a second RA withdraws all three with zero lifetimes; the RAs are crafted over a raw ICMPv6 socket so the test does not depend on an external RA tool; - absence: a userspace deletion request records no cause and must not carry the attribute at all. Signed-off-by: Yuyang Huang --- .../testing/selftests/net/lib/py/__init__.py | 4 +- tools/testing/selftests/net/lib/py/ynl.py | 7 +- tools/testing/selftests/net/rtnetlink.py | 181 +++++++++++++++++- 3 files changed, 187 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing= /selftests/net/lib/py/__init__.py index e58bdbdc58ee..34935886b6ad 100644 --- a/tools/testing/selftests/net/lib/py/__init__.py +++ b/tools/testing/selftests/net/lib/py/__init__.py @@ -17,7 +17,7 @@ from .utils import CmdExitFailure, fd_read_timeout, cmd, = bkg, defer, \ wait_file, tool, tc from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from .ynl import NlError, NlctrlFamily, YnlFamily, \ - EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily + EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily, RtnlRouteFami= ly from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink =20 __all__ =3D ["KSRC", @@ -34,4 +34,4 @@ __all__ =3D ["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/self= tests/net/lib/py/ynl.py index 2e567062aa6c..08deff756f29 100644 --- a/tools/testing/selftests/net/lib/py/ynl.py +++ b/tools/testing/selftests/net/lib/py/ynl.py @@ -29,7 +29,7 @@ except ModuleNotFoundError as e: =20 __all__ =3D [ "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=3D'', recv_size=3Drecv_size) =20 +class RtnlRouteFamily(YnlFamily): + def __init__(self, recv_size=3D0): + super().__init__((SPEC_PATH / Path('rt-route.yaml')).as_posix(), + schema=3D'', recv_size=3Drecv_size) + class NetdevFamily(YnlFamily): def __init__(self, recv_size=3D0): super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(), diff --git a/tools/testing/selftests/net/rtnetlink.py b/tools/testing/selft= ests/net/rtnetlink.py index 0c67c7c00d84..f74ccd5c2d13 100755 --- a/tools/testing/selftests/net/rtnetlink.py +++ b/tools/testing/selftests/net/rtnetlink.py @@ -5,7 +5,8 @@ import socket import struct import time from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_eq, ksft_ge, ksft_tr= ue, 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, Rtnl= RouteFamily =20 IPV4_ALL_HOSTS_MULTICAST =3D b'\xe0\x00\x00\x01' IPV4_TEST_MULTICAST =3D 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 {ifna= me}") =20 +def _rtnl_route_subscribe(ns): + with NetNSEnter(str(ns)): + rtnl =3D RtnlRouteFamily() + rtnl.ntf_subscribe("rtnlgrp-ipv6-route") + return rtnl + + +def _wait_route_ntf(rtnl, name, dst_len, dst=3DNone, deadline=3D10): + """Return the attrs of the first matching notification, None on timeou= t.""" + + for msg in rtnl.poll_ntf(duration=3Ddeadline): + if msg['name'] !=3D name: + continue + attrs =3D msg['msg'] + if attrs['rtm-dst-len'] !=3D dst_len: + continue + if dst is not None and attrs.get('dst') !=3D dst: + continue + return attrs + return None + + +def _collect_route_ntfs(rtnl, name, want, deadline=3D10): + """Gather attrs of matching notifications, keyed by (dst_len, dst).""" + + seen =3D {} + for msg in rtnl.poll_ntf(duration=3Ddeadline): + if msg['name'] !=3D name: + continue + attrs =3D msg['msg'] + key =3D (attrs['rtm-dst-len'], attrs.get('dst')) + if key in want: + seen[key] =3D attrs + if len(seen) =3D=3D 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 =3D=3D expired.""" + + with NetNS() as ns: + rtnl =3D _rtnl_route_subscribe(ns) + with NetNSEnter(str(ns)): + _write_ipv6_sysctl("route/gc_interval", 2) + ip("link add name dummy1 type dummy", ns=3Dstr(ns)) + ip("link set dev dummy1 up", ns=3Dstr(ns)) + ip("-6 route add 2001:db8:2::/64 dev dummy1 expires 2", ns=3Dstr(n= s)) + + attrs =3D _wait_route_ntf(rtnl, 'delroute-ntf', 64, '2001:db8:2::', + deadline=3D15) + 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=3DNone, pio=3DNone): + """The kernel fills in the ICMPv6 checksum on raw ICMPv6 sockets.""" + + # type, code, cksum, hop limit, flags, router lifetime, + # reachable time, retrans timer + ra =3D struct.pack('!BBHBBHII', 134, 0, 0, 64, 0, lifetime, 0, 0) + if rio is not None: + prefix, plen, rio_lifetime =3D rio + # RFC 4191 route information option, /64 prefix (8 bytes) + ra +=3D struct.pack('!BBBBI', 24, 2, plen, 0, rio_lifetime) + ra +=3D socket.inet_pton(socket.AF_INET6, prefix)[:8] + if pio is not None: + prefix, plen, valid_lft =3D pio + # RFC 4861 prefix information option, on-link only (L set, A clear) + ra +=3D struct.pack('!BBBBIII', 3, 4, plen, 0x80, valid_lft, 0, 0) + ra +=3D 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 =3D socket.socket(socket.AF_INET6, socket.SOCK_RAW, + socket.IPPROTO_ICMPV6) + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 2= 55) + 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 =3D {} + 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= =3D2)) + if len(seen) =3D=3D 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 =3D=3D ra-withdrawn. + """ + + # (rtm-dst-len, dst); the default route carries no RTA_DST + routes =3D {(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 a= way + _write_ipv6_sysctl("conf/veth1/accept_dad", 0) + ip("link set dev veth0 up", ns=3Dstr(ns_h)) + ip("link set dev veth1 up", ns=3Dstr(ns_r)) + + rtnl =3D _rtnl_route_subscribe(ns_h) + sock, ifindex =3D _ra_router_sock(ns_r, "veth1") + + seen =3D _ra_advertise_routes(rtnl, sock, ifindex, routes, + lifetime=3D1800, + rio=3D('2001:db8:5::', 64, 600), + pio=3D('2001:db8:6::', 64, 600)) + ksft_eq(set(seen), routes, "not all RA routes were installed") + if set(seen) !=3D routes: + return + + _send_ra(sock, ifindex, 0, rio=3D('2001:db8:5::', 64, 0), + pio=3D('2001:db8:6::', 64, 0)) + seen =3D _collect_route_ntfs(rtnl, 'delroute-ntf', routes) + for key in routes: + attrs =3D 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 =3D _rtnl_route_subscribe(ns) + ip("link add name dummy1 type dummy", ns=3Dstr(ns)) + ip("link set dev dummy1 up", ns=3Dstr(ns)) + ip("-6 route add 2001:db8:1::/64 dev dummy1", ns=3Dstr(ns)) + ip("-6 route del 2001:db8:1::/64 dev dummy1", ns=3Dstr(ns)) + + attrs =3D _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() =20 if __name__ =3D=3D "__main__": --=20 2.43.0