vxlan: allow configuring port range
authorstephen hemminger <shemminger@vyatta.com>
Tue, 9 Oct 2012 20:35:50 +0000 (20:35 +0000)
committerDavid S. Miller <davem@davemloft.net>
Thu, 11 Oct 2012 02:41:21 +0000 (22:41 -0400)
VXLAN bases source UDP port based on flow to help the
receiver to be able to load balance based on outer header flow.

This patch restricts the port range to the normal UDP local
ports, and allows overriding via configuration.

It also uses jhash of Ethernet header when looking at flows
with out know L3 header.

Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/vxlan.c
include/linux/if_link.h

index 43887d92777535e11a628e39c84ca60482a5886f..4be2784e7ac2f2422db489e6dd364319b3adab70 100644 (file)
@@ -106,6 +106,8 @@ struct vxlan_dev {
        __be32            gaddr;        /* multicast group */
        __be32            saddr;        /* source address */
        unsigned int      link;         /* link to multicast over */
+       __u16             port_min;     /* source port range */
+       __u16             port_max;
        __u8              tos;          /* TOS override */
        __u8              ttl;
        bool              learn;
@@ -654,12 +656,29 @@ static void vxlan_set_owner(struct net_device *dev, struct sk_buff *skb)
        skb->destructor = vxlan_sock_free;
 }
 
+/* Compute source port for outgoing packet
+ *   first choice to use L4 flow hash since it will spread
+ *     better and maybe available from hardware
+ *   secondary choice is to use jhash on the Ethernet header
+ */
+static u16 vxlan_src_port(const struct vxlan_dev *vxlan, struct sk_buff *skb)
+{
+       unsigned int range = (vxlan->port_max - vxlan->port_min) + 1;
+       u32 hash;
+
+       hash = skb_get_rxhash(skb);
+       if (!hash)
+               hash = jhash(skb->data, 2 * ETH_ALEN,
+                            (__force u32) skb->protocol);
+
+       return (((u64) hash * range) >> 32) + vxlan->port_min;
+}
+
 /* Transmit local packets over Vxlan
  *
  * Outer IP header inherits ECN and DF from inner header.
  * Outer UDP destination is the VXLAN assigned port.
- *           source port is based on hash of flow if available
- *                       otherwise use a random value
+ *           source port is based on hash of flow
  */
 static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
 {
@@ -671,8 +690,8 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
        struct udphdr *uh;
        struct flowi4 fl4;
        unsigned int pkt_len = skb->len;
-       u32 hash;
        __be32 dst;
+       __u16 src_port;
        __be16 df = 0;
        __u8 tos, ttl;
        int err;
@@ -695,7 +714,7 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
        if (tos == 1)
                tos = vxlan_get_dsfield(old_iph, skb);
 
-       hash = skb_get_rxhash(skb);
+       src_port = vxlan_src_port(vxlan, skb);
 
        memset(&fl4, 0, sizeof(fl4));
        fl4.flowi4_oif = vxlan->link;
@@ -732,7 +751,7 @@ static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
        uh = udp_hdr(skb);
 
        uh->dest = htons(vxlan_port);
-       uh->source = hash ? :random32();
+       uh->source = htons(src_port);
 
        uh->len = htons(skb->len);
        uh->check = 0;
@@ -960,6 +979,7 @@ static void vxlan_setup(struct net_device *dev)
 {
        struct vxlan_dev *vxlan = netdev_priv(dev);
        unsigned h;
+       int low, high;
 
        eth_hw_addr_random(dev);
        ether_setup(dev);
@@ -979,6 +999,10 @@ static void vxlan_setup(struct net_device *dev)
        vxlan->age_timer.function = vxlan_cleanup;
        vxlan->age_timer.data = (unsigned long) vxlan;
 
+       inet_get_local_port_range(&low, &high);
+       vxlan->port_min = low;
+       vxlan->port_max = high;
+
        vxlan->dev = dev;
 
        for (h = 0; h < FDB_HASH_SIZE; ++h)
@@ -995,6 +1019,7 @@ static const struct nla_policy vxlan_policy[IFLA_VXLAN_MAX + 1] = {
        [IFLA_VXLAN_LEARNING]   = { .type = NLA_U8 },
        [IFLA_VXLAN_AGEING]     = { .type = NLA_U32 },
        [IFLA_VXLAN_LIMIT]      = { .type = NLA_U32 },
+       [IFLA_VXLAN_PORT_RANGE] = { .len  = sizeof(struct ifla_vxlan_port_range) },
 };
 
 static int vxlan_validate(struct nlattr *tb[], struct nlattr *data[])
@@ -1027,6 +1052,18 @@ static int vxlan_validate(struct nlattr *tb[], struct nlattr *data[])
                        return -EADDRNOTAVAIL;
                }
        }
