diff options
| author | Paolo Abeni <pabeni@redhat.com> | 2026-09-15 13:21:34 +0200 |
|---|---|---|
| committer | Paolo Abeni <pabeni@redhat.com> | 2026-09-15 13:21:35 +0200 |
| commit | 7bbb398689fe56196c321f4106bf7f570bfa3eaa (patch) | |
| tree | e8ac8d547f9b5e16539f1969166a4564fbfe0b63 | |
| parent | 1142eb185b05db61a78130890fc4ed268f4cb4e6 (diff) | |
| parent | 8f3c724202578644145506486dc1cede108c0a2c (diff) | |
| download | linux-next-7bbb398689fe56196c321f4106bf7f570bfa3eaa.tar.gz linux-next-7bbb398689fe56196c321f4106bf7f570bfa3eaa.zip | |
Merge branch 'ip_tunnel-support-per-netns-device-unregistration'
Kuniyuki Iwashima says:
====================
ip_tunnel: Support per-netns device unregistration.
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.
v2: https://lore.kernel.org/netdev/20260909234422.2416506-1-kuniyu@google.com/
v1: https://lore.kernel.org/netdev/20260907225846.3787676-1-kuniyu@google.com/
====================
Link: https://patch.msgid.link/20260912230043.2586313-1-kuniyu@google.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
| -rw-r--r-- | include/net/ip_tunnels.h | 2 | ||||
| -rw-r--r-- | net/ipv4/ip_gre.c | 6 | ||||
| -rw-r--r-- | net/ipv4/ip_tunnel.c | 132 | ||||
| -rw-r--r-- | net/ipv4/ip_vti.c | 2 | ||||
| -rw-r--r-- | net/ipv4/ipip.c | 2 | ||||
| -rw-r--r-- | net/ipv4/ipmr.c | 12 |
6 files changed, 108 insertions, 48 deletions
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index be4cc10f88ed..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; }; @@ -401,7 +402,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 13b5e35e8790..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) @@ -219,7 +224,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,13 +898,37 @@ static void ip_tunnel_update(struct ip_tunnel_net *itn, netdev_state_change(dev); } +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_net(net, dev, head); +} + 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 net *orig_net = dev_net(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; + + DEBUG_NET_WARN_ON_ONCE(netdev_need_ops_lock(dev)); + + itn = net_generic(net, t->ip_tnl_net_id); + + mutex_lock(&itn->tunnels_lock); + + if (!check_net(net)) { + err = -EBUSY; + goto done; + } switch (cmd) { case SIOCGETTUNNEL: @@ -959,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 { @@ -982,7 +1012,10 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p, goto done; dev = t->dev; } - unregister_netdevice(dev); + + if (!ip_tunnel_unregistering(t)) + __ip_tunnel_dellink(orig_net, dev, &dev_kill_list); + err = 0; break; @@ -991,6 +1024,10 @@ 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; } EXPORT_SYMBOL_GPL(ip_tunnel_ctl); @@ -1085,8 +1122,10 @@ 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); + if (!ip_tunnel_unregistering(tunnel)) + __ip_tunnel_dellink(dev_net(dev), dev, head); + mutex_unlock(&itn->tunnels_lock); } } EXPORT_SYMBOL_GPL(ip_tunnel_dellink); @@ -1118,6 +1157,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; @@ -1149,31 +1190,27 @@ 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); - 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); + mutex_lock(&itn->tunnels_lock); + + 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); + __ip_tunnel_dellink(net, t->dev, head); } + + mutex_unlock(&itn->tunnels_lock); } EXPORT_SYMBOL_GPL(ip_tunnel_delete_net); @@ -1181,21 +1218,25 @@ 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); + mutex_lock(&itn->tunnels_lock); + 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,31 +1263,41 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev, goto err_dev_set_mtu; ip_tunnel_add(itn, nt); - return 0; +out: + mutex_unlock(&itn->tunnels_lock); + + 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; + mutex_lock(&itn->tunnels_lock); + 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 +1310,23 @@ 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; + } } } + if (ip_tunnel_unregistering(t)) { + err = -ENODEV; + goto out; + } + ip_tunnel_update(itn, t, dev, p, !tb[IFLA_MTU], fwmark); - return 0; +out: + mutex_unlock(&itn->tunnels_lock); + + return err; } EXPORT_SYMBOL_GPL(ip_tunnel_changelink); @@ -1303,13 +1364,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)); - if (itn->fb_tunnel_dev == dev) - WRITE_ONCE(itn->fb_tunnel_dev, NULL); dst_cache_reset(&tunnel->dst_cache); } 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 = { 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; |
