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);
--
2.55.0.1007.g17ff1f9808-goog