This is actually intended for use by the VXLAN tunnel implementation, which
should use a source port range, but it is fairly generic infrastructure so
this implements it independent of VXLAN and applies it to CAPWAP.

Not yet tested.
---
 datapath/tunnel.c            |  184 ++++++++++++++++++++++++++++++++++++++----
 datapath/tunnel.h            |   27 ++++++-
 datapath/vport-capwap.c      |   57 ++------------
 include/openvswitch/tunnel.h |    7 ++-
 lib/netdev-vport.c           |  117 ++++++++++++++++++++++++---
 vswitchd/vswitch.xml         |   47 ++++++++++-
 6 files changed, 355 insertions(+), 84 deletions(-)

diff --git a/datapath/tunnel.c b/datapath/tunnel.c
index 372d90e..047961f 100644
--- a/datapath/tunnel.c
+++ b/datapath/tunnel.c
@@ -71,8 +71,24 @@
 #define CACHE_CLEANER_INTERVAL (5 * HZ)
 
 #define CACHE_DATA_ALIGN 16
-#define PORT_TABLE_SIZE  1024
 
+/* Sockets for UDP-based tunnels.
+ *
+ * It's uncommon to have more than one socket per tunnel protocol, and the
+ * table is not traversed on any fast path, so we just use a linked list.
+ *
+ * Only accessed under RTNL.
+ */
+struct tnl_sock {
+       struct list_head list;
+       struct socket *socket;
+       __be16 port;
+       unsigned int refcnt;
+};
+static LIST_HEAD(tnl_socks);
+
+/* Table of struct tnl_vport. */
+#define PORT_TABLE_SIZE  1024
 static struct hlist_head *port_table __read_mostly;
 static int port_table_count;
 
@@ -155,6 +171,8 @@ static void free_cache_rcu(struct rcu_head *rcu)
  */
 static void free_mutable_rtnl(struct tnl_mutable_config *mutable)
 {
+       struct tnl_sock *ts;
+
        ASSERT_RTNL();
        if (ipv4_is_multicast(mutable->key.daddr) && mutable->mlink) {
                struct in_device *in_dev;
@@ -162,6 +180,13 @@ static void free_mutable_rtnl(struct tnl_mutable_config 
*mutable)
                if (in_dev)
                        ip_mc_dec_group(in_dev, mutable->key.daddr);
        }
+
+       ts = mutable->tnl_sock;
+       if (ts && !--ts->refcnt) {
+               sock_release(ts->socket);
+               list_del(&ts->list);
+               kfree(ts);
+       }
 }
 
 static void assign_config_rcu(struct vport *vport,
@@ -1327,10 +1352,76 @@ out:
        return sent_len;
 }
 
