[ovs-dev] [megaflow v5 2/2] ovs-dpctl: Add mega flow support

Andy Zhou azhou at nicira.com
Mon Jun 17 14:51:01 UTC 2013


Added support to allow mega flow specified and displayed. ovs-dpctl tool
is mainly used as debugging tool.

This patch also implements the low level user space routines to send
and receive mega flow netlink messages. Those netlink suppor
routines are required for forthcoming user space mega flow patches.

Added a unit test to test parsing and display of mega flows.

Ethan contributed the ovs-dpctl mega flow output function.

Co-authored-by: Ethan Jackson <ethan at nicira.com>
Signed-off-by: Ethan Jackson <ethan at nicira.com>
Signed-off-by: Andy Zhou <azhou at nicira.com>

---
v1->v2
     Integrated Ethan's patch on ovs-dpctl mega flow output.
     Add Ethan as a co-author for this patch.

v2->v3
     Rebase to head to make review easier.
     ovs-dpctl: Add mask input for tunnel configurations.

v3->v4
     fix a typo

v4->v5
     Add ovs-dpctl unit test cases for parsing mega flow.
     Address Ben's review feedback.
---
 lib/dpif-linux.c       |   19 +
 lib/dpif-netdev.c      |    6 +
 lib/dpif-provider.h    |   12 +-
 lib/dpif.c             |   17 +-
 lib/dpif.h             |    4 +
 lib/netdev-dummy.c     |    2 +-
 lib/odp-util.c         |  978 +++++++++++++++++++++++++++++++++++++++++-------
 lib/odp-util.h         |    8 +-
 lib/packets.h          |    9 +
 ofproto/ofproto-dpif.c |   15 +-
 tests/odp.at           |   65 ++++
 tests/test-odp.c       |   79 ++--
 utilities/ovs-dpctl.c  |   25 +-
 13 files changed, 1039 insertions(+), 200 deletions(-)

