[ovs-dev] [tunnel 09/11] lib: Switch to flow based tunneling.

Ethan Jackson ethan at nicira.com
Mon Jan 28 00:58:10 UTC 2013


With this patch, ovs-vswitchd uses flow based tunneling
exclusively.  I.E. each kind of tunnel shares a single tunnel
backer in the datapath.  Tunnel headers are set by userspace using
the ipv4_tunnel datapath action.  And, the configuration of
individual tunnels is now a userspace responsibility, so
netdev-vport no longer marshals and unmarshals Netlink attributes
for tunnel configuration, instead only storing the configuration
internally.  There are still some significant pieces of work to do,
but the basic building blocks are there to begin testing.

Signed-off-by: Jesse Gross <jesse at nicira.com>
Signed-off-by: Ethan Jackson <ethan at nicira.com>
---
 NEWS                   |    2 +
 lib/dpif-linux.c       |   49 +++++--
 lib/netdev-vport.c     |  335 ++++++++++--------------------------------------
 lib/netdev-vport.h     |   11 +-
 lib/odp-util.c         |   32 +++--
 lib/odp-util.h         |    7 +-
 ofproto/ofproto-dpif.c |  286 ++++++++++++++++++++++++++++++-----------
 tests/ofproto-dpif.at  |   19 ---
 8 files changed, 358 insertions(+), 383 deletions(-)

diff --git a/NEWS b/NEWS
index 62488d0..1164962 100644
--- a/NEWS
+++ b/NEWS
@@ -26,6 +26,8 @@ post-v1.9.0
     - Inheritance of the Don't Fragment bit in IP tunnels (df_inherit) is
       no longer supported.
     - Patch ports are implemented in userspace.
+    - Tunneling requires the version of the kernel module paired with Open
+      vSwitch 1.9.0 or later.
 
 
 v1.9.0 - xx xxx xxxx
diff --git a/lib/dpif-linux.c b/lib/dpif-linux.c
index 1c126ad..7cb601e 100644
--- a/lib/dpif-linux.c
+++ b/lib/dpif-linux.c
@@ -421,6 +421,44 @@ dpif_linux_get_stats(const struct dpif *dpif_, struct dpif_dp_stats *stats)
     return error;
 }
 
+static const char *
+get_vport_type(const struct dpif_linux_vport *vport)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+
+    switch (vport->type) {
+    case OVS_VPORT_TYPE_NETDEV:
+        return "system";
+
+    case OVS_VPORT_TYPE_INTERNAL:
+        return "internal";
+
+    case OVS_VPORT_TYPE_PATCH:
+        return "patch";
+
+    case OVS_VPORT_TYPE_GRE:
+        return "gre";
+
+    case OVS_VPORT_TYPE_GRE64:
+        return "gre64";
+
+    case OVS_VPORT_TYPE_CAPWAP:
+        return "capwap";
+
+    case OVS_VPORT_TYPE_VXLAN:
+        return "vxlan";
+
+    case OVS_VPORT_TYPE_UNSPEC:
+    case OVS_VPORT_TYPE_FT_GRE:
+    case __OVS_VPORT_TYPE_MAX:
+        break;
+    }
+
+    VLOG_WARN_RL(&rl, "dp%d: port `%s' has unsupported type %u",
+                 vport->dp_ifindex, vport->name, (unsigned int) vport->type);
+    return "unknown";
+}
+
 static int
 dpif_linux_port_add(struct dpif *dpif_, struct netdev *netdev,
                     uint32_t *port_nop)
@@ -429,7 +467,6 @@ dpif_linux_port_add(struct dpif *dpif_, struct netdev *netdev,
     const char *name = netdev_get_dpif_port(netdev);
     const char *type = netdev_get_type(netdev);
     struct dpif_linux_vport request, reply;
-    const struct ofpbuf *options;
     struct nl_sock *sock = NULL;
     uint32_t upcall_pid;
     struct ofpbuf *buf;
@@ -455,12 +492,6 @@ dpif_linux_port_add(struct dpif *dpif_, struct netdev *netdev,
     }
     request.name = name;
 
-    options = netdev_vport_get_options(netdev);
-    if (options && options->size) {
-        request.options = options->data;
-        request.options_len = options->size;
-    }
-
     if (request.type == OVS_VPORT_TYPE_NETDEV) {
         netdev_linux_ethtool_set_flag(netdev, ETH_FLAG_LRO, "LRO", false);
     }
@@ -547,7 +578,7 @@ dpif_linux_port_query__(const struct dpif *dpif, uint32_t port_no,
             error = ENODEV;
         } else if (dpif_port) {
             dpif_port->name = xstrdup(reply.name);
-            dpif_port->type = xstrdup(netdev_vport_get_netdev_type(&reply));
+            dpif_port->type = xstrdup(get_vport_type(&reply));
             dpif_port->port_no = reply.port_no;
         }
         ofpbuf_delete(buf);
@@ -647,7 +678,7 @@ dpif_linux_port_dump_next(const struct dpif *dpif OVS_UNUSED, void *state_,
     }
 
     dpif_port->name = CONST_CAST(char *, vport.name);
-    dpif_port->type = CONST_CAST(char *, netdev_vport_get_netdev_type(&vport));
+    dpif_port->type = CONST_CAST(char *, get_vport_type(&vport));
     dpif_port->port_no = vport.port_no;
     return 0;
 }
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index 35f2a0c..3129c49 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -59,29 +59,24 @@ struct netdev_dev_vport {
     struct netdev_dev netdev_dev;
     unsigned int change_seq;
     uint8_t etheraddr[ETH_ADDR_LEN];
+    struct netdev_stats stats;
 
     /* Tunnels. */
-    struct ofpbuf *options;
     struct netdev_tunnel_config tnl_cfg;
 
     /* Patch Ports. */
-    struct netdev_stats stats;
     char *peer;
 };
 
 struct vport_class {
     enum ovs_vport_type type;
+    const char *dpif_port;
     struct netdev_class netdev_class;
 };
 
-static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
-
 static int netdev_vport_create(const struct netdev_class *, const char *,
                                struct netdev_dev **);
-static void netdev_vport_poll_notify(const struct netdev *);
-static int tnl_port_config_from_nlattr(const struct nlattr *options,
-                                       size_t options_len,
-                                       struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1]);
+static void netdev_vport_poll_notify(struct netdev_dev_vport *);
 
 static bool
 is_vport_class(const struct netdev_class *class)
@@ -115,19 +110,6 @@ get_netdev_tunnel_config(const struct netdev_dev *netdev_dev)
     return &netdev_dev_vport_cast(netdev_dev)->tnl_cfg;
 }
 
-/* If 'netdev' is a vport netdev, returns an ofpbuf that contains Netlink
- * options to include in OVS_VPORT_ATTR_OPTIONS for configuring that vport.
- * Otherwise returns NULL. */
-const struct ofpbuf *
-netdev_vport_get_options(const struct netdev *netdev)
-{
-    const struct netdev_dev *dev = netdev_get_dev(netdev);
-
-    return (is_vport_class(netdev_dev_get_class(dev))
-            ? netdev_dev_vport_cast(dev)->options
-            : NULL);
-}
-
 enum ovs_vport_type
 netdev_vport_get_vport_type(const struct netdev *netdev)
 {
@@ -147,62 +129,6 @@ netdev_vport_is_patch(const struct netdev *netdev)
     return netdev_vport_get_vport_type(netdev) == OVS_VPORT_TYPE_PATCH;
 }
 
