From 578fefdbbdaf3d02949ad9bb94736c81de4a02a8 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 23:00:27 +0000 Subject: ipmr: Call ->dellink() to remove DVMRP tunnel device. 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 Link: https://patch.msgid.link/20260912230043.2586313-2-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- 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 1187bfc8a09d..e4c51ca47301 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; -- cgit v1.2.3 From 0239bd767b3ae5d361f348a88babd33a15d498a6 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 23:00:28 +0000 Subject: ip_tunnel: Set itn->fb_tunnel_dev to NULL in ip_tunnel_delete_net(). 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 Link: https://patch.msgid.link/20260912230043.2586313-3-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- 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 13b5e35e8790..d74200ab5762 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -1153,26 +1153,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); @@ -1308,8 +1301,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); } -- cgit v1.2.3 From 7ba090b04ab87b90e714e5fb3ba8748f6f04d2a0 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 23:00:29 +0000 Subject: ip_tunnel: Don't pass rtnl_link_ops to ip_tunnel_delete_net(). ip_tunnel_delete_net() no longer uses the 3rd argument, struct rtnl_link_ops *ops. Let's remove it. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260912230043.2586313-4-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- 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 be4cc10f88ed..a78dfbb98044 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -401,7 +401,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 d74200ab5762..e6bbc5e4c357 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -1149,7 +1149,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 = { -- cgit v1.2.3 From e53013fe10c22fc4e2cc1b45180790a5429c7aa9 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 23:00:30 +0000 Subject: ip_tunnel: Centralise ip_tunnel_del() to ip_tunnel_dellink(). 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 Link: https://patch.msgid.link/20260912230043.2586313-5-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- 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 e6bbc5e4c357..7d7baaa57741 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -895,10 +895,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: @@ -982,7 +985,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; @@ -991,6 +995,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); @@ -1164,7 +1170,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); @@ -1295,11 +1301,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); } -- cgit v1.2.3 From 6faf19060bd0123b69d7e9b04244fd577223ac3c Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 23:00:31 +0000 Subject: ip_tunnel: Unify error paths in ip_tunnel_newlink() and ip_tunnel_changelink(). 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 Link: https://patch.msgid.link/20260912230043.2586313-6-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- 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 7d7baaa57741..d560ae9f0222 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -1179,21 +1179,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; @@ -1220,22 +1222,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; @@ -1243,8 +1249,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; @@ -1257,13 +1265,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); -- cgit v1.2.3 From 6724a7baf149ee883eb0b694e8818eacb899f5d8 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 23:00:32 +0000 Subject: ip_tunnel: Protect ip_tunnel_net.tunnels[] with mutex. 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 Link: https://patch.msgid.link/20260912230043.2586313-7-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- 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 a78dfbb98044..7102aa11fae2 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -218,6 +218,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 d560ae9f0222..d44976395c7c 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) && @@ -892,6 +893,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) { @@ -901,8 +912,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) { @@ -986,7 +1001,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; @@ -995,6 +1010,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; @@ -1091,8 +1108,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); @@ -1124,6 +1142,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; @@ -1162,6 +1182,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++) { @@ -1170,8 +1192,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); @@ -1187,6 +1211,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; @@ -1223,6 +1249,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: @@ -1246,6 +1274,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) { @@ -1274,6 +1304,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); -- cgit v1.2.3 From 8f3c724202578644145506486dc1cede108c0a2c Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 12 Sep 2026 23:00:33 +0000 Subject: ip_tunnel: Support per-netns device unregistration. 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 check_net(t->net), 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 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 Link: https://patch.msgid.link/20260912230043.2586313-8-kuniyu@google.com Reviewed-by: Ido Schimmel Signed-off-by: Paolo Abeni --- 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 d44976395c7c..0875474a578a 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) @@ -893,20 +898,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; @@ -918,6 +925,11 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p, mutex_lock(&itn->tunnels_lock); + if (!check_net(net)) { + err = -EBUSY; + goto done; + } + switch (cmd) { case SIOCGETTUNNEL: if (dev == itn->fb_tunnel_dev) { @@ -977,7 +989,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 { @@ -1001,7 +1013,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; @@ -1109,7 +1123,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); } } @@ -1192,7 +1207,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); @@ -1302,6 +1317,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); -- cgit v1.2.3