diff --git a/lib/dpif-linux.c b/lib/dpif-linux.c
index 1383b58..9f38fc8 100644
--- a/lib/dpif-linux.c
+++ b/lib/dpif-linux.c
@@ -105,6 +105,8 @@ struct dpif_linux_flow {
      * the Netlink version of the command, even if actions_len is zero. */
     const struct nlattr *key;           /* OVS_FLOW_ATTR_KEY. */
     size_t key_len;
+    const struct nlattr *mask;          /* OVS_FLOW_ATTR_MASK. */
+    size_t mask_len;
     const struct nlattr *actions;       /* OVS_FLOW_ATTR_ACTIONS. */
     size_t actions_len;
     const struct ovs_flow_stats *stats; /* OVS_FLOW_ATTR_STATS. */
@@ -807,6 +809,8 @@ dpif_linux_init_flow_put(struct dpif *dpif_, const struct dpif_flow_put *put,
     request->dp_ifindex = dpif->dp_ifindex;
     request->key = put->key;
     request->key_len = put->key_len;
+    request->mask = put->mask;
+    request->mask_len = put->mask_len;
     /* Ensure that OVS_FLOW_ATTR_ACTIONS will always be included. */
     request->actions = (put->actions
                         ? put->actions
@@ -901,6 +905,7 @@ dpif_linux_flow_dump_start(const struct dpif *dpif_, void **statep)
 static int
 dpif_linux_flow_dump_next(const struct dpif *dpif_ OVS_UNUSED, void *state_,
                           const struct nlattr **key, size_t *key_len,
+                          const struct nlattr **mask, size_t *mask_len,
                           const struct nlattr **actions, size_t *actions_len,
                           const struct dpif_flow_stats **stats)
 {
@@ -941,6 +946,10 @@ dpif_linux_flow_dump_next(const struct dpif *dpif_ OVS_UNUSED, void *state_,
         *key = state->flow.key;
         *key_len = state->flow.key_len;
     }
+    if (mask) {
+        *mask = state->flow.mask;
+        *mask_len = state->flow.mask ? state->flow.mask_len: 0;
+    }
     if (stats) {
         dpif_linux_flow_get_stats(&state->flow, &state->stats);
         *stats = &state->stats;
@@ -1832,6 +1841,7 @@ dpif_linux_flow_from_ofpbuf(struct dpif_linux_flow *flow,
 {
     static const struct nl_policy ovs_flow_policy[] = {
         [OVS_FLOW_ATTR_KEY] = { .type = NL_A_NESTED },
+        [OVS_FLOW_ATTR_MASK] = { .type = NL_A_NESTED, .optional = true },
         [OVS_FLOW_ATTR_ACTIONS] = { .type = NL_A_NESTED, .optional = true },
         [OVS_FLOW_ATTR_STATS] = { NL_POLICY_FOR(struct ovs_flow_stats),
                                   .optional = true },
@@ -1863,6 +1873,11 @@ dpif_linux_flow_from_ofpbuf(struct dpif_linux_flow *flow,
     flow->dp_ifindex = ovs_header->dp_ifindex;
     flow->key = nl_attr_get(a[OVS_FLOW_ATTR_KEY]);
     flow->key_len = nl_attr_get_size(a[OVS_FLOW_ATTR_KEY]);
+
+    if (a[OVS_FLOW_ATTR_MASK]) {
+        flow->mask = nl_attr_get(a[OVS_FLOW_ATTR_MASK]);
+        flow->mask_len = nl_attr_get_size(a[OVS_FLOW_ATTR_MASK]);
+    }
     if (a[OVS_FLOW_ATTR_ACTIONS]) {
         flow->actions = nl_attr_get(a[OVS_FLOW_ATTR_ACTIONS]);
         flow->actions_len = nl_attr_get_size(a[OVS_FLOW_ATTR_ACTIONS]);
@@ -1898,6 +1913,10 @@ dpif_linux_flow_to_ofpbuf(const struct dpif_linux_flow *flow,
         nl_msg_put_unspec(buf, OVS_FLOW_ATTR_KEY, flow->key, flow->key_len);
     }
 
+    if (flow->mask_len) {
+        nl_msg_put_unspec(buf, OVS_FLOW_ATTR_MASK, flow->mask, flow->mask_len);
+    }
+
     if (flow->actions || flow->actions_len) {
         nl_msg_put_unspec(buf, OVS_FLOW_ATTR_ACTIONS,
                           flow->actions, flow->actions_len);
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 52aedb6..5a54627 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -879,6 +879,7 @@ dpif_netdev_flow_dump_start(const struct dpif *dpif OVS_UNUSED, void **statep)
 static int
 dpif_netdev_flow_dump_next(const struct dpif *dpif, void *state_,
                            const struct nlattr **key, size_t *key_len,
+                           const struct nlattr **mask, size_t *mask_len,
                            const struct nlattr **actions, size_t *actions_len,
                            const struct dpif_flow_stats **stats)
 {
@@ -904,6 +905,11 @@ dpif_netdev_flow_dump_next(const struct dpif *dpif, void *state_,
         *key_len = buf.size;
     }
 
+    if (mask) {
+        *mask = NULL;
+        *mask_len = 0;
+    }
+
     if (actions) {
         free(state->actions);
         state->actions = xmemdup(flow->actions, flow->actions_len);
diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h
index bea822f..84b5702 100644
--- a/lib/dpif-provider.h
+++ b/lib/dpif-provider.h
@@ -281,10 +281,13 @@ struct dpif_class {
      *
      * On success, if 'key' and 'key_len' are nonnull then '*key' and
      * '*key_len' must be set to Netlink attributes with types OVS_KEY_ATTR_*
-     * representing the dumped flow's key.  If 'actions' and 'actions_len' are
-     * nonnull then they should be set to Netlink attributes with types
-     * OVS_ACTION_ATTR_* representing the dumped flow's actions.  If 'stats'
-     * is nonnull then it should be set to the dumped flow's statistics.
+     * representing the dumped flow's key; if 'mask' and 'mask_len' are
+     * nonnull then '*mask' and '*mask_len' must be set to Netlink attributes
+     * with types of OVS_KEY_ATTR_* representing the dumped flow's mask.
+     * If 'actions' and 'actions_len' are nonnull then they should be set to
+     * Netlink attributes with types OVS_ACTION_ATTR_* representing the dumped
+     * flow's actions.  If 'stats' is nonnull then it should be set to the
+     * dumped flow's statistics.
      *
      * All of the returned data is owned by 'dpif', not by the caller, and the
      * caller must not modify or free it.  'dpif' must guarantee that it
@@ -292,6 +295,7 @@ struct dpif_class {
      * 'flow_dump_next' or 'flow_dump_done' for 'state'. */
     int (*flow_dump_next)(const struct dpif *dpif, void *state,
                           const struct nlattr **key, size_t *key_len,
+                          const struct nlattr **mask, size_t *mask_len,
                           const struct nlattr **actions, size_t *actions_len,
                           const struct dpif_flow_stats **stats);
 
diff --git a/lib/dpif.c b/lib/dpif.c
index 6aa52d5..370de3c 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -832,9 +832,11 @@ dpif_flow_put__(struct dpif *dpif, const struct dpif_flow_put *put)
 }
 
 /* Adds or modifies a flow in 'dpif'.  The flow is specified by the Netlink
- * attributes with types OVS_KEY_ATTR_* in the 'key_len' bytes starting at
- * 'key'.  The associated actions are specified by the Netlink attributes with
- * types OVS_ACTION_ATTR_* in the 'actions_len' bytes starting at 'actions'.
+ * attribute OVS_FLOW_ATTR_KEY with types OVS_KEY_ATTR_* in the 'key_len' bytes
+ * starting at 'key', and OVS_FLOW_ATTR_MASK with types of OVS_KEY_ATTR_* in the
+ * 'mask_len' bytes starting at 'mask'. The associated actions are specified by
+ * the Netlink attributes with types OVS_ACTION_ATTR_* in the 'actions_len'
+ * bytes starting at 'actions'.
  *
  * - If the flow's key does not exist in 'dpif', then the flow will be added if
  *   'flags' includes DPIF_FP_CREATE.  Otherwise the operation will fail with
@@ -854,6 +856,7 @@ dpif_flow_put__(struct dpif *dpif, const struct dpif_flow_put *put)
 int
 dpif_flow_put(struct dpif *dpif, enum dpif_flow_put_flags flags,
               const struct nlattr *key, size_t key_len,
+              const struct nlattr *mask, size_t mask_len,
               const struct nlattr *actions, size_t actions_len,
               struct dpif_flow_stats *stats)
 {
@@ -862,6 +865,8 @@ dpif_flow_put(struct dpif *dpif, enum dpif_flow_put_flags flags,
     put.flags = flags;
     put.key = key;
     put.key_len = key_len;
+    put.mask = mask;
+    put.mask_len = mask_len;
     put.actions = actions;
     put.actions_len = actions_len;
     put.stats = stats;
@@ -937,6 +942,7 @@ dpif_flow_dump_start(struct dpif_flow_dump *dump, const struct dpif *dpif)
 bool
 dpif_flow_dump_next(struct dpif_flow_dump *dump,
                     const struct nlattr **key, size_t *key_len,
+                    const struct nlattr **mask, size_t *mask_len,
                     const struct nlattr **actions, size_t *actions_len,
                     const struct dpif_flow_stats **stats)
 {
@@ -946,6 +952,7 @@ dpif_flow_dump_next(struct dpif_flow_dump *dump,
     if (!error) {
         error = dpif->dpif_class->flow_dump_next(dpif, dump->state,
                                                  key, key_len,
+                                                 mask, mask_len,
                                                  actions, actions_len,
                                                  stats);
         if (error) {
@@ -957,6 +964,10 @@ dpif_flow_dump_next(struct dpif_flow_dump *dump,
             *key = NULL;
             *key_len = 0;
         }
+        if (mask) {
+            *mask = NULL;
+            *mask_len = 0;
+        }
         if (actions) {
             *actions = NULL;
             *actions_len = 0;
diff --git a/lib/dpif.h b/lib/dpif.h
index fd05b2f..11d7445 100644
--- a/lib/dpif.h
+++ b/lib/dpif.h
@@ -447,6 +447,7 @@ enum dpif_flow_put_flags {
 int dpif_flow_flush(struct dpif *);
 int dpif_flow_put(struct dpif *, enum dpif_flow_put_flags,
                   const struct nlattr *key, size_t key_len,
+                  const struct nlattr *mask, size_t mask_len,
                   const struct nlattr *actions, size_t actions_len,
                   struct dpif_flow_stats *);
 int dpif_flow_del(struct dpif *,
@@ -464,6 +465,7 @@ struct dpif_flow_dump {
 void dpif_flow_dump_start(struct dpif_flow_dump *, const struct dpif *);
 bool dpif_flow_dump_next(struct dpif_flow_dump *,
                          const struct nlattr **key, size_t *key_len,
+                         const struct nlattr **mask, size_t *mask_len,
                          const struct nlattr **actions, size_t *actions_len,
                          const struct dpif_flow_stats **);
 int dpif_flow_dump_done(struct dpif_flow_dump *);
@@ -492,6 +494,8 @@ struct dpif_flow_put {
     enum dpif_flow_put_flags flags; /* DPIF_FP_*. */
     const struct nlattr *key;       /* Flow to put. */
     size_t key_len;                 /* Length of 'key' in bytes. */
+    const struct nlattr *mask;      /* Mask to put. */
+    size_t mask_len;                /* Length of 'mask' in bytes. */
     const struct nlattr *actions;   /* Actions to perform on flow. */
     size_t actions_len;             /* Length of 'actions' in bytes. */
 
diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
index 3e2187e..6f0e8f8 100644
--- a/lib/netdev-dummy.c
+++ b/lib/netdev-dummy.c
@@ -623,7 +623,7 @@ eth_from_packet_or_flow(const char *s)
      * settle for parsing a datapath key for now.
      */
     ofpbuf_init(&odp_key, 0);
-    error = odp_flow_key_from_string(s, NULL, &odp_key);
+    error = odp_flow_from_string(s, NULL, &odp_key, NULL);
     if (error) {
         ofpbuf_uninit(&odp_key);
         return NULL;
diff --git a/lib/odp-util.c b/lib/odp-util.c
index acd1a9d..a126148 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -48,9 +48,10 @@ VLOG_DEFINE_THIS_MODULE(odp_util);
  * from another. */
 static const char *delimiters = ", \t\r\n";
 
-static int parse_odp_key_attr(const char *, const struct simap *port_names,
-                              struct ofpbuf *);
-static void format_odp_key_attr(const struct nlattr *a, struct ds *ds);
+static int parse_odp_key_mask_attr(const char *, const struct simap *port_names,
+                              struct ofpbuf *, struct ofpbuf *);
+static void format_odp_key_attr(const struct nlattr *a,
+                                const struct nlattr *ma, struct ds *ds);
 
 /* Returns one the following for the action with the given OVS_ACTION_ATTR_*
  * 'type':
@@ -355,6 +356,25 @@ format_mpls_lse(struct ds *ds, ovs_be32 mpls_lse)
 }
 
 static void
+format_mpls(struct ds *ds, const struct ovs_key_mpls *mpls_key,
+            const struct ovs_key_mpls *mpls_mask)
+{
+    ovs_be32 key = mpls_key->mpls_lse;
+
+    if (mpls_mask == NULL) {
+        format_mpls_lse(ds, key);
+    }else {
+        ovs_be32 mask = mpls_mask->mpls_lse;
+
+        ds_put_format(ds, "label=%"PRIu32"/0x%x,tc=%d/%x,ttl=%d/0x%x,bos=%d/%x",
+                  mpls_lse_to_label(key), mpls_lse_to_label(mask),
+                  mpls_lse_to_tc(key), mpls_lse_to_tc(mask),
+                  mpls_lse_to_ttl(key), mpls_lse_to_ttl(mask),
+                  mpls_lse_to_bos(key), mpls_lse_to_bos(mask));
+    }
+}
+
+static void
 format_odp_action(struct ds *ds, const struct nlattr *a)
 {
     int expected_len;
@@ -378,7 +398,7 @@ format_odp_action(struct ds *ds, const struct nlattr *a)
         break;
     case OVS_ACTION_ATTR_SET:
         ds_put_cstr(ds, "set(");
-        format_odp_key_attr(nl_attr_get(a), ds);
+        format_odp_key_attr(nl_attr_get(a), NULL, ds);
         ds_put_cstr(ds, ")");
         break;
     case OVS_ACTION_ATTR_PUSH_VLAN:
@@ -580,7 +600,7 @@ parse_odp_action(const char *s, const struct simap *port_names,
         int retval;
 
         start_ofs = nl_msg_start_nested(actions, OVS_ACTION_ATTR_SET);
-        retval = parse_odp_key_attr(s + 4, port_names, actions);
+        retval = parse_odp_key_mask_attr(s + 4, port_names, actions, NULL);
         if (retval < 0) {
             return retval;
         }
@@ -747,10 +767,11 @@ format_generic_odp_key(const struct nlattr *a, struct ds *ds)
 
         unspec = nl_attr_get(a);
         for (i = 0; i < len; i++) {
-            ds_put_char(ds, i ? ' ': '(');
+            if (i) {
+                ds_put_char(ds, ' ');
+            }
             ds_put_format(ds, "%02x", unspec[i]);
         }
-        ds_put_char(ds, ')');
     }
 }
 
@@ -876,174 +897,398 @@ tun_key_to_attr(struct ofpbuf *a, const struct flow_tnl *tun_key)
 }
 
 static void
-format_odp_key_attr(const struct nlattr *a, struct ds *ds)
+format_odp_key_attr(const struct nlattr *a, const struct nlattr *ma,
+                    struct ds *ds)
 {
-    const struct ovs_key_ethernet *eth_key;
-    const struct ovs_key_ipv4 *ipv4_key;
-    const struct ovs_key_ipv6 *ipv6_key;
-    const struct ovs_key_tcp *tcp_key;
-    const struct ovs_key_udp *udp_key;
-    const struct ovs_key_icmp *icmp_key;
-    const struct ovs_key_icmpv6 *icmpv6_key;
-    const struct ovs_key_arp *arp_key;
-    const struct ovs_key_nd *nd_key;
     struct flow_tnl tun_key;
     enum ovs_key_attr attr = nl_attr_type(a);
     char namebuf[OVS_KEY_ATTR_BUFSIZE];
     int expected_len;
 
+    if (ma) {
+        bool is_exact;
+
+        if (attr == OVS_KEY_ATTR_TUNNEL) {
+            /* XXX this is a hack for now. Should change
+             * the exact match dection to per field
+             * instead of per attribute.
+             */
+            struct flow_tnl tun_mask;
+            memset(&tun_mask, 0, sizeof tun_mask);
+            odp_tun_key_from_attr(ma, &tun_mask);
+            if (tun_mask.flags == (FLOW_TNL_F_KEY
+                                   | FLOW_TNL_F_DONT_FRAGMENT
+                                   | FLOW_TNL_F_CSUM)) {
+                /* The flags are exact match, check the remaining fields. */
+                tun_mask.flags = 0xffff;
+                is_exact = is_all_ones((uint8_t *)&tun_mask,
+                                      offsetof(struct flow_tnl, ip_ttl));
+            }
+        } else {
+            is_exact = is_all_ones(nl_attr_get(ma), nl_attr_get_size(ma));
+        }
+        ma = is_exact ? NULL : ma;
+    }
+
     ds_put_cstr(ds, ovs_key_attr_to_string(attr, namebuf, sizeof namebuf));
-    expected_len = odp_flow_key_attr_len(nl_attr_type(a));
-    if (expected_len != -2 && nl_attr_get_size(a) != expected_len) {
-        ds_put_format(ds, "(bad length %zu, expected %d)",
+
+    {
+        bool bad_key_len, bad_mask_len;
+        expected_len = odp_flow_key_attr_len(nl_attr_type(a));
+        bad_key_len = (expected_len != -2 && nl_attr_get_size(a) != expected_len);
+
+        if (ma) {
+            expected_len = odp_flow_key_attr_len(nl_attr_type(ma));
+            bad_mask_len = (expected_len != -2 && nl_attr_get_size(ma) != expected_len);
+        } else {
+            bad_mask_len = false;
+        }
+
+        if (bad_key_len || bad_mask_len) {
+            if (bad_key_len) {
+                ds_put_format(ds, "(bad key length %zu, expected %d)(",
                       nl_attr_get_size(a),
                       odp_flow_key_attr_len(nl_attr_type(a)));
-        format_generic_odp_key(a, ds);
-        return;
+            }
+            format_generic_odp_key(a, ds);
+            if (ma) {
+                ds_put_char(ds, '/');
+                if (bad_mask_len) {
+                    ds_put_format(ds, "(bad mask length %zu, expected %d)(",
+                                  nl_attr_get_size(ma),
+                                  odp_flow_key_attr_len(nl_attr_type(ma)));
+                }
+                format_generic_odp_key(ma, ds);
+            }
+
+            ds_put_char(ds, ')');
+            return;
+        }
     }
 
+    ds_put_char(ds, '(');
     switch (attr) {
     case OVS_KEY_ATTR_ENCAP:
-        ds_put_cstr(ds, "(");
-        if (nl_attr_get_size(a)) {
-            odp_flow_key_format(nl_attr_get(a), nl_attr_get_size(a), ds);
+        if (ma && nl_attr_get_size(ma) && nl_attr_get_size(a)) {
+            odp_flow_format(nl_attr_get(a), nl_attr_get_size(a),
+                            nl_attr_get(ma), nl_attr_get_size(ma), ds);
+        } else if (nl_attr_get_size(a)) {
+            odp_flow_format(nl_attr_get(a), nl_attr_get_size(a), NULL, 0, ds);
         }
-        ds_put_char(ds, ')');
         break;
 
     case OVS_KEY_ATTR_PRIORITY:
-        ds_put_format(ds, "(%#"PRIx32")", nl_attr_get_u32(a));
-        break;
-
     case OVS_KEY_ATTR_SKB_MARK:
-        ds_put_format(ds, "(%#"PRIx32")", nl_attr_get_u32(a));
+        ds_put_format(ds, "%#"PRIx32, nl_attr_get_u32(a));
+        if (ma) {
+            ds_put_format(ds, "/%#"PRIx32, nl_attr_get_u32(ma));
+        }
         break;
 
     case OVS_KEY_ATTR_TUNNEL:
         memset(&tun_key, 0, sizeof tun_key);
         if (odp_tun_key_from_attr(a, &tun_key) == ODP_FIT_ERROR) {
-            ds_put_format(ds, "(error)");
+            ds_put_format(ds, "error");
+        } else if (ma) {
+            struct flow_tnl tun_mask;
+
+            memset(&tun_mask, 0, sizeof tun_mask);
+            odp_tun_key_from_attr(ma, &tun_mask);
+            ds_put_format(ds, "tun_id=%#"PRIx64"/%#"PRIx64
+                          ",src="IP_FMT"/"IP_FMT",dst="IP_FMT"/"IP_FMT
+                          ",tos=%#"PRIx8"/%#"PRIx8",ttl=%"PRIu8"/%#"PRIx8
+                          ",flags(",
+                          ntohll(tun_key.tun_id), ntohll(tun_mask.tun_id),
+                          IP_ARGS(tun_key.ip_src), IP_ARGS(tun_mask.ip_src),
+                          IP_ARGS(tun_key.ip_dst), IP_ARGS(tun_mask.ip_dst),
+                          tun_key.ip_tos, tun_mask.ip_tos,
+                          tun_key.ip_ttl, tun_mask.ip_ttl);
+
+            format_flags(ds, flow_tun_flag_to_string, tun_key.flags, ',');
+
+            /* XXX This code is correct, but enabling it would break the unit
+               test. Disable it for now until the input parser is fixed.
+
+                ds_put_char(ds, '/');
+                format_flags(ds, flow_tun_flag_to_string, tun_mask.flags, ',');
+            */
+            ds_put_char(ds, ')');
         } else {
-            ds_put_format(ds, "(tun_id=0x%"PRIx64",src="IP_FMT",dst="IP_FMT","
+            ds_put_format(ds, "tun_id=0x%"PRIx64",src="IP_FMT",dst="IP_FMT","
                           "tos=0x%"PRIx8",ttl=%"PRIu8",flags(",
                           ntohll(tun_key.tun_id),
                           IP_ARGS(tun_key.ip_src),
                           IP_ARGS(tun_key.ip_dst),
                           tun_key.ip_tos, tun_key.ip_ttl);
 
-            format_flags(ds, flow_tun_flag_to_string,
-                         (uint32_t) tun_key.flags, ',');
-            ds_put_format(ds, "))");
+            format_flags(ds, flow_tun_flag_to_string, tun_key.flags, ',');
+            ds_put_char(ds, ')');
         }
         break;
 
     case OVS_KEY_ATTR_IN_PORT:
-        ds_put_format(ds, "(%"PRIu32")", nl_attr_get_u32(a));
+        ds_put_format(ds, "%"PRIu32, nl_attr_get_u32(a));
+        if (ma) {
+            ds_put_format(ds, "/%#"PRIx32, nl_attr_get_u32(ma));
+        }
         break;
 
     case OVS_KEY_ATTR_ETHERNET:
-        eth_key = nl_attr_get(a);
-        ds_put_format(ds, "(src="ETH_ADDR_FMT",dst="ETH_ADDR_FMT")",
-                      ETH_ADDR_ARGS(eth_key->eth_src),
-                      ETH_ADDR_ARGS(eth_key->eth_dst));
+        if (ma) {
+            const struct ovs_key_ethernet *eth_mask = nl_attr_get(ma);
+            const struct ovs_key_ethernet *eth_key = nl_attr_get(a);
+
+            ds_put_format(ds, "src="ETH_ADDR_FMT"/"ETH_ADDR_FMT
+                          ",dst="ETH_ADDR_FMT"/"ETH_ADDR_FMT,
+                          ETH_ADDR_ARGS(eth_key->eth_src),
+                          ETH_ADDR_ARGS(eth_mask->eth_src),
+                          ETH_ADDR_ARGS(eth_key->eth_dst),
+                          ETH_ADDR_ARGS(eth_mask->eth_dst));
+        } else {
+            const struct ovs_key_ethernet *eth_key = nl_attr_get(a);
+
+            ds_put_format(ds, "src="ETH_ADDR_FMT",dst="ETH_ADDR_FMT,
+                          ETH_ADDR_ARGS(eth_key->eth_src),
+                          ETH_ADDR_ARGS(eth_key->eth_dst));
+        }
         break;
 
     case OVS_KEY_ATTR_VLAN:
-        ds_put_char(ds, '(');
-        format_vlan_tci(ds, nl_attr_get_be16(a));
-        ds_put_char(ds, ')');
+        {
+            ovs_be16 vlan_tci = nl_attr_get_be16(a);
+            if (ma) {
+                ovs_be16 mask = nl_attr_get_be16(ma);
+                ds_put_format(ds, "vid=%"PRIu16"/%"PRIx16",pcp=%d/0x%x,cfi=%d/%d",
+                              vlan_tci_to_vid(vlan_tci),
+                              vlan_tci_to_vid(mask),
+                              vlan_tci_to_pcp(vlan_tci),
+                              vlan_tci_to_vid(mask),
+                              vlan_tci_to_cfi(vlan_tci),
+                              vlan_tci_to_cfi(mask));
+            } else {
+                format_vlan_tci(ds, vlan_tci);
+            }
+        }
         break;
 
     case OVS_KEY_ATTR_MPLS: {
         const struct ovs_key_mpls *mpls_key = nl_attr_get(a);
-        ds_put_char(ds, '(');
-        format_mpls_lse(ds, mpls_key->mpls_lse);
-        ds_put_char(ds, ')');
+        const struct ovs_key_mpls *mpls_mask = NULL;
+        if (ma) {
+            mpls_mask = nl_attr_get(ma);
+        }
+        format_mpls(ds, mpls_key, mpls_mask);
         break;
     }
 
     case OVS_KEY_ATTR_ETHERTYPE:
-        ds_put_format(ds, "(0x%04"PRIx16")",
-                      ntohs(nl_attr_get_be16(a)));
+        ds_put_format(ds, "0x%04"PRIx16, ntohs(nl_attr_get_be16(a)));
+        if (ma) {
+            ds_put_format(ds, "/0x%04"PRIx16, ntohs(nl_attr_get_be16(ma)));
+        }
         break;
 
     case OVS_KEY_ATTR_IPV4:
-        ipv4_key = nl_attr_get(a);
-        ds_put_format(ds, "(src="IP_FMT",dst="IP_FMT",proto=%"PRIu8
-                      ",tos=%#"PRIx8",ttl=%"PRIu8",frag=%s)",
-                      IP_ARGS(ipv4_key->ipv4_src),
-                      IP_ARGS(ipv4_key->ipv4_dst),
-                      ipv4_key->ipv4_proto, ipv4_key->ipv4_tos,
-                      ipv4_key->ipv4_ttl,
-                      ovs_frag_type_to_string(ipv4_key->ipv4_frag));
+        if (ma) {
+            const struct ovs_key_ipv4 *ipv4_key = nl_attr_get(a);
+            const struct ovs_key_ipv4 *ipv4_mask = nl_attr_get(ma);
+
+            ds_put_format(ds, "src="IP_FMT"/"IP_FMT",dst="IP_FMT"/"IP_FMT
+                          ",proto=%"PRIu8"/%#"PRIx8",tos=%#"PRIx8"/%#"PRIx8
+                          ",ttl=%"PRIu8"/%#"PRIx8",frag=%s/%#"PRIx8,
+                          IP_ARGS(ipv4_key->ipv4_src),
+                          IP_ARGS(ipv4_mask->ipv4_src),
+                          IP_ARGS(ipv4_key->ipv4_dst),
+                          IP_ARGS(ipv4_mask->ipv4_dst),
+                          ipv4_key->ipv4_proto, ipv4_mask->ipv4_proto,
+                          ipv4_key->ipv4_tos, ipv4_mask->ipv4_tos,
+                          ipv4_key->ipv4_ttl, ipv4_mask->ipv4_ttl,
+                          ovs_frag_type_to_string(ipv4_key->ipv4_frag),
+                          ipv4_mask->ipv4_frag);
+        } else {
+            const struct ovs_key_ipv4 *ipv4_key = nl_attr_get(a);
+
+            ds_put_format(ds, "src="IP_FMT",dst="IP_FMT",proto=%"PRIu8
+                          ",tos=%#"PRIx8",ttl=%"PRIu8",frag=%s",
+                          IP_ARGS(ipv4_key->ipv4_src),
+                          IP_ARGS(ipv4_key->ipv4_dst),
+                          ipv4_key->ipv4_proto, ipv4_key->ipv4_tos,
+                          ipv4_key->ipv4_ttl,
+                          ovs_frag_type_to_string(ipv4_key->ipv4_frag));
+        }
         break;
 
-    case OVS_KEY_ATTR_IPV6: {
-        char src_str[INET6_ADDRSTRLEN];
-        char dst_str[INET6_ADDRSTRLEN];
+    case OVS_KEY_ATTR_IPV6:
+        if (ma) {
+            const struct ovs_key_ipv6 *ipv6_key, *ipv6_mask;
+            char src_str[INET6_ADDRSTRLEN];
+            char dst_str[INET6_ADDRSTRLEN];
+            char src_mask[INET6_ADDRSTRLEN];
+            char dst_mask[INET6_ADDRSTRLEN];
+
+            ipv6_key = nl_attr_get(a);
+            inet_ntop(AF_INET6, ipv6_key->ipv6_src, src_str, sizeof src_str);
+            inet_ntop(AF_INET6, ipv6_key->ipv6_dst, dst_str, sizeof dst_str);
+
+            ipv6_mask = nl_attr_get(ma);
+            inet_ntop(AF_INET6, ipv6_mask->ipv6_src, src_mask, sizeof src_mask);
+            inet_ntop(AF_INET6, ipv6_mask->ipv6_dst, dst_mask, sizeof dst_mask);
+
+            ds_put_format(ds, "src=%s/%s,dst=%s/%s,label=%#"PRIx32"/%#"PRIx32
+                          ",proto=%"PRIu8"/%#"PRIx8",tclass=%#"PRIx8"/%#"PRIx8
+                          ",hlimit=%"PRIu8"/%#"PRIx8",frag=%s/%#"PRIx8,
+                          src_str, src_mask, dst_str, dst_mask,
+                          ntohl(ipv6_key->ipv6_label),
+                          ntohl(ipv6_mask->ipv6_label),
+                          ipv6_key->ipv6_proto, ipv6_mask->ipv6_proto,
+                          ipv6_key->ipv6_tclass, ipv6_mask->ipv6_tclass,
+                          ipv6_key->ipv6_hlimit, ipv6_mask->ipv6_hlimit,
+                          ovs_frag_type_to_string(ipv6_key->ipv6_frag),
+                          ipv6_mask->ipv6_frag);
+        } else {
+            const struct ovs_key_ipv6 *ipv6_key;
+            char src_str[INET6_ADDRSTRLEN];
+            char dst_str[INET6_ADDRSTRLEN];
 
-        ipv6_key = nl_attr_get(a);
-        inet_ntop(AF_INET6, ipv6_key->ipv6_src, src_str, sizeof src_str);
-        inet_ntop(AF_INET6, ipv6_key->ipv6_dst, dst_str, sizeof dst_str);
+            ipv6_key = nl_attr_get(a);
+            inet_ntop(AF_INET6, ipv6_key->ipv6_src, src_str, sizeof src_str);
+            inet_ntop(AF_INET6, ipv6_key->ipv6_dst, dst_str, sizeof dst_str);
 
-        ds_put_format(ds, "(src=%s,dst=%s,label=%#"PRIx32",proto=%"PRIu8
-                      ",tclass=%#"PRIx8",hlimit=%"PRIu8",frag=%s)",
-                      src_str, dst_str, ntohl(ipv6_key->ipv6_label),
-                      ipv6_key->ipv6_proto, ipv6_key->ipv6_tclass,
-                      ipv6_key->ipv6_hlimit,
-                      ovs_frag_type_to_string(ipv6_key->ipv6_frag));
+            ds_put_format(ds, "src=%s,dst=%s,label=%#"PRIx32",proto=%"PRIu8
+                          ",tclass=%#"PRIx8",hlimit=%"PRIu8",frag=%s",
+                          src_str, dst_str, ntohl(ipv6_key->ipv6_label),
+                          ipv6_key->ipv6_proto, ipv6_key->ipv6_tclass,
+                          ipv6_key->ipv6_hlimit,
+                          ovs_frag_type_to_string(ipv6_key->ipv6_frag));
+        }
         break;
-    }
 
     case OVS_KEY_ATTR_TCP:
-        tcp_key = nl_attr_get(a);
-        ds_put_format(ds, "(src=%"PRIu16",dst=%"PRIu16")",
-                      ntohs(tcp_key->tcp_src), ntohs(tcp_key->tcp_dst));
+        if (ma) {
+            const struct ovs_key_tcp *tcp_mask = nl_attr_get(ma);
+            const struct ovs_key_tcp *tcp_key = nl_attr_get(a);
+
+            ds_put_format(ds, "src=%"PRIu16"/%#"PRIx16
+                          ",dst=%"PRIu16"/%#"PRIx16,
+                          ntohs(tcp_key->tcp_src), ntohs(tcp_mask->tcp_src),
+                          ntohs(tcp_key->tcp_dst), ntohs(tcp_mask->tcp_dst));
+        } else {
+            const struct ovs_key_tcp *tcp_key = nl_attr_get(a);
+
+            ds_put_format(ds, "src=%"PRIu16",dst=%"PRIu16,
+                          ntohs(tcp_key->tcp_src), ntohs(tcp_key->tcp_dst));
+        }
         break;
 
     case OVS_KEY_ATTR_UDP:
-        udp_key = nl_attr_get(a);
-        ds_put_format(ds, "(src=%"PRIu16",dst=%"PRIu16")",
-                      ntohs(udp_key->udp_src), ntohs(udp_key->udp_dst));
+        if (ma) {
+            const struct ovs_key_udp *udp_mask = nl_attr_get(ma);
+            const struct ovs_key_udp *udp_key = nl_attr_get(a);
+
+            ds_put_format(ds, "src=%"PRIu16"/%#"PRIx16
+                          ",dst=%"PRIu16"/%#"PRIx16,
+                          ntohs(udp_key->udp_src), ntohs(udp_mask->udp_src),
+                          ntohs(udp_key->udp_dst), ntohs(udp_mask->udp_dst));
+        } else {
+            const struct ovs_key_udp *udp_key = nl_attr_get(a);
+
+            ds_put_format(ds, "src=%"PRIu16",dst=%"PRIu16,
+                          ntohs(udp_key->udp_src), ntohs(udp_key->udp_dst));
+        }
         break;
 
     case OVS_KEY_ATTR_ICMP:
-        icmp_key = nl_attr_get(a);
-        ds_put_format(ds, "(type=%"PRIu8",code=%"PRIu8")",
-                      icmp_key->icmp_type, icmp_key->icmp_code);
+        if (ma) {
+            const struct ovs_key_icmp *icmp_mask = nl_attr_get(ma);
+            const struct ovs_key_icmp *icmp_key = nl_attr_get(a);
+
+            ds_put_format(ds, "type=%"PRIu8"/%#"PRIx8",code=%"PRIu8"/%#"PRIx8,
+                          icmp_key->icmp_type, icmp_mask->icmp_type,
+                          icmp_key->icmp_code, icmp_mask->icmp_code);
+        } else {
+            const struct ovs_key_icmp *icmp_key = nl_attr_get(a);
+
+            ds_put_format(ds, "type=%"PRIu8",code=%"PRIu8,
+                          icmp_key->icmp_type, icmp_key->icmp_code);
+        }
         break;
 
     case OVS_KEY_ATTR_ICMPV6:
-        icmpv6_key = nl_attr_get(a);
-        ds_put_format(ds, "(type=%"PRIu8",code=%"PRIu8")",
-                      icmpv6_key->icmpv6_type, icmpv6_key->icmpv6_code);
+        if (ma) {
+            const struct ovs_key_icmpv6 *icmpv6_mask = nl_attr_get(ma);
+            const struct ovs_key_icmpv6 *icmpv6_key = nl_attr_get(a);
+
+            ds_put_format(ds, "type=%"PRIu8"/%#"PRIx8",code=%"PRIu8"/%#"PRIx8,
+                          icmpv6_key->icmpv6_type, icmpv6_mask->icmpv6_type,
+                          icmpv6_key->icmpv6_code, icmpv6_mask->icmpv6_code);
+        } else {
+            const struct ovs_key_icmpv6 *icmpv6_key = nl_attr_get(a);
+
+            ds_put_format(ds, "type=%"PRIu8",code=%"PRIu8,
+                          icmpv6_key->icmpv6_type, icmpv6_key->icmpv6_code);
+        }
         break;
 
     case OVS_KEY_ATTR_ARP:
-        arp_key = nl_attr_get(a);
-        ds_put_format(ds, "(sip="IP_FMT",tip="IP_FMT",op=%"PRIu16","
-                      "sha="ETH_ADDR_FMT",tha="ETH_ADDR_FMT")",
-                      IP_ARGS(arp_key->arp_sip), IP_ARGS(arp_key->arp_tip),
-                      ntohs(arp_key->arp_op), ETH_ADDR_ARGS(arp_key->arp_sha),
-                      ETH_ADDR_ARGS(arp_key->arp_tha));
+        if (ma) {
+            const struct ovs_key_arp *arp_mask = nl_attr_get(ma);
+            const struct ovs_key_arp *arp_key = nl_attr_get(a);
+
+            ds_put_format(ds, "sip="IP_FMT"/"IP_FMT",tip="IP_FMT"/"IP_FMT
+                          ",op=%"PRIu16"/%#"PRIx16
+                          ",sha="ETH_ADDR_FMT"/"ETH_ADDR_FMT
+                          ",tha="ETH_ADDR_FMT"/"ETH_ADDR_FMT,
+                          IP_ARGS(arp_key->arp_sip),
+                          IP_ARGS(arp_mask->arp_sip),
+                          IP_ARGS(arp_key->arp_tip),
+                          IP_ARGS(arp_mask->arp_tip),
+                          ntohs(arp_key->arp_op), ntohs(arp_mask->arp_op),
+                          ETH_ADDR_ARGS(arp_key->arp_sha),
+                          ETH_ADDR_ARGS(arp_mask->arp_sha),
+                          ETH_ADDR_ARGS(arp_key->arp_tha),
+                          ETH_ADDR_ARGS(arp_mask->arp_tha));
+        } else {
+            const struct ovs_key_arp *arp_key = nl_attr_get(a);
+
+            ds_put_format(ds, "sip="IP_FMT",tip="IP_FMT",op=%"PRIu16","
+                          "sha="ETH_ADDR_FMT",tha="ETH_ADDR_FMT,
+                          IP_ARGS(arp_key->arp_sip), IP_ARGS(arp_key->arp_tip),
+                          ntohs(arp_key->arp_op),
+                          ETH_ADDR_ARGS(arp_key->arp_sha),
+                          ETH_ADDR_ARGS(arp_key->arp_tha));
+        }
         break;
 
     case OVS_KEY_ATTR_ND: {
+        const struct ovs_key_nd *nd_key, *nd_mask;
         char target[INET6_ADDRSTRLEN];
 
         nd_key = nl_attr_get(a);
+        nd_mask = ma ? nl_attr_get(ma) : NULL;
+
         inet_ntop(AF_INET6, nd_key->nd_target, target, sizeof target);
+        ds_put_format(ds, "target=%s", target);
+        if (nd_mask) {
+            inet_ntop(AF_INET6, nd_mask->nd_target, target, sizeof target);
+            ds_put_format(ds, "/%s", target);
+        }
 
-        ds_put_format(ds, "(target=%s", target);
         if (!eth_addr_is_zero(nd_key->nd_sll)) {
             ds_put_format(ds, ",sll="ETH_ADDR_FMT,
                           ETH_ADDR_ARGS(nd_key->nd_sll));
+            if (nd_mask) {
+                ds_put_format(ds, "/"ETH_ADDR_FMT,
+                              ETH_ADDR_ARGS(nd_mask->nd_sll));
+            }
         }
         if (!eth_addr_is_zero(nd_key->nd_tll)) {
             ds_put_format(ds, ",tll="ETH_ADDR_FMT,
                           ETH_ADDR_ARGS(nd_key->nd_tll));
+            if (nd_mask) {
+                ds_put_format(ds, "/"ETH_ADDR_FMT,
+                              ETH_ADDR_ARGS(nd_mask->nd_tll));
+            }
         }
-        ds_put_char(ds, ')');
         break;
     }
 
@@ -1051,24 +1296,39 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
     case __OVS_KEY_ATTR_MAX:
     default:
         format_generic_odp_key(a, ds);
+        if (ma) {
+            ds_put_char(ds, '/');
+            format_generic_odp_key(ma, ds);
+        }
         break;
     }
+    ds_put_char(ds, ')');
 }
 
 /* Appends to 'ds' a string representation of the 'key_len' bytes of
- * OVS_KEY_ATTR_* attributes in 'key'. */
+ * OVS_KEY_ATTR_* attributes in 'key'. If non-null, additionally formats the
+ * 'mask_len' bytes of 'mask' which apply to 'key'. */
 void
-odp_flow_key_format(const struct nlattr *key, size_t key_len, struct ds *ds)
+odp_flow_format(const struct nlattr *key, size_t key_len,
+                const struct nlattr *mask, size_t mask_len,
+                struct ds *ds)
 {
     if (key_len) {
         const struct nlattr *a;
         unsigned int left;
 
         NL_ATTR_FOR_EACH (a, left, key, key_len) {
+            const struct nlattr *ma = NULL;
+
             if (a != key) {
                 ds_put_char(ds, ',');
             }
-            format_odp_key_attr(a, ds);
+
+            if (mask && mask_len) {
+                ma = nl_attr_find__(mask, mask_len, nl_attr_type(a));
+            }
+
+            format_odp_key_attr(a, ma, ds);
         }
         if (left) {
             int i;
@@ -1087,6 +1347,15 @@ odp_flow_key_format(const struct nlattr *key, size_t key_len, struct ds *ds)
     }
 }
 
+/* Appends to 'ds' a string representation of the 'key_len' bytes of
+ * OVS_KEY_ATTR_* attributes in 'key'. */
+void
+odp_flow_key_format(const struct nlattr *key,
+                    size_t key_len, struct ds *ds)
+{
+    odp_flow_format(key, key_len, NULL, 0, ds);
+}
+
 static int
 put_nd_key(int n, const char *nd_target_s,
            const uint8_t *nd_sll, const uint8_t *nd_tll, struct ofpbuf *key)
@@ -1132,8 +1401,8 @@ mpls_lse_from_components(int mpls_label, int mpls_tc, int mpls_ttl, int mpls_bos
 }
 
 static int
-parse_odp_key_attr(const char *s, const struct simap *port_names,
-                   struct ofpbuf *key)
+parse_odp_key_mask_attr(const char *s, const struct simap *port_names,
+                        struct ofpbuf *key, struct ofpbuf *mask)
 {
     /* Many of the sscanf calls in this function use oversized destination
      * fields because some sscanf() implementations truncate the range of %i
@@ -1147,31 +1416,87 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
 
     {
         unsigned long long int priority;
+        unsigned long long int priority_mask;
         int n = -1;
 
-        if (sscanf(s, "skb_priority(%llx)%n", &priority, &n) > 0 && n > 0) {
+        if (mask && sscanf(s, "skb_priority(%lli/%lli)%n", &priority,
+                   &priority_mask, &n) > 0 && n > 0) {
+            nl_msg_put_u32(key, OVS_KEY_ATTR_PRIORITY, priority);
+            nl_msg_put_u32(mask, OVS_KEY_ATTR_PRIORITY, priority_mask);
+            return n;
+        } else if (sscanf(s, "skb_priority(%lli)%n",
+                          &priority, &n) > 0 && n > 0) {
             nl_msg_put_u32(key, OVS_KEY_ATTR_PRIORITY, priority);
+            if (mask) {
+                nl_msg_put_u32(mask, OVS_KEY_ATTR_PRIORITY, UINT32_MAX);
+            }
             return n;
         }
     }
 
     {
         unsigned long long int mark;
+        unsigned long long int mark_mask;
         int n = -1;
 
-        if (sscanf(s, "skb_mark(%llx)%n", &mark, &n) > 0 && n > 0) {
+        if (mask && sscanf(s, "skb_mark(%lli/%lli)%n", &mark,
+                   &mark_mask, &n) > 0 && n > 0) {
+            nl_msg_put_u32(key, OVS_KEY_ATTR_SKB_MARK, mark);
+            nl_msg_put_u32(mask, OVS_KEY_ATTR_SKB_MARK, mark_mask);
+            return n;
+        } else if (sscanf(s, "skb_mark(%lli)%n", &mark, &n) > 0 && n > 0) {
             nl_msg_put_u32(key, OVS_KEY_ATTR_SKB_MARK, mark);
+            if (mask) {
+                nl_msg_put_u32(mask, OVS_KEY_ATTR_SKB_MARK, UINT32_MAX);
+            }
             return n;
         }
     }
 
     {
         char tun_id_s[32];
-        int tos, ttl;
-        struct flow_tnl tun_key;
+        int tos, tos_mask, ttl, ttl_mask;
+        struct flow_tnl tun_key, tun_key_mask;
+        unsigned long long tun_id_mask;
         int n = -1;
 
-        if (sscanf(s, "tunnel(tun_id=%31[x0123456789abcdefABCDEF],"
+        if (mask && sscanf(s, "tunnel(tun_id=%31[x0123456789abcdefABCDEF]/%llx,"
+                   "src="IP_SCAN_FMT"/"IP_SCAN_FMT",dst="IP_SCAN_FMT
+                   "/"IP_SCAN_FMT",tos=%i/%i,ttl=%i/%i,flags%n",
+                   tun_id_s, &tun_id_mask,
+                   IP_SCAN_ARGS(&tun_key.ip_src),
+                   IP_SCAN_ARGS(&tun_key_mask.ip_src),
+                   IP_SCAN_ARGS(&tun_key.ip_dst),
+                   IP_SCAN_ARGS(&tun_key_mask.ip_dst),
+                   &tos, &tos_mask, &ttl, &ttl_mask,
+                   &n) > 0 && n > 0) {
+            int res;
+            uint32_t flags;
+
+            tun_key.tun_id = htonll(strtoull(tun_id_s, NULL, 0));
+            tun_key_mask.tun_id = htonll(tun_id_mask);
+            tun_key.ip_tos = tos;
+            tun_key_mask.ip_tos = tos_mask;
+            tun_key.ip_ttl = ttl;
+            tun_key_mask.ip_ttl = ttl_mask;
+            res = parse_flags(&s[n], flow_tun_flag_to_string, &flags);
+            tun_key.flags = flags;
+            tun_key_mask.flags = UINT16_MAX;
+
+            if (res < 0) {
+                return res;
+            }
+            n += res;
+            if (s[n] != ')') {
+                return -EINVAL;
+            }
+            n++;
+            tun_key_to_attr(key, &tun_key);
+            if (mask) {
+                tun_key_to_attr(mask, &tun_key_mask);
+            }
+            return n;
+        } else if (sscanf(s, "tunnel(tun_id=%31[x0123456789abcdefABCDEF],"
                    "src="IP_SCAN_FMT",dst="IP_SCAN_FMT
                    ",tos=%i,ttl=%i,flags%n", tun_id_s,
                     IP_SCAN_ARGS(&tun_key.ip_src),
@@ -1184,7 +1509,7 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
             tun_key.ip_tos = tos;
             tun_key.ip_ttl = ttl;
             res = parse_flags(&s[n], flow_tun_flag_to_string, &flags);
-            tun_key.flags = (uint16_t) flags;
+            tun_key.flags = flags;
 
             if (res < 0) {
                 return res;
@@ -1195,20 +1520,35 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
             }
             n++;
             tun_key_to_attr(key, &tun_key);
+
+            if (mask) {
+                memset(&tun_key, 0xff, sizeof tun_key);
+                tun_key_to_attr(mask, &tun_key);
+            }
             return n;
         }
     }
 
     {
         unsigned long long int in_port;
+        unsigned long long int in_port_mask;
         int n = -1;
 
-        if (sscanf(s, "in_port(%lli)%n", &in_port, &n) > 0 && n > 0) {
+        if (mask && sscanf(s, "in_port(%lli/%lli)%n", &in_port,
+                   &in_port_mask, &n) > 0 && n > 0) {
+            nl_msg_put_u32(key, OVS_KEY_ATTR_IN_PORT, in_port);
+            nl_msg_put_u32(mask, OVS_KEY_ATTR_IN_PORT, in_port_mask);
+            return n;
+        } else if (sscanf(s, "in_port(%lli)%n", &in_port, &n) > 0 && n > 0) {
             nl_msg_put_u32(key, OVS_KEY_ATTR_IN_PORT, in_port);
+            if (mask) {
+                nl_msg_put_u32(mask, OVS_KEY_ATTR_IN_PORT, UINT32_MAX);
+            }
             return n;
         }
     }
 
+
     if (port_names && !strncmp(s, "in_port(", 8)) {
         const char *name;
         const struct simap_node *node;
@@ -1219,63 +1559,138 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
         node = simap_find_len(port_names, name, name_len);
         if (node) {
             nl_msg_put_u32(key, OVS_KEY_ATTR_IN_PORT, node->data);
+
+            if (mask) {
+                nl_msg_put_u32(mask, OVS_KEY_ATTR_IN_PORT, UINT32_MAX);
+            }
             return 8 + name_len + 1;
         }
     }
 
     {
         struct ovs_key_ethernet eth_key;
+        struct ovs_key_ethernet eth_key_mask;
         int n = -1;
 
-        if (sscanf(s,
+        if (mask && sscanf(s,
+                   "eth(src="ETH_ADDR_SCAN_FMT"/"ETH_ADDR_SCAN_FMT","
+                        "dst="ETH_ADDR_SCAN_FMT"/"ETH_ADDR_SCAN_FMT")%n",
+                ETH_ADDR_SCAN_ARGS(eth_key.eth_src),
+                ETH_ADDR_SCAN_ARGS(eth_key_mask.eth_src),
+                ETH_ADDR_SCAN_ARGS(eth_key.eth_dst),
+                ETH_ADDR_SCAN_ARGS(eth_key_mask.eth_dst), &n) > 0 && n > 0) {
+
+            nl_msg_put_unspec(key, OVS_KEY_ATTR_ETHERNET,
+                              &eth_key, sizeof eth_key);
+            nl_msg_put_unspec(mask, OVS_KEY_ATTR_ETHERNET,
+                              &eth_key_mask, sizeof eth_key_mask);
+            return n;
+        } else if (sscanf(s,
                    "eth(src="ETH_ADDR_SCAN_FMT",dst="ETH_ADDR_SCAN_FMT")%n",
                    ETH_ADDR_SCAN_ARGS(eth_key.eth_src),
                    ETH_ADDR_SCAN_ARGS(eth_key.eth_dst), &n) > 0 && n > 0) {
             nl_msg_put_unspec(key, OVS_KEY_ATTR_ETHERNET,
                               &eth_key, sizeof eth_key);
+
+            if (mask) {
+                memset(&eth_key, 0xff, sizeof eth_key);
+                nl_msg_put_unspec(mask, OVS_KEY_ATTR_ETHERNET,
+                              &eth_key, sizeof eth_key);
+            }
             return n;
         }
     }
 
     {
-        uint16_t vid;
-        int pcp;
-        int cfi;
+        uint16_t vid, vid_mask;
+        int pcp, pcp_mask;
+        int cfi, cfi_mask;
         int n = -1;
 
-        if ((sscanf(s, "vlan(vid=%"SCNi16",pcp=%i)%n", &vid, &pcp, &n) > 0
-             && n > 0)) {
+        if (mask && (sscanf(s, "vlan(vid=%"SCNi16"/%"SCNi16",pcp=%i/%i)%n",
+                            &vid, &vid_mask, &pcp, &pcp_mask, &n) > 0 && n > 0)) {
+            nl_msg_put_be16(key, OVS_KEY_ATTR_VLAN,
+                            htons((vid << VLAN_VID_SHIFT) |
+                                  (pcp << VLAN_PCP_SHIFT) |
+                                  VLAN_CFI));
+            nl_msg_put_be16(mask, OVS_KEY_ATTR_VLAN,
+                            htons((vid_mask << VLAN_VID_SHIFT) |
+                                  (pcp_mask << VLAN_PCP_SHIFT) |
+                                  (1 << VLAN_CFI_SHIFT)));
+            return n;
+        } else if ((sscanf(s, "vlan(vid=%"SCNi16",pcp=%i)%n",
+                           &vid, &pcp, &n) > 0 && n > 0)) {
             nl_msg_put_be16(key, OVS_KEY_ATTR_VLAN,
                             htons((vid << VLAN_VID_SHIFT) |
                                   (pcp << VLAN_PCP_SHIFT) |
                                   VLAN_CFI));
+            if (mask) {
+                nl_msg_put_be16(mask, OVS_KEY_ATTR_VLAN, htons(UINT16_MAX));
+            }
+            return n;
+        } else if (mask && (sscanf(s, "vlan(vid=%"SCNi16"/%"SCNi16",pcp=%i/%i,cfi=%i/%i)%n",
+                                   &vid, &vid_mask, &pcp, &pcp_mask, &cfi, &cfi_mask, &n) > 0 && n > 0)) {
+            nl_msg_put_be16(key, OVS_KEY_ATTR_VLAN,
+                            htons((vid << VLAN_VID_SHIFT) |
+                                  (pcp << VLAN_PCP_SHIFT) |
+                                  (cfi ? VLAN_CFI : 0)));
+            nl_msg_put_be16(mask, OVS_KEY_ATTR_VLAN,
+                            htons((vid_mask << VLAN_VID_SHIFT) |
+                                  (pcp_mask << VLAN_PCP_SHIFT) |
+                                  (cfi_mask << VLAN_CFI_SHIFT)));
             return n;
         } else if ((sscanf(s, "vlan(vid=%"SCNi16",pcp=%i,cfi=%i)%n",
-                           &vid, &pcp, &cfi, &n) > 0
-             && n > 0)) {
+                           &vid, &pcp, &cfi, &n) > 0 && n > 0)) {
             nl_msg_put_be16(key, OVS_KEY_ATTR_VLAN,
                             htons((vid << VLAN_VID_SHIFT) |
                                   (pcp << VLAN_PCP_SHIFT) |
                                   (cfi ? VLAN_CFI : 0)));
+            if (mask) {
+                nl_msg_put_be16(mask, OVS_KEY_ATTR_VLAN, htons(UINT16_MAX));
+            }
             return n;
         }
     }
 
     {
         int eth_type;
+        int eth_type_mask;
         int n = -1;
 
-        if (sscanf(s, "eth_type(%i)%n", &eth_type, &n) > 0 && n > 0) {
+        if (mask && sscanf(s, "eth_type(%i/%i)%n",
+                   &eth_type, &eth_type_mask, &n) > 0 && n > 0) {
+            nl_msg_put_be16(key, OVS_KEY_ATTR_ETHERTYPE, htons(eth_type));
+            nl_msg_put_be16(mask, OVS_KEY_ATTR_ETHERTYPE, htons(eth_type_mask));
+            return n;
+        } else if (sscanf(s, "eth_type(%i)%n", &eth_type, &n) > 0 && n > 0) {
             nl_msg_put_be16(key, OVS_KEY_ATTR_ETHERTYPE, htons(eth_type));
+            if (mask) {
+                nl_msg_put_be16(mask, OVS_KEY_ATTR_ETHERTYPE,
+                                htons(UINT16_MAX));
+            }
             return n;
         }
     }
 
     {
         int label, tc, ttl, bos;
+        int label_mask, tc_mask, ttl_mask, bos_mask;
         int n = -1;
 
-        if (sscanf(s, "mpls(label=%"SCNi32",tc=%i,ttl=%i,bos=%i)%n",
+        if (mask && sscanf(s, "mpls(label=%"SCNi32"/%"SCNi32",tc=%i/%i,ttl=%i/%i,bos=%i/%i)%n",
+                    &label, &label_mask, &tc, &tc_mask, &ttl, &ttl_mask, &bos, &bos_mask, &n) > 0 && n > 0) {
+            struct ovs_key_mpls *mpls, *mpls_mask;
+
+            mpls = nl_msg_put_unspec_uninit(key, OVS_KEY_ATTR_MPLS,
+                                            sizeof *mpls);
+            mpls->mpls_lse = mpls_lse_from_components(label, tc, ttl, bos);
+
+            mpls_mask = nl_msg_put_unspec_uninit(mask, OVS_KEY_ATTR_MPLS,
+                                            sizeof *mpls_mask);
+            mpls_mask->mpls_lse = mpls_lse_from_components(
+                                  label_mask, tc_mask, ttl_mask, bos_mask);
+            return n;
+        } else if (sscanf(s, "mpls(label=%"SCNi32",tc=%i,ttl=%i,bos=%i)%n",
                     &label, &tc, &ttl, &bos, &n) > 0 &&
                     n > 0) {
             struct ovs_key_mpls *mpls;
@@ -1283,21 +1698,60 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
             mpls = nl_msg_put_unspec_uninit(key, OVS_KEY_ATTR_MPLS,
                                             sizeof *mpls);
             mpls->mpls_lse = mpls_lse_from_components(label, tc, ttl, bos);
+            if (mask) {
+                mpls = nl_msg_put_unspec_uninit(mask, OVS_KEY_ATTR_MPLS,
+                                            sizeof *mpls);
+                mpls->mpls_lse = htonl(UINT32_MAX);
+            }
             return n;
         }
     }
 
+
     {
-        ovs_be32 ipv4_src;
-        ovs_be32 ipv4_dst;
-        int ipv4_proto;
-        int ipv4_tos;
-        int ipv4_ttl;
+        ovs_be32 ipv4_src, ipv4_src_mask;
+        ovs_be32 ipv4_dst, ipv4_dst_mask;
+        int ipv4_proto, ipv4_proto_mask;
+        int ipv4_tos, ipv4_tos_mask;
+        int ipv4_ttl, ipv4_ttl_mask;
         char frag[8];
+        int  ipv4_frag_mask;
         enum ovs_frag_type ipv4_frag;
         int n = -1;
 
-        if (sscanf(s, "ipv4(src="IP_SCAN_FMT",dst="IP_SCAN_FMT","
+        if (mask && sscanf(s, "ipv4(src="IP_SCAN_FMT"/"IP_SCAN_FMT","
+                      "dst="IP_SCAN_FMT"/"IP_SCAN_FMT","
+                      "proto=%i/%i,tos=%i/%i,ttl=%i/%i,"
+                      "frag=%7[a-z]/%i)%n",
+                      IP_SCAN_ARGS(&ipv4_src), IP_SCAN_ARGS(&ipv4_src_mask),
+                      IP_SCAN_ARGS(&ipv4_dst), IP_SCAN_ARGS(&ipv4_dst_mask),
+                      &ipv4_proto, &ipv4_proto_mask,
+                      &ipv4_tos, &ipv4_tos_mask, &ipv4_ttl, &ipv4_ttl_mask,
+                      frag, &ipv4_frag_mask, &n) > 0
+            && n > 0
+            && ovs_frag_type_from_string(frag, &ipv4_frag)) {
+            struct ovs_key_ipv4 ipv4_key;
+            struct ovs_key_ipv4 ipv4_mask;
+
+            ipv4_key.ipv4_src = ipv4_src;
+            ipv4_key.ipv4_dst = ipv4_dst;
+            ipv4_key.ipv4_proto = ipv4_proto;
+            ipv4_key.ipv4_tos = ipv4_tos;
+            ipv4_key.ipv4_ttl = ipv4_ttl;
+            ipv4_key.ipv4_frag = ipv4_frag;
+            nl_msg_put_unspec(key, OVS_KEY_ATTR_IPV4,
+                              &ipv4_key, sizeof ipv4_key);
+
+            ipv4_mask.ipv4_src = ipv4_src_mask;
+            ipv4_mask.ipv4_dst = ipv4_dst_mask;
+            ipv4_mask.ipv4_proto = ipv4_proto_mask;
+            ipv4_mask.ipv4_tos = ipv4_tos_mask;
+            ipv4_mask.ipv4_ttl = ipv4_ttl_mask;
+            ipv4_mask.ipv4_frag = ipv4_frag_mask;
+            nl_msg_put_unspec(mask, OVS_KEY_ATTR_IPV4,
+                              &ipv4_mask, sizeof ipv4_mask);
+            return n;
+        } else if (sscanf(s, "ipv4(src="IP_SCAN_FMT",dst="IP_SCAN_FMT","
                    "proto=%i,tos=%i,ttl=%i,frag=%7[a-z])%n",
                    IP_SCAN_ARGS(&ipv4_src), IP_SCAN_ARGS(&ipv4_dst),
                    &ipv4_proto, &ipv4_tos, &ipv4_ttl, frag, &n) > 0
@@ -1313,22 +1767,68 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
             ipv4_key.ipv4_frag = ipv4_frag;
             nl_msg_put_unspec(key, OVS_KEY_ATTR_IPV4,
                               &ipv4_key, sizeof ipv4_key);
+
+            if (mask) {
+                memset(&ipv4_key, 0xff, sizeof ipv4_key);
+                nl_msg_put_unspec(mask, OVS_KEY_ATTR_IPV4,
+                              &ipv4_key, sizeof ipv4_key);
+            }
             return n;
         }
     }
 
     {
         char ipv6_src_s[IPV6_SCAN_LEN + 1];
+        char ipv6_src_mask_s[IPV6_SCAN_LEN + 1];
         char ipv6_dst_s[IPV6_SCAN_LEN + 1];
-        int ipv6_label;
-        int ipv6_proto;
-        int ipv6_tclass;
-        int ipv6_hlimit;
+        char ipv6_dst_mask_s[IPV6_SCAN_LEN + 1];
+        int ipv6_label, ipv6_label_mask;
+        int ipv6_proto, ipv6_proto_mask;
+        int ipv6_tclass, ipv6_tclass_mask;
+        int ipv6_hlimit, ipv6_hlimit_mask;
         char frag[8];
         enum ovs_frag_type ipv6_frag;
+        int ipv6_frag_mask;
         int n = -1;
 
-        if (sscanf(s, "ipv6(src="IPV6_SCAN_FMT",dst="IPV6_SCAN_FMT","
+        if (mask && sscanf(s, "ipv6(src="IPV6_SCAN_FMT"/"IPV6_SCAN_FMT",dst="
+                   IPV6_SCAN_FMT"/"IPV6_SCAN_FMT","
+                   "label=%i/%i,proto=%i/%i,tclass=%i/%i,"
+                   "hlimit=%i/%i,frag=%7[a-z]/%i)%n",
+                   ipv6_src_s, ipv6_src_mask_s, ipv6_dst_s, ipv6_dst_mask_s,
+                   &ipv6_label, &ipv6_label_mask, &ipv6_proto,
+                   &ipv6_proto_mask, &ipv6_tclass, &ipv6_tclass_mask,
+                   &ipv6_hlimit, &ipv6_hlimit_mask, frag,
+                   &ipv6_frag_mask, &n) > 0
+            && n > 0
+            && ovs_frag_type_from_string(frag, &ipv6_frag)) {
+            struct ovs_key_ipv6 ipv6_key;
+            struct ovs_key_ipv6 ipv6_mask;
+
+            if (inet_pton(AF_INET6, ipv6_src_s, &ipv6_key.ipv6_src) != 1 ||
+                inet_pton(AF_INET6, ipv6_dst_s, &ipv6_key.ipv6_dst) != 1 ||
+                inet_pton(AF_INET6, ipv6_src_mask_s, &ipv6_mask.ipv6_src) != 1 ||
+                inet_pton(AF_INET6, ipv6_dst_mask_s, &ipv6_mask.ipv6_dst) != 1) {
+                return -EINVAL;
+            }
+
+            ipv6_key.ipv6_label = htonl(ipv6_label);
+            ipv6_key.ipv6_proto = ipv6_proto;
+            ipv6_key.ipv6_tclass = ipv6_tclass;
+            ipv6_key.ipv6_hlimit = ipv6_hlimit;
+            ipv6_key.ipv6_frag = ipv6_frag;
+            nl_msg_put_unspec(key, OVS_KEY_ATTR_IPV6,
+                              &ipv6_key, sizeof ipv6_key);
+
+            ipv6_mask.ipv6_label = htonl(ipv6_label_mask);
+            ipv6_mask.ipv6_proto = ipv6_proto_mask;
+            ipv6_mask.ipv6_tclass = ipv6_tclass_mask;
+            ipv6_mask.ipv6_hlimit = ipv6_hlimit_mask;
+            ipv6_mask.ipv6_frag = ipv6_frag_mask;
+            nl_msg_put_unspec(mask, OVS_KEY_ATTR_IPV6,
+                              &ipv6_mask, sizeof ipv6_mask);
+            return n;
+        } else if (sscanf(s, "ipv6(src="IPV6_SCAN_FMT",dst="IPV6_SCAN_FMT","
                    "label=%i,proto=%i,tclass=%i,hlimit=%i,frag=%7[a-z])%n",
                    ipv6_src_s, ipv6_dst_s, &ipv6_label,
                    &ipv6_proto, &ipv6_tclass, &ipv6_hlimit, frag, &n) > 0
@@ -1347,6 +1847,12 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
             ipv6_key.ipv6_frag = ipv6_frag;
             nl_msg_put_unspec(key, OVS_KEY_ATTR_IPV6,
                               &ipv6_key, sizeof ipv6_key);
+
+            if (mask) {
+                memset(&ipv6_key, 0xff, sizeof ipv6_key);
+                nl_msg_put_unspec(mask, OVS_KEY_ATTR_IPV6,
+                              &ipv6_key, sizeof ipv6_key);
+            }
             return n;
         }
     }
@@ -1354,15 +1860,38 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
     {
         int tcp_src;
         int tcp_dst;
+        int tcp_src_mask;
+        int tcp_dst_mask;
         int n = -1;
 
-        if (sscanf(s, "tcp(src=%i,dst=%i)%n",&tcp_src, &tcp_dst, &n) > 0
+        if (mask && sscanf(s, "tcp(src=%i/%i,dst=%i/%i)%n",
+                   &tcp_src, &tcp_src_mask, &tcp_dst, &tcp_dst_mask, &n) > 0
             && n > 0) {
             struct ovs_key_tcp tcp_key;
+            struct ovs_key_tcp tcp_mask;
 
             tcp_key.tcp_src = htons(tcp_src);
             tcp_key.tcp_dst = htons(tcp_dst);
             nl_msg_put_unspec(key, OVS_KEY_ATTR_TCP, &tcp_key, sizeof tcp_key);
+
+            tcp_mask.tcp_src = htons(tcp_src_mask);
+            tcp_mask.tcp_dst = htons(tcp_dst_mask);
+            nl_msg_put_unspec(mask, OVS_KEY_ATTR_TCP,
+                              &tcp_mask, sizeof tcp_mask);
+            return n;
+        } else if (sscanf(s, "tcp(src=%i,dst=%i)%n",&tcp_src, &tcp_dst, &n) > 0
+            && n > 0) {
+            struct ovs_key_tcp tcp_key;
+
+            tcp_key.tcp_src = htons(tcp_src);
+            tcp_key.tcp_dst = htons(tcp_dst);
+            nl_msg_put_unspec(key, OVS_KEY_ATTR_TCP, &tcp_key, sizeof tcp_key);
+
+            if (mask) {
+                memset(&tcp_key, 0xff, sizeof tcp_key);
+                nl_msg_put_unspec(mask, OVS_KEY_ATTR_TCP,
+                              &tcp_key, sizeof tcp_key);
+            }
             return n;
         }
     }
@@ -1370,8 +1899,26 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
     {
         int udp_src;
         int udp_dst;
+        int udp_src_mask;
+        int udp_dst_mask;
         int n = -1;
 
+        if (mask && sscanf(s, "udp(src=%i/%i,dst=%i/%i)%n",
+                   &udp_src, &udp_src_mask,
+                   &udp_dst, &udp_dst_mask, &n) > 0 && n > 0) {
+            struct ovs_key_udp udp_key;
+            struct ovs_key_udp udp_mask;
+
+            udp_key.udp_src = htons(udp_src);
+            udp_key.udp_dst = htons(udp_dst);
+            nl_msg_put_unspec(key, OVS_KEY_ATTR_UDP, &udp_key, sizeof udp_key);
+
+            udp_mask.udp_src = htons(udp_src_mask);
+            udp_mask.udp_dst = htons(udp_dst_mask);
+            nl_msg_put_unspec(mask, OVS_KEY_ATTR_UDP,
+                              &udp_mask, sizeof udp_mask);
+            return n;
+        }
         if (sscanf(s, "udp(src=%i,dst=%i)%n", &udp_src, &udp_dst, &n) > 0
             && n > 0) {
             struct ovs_key_udp udp_key;
@@ -1379,6 +1926,11 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
             udp_key.udp_src = htons(udp_src);
             udp_key.udp_dst = htons(udp_dst);
             nl_msg_put_unspec(key, OVS_KEY_ATTR_UDP, &udp_key, sizeof udp_key);
+
+            if (mask) {
+                memset(&udp_key, 0xff, sizeof udp_key);
+                nl_msg_put_unspec(mask, OVS_KEY_ATTR_UDP, &udp_key, sizeof udp_key);
+            }
             return n;
         }
     }
@@ -1386,9 +1938,27 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
     {
         int icmp_type;
         int icmp_code;
+        int icmp_type_mask;
+        int icmp_code_mask;
         int n = -1;
 
-        if (sscanf(s, "icmp(type=%i,code=%i)%n",
+        if (mask && sscanf(s, "icmp(type=%i/%i,code=%i/%i)%n",
+                   &icmp_type, &icmp_type_mask,
+                   &icmp_code, &icmp_code_mask, &n) > 0 && n > 0) {
+            struct ovs_key_icmp icmp_key;
+            struct ovs_key_icmp icmp_mask;
+
+            icmp_key.icmp_type = icmp_type;
+            icmp_key.icmp_code = icmp_code;
+            nl_msg_put_unspec(key, OVS_KEY_ATTR_ICMP,
+                              &icmp_key, sizeof icmp_key);
+
+            icmp_mask.icmp_type = icmp_type_mask;
+            icmp_mask.icmp_code = icmp_code_mask;
+            nl_msg_put_unspec(mask, OVS_KEY_ATTR_ICMP,
+                              &icmp_mask, sizeof icmp_mask);
+            return n;
+        } else if (sscanf(s, "icmp(type=%i,code=%i)%n",
                    &icmp_type, &icmp_code, &n) > 0
             && n > 0) {
             struct ovs_key_icmp icmp_key;
@@ -1397,32 +1967,90 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
             icmp_key.icmp_code = icmp_code;
             nl_msg_put_unspec(key, OVS_KEY_ATTR_ICMP,
                               &icmp_key, sizeof icmp_key);
+            if (mask) {
+                memset(&icmp_key, 0xff, sizeof icmp_key);
+                nl_msg_put_unspec(mask, OVS_KEY_ATTR_ICMP, &icmp_key,
+                              sizeof icmp_key);
+            }
             return n;
         }
     }
 
     {
         struct ovs_key_icmpv6 icmpv6_key;
+        struct ovs_key_icmpv6 icmpv6_mask;
+        int icmpv6_type_mask;
+        int icmpv6_code_mask;
         int n = -1;
 
-        if (sscanf(s, "icmpv6(type=%"SCNi8",code=%"SCNi8")%n",
+        if (mask && sscanf(s, "icmpv6(type=%"SCNi8"/%i,code=%"SCNi8"/%i)%n",
+                   &icmpv6_key.icmpv6_type, &icmpv6_type_mask,
+                   &icmpv6_key.icmpv6_code, &icmpv6_code_mask, &n) > 0
+            && n > 0) {
+            nl_msg_put_unspec(key, OVS_KEY_ATTR_ICMPV6,
+                              &icmpv6_key, sizeof icmpv6_key);
+
+            icmpv6_mask.icmpv6_type = icmpv6_type_mask;
+            icmpv6_mask.icmpv6_code = icmpv6_code_mask;
+            nl_msg_put_unspec(mask, OVS_KEY_ATTR_ICMPV6, &icmpv6_mask,
+                              sizeof icmpv6_mask);
+            return n;
+        } else if (sscanf(s, "icmpv6(type=%"SCNi8",code=%"SCNi8")%n",
                    &icmpv6_key.icmpv6_type, &icmpv6_key.icmpv6_code,&n) > 0
             && n > 0) {
             nl_msg_put_unspec(key, OVS_KEY_ATTR_ICMPV6,
                               &icmpv6_key, sizeof icmpv6_key);
+
+            if (mask) {
+                memset(&icmpv6_key, 0xff, sizeof icmpv6_key);
+                nl_msg_put_unspec(mask, OVS_KEY_ATTR_ICMPV6, &icmpv6_key,
+                              sizeof icmpv6_key);
+            }
             return n;
         }
     }
 
     {
-        ovs_be32 arp_sip;
-        ovs_be32 arp_tip;
-        int arp_op;
+        ovs_be32 arp_sip, arp_sip_mask;
+        ovs_be32 arp_tip, arp_tip_mask;
+        int arp_op, arp_op_mask;
         uint8_t arp_sha[ETH_ADDR_LEN];
+        uint8_t arp_sha_mask[ETH_ADDR_LEN];
         uint8_t arp_tha[ETH_ADDR_LEN];
+        uint8_t arp_tha_mask[ETH_ADDR_LEN];
         int n = -1;
 
-        if (sscanf(s, "arp(sip="IP_SCAN_FMT",tip="IP_SCAN_FMT","
+        if (mask && sscanf(s, "arp(sip="IP_SCAN_FMT"/"IP_SCAN_FMT","
+                   "tip="IP_SCAN_FMT"/"IP_SCAN_FMT","
+                   "op=%i/%i,sha="ETH_ADDR_SCAN_FMT"/"ETH_ADDR_SCAN_FMT","
+                   "tha="ETH_ADDR_SCAN_FMT"/"ETH_ADDR_SCAN_FMT")%n",
+                   IP_SCAN_ARGS(&arp_sip), IP_SCAN_ARGS(&arp_sip_mask),
+                   IP_SCAN_ARGS(&arp_tip), IP_SCAN_ARGS(&arp_tip_mask),
+                   &arp_op, &arp_op_mask,
+                   ETH_ADDR_SCAN_ARGS(arp_sha),
+                   ETH_ADDR_SCAN_ARGS(arp_sha_mask),
+                   ETH_ADDR_SCAN_ARGS(arp_tha),
+                   ETH_ADDR_SCAN_ARGS(arp_tha_mask), &n) > 0 && n > 0) {
+            struct ovs_key_arp arp_key;
+            struct ovs_key_arp arp_mask;
+
+            memset(&arp_key, 0, sizeof arp_key);
+            arp_key.arp_sip = arp_sip;
+            arp_key.arp_tip = arp_tip;
+            arp_key.arp_op = htons(arp_op);
+            memcpy(arp_key.arp_sha, arp_sha, ETH_ADDR_LEN);
+            memcpy(arp_key.arp_tha, arp_tha, ETH_ADDR_LEN);
+            nl_msg_put_unspec(key, OVS_KEY_ATTR_ARP, &arp_key, sizeof arp_key);
+
+            arp_mask.arp_sip = arp_sip_mask;
+            arp_mask.arp_tip = arp_tip_mask;
+            arp_mask.arp_op = htons(arp_op_mask);
+            memcpy(arp_mask.arp_sha, arp_sha_mask, ETH_ADDR_LEN);
+            memcpy(arp_mask.arp_tha, arp_tha_mask, ETH_ADDR_LEN);
+            nl_msg_put_unspec(mask, OVS_KEY_ATTR_ARP,
+                              &arp_mask, sizeof arp_mask);
+            return n;
+        } else if (sscanf(s, "arp(sip="IP_SCAN_FMT",tip="IP_SCAN_FMT","
                    "op=%i,sha="ETH_ADDR_SCAN_FMT",tha="ETH_ADDR_SCAN_FMT")%n",
                    IP_SCAN_ARGS(&arp_sip),
                    IP_SCAN_ARGS(&arp_tip),
@@ -1438,44 +2066,101 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
             memcpy(arp_key.arp_sha, arp_sha, ETH_ADDR_LEN);
             memcpy(arp_key.arp_tha, arp_tha, ETH_ADDR_LEN);
             nl_msg_put_unspec(key, OVS_KEY_ATTR_ARP, &arp_key, sizeof arp_key);
+
+            if (mask) {
+                memset(&arp_key, 0xff, sizeof arp_key);
+                nl_msg_put_unspec(mask, OVS_KEY_ATTR_ARP,
+                                  &arp_key, sizeof arp_key);
+            }
             return n;
         }
     }
 
     {
         char nd_target_s[IPV6_SCAN_LEN + 1];
+        char nd_target_mask_s[IPV6_SCAN_LEN + 1];
         uint8_t nd_sll[ETH_ADDR_LEN];
+        uint8_t nd_sll_mask[ETH_ADDR_LEN];
         uint8_t nd_tll[ETH_ADDR_LEN];
+        uint8_t nd_tll_mask[ETH_ADDR_LEN];
         int n = -1;
 
-        if (sscanf(s, "nd(target="IPV6_SCAN_FMT")%n",
+        memset(&nd_target_mask_s[0], 0xff, sizeof nd_target_s);
+        memset(&nd_sll_mask[0], 0xff, sizeof nd_sll);
+        memset(&nd_tll_mask [0], 0xff, sizeof nd_tll);
+
+        if (mask && sscanf(s, "nd(target="IPV6_SCAN_FMT"/"IPV6_SCAN_FMT")%n",
+                   nd_target_s, nd_target_mask_s, &n) > 0 && n > 0) {
+                put_nd_key(n, nd_target_s, NULL, NULL, key);
+                put_nd_key(n, nd_target_mask_s, NULL, NULL, mask);
+        } else if (sscanf(s, "nd(target="IPV6_SCAN_FMT")%n",
                    nd_target_s, &n) > 0 && n > 0) {
-            return put_nd_key(n, nd_target_s, NULL, NULL, key);
-        }
-        if (sscanf(s, "nd(target="IPV6_SCAN_FMT",sll="ETH_ADDR_SCAN_FMT")%n",
+                put_nd_key(n, nd_target_s, NULL, NULL, key);
+                if (mask) {
+                    put_nd_key(n, nd_target_mask_s, NULL, NULL, mask);
+                }
+        } else if (mask && sscanf(s, "nd(target="IPV6_SCAN_FMT"/"IPV6_SCAN_FMT
+                         ",sll="ETH_ADDR_SCAN_FMT"/"ETH_ADDR_SCAN_FMT")%n",
+                   nd_target_s, nd_target_mask_s,
+                   ETH_ADDR_SCAN_ARGS(nd_sll),
+                   ETH_ADDR_SCAN_ARGS(nd_sll_mask), &n) > 0 && n > 0) {
+            put_nd_key(n, nd_target_s, nd_sll, NULL, key);
+            put_nd_key(n, nd_target_mask_s, nd_sll_mask, NULL, mask);
+        } else if (sscanf(s, "nd(target="IPV6_SCAN_FMT",sll="ETH_ADDR_SCAN_FMT")%n",
                    nd_target_s, ETH_ADDR_SCAN_ARGS(nd_sll), &n) > 0
             && n > 0) {
-            return put_nd_key(n, nd_target_s, nd_sll, NULL, key);
-        }
-        if (sscanf(s, "nd(target="IPV6_SCAN_FMT",tll="ETH_ADDR_SCAN_FMT")%n",
+            put_nd_key(n, nd_target_s, nd_sll, NULL, key);
+            if (mask) {
+                put_nd_key(n, nd_target_mask_s, nd_sll_mask, NULL, mask);
+            }
+        } else if (mask && sscanf(s, "nd(target="IPV6_SCAN_FMT"/"IPV6_SCAN_FMT
+                         ",tll="ETH_ADDR_SCAN_FMT"/"ETH_ADDR_SCAN_FMT")%n",
+                   nd_target_s, nd_target_mask_s,
+                   ETH_ADDR_SCAN_ARGS(nd_tll),
+                   ETH_ADDR_SCAN_ARGS(nd_tll_mask), &n) > 0 && n > 0) {
+            put_nd_key(n, nd_target_s, NULL, nd_tll, key);
+            put_nd_key(n, nd_target_mask_s, NULL, nd_tll_mask, mask);
+        } else if (sscanf(s, "nd(target="IPV6_SCAN_FMT",tll="ETH_ADDR_SCAN_FMT")%n",
                    nd_target_s, ETH_ADDR_SCAN_ARGS(nd_tll), &n) > 0
             && n > 0) {
-            return put_nd_key(n, nd_target_s, NULL, nd_tll, key);
-        }
-        if (sscanf(s, "nd(target="IPV6_SCAN_FMT",sll="ETH_ADDR_SCAN_FMT","
+            put_nd_key(n, nd_target_s, NULL, nd_tll, key);
+            if (mask) {
+                put_nd_key(n, nd_target_mask_s, NULL, nd_tll_mask, mask);
+            }
+        } else if (mask && sscanf(s, "nd(target="IPV6_SCAN_FMT"/"IPV6_SCAN_FMT
+                   ",sll="ETH_ADDR_SCAN_FMT"/"ETH_ADDR_SCAN_FMT","
+                   "tll="ETH_ADDR_SCAN_FMT"/"ETH_ADDR_SCAN_FMT")%n",
+                   nd_target_s, nd_target_mask_s,
+                   ETH_ADDR_SCAN_ARGS(nd_sll), ETH_ADDR_SCAN_ARGS(nd_sll_mask),
+                   ETH_ADDR_SCAN_ARGS(nd_tll), ETH_ADDR_SCAN_ARGS(nd_tll_mask),
+                   &n) > 0
+            && n > 0) {
+            put_nd_key(n, nd_target_s, nd_sll, nd_tll, key);
+            put_nd_key(n, nd_target_mask_s, nd_sll_mask, nd_tll_mask, mask);
+        } else if (sscanf(s, "nd(target="IPV6_SCAN_FMT",sll="ETH_ADDR_SCAN_FMT","
                    "tll="ETH_ADDR_SCAN_FMT")%n",
                    nd_target_s, ETH_ADDR_SCAN_ARGS(nd_sll),
                    ETH_ADDR_SCAN_ARGS(nd_tll), &n) > 0
             && n > 0) {
-            return put_nd_key(n, nd_target_s, nd_sll, nd_tll, key);
+            put_nd_key(n, nd_target_s, nd_sll, nd_tll, key);
+            if (mask) {
+                put_nd_key(n, nd_target_mask_s, nd_sll_mask, nd_tll_mask, mask);
+            }
         }
+
+        if (n != -1)
+            return n;
+
     }
 
     if (!strncmp(s, "encap(", 6)) {
         const char *start = s;
-        size_t encap;
+        size_t encap, encap_mask;
 
         encap = nl_msg_start_nested(key, OVS_KEY_ATTR_ENCAP);
+        if (mask) {
+            encap_mask = nl_msg_start_nested(mask, OVS_KEY_ATTR_ENCAP);
+        }
 
         s += 6;
         for (;;) {
@@ -1488,7 +2173,7 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
                 break;
             }
 
-            retval = parse_odp_key_attr(s, port_names, key);
+            retval = parse_odp_key_mask_attr(s, port_names, key, mask);
             if (retval < 0) {
                 return retval;
             }
@@ -1497,6 +2182,9 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
         s++;
 
         nl_msg_end_nested(key, encap);
+        if (mask) {
+            nl_msg_end_nested(mask, encap_mask);
+        }
 
         return s - start;
     }
@@ -1519,8 +2207,8 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
  * valid, but they may not be valid as a sequence.  'key' might, for example,
  * have duplicated keys.  odp_flow_key_to_flow() will detect those errors. */
 int
-odp_flow_key_from_string(const char *s, const struct simap *port_names,
-                         struct ofpbuf *key)
+odp_flow_from_string(const char *s, const struct simap *port_names,
+                     struct ofpbuf *key, struct ofpbuf *mask)
 {
     const size_t old_size = key->size;
     for (;;) {
@@ -1531,7 +2219,7 @@ odp_flow_key_from_string(const char *s, const struct simap *port_names,
             return 0;
         }
 
-        retval = parse_odp_key_attr(s, port_names, key);
+        retval = parse_odp_key_mask_attr(s, port_names, key, mask);
         if (retval < 0) {
             key->size = old_size;
             return -retval;
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 6213418..8c6bb5c 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -90,9 +90,13 @@ struct odputil_keybuf {
 enum odp_key_fitness odp_tun_key_from_attr(const struct nlattr *,
                                            struct flow_tnl *);
 
+void odp_flow_format(const struct nlattr *key, size_t key_len,
+                     const struct nlattr *mask, size_t mask_len,
+                     struct ds *);
 void odp_flow_key_format(const struct nlattr *, size_t, struct ds *);
-int odp_flow_key_from_string(const char *s, const struct simap *port_names,
-                             struct ofpbuf *);
+int odp_flow_from_string(const char *s,
+                         const struct simap *port_names,
+                         struct ofpbuf *, struct ofpbuf *);
 
 void odp_flow_key_from_flow(struct ofpbuf *, const struct flow *,
                             uint32_t odp_in_port);
diff --git a/lib/packets.h b/lib/packets.h
index b73ff63..543a481 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -259,6 +259,7 @@ BUILD_ASSERT_DECL(LLC_SNAP_HEADER_LEN == sizeof(struct llc_snap_header));
 #define VLAN_PCP_SHIFT 13
 
 #define VLAN_CFI 0x1000
+#define VLAN_CFI_SHIFT 12
 
 /* Given the vlan_tci field from an 802.1Q header, in network byte order,
  * returns the VLAN ID in host byte order. */
@@ -276,6 +277,14 @@ vlan_tci_to_pcp(ovs_be16 vlan_tci)
     return (ntohs(vlan_tci) & VLAN_PCP_MASK) >> VLAN_PCP_SHIFT;
 }
 
+/* Given the vlan_tci field from an 802.1Q header, in network byte order,
+ * returns the Canonical Format Indicator (CFI) in host byte order. */
+static inline int
+vlan_tci_to_cfi(ovs_be16 vlan_tci)
+{
+    return (ntohs(vlan_tci) & VLAN_CFI) >> VLAN_CFI_SHIFT;
+}
+
 #define VLAN_HEADER_LEN 4
 struct vlan_header {
     ovs_be16 vlan_tci;          /* Lowest 12 bits are VLAN ID. */
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index b917dc7..837bc3a 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -3459,6 +3459,8 @@ handle_flow_miss_with_facet(struct flow_miss *miss, struct facet *facet,
         put->flags = DPIF_FP_CREATE | DPIF_FP_MODIFY;
         put->key = miss->key;
         put->key_len = miss->key_len;
+        put->mask = NULL;
+        put->mask_len = 0;
         if (want_path == SF_FAST_PATH) {
             put->actions = facet->xout.odp_actions.data;
             put->actions_len = facet->xout.odp_actions.size;
@@ -3719,7 +3721,8 @@ handle_miss_upcalls(struct dpif_backer *backer, struct dpif_upcall *upcalls,
                 hmap_insert(&backer->drop_keys, &drop_key->hmap_node,
                             hash_bytes(drop_key->key, drop_key->key_len, 0));
                 dpif_flow_put(backer->dpif, DPIF_FP_CREATE | DPIF_FP_MODIFY,
-                              drop_key->key, drop_key->key_len, NULL, 0, NULL);
+                              drop_key->key, drop_key->key_len,
+                              NULL, 0, NULL, 0, NULL);
             }
             continue;
         }
@@ -4115,7 +4118,8 @@ update_stats(struct dpif_backer *backer)
     size_t key_len;
 
     dpif_flow_dump_start(&dump, backer->dpif);
-    while (dpif_flow_dump_next(&dump, &key, &key_len, NULL, NULL, &stats)) {
+    while (dpif_flow_dump_next(&dump, &key, &key_len,
+                               NULL, NULL, NULL, NULL, &stats)) {
         struct subfacet *subfacet;
         uint32_t key_hash;
 
@@ -5020,8 +5024,9 @@ subfacet_install(struct subfacet *subfacet, const struct ofpbuf *odp_actions,
                           &actions, &actions_len);
     }
 
-    ret = dpif_flow_put(subfacet->backer->dpif, flags, subfacet->key,
-                        subfacet->key_len, actions, actions_len, stats);
+    ret = dpif_flow_put(ofproto->backer->dpif, flags, subfacet->key,
+                        subfacet->key_len,  NULL, 0,
+                        actions, actions_len, stats);
 
     if (stats) {
         subfacet_reset_dp_stats(subfacet, stats);
@@ -5859,7 +5864,7 @@ ofproto_unixctl_trace(struct unixctl_conn *conn, int argc, const char *argv[],
      * bridge is specified. If function odp_flow_key_from_string()
      * returns 0, the flow is a odp_flow. If function
      * parse_ofp_exact_flow() returns 0, the flow is a br_flow. */
-    if (!odp_flow_key_from_string(argv[argc - 1], NULL, &odp_key)) {
+    if (!odp_flow_from_string(argv[argc - 1], NULL, &odp_key, NULL)) {
         /* If the odp_flow is the second argument,
          * the datapath name is the first argument. */
         if (argc == 3) {
diff --git a/tests/odp.at b/tests/odp.at
index fd6a192..062f828 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -80,6 +80,71 @@ AT_CHECK_UNQUOTED([test-odp parse-keys < odp.txt], [0], [`cat odp.txt`
 ])
 AT_CLEANUP
 
+AT_SETUP([OVS datapath wildcarded key parsing and formatting - valid forms])
+dnl We could add a test for invalid forms, but that's less important.
+AT_DATA([odp-base.txt], [dnl
+in_port(1/0xff),eth(src=00:01:02:03:04:05/ff:ff:ff:ff:ff:f0,dst=10:11:12:13:14:15/ff:ff:ff:ff:ff:f0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x1234/0xfff0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41/255.255.255.0,dst=172.16.0.20/255.255.255.0,proto=5/0xf0,tos=0x80/0xf0,ttl=128/0xf0,frag=no/0xf0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=6,tos=0,ttl=128,frag=no),tcp(src=80/0xff00,dst=8080/0xff)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=17,tos=0,ttl=128,frag=no),udp(src=81/0xff00,dst=6632/0xff)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=1,tos=0,ttl=128,frag=no),icmp(type=1/0xf0,code=2/0xff)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1/::255,dst=::2/::255,label=0/0xf0,proto=10/0xf0,tclass=0x70/0xf0,hlimit=128/0xf0,frag=no/0xf0)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=6,tclass=0,hlimit=128,frag=no),tcp(src=80/0xff00,dst=8080/0xff)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=17,tclass=0,hlimit=128,frag=no),udp(src=6630/0xff00,dst=22/0xff)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=1/0xf0,code=2/0xff)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=135,code=0),nd(target=::3/::250)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=135,code=0),nd(target=::3/::250,sll=00:05:06:07:08:09/ff:ff:ff:ff:ff:00)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=136,code=0),nd(target=::3/::250,tll=00:0a:0b:0c:0d:0e/ff:ff:ff:ff:ff:00)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=136,code=0),nd(target=::3/::250,sll=00:05:06:07:08:09/ff:ff:ff:ff:ff:00,tll=00:0a:0b:0c:0d:0e/ff:ff:ff:ff:ff:00)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp(sip=1.2.3.4/255.255.255.250,tip=5.6.7.8/255.255.255.250,op=1/0xf0,sha=00:0f:10:11:12:13/ff:ff:ff:ff:ff:00,tha=00:14:15:16:17:18/ff:ff:ff:ff:ff:00)
+skb_mark(0x1234/0xfff0),in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0,proto=58,tclass=0,hlimit=128,frag=no),icmpv6(type=136,code=0),nd(target=::3,sll=00:05:06:07:08:09,tll=00:0a:0b:0c:0d:0e)
+])
+
+(echo '# Valid forms without tun_id or VLAN header.'
+ cat odp-base.txt
+
+ echo
+ echo '# Valid forms with tunnel header.'
+ sed 's/^/tunnel(tun_id=0x7f10354\/0xff,src=10.10.10.10\/255.255.255.0,dst=20.20.20.20\/255.255.255.0,tos=0\/0xff,ttl=64\/0xff,flags(csum,key)),/' odp-base.txt
+
+ echo
+ echo '# Valid forms with VLAN header.'
+ sed 's/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),encap(/
+s/$/)/' odp-base.txt
+
+ echo
+ echo '# Valid forms with MPLS header.'
+ sed 's/\(eth([[^)]]*),?\)/\1,eth_type(0x8847),mpls(label=100\/0xff,tc=7\/7,ttl=64\/0xff,bos=1\/1)/' odp-base.txt
+
+ echo
+ echo '# Valid forms with QoS priority.'
+ sed 's/^/skb_priority(0x1234\/0xff),/' odp-base.txt
+
+ echo
+ echo '# Valid forms with tunnel and VLAN headers.'
+ sed 's/^/tunnel(tun_id=0xfedcba9876543210,src=10.0.0.1,dst=10.0.0.2,tos=0x8,ttl=128,flags(key)),/
+s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99/0xff0,pcp=7/0xe),encap(/
+s/$/)/' odp-base.txt
+
+ echo
+ echo '# Valid forms with QOS priority, tunnel, and VLAN headers.'
+ sed 's/^/skb_priority(0x1234),tunnel(tun_id=0xfedcba9876543210,src=10.10.10.10,dst=20.20.20.20,tos=0x8,ttl=64,flags(key)),/
+s/\(eth([[^)]]*)\),*/\1,eth_type(0x8100),vlan(vid=99,pcp=7),encap(/
+s/$/)/' odp-base.txt
+
+ echo
+ echo '# Valid forms with IP first fragment.'
+sed -n 's/,frag=no),/,frag=first),/p' odp-base.txt
+
+ echo
+ echo '# Valid forms with IP later fragment.'
+sed -n 's/,frag=no),.*/,frag=later)/p' odp-base.txt) > odp.txt
+AT_CAPTURE_FILE([odp.txt])
+AT_CHECK_UNQUOTED([test-odp parse-wc-keys < odp.txt], [0], [`cat odp.txt`
+])
+AT_CLEANUP
+
 AT_SETUP([OVS datapath actions parsing and formatting - valid forms])
 AT_DATA([actions.txt], [dnl
 1,2,3
diff --git a/tests/test-odp.c b/tests/test-odp.c
index 268a105..3bb3a0c 100644
--- a/tests/test-odp.c
+++ b/tests/test-odp.c
@@ -26,7 +26,7 @@
 #include "vlog.h"
 
 static int
-parse_keys(void)
+parse_keys(bool wc_keys)
 {
     int exit_code = 0;
     struct ds in;
@@ -36,51 +36,60 @@ parse_keys(void)
     while (!ds_get_test_line(&in, stdin)) {
         enum odp_key_fitness fitness;
         struct ofpbuf odp_key;
+        struct ofpbuf odp_mask;
         struct flow flow;
         struct ds out;
         int error;
 
         /* Convert string to OVS DP key. */
         ofpbuf_init(&odp_key, 0);
-        error = odp_flow_key_from_string(ds_cstr(&in), NULL, &odp_key);
+        ofpbuf_init(&odp_mask, 0);
+        error = odp_flow_from_string(ds_cstr(&in), NULL,
+                                     &odp_key, &odp_mask);
         if (error) {
-            printf("odp_flow_key_from_string: error\n");
+            printf("odp_flow_from_string: error\n");
             goto next;
         }
 
-        /* Convert odp_key to flow. */
-        fitness = odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow);
-        switch (fitness) {
-        case ODP_FIT_PERFECT:
-            break;
-
-        case ODP_FIT_TOO_LITTLE:
-            printf("ODP_FIT_TOO_LITTLE: ");
-            break;
-
-        case ODP_FIT_TOO_MUCH:
-            printf("ODP_FIT_TOO_MUCH: ");
-            break;
-
-        case ODP_FIT_ERROR:
-            printf("odp_flow_key_to_flow: error\n");
-            goto next;
-        }
-
-        /* Convert cls_rule back to odp_key. */
-        ofpbuf_uninit(&odp_key);
-        ofpbuf_init(&odp_key, 0);
-        odp_flow_key_from_flow(&odp_key, &flow, flow.in_port);
-
-        if (odp_key.size > ODPUTIL_FLOW_KEY_BYTES) {
-            printf ("too long: %zu > %d\n",
-                    odp_key.size, ODPUTIL_FLOW_KEY_BYTES);
-            exit_code = 1;
+        if (!wc_keys) {
+            /* Convert odp_key to flow. */
+            fitness = odp_flow_key_to_flow(odp_key.data, odp_key.size, &flow);
+            switch (fitness) {
+                case ODP_FIT_PERFECT:
+                    break;
+
+                case ODP_FIT_TOO_LITTLE:
+                    printf("ODP_FIT_TOO_LITTLE: ");
+                    break;
+
+                case ODP_FIT_TOO_MUCH:
+                    printf("ODP_FIT_TOO_MUCH: ");
+                    break;
+
+                case ODP_FIT_ERROR:
+                    printf("odp_flow_key_to_flow: error\n");
+                    goto next;
+            }
+            /* Convert cls_rule back to odp_key. */
+            ofpbuf_uninit(&odp_key);
+            ofpbuf_init(&odp_key, 0);
+            odp_flow_key_from_flow(&odp_key, &flow, flow.in_port);
+
+            if (odp_key.size > ODPUTIL_FLOW_KEY_BYTES) {
+                printf ("too long: %zu > %d\n",
+                        odp_key.size, ODPUTIL_FLOW_KEY_BYTES);
+                exit_code = 1;
+            }
         }
 
         /* Convert odp_key to string. */
         ds_init(&out);
-        odp_flow_key_format(odp_key.data, odp_key.size, &out);
+        if (wc_keys) {
+            odp_flow_format(odp_key.data, odp_key.size,
+                            odp_mask.data, odp_mask.size, &out);
+        } else {
+            odp_flow_key_format(odp_key.data, odp_key.size, &out);
+        }
         puts(ds_cstr(&out));
         ds_destroy(&out);
 
@@ -130,10 +139,12 @@ int
 main(int argc, char *argv[])
 {
     if (argc == 2 &&!strcmp(argv[1], "parse-keys")) {
-        return parse_keys();
+        return parse_keys(false);
+    } else if (argc == 2 &&!strcmp(argv[1], "parse-wc-keys")) {
+        return parse_keys(true);
     } else if (argc == 2 && !strcmp(argv[1], "parse-actions")) {
         return parse_actions();
     } else {
-        ovs_fatal(0, "usage: %s parse-keys | parse-actions", argv[0]);
+        ovs_fatal(0, "usage: %s parse-keys | parse-wc-keys | parse-actions", argv[0]);
     }
 }
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index 54505e8..c82171a 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -743,9 +743,11 @@ dpctl_dump_flows(int argc, char *argv[])
     const struct nlattr *actions;
     struct dpif_flow_dump dump;
     const struct nlattr *key;
+    const struct nlattr *mask;
     size_t actions_len;
     struct dpif *dpif;
     size_t key_len;
+    size_t mask_len;
     struct ds ds;
     char *name;
 
@@ -756,10 +758,12 @@ dpctl_dump_flows(int argc, char *argv[])
     ds_init(&ds);
     dpif_flow_dump_start(&dump, dpif);
     while (dpif_flow_dump_next(&dump, &key, &key_len,
+                               &mask, &mask_len,
                                &actions, &actions_len, &stats)) {
         ds_clear(&ds);
-        odp_flow_key_format(key, key_len, &ds);
+        odp_flow_format(key, key_len, mask, mask_len, &ds);
         ds_put_cstr(&ds, ", ");
+
         dpif_flow_stats_format(stats, &ds);
         ds_put_cstr(&ds, ", actions:");
         format_odp_actions(&ds, actions, actions_len);
@@ -778,26 +782,32 @@ dpctl_put_flow(int argc, char *argv[], enum dpif_flow_put_flags flags)
     struct dpif_flow_stats stats;
     struct ofpbuf actions;
     struct ofpbuf key;
+    struct ofpbuf mask;
     struct dpif *dpif;
+    struct ds s;
     char *dp_name;
 
+    ds_init(&s);
     ofpbuf_init(&key, 0);
-    run(odp_flow_key_from_string(key_s, NULL, &key), "parsing flow key");
+    ofpbuf_init(&mask, 0);
+    run(odp_flow_from_string(key_s, NULL, &key, &mask), "parsing flow key");
 
     ofpbuf_init(&actions, 0);
     run(odp_actions_from_string(actions_s, NULL, &actions), "parsing actions");
 
-    dp_name = argc == 3 ? xstrdup(argv[1]) : get_one_dp();
+    dp_name = argc == 4 ? xstrdup(argv[1]) : get_one_dp();
     run(parsed_dpif_open(dp_name, false, &dpif), "opening datapath");
     free(dp_name);
 
     run(dpif_flow_put(dpif, flags,
                       key.data, key.size,
+                      mask.size == 0 ? NULL : mask.data, mask.size,
                       actions.data, actions.size,
                       print_statistics ? &stats : NULL),
         "updating flow table");
 
     ofpbuf_uninit(&key);
+    ofpbuf_uninit(&mask);
     ofpbuf_uninit(&actions);
 
     if (print_statistics) {
@@ -838,11 +848,13 @@ dpctl_del_flow(int argc, char *argv[])
     const char *key_s = argv[argc - 1];
     struct dpif_flow_stats stats;
     struct ofpbuf key;
+    struct ofpbuf mask; /* To be ignored. */
     struct dpif *dpif;
     char *dp_name;
 
     ofpbuf_init(&key, 0);
-    run(odp_flow_key_from_string(key_s, NULL, &key), "parsing flow key");
+    ofpbuf_init(&mask, 0);
+    run(odp_flow_from_string(key_s, NULL, &key, &mask), "parsing flow key");
 
     dp_name = argc == 2 ? xstrdup(argv[1]) : get_one_dp();
     run(parsed_dpif_open(dp_name, false, &dpif), "opening datapath");
@@ -853,6 +865,7 @@ dpctl_del_flow(int argc, char *argv[])
                       print_statistics ? &stats : NULL), "deleting flow");
 
     ofpbuf_uninit(&key);
+    ofpbuf_uninit(&mask);
 
     if (print_statistics) {
         struct ds s;
@@ -1032,11 +1045,11 @@ dpctl_normalize_actions(int argc, char *argv[])
 
     /* Parse flow key. */
     ofpbuf_init(&keybuf, 0);
-    run(odp_flow_key_from_string(argv[1], &port_names, &keybuf),
+    run(odp_flow_from_string(argv[1], &port_names, &keybuf, NULL),
         "odp_flow_key_from_string");
 
     ds_clear(&s);
-    odp_flow_key_format(keybuf.data, keybuf.size, &s);
+    odp_flow_format(keybuf.data, keybuf.size, NULL, 0, &s);
     printf("input flow: %s\n", ds_cstr(&s));
 
     run(odp_flow_key_to_flow(keybuf.data, keybuf.size, &flow),
-- 
1.7.9.5




More information about the dev mailing list