[PATCH RFC net-next 2/3] ipv6: hash uncached routes by device
From: Chris J Arges
Date: Wed Aug 26 2026 - 16:20:55 EST
rt6_uncached_list_flush_dev() currently walks every per-CPU uncached route
list for each device being removed. Hash uncached routes by their inet6
device so ordinary device teardown only visits the matching bucket on each
CPU.
ip6_rt_get_dev_rcu() can return loopback or an L3 master while rt6i_idev
still refers to the original interface, so such a route must be reachable
from either device. Place those routes on a separate per-CPU list that is
always visited in addition to the keyed bucket.
This avoids growing struct rt6_info while filtering most unrelated routes
from ordinary device teardown.
Signed-off-by: Chris J Arges <carges@xxxxxxxxxxxxxx>
---
net/ipv6/route.c | 102 +++++++++++++++++++++++++++++++++++++------------------
1 file changed, 69 insertions(+), 33 deletions(-)
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 16dfac54a259..860530074027 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -40,6 +40,7 @@
#include <linux/seq_file.h>
#include <linux/nsproxy.h>
#include <linux/slab.h>
+#include <linux/hash.h>
#include <linux/jhash.h>
#include <linux/siphash.h>
#include <net/net_namespace.h>
@@ -133,11 +134,28 @@ struct uncached_list {
struct list_head head;
};
-static DEFINE_PER_CPU_ALIGNED(struct uncached_list, rt6_uncached_list);
+#define RT6_UNCACHED_HASH_BITS 6
+#define RT6_UNCACHED_HASH_SIZE BIT(RT6_UNCACHED_HASH_BITS)
+
+struct rt6_uncached_table {
+ struct uncached_list buckets[RT6_UNCACHED_HASH_SIZE];
+ /* Routes that must be discoverable through two different devices. */
+ struct uncached_list mismatch;
+};
+
+static DEFINE_PER_CPU_ALIGNED(struct rt6_uncached_table, rt6_uncached_table);
void rt6_uncached_list_add(struct rt6_info *rt)
{
- struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list);
+ struct rt6_uncached_table *table = raw_cpu_ptr(&rt6_uncached_table);
+ struct net_device *rt_dev = dst_dev(&rt->dst);
+ struct uncached_list *ul;
+
+ if (rt->rt6i_idev && rt->rt6i_idev->dev != rt_dev)
+ ul = &table->mismatch;
+ else
+ ul = &table->buckets[hash_ptr(rt_dev,
+ RT6_UNCACHED_HASH_BITS)];
rt->dst.rt_uncached_list = ul;
@@ -157,40 +175,50 @@ void rt6_uncached_list_del(struct rt6_info *rt)
}
}
+static void rt6_uncached_list_flush(struct uncached_list *ul,
+ struct net_device *dev)
+{
+ struct rt6_info *rt, *safe;
+
+ if (list_empty(&ul->head))
+ return;
+
+ spin_lock_bh(&ul->lock);
+ list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) {
+ struct inet6_dev *rt_idev = rt->rt6i_idev;
+ struct net_device *rt_dev = dst_dev(&rt->dst);
+ bool handled = false;
+
+ if (rt_idev && rt_idev->dev == dev) {
+ rt->rt6i_idev = in6_dev_get(blackhole_netdev);
+ in6_dev_put(rt_idev);
+ handled = true;
+ }
+
+ if (rt_dev == dev) {
+ rt->dst.dev = blackhole_netdev;
+ netdev_ref_replace(rt_dev, blackhole_netdev,
+ &rt->dst.dev_tracker, GFP_ATOMIC);
+ handled = true;
+ }
+ if (handled)
+ list_del_init(&rt->dst.rt_uncached);
+ }
+ spin_unlock_bh(&ul->lock);
+}
+
static void rt6_uncached_list_flush_dev(struct net_device *dev)
{
int cpu;
for_each_possible_cpu(cpu) {
- struct uncached_list *ul = per_cpu_ptr(&rt6_uncached_list, cpu);
- struct rt6_info *rt, *safe;
+ struct rt6_uncached_table *table;
+ struct uncached_list *ul;
- if (list_empty(&ul->head))
- continue;
-
- spin_lock_bh(&ul->lock);
- list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) {
- struct inet6_dev *rt_idev = rt->rt6i_idev;
- struct net_device *rt_dev = rt->dst.dev;
- bool handled = false;
-
- if (rt_idev && rt_idev->dev == dev) {
- rt->rt6i_idev = in6_dev_get(blackhole_netdev);
- in6_dev_put(rt_idev);
- handled = true;
- }
-
- if (rt_dev == dev) {
- rt->dst.dev = blackhole_netdev;
- netdev_ref_replace(rt_dev, blackhole_netdev,
- &rt->dst.dev_tracker,
- GFP_ATOMIC);
- handled = true;
- }
- if (handled)
- list_del_init(&rt->dst.rt_uncached);
- }
- spin_unlock_bh(&ul->lock);
+ table = per_cpu_ptr(&rt6_uncached_table, cpu);
+ ul = &table->buckets[hash_ptr(dev, RT6_UNCACHED_HASH_BITS)];
+ rt6_uncached_list_flush(ul, dev);
+ rt6_uncached_list_flush(&table->mismatch, dev);
}
}
@@ -6982,10 +7010,18 @@ int __init ip6_route_init(void)
#endif
for_each_possible_cpu(cpu) {
- struct uncached_list *ul = per_cpu_ptr(&rt6_uncached_list, cpu);
+ struct rt6_uncached_table *table;
+ int bucket;
+
+ table = per_cpu_ptr(&rt6_uncached_table, cpu);
+ for (bucket = 0; bucket < RT6_UNCACHED_HASH_SIZE; bucket++) {
+ struct uncached_list *ul = &table->buckets[bucket];
- INIT_LIST_HEAD(&ul->head);
- spin_lock_init(&ul->lock);
+ INIT_LIST_HEAD(&ul->head);
+ spin_lock_init(&ul->lock);
+ }
+ INIT_LIST_HEAD(&table->mismatch.head);
+ spin_lock_init(&table->mismatch.lock);
}
out:
--
2.43.0