-static uint32_t
-get_u32_or_zero(const struct nlattr *a)
-{
-    return a ? nl_attr_get_u32(a) : 0;
-}
-
-const char *
-netdev_vport_get_netdev_type(const struct dpif_linux_vport *vport)
-{
-    struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1];
-
-    switch (vport->type) {
-    case OVS_VPORT_TYPE_UNSPEC:
-        break;
-
-    case OVS_VPORT_TYPE_NETDEV:
-        return "system";
-
-    case OVS_VPORT_TYPE_INTERNAL:
-        return "internal";
-
-    case OVS_VPORT_TYPE_PATCH:
-        return "patch";
-
-    case OVS_VPORT_TYPE_GRE:
-        if (tnl_port_config_from_nlattr(vport->options, vport->options_len,
-                                        a)) {
-            break;
-        }
-        return (get_u32_or_zero(a[OVS_TUNNEL_ATTR_FLAGS]) & TNL_F_IPSEC
-                ? "ipsec_gre" : "gre");
-
-    case OVS_VPORT_TYPE_GRE64:
-        if (tnl_port_config_from_nlattr(vport->options, vport->options_len,
-                                        a)) {
-            break;
-        }
-        return (get_u32_or_zero(a[OVS_TUNNEL_ATTR_FLAGS]) & TNL_F_IPSEC
-                ? "ipsec_gre64" : "gre64");
-
-    case OVS_VPORT_TYPE_CAPWAP:
-        return "capwap";
-
-    case OVS_VPORT_TYPE_VXLAN:
-        return "vxlan";
-
-    case OVS_VPORT_TYPE_FT_GRE:
-    case __OVS_VPORT_TYPE_MAX:
-        break;
-    }
-
-    VLOG_WARN_RL(&rl, "dp%d: port `%s' has unsupported type %u",
-                 vport->dp_ifindex, vport->name, (unsigned int) vport->type);
-    return "unknown";
-}
-
 static int
 netdev_vport_create(const struct netdev_class *netdev_class, const char *name,
                     struct netdev_dev **netdev_devp)
@@ -225,7 +151,6 @@ netdev_vport_destroy(struct netdev_dev *netdev_dev_)
 {
     struct netdev_dev_vport *netdev_dev = netdev_dev_vport_cast(netdev_dev_);
 
-    ofpbuf_delete(netdev_dev->options);
     route_table_unregister();
     free(netdev_dev->peer);
     free(netdev_dev);
@@ -249,8 +174,9 @@ static int
 netdev_vport_set_etheraddr(struct netdev *netdev,
                            const uint8_t mac[ETH_ADDR_LEN])
 {
-    memcpy(netdev_vport_get_dev(netdev)->etheraddr, mac, ETH_ADDR_LEN);
-    netdev_vport_poll_notify(netdev);
+    struct netdev_dev_vport *dev = netdev_vport_get_dev(netdev);
+    memcpy(dev->etheraddr, mac, ETH_ADDR_LEN);
+    netdev_vport_poll_notify(dev);
     return 0;
 }
 
@@ -370,10 +296,8 @@ netdev_vport_wait(void)
 /* Helper functions. */
 
 static void
-netdev_vport_poll_notify(const struct netdev *netdev)
+netdev_vport_poll_notify(struct netdev_dev_vport *ndv)
 {
-    struct netdev_dev_vport *ndv = netdev_vport_get_dev(netdev);
-
     ndv->change_seq++;
     if (!ndv->change_seq) {
         ndv->change_seq++;
@@ -418,26 +342,17 @@ set_tunnel_config(struct netdev_dev *dev_, const struct smap *args)
     bool ipsec_mech_set, needs_dst_port, has_csum;
     struct netdev_tunnel_config tnl_cfg;
     struct smap_node *node;
-    struct ofpbuf *options;
-    int error = EINVAL;
-    uint8_t flags;
 
-    flags = TNL_F_DF_DEFAULT;
     has_csum = strstr(type, "gre");
     ipsec_mech_set = false;
     memset(&tnl_cfg, 0, sizeof tnl_cfg);
 
-    options = ofpbuf_new(64);
-
     if (!strcmp(type, "capwap")) {
         VLOG_WARN_ONCE("CAPWAP tunnel support is deprecated.");
     }
 
     needs_dst_port = !strcmp(type, "vxlan");
     tnl_cfg.ipsec = strstr(type, "ipsec");
-    if (tnl_cfg.ipsec) {
-        flags |= TNL_F_IPSEC;
-    }
     tnl_cfg.dont_fragment = true;
 
     SMAP_FOR_EACH (node, args) {
@@ -457,14 +372,12 @@ set_tunnel_config(struct netdev_dev *dev_, const struct smap *args)
             }
         } else if (!strcmp(node->key, "tos")) {
             if (!strcmp(node->value, "inherit")) {
-                flags |= TNL_F_TOS_INHERIT;
                 tnl_cfg.tos_inherit = true;
             } else {
                 char *endptr;
                 int tos;
                 tos = strtol(node->value, &endptr, 0);
                 if (*endptr == '\0' && tos == (tos & IP_DSCP_MASK)) {
-                    nl_msg_put_u8(options, OVS_TUNNEL_ATTR_TOS, tos);
                     tnl_cfg.tos = tos;
                 } else {
                     VLOG_WARN("%s: invalid TOS %s", name, node->value);
@@ -472,24 +385,18 @@ set_tunnel_config(struct netdev_dev *dev_, const struct smap *args)
             }
         } else if (!strcmp(node->key, "ttl")) {
             if (!strcmp(node->value, "inherit")) {
-                flags |= TNL_F_TTL_INHERIT;
                 tnl_cfg.ttl_inherit = true;
             } else {
-                nl_msg_put_u8(options, OVS_TUNNEL_ATTR_TTL, atoi(node->value));
                 tnl_cfg.ttl = atoi(node->value);
             }
         } else if (!strcmp(node->key, "dst_port") && needs_dst_port) {
             tnl_cfg.dst_port = htons(atoi(node->value));
-            nl_msg_put_u16(options, OVS_TUNNEL_ATTR_DST_PORT,
-                           atoi(node->value));
         } else if (!strcmp(node->key, "csum") && has_csum) {
             if (!strcmp(node->value, "true")) {
-                flags |= TNL_F_CSUM;
                 tnl_cfg.csum = true;
             }
         } else if (!strcmp(node->key, "df_default")) {
             if (!strcmp(node->value, "false")) {
-                flags &= ~TNL_F_DF_DEFAULT;
                 tnl_cfg.dont_fragment = false;
             }
         } else if (!strcmp(node->key, "peer_cert") && tnl_cfg.ipsec) {
@@ -508,7 +415,7 @@ set_tunnel_config(struct netdev_dev *dev_, const struct smap *args)
                 if (!use_ssl_cert || strcmp(use_ssl_cert, "true")) {
                     VLOG_ERR("%s: 'peer_cert' requires 'certificate' argument",
                              name);
-                    goto exit;
+                    return EINVAL;
                 }
                 ipsec_mech_set = true;
             }
@@ -530,7 +437,6 @@ set_tunnel_config(struct netdev_dev *dev_, const struct smap *args)
 
     /* Add a default destination port for VXLAN if none specified. */
     if (needs_dst_port && !tnl_cfg.dst_port) {
-        nl_msg_put_u16(options, OVS_TUNNEL_ATTR_DST_PORT, VXLAN_DST_PORT);
         tnl_cfg.dst_port = htons(VXLAN_DST_PORT);
     }
 
@@ -546,34 +452,31 @@ set_tunnel_config(struct netdev_dev *dev_, const struct smap *args)
         if (pid < 0) {
             VLOG_ERR("%s: IPsec requires the ovs-monitor-ipsec daemon",
                      name);
-            goto exit;
+            return EINVAL;
         }
 
         if (smap_get(args, "peer_cert") && smap_get(args, "psk")) {
             VLOG_ERR("%s: cannot define both 'peer_cert' and 'psk'", name);
-            goto exit;
+            return EINVAL;
         }
 
         if (!ipsec_mech_set) {
             VLOG_ERR("%s: IPsec requires an 'peer_cert' or psk' argument",
                      name);
-            goto exit;
+            return EINVAL;
         }
     }
 
     if (!tnl_cfg.ip_dst) {
         VLOG_ERR("%s: %s type requires valid 'remote_ip' argument",
                  name, type);
-        goto exit;
+        return EINVAL;
     }
-    nl_msg_put_be32(options, OVS_TUNNEL_ATTR_DST_IPV4, tnl_cfg.ip_dst);
 
     if (tnl_cfg.ip_src) {
         if (ip_is_multicast(tnl_cfg.ip_dst)) {
             VLOG_WARN("%s: remote_ip is multicast, ignoring local_ip", name);
             tnl_cfg.ip_src = 0;
-        } else {
-            nl_msg_put_be32(options, OVS_TUNNEL_ATTR_SRC_IPV4, tnl_cfg.ip_src);
         }
     }
 
@@ -584,175 +487,76 @@ set_tunnel_config(struct netdev_dev *dev_, const struct smap *args)
     tnl_cfg.in_key = parse_key(args, "in_key",
                                &tnl_cfg.in_key_present,
                                &tnl_cfg.in_key_flow);
-    if (tnl_cfg.in_key_present && !tnl_cfg.in_key_flow) {
-        nl_msg_put_be64(options, OVS_TUNNEL_ATTR_IN_KEY, tnl_cfg.in_key);
-    }
 
     tnl_cfg.out_key = parse_key(args, "out_key",
                                &tnl_cfg.out_key_present,
                                &tnl_cfg.out_key_flow);
-    if (tnl_cfg.out_key_present && !tnl_cfg.out_key_flow) {
-        nl_msg_put_be64(options, OVS_TUNNEL_ATTR_OUT_KEY, tnl_cfg.out_key);
-    }
-    nl_msg_put_u32(options, OVS_TUNNEL_ATTR_FLAGS, flags);
 
     dev->tnl_cfg = tnl_cfg;
+    netdev_vport_poll_notify(dev);
 
-    error = 0;
-    if (!dev->options
-        || options->size != dev->options->size
-        || memcmp(options->data, dev->options->data, options->size)) {
-        struct dpif_linux_vport vport;
-
-        dpif_linux_vport_init(&vport);
-        vport.cmd = OVS_VPORT_CMD_SET;
-        vport.name = name;
-        vport.options = options->data;
-        vport.options_len = options->size;
-        error = dpif_linux_vport_transact(&vport, NULL, NULL);
-        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()). */
-            ofpbuf_delete(dev->options);
-            dev->options = options;
-            options = NULL;
-            error = 0;
-        }
-    }
-
-exit:
-    ofpbuf_delete(options);
-    return error;
-}
-
-static int
-tnl_port_config_from_nlattr(const struct nlattr *options, size_t options_len,
-                            struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1])
-{
-    static const struct nl_policy ovs_tunnel_policy[] = {
-        [OVS_TUNNEL_ATTR_FLAGS] = { .type = NL_A_U32, .optional = true },
-        [OVS_TUNNEL_ATTR_DST_IPV4] = { .type = NL_A_BE32, .optional = true },
-        [OVS_TUNNEL_ATTR_SRC_IPV4] = { .type = NL_A_BE32, .optional = true },
-        [OVS_TUNNEL_ATTR_IN_KEY] = { .type = NL_A_BE64, .optional = true },
-        [OVS_TUNNEL_ATTR_OUT_KEY] = { .type = NL_A_BE64, .optional = true },
-        [OVS_TUNNEL_ATTR_TOS] = { .type = NL_A_U8, .optional = true },
-        [OVS_TUNNEL_ATTR_TTL] = { .type = NL_A_U8, .optional = true },
-        [OVS_TUNNEL_ATTR_DST_PORT] = { .type = NL_A_U16, .optional = true },
-    };
-    struct ofpbuf buf;
-
-    ofpbuf_use_const(&buf, options, options_len);
-    if (!nl_policy_parse(&buf, 0, ovs_tunnel_policy,
-                         a, ARRAY_SIZE(ovs_tunnel_policy))) {
-        return EINVAL;
-    }
     return 0;
 }
 
