/* Set up our datapath device. */
        BUILD_BUG_ON(sizeof(internal_dev_port.devname) != sizeof(devname));
        strcpy(internal_dev_port.devname, devname);
-       internal_dev_port.flags = ODP_PORT_INTERNAL;
+       strcpy(internal_dev_port.type, "internal");
        err = new_dp_port(dp, &internal_dev_port, ODPP_LOCAL);
        if (err) {
                if (err == -EBUSY)
        return 0;
 
 err_destroy_local_port:
-       dp_detach_port(dp->ports[ODPP_LOCAL], 1);
+       dp_detach_port(dp->ports[ODPP_LOCAL]);
 err_destroy_table:
        tbl_destroy(dp->table, NULL);
 err_free_dp:
 
        list_for_each_entry_safe (p, n, &dp->port_list, node)
                if (p->port_no != ODPP_LOCAL)
-                       dp_detach_port(p, 1);
+                       dp_detach_port(p);
 
        dp_sysfs_del_dp(dp);
 
        rcu_assign_pointer(dps[dp->dp_idx], NULL);
 
-       dp_detach_port(dp->ports[ODPP_LOCAL], 1);
+       dp_detach_port(dp->ports[ODPP_LOCAL]);
 
        tbl_destroy(dp->table, flow_free_tbl);
 
 /* Called with RTNL lock and dp_mutex. */
 static int new_dp_port(struct datapath *dp, struct odp_port *odp_port, int port_no)
 {
+       struct vport_parms parms;
        struct vport *vport;
        struct dp_port *p;
        int err;
 
-       vport = vport_locate(odp_port->devname);
-       if (!vport) {
-               struct vport_parms parms;
+       parms.name = odp_port->devname;
+       parms.type = odp_port->type;
+       parms.config = odp_port->config;
 
-               parms.name = odp_port->devname;
-               parms.type = odp_port->flags & ODP_PORT_INTERNAL ? "internal" : "netdev";
-               parms.config = NULL;
+       vport_lock();
+       vport = vport_add(&parms);
+       vport_unlock();
 
-               vport_lock();
-               vport = vport_add(&parms);
-               vport_unlock();
-
-               if (IS_ERR(vport))
-                       return PTR_ERR(vport);
-       }
+       if (IS_ERR(vport))
+               return PTR_ERR(vport);
 
        p = kzalloc(sizeof(*p), GFP_KERNEL);
        if (!p)
        if (copy_from_user(&port, portp, sizeof port))
                goto out;
        port.devname[IFNAMSIZ - 1] = '\0';
+       port.type[VPORT_TYPE_SIZE - 1] = '\0';
 
        rtnl_lock();
        dp = get_dp_locked(dp_idx);
        return err;
 }
 
-int dp_detach_port(struct dp_port *p, int may_delete)
+int dp_detach_port(struct dp_port *p)
 {
        struct vport *vport = p->vport;
        int err;
        /* Then wait until no one is still using it, and destroy it. */
        synchronize_rcu();
 
-       if (may_delete) {
-               const char *port_type = vport_get_type(vport);
-
-               if (!strcmp(port_type, "netdev") || !strcmp(port_type, "internal")) {
-                       vport_lock();
-                       vport_del(vport);
-                       vport_unlock();
-               }
-       }
+       vport_lock();
+       vport_del(vport);
+       vport_unlock();
 
        kobject_put(&p->kobj);
 
        if (!p)
                goto out_unlock_dp;
 
-       err = dp_detach_port(p, 1);
+       err = dp_detach_port(p);
 
 out_unlock_dp:
        mutex_unlock(&dp->mutex);
 
        rcu_read_lock();
        strncpy(op.devname, vport_get_name(p->vport), sizeof op.devname);
+       strncpy(op.type, vport_get_type(p->vport), sizeof op.type);
        rcu_read_unlock();
 
        op.port = p->port_no;
-       op.flags = is_internal_vport(p->vport) ? ODP_PORT_INTERNAL : 0;
 
        return copy_to_user(uop, &op, sizeof op) ? -EFAULT : 0;
 }
                err = destroy_dp(dp_idx);
                goto exit;
 
-       case ODP_PORT_ATTACH:
+       case ODP_VPORT_ATTACH:
                err = attach_port(dp_idx, (struct odp_port __user *)argp);
                goto exit;
 
-       case ODP_PORT_DETACH:
+       case ODP_VPORT_DETACH:
                err = get_user(port_no, (int __user *)argp);
                if (!err)
                        err = detach_port(dp_idx, port_no);
                goto exit;
 
-       case ODP_VPORT_ADD:
-               err = vport_user_add((struct odp_vport_add __user *)argp);
-               goto exit;
-
        case ODP_VPORT_MOD:
-               err = vport_user_mod((struct odp_vport_mod __user *)argp);
-               goto exit;
-
-       case ODP_VPORT_DEL:
-               err = vport_user_del((char __user *)argp);
+               err = vport_user_mod((struct odp_port __user *)argp);
                goto exit;
 
        case ODP_VPORT_STATS_GET:
                        dp->sflow_probability = sflow_probability;
                break;
 
-       case ODP_PORT_QUERY:
+       case ODP_VPORT_QUERY:
                err = query_port(dp, (struct odp_port __user *)argp);
                break;
 
-       case ODP_PORT_LIST:
+       case ODP_VPORT_LIST:
                err = list_ports(dp, (struct odp_portvec __user *)argp);
                break;
 
                return openvswitch_ioctl(f, cmd, argp);
 
        case ODP_DP_CREATE:
-       case ODP_PORT_ATTACH:
-       case ODP_PORT_DETACH:
-       case ODP_VPORT_DEL:
+       case ODP_VPORT_ATTACH:
+       case ODP_VPORT_DETACH:
+       case ODP_VPORT_MOD:
        case ODP_VPORT_MTU_SET:
        case ODP_VPORT_MTU_GET:
        case ODP_VPORT_ETHER_SET:
        case ODP_GET_LISTEN_MASK:
        case ODP_SET_SFLOW_PROBABILITY:
        case ODP_GET_SFLOW_PROBABILITY:
-       case ODP_PORT_QUERY:
+       case ODP_VPORT_QUERY:
                /* Ioctls that just need their pointer argument extended. */
                return openvswitch_ioctl(f, cmd, (unsigned long)compat_ptr(argp));
-
-       case ODP_VPORT_ADD32:
-               return compat_vport_user_add(compat_ptr(argp));
-
-       case ODP_VPORT_MOD32:
-               return compat_vport_user_mod(compat_ptr(argp));
        }
 
        dp = get_dp_locked(dp_idx);
                goto exit;
 
        switch (cmd) {
-       case ODP_PORT_LIST32:
+       case ODP_VPORT_LIST32:
                err = compat_list_ports(dp, compat_ptr(argp));
                break;
 
 
 extern int (*dp_ioctl_hook)(struct net_device *dev, struct ifreq *rq, int cmd);
 
 void dp_process_received_packet(struct dp_port *, struct sk_buff *);
-int dp_detach_port(struct dp_port *, int may_delete);
+int dp_detach_port(struct dp_port *);
 int dp_output_control(struct datapath *, struct sk_buff *, int, u32 arg);
 int dp_min_mtu(const struct datapath *dp);
 void set_internal_devs_mtu(const struct datapath *dp);
 
        switch (event) {
        case NETDEV_UNREGISTER:
                mutex_lock(&dp->mutex);
-               dp_detach_port(p, 1);
+               dp_detach_port(p);
                mutex_unlock(&dp->mutex);
                break;
 
 
 #define ODP_FLOW_DEL32         _IOWR('O', 17, struct compat_odp_flow)
 #define ODP_EXECUTE32          _IOR('O', 18, struct compat_odp_execute)
 #define ODP_FLOW_DEL32         _IOWR('O', 17, struct compat_odp_flow)
-#define ODP_VPORT_ADD32                _IOR('O', 21, struct compat_odp_vport_add)
-#define ODP_VPORT_MOD32                _IOR('O', 22, struct compat_odp_vport_mod)
 
 struct compat_odp_portvec {
        compat_uptr_t ports;
        compat_uptr_t data;
        u32 length;
 };
-
-struct compat_odp_vport_add {
-       char port_type[VPORT_TYPE_SIZE];
-       char devname[16];            /* IFNAMSIZ */
-       compat_uptr_t config;
-};
-
-struct compat_odp_vport_mod {
-       char devname[16];            /* IFNAMSIZ */
-       compat_uptr_t config;
-};
 #endif /* CONFIG_COMPAT */
 
 #endif /* odp-compat.h */
 
        return sent_len;
 }
 
-static int set_config(const void __user *uconfig, const struct tnl_ops *tnl_ops,
+static int set_config(const void *config, const struct tnl_ops *tnl_ops,
                      const struct vport *cur_vport,
                      struct tnl_mutable_config *mutable)
 {
        const struct vport *old_vport;
        const struct tnl_mutable_config *old_mutable;
 
-       if (copy_from_user(&mutable->port_config, uconfig, sizeof(struct tnl_port_config)))
-               return -EFAULT;
+       mutable->port_config = *(struct tnl_port_config *)config;
 
        if (mutable->port_config.daddr == 0)
                return -EINVAL;
        return ERR_PTR(err);
 }
 
-int tnl_modify(struct vport *vport, const void __user *config)
+int tnl_modify(struct vport *vport, struct odp_port *port)
 {
        struct tnl_vport *tnl_vport = tnl_vport_priv(vport);
        struct tnl_mutable_config *mutable;
                goto error;
        }
 
-       err = set_config(config, tnl_vport->tnl_ops, vport, mutable);
+       err = set_config(port->config, tnl_vport->tnl_ops, vport, mutable);
        if (err)
                goto error_free;
 
 
 
 struct vport *tnl_create(const struct vport_parms *, const struct vport_ops *,
                         const struct tnl_ops *);
-int tnl_modify(struct vport *, const void __user *config);
+int tnl_modify(struct vport *, struct odp_port *);
 int tnl_destroy(struct vport *);
 int tnl_set_mtu(struct vport *vport, int mtu);
 int tnl_set_addr(struct vport *vport, const unsigned char *addr);
 
        kfree(peer_table);
 }
 
-static int set_config(struct vport *vport, const void __user *uconfig)
+static int set_config(struct vport *vport, const void *config)
 {
        struct patch_vport *patch_vport = patch_vport_priv(vport);
        char peer_name[IFNAMSIZ];
-       int retval;
 
-       retval = strncpy_from_user(peer_name, uconfig, IFNAMSIZ);
-       if (retval < 0)
-               return -EFAULT;
-       else if (retval >= IFNAMSIZ)
-               return -ENAMETOOLONG;
+       strlcpy(peer_name, config, IFNAMSIZ);
 
        if (!strcmp(patch_vport->name, peer_name))
                return -EINVAL;
        return ERR_PTR(err);
 }
 
-static int patch_modify(struct vport *vport, const void __user *config)
+static int patch_modify(struct vport *vport, struct odp_port *port)
 {
-       return set_config(vport, config);
+       return set_config(vport, port->config);
 }
 
 static int patch_destroy(struct vport *vport)
 
        kfree(dev_table);
 }
 
-static int do_vport_add(struct odp_vport_add *vport_config)
-{
-       struct vport_parms parms;
-       struct vport *vport;
-       int err = 0;
-
-       vport_config->port_type[VPORT_TYPE_SIZE - 1] = '\0';
-       vport_config->devname[IFNAMSIZ - 1] = '\0';
-
-       rtnl_lock();
-
-       vport = vport_locate(vport_config->devname);
-       if (vport) {
-               err = -EBUSY;
-               goto out;
-       }
-
-       parms.name = vport_config->devname;
-       parms.type = vport_config->port_type;
-       parms.config = vport_config->config;
-
-       vport_lock();
-       vport = vport_add(&parms);
-       vport_unlock();
-
-       if (IS_ERR(vport))
-               err = PTR_ERR(vport);
-
-out:
-       rtnl_unlock();
-       return err;
-}
-
-/**
- *     vport_user_add - add vport device (for userspace callers)
- *
- * @uvport_config: New port configuration.
- *
- * Creates a new vport with the specified configuration (which is dependent
- * on device type).  This function is for userspace callers and assumes no
- * locks are held.
- */
-int vport_user_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_user_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
-
-static int do_vport_mod(struct odp_vport_mod *vport_config)
-{
-       struct vport *vport;
-       int err;
-
-       vport_config->devname[IFNAMSIZ - 1] = '\0';
-
-       rtnl_lock();
-
-       vport = vport_locate(vport_config->devname);
-       if (!vport) {
-               err = -ENODEV;
-               goto out;
-       }
-
-       vport_lock();
-       err = vport_mod(vport, vport_config->config);
-       vport_unlock();
-
-out:
-       rtnl_unlock();
-       return err;
-}
-
 /**
  *     vport_user_mod - modify existing vport device (for userspace callers)
  *
- * @uvport_config: New configuration for vport
+ * @uport: New configuration for vport
  *
  * Modifies an existing device with the specified configuration (which is
  * dependent on device type).  This function is for userspace callers and
  * assumes no locks are held.
  */
-int vport_user_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_user_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_user_del - delete existing vport device (for userspace callers)
- *
- * @udevname: Name of device to delete
- *
- * Deletes the specified device.  Detaches the device from a datapath first
- * if it is attached.  Deleting the device will fail if it does not exist or it
- * is the datapath local port.  It is also possible to fail for less obvious
- * reasons, such as lack of memory.  This function is for userspace callers and
- * assumes no locks are held.
- */
-int vport_user_del(const char __user *udevname)
+int vport_user_mod(const struct odp_port __user *uport)
 {
-       char devname[IFNAMSIZ];
+       struct odp_port port;
        struct vport *vport;
-       struct dp_port *dp_port;
-       int err = 0;
-       int retval;
+       int err;
 
-       retval = strncpy_from_user(devname, udevname, IFNAMSIZ);
-       if (retval < 0)
+       if (copy_from_user(&port, uport, sizeof(port)))
                return -EFAULT;
-       else if (retval >= IFNAMSIZ)
-               return -ENAMETOOLONG;
+
+       port.devname[IFNAMSIZ - 1] = '\0';
 
        rtnl_lock();
 
-       vport = vport_locate(devname);
+       vport = vport_locate(port.devname);
        if (!vport) {
                err = -ENODEV;
                goto out;
        }
 
-       dp_port = vport_get_dp_port(vport);
-       if (dp_port) {
-               struct datapath *dp = dp_port->dp;
-
-               mutex_lock(&dp->mutex);
-
-               if (!strcmp(dp_name(dp), devname)) {
-                       err = -EINVAL;
-                       goto dp_port_out;
-               }
-
-               err = dp_detach_port(dp_port, 0);
-
-dp_port_out:
-               mutex_unlock(&dp->mutex);
-
-               if (err)
-                       goto out;
-       }
-
        vport_lock();
-       err = vport_del(vport);
+       err = vport_mod(vport, &port);
        vport_unlock();
 
 out:
  *     vport_mod - modify existing vport device (for kernel callers)
  *
  * @vport: vport to modify.
- * @config: Device type specific configuration.  Userspace pointer.
+ * @port: New configuration.
  *
  * Modifies an existing device with the specified configuration (which is
  * dependent on device type).  Both RTNL and vport locks must be held.
  */
-int vport_mod(struct vport *vport, const void __user *config)
+int vport_mod(struct vport *vport, struct odp_port *port)
 {
        ASSERT_RTNL();
        ASSERT_VPORT();
 
        if (vport->ops->modify)
-               return vport->ops->modify(vport, config);
+               return vport->ops->modify(vport, port);
        else
                return -EOPNOTSUPP;
 }
 
 
 /* The following definitions are for users of the vport subsytem: */
 
-int vport_user_add(const struct odp_vport_add __user *);
-int vport_user_mod(const struct odp_vport_mod __user *);
-int vport_user_del(const char __user *udevname);
-
-#ifdef CONFIG_COMPAT
-int compat_vport_user_add(struct compat_odp_vport_add __user *);
-int compat_vport_user_mod(struct compat_odp_vport_mod __user *);
-#endif
+int vport_user_mod(const struct odp_port __user *);
 
 int vport_user_stats_get(struct odp_vport_stats_req __user *);
 int vport_user_stats_set(struct odp_vport_stats_req __user *);
 void vport_exit(void);
 
 struct vport *vport_add(const struct vport_parms *);
-int vport_mod(struct vport *, const void __user *config);
+int vport_mod(struct vport *, struct odp_port *);
 int vport_del(struct vport *);
 
 struct vport *vport_locate(const char *name);
  *
  * @name: New vport's name.
  * @type: New vport's type.
- * @config: New vport's configuration, as %NULL or a userspace pointer to an
- * arbitrary type-specific structure.
+ * @config: Kernel copy of 'config' member of &struct odp_port describing
+ * configuration for new port.  Exactly %VPORT_CONFIG_SIZE bytes.
  */
 struct vport_parms {
        const char *name;
        const char *type;
-       const void __user *config;
+       const void *config;
 };
 
 /**
 
        /* Called with RTNL lock. */
        struct vport *(*create)(const struct vport_parms *);
-       int (*modify)(struct vport *, const void __user *config);
+       int (*modify)(struct vport *, struct odp_port *);
        int (*destroy)(struct vport *);
 
        int (*attach)(struct vport *);
 
 #include <sys/socket.h>
 #endif
 
+#ifndef __aligned_u64
+#define __aligned_u64 __u64 __attribute__((aligned(8)))
+#endif
+
 #include <linux/if_link.h>
 
 #define ODP_MAX 256             /* Maximum number of datapaths. */
 #define ODP_GET_LISTEN_MASK     _IOW('O', 5, int)
 #define ODP_SET_LISTEN_MASK     _IOR('O', 6, int)
 
-#define ODP_PORT_ATTACH         _IOR('O', 7, struct odp_port)
-#define ODP_PORT_DETACH         _IOR('O', 8, int)
-#define ODP_PORT_QUERY          _IOWR('O', 9, struct odp_port)
-#define ODP_PORT_LIST           _IOWR('O', 10, struct odp_portvec)
+#define ODP_VPORT_ATTACH        _IOR('O', 7, struct odp_port)
+#define ODP_VPORT_DETACH        _IOR('O', 8, int)
+#define ODP_VPORT_QUERY         _IOWR('O', 9, struct odp_port)
+#define ODP_VPORT_LIST          _IOWR('O', 10, struct odp_portvec)
 
 #define ODP_FLOW_GET            _IOWR('O', 13, struct odp_flow)
 #define ODP_FLOW_PUT            _IOWR('O', 14, struct odp_flow)
 #define ODP_SET_SFLOW_PROBABILITY _IOR('O', 19, int)
 #define ODP_GET_SFLOW_PROBABILITY _IOW('O', 20, int)
 
-#define ODP_VPORT_ADD           _IOR('O', 21, struct odp_vport_add)
-#define ODP_VPORT_MOD           _IOR('O', 22, struct odp_vport_mod)
-#define ODP_VPORT_DEL           _IO('O', 23)
+#define ODP_VPORT_MOD           _IOR('O', 22, struct odp_port)
 #define ODP_VPORT_STATS_GET     _IOWR('O', 24, struct odp_vport_stats_req)
 #define ODP_VPORT_ETHER_GET     _IOWR('O', 25, struct odp_vport_ether)
 #define ODP_VPORT_ETHER_SET     _IOW('O', 26, struct odp_vport_ether)
     uint32_t n_actions;
 };
 
