diff options
author | David Ahern <dsa@cumulusnetworks.com> | 2016-06-14 11:37:21 -0700 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-06-15 21:39:59 -0700 |
commit | 810e530bfa1116079bf94b8c93b99b0208959261 (patch) | |
tree | 2c7d8cc68e98a05e5fd841f20ba241b51051dc3f /drivers/net | |
parent | 042ce7223095fc8950e769decc55f39fe85f87b1 (diff) |
net: vrf: Switch dst dev to loopback on device delete
Attempting to delete a VRF device with a socket bound to it can stall:
unregister_netdevice: waiting for red to become free. Usage count = 1
The unregister is waiting for the dst to be released and with it
references to the vrf device. Similar to dst_ifdown switch the dst
dev to loopback on delete for all of the dst's for the vrf device
and release the references to the vrf device.
Fixes: 193125dbd8eb2 ("net: Introduce VRF device driver")
Fixes: 35402e3136634 ("net: Add IPv6 support to VRF device")
Signed-off-by: David Ahern <dsa@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/vrf.c | 55 |
1 files changed, 42 insertions, 13 deletions
diff --git a/drivers/net/vrf.c b/drivers/net/vrf.c index e3fc6d32a289..32173aa9208e 100644 --- a/drivers/net/vrf.c +++ b/drivers/net/vrf.c @@ -378,23 +378,37 @@ static int vrf_output6(struct net *net, struct sock *sk, struct sk_buff *skb) } /* holding rtnl */ -static void vrf_rt6_release(struct net_vrf *vrf) +static void vrf_rt6_release(struct net_device *dev, struct net_vrf *vrf) { struct rt6_info *rt6 = rtnl_dereference(vrf->rt6); struct rt6_info *rt6_local = rtnl_dereference(vrf->rt6_local); + struct net *net = dev_net(dev); + struct dst_entry *dst; RCU_INIT_POINTER(vrf->rt6, NULL); RCU_INIT_POINTER(vrf->rt6_local, NULL); synchronize_rcu(); - if (rt6) - dst_release(&rt6->dst); + /* move dev in dst's to loopback so this VRF device can be deleted + * - based on dst_ifdown + */ + if (rt6) { + dst = &rt6->dst; + dev_put(dst->dev); + dst->dev = net->loopback_dev; + dev_hold(dst->dev); + dst_release(dst); + } if (rt6_local) { if (rt6_local->rt6i_idev) in6_dev_put(rt6_local->rt6i_idev); - dst_release(&rt6_local->dst); + dst = &rt6_local->dst; + dev_put(dst->dev); + dst->dev = net->loopback_dev; + dev_hold(dst->dev); + dst_release(dst); } } @@ -449,7 +463,7 @@ out: return rc; } #else -static void vrf_rt6_release(struct net_vrf *vrf) +static void vrf_rt6_release(struct net_device *dev, struct net_vrf *vrf) { } @@ -518,20 +532,35 @@ static int vrf_output(struct net *net, struct sock *sk, struct sk_buff *skb) } /* holding rtnl */ -static void vrf_rtable_release(struct net_vrf *vrf) +static void vrf_rtable_release(struct net_device *dev, struct net_vrf *vrf) { struct rtable *rth = rtnl_dereference(vrf->rth); struct rtable *rth_local = rtnl_dereference(vrf->rth_local); + struct net *net = dev_net(dev); + struct dst_entry *dst; RCU_INIT_POINTER(vrf->rth, NULL); RCU_INIT_POINTER(vrf->rth_local, NULL); synchronize_rcu(); - if (rth) - dst_release(&rth->dst); + /* move dev in dst's to loopback so this VRF device can be deleted + * - based on dst_ifdown + */ + if (rth) { + dst = &rth->dst; + dev_put(dst->dev); + dst->dev = net->loopback_dev; + dev_hold(dst->dev); + dst_release(dst); + } - if (rth_local) - dst_release(&rth_local->dst); + if (rth_local) { + dst = &rth_local->dst; + dev_put(dst->dev); + dst->dev = net->loopback_dev; + dev_hold(dst->dev); + dst_release(dst); + } } static int vrf_rtable_create(struct net_device *dev) @@ -633,8 +662,8 @@ static void vrf_dev_uninit(struct net_device *dev) struct net_device *port_dev; struct list_head *iter; - vrf_rtable_release(vrf); - vrf_rt6_release(vrf); + vrf_rtable_release(dev, vrf); + vrf_rt6_release(dev, vrf); netdev_for_each_lower_dev(dev, port_dev, iter) vrf_del_slave(dev, port_dev); @@ -669,7 +698,7 @@ static int vrf_dev_init(struct net_device *dev) return 0; out_rth: - vrf_rtable_release(vrf); + vrf_rtable_release(dev, vrf); out_stats: free_percpu(dev->dstats); dev->dstats = NULL; |