* [PATCH v1 net-next 0/6] ip6_gre: Support per-netns device unregistration.
@ 2026-09-16 23:02 Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 1/6] ip6_gre: Initialise ign->tunnels_wc[0] before register_netdev() Kuniyuki Iwashima
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-16 23:02 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
Patch 1 & 2 convert ip6gre_net.tunnels[][] to hlist as a prep.
Patch 3 & 4 unlink devices from ip6gre_net.tunnels[][] in
->dellink().
Patch 5 & 6 adds mutex and support per-netns device unregistration.
Kuniyuki Iwashima (6):
ip6_gre: Initialise ign->tunnels_wc[0] before register_netdev().
ip6_gre: Convert ip6gre_net.tunnels[][] to hlist.
ip6_gre: Clear ign->fb_tunnel_dev in ip6gre_exit_rtnl_net().
ip6_gre: Unlink ip6gre_tunnel_unlink() from ->dellink().
ip6_gre: Protect ip6gre_net.tunnels[][] with mutex.
ip6_gre: Support per-netns device unregistration.
include/net/ip6_tunnel.h | 1 +
net/ipv6/ip6_gre.c | 268 +++++++++++++++++++++++++--------------
2 files changed, 177 insertions(+), 92 deletions(-)
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v1 net-next 1/6] ip6_gre: Initialise ign->tunnels_wc[0] before register_netdev().
2026-09-16 23:02 [PATCH v1 net-next 0/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
@ 2026-09-16 23:02 ` Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 2/6] ip6_gre: Convert ip6gre_net.tunnels[][] to hlist Kuniyuki Iwashima
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-16 23:02 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
ip6gre_init_net() links the fallback device ip6gre0 to
ign->tunnels_wc[0] after publishing it by register_netdev().
While ip6_gre module is being loaded, there is a small race window.
If ioctl(SIOCADDTUNNEL) is called before ip6gre0 is linked to
the hash table, a new device could be linked to ign->tunnels_wc[0].
Then, the fallback device overwrites ign->tunnels_wc[0], and the new
device will be no longer found by ip6gre_tunnel_lookup(), resulting
in no RX.
Let's link the fallback device before register_netdev().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
day 1 bug since 2012, not worth backporting, but if we want
Fixes: c12b395a4664 ("gre: Support GRE over IPv6")
---
net/ipv6/ip6_gre.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index 8ebda0b6a78b..183218100d69 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -1607,12 +1607,13 @@ static int __net_init ip6gre_init_net(struct net *net)
ip6gre_fb_tunnel_init(ign->fb_tunnel_dev);
ign->fb_tunnel_dev->rtnl_link_ops = &ip6gre_link_ops;
+ rcu_assign_pointer(ign->tunnels_wc[0],
+ netdev_priv(ign->fb_tunnel_dev));
+
err = register_netdev(ign->fb_tunnel_dev);
if (err)
goto err_reg_dev;
- rcu_assign_pointer(ign->tunnels_wc[0],
- netdev_priv(ign->fb_tunnel_dev));
return 0;
err_reg_dev:
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v1 net-next 2/6] ip6_gre: Convert ip6gre_net.tunnels[][] to hlist.
2026-09-16 23:02 [PATCH v1 net-next 0/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 1/6] ip6_gre: Initialise ign->tunnels_wc[0] before register_netdev() Kuniyuki Iwashima
@ 2026-09-16 23:02 ` Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 3/6] ip6_gre: Clear ign->fb_tunnel_dev in ip6gre_exit_rtnl_net() Kuniyuki Iwashima
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-16 23:02 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
struct ip6_tnl has a pointer to the next one.
Single linked list requires O(n) iteration for device removal
and IPv4 tunnel already uses hlist for its hash table.
Let's convert ip6gre_net.tunnels[][] to hlist.
Once we convert other users (ip6_tunnel.c, sit.c), we can remove
the *next pointer from struct ip6_tnl.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/net/ip6_tunnel.h | 1 +
net/ipv6/ip6_gre.c | 91 +++++++++++++++++++++-------------------
2 files changed, 48 insertions(+), 44 deletions(-)
diff --git a/include/net/ip6_tunnel.h b/include/net/ip6_tunnel.h
index b99805ee2fd1..d1f0a427e9c8 100644
--- a/include/net/ip6_tunnel.h
+++ b/include/net/ip6_tunnel.h
@@ -45,6 +45,7 @@ struct __ip6_tnl_parm {
/* IPv6 tunnel */
struct ip6_tnl {
struct ip6_tnl __rcu *next; /* next tunnel in list */
+ struct hlist_node hash_node;
struct net_device *dev; /* virtual device associated with tunnel */
netdevice_tracker dev_tracker;
struct net *net; /* netns for packet i/o */
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index 183218100d69..d3a879fa296e 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -64,7 +64,7 @@ MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN");
static unsigned int ip6gre_net_id __read_mostly;
struct ip6gre_net {
- struct ip6_tnl __rcu *tunnels[4][IP6_GRE_HASH_SIZE];
+ struct hlist_head tunnels[4][IP6_GRE_HASH_SIZE];
struct ip6_tnl __rcu *collect_md_tun;
struct ip6_tnl __rcu *collect_md_tun_erspan;
@@ -141,20 +141,24 @@ static struct ip6_tnl *ip6gre_tunnel_lookup(struct net_device *dev,
const struct in6_addr *remote, const struct in6_addr *local,
__be32 key, __be16 gre_proto)
{
- struct net *net = dev_net(dev);
- int link = dev->ifindex;
- unsigned int h0 = HASH_ADDR(remote);
- unsigned int h1 = HASH_KEY(key);
- struct ip6_tnl *t, *cand = NULL;
- struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);
int dev_type = (gre_proto == htons(ETH_P_TEB) ||
gre_proto == htons(ETH_P_ERSPAN) ||
gre_proto == htons(ETH_P_ERSPAN2)) ?
ARPHRD_ETHER : ARPHRD_IP6GRE;
+ unsigned int h0 = HASH_ADDR(remote);
+ unsigned int h1 = HASH_KEY(key);
+ struct ip6_tnl *t, *cand = NULL;
+ struct net *net = dev_net(dev);
struct net_device *ndev;
+ struct hlist_head *head;
+ int link = dev->ifindex;
+ struct ip6gre_net *ign;
int cand_score = 4;
- for_each_ip_tunnel_rcu(t, ign->tunnels_r_l[h0 ^ h1]) {
+ ign = net_generic(net, ip6gre_net_id);
+
+ head = &ign->tunnels_r_l[h0 ^ h1];
+ hlist_for_each_entry_rcu(t, head, hash_node) {
if (!ipv6_addr_equal(local, &t->parms.laddr) ||
!ipv6_addr_equal(remote, &t->parms.raddr) ||
key != t->parms.i_key ||
@@ -165,7 +169,8 @@ static struct ip6_tnl *ip6gre_tunnel_lookup(struct net_device *dev,
return cand;
}
- for_each_ip_tunnel_rcu(t, ign->tunnels_r[h0 ^ h1]) {
+ head = &ign->tunnels_r[h0 ^ h1];
+ hlist_for_each_entry_rcu(t, head, hash_node) {
if (!ipv6_addr_equal(remote, &t->parms.raddr) ||
key != t->parms.i_key ||
!(t->dev->flags & IFF_UP))
@@ -175,7 +180,8 @@ static struct ip6_tnl *ip6gre_tunnel_lookup(struct net_device *dev,
return cand;
}
- for_each_ip_tunnel_rcu(t, ign->tunnels_l[h1]) {
+ head = &ign->tunnels_l[h1];
+ hlist_for_each_entry_rcu(t, head, hash_node) {
if ((!ipv6_addr_equal(local, &t->parms.laddr) &&
(!ipv6_addr_equal(local, &t->parms.raddr) ||
!ipv6_addr_is_multicast(local))) ||
@@ -187,7 +193,8 @@ static struct ip6_tnl *ip6gre_tunnel_lookup(struct net_device *dev,
return cand;
}
- for_each_ip_tunnel_rcu(t, ign->tunnels_wc[h1]) {
+ head = &ign->tunnels_wc[h1];
+ hlist_for_each_entry_rcu(t, head, hash_node) {
if (t->parms.i_key != key ||
!(t->dev->flags & IFF_UP))
continue;
@@ -215,8 +222,8 @@ static struct ip6_tnl *ip6gre_tunnel_lookup(struct net_device *dev,
return NULL;
}
-static struct ip6_tnl __rcu **__ip6gre_bucket(struct ip6gre_net *ign,
- const struct __ip6_tnl_parm *p)
+static struct hlist_head *__ip6gre_bucket(struct ip6gre_net *ign,
+ const struct __ip6_tnl_parm *p)
{
const struct in6_addr *remote = &p->raddr;
const struct in6_addr *local = &p->laddr;
@@ -258,56 +265,46 @@ static void ip6erspan_tunnel_unlink_md(struct ip6gre_net *ign,
rcu_assign_pointer(ign->collect_md_tun_erspan, NULL);
}
-static inline struct ip6_tnl __rcu **ip6gre_bucket(struct ip6gre_net *ign,
- const struct ip6_tnl *t)
+static inline struct hlist_head *ip6gre_bucket(struct ip6gre_net *ign,
+ const struct ip6_tnl *t)
{
return __ip6gre_bucket(ign, &t->parms);
}
static void ip6gre_tunnel_link(struct ip6gre_net *ign, struct ip6_tnl *t)
{
- struct ip6_tnl __rcu **tp = ip6gre_bucket(ign, t);
+ struct hlist_head *head = ip6gre_bucket(ign, t);
- rcu_assign_pointer(t->next, rtnl_dereference(*tp));
- rcu_assign_pointer(*tp, t);
+ hlist_add_head_rcu(&t->hash_node, head);
}
static void ip6gre_tunnel_unlink(struct ip6gre_net *ign, struct ip6_tnl *t)
{
- struct ip6_tnl __rcu **tp;
- struct ip6_tnl *iter;
-
- for (tp = ip6gre_bucket(ign, t);
- (iter = rtnl_dereference(*tp)) != NULL;
- tp = &iter->next) {
- if (t == iter) {
- rcu_assign_pointer(*tp, t->next);
- break;
- }
- }
+ hlist_del_init_rcu(&t->hash_node);
}
static struct ip6_tnl *ip6gre_tunnel_find(struct net *net,
const struct __ip6_tnl_parm *parms,
int type)
{
+ struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);
const struct in6_addr *remote = &parms->raddr;
const struct in6_addr *local = &parms->laddr;
__be32 key = parms->i_key;
+ struct hlist_head *head;
int link = parms->link;
struct ip6_tnl *t;
- struct ip6_tnl __rcu **tp;
- struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);
- for (tp = __ip6gre_bucket(ign, parms);
- (t = rtnl_dereference(*tp)) != NULL;
- tp = &t->next)
+ head = __ip6gre_bucket(ign, parms);
+
+ hlist_for_each_entry(t, head, hash_node) {
if (ipv6_addr_equal(local, &t->parms.laddr) &&
ipv6_addr_equal(remote, &t->parms.raddr) &&
key == t->parms.i_key &&
link == t->parms.link &&
type == t->dev->type)
break;
+ }
return t;
}
@@ -1551,7 +1548,8 @@ static struct inet6_protocol ip6gre_protocol __read_mostly = {
.flags = INET6_PROTO_FINAL,
};
-static void __net_exit ip6gre_exit_rtnl_net(struct net *net, struct list_head *head)
+static void __net_exit ip6gre_exit_rtnl_net(struct net *net,
+ struct list_head *dev_kill_list)
{
struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);
struct net_device *dev, *aux;
@@ -1561,23 +1559,21 @@ static void __net_exit ip6gre_exit_rtnl_net(struct net *net, struct list_head *h
if (dev->rtnl_link_ops == &ip6gre_link_ops ||
dev->rtnl_link_ops == &ip6gre_tap_ops ||
dev->rtnl_link_ops == &ip6erspan_tap_ops)
- unregister_netdevice_queue(dev, head);
+ unregister_netdevice_queue(dev, dev_kill_list);
for (prio = 0; prio < 4; prio++) {
int h;
+
for (h = 0; h < IP6_GRE_HASH_SIZE; h++) {
+ struct hlist_head *head = &ign->tunnels[prio][h];
struct ip6_tnl *t;
- t = rtnl_net_dereference(net, ign->tunnels[prio][h]);
-
- while (t) {
+ hlist_for_each_entry(t, head, hash_node) {
/* If dev is in the same netns, it has already
* been added to the list by the previous loop.
*/
if (!net_eq(dev_net(t->dev), net))
- unregister_netdevice_queue(t->dev, head);
-
- t = rtnl_net_dereference(net, t->next);
+ unregister_netdevice_queue(t->dev, dev_kill_list);
}
}
}
@@ -1587,8 +1583,15 @@ static int __net_init ip6gre_init_net(struct net *net)
{
struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);
struct net_device *ndev;
+ struct ip6_tnl *t;
+ int prio, h;
int err;
+ for (prio = 0; prio < 4; prio++) {
+ for (h = 0; h < IP6_GRE_HASH_SIZE; h++)
+ INIT_HLIST_HEAD(&ign->tunnels[prio][h]);
+ }
+
if (!net_has_fallback_tunnels(net))
return 0;
ndev = alloc_netdev(sizeof(struct ip6_tnl), "ip6gre0",
@@ -1607,8 +1610,8 @@ static int __net_init ip6gre_init_net(struct net *net)
ip6gre_fb_tunnel_init(ign->fb_tunnel_dev);
ign->fb_tunnel_dev->rtnl_link_ops = &ip6gre_link_ops;
- rcu_assign_pointer(ign->tunnels_wc[0],
- netdev_priv(ign->fb_tunnel_dev));
+ t = netdev_priv(ign->fb_tunnel_dev);
+ hlist_add_head_rcu(&t->hash_node, &ign->tunnels_wc[0]);
err = register_netdev(ign->fb_tunnel_dev);
if (err)
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v1 net-next 3/6] ip6_gre: Clear ign->fb_tunnel_dev in ip6gre_exit_rtnl_net().
2026-09-16 23:02 [PATCH v1 net-next 0/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 1/6] ip6_gre: Initialise ign->tunnels_wc[0] before register_netdev() Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 2/6] ip6_gre: Convert ip6gre_net.tunnels[][] to hlist Kuniyuki Iwashima
@ 2026-09-16 23:02 ` Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 4/6] ip6_gre: Unlink ip6gre_tunnel_unlink() from ->dellink() Kuniyuki Iwashima
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-16 23:02 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
ip6gre_dellink() ignores itn->fb_tunnel_dev, so the per-netns
fallback tunnel device cannot be removed by userspace.
This also makes default_device_exit_batch() impossible to remove
the device since it calls ->dellink().
So, ip6gre_exit_rtnl_net() has to iterate devices in the dying
netns and call unregister_netdevice_queue() directly, and
ip6gre_tunnel_unlink() must be called at ->ndo_uninit().
For per-netns device unregistration, we want to check if the
device is being removed in ->dellink(), for which we must call
ip6gre_tunnel_unlink() before unregister_netdevice_queue().
As a prep, let's set itn->fb_tunnel_dev to NULL and remove
remove for_each_netdev_safe() in ip6gre_exit_rtnl_net().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/ipv6/ip6_gre.c | 19 ++++---------------
1 file changed, 4 insertions(+), 15 deletions(-)
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index d3a879fa296e..d5d98474aeb5 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -374,8 +374,6 @@ static void ip6gre_tunnel_uninit(struct net_device *dev)
ip6gre_tunnel_unlink_md(ign, t);
ip6gre_tunnel_unlink(ign, t);
- if (ign->fb_tunnel_dev == dev)
- WRITE_ONCE(ign->fb_tunnel_dev, NULL);
dst_cache_reset(&t->dst_cache);
netdev_put(dev, &t->dev_tracker);
}
@@ -1552,29 +1550,20 @@ static void __net_exit ip6gre_exit_rtnl_net(struct net *net,
struct list_head *dev_kill_list)
{
struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);
- struct net_device *dev, *aux;
int prio;
- for_each_netdev_safe(net, dev, aux)
- if (dev->rtnl_link_ops == &ip6gre_link_ops ||
- dev->rtnl_link_ops == &ip6gre_tap_ops ||
- dev->rtnl_link_ops == &ip6erspan_tap_ops)
- unregister_netdevice_queue(dev, dev_kill_list);
+ WRITE_ONCE(ign->fb_tunnel_dev, NULL);
for (prio = 0; prio < 4; prio++) {
int h;
for (h = 0; h < IP6_GRE_HASH_SIZE; h++) {
struct hlist_head *head = &ign->tunnels[prio][h];
+ struct hlist_node *tmp;
struct ip6_tnl *t;
- hlist_for_each_entry(t, head, hash_node) {
- /* If dev is in the same netns, it has already
- * been added to the list by the previous loop.
- */
- if (!net_eq(dev_net(t->dev), net))
- unregister_netdevice_queue(t->dev, dev_kill_list);
- }
+ hlist_for_each_entry_safe(t, tmp, head, hash_node)
+ unregister_netdevice_queue(t->dev, dev_kill_list);
}
}
}
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v1 net-next 4/6] ip6_gre: Unlink ip6gre_tunnel_unlink() from ->dellink().
2026-09-16 23:02 [PATCH v1 net-next 0/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
` (2 preceding siblings ...)
2026-09-16 23:02 ` [PATCH v1 net-next 3/6] ip6_gre: Clear ign->fb_tunnel_dev in ip6gre_exit_rtnl_net() Kuniyuki Iwashima
@ 2026-09-16 23:02 ` Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 5/6] ip6_gre: Protect ip6gre_net.tunnels[][] with mutex Kuniyuki Iwashima
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-16 23:02 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
With the previous patch, itn->fb_tunnel_dev can be removed
via ->dellink().
However, ioctl(SIOCDELTUNNEL) still uses unregister_netdevice(),
which requires ip6gre_tunnel_unlink() in ip6gre_tunnel_uninit().
Let's use __ip6gre_dellink() everywhere to remove ip6gre (and
variant) devices and remove ip6gre_tunnel_unlink() and
ip6gre_tunnel_unlink_md() in ip6gre_tunnel_uninit().
ip6erspan_tunnel_uninit() is also changed accordingly.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/ipv6/ip6_gre.c | 50 +++++++++++++++++++++++++++++++---------------
1 file changed, 34 insertions(+), 16 deletions(-)
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index d5d98474aeb5..a9059f3841ac 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -79,6 +79,7 @@ static void ip6gre_tunnel_setup(struct net_device *dev);
static void ip6gre_tunnel_link(struct ip6gre_net *ign, struct ip6_tnl *t);
static void ip6gre_tnl_link_config(struct ip6_tnl *t, int set_mtu);
static void ip6erspan_tnl_link_config(struct ip6_tnl *t, int set_mtu);
+static void __ip6gre_dellink(struct net_device *dev, struct list_head *head);
/* Tunnel hash table */
@@ -359,10 +360,7 @@ static struct ip6_tnl *ip6gre_tunnel_locate(struct net *net,
static void ip6erspan_tunnel_uninit(struct net_device *dev)
{
struct ip6_tnl *t = netdev_priv(dev);
- struct ip6gre_net *ign = net_generic(t->net, ip6gre_net_id);
- ip6erspan_tunnel_unlink_md(ign, t);
- ip6gre_tunnel_unlink(ign, t);
dst_cache_reset(&t->dst_cache);
netdev_put(dev, &t->dev_tracker);
}
@@ -370,10 +368,7 @@ static void ip6erspan_tunnel_uninit(struct net_device *dev)
static void ip6gre_tunnel_uninit(struct net_device *dev)
{
struct ip6_tnl *t = netdev_priv(dev);
- struct ip6gre_net *ign = net_generic(t->net, ip6gre_net_id);
- ip6gre_tunnel_unlink_md(ign, t);
- ip6gre_tunnel_unlink(ign, t);
dst_cache_reset(&t->dst_cache);
netdev_put(dev, &t->dev_tracker);
}
@@ -1251,14 +1246,15 @@ static int ip6gre_tunnel_siocdevprivate(struct net_device *dev,
struct ifreq *ifr, void __user *data,
int cmd)
{
- int err = 0;
- struct ip6_tnl_parm2 p;
- struct __ip6_tnl_parm p1;
struct ip6_tnl *t = netdev_priv(dev);
+ struct __ip6_tnl_parm p1 = {};
+ LIST_HEAD(dev_kill_list);
struct net *net = t->net;
- struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);
+ struct ip6_tnl_parm2 p;
+ struct ip6gre_net *ign;
+ int err = 0;
- memset(&p1, 0, sizeof(p1));
+ ign = net_generic(net, ip6gre_net_id);
switch (cmd) {
case SIOCGETTUNNEL:
@@ -1347,7 +1343,8 @@ static int ip6gre_tunnel_siocdevprivate(struct net_device *dev,
goto done;
dev = t->dev;
}
- unregister_netdevice(dev);
+
+ __ip6gre_dellink(dev, &dev_kill_list);
err = 0;
break;
@@ -1356,6 +1353,7 @@ static int ip6gre_tunnel_siocdevprivate(struct net_device *dev,
}
done:
+ unregister_netdevice_many(&dev_kill_list);
return err;
}
@@ -1563,7 +1561,7 @@ static void __net_exit ip6gre_exit_rtnl_net(struct net *net,
struct ip6_tnl *t;
hlist_for_each_entry_safe(t, tmp, head, hash_node)
- unregister_netdevice_queue(t->dev, dev_kill_list);
+ __ip6gre_dellink(t->dev, dev_kill_list);
}
}
}
@@ -2056,13 +2054,31 @@ static int ip6gre_changelink(struct net_device *dev, struct nlattr *tb[],
return 0;
}
+static void __ip6gre_dellink(struct net_device *dev, struct list_head *head)
+{
+ struct ip6_tnl *t = netdev_priv(dev);
+ struct ip6gre_net *ign;
+
+ ign = net_generic(t->net, ip6gre_net_id);
+
+ if (dev->rtnl_link_ops == &ip6erspan_tap_ops)
+ ip6erspan_tunnel_unlink_md(ign, t);
+ else
+ ip6gre_tunnel_unlink_md(ign, t);
+
+ ip6gre_tunnel_unlink(ign, t);
+ unregister_netdevice_queue(dev, head);
+}
+
static void ip6gre_dellink(struct net_device *dev, struct list_head *head)
{
- struct net *net = dev_net(dev);
- struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);
+ struct ip6_tnl *t = netdev_priv(dev);
+ struct ip6gre_net *ign;
+
+ ign = net_generic(t->net, ip6gre_net_id);
if (dev != ign->fb_tunnel_dev)
- unregister_netdevice_queue(dev, head);
+ __ip6gre_dellink(dev, head);
}
static size_t ip6gre_get_size(const struct net_device *dev)
@@ -2304,6 +2320,7 @@ static struct rtnl_link_ops ip6gre_tap_ops __read_mostly = {
.validate = ip6gre_tap_validate,
.newlink = ip6gre_newlink,
.changelink = ip6gre_changelink,
+ .dellink = ip6gre_dellink,
.get_size = ip6gre_get_size,
.fill_info = ip6gre_fill_info,
.get_link_net = ip6_tnl_get_link_net,
@@ -2318,6 +2335,7 @@ static struct rtnl_link_ops ip6erspan_tap_ops __read_mostly = {
.validate = ip6erspan_tap_validate,
.newlink = ip6erspan_newlink,
.changelink = ip6erspan_changelink,
+ .dellink = ip6gre_dellink,
.get_size = ip6gre_get_size,
.fill_info = ip6gre_fill_info,
.get_link_net = ip6_tnl_get_link_net,
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v1 net-next 5/6] ip6_gre: Protect ip6gre_net.tunnels[][] with mutex.
2026-09-16 23:02 [PATCH v1 net-next 0/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
` (3 preceding siblings ...)
2026-09-16 23:02 ` [PATCH v1 net-next 4/6] ip6_gre: Unlink ip6gre_tunnel_unlink() from ->dellink() Kuniyuki Iwashima
@ 2026-09-16 23:02 ` Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 6/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
2026-09-19 1:20 ` [PATCH v1 net-next 0/6] " patchwork-bot+netdevbpf
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-16 23:02 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
struct ip6_tnl.net is the netns where encapsulated packets
flow into.
struct ip6_tnl is linked to ip6gre_net.tunnels[][] of netns.
During netns dismantle or module unload, ip6gre_exit_rtnl_net()
iterates the list and queues devices for destruction regardless
of the devices' netns.
Thus, once RTNL is removed, the list can be modified concurrently
from different netns due to device removal.
Let's protect it with per-netns mutex.
Note that dev_siocdevprivate() calls netdev_lock_ops() but
it must be NOP for tunnel devices to avoid AB-BA deadlock.
DEBUG_NET_WARN_ON_ONCE() is added to annotate the locking
explicitly.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/ipv6/ip6_gre.c | 78 ++++++++++++++++++++++++++++++++++++++--------
1 file changed, 65 insertions(+), 13 deletions(-)
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index a9059f3841ac..ac1087330465 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -65,6 +65,7 @@ MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN");
static unsigned int ip6gre_net_id __read_mostly;
struct ip6gre_net {
struct hlist_head tunnels[4][IP6_GRE_HASH_SIZE];
+ struct mutex tunnels_lock;
struct ip6_tnl __rcu *collect_md_tun;
struct ip6_tnl __rcu *collect_md_tun_erspan;
@@ -1254,8 +1255,12 @@ static int ip6gre_tunnel_siocdevprivate(struct net_device *dev,
struct ip6gre_net *ign;
int err = 0;
+ DEBUG_NET_WARN_ON_ONCE(netdev_need_ops_lock(dev));
+
ign = net_generic(net, ip6gre_net_id);
+ mutex_lock(&ign->tunnels_lock);
+
switch (cmd) {
case SIOCGETTUNNEL:
if (dev == ign->fb_tunnel_dev) {
@@ -1353,6 +1358,8 @@ static int ip6gre_tunnel_siocdevprivate(struct net_device *dev,
}
done:
+ mutex_unlock(&ign->tunnels_lock);
+
unregister_netdevice_many(&dev_kill_list);
return err;
}
@@ -1552,6 +1559,8 @@ static void __net_exit ip6gre_exit_rtnl_net(struct net *net,
WRITE_ONCE(ign->fb_tunnel_dev, NULL);
+ mutex_lock(&ign->tunnels_lock);
+
for (prio = 0; prio < 4; prio++) {
int h;
@@ -1564,6 +1573,8 @@ static void __net_exit ip6gre_exit_rtnl_net(struct net *net,
__ip6gre_dellink(t->dev, dev_kill_list);
}
}
+
+ mutex_unlock(&ign->tunnels_lock);
}
static int __net_init ip6gre_init_net(struct net *net)
@@ -1579,6 +1590,8 @@ static int __net_init ip6gre_init_net(struct net *net)
INIT_HLIST_HEAD(&ign->tunnels[prio][h]);
}
+ mutex_init(&ign->tunnels_lock);
+
if (!net_has_fallback_tunnels(net))
return 0;
ndev = alloc_netdev(sizeof(struct ip6_tnl), "ip6gre0",
@@ -1975,18 +1988,22 @@ static int ip6gre_newlink(struct net_device *dev,
struct nlattr **data = params->data;
struct nlattr **tb = params->tb;
struct ip6gre_net *ign;
- int err;
+ int err = 0;
ip6gre_netlink_parms(data, &nt->parms);
ign = net_generic(net, ip6gre_net_id);
+ mutex_lock(&ign->tunnels_lock);
+
if (nt->parms.collect_md) {
if (rtnl_dereference(ign->collect_md_tun))
- return -EEXIST;
+ err = -EEXIST;
} else {
if (ip6gre_tunnel_find(net, &nt->parms, dev->type))
- return -EEXIST;
+ err = -EEXIST;
}
+ if (err)
+ goto unlock;
err = ip6gre_newlink_common(net, dev, tb, data, extack);
if (!err) {
@@ -1994,6 +2011,10 @@ static int ip6gre_newlink(struct net_device *dev,
ip6gre_tunnel_link_md(ign, nt);
ip6gre_tunnel_link(net_generic(net, ip6gre_net_id), nt);
}
+
+unlock:
+ mutex_unlock(&ign->tunnels_lock);
+
return err;
}
@@ -2036,22 +2057,32 @@ static int ip6gre_changelink(struct net_device *dev, struct nlattr *tb[],
struct netlink_ext_ack *extack)
{
struct ip6_tnl *t = netdev_priv(dev);
- struct ip6gre_net *ign = net_generic(t->net, ip6gre_net_id);
struct __ip6_tnl_parm p;
+ struct ip6gre_net *ign;
+ int err = 0;
+
+ ign = net_generic(t->net, ip6gre_net_id);
if (!rtnl_dev_link_net_capable(dev, t->net))
return -EPERM;
+ mutex_lock(&ign->tunnels_lock);
+
t = ip6gre_changelink_common(dev, tb, data, &p, extack);
- if (IS_ERR(t))
- return PTR_ERR(t);
+ if (IS_ERR(t)) {
+ err = PTR_ERR(t);
+ goto unlock;
+ }
ip6gre_tunnel_unlink_md(ign, t);
ip6gre_tunnel_unlink(ign, t);
ip6gre_tnl_change(t, &p, !tb[IFLA_MTU]);
ip6gre_tunnel_link_md(ign, t);
ip6gre_tunnel_link(ign, t);
- return 0;
+unlock:
+ mutex_unlock(&ign->tunnels_lock);
+
+ return err;
}
static void __ip6gre_dellink(struct net_device *dev, struct list_head *head)
@@ -2077,8 +2108,12 @@ static void ip6gre_dellink(struct net_device *dev, struct list_head *head)
ign = net_generic(t->net, ip6gre_net_id);
+ mutex_lock(&ign->tunnels_lock);
+
if (dev != ign->fb_tunnel_dev)
__ip6gre_dellink(dev, head);
+
+ mutex_unlock(&ign->tunnels_lock);
}
static size_t ip6gre_get_size(const struct net_device *dev)
@@ -2234,19 +2269,23 @@ static int ip6erspan_newlink(struct net_device *dev,
struct nlattr **data = params->data;
struct nlattr **tb = params->tb;
struct ip6gre_net *ign;
- int err;
+ int err = 0;
ip6gre_netlink_parms(data, &nt->parms);
ip6erspan_set_version(data, &nt->parms);
ign = net_generic(net, ip6gre_net_id);
+ mutex_lock(&ign->tunnels_lock);
+
if (nt->parms.collect_md) {
if (rtnl_dereference(ign->collect_md_tun_erspan))
- return -EEXIST;
+ err = -EEXIST;
} else {
if (ip6gre_tunnel_find(net, &nt->parms, dev->type))
- return -EEXIST;
+ err = -EEXIST;
}
+ if (err)
+ goto unlock;
err = ip6gre_newlink_common(net, dev, tb, data, extack);
if (!err) {
@@ -2254,6 +2293,10 @@ static int ip6erspan_newlink(struct net_device *dev,
ip6erspan_tunnel_link_md(ign, nt);
ip6gre_tunnel_link(net_generic(net, ip6gre_net_id), nt);
}
+
+unlock:
+ mutex_unlock(&ign->tunnels_lock);
+
return err;
}
@@ -2278,14 +2321,20 @@ static int ip6erspan_changelink(struct net_device *dev, struct nlattr *tb[],
struct ip6_tnl *t = netdev_priv(dev);
struct __ip6_tnl_parm p;
struct ip6gre_net *ign;
+ int err = 0;
if (!rtnl_dev_link_net_capable(dev, t->net))
return -EPERM;
ign = net_generic(t->net, ip6gre_net_id);
+
+ mutex_lock(&ign->tunnels_lock);
+
t = ip6gre_changelink_common(dev, tb, data, &p, extack);
- if (IS_ERR(t))
- return PTR_ERR(t);
+ if (IS_ERR(t)) {
+ err = PTR_ERR(t);
+ goto unlock;
+ }
ip6erspan_set_version(data, &p);
ip6gre_tunnel_unlink_md(ign, t);
@@ -2293,7 +2342,10 @@ static int ip6erspan_changelink(struct net_device *dev, struct nlattr *tb[],
ip6erspan_tnl_change(t, &p, !tb[IFLA_MTU]);
ip6erspan_tunnel_link_md(ign, t);
ip6gre_tunnel_link(ign, t);
- return 0;
+unlock:
+ mutex_unlock(&ign->tunnels_lock);
+
+ return err;
}
static struct rtnl_link_ops ip6gre_link_ops __read_mostly = {
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v1 net-next 6/6] ip6_gre: Support per-netns device unregistration.
2026-09-16 23:02 [PATCH v1 net-next 0/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
` (4 preceding siblings ...)
2026-09-16 23:02 ` [PATCH v1 net-next 5/6] ip6_gre: Protect ip6gre_net.tunnels[][] with mutex Kuniyuki Iwashima
@ 2026-09-16 23:02 ` Kuniyuki Iwashima
2026-09-19 1:20 ` [PATCH v1 net-next 0/6] " patchwork-bot+netdevbpf
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-16 23:02 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Kuniyuki Iwashima, Kuniyuki Iwashima, netdev
ip6gre_exit_rtnl_net() iterates ip6gre devices and variants whose
link_net is dying and queues them for destruction.
The devices may reside in different netns.
Let's use unregister_netdevice_queue_net() to support per-netns
device unregistration.
Even after ip6gre_exit_rtnl_net() queues a cross-netns ip6gre
device, ip6gre_changelink_common(), ip6gre_dellink(), and
ip6gre_tunnel_siocdevprivate() could be called concurrently for
it (once RTNL is removed). In such a case, __rtnl_net_unlock()
will perform the unregistration.
Also, ip6gre_tunnel_siocdevprivate() needs to check check_net(t->net),
otherwise it could create a new dev in dying netns after
ip6gre_exit_rtnl_net().
In the example below, we can see the fallback tunnel device
(ip6gre0) and the cross-netns device (ip6gre1) are unregistered
by different processes:
# bpftrace -e '#include <linux/netdevice.h>
kprobe:ip6gre_tunnel_uninit {
$dev = (struct net_device *)arg0;
printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack());
}
kprobe:ip6gre_exit_rtnl_net {
printf("PID: %d%s\n", pid, kstack());
}' &
# ip netns add ns1
# ip netns add ns2
# ip -n ns1 link add name ip6gre1 link-netns ns2 \
type ip6gre local 2001:9292::1 remote 2001:9292::2
# ip netns del ns2
PID: 12
ip6gre_exit_rtnl_net+5
ops_undo_list+702
cleanup_net+1122
...
PID: 12 | DEV: ip6gre0 <------ fallback device (itn->fb_tunnel_dev).
ip6gre_tunnel_uninit+5
unregister_netdevice_many_notify+7137
unregister_netdevice_many_net+1050
__rtnl_net_unlock+37
ops_undo_list+754
cleanup_net+1122
...
PID: 10 | DEV: ip6gre1
ip6gre_tunnel_uninit+5
unregister_netdevice_many_notify+7137
unregister_netdevice_many_net+1050
rtnl_net_work_func+136
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/ipv6/ip6_gre.c | 51 ++++++++++++++++++++++++++++++++--------------
1 file changed, 36 insertions(+), 15 deletions(-)
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index ac1087330465..c851af22b9fe 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c
@@ -80,7 +80,8 @@ static void ip6gre_tunnel_setup(struct net_device *dev);
static void ip6gre_tunnel_link(struct ip6gre_net *ign, struct ip6_tnl *t);
static void ip6gre_tnl_link_config(struct ip6_tnl *t, int set_mtu);
static void ip6erspan_tnl_link_config(struct ip6_tnl *t, int set_mtu);
-static void __ip6gre_dellink(struct net_device *dev, struct list_head *head);
+static void __ip6gre_dellink(struct net *net, struct net_device *dev,
+ struct list_head *head);
/* Tunnel hash table */
@@ -285,6 +286,11 @@ static void ip6gre_tunnel_unlink(struct ip6gre_net *ign, struct ip6_tnl *t)
hlist_del_init_rcu(&t->hash_node);
}
+static bool ip6gre_tunnel_unregistering(struct ip6_tnl *t)
+{
+ return hlist_unhashed(&t->hash_node);
+}
+
static struct ip6_tnl *ip6gre_tunnel_find(struct net *net,
const struct __ip6_tnl_parm *parms,
int type)
@@ -1248,6 +1254,7 @@ static int ip6gre_tunnel_siocdevprivate(struct net_device *dev,
int cmd)
{
struct ip6_tnl *t = netdev_priv(dev);
+ struct net *orig_net = dev_net(dev);
struct __ip6_tnl_parm p1 = {};
LIST_HEAD(dev_kill_list);
struct net *net = t->net;
@@ -1261,6 +1268,11 @@ static int ip6gre_tunnel_siocdevprivate(struct net_device *dev,
mutex_lock(&ign->tunnels_lock);
+ if (!check_net(net)) {
+ err = -EBUSY;
+ goto done;
+ }
+
switch (cmd) {
case SIOCGETTUNNEL:
if (dev == ign->fb_tunnel_dev) {
@@ -1302,7 +1314,7 @@ static int ip6gre_tunnel_siocdevprivate(struct net_device *dev,
t = ip6gre_tunnel_locate(net, &p1, cmd == SIOCADDTUNNEL);
if (dev != ign->fb_tunnel_dev && cmd == SIOCCHGTUNNEL) {
- if (t) {
+ if (t && !ip6gre_tunnel_unregistering(t)) {
if (t->dev != dev) {
err = -EEXIST;
break;
@@ -1310,23 +1322,26 @@ static int ip6gre_tunnel_siocdevprivate(struct net_device *dev,
} else {
t = netdev_priv(dev);
- ip6gre_tunnel_unlink(ign, t);
- synchronize_net();
- ip6gre_tnl_change(t, &p1, 1);
- ip6gre_tunnel_link(ign, t);
- netdev_state_change(dev);
+ if (!ip6gre_tunnel_unregistering(t)) {
+ ip6gre_tunnel_unlink(ign, t);
+ synchronize_net();
+ ip6gre_tnl_change(t, &p1, 1);
+ ip6gre_tunnel_link(ign, t);
+ netdev_state_change(dev);
+ }
}
}
- if (t) {
+ if (t && !ip6gre_tunnel_unregistering(t)) {
err = 0;
memset(&p, 0, sizeof(p));
ip6gre_tnl_parm_to_user(&p, &t->parms);
if (copy_to_user(data, &p, sizeof(p)))
err = -EFAULT;
- } else
+ } else {
err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT);
+ }
break;
case SIOCDELTUNNEL:
@@ -1349,7 +1364,8 @@ static int ip6gre_tunnel_siocdevprivate(struct net_device *dev,
dev = t->dev;
}
- __ip6gre_dellink(dev, &dev_kill_list);
+ if (!ip6gre_tunnel_unregistering(t))
+ __ip6gre_dellink(orig_net, dev, &dev_kill_list);
err = 0;
break;
@@ -1570,7 +1586,7 @@ static void __net_exit ip6gre_exit_rtnl_net(struct net *net,
struct ip6_tnl *t;
hlist_for_each_entry_safe(t, tmp, head, hash_node)
- __ip6gre_dellink(t->dev, dev_kill_list);
+ __ip6gre_dellink(net, t->dev, dev_kill_list);
}
}
@@ -2049,6 +2065,9 @@ ip6gre_changelink_common(struct net_device *dev, struct nlattr *tb[],
t = nt;
}
+ if (ip6gre_tunnel_unregistering(t))
+ return ERR_PTR(-ENODEV);
+
return t;
}
@@ -2085,7 +2104,8 @@ static int ip6gre_changelink(struct net_device *dev, struct nlattr *tb[],
return err;
}
-static void __ip6gre_dellink(struct net_device *dev, struct list_head *head)
+static void __ip6gre_dellink(struct net *net, struct net_device *dev,
+ struct list_head *head)
{
struct ip6_tnl *t = netdev_priv(dev);
struct ip6gre_net *ign;
@@ -2098,7 +2118,7 @@ static void __ip6gre_dellink(struct net_device *dev, struct list_head *head)
ip6gre_tunnel_unlink_md(ign, t);
ip6gre_tunnel_unlink(ign, t);
- unregister_netdevice_queue(dev, head);
+ unregister_netdevice_queue_net(net, dev, head);
}
static void ip6gre_dellink(struct net_device *dev, struct list_head *head)
@@ -2110,8 +2130,9 @@ static void ip6gre_dellink(struct net_device *dev, struct list_head *head)
mutex_lock(&ign->tunnels_lock);
- if (dev != ign->fb_tunnel_dev)
- __ip6gre_dellink(dev, head);
+ if (dev != ign->fb_tunnel_dev &&
+ !ip6gre_tunnel_unregistering(t))
+ __ip6gre_dellink(dev_net(dev), dev, head);
mutex_unlock(&ign->tunnels_lock);
}
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v1 net-next 0/6] ip6_gre: Support per-netns device unregistration.
2026-09-16 23:02 [PATCH v1 net-next 0/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
` (5 preceding siblings ...)
2026-09-16 23:02 ` [PATCH v1 net-next 6/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
@ 2026-09-19 1:20 ` patchwork-bot+netdevbpf
6 siblings, 0 replies; 8+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-19 1:20 UTC (permalink / raw)
To: Kuniyuki Iwashima
Cc: davem, edumazet, kuba, pabeni, dsahern, idosch, horms, kuni1840,
netdev
Hello:
This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Wed, 16 Sep 2026 23:02:19 +0000 you wrote:
> Patch 1 & 2 convert ip6gre_net.tunnels[][] to hlist as a prep.
>
> Patch 3 & 4 unlink devices from ip6gre_net.tunnels[][] in
> ->dellink().
>
> Patch 5 & 6 adds mutex and support per-netns device unregistration.
>
> [...]
Here is the summary with links:
- [v1,net-next,1/6] ip6_gre: Initialise ign->tunnels_wc[0] before register_netdev().
https://git.kernel.org/netdev/net-next/c/3e6af2e485e4
- [v1,net-next,2/6] ip6_gre: Convert ip6gre_net.tunnels[][] to hlist.
https://git.kernel.org/netdev/net-next/c/3b2813cc6b1f
- [v1,net-next,3/6] ip6_gre: Clear ign->fb_tunnel_dev in ip6gre_exit_rtnl_net().
https://git.kernel.org/netdev/net-next/c/28328796e0b4
- [v1,net-next,4/6] ip6_gre: Unlink ip6gre_tunnel_unlink() from ->dellink().
https://git.kernel.org/netdev/net-next/c/b7c0d393e108
- [v1,net-next,5/6] ip6_gre: Protect ip6gre_net.tunnels[][] with mutex.
https://git.kernel.org/netdev/net-next/c/cce829e2aa1d
- [v1,net-next,6/6] ip6_gre: Support per-netns device unregistration.
https://git.kernel.org/netdev/net-next/c/b27a8c62875e
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-19 1:21 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-16 23:02 [PATCH v1 net-next 0/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 1/6] ip6_gre: Initialise ign->tunnels_wc[0] before register_netdev() Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 2/6] ip6_gre: Convert ip6gre_net.tunnels[][] to hlist Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 3/6] ip6_gre: Clear ign->fb_tunnel_dev in ip6gre_exit_rtnl_net() Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 4/6] ip6_gre: Unlink ip6gre_tunnel_unlink() from ->dellink() Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 5/6] ip6_gre: Protect ip6gre_net.tunnels[][] with mutex Kuniyuki Iwashima
2026-09-16 23:02 ` [PATCH v1 net-next 6/6] ip6_gre: Support per-netns device unregistration Kuniyuki Iwashima
2026-09-19 1:20 ` [PATCH v1 net-next 0/6] " patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox