tunnels: Remove support for df_inherit from userspace.
[sliver-openvswitch.git] / lib / netdev-vport.c
index 70cdf29..b732d06 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2011 Nicira Networks.
+ * Copyright (c) 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.
@@ -21,6 +21,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <sys/socket.h>
+#include <linux/openvswitch.h>
 #include <linux/rtnetlink.h>
 #include <net/if.h>
 #include <sys/ioctl.h>
@@ -38,7 +39,6 @@
 #include "netlink-notifier.h"
 #include "netlink-socket.h"
 #include "ofpbuf.h"
-#include "openvswitch/datapath-protocol.h"
 #include "openvswitch/tunnel.h"
 #include "packets.h"
 #include "route-table.h"
 
 VLOG_DEFINE_THIS_MODULE(netdev_vport);
 
+/* Default to the OTV port, per the VXLAN IETF draft. */
+#define VXLAN_DST_PORT 8472
+
 struct netdev_dev_vport {
     struct netdev_dev netdev_dev;
     struct ofpbuf *options;
-    int dp_ifindex;             /* -1 if unknown. */
-    uint32_t port_no;           /* UINT32_MAX if unknown. */
     unsigned int change_seq;
-};
-
-struct netdev_vport {
-    struct netdev netdev;
+    uint8_t etheraddr[ETH_ADDR_LEN];
 };
 
 struct vport_class {
     enum ovs_vport_type type;
     struct netdev_class netdev_class;
     int (*parse_config)(const char *name, const char *type,
-                        const struct shash *args, struct ofpbuf *options);
+                        const struct smap *args, struct ofpbuf *options);
     int (*unparse_config)(const char *name, const char *type,
                           const struct nlattr *options, size_t options_len,
-                          struct shash *args);
+                          struct smap *args);
 };
 
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
@@ -80,8 +78,6 @@ 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 char *netdev_vport_get_tnl_iface(const struct netdev *netdev);
-
 static bool
 is_vport_class(const struct netdev_class *class)
 {
@@ -102,12 +98,10 @@ netdev_dev_vport_cast(const struct netdev_dev *netdev_dev)
     return CONTAINER_OF(netdev_dev, struct netdev_dev_vport, netdev_dev);
 }
 
-static struct netdev_vport *
-netdev_vport_cast(const struct netdev *netdev)
+static struct netdev_dev_vport *
+netdev_vport_get_dev(const struct netdev *netdev)
 {
-    struct netdev_dev *netdev_dev = netdev_get_dev(netdev);
-    assert(is_vport_class(netdev_dev_get_class(netdev_dev)));
-    return CONTAINER_OF(netdev, struct netdev_vport, netdev);
+    return netdev_dev_vport_cast(netdev_get_dev(netdev));
 }
 
 /* If 'netdev' is a vport netdev, returns an ofpbuf that contains Netlink
@@ -131,10 +125,17 @@ netdev_vport_get_vport_type(const struct netdev *netdev)
 
     return (is_vport_class(class) ? vport_class_cast(class)->type
             : class == &netdev_internal_class ? OVS_VPORT_TYPE_INTERNAL
-            : class == &netdev_linux_class ? OVS_VPORT_TYPE_NETDEV
+            : (class == &netdev_linux_class ||
+               class == &netdev_tap_class) ? OVS_VPORT_TYPE_NETDEV
             : OVS_VPORT_TYPE_UNSPEC);
 }
 
+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)
 {
@@ -158,12 +159,24 @@ netdev_vport_get_netdev_type(const struct dpif_linux_vport *vport)
                                         a)) {
             break;
         }
-        return (nl_attr_get_u32(a[OVS_TUNNEL_ATTR_FLAGS]) & TNL_F_IPSEC
+        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;
     }
@@ -182,9 +195,8 @@ netdev_vport_create(const struct netdev_class *netdev_class, const char *name,
     dev = xmalloc(sizeof *dev);
     netdev_dev_init(&dev->netdev_dev, name, netdev_class);
     dev->options = NULL;
-    dev->dp_ifindex = -1;
-    dev->port_no = UINT32_MAX;
     dev->change_seq = 1;
+    eth_addr_random(dev->etheraddr);
 
     *netdev_devp = &dev->netdev_dev;
     route_table_register();
@@ -197,31 +209,27 @@ 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);
 }
 
 static int
-netdev_vport_open(struct netdev_dev *netdev_dev_, struct netdev **netdevp)
+netdev_vport_open(struct netdev_dev *netdev_dev, struct netdev **netdevp)
 {
-    struct netdev_vport *netdev;
-
-    netdev = xmalloc(sizeof *netdev);
-    netdev_init(&netdev->netdev, netdev_dev_);
-
-    *netdevp = &netdev->netdev;
+    *netdevp = xmalloc(sizeof **netdevp);
+    netdev_init(*netdevp, netdev_dev);
     return 0;
 }
 
 static void
-netdev_vport_close(struct netdev *netdev_)
+netdev_vport_close(struct netdev *netdev)
 {
-    struct netdev_vport *netdev = netdev_vport_cast(netdev_);
     free(netdev);
 }
 
 static int
-netdev_vport_get_config(struct netdev_dev *dev_, struct shash *args)
+netdev_vport_get_config(struct netdev_dev *dev_, struct smap *args)
 {
     const struct netdev_class *netdev_class = netdev_dev_get_class(dev_);
     const struct vport_class *vport_class = vport_class_cast(netdev_class);
@@ -241,8 +249,6 @@ netdev_vport_get_config(struct netdev_dev *dev_, struct shash *args)
         }
 
         dev->options = ofpbuf_clone_data(reply.options, reply.options_len);
-        dev->dp_ifindex = reply.dp_ifindex;
-        dev->port_no = reply.port_no;
         ofpbuf_delete(buf);
     }
 
@@ -258,7 +264,7 @@ netdev_vport_get_config(struct netdev_dev *dev_, struct shash *args)
 }
 
 static int
-netdev_vport_set_config(struct netdev_dev *dev_, const struct shash *args)
+netdev_vport_set_config(struct netdev_dev *dev_, const struct smap *args)
 {
     const struct netdev_class *netdev_class = netdev_dev_get_class(dev_);
     const struct vport_class *vport_class = vport_class_cast(netdev_class);
@@ -297,69 +303,21 @@ netdev_vport_set_config(struct netdev_dev *dev_, const struct shash *args)
     return error;
 }
 
-static int
-netdev_vport_send(struct netdev *netdev, const void *data, size_t size)
-{
-    struct netdev_dev *dev_ = netdev_get_dev(netdev);
-    struct netdev_dev_vport *dev = netdev_dev_vport_cast(dev_);
-
-    if (dev->dp_ifindex == -1) {
-        const char *name = netdev_get_name(netdev);
-        struct dpif_linux_vport reply;
-        struct ofpbuf *buf;
-        int error;
-
-        error = dpif_linux_vport_get(name, &reply, &buf);
-        if (error) {
-            VLOG_ERR_RL(&rl, "%s: failed to query vport for send (%s)",
-                        name, strerror(error));
-            return error;
-        }
-        dev->dp_ifindex = reply.dp_ifindex;
-        dev->port_no = reply.port_no;
-        ofpbuf_delete(buf);
-    }
-
-    return dpif_linux_vport_send(dev->dp_ifindex, dev->port_no, data, size);
-}
-
 static int
 netdev_vport_set_etheraddr(struct netdev *netdev,
                            const uint8_t mac[ETH_ADDR_LEN])
 {
-    struct dpif_linux_vport vport;
-    int error;
-
-    dpif_linux_vport_init(&vport);
-    vport.cmd = OVS_VPORT_CMD_SET;
-    vport.name = netdev_get_name(netdev);
-    vport.address = mac;
-
-    error = dpif_linux_vport_transact(&vport, NULL, NULL);
-    if (!error) {
-        netdev_vport_poll_notify(netdev);
-    }
-    return error;
+    memcpy(netdev_vport_get_dev(netdev)->etheraddr, mac, ETH_ADDR_LEN);
+    netdev_vport_poll_notify(netdev);
+    return 0;
 }
 
 static int
 netdev_vport_get_etheraddr(const struct netdev *netdev,
                            uint8_t mac[ETH_ADDR_LEN])
 {
-    struct dpif_linux_vport reply;
-    struct ofpbuf *buf;
-    int error;
-
-    error = dpif_linux_vport_get(netdev_get_name(netdev), &reply, &buf);
-    if (!error) {
-        if (reply.address) {
-            memcpy(mac, reply.address, ETH_ADDR_LEN);
-        } else {
-            error = EOPNOTSUPP;
-        }
-        ofpbuf_delete(buf);
-    }
-    return error;
+    memcpy(mac, netdev_vport_get_dev(netdev)->etheraddr, ETH_ADDR_LEN);
+    return 0;
 }
 
 /* Copies 'src' into 'dst', performing format conversion in the process.
@@ -392,21 +350,6 @@ netdev_stats_from_ovs_vport_stats(struct netdev_stats *dst,
     dst->tx_window_errors = 0;
 }
 
-/* Copies 'src' into 'dst', performing format conversion in the process. */
-static void
-netdev_stats_to_ovs_vport_stats(struct ovs_vport_stats *dst,
-                                const struct netdev_stats *src)
-{
-    dst->rx_packets = src->rx_packets;
-    dst->tx_packets = src->tx_packets;
-    dst->rx_bytes = src->rx_bytes;
-    dst->tx_bytes = src->tx_bytes;
-    dst->rx_errors = src->rx_errors;
-    dst->tx_errors = src->tx_errors;
-    dst->rx_dropped = src->rx_dropped;
-    dst->tx_dropped = src->tx_dropped;
-}
-
 int
 netdev_vport_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
 {
@@ -429,47 +372,34 @@ netdev_vport_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
     return 0;
 }
 