+
+       if (data[IFLA_VXLAN_PORT_RANGE]) {
+               const struct ifla_vxlan_port_range *p
+                       = nla_data(data[IFLA_VXLAN_PORT_RANGE]);
+
+               if (ntohs(p->high) < ntohs(p->low)) {
+                       pr_debug("port range %u .. %u not valid\n",
+                                ntohs(p->low), ntohs(p->high));
+                       return -EINVAL;
+               }
+       }
+
        return 0;
 }
 
@@ -1077,6 +1114,13 @@ static int vxlan_newlink(struct net *net, struct net_device *dev,
        if (data[IFLA_VXLAN_LIMIT])
                vxlan->addrmax = nla_get_u32(data[IFLA_VXLAN_LIMIT]);
 
+       if (data[IFLA_VXLAN_PORT_RANGE]) {
+               const struct ifla_vxlan_port_range *p
+                       = nla_data(data[IFLA_VXLAN_PORT_RANGE]);
+               vxlan->port_min = ntohs(p->low);
+               vxlan->port_max = ntohs(p->high);
+       }
+
        err = register_netdevice(dev);
        if (!err)
                hlist_add_head_rcu(&vxlan->hlist, vni_head(net, vxlan->vni));
@@ -1105,12 +1149,17 @@ static size_t vxlan_get_size(const struct net_device *dev)
                nla_total_size(sizeof(__u8)) +  /* IFLA_VXLAN_LEARNING */
                nla_total_size(sizeof(__u32)) + /* IFLA_VXLAN_AGEING */
                nla_total_size(sizeof(__u32)) + /* IFLA_VXLAN_LIMIT */
+               nla_total_size(sizeof(struct ifla_vxlan_port_range)) +
                0;
 }
 
 static int vxlan_fill_info(struct sk_buff *skb, const struct net_device *dev)
 {
        const struct vxlan_dev *vxlan = netdev_priv(dev);
+       struct ifla_vxlan_port_range ports = {
+               .low =  htons(vxlan->port_min),
+               .high = htons(vxlan->port_max),
+       };
 
        if (nla_put_u32(skb, IFLA_VXLAN_ID, vxlan->vni))
                goto nla_put_failure;
@@ -1131,6 +1180,9 @@ static int vxlan_fill_info(struct sk_buff *skb, const struct net_device *dev)
            nla_put_u32(skb, IFLA_VXLAN_LIMIT, vxlan->addrmax))
                goto nla_put_failure;
 
+       if (nla_put(skb, IFLA_VXLAN_PORT_RANGE, sizeof(ports), &ports))
+               goto nla_put_failure;
+
        return 0;
 
 nla_put_failure:
index e4dad4ddf0855b43ed30747ed565203d273acf2d..3265f332998a54c9722fd2d6825aa63f60f3aba2 100644 (file)
@@ -284,10 +284,16 @@ enum {
        IFLA_VXLAN_LEARNING,
        IFLA_VXLAN_AGEING,
        IFLA_VXLAN_LIMIT,
+       IFLA_VXLAN_PORT_RANGE,
        __IFLA_VXLAN_MAX
 };
 #define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1)
 
+struct ifla_vxlan_port_range {
+       __be16  low;
+       __be16  high;
+};
+
 /* SR-IOV virtual function management section */
 
 enum {