* [PATCH v2 net-next 1/7] ipmr: Call ->dellink() to remove DVMRP tunnel device.
2026-09-09 23:43 [PATCH v2 net-next 0/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
@ 2026-09-09 23:43 ` Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 2/7] ip_tunnel: Set itn->fb_tunnel_dev to NULL in ip_tunnel_delete_net() Kuniyuki Iwashima
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-09 23:43 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Steffen Klassert, Herbert Xu, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev
ipmr.c uses unregister_netdevice() to remove DVMRP tunnel devices
created in ipmr_new_tunnel().
This is fine because currently ip_tunnel_uninit() also calls
ip_tunnel_del() to unlink the device from the hash table.
However, we will move ip_tunnel_del() from ip_tunnel_uninit() to
ip_tunnel_dellink().
Removing DVMRP tunnel devices by unregister_netdevice() would leave
them in the hash table.
Let's call ->dellink for DVMRP tunnel devices.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
v2: Correct commit message
---
net/ipv4/ipmr.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index e5f2b1c6150d..02edf9d7f4b5 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -479,6 +479,7 @@ static struct net_device *ipmr_new_tunnel(struct net *net, struct vifctl *v)
{
struct net_device *tunnel_dev, *new_dev;
struct ip_tunnel_parm_kern p = { };
+ LIST_HEAD(dev_kill_list);
int err;
tunnel_dev = __dev_get_by_name(net, "tunl0");
@@ -520,7 +521,8 @@ static struct net_device *ipmr_new_tunnel(struct net *net, struct vifctl *v)
return new_dev;
out_unregister:
- unregister_netdevice(new_dev);
+ new_dev->rtnl_link_ops->dellink(new_dev, &dev_kill_list);
+ unregister_netdevice_many(&dev_kill_list);
out:
return ERR_PTR(-ENOBUFS);
}
@@ -733,8 +735,12 @@ static int vif_delete(struct mr_table *mrt, int vifi, int notify,
ip_rt_multicast_event(in_dev);
}
- if (v->flags & (VIFF_TUNNEL | VIFF_REGISTER) && !notify)
- unregister_netdevice_queue(dev, head);
+ if (!notify) {
+ if (v->flags & VIFF_TUNNEL)
+ dev->rtnl_link_ops->dellink(dev, head);
+ else if (v->flags & VIFF_REGISTER)
+ unregister_netdevice_queue(dev, head);
+ }
netdev_put(dev, &v->dev_tracker);
return 0;
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 net-next 2/7] ip_tunnel: Set itn->fb_tunnel_dev to NULL in ip_tunnel_delete_net().
2026-09-09 23:43 [PATCH v2 net-next 0/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 1/7] ipmr: Call ->dellink() to remove DVMRP tunnel device Kuniyuki Iwashima
@ 2026-09-09 23:43 ` Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 3/7] ip_tunnel: Don't pass rtnl_link_ops to ip_tunnel_delete_net() Kuniyuki Iwashima
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-09 23:43 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Steffen Klassert, Herbert Xu, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev
ip_tunnel_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, ip_tunnel_delete_net() has to iterate devices in the dying netns
and call unregister_netdevice_queue() directly.
But then, this duplicates ip_tunnel_del() in ip_tunnel_dellink()
and ip_tunnel_uninit().
Let's set itn->fb_tunnel_dev to NULL in ip_tunnel_delete_net() and
remove for_each_netdev_safe() in ip_tunnel_delete_net().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/ipv4/ip_tunnel.c | 17 ++++-------------
1 file changed, 4 insertions(+), 13 deletions(-)
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index e6bcf01411d0..d29e2ba7cdd3 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -1155,26 +1155,19 @@ void ip_tunnel_delete_net(struct net *net, unsigned int id,
struct list_head *head)
{
struct ip_tunnel_net *itn = net_generic(net, id);
- struct net_device *dev, *aux;
int h;
ASSERT_RTNL_NET(net);
- for_each_netdev_safe(net, dev, aux)
- if (dev->rtnl_link_ops == ops)
- unregister_netdevice_queue(dev, head);
+ WRITE_ONCE(itn->fb_tunnel_dev, NULL);
for (h = 0; h < IP_TNL_HASH_SIZE; h++) {
- struct ip_tunnel *t;
- struct hlist_node *n;
struct hlist_head *thead = &itn->tunnels[h];
+ struct hlist_node *n;
+ struct ip_tunnel *t;
hlist_for_each_entry_safe(t, n, thead, 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);
+ unregister_netdevice_queue(t->dev, head);
}
}
EXPORT_SYMBOL_GPL(ip_tunnel_delete_net);
@@ -1310,8 +1303,6 @@ void ip_tunnel_uninit(struct net_device *dev)
itn = net_generic(net, tunnel->ip_tnl_net_id);
ip_tunnel_del(itn, netdev_priv(dev));
- if (itn->fb_tunnel_dev == dev)
- WRITE_ONCE(itn->fb_tunnel_dev, NULL);
dst_cache_reset(&tunnel->dst_cache);
}
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 net-next 3/7] ip_tunnel: Don't pass rtnl_link_ops to ip_tunnel_delete_net().
2026-09-09 23:43 [PATCH v2 net-next 0/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 1/7] ipmr: Call ->dellink() to remove DVMRP tunnel device Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 2/7] ip_tunnel: Set itn->fb_tunnel_dev to NULL in ip_tunnel_delete_net() Kuniyuki Iwashima
@ 2026-09-09 23:43 ` Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 4/7] ip_tunnel: Centralise ip_tunnel_del() to ip_tunnel_dellink() Kuniyuki Iwashima
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-09 23:43 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Steffen Klassert, Herbert Xu, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev
ip_tunnel_delete_net() no longer uses the 3rd argument,
struct rtnl_link_ops *ops.
Let's remove it.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
include/net/ip_tunnels.h | 1 -
net/ipv4/ip_gre.c | 6 +++---
net/ipv4/ip_tunnel.c | 1 -
net/ipv4/ip_vti.c | 2 +-
net/ipv4/ipip.c | 2 +-
5 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index 7c9aadfe8fe3..b0f9d02a7f18 100644
--- a/include/net/ip_tunnels.h
+++ b/include/net/ip_tunnels.h
@@ -398,7 +398,6 @@ int ip_tunnel_get_iflink(const struct net_device *dev);
int ip_tunnel_init_net(struct net *net, unsigned int ip_tnl_net_id,
struct rtnl_link_ops *ops, char *devname);
void ip_tunnel_delete_net(struct net *net, unsigned int id,
- struct rtnl_link_ops *ops,
struct list_head *dev_to_kill);
void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 82309efd417e..5e877018e006 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c
@@ -1084,7 +1084,7 @@ static int __net_init ipgre_init_net(struct net *net)
static void __net_exit ipgre_exit_rtnl(struct net *net,
struct list_head *dev_to_kill)
{
- ip_tunnel_delete_net(net, ipgre_net_id, &ipgre_link_ops, dev_to_kill);
+ ip_tunnel_delete_net(net, ipgre_net_id, dev_to_kill);
}
static struct pernet_operations ipgre_net_ops = {
@@ -1728,7 +1728,7 @@ static int __net_init ipgre_tap_init_net(struct net *net)
static void __net_exit ipgre_tap_exit_rtnl(struct net *net,
struct list_head *dev_to_kill)
{
- ip_tunnel_delete_net(net, gre_tap_net_id, &ipgre_tap_ops, dev_to_kill);
+ ip_tunnel_delete_net(net, gre_tap_net_id, dev_to_kill);
}
static struct pernet_operations ipgre_tap_net_ops = {
@@ -1747,7 +1747,7 @@ static int __net_init erspan_init_net(struct net *net)
static void __net_exit erspan_exit_rtnl(struct net *net,
struct list_head *dev_to_kill)
{
- ip_tunnel_delete_net(net, erspan_net_id, &erspan_link_ops, dev_to_kill);
+ ip_tunnel_delete_net(net, erspan_net_id, dev_to_kill);
}
static struct pernet_operations erspan_net_ops = {
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index d29e2ba7cdd3..1696ba1f3ed9 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -1151,7 +1151,6 @@ int ip_tunnel_init_net(struct net *net, unsigned int ip_tnl_net_id,
EXPORT_SYMBOL_GPL(ip_tunnel_init_net);
void ip_tunnel_delete_net(struct net *net, unsigned int id,
- struct rtnl_link_ops *ops,
struct list_head *head)
{
struct ip_tunnel_net *itn = net_generic(net, id);
diff --git a/net/ipv4/ip_vti.c b/net/ipv4/ip_vti.c
index 3b80929994a0..c4f14d42df24 100644
--- a/net/ipv4/ip_vti.c
+++ b/net/ipv4/ip_vti.c
@@ -526,7 +526,7 @@ static int __net_init vti_init_net(struct net *net)
static void __net_exit vti_exit_rtnl(struct net *net,
struct list_head *dev_to_kill)
{
- ip_tunnel_delete_net(net, vti_net_id, &vti_link_ops, dev_to_kill);
+ ip_tunnel_delete_net(net, vti_net_id, dev_to_kill);
}
static struct pernet_operations vti_net_ops = {
diff --git a/net/ipv4/ipip.c b/net/ipv4/ipip.c
index f684baf8e58f..2ffe64e736e0 100644
--- a/net/ipv4/ipip.c
+++ b/net/ipv4/ipip.c
@@ -645,7 +645,7 @@ static int __net_init ipip_init_net(struct net *net)
static void __net_exit ipip_exit_rtnl(struct net *net,
struct list_head *dev_to_kill)
{
- ip_tunnel_delete_net(net, ipip_net_id, &ipip_link_ops, dev_to_kill);
+ ip_tunnel_delete_net(net, ipip_net_id, dev_to_kill);
}
static struct pernet_operations ipip_net_ops = {
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 net-next 4/7] ip_tunnel: Centralise ip_tunnel_del() to ip_tunnel_dellink().
2026-09-09 23:43 [PATCH v2 net-next 0/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
` (2 preceding siblings ...)
2026-09-09 23:43 ` [PATCH v2 net-next 3/7] ip_tunnel: Don't pass rtnl_link_ops to ip_tunnel_delete_net() Kuniyuki Iwashima
@ 2026-09-09 23:43 ` Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 5/7] ip_tunnel: Unify error paths in ip_tunnel_newlink() and ip_tunnel_changelink() Kuniyuki Iwashima
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-09 23:43 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Steffen Klassert, Herbert Xu, 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 ip_tunnel_del() in ip_tunnel_uninit().
Let's use ip_tunnel_dellink() everywhere to remove ip_tunnel device
and remove ip_tunnel_del() in ip_tunnel_uninit().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/ipv4/ip_tunnel.c | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 1696ba1f3ed9..5833f93c1964 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -897,10 +897,13 @@ static void ip_tunnel_update(struct ip_tunnel_net *itn,
int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
int cmd)
{
- int err = 0;
struct ip_tunnel *t = netdev_priv(dev);
+ struct ip_tunnel_net *itn;
+ LIST_HEAD(dev_kill_list);
struct net *net = t->net;
- struct ip_tunnel_net *itn = net_generic(net, t->ip_tnl_net_id);
+ int err = 0;
+
+ itn = net_generic(net, t->ip_tnl_net_id);
switch (cmd) {
case SIOCGETTUNNEL:
@@ -984,7 +987,8 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
goto done;
dev = t->dev;
}
- unregister_netdevice(dev);
+
+ ip_tunnel_dellink(dev, &dev_kill_list);
err = 0;
break;
@@ -993,6 +997,8 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
}
done:
+ unregister_netdevice_many(&dev_kill_list);
+
return err;
}
EXPORT_SYMBOL_GPL(ip_tunnel_ctl);
@@ -1166,7 +1172,7 @@ void ip_tunnel_delete_net(struct net *net, unsigned int id,
struct ip_tunnel *t;
hlist_for_each_entry_safe(t, n, thead, hash_node)
- unregister_netdevice_queue(t->dev, head);
+ ip_tunnel_dellink(t->dev, head);
}
}
EXPORT_SYMBOL_GPL(ip_tunnel_delete_net);
@@ -1297,11 +1303,6 @@ EXPORT_SYMBOL_GPL(__ip_tunnel_init);
void ip_tunnel_uninit(struct net_device *dev)
{
struct ip_tunnel *tunnel = netdev_priv(dev);
- struct net *net = tunnel->net;
- struct ip_tunnel_net *itn;
-
- itn = net_generic(net, tunnel->ip_tnl_net_id);
- ip_tunnel_del(itn, netdev_priv(dev));
dst_cache_reset(&tunnel->dst_cache);
}
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 net-next 5/7] ip_tunnel: Unify error paths in ip_tunnel_newlink() and ip_tunnel_changelink().
2026-09-09 23:43 [PATCH v2 net-next 0/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
` (3 preceding siblings ...)
2026-09-09 23:43 ` [PATCH v2 net-next 4/7] ip_tunnel: Centralise ip_tunnel_del() to ip_tunnel_dellink() Kuniyuki Iwashima
@ 2026-09-09 23:43 ` Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 6/7] ip_tunnel: Protect ip_tunnel_net.tunnels[] with mutex Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 7/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-09 23:43 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Steffen Klassert, Herbert Xu, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev
The next patch will introduce per-netns mutex and acquire it
in ip_tunnel_newlink() and ip_tunnel_changelink().
To make the diff cleaner, let's unify the error paths.
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/ipv4/ip_tunnel.c | 37 ++++++++++++++++++++++++-------------
1 file changed, 24 insertions(+), 13 deletions(-)
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 5833f93c1964..3ba03c2b3b90 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -1181,21 +1181,23 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev,
struct nlattr *tb[], struct ip_tunnel_parm_kern *p,
__u32 fwmark)
{
- struct ip_tunnel *nt;
struct ip_tunnel_net *itn;
+ struct ip_tunnel *nt;
+ int err = 0;
int mtu;
- int err;
nt = netdev_priv(dev);
itn = net_generic(net, nt->ip_tnl_net_id);
if (nt->collect_md) {
if (rtnl_dereference(itn->collect_md_tun))
- return -EEXIST;
+ err = -EEXIST;
} else {
if (ip_tunnel_find(itn, p, dev->type))
- return -EEXIST;
+ err = -EEXIST;
}
+ if (err)
+ goto out;
nt->net = net;
nt->parms = *p;
@@ -1222,22 +1224,26 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev,
goto err_dev_set_mtu;
ip_tunnel_add(itn, nt);
- return 0;
+out:
+ return err;
err_dev_set_mtu:
unregister_netdevice(dev);
err_register_netdevice:
- return err;
+ goto out;
}
EXPORT_SYMBOL_GPL(ip_tunnel_newlink);
int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
struct ip_tunnel_parm_kern *p, __u32 fwmark)
{
- struct ip_tunnel *t;
struct ip_tunnel *tunnel = netdev_priv(dev);
struct net *net = tunnel->net;
- struct ip_tunnel_net *itn = net_generic(net, tunnel->ip_tnl_net_id);
+ struct ip_tunnel_net *itn;
+ struct ip_tunnel *t;
+ int err = 0;
+
+ itn = net_generic(net, tunnel->ip_tnl_net_id);
if (dev == itn->fb_tunnel_dev)
return -EINVAL;
@@ -1245,8 +1251,10 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
t = ip_tunnel_find(itn, p, dev->type);
if (t) {
- if (t->dev != dev)
- return -EEXIST;
+ if (t->dev != dev) {
+ err = -EEXIST;
+ goto out;
+ }
} else {
t = tunnel;
@@ -1259,13 +1267,16 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
nflags = IFF_POINTOPOINT;
if ((dev->flags ^ nflags) &
- (IFF_POINTOPOINT | IFF_BROADCAST))
- return -EINVAL;
+ (IFF_POINTOPOINT | IFF_BROADCAST)) {
+ err = -EINVAL;
+ goto out;
+ }
}
}
ip_tunnel_update(itn, t, dev, p, !tb[IFLA_MTU], fwmark);
- return 0;
+out:
+ return err;
}
EXPORT_SYMBOL_GPL(ip_tunnel_changelink);
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 net-next 6/7] ip_tunnel: Protect ip_tunnel_net.tunnels[] with mutex.
2026-09-09 23:43 [PATCH v2 net-next 0/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
` (4 preceding siblings ...)
2026-09-09 23:43 ` [PATCH v2 net-next 5/7] ip_tunnel: Unify error paths in ip_tunnel_newlink() and ip_tunnel_changelink() Kuniyuki Iwashima
@ 2026-09-09 23:43 ` Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 7/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-09 23:43 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Steffen Klassert, Herbert Xu, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev
struct ip_tunnel.net is the netns where encapsulated packets
flow into.
struct ip_tunnel is linked to ip_tunnel_net.tunnels[] of netns.
During netns dismantle or module unload, ip_tunnel_delete_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>
---
include/net/ip_tunnels.h | 1 +
net/ipv4/ip_tunnel.c | 42 +++++++++++++++++++++++++++++++++++-----
2 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index b0f9d02a7f18..57a67900e2d3 100644
--- a/include/net/ip_tunnels.h
+++ b/include/net/ip_tunnels.h
@@ -215,6 +215,7 @@ struct ip_tunnel_net {
struct net_device *fb_tunnel_dev;
struct rtnl_link_ops *rtnl_link_ops;
struct hlist_head tunnels[IP_TNL_HASH_SIZE];
+ struct mutex tunnels_lock;
struct ip_tunnel __rcu *collect_md_tun;
int type;
};
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 3ba03c2b3b90..9ad63f1af37a 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -219,7 +219,8 @@ static struct ip_tunnel *ip_tunnel_find(struct ip_tunnel_net *itn,
ip_tunnel_flags_copy(flags, parms->i_flags);
- hlist_for_each_entry_rcu(t, head, hash_node, lockdep_rtnl_is_held()) {
+ hlist_for_each_entry_rcu(t, head, hash_node,
+ lockdep_is_held(&itn->tunnels_lock)) {
if (local == t->parms.iph.saddr &&
remote == t->parms.iph.daddr &&
link == READ_ONCE(t->parms.link) &&
@@ -894,6 +895,16 @@ static void ip_tunnel_update(struct ip_tunnel_net *itn,
netdev_state_change(dev);
}
+static void __ip_tunnel_dellink(struct net_device *dev, struct list_head *head)
+{
+ struct ip_tunnel *tunnel = netdev_priv(dev);
+ struct ip_tunnel_net *itn;
+
+ itn = net_generic(tunnel->net, tunnel->ip_tnl_net_id);
+ ip_tunnel_del(itn, tunnel);
+ unregister_netdevice_queue(dev, head);
+}
+
int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
int cmd)
{
@@ -903,8 +914,12 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
struct net *net = t->net;
int err = 0;
+ DEBUG_NET_WARN_ON_ONCE(netdev_need_ops_lock(dev));
+
itn = net_generic(net, t->ip_tnl_net_id);
+ mutex_lock(&itn->tunnels_lock);
+
switch (cmd) {
case SIOCGETTUNNEL:
if (dev == itn->fb_tunnel_dev) {
@@ -988,7 +1003,7 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
dev = t->dev;
}
- ip_tunnel_dellink(dev, &dev_kill_list);
+ __ip_tunnel_dellink(dev, &dev_kill_list);
err = 0;
break;
@@ -997,6 +1012,8 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
}
done:
+ mutex_unlock(&itn->tunnels_lock);
+
unregister_netdevice_many(&dev_kill_list);
return err;
@@ -1093,8 +1110,9 @@ void ip_tunnel_dellink(struct net_device *dev, struct list_head *head)
itn = net_generic(tunnel->net, tunnel->ip_tnl_net_id);
if (itn->fb_tunnel_dev != dev) {
- ip_tunnel_del(itn, netdev_priv(dev));
- unregister_netdevice_queue(dev, head);
+ mutex_lock(&itn->tunnels_lock);
+ __ip_tunnel_dellink(dev, head);
+ mutex_unlock(&itn->tunnels_lock);
}
}
EXPORT_SYMBOL_GPL(ip_tunnel_dellink);
@@ -1126,6 +1144,8 @@ int ip_tunnel_init_net(struct net *net, unsigned int ip_tnl_net_id,
for (i = 0; i < IP_TNL_HASH_SIZE; i++)
INIT_HLIST_HEAD(&itn->tunnels[i]);
+ mutex_init(&itn->tunnels_lock);
+
if (!ops || !net_has_fallback_tunnels(net)) {
struct ip_tunnel_net *it_init_net;
@@ -1164,6 +1184,8 @@ void ip_tunnel_delete_net(struct net *net, unsigned int id,
ASSERT_RTNL_NET(net);
+ mutex_lock(&itn->tunnels_lock);
+
WRITE_ONCE(itn->fb_tunnel_dev, NULL);
for (h = 0; h < IP_TNL_HASH_SIZE; h++) {
@@ -1172,8 +1194,10 @@ void ip_tunnel_delete_net(struct net *net, unsigned int id,
struct ip_tunnel *t;
hlist_for_each_entry_safe(t, n, thead, hash_node)
- ip_tunnel_dellink(t->dev, head);
+ __ip_tunnel_dellink(t->dev, head);
}
+
+ mutex_unlock(&itn->tunnels_lock);
}
EXPORT_SYMBOL_GPL(ip_tunnel_delete_net);
@@ -1189,6 +1213,8 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev,
nt = netdev_priv(dev);
itn = net_generic(net, nt->ip_tnl_net_id);
+ mutex_lock(&itn->tunnels_lock);
+
if (nt->collect_md) {
if (rtnl_dereference(itn->collect_md_tun))
err = -EEXIST;
@@ -1225,6 +1251,8 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev,
ip_tunnel_add(itn, nt);
out:
+ mutex_unlock(&itn->tunnels_lock);
+
return err;
err_dev_set_mtu:
@@ -1248,6 +1276,8 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
if (dev == itn->fb_tunnel_dev)
return -EINVAL;
+ mutex_lock(&itn->tunnels_lock);
+
t = ip_tunnel_find(itn, p, dev->type);
if (t) {
@@ -1276,6 +1306,8 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
ip_tunnel_update(itn, t, dev, p, !tb[IFLA_MTU], fwmark);
out:
+ mutex_unlock(&itn->tunnels_lock);
+
return err;
}
EXPORT_SYMBOL_GPL(ip_tunnel_changelink);
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 net-next 7/7] ip_tunnel: Support per-netns device unregistration.
2026-09-09 23:43 [PATCH v2 net-next 0/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
` (5 preceding siblings ...)
2026-09-09 23:43 ` [PATCH v2 net-next 6/7] ip_tunnel: Protect ip_tunnel_net.tunnels[] with mutex Kuniyuki Iwashima
@ 2026-09-09 23:43 ` Kuniyuki Iwashima
6 siblings, 0 replies; 8+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-09 23:43 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Ido Schimmel
Cc: Simon Horman, Steffen Klassert, Herbert Xu, Kuniyuki Iwashima,
Kuniyuki Iwashima, netdev
ip_tunnel_delete_net() iterates ip_tunnel devices 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 ip_tunnel_delete_net() queues a cross-netns ip_tunnel
device, ip_tunnel_changelink(), ip_tunnel_dellink(), and
ip_tunnel_ctl() could be called concurrently for it (once RTNL is
removed). In such a case, __rtnl_net_unlock() will perform the
unregistration.
Also, ip_tunnel_ctl() needs to check if itn->fb_tunnel_dev is
NULL, otherwise it could create a new dev in dying netns after
ip_tunnel_delete_net().
In the example below, we can see the fallback tunnel device (gre0)
and the cross-netns device (gre1) are unregistered by different
processes:
# bpftrace -e '#include <linux/netdevice.h>
kprobe:ip_tunnel_uninit {
$dev = (struct net_device *)arg0;
printf("PID: %d | DEV: %s%s\n", pid, $dev->name, kstack());
}
kprobe:ipgre_exit_rtnl {
printf("PID: %d%s\n", pid, kstack());
}' &
# ip netns add ns1
# ip netns add ns2
# ip -n ns1 link add name gre1 link-netns ns2 \
type gre local 192.168.0.1 remote 192.168.1.1
# ip netns del ns2
PID: 12
ipgre_exit_rtnl+5
ops_undo_list+702
cleanup_net+1122
process_scheduled_works+2538
...
PID: 12 | DEV: gre0 <------ fallback device (itn->fb_tunnel_dev).
ip_tunnel_uninit+5
unregister_netdevice_many_notify+7129
unregister_netdevice_many_net+1050
__rtnl_net_unlock+37
ops_undo_list+754
cleanup_net+1122
process_scheduled_works+2538
...
PID: 10 | DEV: gre1
ip_tunnel_uninit+5
unregister_netdevice_many_notify+7129
unregister_netdevice_many_net+1050
rtnl_net_work_func+136
process_scheduled_works+2538
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
v2: Check if (!itn->fb_tunnel_dev) in ip_tunnel_ctl().
---
net/ipv4/ip_tunnel.c | 32 ++++++++++++++++++++++++++------
1 file changed, 26 insertions(+), 6 deletions(-)
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 9ad63f1af37a..b07cc453e28b 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -205,6 +205,11 @@ static void ip_tunnel_del(struct ip_tunnel_net *itn, struct ip_tunnel *t)
hlist_del_init_rcu(&t->hash_node);
}
+static bool ip_tunnel_unregistering(struct ip_tunnel *t)
+{
+ return hlist_unhashed(&t->hash_node);
+}
+
static struct ip_tunnel *ip_tunnel_find(struct ip_tunnel_net *itn,
struct ip_tunnel_parm_kern *parms,
int type)
@@ -895,20 +900,22 @@ static void ip_tunnel_update(struct ip_tunnel_net *itn,
netdev_state_change(dev);
}
-static void __ip_tunnel_dellink(struct net_device *dev, struct list_head *head)
+static void __ip_tunnel_dellink(struct net *net, struct net_device *dev,
+ struct list_head *head)
{
struct ip_tunnel *tunnel = netdev_priv(dev);
struct ip_tunnel_net *itn;
itn = net_generic(tunnel->net, tunnel->ip_tnl_net_id);
ip_tunnel_del(itn, tunnel);
- unregister_netdevice_queue(dev, head);
+ unregister_netdevice_queue_net(net, dev, head);
}
int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
int cmd)
{
struct ip_tunnel *t = netdev_priv(dev);
+ struct net *orig_net = dev_net(dev);
struct ip_tunnel_net *itn;
LIST_HEAD(dev_kill_list);
struct net *net = t->net;
@@ -920,6 +927,11 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
mutex_lock(&itn->tunnels_lock);
+ if (!itn->fb_tunnel_dev) {
+ err = -EBUSY;
+ goto done;
+ }
+
switch (cmd) {
case SIOCGETTUNNEL:
if (dev == itn->fb_tunnel_dev) {
@@ -979,7 +991,7 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
}
}
- if (t) {
+ if (t && !ip_tunnel_unregistering(t)) {
err = 0;
ip_tunnel_update(itn, t, dev, p, true, 0);
} else {
@@ -1003,7 +1015,9 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
dev = t->dev;
}
- __ip_tunnel_dellink(dev, &dev_kill_list);
+ if (!ip_tunnel_unregistering(t))
+ __ip_tunnel_dellink(orig_net, dev, &dev_kill_list);
+
err = 0;
break;
@@ -1111,7 +1125,8 @@ void ip_tunnel_dellink(struct net_device *dev, struct list_head *head)
if (itn->fb_tunnel_dev != dev) {
mutex_lock(&itn->tunnels_lock);
- __ip_tunnel_dellink(dev, head);
+ if (!ip_tunnel_unregistering(tunnel))
+ __ip_tunnel_dellink(dev_net(dev), dev, head);
mutex_unlock(&itn->tunnels_lock);
}
}
@@ -1194,7 +1209,7 @@ void ip_tunnel_delete_net(struct net *net, unsigned int id,
struct ip_tunnel *t;
hlist_for_each_entry_safe(t, n, thead, hash_node)
- __ip_tunnel_dellink(t->dev, head);
+ __ip_tunnel_dellink(net, t->dev, head);
}
mutex_unlock(&itn->tunnels_lock);
@@ -1304,6 +1319,11 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
}
}
+ if (ip_tunnel_unregistering(t)) {
+ err = -ENODEV;
+ goto out;
+ }
+
ip_tunnel_update(itn, t, dev, p, !tb[IFLA_MTU], fwmark);
out:
mutex_unlock(&itn->tunnels_lock);
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread