datapath: Hold rcu_read_lock where we claim to.
[openvswitch] / datapath / vport.c
index ef4d7db206f3b7380e2d772481e64b42783b8259..691ab84ba8a15af10c6ee1d172fa0ca0aec14c0c 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/mutex.h>
 #include <linux/percpu.h>
 #include <linux/rtnetlink.h>
+#include <linux/compat.h>
 
 #include "vport.h"
 
@@ -187,30 +188,26 @@ vport_exit(void)
  * on device type).  This function is for userspace callers and assumes no
  * locks are held.
  */
-int
-vport_add(const struct odp_vport_add __user *uvport_config)
+static int
+do_vport_add(struct odp_vport_add *vport_config)
 {
-       struct odp_vport_add vport_config;
        struct vport *vport;
        int err = 0;
 
-       if (copy_from_user(&vport_config, uvport_config, sizeof(struct odp_vport_add)))
-               return -EFAULT;
-
-       vport_config.port_type[VPORT_TYPE_SIZE - 1] = '\0';
-       vport_config.devname[IFNAMSIZ - 1] = '\0';
+       vport_config->port_type[VPORT_TYPE_SIZE - 1] = '\0';
+       vport_config->devname[IFNAMSIZ - 1] = '\0';
 
        rtnl_lock();
 
-       vport = vport_locate(vport_config.devname);
+       vport = vport_locate(vport_config->devname);
        if (vport) {
                err = -EEXIST;
                goto out;
        }
 
        vport_lock();
-       vport = __vport_add(vport_config.devname, vport_config.port_type,
-                           vport_config.config);
+       vport = __vport_add(vport_config->devname, vport_config->port_type,
+                           vport_config->config);
        vport_unlock();
 
        if (IS_ERR(vport))
@@ -221,6 +218,35 @@ out:
        return err;
 }
 
+int
+vport_add(const struct odp_vport_add __user *uvport_config)
+{
+       struct odp_vport_add vport_config;
+
+       if (copy_from_user(&vport_config, uvport_config, sizeof(struct odp_vport_add)))
+               return -EFAULT;
+
+       return do_vport_add(&vport_config);
+}
+
+#ifdef CONFIG_COMPAT
+int
+compat_vport_add(struct compat_odp_vport_add *ucompat)
+{
+       struct compat_odp_vport_add compat;
+       struct odp_vport_add vport_config;
+
+       if (copy_from_user(&compat, ucompat, sizeof(struct compat_odp_vport_add)))
+               return -EFAULT;
+
+       memcpy(vport_config.port_type, compat.port_type, VPORT_TYPE_SIZE);
+       memcpy(vport_config.devname, compat.devname, IFNAMSIZ);
+       vport_config.config = compat_ptr(compat.config);
+
+       return do_vport_add(&vport_config);
+}
+#endif
+
 /**
  *     vport_mod - modify existing vport device (for userspace callers)
  *
@@ -230,28 +256,24 @@ out:
  * dependent on device type).  This function is for userspace callers and
  * assumes no locks are held.
  */
-int
-vport_mod(const struct odp_vport_mod __user *uvport_config)
+static int
+do_vport_mod(struct odp_vport_mod *vport_config)
 {
-       struct odp_vport_mod vport_config;
        struct vport *vport;
        int err;
 
-       if (copy_from_user(&vport_config, uvport_config, sizeof(struct odp_vport_mod)))
-               return -EFAULT;
-
-       vport_config.devname[IFNAMSIZ - 1] = '\0';
+       vport_config->devname[IFNAMSIZ - 1] = '\0';
 
        rtnl_lock();
 
-       vport = vport_locate(vport_config.devname);
+       vport = vport_locate(vport_config->devname);
        if (!vport) {
                err = -ENODEV;
                goto out;
        }
 
        vport_lock();
-       err = __vport_mod(vport, vport_config.config);
+       err = __vport_mod(vport, vport_config->config);
        vport_unlock();
 
 out:
@@ -259,6 +281,34 @@ out:
        return err;
 }
 
+int
+vport_mod(const struct odp_vport_mod __user *uvport_config)
+{
+       struct odp_vport_mod vport_config;
+
+       if (copy_from_user(&vport_config, uvport_config, sizeof(struct odp_vport_mod)))
+               return -EFAULT;
+
+       return do_vport_mod(&vport_config);
+}
+
+#ifdef CONFIG_COMPAT
+int
+compat_vport_mod(struct compat_odp_vport_mod *ucompat)
+{
+       struct compat_odp_vport_mod compat;
+       struct odp_vport_mod vport_config;
+
+       if (copy_from_user(&compat, ucompat, sizeof(struct compat_odp_vport_mod)))
+               return -EFAULT;
+
+       memcpy(vport_config.devname, compat.devname, IFNAMSIZ);
+       vport_config.config = compat_ptr(compat.config);
+
+       return do_vport_mod(&vport_config);
+}
+#endif
+
 /**
  *     vport_del - delete existing vport device (for userspace callers)
  *
@@ -277,10 +327,13 @@ vport_del(const char __user *udevname)
        struct vport *vport;
        struct dp_port *dp_port;
        int err = 0;
+       int retval;
 
-       if (strncpy_from_user(devname, udevname, IFNAMSIZ - 1) < 0)
+       retval = strncpy_from_user(devname, udevname, IFNAMSIZ);
+       if (retval < 0)
                return -EFAULT;
-       devname[IFNAMSIZ - 1] = '\0';
+       else if (retval >= IFNAMSIZ)
+               return -ENAMETOOLONG;
 
        rtnl_lock();
 
@@ -347,9 +400,12 @@ vport_stats_get(struct odp_vport_stats_req __user *ustats_req)
                goto out;
        }
 
-       if (vport->ops->get_stats)
+       if (vport->ops->get_stats) {
+               rcu_read_lock();
                err = vport->ops->get_stats(vport, &stats_req.stats);
-       else if (vport->ops->flags & VPORT_F_GEN_STATS) {
+               rcu_read_unlock();
+
+       } else if (vport->ops->flags & VPORT_F_GEN_STATS) {
                int i;
 
                memset(&stats_req.stats, 0, sizeof(struct odp_vport_stats));
@@ -422,7 +478,9 @@ vport_ether_get(struct odp_vport_ether __user *uvport_ether)
                goto out;
        }
 
+       rcu_read_lock();
        memcpy(vport_ether.ether_addr, vport_get_addr(vport), ETH_ALEN);
+       rcu_read_unlock();
 
 out:
        vport_unlock();
@@ -579,11 +637,17 @@ vport_locate(const char *name)
                dump_stack();
        }
 
+       rcu_read_lock();
+
        hlist_for_each_entry(vport, node, bucket, hash_node)
                if (!strcmp(name, vport_get_name(vport)))
-                       return vport;
+                       goto out;
+
+       vport = NULL;
 
-       return NULL;
+out:
+       rcu_read_unlock();
+       return vport;
 }
 
 static void
@@ -1034,16 +1098,21 @@ vport_get_mtu(const struct vport *vport)
  * @vport: vport that received the packet
  * @skb: skb that was received
  *
- * Must be called with rcu_read_lock and bottom halves disabled.  The packet
- * cannot be shared and skb->data should point to the Ethernet header.
+ * Must be called with rcu_read_lock.  The packet cannot be shared and
+ * skb->data should point to the Ethernet header.  The caller must have already
+ * called compute_ip_summed() to initialize the checksumming fields.
  */
 void
 vport_receive(struct vport *vport, struct sk_buff *skb)
 {
        struct dp_port *dp_port = vport_get_dp_port(vport);
 
-       if (!dp_port)
+       if (!dp_port) {
+               vport_record_error(vport, VPORT_E_RX_DROPPED);
+               kfree_skb(skb);
+
                return;
+       }
 
        if (vport->ops->flags & VPORT_F_GEN_STATS) {
                struct vport_percpu_stats *stats;