]> Pileus Git - ~andy/linux/blobdiff - drivers/net/vxlan.c
Merge tag 'asoc-v3.11-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie...
[~andy/linux] / drivers / net / vxlan.c
index 0ba1e7edbb1b443d8fe34a685baf4fe934c91175..f4c6db419ddb3b56a9ecede0b62988c33e1d7c29 100644 (file)
@@ -136,7 +136,8 @@ struct vxlan_dev {
        u32               flags;        /* VXLAN_F_* below */
 
        struct work_struct sock_work;
-       struct work_struct igmp_work;
+       struct work_struct igmp_join;
+       struct work_struct igmp_leave;
 
        unsigned long     age_interval;
        struct timer_list age_timer;
@@ -736,7 +737,6 @@ static bool vxlan_snoop(struct net_device *dev,
        return false;
 }
 
-
 /* See if multicast group is already in use by other ID */
 static bool vxlan_group_used(struct vxlan_net *vn, __be32 remote_ip)
 {
@@ -770,12 +770,13 @@ static void vxlan_sock_release(struct vxlan_net *vn, struct vxlan_sock *vs)
        queue_work(vxlan_wq, &vs->del_work);
 }
 
-/* Callback to update multicast group membership.
- * Scheduled when vxlan goes up/down.
+/* Callback to update multicast group membership when first VNI on
+ * multicast asddress is brought up
+ * Done as workqueue because ip_mc_join_group acquires RTNL.
  */
-static void vxlan_igmp_work(struct work_struct *work)
+static void vxlan_igmp_join(struct work_struct *work)
 {
-       struct vxlan_dev *vxlan = container_of(work, struct vxlan_dev, igmp_work);
+       struct vxlan_dev *vxlan = container_of(work, struct vxlan_dev, igmp_join);
        struct vxlan_net *vn = net_generic(dev_net(vxlan->dev), vxlan_net_id);
        struct vxlan_sock *vs = vxlan->vn_sock;
        struct sock *sk = vs->sock->sk;
@@ -785,10 +786,27 @@ static void vxlan_igmp_work(struct work_struct *work)
        };
 
        lock_sock(sk);
-       if (vxlan_group_used(vn, vxlan->default_dst.remote_ip))
-               ip_mc_join_group(sk, &mreq);
-       else
-               ip_mc_leave_group(sk, &mreq);
+       ip_mc_join_group(sk, &mreq);
+       release_sock(sk);
+
+       vxlan_sock_release(vn, vs);
+       dev_put(vxlan->dev);
+}
+
+/* Inverse of vxlan_igmp_join when last VNI is brought down */
+static void vxlan_igmp_leave(struct work_struct *work)
+{
+       struct vxlan_dev *vxlan = container_of(work, struct vxlan_dev, igmp_leave);
+       struct vxlan_net *vn = net_generic(dev_net(vxlan->dev), vxlan_net_id);
+       struct vxlan_sock *vs = vxlan->vn_sock;
+       struct sock *sk = vs->sock->sk;
+       struct ip_mreqn mreq = {
+               .imr_multiaddr.s_addr   = vxlan->default_dst.remote_ip,
+               .imr_ifindex            = vxlan->default_dst.remote_ifindex,
+       };
+
+       lock_sock(sk);
+       ip_mc_leave_group(sk, &mreq);
        release_sock(sk);
 
        vxlan_sock_release(vn, vs);
@@ -1359,6 +1377,7 @@ static void vxlan_uninit(struct net_device *dev)
 /* Start ageing timer and join group when device is brought up */
 static int vxlan_open(struct net_device *dev)
 {
+       struct vxlan_net *vn = net_generic(dev_net(dev), vxlan_net_id);
        struct vxlan_dev *vxlan = netdev_priv(dev);
        struct vxlan_sock *vs = vxlan->vn_sock;
 
@@ -1366,10 +1385,11 @@ static int vxlan_open(struct net_device *dev)
        if (!vs)
                return -ENOTCONN;
 
-       if (IN_MULTICAST(ntohl(vxlan->default_dst.remote_ip))) {
+       if (IN_MULTICAST(ntohl(vxlan->default_dst.remote_ip)) &&
+           ! vxlan_group_used(vn, vxlan->default_dst.remote_ip)) {
                vxlan_sock_hold(vs);
                dev_hold(dev);
-               queue_work(vxlan_wq, &vxlan->igmp_work);
+               queue_work(vxlan_wq, &vxlan->igmp_join);
        }
 
        if (vxlan->age_interval)
@@ -1400,13 +1420,15 @@ static void vxlan_flush(struct vxlan_dev *vxlan)
 /* Cleanup timer and forwarding table on shutdown */
 static int vxlan_stop(struct net_device *dev)
 {
+       struct vxlan_net *vn = net_generic(dev_net(dev), vxlan_net_id);
        struct vxlan_dev *vxlan = netdev_priv(dev);
        struct vxlan_sock *vs = vxlan->vn_sock;
 
-       if (vs && IN_MULTICAST(ntohl(vxlan->default_dst.remote_ip))) {
+       if (vs && IN_MULTICAST(ntohl(vxlan->default_dst.remote_ip)) &&
+           ! vxlan_group_used(vn, vxlan->default_dst.remote_ip)) {
                vxlan_sock_hold(vs);
                dev_hold(dev);
-               queue_work(vxlan_wq, &vxlan->igmp_work);
+               queue_work(vxlan_wq, &vxlan->igmp_leave);
        }
 
        del_timer_sync(&vxlan->age_timer);
@@ -1471,7 +1493,8 @@ static void vxlan_setup(struct net_device *dev)
 
        INIT_LIST_HEAD(&vxlan->next);
        spin_lock_init(&vxlan->hash_lock);
-       INIT_WORK(&vxlan->igmp_work, vxlan_igmp_work);
+       INIT_WORK(&vxlan->igmp_join, vxlan_igmp_join);
+       INIT_WORK(&vxlan->igmp_leave, vxlan_igmp_leave);
        INIT_WORK(&vxlan->sock_work, vxlan_sock_work);
 
        init_timer_deferrable(&vxlan->age_timer);
@@ -1767,9 +1790,15 @@ static int vxlan_newlink(struct net *net, struct net_device *dev,
 
 static void vxlan_dellink(struct net_device *dev, struct list_head *head)
 {
+       struct vxlan_net *vn = net_generic(dev_net(dev), vxlan_net_id);
        struct vxlan_dev *vxlan = netdev_priv(dev);
 
+       flush_workqueue(vxlan_wq);
+
+       spin_lock(&vn->sock_lock);
        hlist_del_rcu(&vxlan->hlist);
+       spin_unlock(&vn->sock_lock);
+
        list_del(&vxlan->next);
        unregister_netdevice_queue(dev, head);
 }
@@ -1872,10 +1901,12 @@ static __net_exit void vxlan_exit_net(struct net *net)
 {
        struct vxlan_net *vn = net_generic(net, vxlan_net_id);
        struct vxlan_dev *vxlan;
+       LIST_HEAD(list);
 
        rtnl_lock();
        list_for_each_entry(vxlan, &vn->vxlan_list, next)
-               dev_close(vxlan->dev);
+               unregister_netdevice_queue(vxlan->dev, &list);
+       unregister_netdevice_many(&list);
        rtnl_unlock();
 }