-#define ODP_PORT_INTERNAL (1 << 0) /* This port is simulated. */
+#define VPORT_TYPE_SIZE     16
+#define VPORT_CONFIG_SIZE     32
 struct odp_port {
     char devname[16];           /* IFNAMSIZ */
+    char type[VPORT_TYPE_SIZE];
     uint16_t port;
-    uint16_t flags;
+    uint16_t reserved1;
     uint32_t reserved2;
+    __aligned_u64 config[VPORT_CONFIG_SIZE / 8]; /* type-specific */
 };
 
 struct odp_portvec {
 
 #define OPENVSWITCH_TUNNEL_H 1
 
 #include <linux/types.h>
+#include "openvswitch/datapath-protocol.h"
 
 #define TNL_F_CSUM             (1 << 1) /* Checksum packets. */
 #define TNL_F_IN_KEY_MATCH     (1 << 2) /* Store the key in tun_id to match in flow table. */
 #define TNL_F_PMTUD            (1 << 6) /* Enable path MTU discovery. */
 #define TNL_F_HDR_CACHE                (1 << 7) /* Enable tunnel header caching. */
 
+/* This goes in the "config" member of struct odp_port for tunnel vports. */
 struct tnl_port_config {
        __u32   flags;
        __be32  saddr;
 
 
 #include "dpif-provider.h"
 #include "netdev.h"
+#include "netdev-vport.h"
 #include "ofpbuf.h"
 #include "poll-loop.h"
 #include "rtnetlink.h"
          * getting the local port's name. */
         memset(&port, 0, sizeof port);
         port.port = ODPP_LOCAL;
-        if (ioctl(dpif->fd, ODP_PORT_QUERY, &port)) {
+        if (ioctl(dpif->fd, ODP_VPORT_QUERY, &port)) {
             error = errno;
             if (error != ENODEV) {
                 VLOG_WARN("%s: probe returned unexpected error: %s",
 static int
 dpif_linux_destroy(struct dpif *dpif_)
 {
-    struct odp_port *ports;
-    size_t n_ports;
-    int err;
-    int i;
-
-    err = dpif_port_list(dpif_, &ports, &n_ports);
-    if (err) {
-        return err;
-    }
-
-    for (i = 0; i < n_ports; i++) {
-        if (ports[i].port != ODPP_LOCAL) {
-            err = do_ioctl(dpif_, ODP_VPORT_DEL, ports[i].devname);
-            if (err) {
-                VLOG_WARN_RL(&error_rl, "%s: error deleting port %s (%s)",
-                             dpif_name(dpif_), ports[i].devname, strerror(err));
-            }
-        }
-    }
-
-    free(ports);
-
     return do_ioctl(dpif_, ODP_DP_DESTROY, NULL);
 }
 
     return do_ioctl(dpif_, ODP_SET_DROP_FRAGS, &drop_frags_int);
 }
 
+static void
+translate_vport_type_to_netdev_type(char *type, size_t size)
+{
+    if (!strcmp(type, "netdev")) {
+        ovs_strlcpy(type, "system", size);
+    }
+}
+
+static void
+translate_netdev_type_to_vport_type(char *type, size_t size)
+{
+    if (!strcmp(type, "system")) {
+        ovs_strlcpy(type, "netdev", size);
+    }
+}
+
 static int
-dpif_linux_port_add(struct dpif *dpif_, const char *devname, uint16_t flags,
-                    uint16_t *port_no)
+dpif_linux_port_add(struct dpif *dpif, struct netdev *netdev,
+                    uint16_t *port_nop)
 {
+    const char *name = netdev_get_name(netdev);
+    const char *type = netdev_get_type(netdev);
     struct odp_port port;
     int error;
 
     memset(&port, 0, sizeof port);
-    strncpy(port.devname, devname, sizeof port.devname);
-    port.flags = flags;
-    error = do_ioctl(dpif_, ODP_PORT_ATTACH, &port);
+    strncpy(port.devname, name, sizeof port.devname);
+    strncpy(port.type, type, sizeof port.type);
+    translate_netdev_type_to_vport_type(port.type, sizeof port.type);
+    netdev_vport_get_config(netdev, port.config);
+
+    error = do_ioctl(dpif, ODP_VPORT_ATTACH, &port);
     if (!error) {
-        *port_no = port.port;
+        *port_nop = port.port;
     }
+
     return error;
 }
 
 static int
-dpif_linux_port_del(struct dpif *dpif_, uint16_t port_no)
+dpif_linux_port_del(struct dpif *dpif_, uint16_t port_no_)
 {
-    int tmp = port_no;
-    int err;
-    struct odp_port port;
-
-    err = dpif_port_query_by_number(dpif_, port_no, &port);
-    if (err) {
-        return err;
-    }
-
-    err = do_ioctl(dpif_, ODP_PORT_DETACH, &tmp);
-    if (err) {
-        return err;
-    }
+    int port_no = port_no_;     /* Kernel expects an "int". */
+    return do_ioctl(dpif_, ODP_VPORT_DETACH, &port_no);
+}
 
-    if (!netdev_is_open(port.devname)) {
-        /* Try deleting the port if no one has it open.  This shouldn't
-         * actually be necessary unless the config changed while we weren't
-         * running but it won't hurt anything if the port is already gone. */
-        do_ioctl(dpif_, ODP_VPORT_DEL, port.devname);
+static int
+dpif_linux_port_query__(const struct dpif *dpif, struct odp_port *port)
+{
+    int error = do_ioctl(dpif, ODP_VPORT_QUERY, port);
+    if (!error) {
+        translate_vport_type_to_netdev_type(port->type, sizeof port->type);
     }
-
-    return 0;
+    return error;
 }
 
 static int
-dpif_linux_port_query_by_number(const struct dpif *dpif_, uint16_t port_no,
-                          struct odp_port *port)
+dpif_linux_port_query_by_number(const struct dpif *dpif, uint16_t port_no,
+                                struct odp_port *port)
 {
     memset(port, 0, sizeof *port);
     port->port = port_no;
-    return do_ioctl(dpif_, ODP_PORT_QUERY, port);
+    return dpif_linux_port_query__(dpif, port);
 }
 
 static int
-dpif_linux_port_query_by_name(const struct dpif *dpif_, const char *devname,
+dpif_linux_port_query_by_name(const struct dpif *dpif, const char *devname,
                               struct odp_port *port)
 {
     memset(port, 0, sizeof *port);
     strncpy(port->devname, devname, sizeof port->devname);
-    return do_ioctl(dpif_, ODP_PORT_QUERY, port);
+    return dpif_linux_port_query__(dpif, port);
 }
 
 static int
 dpif_linux_port_list(const struct dpif *dpif_, struct odp_port *ports, int n)
 {
     struct odp_portvec pv;
+    unsigned int i;
     int error;
 
     pv.ports = ports;
     pv.n_ports = n;
-    error = do_ioctl(dpif_, ODP_PORT_LIST, &pv);
-    return error ? -error : pv.n_ports;
+    error = do_ioctl(dpif_, ODP_VPORT_LIST, &pv);
+    if (error) {
+        return -error;
+    }
+
+    for (i = 0; i < pv.n_ports; i++) {
+        struct odp_port *port = &pv.ports[i];
+
+        translate_vport_type_to_netdev_type(port->type, sizeof port->type);
+    }
+    return pv.n_ports;
 }
 
 static int
 
     int port_no;                /* Index into dp_netdev's 'ports'. */
     struct list node;           /* Element in dp_netdev's 'port_list'. */
     struct netdev *netdev;
-    bool internal;              /* Internal port (as ODP_PORT_INTERNAL)? */
+    bool internal;              /* Internal port? */
 };
 
 /* A flow in dp_netdev's 'flow_table'. */
                             struct dp_netdev_port **portp);
 static void dp_netdev_free(struct dp_netdev *);
 static void dp_netdev_flow_flush(struct dp_netdev *);
-static int do_add_port(struct dp_netdev *, const char *devname, uint16_t flags,
-                       uint16_t port_no);
+static int do_add_port(struct dp_netdev *, const char *devname,
+                       const char *type, uint16_t port_no);
 static int do_del_port(struct dp_netdev *, uint16_t port_no);
 static int dpif_netdev_open(const struct dpif_class *, const char *name,
                             bool create, struct dpif **);
     }
     hmap_init(&dp->flow_table);
     list_init(&dp->port_list);
-    error = do_add_port(dp, name, ODP_PORT_INTERNAL, ODPP_LOCAL);
+    error = do_add_port(dp, name, "internal", ODPP_LOCAL);
     if (error) {
         dp_netdev_free(dp);
         return error;
 }
 
 static int
-do_add_port(struct dp_netdev *dp, const char *devname, uint16_t flags,
+do_add_port(struct dp_netdev *dp, const char *devname, const char *type,
             uint16_t port_no)
 {
-    bool internal = (flags & ODP_PORT_INTERNAL) != 0;
     struct dp_netdev_port *port;
     struct netdev_options netdev_options;
     struct netdev *netdev;
+    bool internal;
     int mtu;
     int error;
 
     /* XXX reject devices already in some dp_netdev. */
+    if (type[0] == '\0' || !strcmp(type, "system")) {
+        internal = false;
+    } else if (!strcmp(type, "internal")) {
+        internal = true;
+    } else {
+        VLOG_WARN("%s: unsupported port type %s", devname, type);
+        return EINVAL;
+    }
 
     /* Open and validate network device. */
     memset(&netdev_options, 0, sizeof netdev_options);
 }
 
 static int
-dpif_netdev_port_add(struct dpif *dpif, const char *devname, uint16_t flags,
+dpif_netdev_port_add(struct dpif *dpif, struct netdev *netdev,
                      uint16_t *port_nop)
 {
     struct dp_netdev *dp = get_dp_netdev(dpif);
     for (port_no = 0; port_no < MAX_PORTS; port_no++) {
         if (!dp->ports[port_no]) {
             *port_nop = port_no;
-            return do_add_port(dp, devname, flags, port_no);
+            return do_add_port(dp, netdev_get_name(netdev),
+                               netdev_get_type(netdev), port_no);
         }
     }
     return EFBIG;
     ovs_strlcpy(odp_port->devname, netdev_get_name(port->netdev),
                 sizeof odp_port->devname);
     odp_port->port = port->port_no;
-    odp_port->flags = port->internal ? ODP_PORT_INTERNAL : 0;
+    strcpy(odp_port->type, port->internal ? "internal" : "system");
 }
 
 static int
 
      * meaning is the same as for the get_drop_frags member function. */
     int (*set_drop_frags)(struct dpif *dpif, bool drop_frags);
 
-    /* Creates a new port in 'dpif' connected to network device 'devname'.
-     * 'flags' is a set of ODP_PORT_* flags.  If successful, sets '*port_no'
+    /* Adds 'netdev' as a new port in 'dpif'.  If successful, sets '*port_no'
      * to the new port's port number. */
-    int (*port_add)(struct dpif *dpif, const char *devname, uint16_t flags,
+    int (*port_add)(struct dpif *dpif, struct netdev *netdev,
                     uint16_t *port_no);
 
     /* Removes port numbered 'port_no' from 'dpif'. */
 
 #include "coverage.h"
 #include "dynamic-string.h"
 #include "flow.h"
+#include "netdev.h"
 #include "netlink.h"
 #include "odp-util.h"
 #include "ofp-print.h"
     return error;
 }
 
-/* Attempts to add 'devname' as a port on 'dpif', given the combination of
- * ODP_PORT_* flags in 'flags'.  If successful, returns 0 and sets '*port_nop'
- * to the new port's port number (if 'port_nop' is non-null).  On failure,
- * returns a positive errno value and sets '*port_nop' to UINT16_MAX (if
- * 'port_nop' is non-null). */
+/* Attempts to add 'netdev' as a port on 'dpif'.  If successful, returns 0 and
+ * sets '*port_nop' to the new port's port number (if 'port_nop' is non-null).
+ * On failure, returns a positive errno value and sets '*port_nop' to
+ * UINT16_MAX (if 'port_nop' is non-null). */
 int
-dpif_port_add(struct dpif *dpif, const char *devname, uint16_t flags,
-              uint16_t *port_nop)
+dpif_port_add(struct dpif *dpif, struct netdev *netdev, uint16_t *port_nop)
 {
+    const char *netdev_name = netdev_get_name(netdev);
     uint16_t port_no;
     int error;
 
     COVERAGE_INC(dpif_port_add);
 
-    error = dpif->dpif_class->port_add(dpif, devname, flags, &port_no);
+    error = dpif->dpif_class->port_add(dpif, netdev, &port_no);
     if (!error) {
         VLOG_DBG_RL(&dpmsg_rl, "%s: added %s as port %"PRIu16,
-                    dpif_name(dpif), devname, port_no);
+                    dpif_name(dpif), netdev_name, port_no);
     } else {
         VLOG_WARN_RL(&error_rl, "%s: failed to add %s as port: %s",
-                     dpif_name(dpif), devname, strerror(error));
+                     dpif_name(dpif), netdev_name, strerror(error));
         port_no = UINT16_MAX;
     }
     if (port_nop) {
 
 #endif
 
 struct dpif;
+struct netdev;
 struct ofpbuf;
 struct svec;
 struct dpif_class;
 int dpif_get_drop_frags(const struct dpif *, bool *drop_frags);
 int dpif_set_drop_frags(struct dpif *, bool drop_frags);
 
-int dpif_port_add(struct dpif *, const char *devname, uint16_t flags,
-                  uint16_t *port_no);
+int dpif_port_add(struct dpif *, struct netdev *, uint16_t *port_nop);
 int dpif_port_del(struct dpif *, uint16_t port_no);
 int dpif_port_query_by_number(const struct dpif *, uint16_t port_no,
                               struct odp_port *);
 
     }
 }
 
-/* Creates the netdev device of 'type' with 'name'. */
+/* Creates system and internal devices. */
 static int
-netdev_linux_create_system(const struct netdev_class *class OVS_UNUSED,
+netdev_linux_create(const struct netdev_class *class,
                            const char *name, const struct shash *args,
                            struct netdev_dev **netdev_devp)
 {
     int error;
 
     if (!shash_is_empty(args)) {
-        VLOG_WARN("%s: arguments for system devices should be empty", name);
+        VLOG_WARN("%s: arguments for %s devices should be empty",
+                  name, class->type);
     }
 
     if (!cache_notifier_refcount) {
     cache_notifier_refcount++;
 
     netdev_dev = xzalloc(sizeof *netdev_dev);
-    netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_linux_class);
+    netdev_dev_init(&netdev_dev->netdev_dev, name, class);
 
     *netdev_devp = &netdev_dev->netdev_dev;
     return 0;
     netdev->fd = -1;
     netdev_init(&netdev->netdev, netdev_dev_);
 
-    error = netdev_get_flags(&netdev->netdev, &flags);
-    if (error == ENODEV) {
-        goto error;
+    /* Verify that the device really exists, by attempting to read its flags.
+     * (The flags might be cached, in which case this won't actually do an
+     * ioctl.)
+     *
+     * Don't do this for "internal" netdevs, though, because those have to be
+     * created as netdev objects before they exist in the kernel, because
+     * creating them in the kernel happens by passing a netdev object to
+     * dpif_port_add(). */
+    if (netdev_dev_get_class(netdev_dev_) != &netdev_internal_class) {
+        error = netdev_get_flags(&netdev->netdev, &flags);
+        if (error == ENODEV) {
+            goto error;
+        }
     }
 
     if (!strcmp(netdev_dev_get_type(netdev_dev_), "tap") &&
     }
 }
 
-const struct netdev_class netdev_linux_class = {
-    "system",
-
-    netdev_linux_init,
-    netdev_linux_run,
-    netdev_linux_wait,
-
-    netdev_linux_create_system,
-    netdev_linux_destroy,
-    NULL,                       /* reconfigure */
-
-    netdev_linux_open,
-    netdev_linux_close,
-
-    netdev_linux_enumerate,
-
-    netdev_linux_recv,
-    netdev_linux_recv_wait,
-    netdev_linux_drain,
-
-    netdev_linux_send,
-    netdev_linux_send_wait,
-
-    netdev_linux_set_etheraddr,
-    netdev_linux_get_etheraddr,
-    netdev_linux_get_mtu,
-    netdev_linux_get_ifindex,
-    netdev_linux_get_carrier,
-    netdev_linux_get_stats,
-    netdev_vport_set_stats,
-
-    netdev_linux_get_features,
-    netdev_linux_set_advertisements,
-    netdev_linux_get_vlan_vid,
-
-    netdev_linux_set_policing,
-    netdev_linux_get_qos_types,
-    netdev_linux_get_qos_capabilities,
-    netdev_linux_get_qos,
-    netdev_linux_set_qos,
-    netdev_linux_get_queue,
-    netdev_linux_set_queue,
-    netdev_linux_delete_queue,
-    netdev_linux_get_queue_stats,
-    netdev_linux_dump_queues,
-    netdev_linux_dump_queue_stats,
-
-    netdev_linux_get_in4,
-    netdev_linux_set_in4,
-    netdev_linux_get_in6,
-    netdev_linux_add_router,
-    netdev_linux_get_next_hop,
-    netdev_linux_arp_lookup,
-
-    netdev_linux_update_flags,
-
-    netdev_linux_poll_add,
-    netdev_linux_poll_remove,
-};
-
-const struct netdev_class netdev_tap_class = {
-    "tap",
-
-    netdev_linux_init,
-    netdev_linux_run,
-    netdev_linux_wait,
-
-    netdev_linux_create_tap,
-    netdev_linux_destroy,
-    NULL,                       /* reconfigure */
-
-    netdev_linux_open,
-    netdev_linux_close,
-
-    NULL,                       /* enumerate */
-
-    netdev_linux_recv,
-    netdev_linux_recv_wait,
-    netdev_linux_drain,
-
-    netdev_linux_send,
-    netdev_linux_send_wait,
-
-    netdev_linux_set_etheraddr,
-    netdev_linux_get_etheraddr,
-    netdev_linux_get_mtu,
-    netdev_linux_get_ifindex,
-    netdev_linux_get_carrier,
-    netdev_linux_get_stats,
-    NULL,                       /* set_stats */
-
-    netdev_linux_get_features,
-    netdev_linux_set_advertisements,
-    netdev_linux_get_vlan_vid,
-
-    netdev_linux_set_policing,
-    netdev_linux_get_qos_types,
-    netdev_linux_get_qos_capabilities,
-    netdev_linux_get_qos,
-    netdev_linux_set_qos,
-    netdev_linux_get_queue,
-    netdev_linux_set_queue,
-    netdev_linux_delete_queue,
-    netdev_linux_get_queue_stats,
-    netdev_linux_dump_queues,
-    netdev_linux_dump_queue_stats,
-
-    netdev_linux_get_in4,
-    netdev_linux_set_in4,
-    netdev_linux_get_in6,
-    netdev_linux_add_router,
-    netdev_linux_get_next_hop,
-    netdev_linux_arp_lookup,
-
-    netdev_linux_update_flags,
-
-    netdev_linux_poll_add,
-    netdev_linux_poll_remove,
-};
+#define NETDEV_LINUX_CLASS(NAME, CREATE, ENUMERATE, SET_STATS)  \
+{                                                               \
+    NAME,                                                       \
+                                                                \
+    netdev_linux_init,                                          \
+    netdev_linux_run,                                           \
+    netdev_linux_wait,                                          \
+                                                                \
+    CREATE,                                                     \
+    netdev_linux_destroy,                                       \
+    NULL,                       /* reconfigure */               \
+                                                                \
+    netdev_linux_open,                                          \
+    netdev_linux_close,                                         \
+                                                                \
+    ENUMERATE,                                                  \
+                                                                \
+    netdev_linux_recv,                                          \
+    netdev_linux_recv_wait,                                     \
+    netdev_linux_drain,                                         \
+                                                                \
+    netdev_linux_send,                                          \
+    netdev_linux_send_wait,                                     \
+                                                                \
+    netdev_linux_set_etheraddr,                                 \
+    netdev_linux_get_etheraddr,                                 \
+    netdev_linux_get_mtu,                                       \
+    netdev_linux_get_ifindex,                                   \
+    netdev_linux_get_carrier,                                   \
+    netdev_linux_get_stats,                                     \
+    SET_STATS,                                                  \
+                                                                \
+    netdev_linux_get_features,                                  \
+    netdev_linux_set_advertisements,                            \
+    netdev_linux_get_vlan_vid,                                  \
+                                                                \
+    netdev_linux_set_policing,                                  \
+    netdev_linux_get_qos_types,                                 \
+    netdev_linux_get_qos_capabilities,                          \
+    netdev_linux_get_qos,                                       \
+    netdev_linux_set_qos,                                       \
+    netdev_linux_get_queue,                                     \
+    netdev_linux_set_queue,                                     \
+    netdev_linux_delete_queue,                                  \
+    netdev_linux_get_queue_stats,                               \
+    netdev_linux_dump_queues,                                   \
+    netdev_linux_dump_queue_stats,                              \
+                                                                \
+    netdev_linux_get_in4,                                       \
+    netdev_linux_set_in4,                                       \
+    netdev_linux_get_in6,                                       \
+    netdev_linux_add_router,                                    \
+    netdev_linux_get_next_hop,                                  \
+    netdev_linux_arp_lookup,                                    \
+                                                                \
+    netdev_linux_update_flags,                                  \
+                                                                \
+    netdev_linux_poll_add,                                      \
+    netdev_linux_poll_remove                                    \
+}
+
+const struct netdev_class netdev_linux_class =
+    NETDEV_LINUX_CLASS(
+        "system",
+        netdev_linux_create,
+        netdev_linux_enumerate,
+        netdev_vport_set_stats);
+
+const struct netdev_class netdev_tap_class =
+    NETDEV_LINUX_CLASS(
+        "tap",
+        netdev_linux_create_tap,
+        NULL,                   /* enumerate */
+        NULL);                  /* set_stats */
+
+const struct netdev_class netdev_internal_class =
+    NETDEV_LINUX_CLASS(
+        "internal",
+        netdev_linux_create,
+        NULL,                    /* enumerate */
+        netdev_vport_set_stats);
 \f
 /* HTB traffic control class. */
 
 
 
 int netdev_register_provider(const struct netdev_class *);
 int netdev_unregister_provider(const char *type);
+const struct netdev_class *netdev_lookup_provider(const char *type);
 
 extern const struct netdev_class netdev_linux_class;
+extern const struct netdev_class netdev_internal_class;
 extern const struct netdev_class netdev_tap_class;
 
 #ifdef  __cplusplus
 
 
 struct netdev_dev_vport {
     struct netdev_dev netdev_dev;
+    uint64_t config[VPORT_CONFIG_SIZE / 8];
 };
 
 struct netdev_vport {
     struct netdev netdev;
 };
 
-struct vport_info {
-    const char *devname;
-    const char *type;
-    void *config;
-};
-
 struct vport_class {
-    const struct netdev_class netdev_class;
-    int (*parse_config)(struct vport_info *port, const struct shash *args);
+    struct netdev_class netdev_class;
+    int (*parse_config)(const struct netdev_dev *, const struct shash *args,
+                        void *config);
 };
 
 static struct shash netdev_vport_notifiers =
     return CONTAINER_OF(netdev, struct netdev_vport, netdev);
 }
 
-static int
-netdev_vport_parse_config(const struct netdev_class *netdev_class,
-                          const char *name, const struct shash *args,
-                          void **configp)
+/* If 'netdev' is a vport netdev, copies its kernel configuration into
+ * 'config'.  Otherwise leaves 'config' untouched. */
+void
+netdev_vport_get_config(const struct netdev *netdev, void *config)
 {
-    const struct vport_class *c = vport_class_cast(netdev_class);
-    if (c->parse_config) {
-        struct vport_info info;
-        int error;
-
-        info.devname = name;
-        info.type = netdev_class->type;
-        error = (c->parse_config)(&info, args);
-        *configp = error ? NULL : info.config;
-        return error;
-    } else {
-        if (!shash_is_empty(args)) {
-            VLOG_WARN("%s: arguments for %s vports should be empty",
-                      name, netdev_class->type);
-        }
-        *configp = NULL;
-        return 0;
+    const struct netdev_dev *dev = netdev_get_dev(netdev);
+
+    if (is_vport_class(netdev_dev_get_class(dev))) {
+        const struct netdev_dev_vport *vport = netdev_dev_vport_cast(dev);
+        memcpy(config, vport->config, VPORT_CONFIG_SIZE);
     }
 }
 
 static int
-netdev_vport_create(const struct netdev_class *class, const char *name,
-                    const struct shash *args, struct netdev_dev **netdev_devp)
+netdev_vport_create(const struct netdev_class *netdev_class, const char *name,
+                    const struct shash *args,
+                    struct netdev_dev **netdev_devp)
 {
-    int err;
-    struct odp_vport_add ova;
-    struct netdev_dev_vport *netdev_dev;
+    const struct vport_class *vport_class = vport_class_cast(netdev_class);
+    struct netdev_dev_vport *dev;
+    int error;
 
-    ovs_strlcpy(ova.port_type, class->type, sizeof ova.port_type);
-    ovs_strlcpy(ova.devname, name, sizeof ova.devname);
-    err = netdev_vport_parse_config(class, name, args, &ova.config);
-    if (err) {
-        goto exit;
-    }
-
-    err = netdev_vport_do_ioctl(ODP_VPORT_ADD, &ova);
-
-    if (err == EBUSY) {
-        VLOG_WARN("%s: destroying existing device", name);
+    dev = xmalloc(sizeof *dev);
+    *netdev_devp = &dev->netdev_dev;
+    netdev_dev_init(&dev->netdev_dev, name, netdev_class);
 
-        err = netdev_vport_do_ioctl(ODP_VPORT_DEL, ova.devname);
-        if (err) {
-            goto exit;
-        }
+    memset(dev->config, 0, sizeof dev->config);
+    error = vport_class->parse_config(&dev->netdev_dev, args, dev->config);
 
-        err = netdev_vport_do_ioctl(ODP_VPORT_ADD, &ova);
-    }
-    if (err) {
-        goto exit;
+    if (error) {
+        netdev_dev_uninit(&dev->netdev_dev, true);
     }
-
-    netdev_dev = xmalloc(sizeof *netdev_dev);
-    netdev_dev_init(&netdev_dev->netdev_dev, name, class);
-
-    *netdev_devp = &netdev_dev->netdev_dev;
-
-exit:
-    free(ova.config);
-    return err;
+    return error;
 }
 
 static void
 {
     struct netdev_dev_vport *netdev_dev = netdev_dev_vport_cast(netdev_dev_);
 
-    netdev_vport_do_ioctl(ODP_VPORT_DEL, 
-                          (char *)netdev_dev_get_name(netdev_dev_));
     free(netdev_dev);
 }
 
 }
 
 static int
-netdev_vport_reconfigure(struct netdev_dev *netdev_dev,
+netdev_vport_reconfigure(struct netdev_dev *dev_,
                          const struct shash *args)
 {
-    const char *name = netdev_dev_get_name(netdev_dev);
-    struct odp_vport_mod ovm;
-    int err;
-
-    ovs_strlcpy(ovm.devname, name, sizeof ovm.devname);
-    err = netdev_vport_parse_config(netdev_dev_get_class(netdev_dev), name,
-                                    args, &ovm.config);
-    if (err) {
-        return err;
+    const struct netdev_class *netdev_class = netdev_dev_get_class(dev_);
+    const struct vport_class *vport_class = vport_class_cast(netdev_class);
+    struct netdev_dev_vport *dev = netdev_dev_vport_cast(dev_);
+    struct odp_port port;
+    int error;
+
+    memset(&port, 0, sizeof port);
+    strncpy(port.devname, netdev_dev_get_name(dev_), sizeof port.devname);
+    strncpy(port.type, netdev_dev_get_type(dev_), sizeof port.type);
+    error = vport_class->parse_config(dev_, args, port.config);
+    if (!error && memcmp(port.config, dev->config, sizeof dev->config)) {
+        error = netdev_vport_do_ioctl(ODP_VPORT_MOD, &port);
+        if (!error || error == ENODEV) {
+            /* Either reconfiguration succeeded or this vport is not installed
+             * in the kernel (e.g. it hasn't been added to a dpif yet with
+             * dpif_port_add()). */
+            memcpy(dev->config, port.config, sizeof dev->config);
+        }
     }
-
-    err = netdev_vport_do_ioctl(ODP_VPORT_MOD, &ovm);
-    free(ovm.config);
-    return err;
+    return error;
 }
 
 static int
 /* Code specific to individual vport types. */
 
 static int
-parse_tunnel_config(struct vport_info *port, const struct shash *args)
+parse_tunnel_config(const struct netdev_dev *dev, const struct shash *args,
+                    void *configp)
 {
-    const char *name = port->devname;
-    bool is_gre = !strcmp(port->type, "gre");
-    struct tnl_port_config *config;
+    const char *name = netdev_dev_get_name(dev);
+    const char *type = netdev_dev_get_type(dev);
+    bool is_gre = !strcmp(type, "gre");
+    struct tnl_port_config config;
     struct shash_node *node;
     bool ipsec_ip_set = false;
     bool ipsec_mech_set = false;
 
-    config = port->config = xzalloc(sizeof *config);
-    config->flags |= TNL_F_PMTUD;
-    config->flags |= TNL_F_HDR_CACHE;
+    config.flags |= TNL_F_PMTUD;
+    config.flags |= TNL_F_HDR_CACHE;
 
     SHASH_FOR_EACH (node, args) {
         if (!strcmp(node->name, "remote_ip")) {
             struct in_addr in_addr;
             if (lookup_ip(node->data, &in_addr)) {
-                VLOG_WARN("%s: bad %s 'remote_ip'", name, port->type);
+                VLOG_WARN("%s: bad %s 'remote_ip'", name, type);
             } else {
-                config->daddr = in_addr.s_addr;
+                config.daddr = in_addr.s_addr;
             }
         } else if (!strcmp(node->name, "local_ip")) {
             struct in_addr in_addr;
             if (lookup_ip(node->data, &in_addr)) {
-                VLOG_WARN("%s: bad %s 'local_ip'", name, port->type);
+                VLOG_WARN("%s: bad %s 'local_ip'", name, type);
             } else {
-                config->saddr = in_addr.s_addr;
+                config.saddr = in_addr.s_addr;
             }
         } else if (!strcmp(node->name, "key") && is_gre) {
             if (!strcmp(node->data, "flow")) {
-                config->flags |= TNL_F_IN_KEY_MATCH;
-                config->flags |= TNL_F_OUT_KEY_ACTION;
+                config.flags |= TNL_F_IN_KEY_MATCH;
+                config.flags |= TNL_F_OUT_KEY_ACTION;
             } else {
-                config->out_key = config->in_key = htonl(atoi(node->data));
+                config.out_key = config.in_key = htonl(atoi(node->data));
             }
         } else if (!strcmp(node->name, "in_key") && is_gre) {
             if (!strcmp(node->data, "flow")) {
-                config->flags |= TNL_F_IN_KEY_MATCH;
+                config.flags |= TNL_F_IN_KEY_MATCH;
             } else {
-                config->in_key = htonl(atoi(node->data));
+                config.in_key = htonl(atoi(node->data));
             }
         } else if (!strcmp(node->name, "out_key") && is_gre) {
             if (!strcmp(node->data, "flow")) {
-                config->flags |= TNL_F_OUT_KEY_ACTION;
+                config.flags |= TNL_F_OUT_KEY_ACTION;
             } else {
-                config->out_key = htonl(atoi(node->data));
+                config.out_key = htonl(atoi(node->data));
             }
         } else if (!strcmp(node->name, "tos")) {
             if (!strcmp(node->data, "inherit")) {
-                config->flags |= TNL_F_TOS_INHERIT;
+                config.flags |= TNL_F_TOS_INHERIT;
             } else {
-                config->tos = atoi(node->data);
+                config.tos = atoi(node->data);
             }
         } else if (!strcmp(node->name, "ttl")) {
             if (!strcmp(node->data, "inherit")) {
-                config->flags |= TNL_F_TTL_INHERIT;
+                config.flags |= TNL_F_TTL_INHERIT;
             } else {
-                config->ttl = atoi(node->data);
+                config.ttl = atoi(node->data);
             }
         } else if (!strcmp(node->name, "csum") && is_gre) {
             if (!strcmp(node->data, "true")) {
-                config->flags |= TNL_F_CSUM;
+                config.flags |= TNL_F_CSUM;
             }
         } else if (!strcmp(node->name, "pmtud")) {
             if (!strcmp(node->data, "false")) {
-                config->flags &= ~TNL_F_PMTUD;
+                config.flags &= ~TNL_F_PMTUD;
             }
         } else if (!strcmp(node->name, "header_cache")) {
             if (!strcmp(node->data, "false")) {
-                config->flags &= ~TNL_F_HDR_CACHE;
+                config.flags &= ~TNL_F_HDR_CACHE;
             }
         } else if (!strcmp(node->name, "ipsec_local_ip")) {
             ipsec_ip_set = true;
             ipsec_mech_set = true;
         } else {
             VLOG_WARN("%s: unknown %s argument '%s'",
-                      name, port->type, node->name);
+                      name, type, node->name);
         }
     }
 
     * IPsec local IP address and authentication mechanism have been defined. */
     if (ipsec_ip_set && ipsec_mech_set) {
         VLOG_INFO("%s: header caching disabled due to use of IPsec", name);
-        config->flags &= ~TNL_F_HDR_CACHE;
+        config.flags &= ~TNL_F_HDR_CACHE;
     }
 
-    if (!config->daddr) {
+    if (!config.daddr) {
         VLOG_WARN("%s: %s type requires valid 'remote_ip' argument",
-                  name, port->type);
+                  name, type);
         return EINVAL;
     }
 
+    BUILD_ASSERT(sizeof config <= VPORT_CONFIG_SIZE);
+    memcpy(configp, &config, sizeof config);
     return 0;
 }
 
 static int
-parse_patch_config(struct vport_info *port, const struct shash *args)
+parse_patch_config(const struct netdev_dev *dev, const struct shash *args,
+                   void *configp)
 {
-    const char *name = port->devname;
+    const char *name = netdev_dev_get_name(dev);
     const char *peer;
 
     peer = shash_find_data(args, "peer");
         return EINVAL;
     }
 
-    if (strlen(peer) >= IFNAMSIZ) {
+    if (strlen(peer) >= MIN(IFNAMSIZ, VPORT_CONFIG_SIZE)) {
         VLOG_WARN("%s: patch 'peer' arg too long", name);
         return EINVAL;
     }
         return EINVAL;
     }
 
-    port->config = xstrdup(peer);
+    strncpy(configp, peer, VPORT_CONFIG_SIZE);
 
     return 0;
 }
     netdev_vport_poll_add,                                  \
     netdev_vport_poll_remove,
 
-static const struct vport_class vport_gre_class
-        = { { "gre", VPORT_FUNCTIONS }, parse_tunnel_config };
-
-static const struct vport_class vport_capwap_class
-        = { { "capwap", VPORT_FUNCTIONS }, parse_tunnel_config };
-
-static const struct vport_class vport_patch_class
-        = { { "patch", VPORT_FUNCTIONS }, parse_patch_config };
-
 void
 netdev_vport_register(void)
 {
-    netdev_register_provider(&vport_gre_class.netdev_class);
-    netdev_register_provider(&vport_capwap_class.netdev_class);
-    netdev_register_provider(&vport_patch_class.netdev_class);
+    static const struct vport_class vport_classes[] = {
+        { { "gre", VPORT_FUNCTIONS }, parse_tunnel_config },
+        { { "capwap", VPORT_FUNCTIONS }, parse_tunnel_config },
+        { { "patch", VPORT_FUNCTIONS }, parse_patch_config }
+    };
+
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(vport_classes); i++) {
+        netdev_register_provider(&vport_classes[i].netdev_class);
+    }
 }
 
 struct shash;
 
 void netdev_vport_register(void);
+
+void netdev_vport_get_config(const struct netdev *, void *config);
+
 int netdev_vport_get_stats(const struct netdev *, struct netdev_stats *);
 int netdev_vport_set_stats(struct netdev *, const struct netdev_stats *);
 
 
 
 #ifdef HAVE_NETLINK
         netdev_register_provider(&netdev_linux_class);
+        netdev_register_provider(&netdev_internal_class);
         netdev_register_provider(&netdev_tap_class);
         netdev_vport_register();
 #endif
     return 0;
 }
 
+const struct netdev_class *
+netdev_lookup_provider(const char *type)
+{
+    netdev_initialize();
+    return shash_find_data(&netdev_classes, type && type[0] ? type : "system");
+}
+
 /* Clears 'types' and enumerates the types of all currently registered netdev
  * providers into it.  The caller must first initialize the svec. */
 void
     qsort(dev->args, dev->n_args, sizeof *dev->args, compare_args);
 }
 
-static int
-create_device(struct netdev_options *options, struct netdev_dev **netdev_devp)
-{
-    struct netdev_class *netdev_class;
-    int error;
-
-    if (!options->type || strlen(options->type) == 0) {
-        /* Default to system. */
-        options->type = "system";
-    }
-
-    netdev_class = shash_find_data(&netdev_classes, options->type);
-    if (!netdev_class) {
-        return EAFNOSUPPORT;
-    }
-
-    error = netdev_class->create(netdev_class, options->name, options->args,
-                                 netdev_devp);
-    assert(error || (*netdev_devp)->netdev_class == netdev_class);
-    return error;
-}
-
 /* Opens the network device named 'name' (e.g. "eth0") and returns zero if
  * successful, otherwise a positive errno value.  On success, sets '*netdevp'
  * to the new network device, otherwise to null.
     netdev_dev = shash_find_data(&netdev_dev_shash, options->name);
 
     if (!netdev_dev) {
-        error = create_device(options, &netdev_dev);
+        const struct netdev_class *class;
+
+        class = netdev_lookup_provider(options->type);
+        if (!class) {
+            VLOG_WARN("could not create netdev %s of unknown type %s",
+                      options->name, options->type);
+            return EAFNOSUPPORT;
+        }
+        error = class->create(class, options->name, options->args,
+                              &netdev_dev);
         if (error) {
-            if (error == EAFNOSUPPORT) {
-                VLOG_WARN("could not create netdev %s of unknown type %s",
-                          options->name, options->type);
-            }
             return error;
         }
+        assert(netdev_dev->netdev_class == class);
         update_device_args(netdev_dev, options->args);
 
     } else if (!shash_is_empty(options->args) &&
 
 
     memset(&netdev_options, 0, sizeof netdev_options);
     netdev_options.name = odp_port->devname;
+    netdev_options.type = odp_port->type;
     netdev_options.ethertype = NETDEV_ETH_TYPE_NONE;
 
     error = netdev_open(&netdev_options, &netdev);
 
 given network device, use the \fBadd\-if\fR command to explicitly add
 that network device to the datapath.
 .PP
-Do not use \fBovs\-dpctl\fR commands to modify datapaths if
-\fBovs\-vswitchd\fR(8) is in use.  Instead, modify the
-\fBovs\-vswitchd\fR configuration file and send \fBSIGHUP\fR to the
-\fBovs\-vswitchd\fR process.
+If \fBovs\-vswitchd\fR(8) is in use, use \fBovs\-vsctl\fR(8) instead
+of \fBovs\-dpctl\fR.
 .PP
 Most \fBovs\-dpctl\fR commands that work with datapaths take an argument
 that specifies the name of the datapath, in one of the following
 The following options are currently supported:
 .
 .RS
-.IP "\fBinternal\fR"
-Instead of attaching an existing \fInetdev\fR, creates an internal
-port (analogous to the local port) with that name.
+.IP "\fBtype=\fItype\fR"
+Specifies the type of port to add.  The default type is \fBsystem\fR.
+.IP "\fIkey\fB=\fIvalue\fR"
+Adds an arbitrary key-value option to the port's configuration.
 .RE
+.IP
+\fBovs\-vswitchd.conf.db\fR(5) documents the available port types and
+options.
 .
 .TP
 \fBdel\-if \fIdp netdev\fR...
 
 #include "dynamic-string.h"
 #include "netdev.h"
 #include "odp-util.h"
+#include "shash.h"
 #include "svec.h"
 #include "timeval.h"
 #include "util.h"
     run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
     for (i = 2; i < argc; i++) {
         char *save_ptr = NULL;
-        char *devname, *suboptions;
-        int flags = 0;
+        struct netdev_options options;
+        struct netdev *netdev;
+        struct shash args;
+        char *option;
         int error;
 
-        devname = strtok_r(argv[i], ",", &save_ptr);
-        if (!devname) {
+        options.name = strtok_r(argv[i], ",", &save_ptr);
+        options.type = "system";
+        options.args = &args;
+        options.ethertype = NETDEV_ETH_TYPE_NONE;
+
+        if (!options.name) {
             ovs_error(0, "%s is not a valid network device name", argv[i]);
             continue;
         }
 
-        suboptions = strtok_r(NULL, "", &save_ptr);
-        if (suboptions) {
-            enum {
-                AP_INTERNAL
-            };
-            static char *options[] = {
-                "internal"
-            };
-
-            while (*suboptions != '\0') {
-                char *value;
-
-                switch (getsubopt(&suboptions, options, &value)) {
-                case AP_INTERNAL:
-                    flags |= ODP_PORT_INTERNAL;
-                    break;
-
-                default:
-                    ovs_error(0, "unknown suboption '%s'", value);
-                    break;
-                }
+        shash_init(&args);
+        while ((option = strtok_r(NULL, "", &save_ptr)) != NULL) {
+            char *save_ptr_2 = NULL;
+            char *key, *value;
+
+            key = strtok_r(option, "=", &save_ptr_2);
+            value = strtok_r(NULL, "", &save_ptr_2);
+            if (!value) {
+                value = "";
+            }
+
+            if (!strcmp(key, "type")) {
+                options.type = value;
+            } else if (!shash_add_once(&args, key, value)) {
+                ovs_error(0, "duplicate \"%s\" option", key);
             }
         }
 
-        error = dpif_port_add(dpif, devname, flags, NULL);
+        error = netdev_open(&options, &netdev);
+        if (error) {
+            ovs_error(error, "%s: failed to open network device",
+                      options.name);
+        } else {
+            error = dpif_port_add(dpif, netdev, NULL);
+            if (error) {
+                ovs_error(error, "adding %s to %s failed",
+                          options.name, argv[1]);
+            } else {
+                error = if_up(options.name);
+            }
+            netdev_close(netdev);
+        }
         if (error) {
-            ovs_error(error, "adding %s to %s failed", devname, argv[1]);
-            failure = true;
-        } else if (if_up(devname)) {
             failure = true;
         }
     }
     }
     query_ports(dpif, &ports, &n_ports);
     for (i = 0; i < n_ports; i++) {
-        printf("\tport %u: %s", ports[i].port, ports[i].devname);
-        if (ports[i].flags & ODP_PORT_INTERNAL) {
-            printf(" (internal)");
+        const struct odp_port *p = &ports[i];
+
+        printf("\tport %u: %s", p->port, p->devname);
+        if (strcmp(p->type, "system")) {
+            printf(" (%s)", p->type);
         }
         printf("\n");
     }
 
         size_t i;
 
         SVEC_FOR_EACH (i, port, &s.ports) {
-            error = dpif_port_add(dpif, port, 0, NULL);
+            struct netdev *netdev;
+
+            error = netdev_open_default(port, &netdev);
+            if (error) {
+                ovs_fatal(error, "%s: failed to open network device", port);
+            }
+
+            error = dpif_port_add(dpif, netdev, NULL);
             if (error) {
                 ovs_fatal(error, "failed to add %s as a port", port);
             }
+
+            netdev_close(netdev);
         }
     }
 
 
 
 /* Initializes 'options' and fills it with the options for 'if_cfg'. Merges
  * keys from "options" and "other_config", preferring "options" keys over
- * "other_config" keys.
- *
- * The value strings in '*options' are taken directly from if_cfg, not copied,
- * so the caller should not modify or free them. */
+ * "other_config" keys. */
 static void
 iface_get_options(const struct ovsrec_interface *if_cfg, struct shash *options)
 {
     }
 }
 
-/* Returns the type of network device that 'iface' should have.  (This is
- * ordinarily the same type as the interface, but the network devices for
- * "internal" ports have type "system".) */
-static const char *
-iface_get_netdev_type(const struct iface *iface)
-{
-    return !strcmp(iface->type, "internal") ? "system" : iface->type;
-}
-
-/* Attempt to create the network device for 'iface' through the netdev
- * library. */
-static int
-create_iface_netdev(struct iface *iface)
-{
-    struct netdev_options netdev_options;
-    struct shash options;
-    int error;
-
-    memset(&netdev_options, 0, sizeof netdev_options);
-    netdev_options.name = iface->cfg->name;
-    netdev_options.type = iface_get_netdev_type(iface);
-    netdev_options.args = &options;
-    netdev_options.ethertype = NETDEV_ETH_TYPE_NONE;
-
-    iface_get_options(iface->cfg, &options);
-
-    error = netdev_open(&netdev_options, &iface->netdev);
-
-    if (iface->netdev) {
-        iface->enabled = netdev_get_carrier(iface->netdev);
-    }
-
-    shash_destroy(&options);
-
-    return error;
-}
-
-static int
-reconfigure_iface_netdev(struct iface *iface)
-{
-    const char *netdev_type, *iface_type;
-    struct shash options;
-    int error;
-
-    /* Skip reconfiguration if the device has the wrong type. This shouldn't
-     * happen, but... */
-    iface_type = iface_get_netdev_type(iface);
-    netdev_type = netdev_get_type(iface->netdev);
-    if (iface_type && strcmp(netdev_type, iface_type)) {
-        VLOG_WARN("%s: attempting change device type from %s to %s",
-                  iface->cfg->name, netdev_type, iface_type);
-        return EINVAL;
-    }
-
-    /* Reconfigure device. */
-    iface_get_options(iface->cfg, &options);
-    error = netdev_reconfigure(iface->netdev, &options);
-    shash_destroy(&options);
-
-    return error;
-}
-
 /* Callback for iterate_and_prune_ifaces(). */
 static bool
 check_iface(struct bridge *br, struct iface *iface, void *aux OVS_UNUSED)
         SHASH_FOR_EACH (node, &want_ifaces) {
             const char *if_name = node->name;
             struct iface *iface = node->data;
-            bool internal = !iface || !strcmp(iface->type, "internal");
             struct odp_port *dpif_port = shash_find_data(&cur_ifaces, if_name);
+            const char *type = iface ? iface->type : "internal";
             int error;
 
             /* If we have a port or a netdev already, and it's not the type we
              * want, then delete the port (if any) and close the netdev (if
              * any). */
-            if (internal
-                ? dpif_port && !(dpif_port->flags & ODP_PORT_INTERNAL)
-                : (iface->netdev
-                   && strcmp(iface->type, netdev_get_type(iface->netdev))))
-            {
+            if ((dpif_port && strcmp(dpif_port->type, type))
+                || (iface && iface->netdev
+                    && strcmp(type, netdev_get_type(iface->netdev)))) {
                 if (dpif_port) {
                     error = ofproto_port_del(br->ofproto, dpif_port->port);
                     if (error) {
                 }
             }
 
-            /* If it's not an internal port, open (possibly create) the
-             * netdev. */
-            if (!internal) {
-                if (!iface->netdev) {
-                    error = create_iface_netdev(iface);
-                    if (error) {
-                        VLOG_WARN("could not create iface %s: %s", iface->name,
-                                  strerror(error));
-                        continue;
-                    }
-                } else {
-                    reconfigure_iface_netdev(iface);
+            /* If the port doesn't exist or we don't have the netdev open,
+             * we need to do more work. */
+            if (!dpif_port || (iface && !iface->netdev)) {
+                struct netdev_options options;
+                struct netdev *netdev;
+                struct shash args;
+
+                /* First open the network device. */
+                options.name = if_name;
+                options.type = type;
+                options.args = &args;
+                options.ethertype = NETDEV_ETH_TYPE_NONE;
+
+                shash_init(&args);
+                if (iface) {
+                    iface_get_options(iface->cfg, &args);
                 }
-            }
+                error = netdev_open(&options, &netdev);
+                shash_destroy(&args);
 
-            /* If it's not part of the datapath, add it. */
-            if (!dpif_port) {
-                error = dpif_port_add(br->dpif, if_name,
-                                      internal ? ODP_PORT_INTERNAL : 0, NULL);
-                if (error == EFBIG) {
-                    VLOG_ERR("ran out of valid port numbers on %s",
-                             dpif_name(br->dpif));
-                    break;
-                } else if (error) {
-                    VLOG_ERR("failed to add %s interface to %s: %s",
-                             if_name, dpif_name(br->dpif), strerror(error));
+                if (error) {
+                    VLOG_WARN("could not open network device %s (%s)",
+                              if_name, strerror(error));
                     continue;
                 }
-            }
 
-            /* If it's an internal port, open the netdev. */
-            if (internal) {
-                if (iface && !iface->netdev) {
-                    error = create_iface_netdev(iface);
+                /* Then add the port if we haven't already. */
+                if (!dpif_port) {
+                    error = dpif_port_add(br->dpif, netdev, NULL);
                     if (error) {
-                        VLOG_WARN("could not create iface %s: %s", iface->name,
-                                  strerror(error));
-                        continue;
+                        netdev_close(netdev);
+                        if (error == EFBIG) {
+                            VLOG_ERR("ran out of valid port numbers on %s",
+                                     dpif_name(br->dpif));
+                            break;
+                        } else {
+                            VLOG_ERR("failed to add %s interface to %s: %s",
+                                     if_name, dpif_name(br->dpif),
+                                     strerror(error));
+                            continue;
+                        }
                     }
                 }
-            } else {
-                assert(iface->netdev != NULL);
+
+                /* Update 'iface'. */
+                if (iface) {
+                    iface->netdev = netdev;
+                    iface->enabled = netdev_get_carrier(iface->netdev);
+                }
+            } else if (iface && iface->netdev) {
+                struct shash args;
+
+                shash_init(&args);
+                iface_get_options(iface->cfg, &args);
+                netdev_reconfigure(iface->netdev, &args);
+                shash_destroy(&args);
             }
         }
         free(dpif_ports);