* [PATCH v4 net 1/2] ipv6: Remove permanent routes from tb6_gc_hlist when all exceptions expire.
2026-03-11 5:29 [PATCH v4 net 0/2] ipv6: Fix two GC issues with permanent routes Kuniyuki Iwashima
@ 2026-03-11 5:29 ` Kuniyuki Iwashima
2026-03-11 5:29 ` [PATCH v4 net 2/2] ipv6: Don't remove permanent routes with exceptions from tb6_gc_hlist Kuniyuki Iwashima
2026-03-13 15:03 ` [PATCH v4 net 0/2] ipv6: Fix two GC issues with permanent routes David Ahern
2 siblings, 0 replies; 6+ messages in thread
From: Kuniyuki Iwashima @ 2026-03-11 5:29 UTC (permalink / raw)
To: David Ahern, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Kui-Feng Lee, Xin Long, Simon Horman, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev
Commit 5eb902b8e719 ("net/ipv6: Remove expired routes with a
separated list of routes.") introduced a per-table GC list and
changed GC to iterate over that list instead of traversing
the entire route table.
However, it forgot to add permanent routes to tb6_gc_hlist
when exception routes are added.
Commit cfe82469a00f ("ipv6: add exception routes to GC list
in rt6_insert_exception") fixed that issue but introduced
another one.
Even after all exception routes expire, the permanent routes
remain in tb6_gc_hlist, potentially negating the performance
benefits intended by the initial change.
Let's count gc_args->more before and after rt6_age_exceptions()
and remove the permanent route when the delta is 0.
Note that the next patch will reuse fib6_age_exceptions().
Fixes: cfe82469a00f ("ipv6: add exception routes to GC list in rt6_insert_exception")
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Xin Long <lucien.xin@gmail.com>
---
net/ipv6/ip6_fib.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index 9058e71241dc..fadfca49d6b1 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -2348,6 +2348,17 @@ static void fib6_flush_trees(struct net *net)
/*
* Garbage collection
*/
+static void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args,
+ unsigned long now)
+{
+ bool may_expire = rt->fib6_flags & RTF_EXPIRES && rt->expires;
+ int old_more = gc_args->more;
+
+ rt6_age_exceptions(rt, gc_args, now);
+
+ if (!may_expire && old_more == gc_args->more)
+ fib6_remove_gc_list(rt);
+}
static int fib6_age(struct fib6_info *rt, struct fib6_gc_args *gc_args)
{
@@ -2370,7 +2381,7 @@ static int fib6_age(struct fib6_info *rt, struct fib6_gc_args *gc_args)
* Note, that clones are aged out
* only if they are not in use now.
*/
- rt6_age_exceptions(rt, gc_args, now);
+ fib6_age_exceptions(rt, gc_args, now);
return 0;
}
--
2.53.0.473.g4a7958ca14-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v4 net 2/2] ipv6: Don't remove permanent routes with exceptions from tb6_gc_hlist.
2026-03-11 5:29 [PATCH v4 net 0/2] ipv6: Fix two GC issues with permanent routes Kuniyuki Iwashima
2026-03-11 5:29 ` [PATCH v4 net 1/2] ipv6: Remove permanent routes from tb6_gc_hlist when all exceptions expire Kuniyuki Iwashima
@ 2026-03-11 5:29 ` Kuniyuki Iwashima
2026-03-13 15:03 ` [PATCH v4 net 0/2] ipv6: Fix two GC issues with permanent routes David Ahern
2 siblings, 0 replies; 6+ messages in thread
From: Kuniyuki Iwashima @ 2026-03-11 5:29 UTC (permalink / raw)
To: David Ahern, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: Kui-Feng Lee, Xin Long, Simon Horman, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev
The cited commit mechanically put fib6_remove_gc_list()
just after every fib6_clean_expires() call.
When a temporary route is promoted to a permanent route,
there may already be exception routes tied to it.
If fib6_remove_gc_list() removes the route from tb6_gc_hlist,
such exception routes will no longer be aged.
Let's replace fib6_remove_gc_list() with a new helper
fib6_may_remove_gc_list() and use fib6_age_exceptions() there.
Note that net->ipv6 is only compiled when CONFIG_IPV6 is
enabled, so fib6_{add,remove,may_remove}_gc_list() are guarded.
Fixes: 5eb902b8e719 ("net/ipv6: Remove expired routes with a separated list of routes.")
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
v4: s/,/;/ for fib6_gc_args initialisation
v3: Use IS_ENABLED()
v2: Fix build failure when CONFIG_IPV6=n (no net->ipv6 definition)
---
include/net/ip6_fib.h | 19 ++++++++++++++++++-
net/ipv6/addrconf.c | 4 ++--
net/ipv6/ip6_fib.c | 6 +++---
net/ipv6/route.c | 2 +-
4 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index 88b0dd4d8e09..438bcbc54a35 100644
--- a/include/net/ip6_fib.h
+++ b/include/net/ip6_fib.h
@@ -507,12 +507,14 @@ void fib6_rt_update(struct net *net, struct fib6_info *rt,
void inet6_rt_notify(int event, struct fib6_info *rt, struct nl_info *info,
unsigned int flags);
+void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args,
+ unsigned long now);
void fib6_run_gc(unsigned long expires, struct net *net, bool force);
-
void fib6_gc_cleanup(void);
int fib6_init(void);
+#if IS_ENABLED(CONFIG_IPV6)
/* Add the route to the gc list if it is not already there
*
* The callers should hold f6i->fib6_table->tb6_lock.
@@ -545,6 +547,21 @@ static inline void fib6_remove_gc_list(struct fib6_info *f6i)
hlist_del_init(&f6i->gc_link);
}
+static inline void fib6_may_remove_gc_list(struct net *net,
+ struct fib6_info *f6i)
+{
+ struct fib6_gc_args gc_args;
+
+ if (hlist_unhashed(&f6i->gc_link))
+ return;
+
+ gc_args.timeout = READ_ONCE(net->ipv6.sysctl.ip6_rt_gc_interval);
+ gc_args.more = 0;
+
+ fib6_age_exceptions(f6i, &gc_args, jiffies);
+}
+#endif
+
struct ipv6_route_iter {
struct seq_net_private p;
struct fib6_walker w;
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 0e55f139e05d..f4e23b543585 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -2862,7 +2862,7 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
fib6_add_gc_list(rt);
} else {
fib6_clean_expires(rt);
- fib6_remove_gc_list(rt);
+ fib6_may_remove_gc_list(net, rt);
}
spin_unlock_bh(&table->tb6_lock);
@@ -4840,7 +4840,7 @@ static int modify_prefix_route(struct net *net, struct inet6_ifaddr *ifp,
if (!(flags & RTF_EXPIRES)) {
fib6_clean_expires(f6i);
- fib6_remove_gc_list(f6i);
+ fib6_may_remove_gc_list(net, f6i);
} else {
fib6_set_expires(f6i, expires);
fib6_add_gc_list(f6i);
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index fadfca49d6b1..dd26657b6a4a 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -1133,7 +1133,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt,
return -EEXIST;
if (!(rt->fib6_flags & RTF_EXPIRES)) {
fib6_clean_expires(iter);
- fib6_remove_gc_list(iter);
+ fib6_may_remove_gc_list(info->nl_net, iter);
} else {
fib6_set_expires(iter, rt->expires);
fib6_add_gc_list(iter);
@@ -2348,8 +2348,8 @@ static void fib6_flush_trees(struct net *net)
/*
* Garbage collection
*/
-static void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args,
- unsigned long now)
+void fib6_age_exceptions(struct fib6_info *rt, struct fib6_gc_args *gc_args,
+ unsigned long now)
{
bool may_expire = rt->fib6_flags & RTF_EXPIRES && rt->expires;
int old_more = gc_args->more;
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 08cd86f49bf9..cb521700cee7 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -1033,7 +1033,7 @@ int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
if (!addrconf_finite_timeout(lifetime)) {
fib6_clean_expires(rt);
- fib6_remove_gc_list(rt);
+ fib6_may_remove_gc_list(net, rt);
} else {
fib6_set_expires(rt, jiffies + HZ * lifetime);
fib6_add_gc_list(rt);
--
2.53.0.473.g4a7958ca14-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread