* [PATCH v2 net-next 0/7] ip_tunnel: Support per-netns device unregistration.
@ 2026-09-09 23:43 Kuniyuki Iwashima
2026-09-09 23:43 ` [PATCH v2 net-next 1/7] ipmr: Call ->dellink() to remove DVMRP tunnel device Kuniyuki Iwashima
` (6 more replies)
0 siblings, 7 replies; 13+ 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
Patch 1 - 3 are prep patches to unlink ip_tunnel device from
the hash table in ip_tunnel_dellink() only.
Patch 4 removes ip_tunnel_del() from ip_tunnel_uninit() to
ip_tunnel_dellink().
Patch 5 & 6 adds mutex to protect the per-netns hash table.
Patch 7 uses unregister_netdevice_queue_net() to support
cross-netns device unregistration.
Changes:
v2:
* Patch 1
* Correct commit message; ip_tunnel_uninit() unlinks
DVMRP device, instead of ->dellink()
* Patch 7
* Check if (!itn->fb_tunnel_dev) in ip_tunnel_ctl().
v1: https://lore.kernel.org/netdev/20260907225846.3787676-1-kuniyu@google.com/
Kuniyuki Iwashima (7):
ipmr: Call ->dellink() to remove DVMRP tunnel device.
ip_tunnel: Set itn->fb_tunnel_dev to NULL in ip_tunnel_delete_net().
ip_tunnel: Don't pass rtnl_link_ops to ip_tunnel_delete_net().
ip_tunnel: Centralise ip_tunnel_del() to ip_tunnel_dellink().
ip_tunnel: Unify error paths in ip_tunnel_newlink() and
ip_tunnel_changelink().
ip_tunnel: Protect ip_tunnel_net.tunnels[] with mutex.
ip_tunnel: Support per-netns device unregistration.
include/net/ip_tunnels.h | 2 +-
net/ipv4/ip_gre.c | 6 +-
net/ipv4/ip_tunnel.c | 132 +++++++++++++++++++++++++++------------
net/ipv4/ip_vti.c | 2 +-
net/ipv4/ipip.c | 2 +-
net/ipv4/ipmr.c | 12 +++-
6 files changed, 108 insertions(+), 48 deletions(-)
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply [flat|nested] 13+ messages in thread
* [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; 13+ 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] 13+ 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-11 2:44 ` netdev-bot+sashiko
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, 1 reply; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ 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; 13+ 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] 13+ 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-11 2:44 ` netdev-bot+sashiko
2026-09-09 23:43 ` [PATCH v2 net-next 7/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
6 siblings, 1 reply; 13+ 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] 13+ 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
2026-09-10 17:13 ` Ido Schimmel
2026-09-11 2:44 ` netdev-bot+sashiko
6 siblings, 2 replies; 13+ 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] 13+ messages in thread
* Re: [PATCH v2 net-next 7/7] ip_tunnel: Support per-netns device unregistration.
2026-09-09 23:43 ` [PATCH v2 net-next 7/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
@ 2026-09-10 17:13 ` Ido Schimmel
2026-09-10 17:35 ` Kuniyuki Iwashima
2026-09-11 2:44 ` netdev-bot+sashiko
1 sibling, 1 reply; 13+ messages in thread
From: Ido Schimmel @ 2026-09-10 17:13 UTC (permalink / raw)
To: Kuniyuki Iwashima
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Steffen Klassert, Herbert Xu,
Kuniyuki Iwashima, netdev
On Wed, Sep 09, 2026 at 11:43:50PM +0000, Kuniyuki Iwashima wrote:
> @@ -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;
> + }
This is a problem in network namespaces where fallback tunnels are not
created and the pointer is set to NULL.
Before:
# sysctl -wq net.core.fb_tunnels_only_for_init_net=1
# ip netns add bla
# ip -n bla link add name gre1 up type gre local 192.0.2.1 remote 198.51.100.1
# ip -n bla tunnel show gre1
gre1: gre/ip remote 198.51.100.1 local 192.0.2.1 ttl inherit
After:
# sysctl -wq net.core.fb_tunnels_only_for_init_net=1
# ip netns add bla
# ip -n bla link add name gre1 up type gre local 192.0.2.1 remote 198.51.100.1
# ip -n bla tunnel show gre1
get tunnel "gre1" failed: Device or resource busy
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 net-next 7/7] ip_tunnel: Support per-netns device unregistration.
2026-09-10 17:13 ` Ido Schimmel
@ 2026-09-10 17:35 ` Kuniyuki Iwashima
0 siblings, 0 replies; 13+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-10 17:35 UTC (permalink / raw)
To: Ido Schimmel
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Steffen Klassert, Herbert Xu,
Kuniyuki Iwashima, netdev
On Thu, Sep 10, 2026 at 10:13 AM Ido Schimmel <idosch@nvidia.com> wrote:
>
> On Wed, Sep 09, 2026 at 11:43:50PM +0000, Kuniyuki Iwashima wrote:
> > @@ -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;
> > + }
>
> This is a problem in network namespaces where fallback tunnels are not
> created and the pointer is set to NULL.
Ah, I should have used check_net().
Will fix it in v3.
Thanks !
---
pw-bot: cr
>
> Before:
>
> # sysctl -wq net.core.fb_tunnels_only_for_init_net=1
> # ip netns add bla
> # ip -n bla link add name gre1 up type gre local 192.0.2.1 remote 198.51.100.1
> # ip -n bla tunnel show gre1
> gre1: gre/ip remote 198.51.100.1 local 192.0.2.1 ttl inherit
>
> After:
>
> # sysctl -wq net.core.fb_tunnels_only_for_init_net=1
> # ip netns add bla
> # ip -n bla link add name gre1 up type gre local 192.0.2.1 remote 198.51.100.1
> # ip -n bla tunnel show gre1
> get tunnel "gre1" failed: Device or resource busy
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [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 2/7] ip_tunnel: Set itn->fb_tunnel_dev to NULL in ip_tunnel_delete_net() Kuniyuki Iwashima
@ 2026-09-11 2:44 ` netdev-bot+sashiko
0 siblings, 0 replies; 13+ messages in thread
From: netdev-bot+sashiko @ 2026-09-11 2:44 UTC (permalink / raw)
To: kuniyu
Cc: davem, edumazet, kuba, pabeni, dsahern, idosch, horms,
steffen.klassert, herbert, kuni1840, netdev
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 0
- [Medium] Commit message omits two behavioural changes that the diff
makes to net/ipv4/ip_tunnel.c.
--
ip_tunnel: Set itn->fb_tunnel_dev to NULL in ip_tunnel_delete_net().
This drops the for_each_netdev_safe() loop from ip_tunnel_delete_net() and
instead clears itn->fb_tunnel_dev there, so the unconditional hash walk can
queue every tunnel, including the fallback device. The matching
fb_tunnel_dev clearing in ip_tunnel_uninit() is removed.
> diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
> index e6bcf01411d0b..d29e2ba7cdd3a 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);
[Severity: Medium]
Could the changelog say something about the devices that only the removed
for_each_netdev_safe() loop covered?
The loop walked devices by residence (dev_net(dev) == net), while the hash
walk below covers devices by tunnel->net, i.e. the netns the ip_tunnel is
hashed in. Those two differ when IFLA_LINK_NETNSID is used, because
ipgre_newlink() and friends pass params->link_net into ip_tunnel_newlink():
ip -n ns1 link add gre1 link-netns ns2 type gre local X remote Y
ip netns del ns1
Here gre1 lives in ns1 but is hashed in ns2's itn, so after this change
ip_tunnel_delete_net(ns1) does not see it and its removal is deferred to
net/core/dev.c:default_device_exit_batch():
if (dev->rtnl_link_ops && dev->rtnl_link_ops->dellink)
dev->rtnl_link_ops->dellink(dev, &dev_kill_list);
I traced that path and the device does get unregistered there (ipgre_link_ops
does not set netns_refund, so default_device_exit_net() leaves it alone and
the second loop calls ->dellink()), so this looks like a teardown ordering
change rather than a leak.
But it is the same path the changelog describes as unusable:
"This also makes default_device_exit_batch() impossible to remove
the device since it calls ->dellink()."
Since itn->fb_tunnel_dev is NULL by then, ip_tunnel_dellink() no longer skips
the device, so the statement holds only for the fallback device before the
netns is dying. Would it be worth spelling out that teardown of tunnels
resident in the dying netns but hashed in another netns' itn now relies on
default_device_exit_batch()?
> 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);
> }
[Severity: Medium]
This hunk partially reverts ba61539c6ae5 ("ip_tunnel: fix use-after-free in
ip_tunnel_lookup()"), and the changelog does not mention it. Should the
reason the guard is now redundant be recorded somewhere?
As far as I can tell the new code depends on the invariant that a fallback
device can only ever be unregistered from ip_tunnel_delete_net():
- ip_tunnel_dellink() skips it while itn->fb_tunnel_dev is set
- ip_tunnel_ctl() refuses SIOCDELTUNNEL on it with -EPERM
- ip_tunnel_init_net() sets netns_immutable = true, so it cannot move
That invariant does hold at this commit, so I did not find a live
use-after-free. Two related points though:
net/ipv6/ip6_gre.c and net/ipv6/sit.c still clear fb_tunnel_dev from their
uninit paths, so the two families now differ without an explanation.
And ip_tunnel_init_net() adds the fallback device to itn->tunnels:
ip_tunnel_add(itn, netdev_priv(itn->fb_tunnel_dev));
so the hash walk in ip_tunnel_delete_net() queues it directly anyway. Is the
statement
"So, ip_tunnel_delete_net() has to iterate devices in the dying netns
and call unregister_netdevice_queue() directly."
still accurate as a description of what the fallback device needs, given the
device is reachable through the hash?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909234422.2416506-1-kuniyu%40google.com
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 net-next 6/7] ip_tunnel: Protect ip_tunnel_net.tunnels[] with mutex.
2026-09-09 23:43 ` [PATCH v2 net-next 6/7] ip_tunnel: Protect ip_tunnel_net.tunnels[] with mutex Kuniyuki Iwashima
@ 2026-09-11 2:44 ` netdev-bot+sashiko
0 siblings, 0 replies; 13+ messages in thread
From: netdev-bot+sashiko @ 2026-09-11 2:44 UTC (permalink / raw)
To: kuniyu
Cc: davem, edumazet, kuba, pabeni, dsahern, idosch, horms,
steffen.klassert, herbert, kuni1840, netdev
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 1
- [Low] Reported concern (judged a false positive after verification):
under the new mutex the check-then-act sequences in `ip_tunnel_ctl()`…
--
ip_tunnel: Protect ip_tunnel_net.tunnels[] with mutex.
This adds a per-netns mutex to struct ip_tunnel_net and takes it around the
paths that add, remove, look up or update entries in
ip_tunnel_net.tunnels[], replacing the implicit RTNL protection assumed by
ip_tunnel_find(). A DEBUG_NET_WARN_ON_ONCE(netdev_need_ops_lock(dev)) is
also added to ip_tunnel_ctl() to document that the ops lock must be a no-op
for tunnel devices.
No Link: tags are present in the commit header.
I have no regressions to report against this commit. Quoting the hunks I
looked at most closely, with a note on the one candidate issue I chased and
why it does not look reachable here.
> diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
> index 3ba03c2b3b900..9ad63f1af37a6 100644
> --- a/net/ipv4/ip_tunnel.c
> +++ b/net/ipv4/ip_tunnel.c
[ ... ]
> @@ -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;
[ ... ]
> @@ -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);
The one thing I checked was whether a tunnel already unhashed and queued for
unregistration by ip_tunnel_delete_net() above could come back, either via
ip_tunnel_update() in ip_tunnel_changelink()/ip_tunnel_ctl() (ip_tunnel_del()
becomes a no-op, then ip_tunnel_add() relinks it) or via a second
__ip_tunnel_dellink() queueing it onto another kill list.
That does not look reachable at this commit: rtnl_net_lock() still takes the
global RTNL, and the pernet exit path runs ip_tunnel_delete_net() plus the
queued unregister_netdevice_many*() inside one RTNL section, so the ioctl and
rtnetlink paths cannot interleave with teardown yet. The later patch in the
series adding ip_tunnel_unregistering() covers those call sites anyway.
> @@ -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);
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909234422.2416506-1-kuniyu%40google.com
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v2 net-next 7/7] ip_tunnel: Support per-netns device unregistration.
2026-09-09 23:43 ` [PATCH v2 net-next 7/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
2026-09-10 17:13 ` Ido Schimmel
@ 2026-09-11 2:44 ` netdev-bot+sashiko
1 sibling, 0 replies; 13+ messages in thread
From: netdev-bot+sashiko @ 2026-09-11 2:44 UTC (permalink / raw)
To: kuniyu
Cc: davem, edumazet, kuba, pabeni, dsahern, idosch, horms,
steffen.klassert, herbert, kuni1840, netdev
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 1 · Medium: 0 · Low: 0
- [High] In `ip_tunnel_ctl()` (net/ipv4/ip_tunnel.c:930-933) the newly
added gate `if (!itn->fb_tunnel_dev) { err = -EBUSY; goto done; }`…
--
ip_tunnel: Support per-netns device unregistration.
This switches __ip_tunnel_dellink() to unregister_netdevice_queue_net() so
ip_tunnel_delete_net() can queue cross-netns devices for per-netns
unregistration, and adds ip_tunnel_unregistering() checks so
ip_tunnel_changelink()/ip_tunnel_dellink()/ip_tunnel_ctl() skip devices
that are already queued. It also adds a NULL check on itn->fb_tunnel_dev
in ip_tunnel_ctl().
> diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
> index 9ad63f1af37a6..b07cc453e28be 100644
> --- a/net/ipv4/ip_tunnel.c
> +++ b/net/ipv4/ip_tunnel.c
[ ... ]
> @@ -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;
> + }
> +
[Severity: High]
Is itn->fb_tunnel_dev == NULL a reliable indicator that the netns is
dying?
ip_tunnel_init_net() leaves fb_tunnel_dev permanently NULL for a perfectly
live netns:
net/ipv4/ip_tunnel.c:ip_tunnel_init_net() {
...
if (!ops || !net_has_fallback_tunnels(net)) {
struct ip_tunnel_net *it_init_net;
it_init_net = net_generic(&init_net, ip_tnl_net_id);
itn->type = it_init_net->type;
itn->fb_tunnel_dev = NULL;
return 0;
}
...
}
net_has_fallback_tunnels() returns false for all non-init netns when
net.core.fb_tunnels_only_for_init_net == 1, and for every netns including
init_net when it is 2 (or the fb_tunnels=none boot arg is used). That
sysctl is mode 0644 in net/core/sysctl_net_core.c, so it is runtime
settable.
In those configurations, does this new gate make all four legacy ioctls
fail with -EBUSY on ipip/gre/gretap/erspan/vti devices? On the parent
commit, with fb_tunnel_dev == NULL:
- SIOCGETTUNNEL skips the "dev == itn->fb_tunnel_dev" branch and does
memcpy(p, &t->parms, sizeof(*p)) with t = netdev_priv(dev)
- SIOCADDTUNNEL reaches ip_tunnel_create(net, itn, p), which only needs
itn->rtnl_link_ops (assigned before the early return above)
- SIOCCHGTUNNEL takes the "dev != itn->fb_tunnel_dev" branch
- SIOCDELTUNNEL reaches __ip_tunnel_dellink()
The path is reachable per device without any fallback device existing,
since ipgre_netdev_ops, ipip_netdev_ops and vti_netdev_ops all install
.ndo_siocdevprivate = ip_tunnel_siocdevprivate:
ioctl(fd, SIOCGETTUNNEL, {ifr_name="gre1"})
-> ip_tunnel_siocdevprivate()
-> dev->netdev_ops->ndo_tunnel_ctl(dev, &p, cmd)
-> ip_tunnel_ctl()
So does "ip tunnel show/add/change/del" on a netlink-created device stop
working after this change when fallback tunnels are disabled? The
SIOCGETTUNNEL branch has no capability check, so this is reachable
unprivileged as well.
Is the gate needed for NULL-safety at all? Every remaining dereference
of itn->fb_tunnel_dev inside ip_tunnel_ctl() is guarded by a
"dev == itn->fb_tunnel_dev" comparison, so its only role appears to be the
dying-netns policy described in the commit message.
Would an explicit "itn is being destroyed" flag (or the
ip_tunnel_unregistering() semantics added by this same patch) work better
here, given that fb_tunnel_dev == NULL cannot distinguish "this netns
never had a fallback device" from "this netns is going away"?
Other in-tree code already treats a NULL fb_tunnel_dev as a normal state
for a healthy netns:
net/ipv4/ip_vti.c:vti_init_net() {
...
itn = net_generic(net, vti_net_id);
if (itn->fb_tunnel_dev)
vti_fb_tunnel_init(itn->fb_tunnel_dev);
...
}
> switch (cmd) {
> case SIOCGETTUNNEL:
> if (dev == itn->fb_tunnel_dev) {
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909234422.2416506-1-kuniyu%40google.com
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-09-11 2:44 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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-11 2:44 ` netdev-bot+sashiko
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 ` [PATCH v2 net-next 4/7] ip_tunnel: Centralise ip_tunnel_del() to ip_tunnel_dellink() 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
2026-09-09 23:43 ` [PATCH v2 net-next 6/7] ip_tunnel: Protect ip_tunnel_net.tunnels[] with mutex Kuniyuki Iwashima
2026-09-11 2:44 ` netdev-bot+sashiko
2026-09-09 23:43 ` [PATCH v2 net-next 7/7] ip_tunnel: Support per-netns device unregistration Kuniyuki Iwashima
2026-09-10 17:13 ` Ido Schimmel
2026-09-10 17:35 ` Kuniyuki Iwashima
2026-09-11 2:44 ` netdev-bot+sashiko
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.