datapath/flow_netlink: Fix NDP flow mask validation
[cascardo/ovs.git] / datapath / flow.c
index 62357b9..78924d6 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/jhash.h>
 #include <linux/jiffies.h>
 #include <linux/llc.h>
+#include <linux/list.h>
 #include <linux/module.h>
 #include <linux/in.h>
 #include <linux/rcupdate.h>
@@ -55,8 +56,8 @@ static void update_range__(struct sw_flow_match *match,
                          size_t offset, size_t size, bool is_mask)
 {
        struct sw_flow_key_range *range = NULL;
-       size_t start = offset;
-       size_t end = offset + size;
+       size_t start = rounddown(offset, sizeof(long));
+       size_t end = roundup(offset + size, sizeof(long));
 
        if (!is_mask)
                range = &match->range;
@@ -103,6 +104,11 @@ static void update_range__(struct sw_flow_match *match,
                }                                                           \
        } while (0)
 
+static u16 range_n_bytes(const struct sw_flow_key_range *range)
+{
+       return range->end - range->start;
+}
+
 void ovs_match_init(struct sw_flow_match *match,
                    struct sw_flow_key *key,
                    struct sw_flow_mask *mask)
@@ -215,7 +221,7 @@ static bool ovs_match_validate(const struct sw_flow_match *match,
                                                htons(NDISC_NEIGHBOUR_SOLICITATION) ||
                                    match->key->ipv6.tp.src == htons(NDISC_NEIGHBOUR_ADVERTISEMENT)) {
                                        key_expected |= 1ULL << OVS_KEY_ATTR_ND;
-                                       if (match->mask && (match->mask->key.ipv6.tp.src == htons(0xffff)))
+                                       if (match->mask && (match->mask->key.ipv6.tp.src == htons(0xff)))
                                                mask_allowed |= 1ULL << OVS_KEY_ATTR_ND;
                                }
                        }
@@ -371,16 +377,17 @@ static bool icmp6hdr_ok(struct sk_buff *skb)
 void ovs_flow_key_mask(struct sw_flow_key *dst, const struct sw_flow_key *src,
                       const struct sw_flow_mask *mask)
 {
-       u8 *m = (u8 *)&mask->key + mask->range.start;
-       u8 *s = (u8 *)src + mask->range.start;
-       u8 *d = (u8 *)dst + mask->range.start;
+       const long *m = (long *)((u8 *)&mask->key + mask->range.start);
+       const long *s = (long *)((u8 *)src + mask->range.start);
+       long *d = (long *)((u8 *)dst + mask->range.start);
        int i;
 
-       memset(dst, 0, sizeof(*dst));
-       for (i = 0; i < ovs_sw_flow_mask_size_roundup(mask); i++) {
-               *d = *s & *m;
-               d++, s++, m++;
-       }
+       /* The memory outside of the 'mask->range' are not set since
+        * further operations on 'dst' only uses contents within
+        * 'mask->range'.
+        */
+       for (i = 0; i < range_n_bytes(&mask->range); i += sizeof(long))
+               *d++ = *s++ & *m++;
 }
 
 #define TCP_FLAGS_OFFSET 13
@@ -496,27 +503,11 @@ static struct flow_table *__flow_tbl_alloc(int new_size)
 
 static void __flow_tbl_destroy(struct flow_table *table)
 {
-       int i;
-
-       if (table->keep_flows)
-               goto skip_flows;
-
-       for (i = 0; i < table->n_buckets; i++) {
-               struct sw_flow *flow;
-               struct hlist_head *head = flex_array_get(table->buckets, i);
-               struct hlist_node *n;
-               int ver = table->node_ver;
-
-               hlist_for_each_entry_safe(flow, n, head, hash_node[ver]) {
-                       hlist_del(&flow->hash_node[ver]);
-                       ovs_flow_free(flow, false);
-               }
+       if (!table->keep_flows) {
+               BUG_ON(!list_empty(table->mask_list));
+               kfree(table->mask_list);
        }
 
-       BUG_ON(!list_empty(table->mask_list));
-       kfree(table->mask_list);
-
-skip_flows:
        free_buckets(table->buckets);
        kfree(table);
 }
@@ -548,9 +539,27 @@ static void flow_tbl_destroy_rcu_cb(struct rcu_head *rcu)
 
 void ovs_flow_tbl_destroy(struct flow_table *table, bool deferred)
 {
+       int i;
+
        if (!table)
                return;
 
+       if (table->keep_flows)
+               goto skip_flows;
+
+       for (i = 0; i < table->n_buckets; i++) {
+               struct sw_flow *flow;
+               struct hlist_head *head = flex_array_get(table->buckets, i);
+               struct hlist_node *n;
+               int ver = table->node_ver;
+
+               hlist_for_each_entry_safe(flow, n, head, hash_node[ver]) {
+                       hlist_del_rcu(&flow->hash_node[ver]);
+                       ovs_flow_free(flow, deferred);
+               }
+       }
+
+skip_flows:
        if (deferred)
                call_rcu(&table->rcu, flow_tbl_destroy_rcu_cb);
        else
@@ -847,7 +856,7 @@ int ovs_flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key)
        if (OVS_CB(skb)->tun_key)
                memcpy(&key->tun_key, OVS_CB(skb)->tun_key, sizeof(key->tun_key));
        key->phy.in_port = in_port;
-       key->phy.skb_mark = skb_get_mark(skb);
+       key->phy.skb_mark = skb->mark;
 
        skb_reset_mac_header(skb);
 
@@ -1005,10 +1014,16 @@ int ovs_flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key)
        return 0;
 }
 