-int
-netdev_vport_set_stats(struct netdev *netdev, const struct netdev_stats *stats)
+static int
+tunnel_get_status(const struct netdev *netdev, struct smap *smap)
 {
-    struct ovs_vport_stats rtnl_stats;
-    struct dpif_linux_vport vport;
-    int err;
-
-    netdev_stats_to_ovs_vport_stats(&rtnl_stats, stats);
-
-    dpif_linux_vport_init(&vport);
-    vport.cmd = OVS_VPORT_CMD_SET;
-    vport.name = netdev_get_name(netdev);
-    vport.stats = &rtnl_stats;
-
-    err = dpif_linux_vport_transact(&vport, NULL, NULL);
+    struct netdev_dev_vport *ndv = netdev_vport_get_dev(netdev);
+    struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1];
+    static char iface[IFNAMSIZ];
+    ovs_be32 route;
 
-    /* If the vport layer doesn't know about the device, that doesn't mean it
-     * doesn't exist (after all were able to open it when netdev_open() was
-     * called), it just means that it isn't attached and we'll be getting
-     * stats a different way. */
-    if (err == ENODEV) {
-        err = EOPNOTSUPP;
+    if (!ndv->options) {
+        /* Race condition when 'ndv' was created, but did not have it's
+         * configuration set yet. */
+        return 0;
     }
 
-    return err;
-}
-
-static int
-netdev_vport_get_status(const struct netdev *netdev, struct shash *sh)
-{
-    const char *iface = netdev_vport_get_tnl_iface(netdev);
+    if (tnl_port_config_from_nlattr(ndv->options->data,
+                                    ndv->options->size, a)) {
+        return 0;
+    }
+    route = nl_attr_get_be32(a[OVS_TUNNEL_ATTR_DST_IPV4]);
 
-    if (iface) {
+    if (route_table_get_name(route, iface)) {
         struct netdev *egress_netdev;
 
-        shash_add(sh, "tunnel_egress_iface", xstrdup(iface));
+        smap_add(smap, "tunnel_egress_iface", iface);
 
         if (!netdev_open(iface, "system", &egress_netdev)) {
-            shash_add(sh, "tunnel_egress_iface_carrier",
-                      xstrdup(netdev_get_carrier(egress_netdev)
-                              ? "up" : "down"));
+            smap_add(smap, "tunnel_egress_iface_carrier",
+                     netdev_get_carrier(egress_netdev) ? "up" : "down");
             netdev_close(egress_netdev);
         }
     }
@@ -493,7 +423,7 @@ netdev_vport_update_flags(struct netdev *netdev OVS_UNUSED,
 static unsigned int
 netdev_vport_change_seq(const struct netdev *netdev)
 {
-    return netdev_dev_vport_cast(netdev_get_dev(netdev))->change_seq;
+    return netdev_vport_get_dev(netdev)->change_seq;
 }
 
 static void
@@ -508,37 +438,12 @@ netdev_vport_wait(void)
     route_table_wait();
 }
 \f
-/* get_tnl_iface() implementation. */
-static const char *
-netdev_vport_get_tnl_iface(const struct netdev *netdev)
-{
-    struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1];
-    ovs_be32 route;
-    struct netdev_dev_vport *ndv;
-    static char name[IFNAMSIZ];
-
-    ndv = netdev_dev_vport_cast(netdev_get_dev(netdev));
-    if (tnl_port_config_from_nlattr(ndv->options->data, ndv->options->size,
-                                    a)) {
-        return NULL;
-    }
-    route = nl_attr_get_be32(a[OVS_TUNNEL_ATTR_DST_IPV4]);
-
-    if (route_table_get_name(route, name)) {
-        return name;
-    }
-
-    return NULL;
-}
-\f
 /* Helper functions. */
 
 static void
 netdev_vport_poll_notify(const struct netdev *netdev)
 {
-    struct netdev_dev_vport *ndv;
-
-    ndv = netdev_dev_vport_cast(netdev_get_dev(netdev));
+    struct netdev_dev_vport *ndv = netdev_vport_get_dev(netdev);
 
     ndv->change_seq++;
     if (!ndv->change_seq) {
@@ -549,14 +454,14 @@ netdev_vport_poll_notify(const struct netdev *netdev)
 /* Code specific to individual vport types. */
 
 static void
-set_key(const struct shash *args, const char *name, uint16_t type,
+set_key(const struct smap *args, const char *name, uint16_t type,
         struct ofpbuf *options)
 {
     const char *s;
 
-    s = shash_find_data(args, name);
+    s = smap_get(args, name);
     if (!s) {
-        s = shash_find_data(args, "key");
+        s = smap_get(args, "key");
         if (!s) {
             s = "0";
         }
@@ -571,75 +476,81 @@ set_key(const struct shash *args, const char *name, uint16_t type,
 
 static int
 parse_tunnel_config(const char *name, const char *type,
-                    const struct shash *args, struct ofpbuf *options)
+                    const struct smap *args, struct ofpbuf *options)
 {
     bool is_gre = false;
     bool is_ipsec = false;
-    struct shash_node *node;
+    bool needs_dst_port = false;
+    bool found_dst_port = false;
+    struct smap_node *node;
     bool ipsec_mech_set = false;
     ovs_be32 daddr = htonl(0);
+    ovs_be32 saddr = htonl(0);
     uint32_t flags;
 
-    flags = TNL_F_DF_DEFAULT | TNL_F_PMTUD | TNL_F_HDR_CACHE;
-    if (!strcmp(type, "gre")) {
+    if (!strcmp(type, "capwap")) {
+        VLOG_WARN_ONCE("CAPWAP tunnel support is deprecated.");
+    }
+
+    flags = TNL_F_DF_DEFAULT;
+    if (!strcmp(type, "gre") || !strcmp(type, "gre64")) {
         is_gre = true;
-    } else if (!strcmp(type, "ipsec_gre")) {
+    } else if (!strcmp(type, "ipsec_gre") || !strcmp(type, "ipsec_gre64")) {
         is_gre = true;
         is_ipsec = true;
         flags |= TNL_F_IPSEC;
-        flags &= ~TNL_F_HDR_CACHE;
+    } else if (!strcmp(type, "vxlan")) {
+        needs_dst_port = true;
     }
 
-    SHASH_FOR_EACH (node, args) {
-        if (!strcmp(node->name, "remote_ip")) {
+    SMAP_FOR_EACH (node, args) {
+        if (!strcmp(node->key, "remote_ip")) {
             struct in_addr in_addr;
-            if (lookup_ip(node->data, &in_addr)) {
+            if (lookup_ip(node->value, &in_addr)) {
                 VLOG_WARN("%s: bad %s 'remote_ip'", name, type);
             } else {
                 daddr = in_addr.s_addr;
             }
-        } else if (!strcmp(node->name, "local_ip")) {
+        } else if (!strcmp(node->key, "local_ip")) {
             struct in_addr in_addr;
-            if (lookup_ip(node->data, &in_addr)) {
+            if (lookup_ip(node->value, &in_addr)) {
                 VLOG_WARN("%s: bad %s 'local_ip'", name, type);
             } else {
-                nl_msg_put_be32(options, OVS_TUNNEL_ATTR_SRC_IPV4,
-                                in_addr.s_addr);
+                saddr = in_addr.s_addr;
             }
-        } else if (!strcmp(node->name, "tos")) {
-            if (!strcmp(node->data, "inherit")) {
+        } else if (!strcmp(node->key, "tos")) {
+            if (!strcmp(node->value, "inherit")) {
                 flags |= TNL_F_TOS_INHERIT;
             } else {
-                nl_msg_put_u8(options, OVS_TUNNEL_ATTR_TOS, atoi(node->data));
+                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);
+                } else {
+                    VLOG_WARN("%s: invalid TOS %s", name, node->value);
+                }
             }
-        } else if (!strcmp(node->name, "ttl")) {
-            if (!strcmp(node->data, "inherit")) {
+        } else if (!strcmp(node->key, "ttl")) {
+            if (!strcmp(node->value, "inherit")) {
                 flags |= TNL_F_TTL_INHERIT;
             } else {
-                nl_msg_put_u8(options, OVS_TUNNEL_ATTR_TTL, atoi(node->data));
+                nl_msg_put_u8(options, OVS_TUNNEL_ATTR_TTL, atoi(node->value));
             }
-        } else if (!strcmp(node->name, "csum") && is_gre) {
-            if (!strcmp(node->data, "true")) {
+        } else if (!strcmp(node->key, "dst_port") && needs_dst_port) {
+            nl_msg_put_u16(options, OVS_TUNNEL_ATTR_DST_PORT,
+                           atoi(node->value));
+            found_dst_port = true;
+        } else if (!strcmp(node->key, "csum") && is_gre) {
+            if (!strcmp(node->value, "true")) {
                 flags |= TNL_F_CSUM;
             }
-        } else if (!strcmp(node->name, "df_inherit")) {
-            if (!strcmp(node->data, "true")) {
-                flags |= TNL_F_DF_INHERIT;
-            }
-        } else if (!strcmp(node->name, "df_default")) {
-            if (!strcmp(node->data, "false")) {
+        } else if (!strcmp(node->key, "df_default")) {
+            if (!strcmp(node->value, "false")) {
                 flags &= ~TNL_F_DF_DEFAULT;
             }
-        } else if (!strcmp(node->name, "pmtud")) {
-            if (!strcmp(node->data, "false")) {
-                flags &= ~TNL_F_PMTUD;
-            }
-        } else if (!strcmp(node->name, "header_cache")) {
-            if (!strcmp(node->data, "false")) {
-                flags &= ~TNL_F_HDR_CACHE;
-            }
-        } else if (!strcmp(node->name, "peer_cert") && is_ipsec) {
-            if (shash_find(args, "certificate")) {
+        } else if (!strcmp(node->key, "peer_cert") && is_ipsec) {
+            if (smap_get(args, "certificate")) {
                 ipsec_mech_set = true;
             } else {
                 const char *use_ssl_cert;
@@ -650,7 +561,7 @@ parse_tunnel_config(const char *name, const char *type,
                  * will like be removed when multiple SSL configurations
                  * are supported by OVS.
                  */
-                use_ssl_cert = shash_find_data(args, "use_ssl_cert");
+                use_ssl_cert = smap_get(args, "use_ssl_cert");
                 if (!use_ssl_cert || strcmp(use_ssl_cert, "true")) {
                     VLOG_ERR("%s: 'peer_cert' requires 'certificate' argument",
                              name);
@@ -658,34 +569,43 @@ parse_tunnel_config(const char *name, const char *type,
                 }
                 ipsec_mech_set = true;
             }
-        } else if (!strcmp(node->name, "psk") && is_ipsec) {
+        } else if (!strcmp(node->key, "psk") && is_ipsec) {
             ipsec_mech_set = true;
         } else if (is_ipsec
-                && (!strcmp(node->name, "certificate")
-                    || !strcmp(node->name, "private_key")
-                    || !strcmp(node->name, "use_ssl_cert"))) {
+                && (!strcmp(node->key, "certificate")
+                    || !strcmp(node->key, "private_key")
+                    || !strcmp(node->key, "use_ssl_cert"))) {
             /* Ignore options not used by the netdev. */
-        } else if (!strcmp(node->name, "key") ||
-                   !strcmp(node->name, "in_key") ||
-                   !strcmp(node->name, "out_key")) {
+        } else if (!strcmp(node->key, "key") ||
+                   !strcmp(node->key, "in_key") ||
+                   !strcmp(node->key, "out_key")) {
             /* Handled separately below. */
         } else {
-            VLOG_WARN("%s: unknown %s argument '%s'", name, type, node->name);
+            VLOG_WARN("%s: unknown %s argument '%s'", name, type, node->key);
         }
     }
 
+    /* Add a default destination port for VXLAN if none specified. */
+    if (needs_dst_port && !found_dst_port) {
+        nl_msg_put_u16(options, OVS_TUNNEL_ATTR_DST_PORT, VXLAN_DST_PORT);
+    }
+
     if (is_ipsec) {
-        char *file_name = xasprintf("%s/%s", ovs_rundir(),
-                "ovs-monitor-ipsec.pid");
-        pid_t pid = read_pidfile(file_name);
-        free(file_name);
+        static pid_t pid = 0;
+        if (pid <= 0) {
+            char *file_name = xasprintf("%s/%s", ovs_rundir(),
+                                        "ovs-monitor-ipsec.pid");
+            pid = read_pidfile(file_name);
+            free(file_name);
+        }
+
         if (pid < 0) {
             VLOG_ERR("%s: IPsec requires the ovs-monitor-ipsec daemon",
                      name);
             return EINVAL;
         }
 
-        if (shash_find(args, "peer_cert") && shash_find(args, "psk")) {
+        if (smap_get(args, "peer_cert") && smap_get(args, "psk")) {
             VLOG_ERR("%s: cannot define both 'peer_cert' and 'psk'", name);
             return EINVAL;
         }
@@ -707,6 +627,14 @@ parse_tunnel_config(const char *name, const char *type,
     }
     nl_msg_put_be32(options, OVS_TUNNEL_ATTR_DST_IPV4, daddr);
 
+    if (saddr) {
+        if (ip_is_multicast(daddr)) {
+            VLOG_WARN("%s: remote_ip is multicast, ignoring local_ip", name);
+        } else {
+            nl_msg_put_be32(options, OVS_TUNNEL_ATTR_SRC_IPV4, saddr);
+        }
+    }
+
     nl_msg_put_u32(options, OVS_TUNNEL_ATTR_FLAGS, flags);
 
     return 0;
@@ -717,13 +645,14 @@ 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 },
-        [OVS_TUNNEL_ATTR_DST_IPV4] = { .type = NL_A_BE32 },
+        [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;
 
@@ -744,10 +673,9 @@ get_be64_or_zero(const struct nlattr *a)
 static int
 unparse_tunnel_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED,
                       const struct nlattr *options, size_t options_len,
-                      struct shash *args)
+                      struct smap *args)
 {
     struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1];
-    ovs_be32 daddr;
     uint32_t flags;
     int error;
 
@@ -756,18 +684,14 @@ unparse_tunnel_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED,
         return error;
     }
 
-    flags = nl_attr_get_u32(a[OVS_TUNNEL_ATTR_FLAGS]);
-    if (!(flags & TNL_F_HDR_CACHE) == !(flags & TNL_F_IPSEC)) {
-        smap_add(args, "header_cache",
-                 flags & TNL_F_HDR_CACHE ? "true" : "false");
+    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));
     }
 
-    daddr = nl_attr_get_be32(a[OVS_TUNNEL_ATTR_DST_IPV4]);
-    shash_add(args, "remote_ip", xasprintf(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]);
-        shash_add(args, "local_ip", xasprintf(IP_FMT, IP_ARGS(&saddr)));
+        smap_add_format(args, "local_ip", IP_FMT, IP_ARGS(saddr));
     }
 
     if (!a[OVS_TUNNEL_ATTR_IN_KEY] && !a[OVS_TUNNEL_ATTR_OUT_KEY]) {
@@ -777,65 +701,73 @@ unparse_tunnel_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED,
         uint64_t out_key = get_be64_or_zero(a[OVS_TUNNEL_ATTR_OUT_KEY]);
 
         if (in_key && in_key == out_key) {
-            shash_add(args, "key", xasprintf("%"PRIu64, in_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) {
-                shash_add(args, "in_key", xasprintf("%"PRIu64, in_key));
+                smap_add_format(args, "in_key", "%"PRIu64, in_key);
             }
 
             if (!a[OVS_TUNNEL_ATTR_OUT_KEY]) {
                 smap_add(args, "out_key", "flow");
             } else if (out_key) {
-                shash_add(args, "out_key", xasprintf("%"PRIu64, out_key));
+                smap_add_format(args, "out_key", "%"PRIu64, out_key);
             }
         }
     }
 
+    flags = get_u32_or_zero(a[OVS_TUNNEL_ATTR_FLAGS]);
+
     if (flags & TNL_F_TTL_INHERIT) {
-        smap_add(args, "tos", "inherit");
+        smap_add(args, "ttl", "inherit");
     } else if (a[OVS_TUNNEL_ATTR_TTL]) {
         int ttl = nl_attr_get_u8(a[OVS_TUNNEL_ATTR_TTL]);
-        shash_add(args, "tos", xasprintf("%d", ttl));
+        smap_add_format(args, "ttl", "%d", ttl);
     }
 
     if (flags & TNL_F_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]);
-        shash_add(args, "tos", xasprintf("%d", tos));
+        smap_add_format(args, "tos", "0x%x", tos);
+    }
+
+    if (a[OVS_TUNNEL_ATTR_DST_PORT]) {
+        uint16_t dst_port = nl_attr_get_u16(a[OVS_TUNNEL_ATTR_DST_PORT]);
+        if (dst_port != VXLAN_DST_PORT) {
+            smap_add_format(args, "dst_port", "%d", dst_port);
+        }
     }
 
     if (flags & TNL_F_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)) {
         smap_add(args, "df_default", "false");
     }
-    if (!(flags & TNL_F_PMTUD)) {
-        smap_add(args, "pmtud", "false");
-    }
 
     return 0;
 }
 
 static int
 parse_patch_config(const char *name, const char *type OVS_UNUSED,
-                   const struct shash *args, struct ofpbuf *options)
+                   const struct smap *args, struct ofpbuf *options)
 {
     const char *peer;
 
-    peer = shash_find_data(args, "peer");
+    peer = smap_get(args, "peer");
     if (!peer) {
         VLOG_ERR("%s: patch type requires valid 'peer' argument", name);
         return EINVAL;
     }
 
-    if (shash_count(args) > 1) {
+    if (smap_count(args) > 1) {
         VLOG_ERR("%s: patch type takes only a 'peer' argument", name);
         return EINVAL;
     }
@@ -858,7 +790,7 @@ parse_patch_config(const char *name, const char *type OVS_UNUSED,
 static int
 unparse_patch_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED,
                      const struct nlattr *options, size_t options_len,
-                     struct shash *args)
+                     struct smap *args)
 {
     static const struct nl_policy ovs_patch_policy[] = {
         [OVS_PATCH_ATTR_PEER] = { .type = NL_A_STRING,
@@ -897,7 +829,7 @@ unparse_patch_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED,
     NULL,                       /* recv_wait */             \
     NULL,                       /* drain */                 \
                                                             \
-    netdev_vport_send,          /* send */                  \
+    NULL,                       /* send */                  \
     NULL,                       /* send_wait */             \
                                                             \
     netdev_vport_set_etheraddr,                             \
@@ -906,13 +838,13 @@ unparse_patch_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED,
     NULL,                       /* set_mtu */               \
     NULL,                       /* get_ifindex */           \
     NULL,                       /* get_carrier */           \
+    NULL,                       /* get_carrier_resets */    \
     NULL,                       /* get_miimon */            \
     netdev_vport_get_stats,                                 \
-    netdev_vport_set_stats,                                 \
+    NULL,                       /* set_stats */             \
                                                             \
     NULL,                       /* get_features */          \
     NULL,                       /* set_advertisements */    \
-    NULL,                       /* get_vlan_vid */          \
                                                             \
     NULL,                       /* set_policing */          \
     NULL,                       /* get_qos_types */         \
@@ -938,21 +870,21 @@ unparse_patch_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED,
                                                             \
     netdev_vport_change_seq
 
+#define TUNNEL_CLASS(NAME, VPORT_TYPE)                      \
+    { VPORT_TYPE,                                           \
+        { NAME, VPORT_FUNCTIONS(tunnel_get_status) },       \
+            parse_tunnel_config, unparse_tunnel_config }
+
 void
 netdev_vport_register(void)
 {
     static const struct vport_class vport_classes[] = {
-        { OVS_VPORT_TYPE_GRE,
-          { "gre", VPORT_FUNCTIONS(netdev_vport_get_status) },
-          parse_tunnel_config, unparse_tunnel_config },
-
-        { OVS_VPORT_TYPE_GRE,
-          { "ipsec_gre", VPORT_FUNCTIONS(netdev_vport_get_status) },
-          parse_tunnel_config, unparse_tunnel_config },
-
-        { OVS_VPORT_TYPE_CAPWAP,
-          { "capwap", VPORT_FUNCTIONS(netdev_vport_get_status) },
-          parse_tunnel_config, unparse_tunnel_config },
+        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,
           { "patch", VPORT_FUNCTIONS(NULL) },