-static uint64_t
-get_be64_or_zero(const struct nlattr *a)
-{
-    return a ? ntohll(nl_attr_get_be64(a)) : 0;
-}
-
 static int
-get_tunnel_config(struct netdev_dev *dev_, struct smap *args)
+get_tunnel_config(struct netdev_dev *dev, struct smap *args)
 {
-    struct netdev_dev_vport *dev = netdev_dev_vport_cast(dev_);
-    const char *name = netdev_dev_get_name(dev_);
-    struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1];
-    uint32_t flags;
-    int error;
+    const struct netdev_tunnel_config *tnl_cfg =
+        &netdev_dev_vport_cast(dev)->tnl_cfg;
 
-    if (!dev->options) {
-        struct dpif_linux_vport reply;
-        struct ofpbuf *buf;
-
-        error = dpif_linux_vport_get(name, &reply, &buf);
-        if (error) {
-            VLOG_ERR_RL(&rl, "%s: vport query failed (%s)", name,
-                        strerror(error));
-            return error;
-        }
-
-        dev->options = ofpbuf_clone_data(reply.options, reply.options_len);
-        ofpbuf_delete(buf);
+    if (tnl_cfg->ip_dst) {
+        smap_add_format(args, "remote_ip", IP_FMT, IP_ARGS(tnl_cfg->ip_dst));
     }
 
-    error = tnl_port_config_from_nlattr(dev->options->data, dev->options->size,
-                                        a);
-    if (error) {
-        VLOG_ERR_RL(&rl, "%s: failed to parse kernel config (%s)",
-                    name, strerror(error));
-        return error;
+    if (tnl_cfg->ip_src) {
+        smap_add_format(args, "local_ip", IP_FMT, IP_ARGS(tnl_cfg->ip_src));
     }
 
-    if (a[OVS_TUNNEL_ATTR_DST_IPV4]) {
-        ovs_be32 daddr = nl_attr_get_be32(a[OVS_TUNNEL_ATTR_DST_IPV4]);
-        smap_add_format(args, "remote_ip", IP_FMT, IP_ARGS(daddr));
-    }
-
-    if (a[OVS_TUNNEL_ATTR_SRC_IPV4]) {
-        ovs_be32 saddr = nl_attr_get_be32(a[OVS_TUNNEL_ATTR_SRC_IPV4]);
-        smap_add_format(args, "local_ip", IP_FMT, IP_ARGS(saddr));
-    }
-
-    if (!a[OVS_TUNNEL_ATTR_IN_KEY] && !a[OVS_TUNNEL_ATTR_OUT_KEY]) {
+    if (tnl_cfg->in_key_flow && tnl_cfg->out_key_flow) {
         smap_add(args, "key", "flow");
+    } else if (tnl_cfg->in_key_present && tnl_cfg->out_key_present
+               && tnl_cfg->in_key == tnl_cfg->out_key) {
+        smap_add_format(args, "key", "%"PRIu64, ntohll(tnl_cfg->in_key));
     } else {
-        uint64_t in_key = get_be64_or_zero(a[OVS_TUNNEL_ATTR_IN_KEY]);
-        uint64_t out_key = get_be64_or_zero(a[OVS_TUNNEL_ATTR_OUT_KEY]);
-
-        if (in_key && in_key == out_key) {
-            smap_add_format(args, "key", "%"PRIu64, in_key);
-        } else {
-            if (!a[OVS_TUNNEL_ATTR_IN_KEY]) {
-                smap_add(args, "in_key", "flow");
-            } else if (in_key) {
-                smap_add_format(args, "in_key", "%"PRIu64, in_key);
-            }
+        if (tnl_cfg->in_key_flow) {
+            smap_add(args, "in_key", "flow");
+        } else if (tnl_cfg->in_key_present) {
+            smap_add_format(args, "in_key", "%"PRIu64,
+                            ntohll(tnl_cfg->in_key));
+        }
 
-            if (!a[OVS_TUNNEL_ATTR_OUT_KEY]) {
-                smap_add(args, "out_key", "flow");
-            } else if (out_key) {
-                smap_add_format(args, "out_key", "%"PRIu64, out_key);
-            }
+        if (tnl_cfg->out_key_flow) {
+            smap_add(args, "out_key", "flow");
+        } else if (tnl_cfg->out_key_present) {
+            smap_add_format(args, "out_key", "%"PRIu64,
+                            ntohll(tnl_cfg->out_key));
         }
     }
 
-    flags = get_u32_or_zero(a[OVS_TUNNEL_ATTR_FLAGS]);
-
-    if (flags & TNL_F_TTL_INHERIT) {
+    if (tnl_cfg->ttl_inherit) {
         smap_add(args, "ttl", "inherit");
-    } else if (a[OVS_TUNNEL_ATTR_TTL]) {
-        int ttl = nl_attr_get_u8(a[OVS_TUNNEL_ATTR_TTL]);
-        smap_add_format(args, "ttl", "%d", ttl);
+    } else if (tnl_cfg->ttl != DEFAULT_TTL) {
+        smap_add_format(args, "ttl", "%"PRIu8, tnl_cfg->ttl);
     }
 
-    if (flags & TNL_F_TOS_INHERIT) {
+    if (tnl_cfg->tos_inherit) {
         smap_add(args, "tos", "inherit");
-    } else if (a[OVS_TUNNEL_ATTR_TOS]) {
-        int tos = nl_attr_get_u8(a[OVS_TUNNEL_ATTR_TOS]);
-        smap_add_format(args, "tos", "0x%x", tos);
+    } else if (tnl_cfg->tos) {
+        smap_add_format(args, "tos", "0x%x", tnl_cfg->tos);
     }
 
-    if (a[OVS_TUNNEL_ATTR_DST_PORT]) {
-        uint16_t dst_port = nl_attr_get_u16(a[OVS_TUNNEL_ATTR_DST_PORT]);
+    if (tnl_cfg->dst_port) {
+        uint16_t dst_port = ntohs(tnl_cfg->dst_port);
         if (dst_port != VXLAN_DST_PORT) {
             smap_add_format(args, "dst_port", "%d", dst_port);
         }
     }
 
-    if (flags & TNL_F_CSUM) {
+    if (tnl_cfg->csum) {
         smap_add(args, "csum", "true");
     }
-    if (flags & TNL_F_DF_INHERIT) {
-        /* Shouldn't happen as "df_inherit" is no longer supported.  However,
-         * for completeness we report it if it's there. */
-        smap_add(args, "df_inherit", "true");
-    }
-    if (!(flags & TNL_F_DF_DEFAULT)) {
+
+    if (!tnl_cfg->dont_fragment) {
         smap_add(args, "df_default", "false");
     }
 
@@ -770,10 +574,10 @@ netdev_vport_patch_peer(const struct netdev *netdev)
 }
 
 void
-netdev_vport_patch_inc_rx(const struct netdev *netdev,
+netdev_vport_inc_rx(const struct netdev *netdev,
                           const struct dpif_flow_stats *stats)
 {
-    if (netdev_vport_is_patch(netdev)) {
+    if (is_vport_class(netdev_dev_get_class(netdev_get_dev(netdev)))) {
         struct netdev_dev_vport *dev = netdev_vport_get_dev(netdev);
         dev->stats.rx_packets += stats->n_packets;
         dev->stats.rx_bytes += stats->n_bytes;
@@ -781,16 +585,22 @@ netdev_vport_patch_inc_rx(const struct netdev *netdev,
 }
 
 void
-netdev_vport_patch_inc_tx(const struct netdev *netdev,
-                          const struct dpif_flow_stats *stats)
+netdev_vport_inc_tx(const struct netdev *netdev,
+                    const struct dpif_flow_stats *stats)
 {
-    if (netdev_vport_is_patch(netdev)) {
+    if (is_vport_class(netdev_dev_get_class(netdev_get_dev(netdev)))) {
         struct netdev_dev_vport *dev = netdev_vport_get_dev(netdev);
         dev->stats.tx_packets += stats->n_packets;
         dev->stats.tx_bytes += stats->n_bytes;
     }
 }
 
+static const char *
+get_dpif_port(struct netdev_dev *dev)
+{
+    return vport_class_cast(netdev_dev_get_class(dev))->dpif_port;
+}
+
 static int
 get_patch_config(struct netdev_dev *dev_, struct smap *args)
 {
@@ -832,7 +642,7 @@ set_patch_config(struct netdev_dev *dev_, const struct smap *args)
 }
 
 static int
-patch_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
+get_stats(const struct netdev *netdev, struct netdev_stats *stats)
 {
     struct netdev_dev_vport *dev = netdev_vport_get_dev(netdev);
     memcpy(stats, &dev->stats, sizeof *stats);
@@ -840,15 +650,14 @@ patch_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
 }
 
 #define VPORT_FUNCTIONS(GET_CONFIG, SET_CONFIG,             \
-                        GET_TUNNEL_CONFIG, GET_STATS,       \
-                        GET_STATUS)                         \
+                        GET_TUNNEL_CONFIG, GET_STATUS)      \
     NULL,                                                   \
     netdev_vport_run,                                       \
     netdev_vport_wait,                                      \
                                                             \
     netdev_vport_create,                                    \
     netdev_vport_destroy,                                   \
-    NULL,                       /* get_dpif_port */         \
+    get_dpif_port,                                          \
     GET_CONFIG,                                             \
     SET_CONFIG,                                             \
     GET_TUNNEL_CONFIG,                                      \
@@ -872,7 +681,7 @@ patch_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
     NULL,                       /* get_carrier */           \
     NULL,                       /* get_carrier_resets */    \
     NULL,                       /* get_miimon */            \
-    GET_STATS,                                              \
+    get_stats,                                              \
     NULL,                       /* set_stats */             \
                                                             \
     NULL,                       /* get_features */          \
@@ -902,30 +711,28 @@ patch_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
                                                             \
     netdev_vport_change_seq
 
-#define TUNNEL_CLASS(NAME, VPORT_TYPE)                      \
-    { VPORT_TYPE,                                           \
+#define TUNNEL_CLASS(NAME, VPORT_TYPE, DPIF_PORT)           \
+    { VPORT_TYPE, DPIF_PORT,                                \
         { NAME, VPORT_FUNCTIONS(get_tunnel_config,          \
                                 set_tunnel_config,          \
                                 get_netdev_tunnel_config,   \
-                                netdev_vport_get_stats,     \
                                 tunnel_get_status) }}
 
 void
 netdev_vport_register(void)
 {
     static const struct vport_class vport_classes[] = {
-        TUNNEL_CLASS("gre", OVS_VPORT_TYPE_GRE),
-        TUNNEL_CLASS("ipsec_gre", OVS_VPORT_TYPE_GRE),
-        TUNNEL_CLASS("gre64", OVS_VPORT_TYPE_GRE64),
-        TUNNEL_CLASS("ipsec_gre64", OVS_VPORT_TYPE_GRE64),
-        TUNNEL_CLASS("capwap", OVS_VPORT_TYPE_CAPWAP),
-        TUNNEL_CLASS("vxlan", OVS_VPORT_TYPE_VXLAN),
-
-        { OVS_VPORT_TYPE_PATCH,
+        TUNNEL_CLASS("gre", OVS_VPORT_TYPE_GRE, "gre_system"),
+        TUNNEL_CLASS("ipsec_gre", OVS_VPORT_TYPE_GRE, "gre_system"),
+        TUNNEL_CLASS("gre64", OVS_VPORT_TYPE_GRE64, "gre64_system"),
+        TUNNEL_CLASS("ipsec_gre64", OVS_VPORT_TYPE_GRE64, "gre64_system"),
+        TUNNEL_CLASS("capwap", OVS_VPORT_TYPE_CAPWAP, "capwap_system"),
+        TUNNEL_CLASS("vxlan", OVS_VPORT_TYPE_VXLAN, "vxlan_system"),
+
+        { OVS_VPORT_TYPE_PATCH, NULL,
           { "patch", VPORT_FUNCTIONS(get_patch_config,
                                      set_patch_config,
                                      NULL,
-                                     patch_get_stats,
                                      NULL) }},
     };
 
diff --git a/lib/netdev-vport.h b/lib/netdev-vport.h
index b372a74..caf8687 100644
--- a/lib/netdev-vport.h
+++ b/lib/netdev-vport.h
@@ -26,19 +26,16 @@ struct netdev_stats;
 
 void netdev_vport_register(void);
 
-const struct ofpbuf *netdev_vport_get_options(const struct netdev *);
-
 enum ovs_vport_type netdev_vport_get_vport_type(const struct netdev *);
-const char *netdev_vport_get_netdev_type(const struct dpif_linux_vport *);
 bool netdev_vport_is_patch(const struct netdev *);
 
 int netdev_vport_get_stats(const struct netdev *, struct netdev_stats *);
 
 const char *netdev_vport_patch_peer(const struct netdev *netdev);
 
-void netdev_vport_patch_inc_rx(const struct netdev *,
-                               const struct dpif_flow_stats *);
-void netdev_vport_patch_inc_tx(const struct netdev *,
-                               const struct dpif_flow_stats *);
+void netdev_vport_inc_rx(const struct netdev *,
+                         const struct dpif_flow_stats *);
+void netdev_vport_inc_tx(const struct netdev *,
+                         const struct dpif_flow_stats *);
 
 #endif /* netdev-vport.h */
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 96a9523..3c5c14b 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -2045,6 +2045,15 @@ odp_put_userspace_action(uint32_t pid, const union user_action_cookie *cookie,
 
     return cookie ? odp_actions->size - NLA_ALIGN(sizeof *cookie) : 0;
 }
+
+void
+odp_put_tunnel_action(const struct flow_tnl *tunnel,
+                      struct ofpbuf *odp_actions)
+{
+    size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET);
+    tun_key_to_attr(odp_actions, tunnel);
+    nl_msg_end_nested(odp_actions, offset);
+}
 
 /* The commit_odp_actions() function and its helpers. */
 
@@ -2057,8 +2066,14 @@ commit_set_action(struct ofpbuf *odp_actions, enum ovs_key_attr key_type,
     nl_msg_end_nested(odp_actions, offset);
 }
 
-static void
-commit_set_tunnel_action(const struct flow *flow, struct flow *base,
+/* If any of the flow key data that ODP actions can modify are different in
+ * 'base->tunnel' and 'flow->tunnel', appends a set_tunnel ODP action to
+ * 'odp_actions' that change the flow tunneling information in key from
+ * 'base->tunnel' into 'flow->tunnel', and then changes 'base->tunnel' in the
+ * same way.  In other words, operates the same as commit_odp_actions(), but
+ * only on tunneling information. */
+void
+commit_odp_tunnel_action(const struct flow *flow, struct flow *base,
                          struct ofpbuf *odp_actions)
 {
     if (!memcmp(&base->tunnel, &flow->tunnel, sizeof base->tunnel)) {
@@ -2068,11 +2083,7 @@ commit_set_tunnel_action(const struct flow *flow, struct flow *base,
 
     /* A valid IPV4_TUNNEL must have non-zero ip_dst. */
     if (flow->tunnel.ip_dst) {
-        size_t offset;
-
-        offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET);
-        tun_key_to_attr(odp_actions, &base->tunnel);
-        nl_msg_end_nested(odp_actions, offset);
+        odp_put_tunnel_action(&base->tunnel, odp_actions);
     } else {
         commit_set_action(odp_actions, OVS_KEY_ATTR_TUN_ID,
                           &base->tunnel.tun_id, sizeof base->tunnel.tun_id);
@@ -2254,12 +2265,13 @@ commit_set_skb_mark_action(const struct flow *flow, struct flow *base,
 }
 /* If any of the flow key data that ODP actions can modify are different in
  * 'base' and 'flow', appends ODP actions to 'odp_actions' that change the flow
- * key from 'base' into 'flow', and then changes 'base' the same way. */
+ * key from 'base' into 'flow', and then changes 'base' the same way.  Does not
+ * commit set_tunnel actions.  Users should call commit_odp_tunnel_action()
+ * in addition to this function if needed. */
 void
 commit_odp_actions(const struct flow *flow, struct flow *base,
                    struct ofpbuf *odp_actions)
 {
-    commit_set_tunnel_action(flow, base, odp_actions);
     commit_set_ether_addr_action(flow, base, odp_actions);
     commit_vlan_action(flow, base, odp_actions);
     commit_set_nw_action(flow, base, odp_actions);
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 9d0cc86..ccf6c2a 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
 
 struct ds;
 struct flow;
+struct flow_tnl;
 struct nlattr;
 struct ofpbuf;
 struct simap;
@@ -113,6 +114,8 @@ enum odp_key_fitness odp_flow_key_to_flow(const struct nlattr *, size_t,
                                           struct flow *);
 const char *odp_key_fitness_to_string(enum odp_key_fitness);
 
+void commit_odp_tunnel_action(const struct flow *, struct flow *base,
+                              struct ofpbuf *odp_actions);
 void commit_odp_actions(const struct flow *, struct flow *base,
                         struct ofpbuf *odp_actions);
 
@@ -151,6 +154,8 @@ BUILD_ASSERT_DECL(sizeof(union user_action_cookie) == 8);
 size_t odp_put_userspace_action(uint32_t pid,
                                 const union user_action_cookie *,
                                 struct ofpbuf *odp_actions);
+void odp_put_tunnel_action(const struct flow_tnl *tunnel,
+                           struct ofpbuf *odp_actions);
 
 /* Reasons why a subfacet might not be fast-pathable. */
 enum slow_path_reason {
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 2d7db24..6429d18 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -52,6 +52,7 @@
 #include "simap.h"
 #include "smap.h"
 #include "timer.h"
+#include "tunnel.h"
 #include "unaligned.h"
 #include "unixctl.h"
 #include "vlan-bitmap.h"
@@ -140,7 +141,7 @@ struct ofmirror {
     unsigned long *vlans;       /* Bitmap of chosen VLANs, NULL selects all. */
 
     /* Output (exactly one of out == NULL and out_vlan == -1 is true). */
-    struct ofbundle *out;       /* Output port or NULL. */
+    struct ofbundle *out;       /* Output port or null. */
     int out_vlan;               /* Output VLAN or -1. */
     mirror_mask_t dup_mirrors;  /* Bitmap of mirrors with the same output. */
 
@@ -512,6 +513,7 @@ struct ofport_dpif {
     uint32_t bond_stable_id;    /* stable_id to use as bond slave, or 0. */
     bool may_enable;            /* May be enabled in bonds. */
     long long int carrier_seq;  /* Carrier status changes. */
+    struct tnl_port *tnl_port;  /* Tunnel handle, or NULL. */
 
     /* Spanning tree. */
     struct stp_port *stp_port;  /* Spanning Tree Protocol, if any. */
@@ -623,6 +625,8 @@ struct dpif_backer {
     struct timer next_expiration;
     struct hmap odp_to_ofport_map; /* ODP port to ofport mapping. */
 
+    struct sset tnl_backers;       /* Set of dpif ports backing tunnels. */
+
     /* Facet revalidation flags applying to facets which use this backer. */
     enum revalidate_reason need_revalidate; /* Revalidate every facet. */
     struct tag_set revalidate_set; /* Revalidate only matching facets. */
@@ -714,6 +718,7 @@ static struct ofport_dpif *get_odp_port(const struct ofproto_dpif *,
 static void ofproto_trace(struct ofproto_dpif *, const struct flow *,
                           const struct ofpbuf *, ovs_be16 initial_tci,
                           struct ds *);
+static bool may_dpif_port_del(struct ofport_dpif *);
 
 /* Packet processing. */
 static void update_learning_table(struct ofproto_dpif *,
@@ -891,6 +896,14 @@ type_run(const char *type)
             goto next;
         }
 
+        HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node,
+                       &all_ofproto_dpifs) {
+            if (sset_contains(&ofproto->ports, devname)
+                || sset_contains(&ofproto->backer->tnl_backers, devname)) {
+                goto next;
+            }
+        }
+
         ofproto = lookup_ofproto_dpif_by_port_name(devname);
         if (dpif_port_query_by_name(backer->dpif, devname, &port)) {
             /* The port was removed.  If we know the datapath,
@@ -1008,6 +1021,7 @@ close_dpif_backer(struct dpif_backer *backer)
         return;
     }
 
+    sset_destroy(&backer->tnl_backers);
     hmap_destroy(&backer->odp_to_ofport_map);
     node = shash_find(&all_dpif_backers, backer->type);
     free(backer->type);
@@ -1084,6 +1098,7 @@ open_dpif_backer(const char *type, struct dpif_backer **backerp)
     hmap_init(&backer->drop_keys);
     timer_set_duration(&backer->next_expiration, 1000);
     backer->need_revalidate = 0;
+    sset_init(&backer->tnl_backers);
     tag_set_init(&backer->revalidate_set);
     *backerp = backer;
 
@@ -1548,6 +1563,7 @@ port_construct(struct ofport *port_)
     port->may_enable = true;
     port->stp_port = NULL;
     port->stp_state = STP_DISABLED;
+    port->tnl_port = NULL;
     hmap_init(&port->priorities);
     port->realdev_ofp_port = 0;
     port->vlandev_vid = 0;
@@ -1560,7 +1576,7 @@ port_construct(struct ofport *port_)
     }
 
     error = dpif_port_query_by_name(ofproto->backer->dpif,
-                                    netdev_get_name(port->up.netdev),
+                                    netdev_get_dpif_port(port->up.netdev),
                                     &dpif_port);
     if (error) {
         return error;
@@ -1568,16 +1584,20 @@ port_construct(struct ofport *port_)
 
     port->odp_port = dpif_port.port_no;
 
-    /* Sanity-check that a mapping doesn't already exist.  This
-     * shouldn't happen. */
-    if (odp_port_to_ofp_port(ofproto, port->odp_port) != OFPP_NONE) {
-        VLOG_ERR("port %s already has an OpenFlow port number\n",
-                 dpif_port.name);
-        return EBUSY;
-    }
+    if (netdev_get_tunnel_config(port->up.netdev)) {
+        port->tnl_port = tnl_port_add(&port->up, port->odp_port);
+    } else {
+        /* Sanity-check that a mapping doesn't already exist.  This
+         * shouldn't happen for non-tunnel ports. */
+        if (odp_port_to_ofp_port(ofproto, port->odp_port) != OFPP_NONE) {
+            VLOG_ERR("port %s already has an OpenFlow port number",
+                     dpif_port.name);
+            return EBUSY;
+        }
 
-    hmap_insert(&ofproto->backer->odp_to_ofport_map, &port->odp_port_node,
-                hash_int(port->odp_port, 0));
+        hmap_insert(&ofproto->backer->odp_to_ofport_map, &port->odp_port_node,
+                    hash_int(port->odp_port, 0));
+    }
 
     if (ofproto->sflow) {
         dpif_sflow_add_port(ofproto->sflow, port_, port->odp_port);
@@ -1591,20 +1611,24 @@ port_destruct(struct ofport *port_)
 {
     struct ofport_dpif *port = ofport_dpif_cast(port_);
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(port->up.ofproto);
+    const char *dp_port_name = netdev_get_dpif_port(port->up.netdev);
     const char *devname = netdev_get_name(port->up.netdev);
 
-    if (dpif_port_exists(ofproto->backer->dpif, devname)) {
+    if (dpif_port_exists(ofproto->backer->dpif, dp_port_name)
+        && may_dpif_port_del(port)) {
         /* The underlying device is still there, so delete it.  This
          * happens when the ofproto is being destroyed, since the caller
          * assumes that removal of attached ports will happen as part of
          * destruction. */
         dpif_port_del(ofproto->backer->dpif, port->odp_port);
+        sset_find_and_delete(&ofproto->backer->tnl_backers, dp_port_name);
     }
 
-    if (port->odp_port != OVSP_NONE) {
+    if (port->odp_port != OVSP_NONE && !port->tnl_port) {
         hmap_remove(&ofproto->backer->odp_to_ofport_map, &port->odp_port_node);
     }
 
+    tnl_port_del(port->tnl_port);
     sset_find_and_delete(&ofproto->ports, devname);
     sset_find_and_delete(&ofproto->ghost_ports, devname);
     ofproto->backer->need_revalidate = REV_RECONFIGURE;
@@ -2893,6 +2917,13 @@ port_run(struct ofport_dpif *ofport)
     ofport->carrier_seq = carrier_seq;
 
     port_run_fast(ofport);
+
+    if (ofport->tnl_port
+        && tnl_port_reconfigure(&ofport->up, ofport->odp_port,
+                                &ofport->tnl_port)) {
+        ofproto_dpif_cast(ofport->up.ofproto)->backer->need_revalidate = true;
+    }
+
     if (ofport->cfm) {
         int cfm_opup = cfm_get_opup(ofport->cfm);
 
@@ -2971,39 +3002,90 @@ static int
 port_add(struct ofproto *ofproto_, struct netdev *netdev)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
-    uint32_t odp_port = UINT32_MAX;
-    int error;
+    const char *dp_port_name = netdev_get_dpif_port(netdev);
+    const char *devname = netdev_get_name(netdev);
 
     if (netdev_vport_is_patch(netdev)) {
         sset_add(&ofproto->ghost_ports, netdev_get_name(netdev));
         return 0;
     }
 
-    error = dpif_port_add(ofproto->backer->dpif, netdev, &odp_port);
-    if (!error) {
-        sset_add(&ofproto->ports, netdev_get_name(netdev));
+    if (!dpif_port_exists(ofproto->backer->dpif, dp_port_name)) {
+        int error = dpif_port_add(ofproto->backer->dpif, netdev, NULL);
+        if (error) {
+            return error;
+        }
     }
-    return error;
+
+    if (netdev_get_tunnel_config(netdev)) {
+        sset_add(&ofproto->ghost_ports, devname);
+        sset_add(&ofproto->backer->tnl_backers, dp_port_name);
+    } else {
+        sset_add(&ofproto->ports, devname);
+    }
+    return 0;
+}
+
+/* Returns true if the odp_port backing 'ofport' may be deleted from the
+ * datapath. In most cases, this function simply returns true. However, for
+ * tunnels it's possible that multiple ofports use the same odp_port, in which
+ * case we need to keep the odp_port backer around until the last ofport is
+ * deleted. */
+static bool
+may_dpif_port_del(struct ofport_dpif *ofport)
+{
+    struct dpif_backer *backer = ofproto_dpif_cast(ofport->up.ofproto)->backer;
+    struct ofproto_dpif *ofproto_iter;
+
+    if (!ofport->tnl_port) {
+        return true;
+    }
+
+    HMAP_FOR_EACH (ofproto_iter, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
+        struct ofport_dpif *iter;
+
+        if (backer != ofproto_iter->backer) {
+            continue;
+        }
+
+        HMAP_FOR_EACH (iter, up.hmap_node, &ofproto_iter->up.ports) {
+            if (ofport == iter) {
+                continue;
+            }
+
+            if (!strcmp(netdev_get_dpif_port(ofport->up.netdev),
+                        netdev_get_dpif_port(iter->up.netdev))) {
+                return false;
+            }
+        }
+    }
+
+    return true;
 }
 
 static int
 port_del(struct ofproto *ofproto_, uint16_t ofp_port)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
-    uint32_t odp_port = ofp_port_to_odp_port(ofproto, ofp_port);
+    struct ofport_dpif *ofport = get_ofp_port(ofproto, ofp_port);
     int error = 0;
 
-    if (odp_port != OFPP_NONE) {
-        error = dpif_port_del(ofproto->backer->dpif, odp_port);
+    if (!ofport) {
+        return 0;
     }
-    if (!error) {
-        struct ofport_dpif *ofport = get_ofp_port(ofproto, ofp_port);
-        if (ofport) {
+
+    sset_find_and_delete(&ofproto->ghost_ports,
+                         netdev_get_name(ofport->up.netdev));
+    if (may_dpif_port_del(ofport)) {
+        error = dpif_port_del(ofproto->backer->dpif, ofport->odp_port);
+        if (!error) {
             /* The caller is going to close ofport->up.netdev.  If this is a
              * bonded port, then the bond is using that netdev, so remove it
              * from the bond.  The client will need to reconfigure everything
              * after deleting ports, so then the slave will get re-added. */
             bundle_remove(&ofport->up);
+            sset_find_and_delete(&ofproto->backer->tnl_backers,
+                                 netdev_get_dpif_port(ofport->up.netdev));
         }
     }
     return error;
@@ -3080,7 +3162,7 @@ port_dump_start(const struct ofproto *ofproto_ OVS_UNUSED, void **statep)
 }
 
 static int
-port_dump_next(const struct ofproto *ofproto_ OVS_UNUSED, void *state_,
+port_dump_next(const struct ofproto *ofproto_, void *state_,
                struct ofproto_port *port)
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
@@ -3548,6 +3630,10 @@ drop_key_clear(struct dpif_backer *backer)
  * odp_flow_key_to_flow().  (This differs from the value returned in
  * flow->vlan_tci only for packets received on VLAN splinters.)
  *
+ * Similarly, this function also includes some logic to help with tunnels.  It
+ * may modify 'flow' as necessary to make the tunneling implementation
+ * transparent to the upcall processing logic.
+ *
  * Returns 0 if successful, ENODEV if the parsed flow has no associated ofport,
  * or some other positive errno if there are other problems. */
 static int
@@ -3559,7 +3645,7 @@ ofproto_receive(const struct dpif_backer *backer, struct ofpbuf *packet,
 {
     const struct ofport_dpif *port;
     enum odp_key_fitness fitness;
-    int error;
+    int error = ENODEV;
 
     fitness = odp_flow_key_to_flow(key, key_len, flow);
     if (fitness == ODP_FIT_ERROR) {
@@ -3575,44 +3661,60 @@ ofproto_receive(const struct dpif_backer *backer, struct ofpbuf *packet,
         *odp_in_port = flow->in_port;
     }
 
-    port = odp_port_to_ofport(backer, flow->in_port);
-    if (!port) {
-        flow->in_port = OFPP_NONE;
-        error = ENODEV;
-        goto exit;
-    }
+    if (tnl_port_should_receive(flow)) {
+        const struct ofport *ofport = tnl_port_receive(flow);
+        if (!ofport) {
+            flow->in_port = OFPP_NONE;
+            goto exit;
+        }
+        port = ofport_dpif_cast(ofport);
 
-    if (ofproto) {
-        *ofproto = ofproto_dpif_cast(port->up.ofproto);
-    }
+        /* We can't reproduce 'key' from 'flow'. */
+        fitness = fitness == ODP_FIT_PERFECT ? ODP_FIT_TOO_MUCH : fitness;
 
-    flow->in_port = port->up.ofp_port;
-    if (vsp_adjust_flow(ofproto_dpif_cast(port->up.ofproto), flow)) {
-        if (packet) {
-            /* Make the packet resemble the flow, so that it gets sent to an
-             * OpenFlow controller properly, so that it looks correct for
-             * sFlow, and so that flow_extract() will get the correct vlan_tci
-             * if it is called on 'packet'.
-             *
-             * The allocated space inside 'packet' probably also contains
-             * 'key', that is, both 'packet' and 'key' are probably part of a
-             * struct dpif_upcall (see the large comment on that structure
-             * definition), so pushing data on 'packet' is in general not a
-             * good idea since it could overwrite 'key' or free it as a side
-             * effect.  However, it's OK in this special case because we know
-             * that 'packet' is inside a Netlink attribute: pushing 4 bytes
-             * will just overwrite the 4-byte "struct nlattr", which is fine
-             * since we don't need that header anymore. */
-            eth_push_vlan(packet, flow->vlan_tci);
-        }
-
-        /* Let the caller know that we can't reproduce 'key' from 'flow'. */
-        if (fitness == ODP_FIT_PERFECT) {
-            fitness = ODP_FIT_TOO_MUCH;
+        /* XXX: Since the tunnel module is not scoped per backer, it's
+         * theoretically possible that we'll receive an ofport belonging to an
+         * entirely different datapath.  In practice, this can't happen because
+         * no platforms has two separate datapaths which each support
+         * tunneling. */
+        ovs_assert(ofproto_dpif_cast(port->up.ofproto)->backer == backer);
+    } else {
+        port = odp_port_to_ofport(backer, flow->in_port);
+        if (!port) {
+            flow->in_port = OFPP_NONE;
+            goto exit;
+        }
+
+        flow->in_port = port->up.ofp_port;
+        if (vsp_adjust_flow(ofproto_dpif_cast(port->up.ofproto), flow)) {
+            if (packet) {
+                /* Make the packet resemble the flow, so that it gets sent to
+                 * an OpenFlow controller properly, so that it looks correct
+                 * for sFlow, and so that flow_extract() will get the correct
+                 * vlan_tci if it is called on 'packet'.
+                 *
+                 * The allocated space inside 'packet' probably also contains
+                 * 'key', that is, both 'packet' and 'key' are probably part of
+                 * a struct dpif_upcall (see the large comment on that
+                 * structure definition), so pushing data on 'packet' is in
+                 * general not a good idea since it could overwrite 'key' or
+                 * free it as a side effect.  However, it's OK in this special
+                 * case because we know that 'packet' is inside a Netlink
+                 * attribute: pushing 4 bytes will just overwrite the 4-byte
+                 * "struct nlattr", which is fine since we don't need that
+                 * header anymore. */
+                eth_push_vlan(packet, flow->vlan_tci);
+            }
+            /* We can't reproduce 'key' from 'flow'. */
+            fitness = fitness == ODP_FIT_PERFECT ? ODP_FIT_TOO_MUCH : fitness;
         }
     }
     error = 0;
 
+    if (ofproto) {
+        *ofproto = ofproto_dpif_cast(port->up.ofproto);
+    }
+
 exit:
     if (fitnessp) {
         *fitnessp = fitness;
@@ -3988,6 +4090,7 @@ update_stats(struct dpif_backer *backer)
         struct flow flow;
         struct subfacet *subfacet;
         struct ofproto_dpif *ofproto;
+        struct ofport_dpif *ofport;
         uint32_t key_hash;
 
         if (drop_key_lookup(backer, key, key_len)) {
@@ -3999,6 +4102,11 @@ update_stats(struct dpif_backer *backer)
             continue;
         }
 
+        ofport = get_ofp_port(ofproto, flow.in_port);
+        if (ofport && ofport->tnl_port) {
+            netdev_vport_inc_rx(ofport->up.netdev, stats);
+        }
+
         key_hash = odp_flow_key_hash(key, key_len);
         subfacet = subfacet_find(ofproto, key, key_len, key_hash, &flow);
         switch (subfacet ? subfacet->path : SF_NOT_INSTALLED) {
@@ -5341,6 +5449,7 @@ static int
 send_packet(const struct ofport_dpif *ofport, struct ofpbuf *packet)
 {
     const struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+    uint64_t odp_actions_stub[1024 / 8];
     struct ofpbuf key, odp_actions;
     struct odputil_keybuf keybuf;
     uint32_t odp_port;
@@ -5360,8 +5469,8 @@ send_packet(const struct ofport_dpif *ofport, struct ofpbuf *packet)
         }
 
         dpif_flow_stats_extract(&flow, packet, time_msec(), &stats);
-        netdev_vport_patch_inc_tx(ofport->up.netdev, &stats);
-        netdev_vport_patch_inc_rx(peer->up.netdev, &stats);
+        netdev_vport_inc_tx(ofport->up.netdev, &stats);
+        netdev_vport_inc_rx(peer->up.netdev, &stats);
 
         flow.in_port = peer->up.ofp_port;
         peer_ofproto = ofproto_dpif_cast(peer->up.ofproto);
@@ -5371,18 +5480,32 @@ send_packet(const struct ofport_dpif *ofport, struct ofpbuf *packet)
         return 0;
     }
 
-    odp_port = vsp_realdev_to_vlandev(ofproto, ofport->odp_port,
-                                      flow.vlan_tci);
-    if (odp_port != ofport->odp_port) {
-        eth_pop_vlan(packet);
-        flow.vlan_tci = htons(0);
+    ofpbuf_use_stub(&odp_actions, odp_actions_stub, sizeof odp_actions_stub);
+
+    if (ofport->tnl_port) {
+        struct dpif_flow_stats stats;
+
+        odp_port = tnl_port_send(ofport->tnl_port, &flow);
+        if (odp_port == OVSP_NONE) {
+            return ENODEV;
+        }
+
+        dpif_flow_stats_extract(&flow, packet, time_msec(), &stats);
+        netdev_vport_inc_tx(ofport->up.netdev, &stats);
+        odp_put_tunnel_action(&flow.tunnel, &odp_actions);
+    } else {
+        odp_port = vsp_realdev_to_vlandev(ofproto, ofport->odp_port,
+                                          flow.vlan_tci);
+        if (odp_port != ofport->odp_port) {
+            eth_pop_vlan(packet);
+            flow.vlan_tci = htons(0);
+        }
     }
 
     ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
     odp_flow_key_from_flow(&key, &flow,
                            ofp_port_to_odp_port(ofproto, flow.in_port));
 
-    ofpbuf_init(&odp_actions, 32);
     compose_sflow_action(ofproto, &odp_actions, &flow, odp_port);
 
     nl_msg_put_u32(&odp_actions, OVS_ACTION_ATTR_OUTPUT, odp_port);
@@ -5558,6 +5681,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
 {
     const struct ofport_dpif *ofport = get_ofp_port(ctx->ofproto, ofp_port);
     ovs_be16 flow_vlan_tci = ctx->flow.vlan_tci;
+    ovs_be64 flow_tun_id = ctx->flow.tunnel.tun_id;
     uint8_t flow_nw_tos = ctx->flow.nw_tos;
     struct priority_to_dscp *pdscp;
     uint32_t out_port, odp_port;
@@ -5603,8 +5727,8 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
         ctx->ofproto = ofproto_dpif_cast(ofport->up.ofproto);
 
         if (ctx->resubmit_stats) {
-            netdev_vport_patch_inc_tx(ofport->up.netdev, ctx->resubmit_stats);
-            netdev_vport_patch_inc_rx(peer->up.netdev, ctx->resubmit_stats);
+            netdev_vport_inc_tx(ofport->up.netdev, ctx->resubmit_stats);
+            netdev_vport_inc_rx(peer->up.netdev, ctx->resubmit_stats);
         }
 
         return;
@@ -5617,10 +5741,25 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
     }
 
     odp_port = ofp_port_to_odp_port(ctx->ofproto, ofp_port);
-    out_port = vsp_realdev_to_vlandev(ctx->ofproto, odp_port,
-                                      ctx->flow.vlan_tci);
-    if (out_port != odp_port) {
-        ctx->flow.vlan_tci = htons(0);
+    if (ofport->tnl_port) {
+        odp_port = tnl_port_send(ofport->tnl_port, &ctx->flow);
+        if (odp_port == OVSP_NONE) {
+            xlate_report(ctx, "Tunneling decided against output");
+            return;
+        }
+
+        if (ctx->resubmit_stats) {
+            netdev_vport_inc_tx(ofport->up.netdev, ctx->resubmit_stats);
+        }
+        out_port = odp_port;
+        commit_odp_tunnel_action(&ctx->flow, &ctx->base_flow,
+                                 ctx->odp_actions);
+    } else {
+        out_port = vsp_realdev_to_vlandev(ctx->ofproto, odp_port,
+                                          ctx->flow.vlan_tci);
+        if (out_port != odp_port) {
+            ctx->flow.vlan_tci = htons(0);
+        }
     }
     commit_odp_actions(&ctx->flow, &ctx->base_flow, ctx->odp_actions);
     nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_OUTPUT, out_port);
@@ -5628,6 +5767,7 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
     ctx->sflow_odp_port = odp_port;
     ctx->sflow_n_outputs++;
     ctx->nf_output_iface = ofp_port;
+    ctx->flow.tunnel.tun_id = flow_tun_id;
     ctx->flow.vlan_tci = flow_vlan_tci;
     ctx->flow.nw_tos = flow_nw_tos;
 }
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 6cde97c..3eec947 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -212,25 +212,6 @@ AT_CHECK([tail -1 stdout], [0],
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
-AT_SETUP([ofproto-dpif - set_tunnel])
-OVS_VSWITCHD_START
-ADD_OF_PORTS([br0], [1], [2], [3], [4], [5], [90])
-AT_DATA([flows.txt], [dnl
-in_port=90 actions=resubmit:1,resubmit:2,resubmit:3,resubmit:4,resubmit:5
-in_port=1 actions=set_tunnel:1,output:1
-in_port=2 actions=set_tunnel:1,output:2
-in_port=3 actions=set_tunnel:2,set_tunnel:3,output:3
-in_port=4 actions=set_tunnel:4,set_tunnel:3,output:4
-in_port=5 actions=set_tunnel:5
-])
-AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
-AT_CHECK([ovs-appctl ofproto/trace br0 'tun_id(0x1),in_port(90),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)'], [0], [stdout])
-AT_CHECK([tail -1 stdout], [0],
-  [Datapath actions: set(tun_id(0x1)),1,2,set(tun_id(0x3)),3,4
-])
-OVS_VSWITCHD_STOP
-AT_CLEANUP
-
 AT_SETUP([ofproto-dpif - controller])
 OVS_VSWITCHD_START([dnl
    add-port br0 p1 -- set Interface p1 type=dummy
-- 
1.7.9.5




More information about the dev mailing list