+static int tnl_sock_bind(const struct tnl_ops *tnl_ops, struct 
tnl_mutable_config *mutable)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+       struct sockaddr_in sin;
+       struct tnl_sock *ts;
+       int err;
+
+       if (tnl_ops->ipproto != IPPROTO_UDP)
+               return 0;
+
+       list_for_each_entry (ts, &tnl_socks, list) {
+               if (ts->port == mutable->dst_port) {
+                       if (udp_sk(ts->socket->sk)->encap_rcv != 
tnl_ops->udp_rcv)
+                               return -EADDRINUSE;
+                       ts->refcnt++;
+                       return 0;
+               }
+       }
+
+       ts = kmalloc(sizeof(struct tnl_sock), GFP_KERNEL);
+       if (!ts) {
+               err = -ENOMEM;
+               goto error;
+       }
+       ts->port = mutable->dst_port;
+       ts->refcnt = 1;
+
+       err = sock_create(AF_INET, SOCK_DGRAM, 0, &ts->socket);
+       if (err)
+               goto error_kfree_ts;
+
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_ANY);
+       sin.sin_port = mutable->dst_port;
+
+       err = kernel_bind(ts->socket, (struct sockaddr *)&sin,
+                         sizeof(struct sockaddr_in));
+       if (err)
+               goto error_release_sock;
+
+       /* The particular encap_type value set here is unimportant, as long as
+        * it's not 0, since we set the handler.
+        */
+       udp_sk(ts->socket->sk)->encap_type = !0;
+       udp_sk(ts->socket->sk)->encap_rcv = tnl_ops->udp_rcv;
+
+       list_add(&ts->list, &tnl_socks);
+
+       return 0;
+
+error_release_sock:
+       sock_release(ts->socket);
+error_kfree_ts:
+       kfree(ts);
+error:
+       return err;
+#else
+       WARN_ON(tnl_ops->ipproto == IPPROTO_UDP);
+
+       return 0;
+#endif
+}
+
 static const struct nla_policy tnl_policy[OVS_TUNNEL_ATTR_MAX + 1] = {
        [OVS_TUNNEL_ATTR_FLAGS]    = { .type = NLA_U32 },
        [OVS_TUNNEL_ATTR_DST_IPV4] = { .type = NLA_U32 },
        [OVS_TUNNEL_ATTR_SRC_IPV4] = { .type = NLA_U32 },
+       [OVS_TUNNEL_ATTR_DST_PORT] = { .type = NLA_U16 },
+       [OVS_TUNNEL_ATTR_SRC_PORT_BASE] = { .type = NLA_U16 },
+       [OVS_TUNNEL_ATTR_SRC_PORT_HASH] = { .type = NLA_U8 },
        [OVS_TUNNEL_ATTR_OUT_KEY]  = { .type = NLA_U64 },
        [OVS_TUNNEL_ATTR_IN_KEY]   = { .type = NLA_U64 },
        [OVS_TUNNEL_ATTR_TOS]      = { .type = NLA_U8 },
@@ -1347,29 +1438,57 @@ static int tnl_set_config(struct nlattr *options, const 
struct tnl_ops *tnl_ops,
        struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1];
        int err;
 
+       err = -EINVAL;
        if (!options)
-               return -EINVAL;
+               goto error;
 
        err = nla_parse_nested(a, OVS_TUNNEL_ATTR_MAX, options, tnl_policy);
        if (err)
-               return err;
+               goto error;
 
-       if (!a[OVS_TUNNEL_ATTR_FLAGS] || !a[OVS_TUNNEL_ATTR_DST_IPV4])
-               return -EINVAL;
+       if (!a[OVS_TUNNEL_ATTR_FLAGS] || !a[OVS_TUNNEL_ATTR_DST_IPV4]) {
+               err = -EINVAL;
+               goto error;
+       }
 
        mutable->flags = nla_get_u32(a[OVS_TUNNEL_ATTR_FLAGS]) & TNL_F_PUBLIC;
 
        mutable->key.daddr = nla_get_be32(a[OVS_TUNNEL_ATTR_DST_IPV4]);
        if (a[OVS_TUNNEL_ATTR_SRC_IPV4]) {
-               if (ipv4_is_multicast(mutable->key.daddr))
-                       return -EINVAL;
+               if (ipv4_is_multicast(mutable->key.daddr)) {
+                       err = -EINVAL;
+                       goto error;
+               }
                mutable->key.saddr = nla_get_be32(a[OVS_TUNNEL_ATTR_SRC_IPV4]);
        }
 
+       if (tnl_ops->ipproto == IPPROTO_UDP) {
+               struct nlattr *src_base = a[OVS_TUNNEL_ATTR_SRC_PORT_BASE];
+               struct nlattr *src_hash = a[OVS_TUNNEL_ATTR_SRC_PORT_HASH];
+               struct nlattr *dst = a[OVS_TUNNEL_ATTR_DST_PORT];
+
+               if (!src_base || !dst) {
+                       err = -EINVAL;
+                       goto error;
+               }
+               mutable->src_port_base = ntohs(nla_get_be16(src_base));
+               mutable->dst_port = nla_get_be16(dst);
+
+               if (src_hash) {
+                       mutable->src_port_hash = nla_get_u8(src_hash);
+                       if (mutable->src_port_hash > 16) {
+                               err = -EINVAL;
+                               goto error;
+                       }
+               }
+       }
+
        if (a[OVS_TUNNEL_ATTR_TOS]) {
                mutable->tos = nla_get_u8(a[OVS_TUNNEL_ATTR_TOS]);
-               if (mutable->tos != RT_TOS(mutable->tos))
-                       return -EINVAL;
+               if (mutable->tos != RT_TOS(mutable->tos)) {
+                       err = -EINVAL;
+                       goto error;
+               }
        }
 
        if (a[OVS_TUNNEL_ATTR_TTL])
@@ -1396,8 +1515,10 @@ static int tnl_set_config(struct nlattr *options, const 
struct tnl_ops *tnl_ops,
        mutable->tunnel_hlen += sizeof(struct iphdr);
 
        old_vport = port_table_lookup(&mutable->key, &old_mutable);
-       if (old_vport && old_vport != cur_vport)
-               return -EEXIST;
+       if (old_vport && old_vport != cur_vport) {
+               err = -EEXIST;
+               goto error;
+       }
 
        mutable->mlink = 0;
        if (ipv4_is_multicast(mutable->key.daddr)) {
@@ -1405,17 +1526,30 @@ static int tnl_set_config(struct nlattr *options, const 
struct tnl_ops *tnl_ops,
                struct rtable *rt;
 
                rt = __find_route(mutable, tnl_ops->ipproto, mutable->tos);
-               if (IS_ERR(rt))
-                       return -EADDRNOTAVAIL;
+               if (IS_ERR(rt)) {
+                       err = -EADDRNOTAVAIL;
+                       goto error;
+               }
                dev = rt_dst(rt).dev;
                ip_rt_put(rt);
-               if (__in_dev_get_rtnl(dev) == NULL)
-                       return -EADDRNOTAVAIL;
+               if (__in_dev_get_rtnl(dev) == NULL) {
+                       err = -EADDRNOTAVAIL;
+                       goto error;
+               }
                mutable->mlink = dev->ifindex;
                ip_mc_inc_group(__in_dev_get_rtnl(dev), mutable->key.daddr);
        }
 
+       err = tnl_sock_bind(tnl_ops, mutable);
+       if (err)
+               goto err_dec_mc_group;
+
        return 0;
+
+err_dec_mc_group:
+       free_mutable_rtnl(mutable);
+error:
+       return err;
 }
 
 struct vport *tnl_create(const struct vport_parms *parms,
@@ -1531,6 +1665,13 @@ int tnl_get_options(const struct vport *vport, struct 
sk_buff *skb)
        if (mutable->ttl)
                NLA_PUT_U8(skb, OVS_TUNNEL_ATTR_TTL, mutable->ttl);
 
+       if (tnl_vport->tnl_ops->ipproto == IPPROTO_UDP) {
+               NLA_PUT_BE16(skb, OVS_TUNNEL_ATTR_SRC_PORT_BASE, 
htons(mutable->src_port_base));
+               if (mutable->src_port_hash)
+                       NLA_PUT_U8(skb, OVS_TUNNEL_ATTR_SRC_PORT_HASH, 
mutable->src_port_hash);
+               NLA_PUT_BE16(skb, OVS_TUNNEL_ATTR_DST_PORT, mutable->dst_port);
+       }
+
        return 0;
 
 nla_put_failure:
@@ -1568,7 +1709,9 @@ int tnl_set_addr(struct vport *vport, const unsigned char 
*addr)
        if (!mutable)
                return -ENOMEM;
 
+       /* Transfer state ownership from old_mutable to mutable. */
        old_mutable->mlink = 0;
+       old_mutable->tnl_sock = NULL;
 
        memcpy(mutable->eth_addr, addr, ETH_ALEN);
        assign_config_rcu(vport, mutable);
@@ -1597,6 +1740,16 @@ void tnl_free_linked_skbs(struct sk_buff *skb)
        }
 }
 
+__be16 tnl_get_src_port(const struct sk_buff *skb, const struct 
tnl_mutable_config *mutable)
+{
+       u16 port = mutable->src_port_base;
+       if (mutable->src_port_hash) {
+               u16 mask = (1 << mutable->src_port_hash) - 1;
+               port += OVS_CB(skb)->flow->hash & mask;
+       }
+       return htons(port);
+}
+
 int tnl_init(void)
 {
        int i;
@@ -1616,6 +1769,7 @@ void tnl_exit(void)
 {
        int i;
 
+       BUG_ON(!list_empty(&tnl_socks));
        for (i = 0; i < PORT_TABLE_SIZE; i++) {
                struct tnl_vport * tnl_vport;
                struct hlist_head *hash_head;
diff --git a/datapath/tunnel.h b/datapath/tunnel.h
index f80df99..dfcd3a8 100644
--- a/datapath/tunnel.h
+++ b/datapath/tunnel.h
@@ -69,10 +69,20 @@ struct port_lookup_key {
  * @tunnel_hlen: Tunnel header length.
  * @eth_addr: Source address for packets generated by tunnel itself
  * (e.g. ICMP fragmentation needed messages).
+ * @src_port_base: Base source port.
+ * @src_port_hash: Number of bits of flow hash to add to @src_port_base.
+ * @dst_port: Destination port.
  * @out_key: Key to use on output, 0 if this tunnel has no fixed output key.
  * @flags: TNL_F_* flags.
  * @tos: IPv4 TOS value to use for tunnel, 0 if no fixed TOS.
  * @ttl: IPv4 TTL value to use for tunnel, 0 if no fixed TTL.
+ * @mlink: ifindex of network device on which a multicast address for this
+ * tunnel is registered, 0 if none.
+ * @tnl_sock: &struct tnl_sock in which this tunnel owns a refcount, or %NULL.
+ *
+ * The @src_port_base, @src_port_hash, and @dst_port members are relevant only
+ * to tunnels with L4 port numbers.  Currently that's just tunnels with
+ * @ipproto == %IPPROTO_UDP.
  */
 struct tnl_mutable_config {
        struct port_lookup_key key;
@@ -85,13 +95,17 @@ struct tnl_mutable_config {
        unsigned char eth_addr[ETH_ALEN];
 
        /* Configured via OVS_TUNNEL_ATTR_* attributes. */
+       u16     src_port_base;
+       u8      src_port_hash;
+       __be16  dst_port;
        __be64  out_key;
        u32     flags;
        u8      tos;
        u8      ttl;
 
-       /* Multicast configuration. */
+       /* State that needs to be freed. */
        int     mlink;
+       struct tnl_sock *tnl_sock;
 };
 
 struct tnl_ops {
@@ -127,6 +141,15 @@ struct tnl_ops {
        struct sk_buff *(*update_header)(const struct vport *,
                                         const struct tnl_mutable_config *,
                                         struct dst_entry *, struct sk_buff *);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+       /*
+        * Handler for received packets, used as encap_rcv in struct udp_sock.
+        *
+        * Required if ipproto is IPPROTO_UDP, not used otherwise.
+        */
+       int (*udp_rcv)(struct sock *sk, struct sk_buff *skb);
+#endif
 };
 
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
@@ -250,6 +273,8 @@ bool tnl_frag_needed(struct vport *vport,
                     struct sk_buff *skb, unsigned int mtu, __be64 flow_key);
 void tnl_free_linked_skbs(struct sk_buff *skb);
 
+__be16 tnl_get_src_port(const struct sk_buff *, const struct 
tnl_mutable_config *);
+
 int tnl_init(void);
 void tnl_exit(void);
 static inline struct tnl_vport *tnl_vport_priv(const struct vport *vport)
diff --git a/datapath/vport-capwap.c b/datapath/vport-capwap.c
index 8d78b6d..74b8bbd 100644
--- a/datapath/vport-capwap.c
+++ b/datapath/vport-capwap.c
@@ -27,9 +27,6 @@
 #include "vport.h"
 #include "vport-generic.h"
 
-#define CAPWAP_SRC_PORT 58881
-#define CAPWAP_DST_PORT 58882
-
 #define CAPWAP_FRAG_TIMEOUT (30 * HZ)
 #define CAPWAP_FRAG_MAX_MEM (256 * 1024)
 #define CAPWAP_FRAG_PRUNE_MEM (192 *1024)
@@ -137,8 +134,6 @@ struct frag_skb_cb {
 
 static struct sk_buff *fragment(struct sk_buff *, const struct vport *,
                                struct dst_entry *dst, unsigned int hlen);
-static void defrag_init(void);
-static void defrag_exit(void);
 static struct sk_buff *defrag(struct sk_buff *, bool frag_last);
 
 static void capwap_frag_init(struct inet_frag_queue *, void *match);
@@ -160,8 +155,6 @@ static struct netns_frags frag_netns_state = {
        .low_thresh     = CAPWAP_FRAG_PRUNE_MEM,
 };
 
-static struct socket *capwap_rcv_socket;
-
 static int capwap_hdr_len(const struct tnl_mutable_config *mutable)
 {
        int size = CAPWAP_MIN_HLEN;
@@ -186,8 +179,7 @@ static void capwap_build_header(const struct vport *vport,
        struct udphdr *udph = header;
        struct capwaphdr *cwh = (struct capwaphdr *)(udph + 1);
 
-       udph->source = htons(CAPWAP_SRC_PORT);
-       udph->dest = htons(CAPWAP_DST_PORT);
+       udph->dest = mutable->dst_port;
        udph->check = 0;
 
        cwh->frag_id = 0;
@@ -230,6 +222,7 @@ static struct sk_buff *capwap_update_header(const struct 
vport *vport,
                opt->key = OVS_CB(skb)->tun_id;
        }
 
+       udph->source = tnl_get_src_port(skb, mutable);
        udph->len = htons(skb->len - skb_transport_offset(skb));
 
        if (unlikely(skb->len - skb_network_offset(skb) > dst_mtu(dst))) {
@@ -361,6 +354,7 @@ static const struct tnl_ops capwap_tnl_ops = {
        .hdr_len        = capwap_hdr_len,
        .build_header   = capwap_build_header,
        .update_header  = capwap_update_header,
+       .udp_rcv        = capwap_rcv,
 };
 
 static struct vport *capwap_create(const struct vport_parms *parms)
@@ -368,46 +362,6 @@ static struct vport *capwap_create(const struct 
vport_parms *parms)
        return tnl_create(parms, &capwap_vport_ops, &capwap_tnl_ops);
 }
 
-/* Random value.  Irrelevant as long as it's not 0 since we set the handler. */
-#define UDP_ENCAP_CAPWAP 10
-static int capwap_init(void)
-{
-       int err;
-       struct sockaddr_in sin;
-
-       err = sock_create(AF_INET, SOCK_DGRAM, 0, &capwap_rcv_socket);
-       if (err)
-               goto error;
-
-       sin.sin_family = AF_INET;
-       sin.sin_addr.s_addr = htonl(INADDR_ANY);
-       sin.sin_port = htons(CAPWAP_DST_PORT);
-
-       err = kernel_bind(capwap_rcv_socket, (struct sockaddr *)&sin,
-                         sizeof(struct sockaddr_in));
-       if (err)
-               goto error_sock;
-
-       udp_sk(capwap_rcv_socket->sk)->encap_type = UDP_ENCAP_CAPWAP;
-       udp_sk(capwap_rcv_socket->sk)->encap_rcv = capwap_rcv;
-
-       defrag_init();
-
-       return 0;
-
-error_sock:
-       sock_release(capwap_rcv_socket);
-error:
-       pr_warn("cannot register capwap protocol handler\n");
-       return err;
-}
-
-static void capwap_exit(void)
-{
-       defrag_exit();
-       sock_release(capwap_rcv_socket);
-}
-
 static void copy_skb_metadata(struct sk_buff *from, struct sk_buff *to)
 {
        to->pkt_type = from->pkt_type;
@@ -738,13 +692,14 @@ static struct sk_buff *defrag(struct sk_buff *skb, bool 
frag_last)
        return NULL;
 }
 
-static void defrag_init(void)
+static int capwap_init(void)
 {
        inet_frags_init(&frag_state);
        inet_frags_init_net(&frag_netns_state);
+       return 0;
 }
 
-static void defrag_exit(void)
+static void capwap_exit(void)
 {
        inet_frags_exit_net(&frag_netns_state, &frag_state);
        inet_frags_fini(&frag_state);
diff --git a/include/openvswitch/tunnel.h b/include/openvswitch/tunnel.h
index 110e652..c2febb1 100644
--- a/include/openvswitch/tunnel.h
+++ b/include/openvswitch/tunnel.h
@@ -45,7 +45,9 @@
 
 /* OVS_VPORT_ATTR_OPTIONS attributes for tunnels.
  *
- * OVS_TUNNEL_ATTR_FLAGS and OVS_TUNNEL_ATTR_DST_IPV4 are required.  All other
+ * All tunnels require OVS_TUNNEL_ATTR_FLAGS and OVS_TUNNEL_ATTR_DST_IPV4.
+ * Tunnels with L4 port numbers (currently just UDP-based tunnels) require
+ * OVS_TUNNEL_ATTR_SRC_PORT_BASE and OVS_TUNNEL_ATTR_DST_PORT.  All other
  * attributes are optional.
  */
 enum {
@@ -53,6 +55,9 @@ enum {
        OVS_TUNNEL_ATTR_FLAGS,    /* 32-bit TNL_F_*. */
        OVS_TUNNEL_ATTR_DST_IPV4, /* IPv4 destination address. */
        OVS_TUNNEL_ATTR_SRC_IPV4, /* IPv4 source address. */
+       OVS_TUNNEL_ATTR_SRC_PORT_BASE, /* __be16 base source port. */
+       OVS_TUNNEL_ATTR_SRC_PORT_HASH, /* u8 number of random source port bits. 
*/
+       OVS_TUNNEL_ATTR_DST_PORT, /* __be16 destination port. */
        OVS_TUNNEL_ATTR_OUT_KEY,  /* __be64 key to use on output. */
        OVS_TUNNEL_ATTR_IN_KEY,   /* __be64 key to match on input. */
        OVS_TUNNEL_ATTR_TOS,      /* 8-bit TOS value. */
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index f6dbd03..5a562c3 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -30,6 +30,7 @@
 #include "daemon.h"
 #include "dirs.h"
 #include "dpif-linux.h"
+#include "dynamic-string.h"
 #include "hash.h"
 #include "hmap.h"
 #include "list.h"
@@ -63,10 +64,18 @@ struct netdev_vport {
 
 struct vport_class {
     enum ovs_vport_type type;
+
+    /* Defaults.  src_port_base and dst_port must be nonzero if and only if
+     * this vport class is a tunnel that uses L4 port numbers. */
+    uint16_t src_port_base;
+    uint8_t src_port_hash;
+    uint16_t dst_port;
+
     struct netdev_class netdev_class;
-    int (*parse_config)(const char *name, const char *type,
+
+    int (*parse_config)(const char *name, const struct vport_class *,
                         const struct shash *args, struct ofpbuf *options);
-    int (*unparse_config)(const char *name, const char *type,
+    int (*unparse_config)(const char *name, const struct vport_class *,
                           const struct nlattr *options, size_t options_len,
                           struct shash *args);
 };
@@ -246,7 +255,7 @@ netdev_vport_get_config(struct netdev_dev *dev_, struct 
shash *args)
         ofpbuf_delete(buf);
     }
 
-    error = vport_class->unparse_config(name, netdev_class->type,
+    error = vport_class->unparse_config(name, vport_class,
                                         dev->options->data,
                                         dev->options->size,
                                         args);
@@ -268,8 +277,7 @@ netdev_vport_set_config(struct netdev_dev *dev_, const 
struct shash *args)
     int error;
 
     options = ofpbuf_new(64);
-    error = vport_class->parse_config(name, netdev_dev_get_type(dev_),
-                                      args, options);
+    error = vport_class->parse_config(name, vport_class, args, options);
     if (!error
         && (!dev->options
             || options->size != dev->options->size
@@ -570,15 +578,19 @@ set_key(const struct shash *args, const char *name, 
uint16_t type,
 }
 
 static int
-parse_tunnel_config(const char *name, const char *type,
+parse_tunnel_config(const char *name, const struct vport_class *vport_class,
                     const struct shash *args, struct ofpbuf *options)
 {
+    const char *type = vport_class->netdev_class.type;
     bool is_gre = false;
     bool is_ipsec = false;
     struct shash_node *node;
     bool ipsec_mech_set = false;
     ovs_be32 daddr = htonl(0);
     ovs_be32 saddr = htonl(0);
+    uint16_t src_port_base;
+    uint8_t src_port_hash;
+    uint16_t dst_port;
     uint32_t flags;
 
     flags = TNL_F_DF_DEFAULT | TNL_F_PMTUD | TNL_F_HDR_CACHE;
@@ -590,8 +602,13 @@ parse_tunnel_config(const char *name, const char *type,
         flags |= TNL_F_IPSEC;
         flags &= ~TNL_F_HDR_CACHE;
     }
+    src_port_base = vport_class->src_port_base;
+    src_port_hash = vport_class->src_port_hash;
+    dst_port = vport_class->dst_port;
 
     SHASH_FOR_EACH (node, args) {
+        const char *value = node->data;
+
         if (!strcmp(node->name, "remote_ip")) {
             struct in_addr in_addr;
             if (lookup_ip(node->data, &in_addr)) {
@@ -606,6 +623,26 @@ parse_tunnel_config(const char *name, const char *type,
             } else {
                 saddr = in_addr.s_addr;
             }
+        } else if (src_port_base && !strcmp(node->name, "src_port")) {
+            int n = -1;
+            if (sscanf(node->data, "%"SCNu16"/%"SCNu8"%n", &src_port_base,
+                       &src_port_hash, &n) > 0 && src_port_base != 0) {
+                if (n == -1) {
+                    src_port_hash = 0;
+                } else if (src_port_hash > 16) {
+                    VLOG_WARN("%s: ignoring invalid src_port hash bits", name);
+                    src_port_hash = 0;
+                }
+            } else {
+                VLOG_ERR("%s: invalid src_port \"%s\"", name, value);
+                return EINVAL;
+            }
+        } else if (dst_port && !strcmp(node->name, "dst_port")) {
+            dst_port = atoi(node->data);
+            if (!dst_port) {
+                VLOG_ERR("%s: invalid dst_port \"%s\"", name, value);
+                return EINVAL;
+            }
         } else if (!strcmp(node->name, "tos")) {
             if (!strcmp(node->data, "inherit")) {
                 flags |= TNL_F_TOS_INHERIT;
@@ -715,6 +752,19 @@ parse_tunnel_config(const char *name, const char *type,
         }
     }
 
+    if (src_port_base) {
+        nl_msg_put_be16(options, OVS_TUNNEL_ATTR_SRC_PORT_BASE,
+                        htons(src_port_base));
+        if (src_port_hash) {
+            nl_msg_put_u8(options, OVS_TUNNEL_ATTR_SRC_PORT_HASH,
+                          src_port_hash);
+        }
+    }
+
+    if (dst_port) {
+        nl_msg_put_be16(options, OVS_TUNNEL_ATTR_DST_PORT, htons(dst_port));
+    }
+
     nl_msg_put_u32(options, OVS_TUNNEL_ATTR_FLAGS, flags);
 
     return 0;
@@ -728,6 +778,11 @@ tnl_port_config_from_nlattr(const struct nlattr *options, 
size_t options_len,
         [OVS_TUNNEL_ATTR_FLAGS] = { .type = NL_A_U32 },
         [OVS_TUNNEL_ATTR_DST_IPV4] = { .type = NL_A_BE32 },
         [OVS_TUNNEL_ATTR_SRC_IPV4] = { .type = NL_A_BE32, .optional = true },
+        [OVS_TUNNEL_ATTR_SRC_PORT_BASE] = { .type = NL_A_BE16,
+                                            .optional = true },
+        [OVS_TUNNEL_ATTR_SRC_PORT_HASH] = { .type = NL_A_U8,
+                                            .optional = true },
+        [OVS_TUNNEL_ATTR_DST_PORT] = { .type = NL_A_BE16, .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 },
@@ -749,12 +804,27 @@ get_be64_or_zero(const struct nlattr *a)
     return a ? ntohll(nl_attr_get_be64(a)) : 0;
 }
 
+static uint16_t
+get_be16_or_zero(const struct nlattr *a)
+{
+    return a ? ntohs(nl_attr_get_be16(a)) : 0;
+}
+
+static uint8_t
+get_u8_or_zero(const struct nlattr *a)
+{
+    return a ? nl_attr_get_u8(a) : 0;
+}
+
 static int
-unparse_tunnel_config(const char *name OVS_UNUSED, const char *type OVS_UNUSED,
+unparse_tunnel_config(const char *name OVS_UNUSED,
+                      const struct vport_class *vport_class,
                       const struct nlattr *options, size_t options_len,
                       struct shash *args)
 {
     struct nlattr *a[OVS_TUNNEL_ATTR_MAX + 1];
+    uint16_t src_port_base, dst_port;
+    int src_port_hash;
     ovs_be32 daddr;
     uint32_t flags;
     int error;
@@ -778,6 +848,25 @@ unparse_tunnel_config(const char *name OVS_UNUSED, const 
char *type OVS_UNUSED,
         shash_add(args, "local_ip", xasprintf(IP_FMT, IP_ARGS(&saddr)));
     }
 
+    src_port_base = get_be16_or_zero(a[OVS_TUNNEL_ATTR_SRC_PORT_BASE]);
+    src_port_hash = get_u8_or_zero(a[OVS_TUNNEL_ATTR_SRC_PORT_HASH]);
+    if (src_port_base != vport_class->src_port_base ||
+        src_port_hash != vport_class->src_port_hash) {
+        struct ds s;
+
+        ds_init(&s);
+        ds_put_format(&s, "%"PRIu16, src_port_base);
+        if (src_port_hash) {
+            ds_put_format(&s, "/%d", src_port_hash);
+        }
+        shash_add_nocopy(args, "src_port", ds_steal_cstr(&s));
+    }
+
+    dst_port = get_be16_or_zero(a[OVS_TUNNEL_ATTR_DST_PORT]);
+    if (dst_port != vport_class->dst_port) {
+        shash_add_nocopy(args, "dst_port", xasprintf("%"PRIu16, dst_port));
+    }
+
     if (!a[OVS_TUNNEL_ATTR_IN_KEY] && !a[OVS_TUNNEL_ATTR_OUT_KEY]) {
         smap_add(args, "key", "flow");
     } else {
@@ -832,7 +921,8 @@ unparse_tunnel_config(const char *name OVS_UNUSED, const 
char *type OVS_UNUSED,
 }
 
 static int
-parse_patch_config(const char *name, const char *type OVS_UNUSED,
+parse_patch_config(const char *name,
+                   const struct vport_class *vport_class OVS_UNUSED,
                    const struct shash *args, struct ofpbuf *options)
 {
     const char *peer;
@@ -864,7 +954,8 @@ 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,
+unparse_patch_config(const char *name OVS_UNUSED,
+                     const struct vport_class *vport_class OVS_UNUSED,
                      const struct nlattr *options, size_t options_len,
                      struct shash *args)
 {
@@ -951,19 +1042,19 @@ void
 netdev_vport_register(void)
 {
     static const struct vport_class vport_classes[] = {
-        { OVS_VPORT_TYPE_GRE,
+        { OVS_VPORT_TYPE_GRE, 0, 0, 0,
           { "gre", VPORT_FUNCTIONS(netdev_vport_get_status) },
           parse_tunnel_config, unparse_tunnel_config },
 
-        { OVS_VPORT_TYPE_GRE,
+        { OVS_VPORT_TYPE_GRE, 0, 0, 0,
           { "ipsec_gre", VPORT_FUNCTIONS(netdev_vport_get_status) },
           parse_tunnel_config, unparse_tunnel_config },
 
-        { OVS_VPORT_TYPE_CAPWAP,
+        { OVS_VPORT_TYPE_CAPWAP, 58881, 0, 58882,
           { "capwap", VPORT_FUNCTIONS(netdev_vport_get_status) },
           parse_tunnel_config, unparse_tunnel_config },
 
-        { OVS_VPORT_TYPE_PATCH,
+        { OVS_VPORT_TYPE_PATCH, 0, 0, 0,
           { "patch", VPORT_FUNCTIONS(NULL) },
           parse_patch_config, unparse_patch_config }
     };
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index bcb6b6f..8791114 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -1084,9 +1084,8 @@
             An Ethernet tunnel over the UDP transport portion of CAPWAP (RFC
             5415).  This allows interoperability with certain switches that do
             not support GRE.  Only the tunneling component of the protocol is
-            implemented.  UDP ports 58881 and 58882 are used as the source and
-            destination ports respectively.  CAPWAP is currently supported only
-            with the Linux kernel datapath with kernel version 2.6.26 or later.
+            implemented.  CAPWAP is currently supported only with the Linux
+            kernel datapath with kernel version 2.6.26 or later.
           </dd>
 
           <dt><code>patch</code></dt>
@@ -1306,6 +1305,48 @@
           tunnel.
         </column>
       </group>
+
+      <group title="Tunnel Options: capwap only">
+       <p>
+         Only <code>capwap</code> interfaces support this options.
+       </p>
+
+       <column name="options" key="src_port">
+         Determines the UDP source port used for sending packets, in one of
+         these forms:
+
+         <dl>
+           <dt><code><var>port</var></code></dt>
+           <dd>
+             Uses <var>port</var> as the source port.
+           </dd>
+
+           <dt><code><var>base</var>/<var>hashbits</var></code></dt>
+           <dd>
+             <p>
+               The source port is a range of
+               <code>2**<var>hashbits</var></code> total ports starting at
+               port <var>base</var>.  For example, <code>32768/15</code>
+               allows the source ports to vary between 32768 and 65535, a
+               range of 32768 (<code>2**15</code>) total ports.  For any given
+               packet, the port within the range is chosen based on a hash of
+               its flow.
+             </p>
+             <p>
+                Source port ranges allow for load balancing across network
+                elements that cannot look into the tunneled packet.
+             </p>
+           </dd>
+         </dl>
+
+         The default source port is 58881.
+       </column>
+
+       <column name="options" key="dst_port">
+         The UDP destination port to which packets are sent and on which they
+         are expected to be received.  The default is 58882.
+       </column>
+      </group>
     </group>
 
     <group title="Patch Options">
-- 
1.7.2.5

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to