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);