summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/net/ip_tunnels.h2
-rw-r--r--net/ipv4/ip_gre.c6
-rw-r--r--net/ipv4/ip_tunnel.c132
-rw-r--r--net/ipv4/ip_vti.c2
-rw-r--r--net/ipv4/ipip.c2
-rw-r--r--net/ipv4/ipmr.c12
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;