-static u32 ovs_flow_hash(const struct sw_flow_key *key, int key_start, int key_len)
+static u32 ovs_flow_hash(const struct sw_flow_key *key, int key_start,
+                        int key_end)
 {
-       return jhash2((u32 *)((u8 *)key + key_start),
-                     DIV_ROUND_UP(key_len - key_start, sizeof(u32)), 0);
+       u32 *hash_key = (u32 *)((u8 *)key + key_start);
+       int hash_u32s = (key_end - key_start) >> 2;
+
+       /* Make sure number of hash bytes are multiple of u32. */
+       BUILD_BUG_ON(sizeof(long) % sizeof(u32));
+
+       return jhash2(hash_key, hash_u32s, 0);
 }
 
 static int flow_key_start(const struct sw_flow_key *key)
@@ -1016,71 +1031,84 @@ static int flow_key_start(const struct sw_flow_key *key)
        if (key->tun_key.ipv4_dst)
                return 0;
        else
-               return offsetof(struct sw_flow_key, phy);
+               return rounddown(offsetof(struct sw_flow_key, phy),
+                                         sizeof(long));
 }
 
 static bool __cmp_key(const struct sw_flow_key *key1,
-               const struct sw_flow_key *key2,  int key_start, int key_len)
+               const struct sw_flow_key *key2,  int key_start, int key_end)
 {
-       return !memcmp((u8 *)key1 + key_start,
-                       (u8 *)key2 + key_start, (key_len - key_start));
+       const long *cp1 = (long *)((u8 *)key1 + key_start);
+       const long *cp2 = (long *)((u8 *)key2 + key_start);
+       long diffs = 0;
+       int i;
+
+       for (i = key_start; i < key_end;  i += sizeof(long))
+               diffs |= *cp1++ ^ *cp2++;
+
+       return diffs == 0;
 }
 
-static bool __flow_cmp_key(const struct sw_flow *flow,
-               const struct sw_flow_key *key, int key_start, int key_len)
+static bool __flow_cmp_masked_key(const struct sw_flow *flow,
+               const struct sw_flow_key *key, int key_start, int key_end)
 {
-       return __cmp_key(&flow->key, key, key_start, key_len);
+       return __cmp_key(&flow->key, key, key_start, key_end);
 }
 
 static bool __flow_cmp_unmasked_key(const struct sw_flow *flow,
-                 const struct sw_flow_key *key, int key_start, int key_len)
+                 const struct sw_flow_key *key, int key_start, int key_end)
 {
-       return __cmp_key(&flow->unmasked_key, key, key_start, key_len);
+       return __cmp_key(&flow->unmasked_key, key, key_start, key_end);
 }
 
 bool ovs_flow_cmp_unmasked_key(const struct sw_flow *flow,
-               const struct sw_flow_key *key, int key_len)
+               const struct sw_flow_key *key, int key_end)
 {
        int key_start;
        key_start = flow_key_start(key);
 
-       return __flow_cmp_unmasked_key(flow, key, key_start, key_len);
-
-}
-
-struct sw_flow *ovs_flow_lookup_unmasked_key(struct flow_table *table,
-                                      struct sw_flow_match *match)
-{
-       struct sw_flow_key *unmasked = match->key;
-       int key_len = match->range.end;
-       struct sw_flow *flow;
-
-       flow = ovs_flow_lookup(table, unmasked);
-       if (flow && (!ovs_flow_cmp_unmasked_key(flow, unmasked, key_len)))
-               flow = NULL;
+       return __flow_cmp_unmasked_key(flow, key, key_start, key_end);
 
-       return flow;
 }
 
 static struct sw_flow *ovs_masked_flow_lookup(struct flow_table *table,
-                                   const struct sw_flow_key *flow_key,
+                                   const struct sw_flow_key *unmasked,
                                    struct sw_flow_mask *mask)
 {
        struct sw_flow *flow;
        struct hlist_head *head;
        int key_start = mask->range.start;
-       int key_len = mask->range.end;
+       int key_end = mask->range.end;
        u32 hash;
        struct sw_flow_key masked_key;
 
-       ovs_flow_key_mask(&masked_key, flow_key, mask);
-       hash = ovs_flow_hash(&masked_key, key_start, key_len);
+       ovs_flow_key_mask(&masked_key, unmasked, mask);
+       hash = ovs_flow_hash(&masked_key, key_start, key_end);
        head = find_bucket(table, hash);
        hlist_for_each_entry_rcu(flow, head, hash_node[table->node_ver]) {
                if (flow->mask == mask &&
-                   __flow_cmp_key(flow, &masked_key, key_start, key_len))
+                   __flow_cmp_masked_key(flow, &masked_key,
+                                         key_start, key_end))
+                       return flow;
+       }
+       return NULL;
+}
+
+struct sw_flow *ovs_flow_lookup_exact(struct flow_table *tbl,
+                                     struct sw_flow_match *match)
+{
+       struct sw_flow_key *unmasked = match->key;
+       struct sw_flow *flow;
+       struct sw_flow_mask *mask;
+       int key_end = match->range.end;
+
+       /* Always called under ovs-mutex. */
+       list_for_each_entry(mask, tbl->mask_list, list) {
+               flow = ovs_masked_flow_lookup(tbl, unmasked, mask);
+               if (flow && ovs_flow_cmp_unmasked_key(flow, unmasked, key_end))  /* Found */
                        return flow;
        }
+
        return NULL;
 }
 
