datapath: Avoid using wrong metadata for recic action.
[cascardo/ovs.git] / datapath / flow_netlink.c
index f0cf2ed..75172de 100644 (file)
@@ -106,10 +106,19 @@ static void update_range__(struct sw_flow_match *match,
        SW_FLOW_KEY_MEMCPY_OFFSET(match, offsetof(struct sw_flow_key, field), \
                                  value_p, len, is_mask)
 
-static u16 range_n_bytes(const struct sw_flow_key_range *range)
-{
-       return range->end - range->start;
-}
+#define SW_FLOW_KEY_MEMSET_FIELD(match, field, value, is_mask) \
+       do { \
+               update_range__(match, offsetof(struct sw_flow_key, field),  \
+                                    sizeof((match)->key->field), is_mask); \
+               if (is_mask) {                                              \
+                       if ((match)->mask)                                  \
+                               memset((u8 *)&(match)->mask->key.field, value,\
+                                      sizeof((match)->mask->key.field));   \
+               } else {                                                    \
+                       memset((u8 *)&(match)->key->field, value,           \
+                              sizeof((match)->key->field));                \
+               }                                                           \
+       } while (0)
 
 static bool match_validate(const struct sw_flow_match *match,
                           u64 key_attrs, u64 mask_attrs)
@@ -412,6 +421,7 @@ static int ipv4_tun_from_nlattr(const struct nlattr *attr,
                        tun_flags |= TUNNEL_OAM;
                        break;
                case OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS:
+                       tun_flags |= TUNNEL_OPTIONS_PRESENT;
                        if (nla_len(a) > sizeof(match->key->tun_opts)) {
                                OVS_NLERR("Geneve option length exceeds "
                                          "maximum size (len %d, max %zu).\n",
@@ -732,7 +742,7 @@ static int ovs_key_from_nlattrs(struct sw_flow_match *match, u64 attrs,
                                mpls_key->mpls_lse, is_mask);
 
                attrs &= ~(1ULL << OVS_KEY_ATTR_MPLS);
-        }
+       }
 
        if (attrs & (1ULL << OVS_KEY_ATTR_TCP)) {
                const struct ovs_key_tcp *tcp_key;
@@ -814,13 +824,26 @@ static int ovs_key_from_nlattrs(struct sw_flow_match *match, u64 attrs,
        return 0;
 }
 
-static void sw_flow_mask_set(struct sw_flow_mask *mask,
-                            struct sw_flow_key_range *range, u8 val)
+static void nlattr_set(struct nlattr *attr, u8 val, bool is_attr_mask_key)
 {
-       u8 *m = (u8 *)&mask->key + range->start;
+       struct nlattr *nla;
+       int rem;
 
-       mask->range = *range;
-       memset(m, val, range_n_bytes(range));
+       /* The nlattr stream should already have been validated */
+       nla_for_each_nested(nla, attr, rem) {
+               /* We assume that ovs_key_lens[type] == -1 means that type is a
+                * nested attribute
+                */
+               if (is_attr_mask_key && ovs_key_lens[nla_type(nla)] == -1)
+                       nlattr_set(nla, val, false);
+               else
+                       memset(nla_data(nla), val, nla_len(nla));
+       }
+}
+
+static void mask_set_nlattr(struct nlattr *attr, u8 val)
+{
+       nlattr_set(attr, val, true);
 }
 
 /**
@@ -841,6 +864,7 @@ int ovs_nla_get_match(struct sw_flow_match *match,
 {
        const struct nlattr *a[OVS_KEY_ATTR_MAX + 1];
        const struct nlattr *encap;
+       struct nlattr *newmask = NULL;
        u64 key_attrs = 0;
        u64 mask_attrs = 0;
        bool encap_valid = false;
@@ -887,18 +911,43 @@ int ovs_nla_get_match(struct sw_flow_match *match,
        if (err)
                return err;
 
+       if (match->mask && !mask) {
+               /* Create an exact match mask. We need to set to 0xff all the
+                * 'match->mask' fields that have been touched in 'match->key'.
+                * We cannot simply memset 'match->mask', because padding bytes
+                * and fields not specified in 'match->key' should be left to 0.
+                * Instead, we use a stream of netlink attributes, copied from
+                * 'key' and set to 0xff: ovs_key_from_nlattrs() will take care
+                * of filling 'match->mask' appropriately.
+                */
+               newmask = kmemdup(key, nla_total_size(nla_len(key)),
+                                 GFP_KERNEL);
+               if (!newmask)
+                       return -ENOMEM;
+
+               mask_set_nlattr(newmask, 0xff);
+
+               /* The userspace does not send tunnel attributes that are 0,
+                * but we should not wildcard them nonetheless. */
+               if (match->key->tun_key.ipv4_dst)
+                       SW_FLOW_KEY_MEMSET_FIELD(match, tun_key, 0xff, true);
+
+               mask = newmask;
+       }
+
        if (mask) {
                err = parse_flow_mask_nlattrs(mask, a, &mask_attrs);
                if (err)
-                       return err;
+                       goto free_newmask;
 
-               if (mask_attrs & 1ULL << OVS_KEY_ATTR_ENCAP)  {
+               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;
+                               err = -EINVAL;
+                               goto free_newmask;
                        }
 
                        mask_attrs &= ~(1ULL << OVS_KEY_ATTR_ENCAP);
@@ -909,10 +958,13 @@ int ovs_nla_get_match(struct sw_flow_match *match,
                                mask_attrs &= ~(1ULL << OVS_KEY_ATTR_ETHERTYPE);
                                encap = a[OVS_KEY_ATTR_ENCAP];
                                err = parse_flow_mask_nlattrs(encap, a, &mask_attrs);
+                               if (err)
+                                       goto free_newmask;
                        } else {
                                OVS_NLERR("VLAN frames must have an exact match on the TPID (mask=%x).\n",
                                                ntohs(eth_type));
-                               return -EINVAL;
+                               err = -EINVAL;
+                               goto free_newmask;
                        }
 
                        if (a[OVS_KEY_ATTR_VLAN])
@@ -920,28 +972,27 @@ int ovs_nla_get_match(struct sw_flow_match *match,
 
                        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 = -EINVAL;
+                               goto free_newmask;
                        }
                }
 
                err = ovs_key_from_nlattrs(match, mask_attrs, a, true);
                if (err)
-                       return err;
-       } else {
-               /* Populate exact match flow's key mask. */
-               if (match->mask)
-                       sw_flow_mask_set(match->mask, &match->range, 0xff);
+                       goto free_newmask;
        }
 
        if (!match_validate(match, key_attrs, mask_attrs))
-               return -EINVAL;
+               err = -EINVAL;
 
-       return 0;
+free_newmask:
+       kfree(newmask);
+       return err;
 }
 
 /**
  * ovs_nla_get_flow_metadata - parses Netlink attributes into a flow key.
- * @flow: Receives extracted in_port, priority, tun_key and skb_mark.
+ * @key: Receives extracted in_port, priority, tun_key and skb_mark.
  * @attr: Netlink attribute holding nested %OVS_KEY_ATTR_* Netlink attribute
  * sequence.
  *
@@ -950,35 +1001,25 @@ int ovs_nla_get_match(struct sw_flow_match *match,
  * get the metadata, that is, the parts of the flow key that cannot be
  * extracted from the packet itself.
  */
-
-int ovs_nla_get_flow_metadata(struct sw_flow *flow,
-                             const struct nlattr *attr)
+int ovs_nla_get_flow_metadata(const struct nlattr *attr,
+                             struct sw_flow_key *key)
 {
-       struct ovs_key_ipv4_tunnel *tun_key = &flow->key.tun_key;
        const struct nlattr *a[OVS_KEY_ATTR_MAX + 1];
+       struct sw_flow_match match;
        u64 attrs = 0;
        int err;
-       struct sw_flow_match match;
-
-       flow->key.phy.in_port = DP_MAX_PORTS;
-       flow->key.phy.priority = 0;
-       flow->key.phy.skb_mark = 0;
-       flow->key.ovs_flow_hash = 0;
-       flow->key.recirc_id = 0;
-       memset(tun_key, 0, sizeof(flow->key.tun_key));
 
        err = parse_flow_nlattrs(attr, a, &attrs);
        if (err)
                return -EINVAL;
 
        memset(&match, 0, sizeof(match));
-       match.key = &flow->key;
+       match.key = key;
 
-       err = metadata_from_nlattrs(&match, &attrs, a, false);
-       if (err)
-               return err;
+       memset(key, 0, OVS_SW_FLOW_KEY_METADATA_SIZE);
+       key->phy.in_port = DP_MAX_PORTS;
 
-       return 0;
+       return metadata_from_nlattrs(&match, &attrs, a, false);
 }
 
 int ovs_nla_put_flow(struct datapath *dp, const struct sw_flow_key *swkey,
@@ -1000,16 +1041,8 @@ int ovs_nla_put_flow(struct datapath *dp, const struct sw_flow_key *swkey,
        if ((swkey->tun_key.ipv4_dst || is_mask)) {
                const struct geneve_opt *opts = NULL;
 
-               if (!is_mask) {
-                       struct vport *in_port;
-
-                       in_port = ovs_vport_ovsl_rcu(dp, swkey->phy.in_port);
-                       if (in_port->ops->type == OVS_VPORT_TYPE_GENEVE)
-                               opts = GENEVE_OPTS(output, swkey->tun_opts_len);
-               } else {
-                       if (output->tun_opts_len)
-                               opts = GENEVE_OPTS(output, swkey->tun_opts_len);
-               }
+               if (output->tun_key.tun_flags & TUNNEL_OPTIONS_PRESENT)
+                       opts = GENEVE_OPTS(output, swkey->tun_opts_len);
 
                if (ipv4_tun_to_nlattr(skb, &output->tun_key, opts,
                                        swkey->tun_opts_len))