]> Pileus Git - ~andy/linux/blobdiff - drivers/net/vxlan.c
vxlan: Restructure vxlan socket apis.
[~andy/linux] / drivers / net / vxlan.c
index f4c6db419ddb3b56a9ecede0b62988c33e1d7c29..b784ee668a4e2804987ee2b012d4aca14e63f136 100644 (file)
@@ -177,13 +177,18 @@ static inline struct hlist_head *vs_head(struct net *net, __be16 port)
 /* First remote destination for a forwarding entry.
  * Guaranteed to be non-NULL because remotes are never deleted.
  */
-static inline struct vxlan_rdst *first_remote(struct vxlan_fdb *fdb)
+static inline struct vxlan_rdst *first_remote_rcu(struct vxlan_fdb *fdb)
 {
-       return list_first_or_null_rcu(&fdb->remotes, struct vxlan_rdst, list);
+       return list_entry_rcu(fdb->remotes.next, struct vxlan_rdst, list);
+}
+
+static inline struct vxlan_rdst *first_remote_rtnl(struct vxlan_fdb *fdb)
+{
+       return list_first_entry(&fdb->remotes, struct vxlan_rdst, list);
 }
 
 /* Find VXLAN socket based on network namespace and UDP port */
-static struct vxlan_sock *vxlan_find_port(struct net *net, __be16 port)
+static struct vxlan_sock *vxlan_find_sock(struct net *net, __be16 port)
 {
        struct vxlan_sock *vs;
 
@@ -200,7 +205,7 @@ static struct vxlan_dev *vxlan_find_vni(struct net *net, u32 id, __be16 port)
        struct vxlan_sock *vs;
        struct vxlan_dev *vxlan;
 
-       vs = vxlan_find_port(net, port);
+       vs = vxlan_find_sock(net, port);
        if (!vs)
                return NULL;
 
@@ -297,7 +302,8 @@ static void vxlan_fdb_notify(struct vxlan_dev *vxlan,
        if (skb == NULL)
                goto errout;
 
-       err = vxlan_fdb_info(skb, vxlan, fdb, 0, 0, type, 0, first_remote(fdb));
+       err = vxlan_fdb_info(skb, vxlan, fdb, 0, 0, type, 0,
+                            first_remote_rtnl(fdb));
        if (err < 0) {
                /* -EMSGSIZE implies BUG in vxlan_nlmsg_size() */
                WARN_ON(err == -EMSGSIZE);
@@ -408,6 +414,26 @@ static struct vxlan_rdst *vxlan_fdb_find_rdst(struct vxlan_fdb *f,
        return NULL;
 }
 
+/* Replace destination of unicast mac */
+static int vxlan_fdb_replace(struct vxlan_fdb *f,
+                           __be32 ip, __be16 port, __u32 vni, __u32 ifindex)
+{
+       struct vxlan_rdst *rd;
+
+       rd = vxlan_fdb_find_rdst(f, ip, port, vni, ifindex);
+       if (rd)
+               return 0;
+
+       rd = list_first_entry_or_null(&f->remotes, struct vxlan_rdst, list);
+       if (!rd)
+               return 0;
+       rd->remote_ip = ip;
+       rd->remote_port = port;
+       rd->remote_vni = vni;
+       rd->remote_ifindex = ifindex;
+       return 1;
+}
+
 /* Add/update destinations for multicast */
 static int vxlan_fdb_append(struct vxlan_fdb *f,
                            __be32 ip, __be16 port, __u32 vni, __u32 ifindex)
@@ -458,6 +484,19 @@ static int vxlan_fdb_create(struct vxlan_dev *vxlan,
                        f->updated = jiffies;
                        notify = 1;
                }
+               if ((flags & NLM_F_REPLACE)) {
+                       /* Only change unicasts */
+                       if (!(is_multicast_ether_addr(f->eth_addr) ||
+                            is_zero_ether_addr(f->eth_addr))) {
+                               int rc = vxlan_fdb_replace(f, ip, port, vni,
+                                                          ifindex);
+
+                               if (rc < 0)
+                                       return rc;
+                               notify |= rc;
+                       } else
+                               return -EOPNOTSUPP;
+               }
                if ((flags & NLM_F_APPEND) &&
                    (is_multicast_ether_addr(f->eth_addr) ||
                     is_zero_ether_addr(f->eth_addr))) {
@@ -474,6 +513,11 @@ static int vxlan_fdb_create(struct vxlan_dev *vxlan,
                if (vxlan->addrmax && vxlan->addrcnt >= vxlan->addrmax)
                        return -ENOSPC;
 
+               /* Disallow replace to add a multicast entry */
+               if ((flags & NLM_F_REPLACE) &&
+                   (is_multicast_ether_addr(mac) || is_zero_ether_addr(mac)))
+                       return -EOPNOTSUPP;
+
                netdev_dbg(vxlan->dev, "add %pM -> %pI4\n", mac, &ip);
                f = kmalloc(sizeof(*f), GFP_ATOMIC);
                if (!f)
@@ -702,7 +746,7 @@ static bool vxlan_snoop(struct net_device *dev,
 
        f = vxlan_find_mac(vxlan, src_mac);
        if (likely(f)) {
-               struct vxlan_rdst *rdst = first_remote(f);
+               struct vxlan_rdst *rdst = first_remote_rcu(f);
 
                if (likely(rdst->remote_ip == src_ip))
                        return false;
@@ -967,7 +1011,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb)
                }
 
                f = vxlan_find_mac(vxlan, n->ha);
-               if (f && first_remote(f)->remote_ip == htonl(INADDR_ANY)) {
+               if (f && first_remote_rcu(f)->remote_ip == htonl(INADDR_ANY)) {
                        /* bridge-local neighbor */
                        neigh_release(n);
                        goto out;
@@ -1321,25 +1365,31 @@ static void vxlan_cleanup(unsigned long arg)
        mod_timer(&vxlan->age_timer, next_timer);
 }
 
+static void vxlan_vs_add_dev(struct vxlan_sock *vs, struct vxlan_dev *vxlan)
+{
+       __u32 vni = vxlan->default_dst.remote_vni;
+
+       vxlan->vn_sock = vs;
+       hlist_add_head_rcu(&vxlan->hlist, vni_head(vs, vni));
+}
+
 /* Setup stats when device is created */
 static int vxlan_init(struct net_device *dev)
 {
        struct vxlan_dev *vxlan = netdev_priv(dev);
        struct vxlan_net *vn = net_generic(dev_net(dev), vxlan_net_id);
        struct vxlan_sock *vs;
-       __u32 vni = vxlan->default_dst.remote_vni;
 
        dev->tstats = alloc_percpu(struct pcpu_tstats);
        if (!dev->tstats)
                return -ENOMEM;
 
        spin_lock(&vn->sock_lock);
-       vs = vxlan_find_port(dev_net(dev), vxlan->dst_port);
+       vs = vxlan_find_sock(dev_net(dev), vxlan->dst_port);
        if (vs) {
                /* If we have a socket with same port already, reuse it */
                atomic_inc(&vs->refcnt);
-               vxlan->vn_sock = vs;
-               hlist_add_head_rcu(&vxlan->hlist, vni_head(vs, vni));
+               vxlan_vs_add_dev(vs, vxlan);
        } else {
                /* otherwise make new socket outside of RTNL */
                dev_hold(dev);
@@ -1386,7 +1436,7 @@ static int vxlan_open(struct net_device *dev)
                return -ENOTCONN;
 
        if (IN_MULTICAST(ntohl(vxlan->default_dst.remote_ip)) &&
-           vxlan_group_used(vn, 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_join);
@@ -1589,6 +1639,7 @@ static void vxlan_del_work(struct work_struct *work)
 
 static struct vxlan_sock *vxlan_socket_create(struct net *net, __be16 port)
 {
+       struct vxlan_net *vn = net_generic(net, vxlan_net_id);
        struct vxlan_sock *vs;
        struct sock *sk;
        struct sockaddr_in vxlan_addr = {
@@ -1600,8 +1651,10 @@ static struct vxlan_sock *vxlan_socket_create(struct net *net, __be16 port)
        unsigned int h;
 
        vs = kmalloc(sizeof(*vs), GFP_KERNEL);
-       if (!vs)
+       if (!vs) {
+               pr_debug("memory alocation failure\n");
                return ERR_PTR(-ENOMEM);
+       }
 
        for (h = 0; h < VNI_HASH_SIZE; ++h)
                INIT_HLIST_HEAD(&vs->vni_list[h]);
@@ -1629,57 +1682,57 @@ static struct vxlan_sock *vxlan_socket_create(struct net *net, __be16 port)
                kfree(vs);
                return ERR_PTR(rc);
        }
+       atomic_set(&vs->refcnt, 1);
 
        /* Disable multicast loopback */
        inet_sk(sk)->mc_loop = 0;
+       spin_lock(&vn->sock_lock);
+       hlist_add_head_rcu(&vs->hlist, vs_head(net, port));
+       spin_unlock(&vn->sock_lock);
 
        /* Mark socket as an encapsulation socket. */
        udp_sk(sk)->encap_type = 1;
        udp_sk(sk)->encap_rcv = vxlan_udp_encap_recv;
        udp_encap_enable();
-       atomic_set(&vs->refcnt, 1);
+       return vs;
+}
+
+static struct vxlan_sock *vxlan_sock_add(struct net *net, __be16 port)
+{
+       struct vxlan_net *vn = net_generic(net, vxlan_net_id);
+       struct vxlan_sock *vs;
+
+       vs = vxlan_socket_create(net, port);
+       if (!IS_ERR(vs))
+               return vs;
+
+       spin_lock(&vn->sock_lock);
+       vs = vxlan_find_sock(net, port);
+       if (vs)
+               atomic_inc(&vs->refcnt);
+       else
+               vs = ERR_PTR(-EINVAL);
 
+       spin_unlock(&vn->sock_lock);
        return vs;
 }
 
 /* Scheduled at device creation to bind to a socket */
 static void vxlan_sock_work(struct work_struct *work)
 {
-       struct vxlan_dev *vxlan
-               = container_of(work, struct vxlan_dev, sock_work);
-       struct net_device *dev = vxlan->dev;
-       struct net *net = dev_net(dev);
-       __u32 vni = vxlan->default_dst.remote_vni;
-       __be16 port = vxlan->dst_port;
+       struct vxlan_dev *vxlan = container_of(work, struct vxlan_dev, sock_work);
+       struct net *net = dev_net(vxlan->dev);
        struct vxlan_net *vn = net_generic(net, vxlan_net_id);
-       struct vxlan_sock *nvs, *ovs;
-
-       nvs = vxlan_socket_create(net, port);
-       if (IS_ERR(nvs)) {
-               netdev_err(vxlan->dev, "Can not create UDP socket, %ld\n",
-                          PTR_ERR(nvs));
-               goto out;
-       }
+       __be16 port = vxlan->dst_port;
+       struct vxlan_sock *nvs;
 
+       nvs = vxlan_sock_add(net, port);
        spin_lock(&vn->sock_lock);
-       /* Look again to see if can reuse socket */
-       ovs = vxlan_find_port(net, port);
-       if (ovs) {
-               atomic_inc(&ovs->refcnt);
-               vxlan->vn_sock = ovs;
-               hlist_add_head_rcu(&vxlan->hlist, vni_head(ovs, vni));
-               spin_unlock(&vn->sock_lock);
-
-               sk_release_kernel(nvs->sock->sk);
-               kfree(nvs);
-       } else {
-               vxlan->vn_sock = nvs;
-               hlist_add_head_rcu(&nvs->hlist, vs_head(net, port));
-               hlist_add_head_rcu(&vxlan->hlist, vni_head(nvs, vni));
-               spin_unlock(&vn->sock_lock);
-       }
-out:
-       dev_put(dev);
+       if (!IS_ERR(nvs))
+               vxlan_vs_add_dev(nvs, vxlan);
+       spin_unlock(&vn->sock_lock);
+
+       dev_put(vxlan->dev);
 }
 
 static int vxlan_newlink(struct net *net, struct net_device *dev,
@@ -1793,10 +1846,9 @@ 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);
+       if (!hlist_unhashed(&vxlan->hlist))
+               hlist_del_rcu(&vxlan->hlist);
        spin_unlock(&vn->sock_lock);
 
        list_del(&vxlan->next);