@@ -1165,6 +1193,7 @@ static int __parse_flow_nlattrs(const struct nlattr *attr,
                if (type > OVS_KEY_ATTR_MAX) {
                        OVS_NLERR("Unknown key attribute (type=%d, max=%d).\n",
                                  type, OVS_KEY_ATTR_MAX);
+                       return -EINVAL;
                }
 
                if (attrs & (1ULL << type)) {
@@ -1356,12 +1385,7 @@ static int metadata_from_nlattrs(struct sw_flow_match *match,  u64 *attrs,
 
        if (*attrs & (1ULL << OVS_KEY_ATTR_SKB_MARK)) {
                uint32_t mark = nla_get_u32(a[OVS_KEY_ATTR_SKB_MARK]);
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) && !defined(CONFIG_NETFILTER)
-               if (!is_mask && mark != 0) {
-                       OVS_NLERR("skb->mark must be zero on this kernel (mark=%d).\n", mark);
-                       return -EINVAL;
-               }
-#endif
+
                SW_FLOW_KEY_PUT(match, phy.skb_mark, mark, is_mask);
                *attrs &= ~(1ULL << OVS_KEY_ATTR_SKB_MARK);
        }
@@ -1635,26 +1659,36 @@ int ovs_match_from_nlattrs(struct sw_flow_match *match,
        if (err)
                return err;
 
-       if (key_attrs & 1ULL << OVS_KEY_ATTR_ENCAP) {
-               encap = a[OVS_KEY_ATTR_ENCAP];
-               key_attrs &= ~(1ULL << OVS_KEY_ATTR_ENCAP);
-               if (nla_len(encap)) {
-                       __be16 eth_type = 0; /* ETH_P_8021Q */
+       if ((key_attrs & (1ULL << OVS_KEY_ATTR_ETHERNET)) &&
+           (key_attrs & (1ULL << OVS_KEY_ATTR_ETHERTYPE)) &&
+           (nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]) == htons(ETH_P_8021Q))) {
+               __be16 tci;
 
-                       if (a[OVS_KEY_ATTR_ETHERTYPE])
-                               eth_type = nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]);
+               if (!((key_attrs & (1ULL << OVS_KEY_ATTR_VLAN)) &&
+                     (key_attrs & (1ULL << OVS_KEY_ATTR_ENCAP)))) {
+                       OVS_NLERR("Invalid Vlan frame.\n");
+                       return -EINVAL;
+               }
 
-                       if  ((eth_type == htons(ETH_P_8021Q)) && (a[OVS_KEY_ATTR_VLAN])) {
-                               encap_valid = true;
-                               key_attrs &= ~(1ULL << OVS_KEY_ATTR_ETHERTYPE);
-                               err = parse_flow_nlattrs(encap, a, &key_attrs);
-                       } else {
-                               OVS_NLERR("Encap attribute is set for a non-VLAN frame.\n");
-                               err = -EINVAL;
-                       }
+               key_attrs &= ~(1ULL << OVS_KEY_ATTR_ETHERTYPE);
+               tci = nla_get_be16(a[OVS_KEY_ATTR_VLAN]);
+               encap = a[OVS_KEY_ATTR_ENCAP];
+               key_attrs &= ~(1ULL << OVS_KEY_ATTR_ENCAP);
+               encap_valid = true;
 
+               if (tci & htons(VLAN_TAG_PRESENT)) {
+                       err = parse_flow_nlattrs(encap, a, &key_attrs);
                        if (err)
                                return err;
+               } else if (!tci) {
+                       /* Corner case for truncated 802.1Q header. */
+                       if (nla_len(encap)) {
+                               OVS_NLERR("Truncated 802.1Q header has non-zero encap attribute.\n");
+                               return -EINVAL;
+                       }
+               } else {
+                       OVS_NLERR("Encap attribute is set for a non-VLAN frame.\n");
+                       return  -EINVAL;
                }
        }
 
@@ -1667,25 +1701,36 @@ int ovs_match_from_nlattrs(struct sw_flow_match *match,
                if (err)
                        return err;
 
-               if ((mask_attrs & 1ULL << OVS_KEY_ATTR_ENCAP) && encap_valid) {
+               if (mask_attrs & 1ULL << OVS_KEY_ATTR_ENCAP)  {
                        __be16 eth_type = 0;
+                       __be16 tci = 0;
+
+                       if (!encap_valid) {
+                               OVS_NLERR("Encap mask attribute is set for non-VLAN frame.\n");
+                               return  -EINVAL;
+                       }
 
                        mask_attrs &= ~(1ULL << OVS_KEY_ATTR_ENCAP);
                        if (a[OVS_KEY_ATTR_ETHERTYPE])
                                eth_type = nla_get_be16(a[OVS_KEY_ATTR_ETHERTYPE]);
+
                        if (eth_type == htons(0xffff)) {
                                mask_attrs &= ~(1ULL << OVS_KEY_ATTR_ETHERTYPE);
                                encap = a[OVS_KEY_ATTR_ENCAP];
                                err = parse_flow_mask_nlattrs(encap, a, &mask_attrs);
                        } else {
-                               OVS_NLERR("VLAN frames must have an exact match"
-                                        " on the TPID (mask=%x).\n",
-                                        ntohs(eth_type));
-                               err = -EINVAL;
+                               OVS_NLERR("VLAN frames must have an exact match on the TPID (mask=%x).\n",
+                                               ntohs(eth_type));
+                               return -EINVAL;
                        }
 
-                       if (err)
-                               return err;
+                       if (a[OVS_KEY_ATTR_VLAN])
+                               tci = nla_get_be16(a[OVS_KEY_ATTR_VLAN]);
+
+                       if (!(tci & htons(VLAN_TAG_PRESENT))) {
+                               OVS_NLERR("VLAN tag present bit must have an exact match (tci_mask=%x).\n", ntohs(tci));
+                               return -EINVAL;
+                       }
                }
 
                err = ovs_key_from_nlattrs(match, mask_attrs, a, true);
@@ -1953,6 +1998,9 @@ nla_put_failure:
  * Returns zero if successful or a negative error code. */
 int ovs_flow_init(void)
 {
+       BUILD_BUG_ON(__alignof__(struct sw_flow_key) % __alignof__(long));
+       BUILD_BUG_ON(sizeof(struct sw_flow_key) % sizeof(long));
+
        flow_cache = kmem_cache_create("sw_flow", sizeof(struct sw_flow), 0,
                                        0, NULL);
        if (flow_cache == NULL)
@@ -2015,7 +2063,7 @@ static bool ovs_sw_flow_mask_equal(const struct sw_flow_mask *a,
 
        return  (a->range.end == b->range.end)
                && (a->range.start == b->range.start)
-               && (memcmp(a_, b_, ovs_sw_flow_mask_actual_size(a)) == 0);
+               && (memcmp(a_, b_, range_n_bytes(&a->range)) == 0);
 }
 
 struct sw_flow_mask *ovs_sw_flow_mask_find(const struct flow_table *tbl,
@@ -2052,5 +2100,5 @@ static void ovs_sw_flow_mask_set(struct sw_flow_mask *mask,
        u8 *m = (u8 *)&mask->key + range->start;
 
        mask->range = *range;
-       memset(m, val, ovs_sw_flow_mask_size_roundup(mask));
+       memset(m, val, range_n_bytes(range));
 }