[ovs-dev] [PATCHv5 08/12] dpif: Add Unique flow identifiers.

Joe Stringer joestringer at nicira.com
Mon Sep 15 02:25:14 UTC 2014


One of the limiting factors on the number of flows that can be supported
in the datapath is the overhead of assembling flow dump messages in the
datapath. This patch adds a new alternative to dumping the key, mask and
actions from the datapath, which is a 128-bit unique identifier for the
flow. For each flow dump, the dpif user specifies whether to skip these
attributes, allowing the common case to only dump a pair of 128-bit ID and
flow stats. This increases the number of flows that a revalidator can
handle per second by up to 40% with the linux datapath.

The unique identifiers ("UID") for a flow are generated by the dpif.
During upcall processing, the dpif will provide a UID for each flow
which is based on the original flow key. During flow dump, if the
datapath provides the UID, then the dpif will pass it up transparently.
Otherwise, the dpif will generate the UID. This simplifies backward
compatibility handling in ofproto-dpif-upcall.

Signed-off-by: Joe Stringer <joestringer at nicira.com>
---
v5: Always pass flow_key down to dpif, to improve error logging.
    Fix extraneous netflow_unref.
    Fix testsuite failure.
    Rebase.
v4: Generate UID in dpif layer and pass up to ofproto-dpif-upcall.
    Fix bug where UID-based terse dump wasn't enabled by default.
    Skip sending flow key in flow_del.
    Fix race conditions with tests.
    Combine dpif,upcall,dpif-netdev,dpif-linux changes into one patch.
    Display whether terse dump is enabled in ovs-appctl upcall/show.
    Log UID in flow_del/flow_get/flow_dump messages.
v3: Rebase.
---
 datapath/linux/compat/include/linux/openvswitch.h |   26 +++
 lib/dpctl.c                                       |   14 +-
 lib/dpif-linux.c                                  |  132 ++++++++------
 lib/dpif-netdev.c                                 |  144 ++++++++-------
 lib/dpif-provider.h                               |    9 +-
 lib/dpif.c                                        |   47 +++--
 lib/dpif.h                                        |   31 +++-
 lib/flow.h                                        |    6 +
 lib/odp-util.c                                    |   55 ++++++
 lib/odp-util.h                                    |   25 +++
 ofproto/ofproto-dpif-upcall.c                     |  195 ++++++++++++++++-----
 ofproto/ofproto-dpif-upcall.h                     |    1 +
 ofproto/ofproto-dpif.c                            |   93 +++++++++-
 ofproto/ofproto-dpif.h                            |    2 +
 tests/dpif-netdev.at                              |    5 +
 tests/ofproto-dpif.at                             |   28 +--
 tests/ofproto-macros.at                           |    1 +
 17 files changed, 601 insertions(+), 213 deletions(-)

diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 306ea86..21b81ae 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -470,6 +470,10 @@ struct ovs_key_nd {
  * a wildcarded match. Omitting attribute is treated as wildcarding all
  * corresponding fields. Optional for all requests. If not present,
  * all flow key bits are exact match bits.
+ * @OVS_FLOW_ATTR_UID: Nested %OVS_UID_ATTR_* attributes specifying unique
+ * identifiers for flows and providing alternative semantics for flow
+ * installation and retrieval. Required for all requests if the datapath is
+ * created with %OVS_DP_F_INDEX_BY_UID.
  *
  * These attributes follow the &struct ovs_header within the Generic Netlink
  * payload for %OVS_FLOW_* commands.
@@ -483,12 +487,34 @@ enum ovs_flow_attr {
 	OVS_FLOW_ATTR_USED,      /* u64 msecs last used in monotonic time. */
 	OVS_FLOW_ATTR_CLEAR,     /* Flag to clear stats, tcp_flags, used. */
 	OVS_FLOW_ATTR_MASK,      /* Sequence of OVS_KEY_ATTR_* attributes. */
+	OVS_FLOW_ATTR_UID,       /* 64-bit Unique flow identifier. */
 	__OVS_FLOW_ATTR_MAX
 };
 
 #define OVS_FLOW_ATTR_MAX (__OVS_FLOW_ATTR_MAX - 1)
 
 /**
+ * enum ovs_uid_attr - Unique identifier types.
+ *
+ * @OVS_UID_ATTR_FLAGS: A 32-bit value specifying changes to the behaviour of
+ * the current %OVS_FLOW_CMD_* request. Optional for all requests.
+ * @OVS_UID_ATTR_ID: A 64-bit value uniquely identifying the flow.
+ */
+enum ovs_uid_attr {
+	OVS_UID_ATTR_UNSPEC,
+	OVS_UID_ATTR_FLAGS,	/* u32 of OVS_UID_F_* */
+	OVS_UID_ATTR_ID,	/* u64 unique flow identifier. */
+	__OVS_UID_ATTR_MAX
+};
+
+#define OVS_UID_ATTR_MAX (__OVS_UID_ATTR_MAX - 1)
+
+/* Skip attributes for notifications. */
+#define OVS_UID_F_SKIP_KEY	(1 << 0)
+#define OVS_UID_F_SKIP_MASK	(1 << 1)
+#define OVS_UID_F_SKIP_ACTIONS	(1 << 2)
+
+/**
  * enum ovs_sample_attr - Attributes for %OVS_ACTION_ATTR_SAMPLE action.
  * @OVS_SAMPLE_ATTR_PROBABILITY: 32-bit fraction of packets to sample with
  * @OVS_ACTION_ATTR_SAMPLE.  A value of 0 samples no packets, a value of
diff --git a/lib/dpctl.c b/lib/dpctl.c
index 623f5b1..8956569 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -736,7 +736,7 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
     }
 
     ds_init(&ds);
-    flow_dump = dpif_flow_dump_create(dpif);
+    flow_dump = dpif_flow_dump_create(dpif, false);
     flow_dump_thread = dpif_flow_dump_thread_create(flow_dump);
     while (dpif_flow_dump_next(flow_dump_thread, &f, 1)) {
         if (filter) {
@@ -760,6 +760,8 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
             minimatch_destroy(&minimatch);
         }
         ds_clear(&ds);
+        odp_format_uid(&f.uid, &ds);
+        ds_put_cstr(&ds, " ");
         odp_flow_format(f.key, f.key_len, f.mask, f.mask_len,
                         &portno_names, &ds, dpctl_p->verbosity);
         ds_put_cstr(&ds, ", ");
@@ -803,6 +805,7 @@ dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
     char *dp_name;
     struct simap port_names;
     int error;
+    ovs_u128 uid;
 
     dp_name = argc == 4 ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
     if (!dp_name) {
@@ -836,12 +839,13 @@ dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
         dpctl_error(dpctl_p, error, "parsing actions");
         goto out_freeactions;
     }
+    dpif_flow_hash(dpif, ofpbuf_data(&key), ofpbuf_size(&key), &uid);
     error = dpif_flow_put(dpif, flags,
                           ofpbuf_data(&key), ofpbuf_size(&key),
                           ofpbuf_size(&mask) == 0 ? NULL : ofpbuf_data(&mask),
                           ofpbuf_size(&mask),
                           ofpbuf_data(&actions), ofpbuf_size(&actions),
-                          dpctl_p->print_statistics ? &stats : NULL);
+                          &uid, dpctl_p->print_statistics ? &stats : NULL);
     if (error) {
         dpctl_error(dpctl_p, error, "updating flow table");
         goto out_freeactions;
@@ -899,6 +903,7 @@ dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
     struct dpif *dpif;
     char *dp_name;
     struct simap port_names;
+    ovs_u128 uid;
     int error;
 
     dp_name = argc == 3 ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
@@ -926,9 +931,10 @@ dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
         goto out;
     }
 
+    dpif_flow_hash(dpif, ofpbuf_data(&key), ofpbuf_size(&key), &uid);
     error = dpif_flow_del(dpif,
-                          ofpbuf_data(&key), ofpbuf_size(&key),
-                          dpctl_p->print_statistics ? &stats : NULL);
+                          ofpbuf_data(&key), ofpbuf_size(&key), &uid,
+                          dpctl_p->print_statistics ? &stats : NULL, false);
     if (error) {
         dpctl_error(dpctl_p, error, "deleting flow");
         goto out;
diff --git a/lib/dpif-linux.c b/lib/dpif-linux.c
index 2c387ed..2405ccd 100644
--- a/lib/dpif-linux.c
+++ b/lib/dpif-linux.c
@@ -110,10 +110,14 @@ struct dpif_linux_flow {
     size_t mask_len;
     const struct nlattr *actions;       /* OVS_FLOW_ATTR_ACTIONS. */
     size_t actions_len;
+    const struct nlattr *uid;           /* OVS_FLOW_ATTR_UID. */
+    size_t uid_len;
     const struct ovs_flow_stats *stats; /* OVS_FLOW_ATTR_STATS. */
     const uint8_t *tcp_flags;           /* OVS_FLOW_ATTR_TCP_FLAGS. */
     const ovs_32aligned_u64 *used;      /* OVS_FLOW_ATTR_USED. */
     bool clear;                         /* OVS_FLOW_ATTR_CLEAR. */
+
+    struct odputil_uidbuf uid_buf;      /* Buffer to hold 'uid'. */
 };
 
 static void dpif_linux_flow_init(struct dpif_linux_flow *);
@@ -126,7 +130,7 @@ static int dpif_linux_flow_transact(struct dpif_linux_flow *request,
                                     struct ofpbuf **bufp);
 static void dpif_linux_flow_get_stats(const struct dpif_linux_flow *,
                                       struct dpif_flow_stats *);
-static void dpif_linux_flow_to_dpif_flow(struct dpif_flow *,
+static void dpif_linux_flow_to_dpif_flow(struct dpif *, struct dpif_flow *,
                                          const struct dpif_linux_flow *);
 
 /* One of the dpif channels between the kernel and userspace. */
@@ -1049,26 +1053,31 @@ dpif_linux_port_poll_wait(const struct dpif *dpif_)
 }
 
 static void
+dpif_linux_init_uid(const ovs_u128 *uid, struct odputil_uidbuf *uidbuf,
+                    bool terse, const struct nlattr **nla, size_t *nla_len)
+{
+    static const uint32_t flags = OVS_UID_F_SKIP_KEY | OVS_UID_F_SKIP_MASK
+                                  | OVS_UID_F_SKIP_ACTIONS;
+    struct ofpbuf buf;
+
+    ofpbuf_use_stack(&buf, uidbuf, sizeof *uidbuf);
+    odp_uid_to_nlattrs(&buf, uid, terse ? flags : 0);
+    *nla = ofpbuf_data(&buf);
+    *nla_len = ofpbuf_size(&buf);
+}
+
+static void
 dpif_linux_init_flow_get(const struct dpif_linux *dpif,
-                         const struct nlattr *key, size_t key_len,
+                         const struct dpif_flow_get *get,
                          struct dpif_linux_flow *request)
 {
     dpif_linux_flow_init(request);
     request->cmd = OVS_FLOW_CMD_GET;
     request->dp_ifindex = dpif->dp_ifindex;
-    request->key = key;
-    request->key_len = key_len;
-}
-
-static int
-dpif_linux_flow_get(const struct dpif_linux *dpif,
-                    const struct nlattr *key, size_t key_len,
-                    struct dpif_linux_flow *reply, struct ofpbuf **bufp)
-{
-    struct dpif_linux_flow request;
-
-    dpif_linux_init_flow_get(dpif, key, key_len, &request);
-    return dpif_linux_flow_transact(&request, reply, bufp);
+    request->key = get->key;
+    request->key_len = get->key_len;
+    dpif_linux_init_uid(get->uid, &request->uid_buf, false,
+                        &request->uid, &request->uid_len);
 }
 
 static void
@@ -1085,6 +1094,9 @@ dpif_linux_init_flow_put(struct dpif_linux *dpif, const struct dpif_flow_put *pu
     request->key_len = put->key_len;
     request->mask = put->mask;
     request->mask_len = put->mask_len;
+    dpif_linux_init_uid(put->uid, &request->uid_buf, false,
+                        &request->uid, &request->uid_len);
+
     /* Ensure that OVS_FLOW_ATTR_ACTIONS will always be included. */
     request->actions = (put->actions
                         ? put->actions
@@ -1103,8 +1115,12 @@ dpif_linux_init_flow_del(struct dpif_linux *dpif, const struct dpif_flow_del *de
     dpif_linux_flow_init(request);
     request->cmd = OVS_FLOW_CMD_DEL;
     request->dp_ifindex = dpif->dp_ifindex;
-    request->key = del->key;
-    request->key_len = del->key_len;
+    if (!del->terse) {
+        request->key = del->key;
+        request->key_len = del->key_len;
+    }
+    dpif_linux_init_uid(del->uid, &request->uid_buf, del->terse,
+                        &request->uid, &request->uid_len);
 }
 
 struct dpif_linux_flow_dump {
@@ -1120,12 +1136,14 @@ dpif_linux_flow_dump_cast(struct dpif_flow_dump *dump)
 }
 
 static struct dpif_flow_dump *
-dpif_linux_flow_dump_create(const struct dpif *dpif_)
+dpif_linux_flow_dump_create(const struct dpif *dpif_, bool terse)
 {
+    static const uint32_t dump_flags = OVS_UID_F_SKIP_KEY | OVS_UID_F_SKIP_MASK
+                                       | OVS_UID_F_SKIP_ACTIONS;
     const struct dpif_linux *dpif = dpif_linux_cast(dpif_);
     struct dpif_linux_flow_dump *dump;
     struct dpif_linux_flow request;
-    struct ofpbuf *buf;
+    struct ofpbuf *buf, uid;
 
     dump = xmalloc(sizeof *dump);
     dpif_flow_dump_init(&dump->up, dpif_);
@@ -1134,6 +1152,13 @@ dpif_linux_flow_dump_create(const struct dpif *dpif_)
     request.cmd = OVS_FLOW_CMD_GET;
     request.dp_ifindex = dpif->dp_ifindex;
 
+    ofpbuf_use_stack(&uid, &request.uid_buf, sizeof request.uid_buf);
+    if (terse) {
+        odp_uid_to_nlattrs(&uid, NULL, dump_flags);
+    }
+    request.uid = ofpbuf_data(&uid);
+    request.uid_len = ofpbuf_size(&uid);
+
     buf = ofpbuf_new(1024);
     dpif_linux_flow_to_ofpbuf(&request, buf);
     nl_dump_start(&dump->nl_dump, NETLINK_GENERIC, buf);
@@ -1198,7 +1223,7 @@ dpif_linux_flow_dump_thread_destroy(struct dpif_flow_dump_thread *thread_)
 }
 
 static void
-dpif_linux_flow_to_dpif_flow(struct dpif_flow *dpif_flow,
+dpif_linux_flow_to_dpif_flow(struct dpif *dpif, struct dpif_flow *dpif_flow,
                              const struct dpif_linux_flow *linux_flow)
 {
     dpif_flow->key = linux_flow->key;
@@ -1207,6 +1232,13 @@ dpif_linux_flow_to_dpif_flow(struct dpif_flow *dpif_flow,
     dpif_flow->mask_len = linux_flow->mask_len;
     dpif_flow->actions = linux_flow->actions;
     dpif_flow->actions_len = linux_flow->actions_len;
+    if (linux_flow->uid_len) {
+        odp_uid_from_nlattrs(linux_flow->uid, linux_flow->uid_len,
+                             &dpif_flow->uid, NULL);
+    } else {
+        dpif_flow_hash(dpif, linux_flow->key, linux_flow->key_len,
+                       &dpif_flow->uid);
+    }
     dpif_linux_flow_get_stats(linux_flow, &dpif_flow->stats);
 }
 
@@ -1217,7 +1249,7 @@ dpif_linux_flow_dump_next(struct dpif_flow_dump_thread *thread_,
     struct dpif_linux_flow_dump_thread *thread
         = dpif_linux_flow_dump_thread_cast(thread_);
     struct dpif_linux_flow_dump *dump = thread->dump;
-    struct dpif_linux *dpif = dpif_linux_cast(thread->up.dpif);
+    struct dpif *dpif = thread->up.dpif;
     int n_flows;
 
     ofpbuf_delete(thread->nl_actions);
@@ -1242,30 +1274,7 @@ dpif_linux_flow_dump_next(struct dpif_flow_dump_thread *thread_,
             break;
         }
 
-        if (linux_flow.actions) {
-            /* Common case: the flow includes actions. */
-            dpif_linux_flow_to_dpif_flow(&flows[n_flows++], &linux_flow);
-        } else {
-            /* Rare case: the flow does not include actions.  Retrieve this
-             * individual flow again to get the actions. */
-            error = dpif_linux_flow_get(dpif, linux_flow.key,
-                                        linux_flow.key_len, &linux_flow,
-                                        &thread->nl_actions);
-            if (error == ENOENT) {
-                VLOG_DBG("dumped flow disappeared on get");
-                continue;
-            } else if (error) {
-                VLOG_WARN("error fetching dumped flow: %s",
-                          ovs_strerror(error));
-                atomic_store_relaxed(&dump->status, error);
-                break;
-            }
-
-            /* Save this flow.  Then exit, because we only have one buffer to
-             * handle this case. */
-            dpif_linux_flow_to_dpif_flow(&flows[n_flows++], &linux_flow);
-            break;
-        }
+        dpif_linux_flow_to_dpif_flow(dpif, &flows[n_flows++], &linux_flow);
     }
     return n_flows;
 }
@@ -1365,7 +1374,7 @@ dpif_linux_operate__(struct dpif_linux *dpif,
 
         case DPIF_OP_FLOW_GET:
             get = &op->u.flow_get;
-            dpif_linux_init_flow_get(dpif, get->key, get->key_len, &flow);
+            dpif_linux_init_flow_get(dpif, get, &flow);
             aux->txn.reply = get->buffer;
             dpif_linux_flow_to_ofpbuf(&flow, &aux->request);
             break;
@@ -1431,7 +1440,8 @@ dpif_linux_operate__(struct dpif_linux *dpif,
 
                 op->error = dpif_linux_flow_from_ofpbuf(&reply, txn->reply);
                 if (!op->error) {
-                    dpif_linux_flow_to_dpif_flow(get->flow, &reply);
+                    dpif_linux_flow_to_dpif_flow(&dpif->dpif, get->flow,
+                                                 &reply);
                 }
             }
             break;
@@ -1643,8 +1653,8 @@ dpif_linux_queue_to_priority(const struct dpif *dpif OVS_UNUSED,
 }
 
 static int
-parse_odp_packet(struct ofpbuf *buf, struct dpif_upcall *upcall,
-                 int *dp_ifindex)
+parse_odp_packet(const struct dpif_linux *dpif, struct ofpbuf *buf,
+                 struct dpif_upcall *upcall, int *dp_ifindex)
 {
     static const struct nl_policy ovs_packet_policy[] = {
         /* Always present. */
@@ -1688,6 +1698,7 @@ parse_odp_packet(struct ofpbuf *buf, struct dpif_upcall *upcall,
     upcall->key = CONST_CAST(struct nlattr *,
                              nl_attr_get(a[OVS_PACKET_ATTR_KEY]));
     upcall->key_len = nl_attr_get_size(a[OVS_PACKET_ATTR_KEY]);
+    dpif_flow_hash(&dpif->dpif, upcall->key, upcall->key_len, &upcall->uid);
     upcall->userdata = a[OVS_PACKET_ATTR_USERDATA];
     upcall->out_tun_key = a[OVS_PACKET_ATTR_EGRESS_TUN_KEY];
 
@@ -1768,7 +1779,7 @@ dpif_linux_recv__(struct dpif_linux *dpif, uint32_t handler_id,
                 return error;
             }
 
-            error = parse_odp_packet(buf, upcall, &dp_ifindex);
+            error = parse_odp_packet(dpif, buf, upcall, &dp_ifindex);
             if (!error && dp_ifindex == dpif->dp_ifindex) {
                 return 0;
             } else if (error) {
@@ -2266,14 +2277,15 @@ static int
 dpif_linux_flow_from_ofpbuf(struct dpif_linux_flow *flow,
                             const struct ofpbuf *buf)
 {
-    static const struct nl_policy ovs_flow_policy[] = {
-        [OVS_FLOW_ATTR_KEY] = { .type = NL_A_NESTED },
+    static const struct nl_policy ovs_flow_policy[__OVS_FLOW_ATTR_MAX] = {
+        [OVS_FLOW_ATTR_KEY] = { .type = NL_A_NESTED, .optional = true },
         [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 },
         [OVS_FLOW_ATTR_TCP_FLAGS] = { .type = NL_A_U8, .optional = true },
         [OVS_FLOW_ATTR_USED] = { .type = NL_A_U64, .optional = true },
+        [OVS_FLOW_ATTR_UID] = { .type = NL_A_NESTED, .optional = true },
         /* The kernel never uses OVS_FLOW_ATTR_CLEAR. */
     };
 
@@ -2295,12 +2307,21 @@ dpif_linux_flow_from_ofpbuf(struct dpif_linux_flow *flow,
                             ARRAY_SIZE(ovs_flow_policy))) {
         return EINVAL;
     }
+    if (!a[OVS_FLOW_ATTR_KEY] && !a[OVS_FLOW_ATTR_UID]) {
+        return EINVAL;
+    }
 
     flow->nlmsg_flags = nlmsg->nlmsg_flags;
     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_KEY]) {
+        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_UID]) {
+        flow->uid = nl_attr_get(a[OVS_FLOW_ATTR_UID]);
+        flow->uid_len = nl_attr_get_size(a[OVS_FLOW_ATTR_UID]);
+    }
     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]);
@@ -2344,6 +2365,9 @@ dpif_linux_flow_to_ofpbuf(const struct dpif_linux_flow *flow,
         nl_msg_put_unspec(buf, OVS_FLOW_ATTR_MASK, flow->mask, flow->mask_len);
     }
 
+    if (flow->uid_len) {
+        nl_msg_put_unspec(buf, OVS_FLOW_ATTR_UID, flow->uid, flow->uid_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 5486dd6..07cea1e 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -280,8 +280,9 @@ struct dp_netdev_flow {
     /* Packet classification. */
     const struct cls_rule cr;   /* In owning dp_netdev's 'cls'. */
 
-    /* Hash table index by unmasked flow. */
+    /* Hash table index by uid. */
     const struct cmap_node node; /* In owning dp_netdev's 'flow_table'. */
+    const ovs_u128 uid;          /* Unique flow identifier. */
     const struct flow flow;      /* The flow that created this entry. */
 
     /* Number of references.
@@ -1018,7 +1019,7 @@ dp_netdev_remove_flow(struct dp_netdev *dp, struct dp_netdev_flow *flow)
     struct cmap_node *node = CONST_CAST(struct cmap_node *, &flow->node);
 
     classifier_remove(&dp->cls, cr);
-    cmap_remove(&dp->flow_table, node, flow_hash(&flow->flow, 0));
+    cmap_remove(&dp->flow_table, node, flow_uid_hash(&flow->uid, 0));
     flow->dead = true;
 
     dp_netdev_flow_unref(flow);
@@ -1229,13 +1230,13 @@ dp_netdev_lookup_flow(const struct dp_netdev *dp, const struct miniflow *key)
 }
 
 static struct dp_netdev_flow *
-dp_netdev_find_flow(const struct dp_netdev *dp, const struct flow *flow)
+dp_netdev_find_flow(const struct dp_netdev *dp, const ovs_u128 *uid)
 {
     struct dp_netdev_flow *netdev_flow;
 
-    CMAP_FOR_EACH_WITH_HASH (netdev_flow, node, flow_hash(flow, 0),
+    CMAP_FOR_EACH_WITH_HASH (netdev_flow, node, flow_uid_hash(uid, 0),
                              &dp->flow_table) {
-        if (flow_equal(&netdev_flow->flow, flow)) {
+        if (!memcmp(&netdev_flow->uid, uid, sizeof *uid)) {
             return netdev_flow;
         }
     }
@@ -1263,22 +1264,40 @@ get_dpif_flow_stats(const struct dp_netdev_flow *netdev_flow,
 
 static void
 dp_netdev_flow_to_dpif_flow(const struct dp_netdev_flow *netdev_flow,
-                            struct ofpbuf *buffer, struct dpif_flow *flow)
+                            struct ofpbuf *key_buf, struct ofpbuf *mask_buf,
+                            struct dpif_flow *flow, bool terse)
 {
-    struct flow_wildcards wc;
-    struct dp_netdev_actions *actions;
+    if (terse) {
+        memset(flow, 0, sizeof *flow);
+    } else {
+        struct dp_netdev_actions *netdev_actions;
+        struct flow_wildcards wc;
+        size_t offset;
+
+        minimask_expand(&netdev_flow->cr.match.mask, &wc);
+
+        /* Key */
+        offset = ofpbuf_size(key_buf);
+        flow->key = ofpbuf_tail(key_buf);
+        odp_flow_key_from_flow(key_buf, &netdev_flow->flow, &wc.masks,
+                               netdev_flow->flow.in_port.odp_port, true);
+        flow->key_len = ofpbuf_size(key_buf) - offset;
 
-    minimask_expand(&netdev_flow->cr.match.mask, &wc);
-    odp_flow_key_from_mask(buffer, &wc.masks, &netdev_flow->flow,
-                           odp_to_u32(wc.masks.in_port.odp_port),
-                           SIZE_MAX, true);
-    flow->mask = ofpbuf_data(buffer);
-    flow->mask_len = ofpbuf_size(buffer);
+        /* Mask */
+        offset = ofpbuf_size(mask_buf);
+        flow->mask = ofpbuf_tail(mask_buf);
+        odp_flow_key_from_mask(mask_buf, &wc.masks, &netdev_flow->flow,
+                               odp_to_u32(wc.masks.in_port.odp_port),
+                               SIZE_MAX, true);
+        flow->mask_len = ofpbuf_size(mask_buf) - offset;
 
-    actions = dp_netdev_flow_get_actions(netdev_flow);
-    flow->actions = actions->actions;
-    flow->actions_len = actions->size;
+        /* Actions */
+        netdev_actions = dp_netdev_flow_get_actions(netdev_flow);
+        flow->actions = netdev_actions->actions;
+        flow->actions_len = netdev_actions->size;
+    }
 
+    memcpy(&flow->uid, &netdev_flow->uid, sizeof flow->uid);
     get_dpif_flow_stats(netdev_flow, &flow->stats);
 }
 
@@ -1379,18 +1398,16 @@ dpif_netdev_flow_get(const struct dpif *dpif, const struct dpif_flow_get *get)
 {
     struct dp_netdev *dp = get_dp_netdev(dpif);
     struct dp_netdev_flow *netdev_flow;
-    struct flow key;
-    int error;
+    int error = 0;
 
-    error = dpif_netdev_flow_from_nlattrs(get->key, get->key_len, &key);
-    if (error) {
-        return error;
+    if (!get->uid) {
+        return EINVAL;
     }
 
-    netdev_flow = dp_netdev_find_flow(dp, &key);
-
+    netdev_flow = dp_netdev_find_flow(dp, get->uid);
     if (netdev_flow) {
-        dp_netdev_flow_to_dpif_flow(netdev_flow, get->buffer, get->flow);
+        dp_netdev_flow_to_dpif_flow(netdev_flow, get->buffer, get->buffer,
+                                    get->flow, false);
      } else {
         error = ENOENT;
     }
@@ -1400,6 +1417,7 @@ dpif_netdev_flow_get(const struct dpif *dpif, const struct dpif_flow_get *get)
 
 static int
 dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
+                   const ovs_u128 *uid,
                    const struct nlattr *actions, size_t actions_len)
     OVS_REQUIRES(dp->flow_mutex)
 {
@@ -1407,7 +1425,7 @@ dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
 
     netdev_flow = xzalloc(sizeof *netdev_flow);
     *CONST_CAST(struct flow *, &netdev_flow->flow) = match->flow;
-
+    *CONST_CAST(ovs_u128 *, &netdev_flow->uid) = *uid;
     ovs_refcount_init(&netdev_flow->ref_cnt);
 
     ovsthread_stats_init(&netdev_flow->stats);
@@ -1419,7 +1437,7 @@ dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
                   match, NETDEV_RULE_PRIORITY);
     cmap_insert(&dp->flow_table,
                 CONST_CAST(struct cmap_node *, &netdev_flow->node),
-                flow_hash(&match->flow, 0));
+                flow_uid_hash(uid, 0));
     classifier_insert(&dp->cls,
                       CONST_CAST(struct cls_rule *, &netdev_flow->cr));
 
@@ -1427,6 +1445,8 @@ dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
         struct ds ds = DS_EMPTY_INITIALIZER;
 
         ds_put_cstr(&ds, "flow_add: ");
+        odp_format_uid(uid, &ds);
+        ds_put_cstr(&ds, " ");
         match_format(match, &ds, OFP_DEFAULT_PRIORITY);
         ds_put_cstr(&ds, ", actions:");
         format_odp_actions(&ds, actions, actions_len);
@@ -1476,6 +1496,10 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
     }
     miniflow_init(&miniflow, &match.flow);
 
+    if (!put->uid) {
+        return EINVAL;
+    }
+
     ovs_mutex_lock(&dp->flow_mutex);
     netdev_flow = dp_netdev_lookup_flow(dp, &miniflow);
     if (!netdev_flow) {
@@ -1484,7 +1508,7 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
                 if (put->stats) {
                     memset(put->stats, 0, sizeof *put->stats);
                 }
-                error = dp_netdev_flow_add(dp, &match, put->actions,
+                error = dp_netdev_flow_add(dp, &match, put->uid, put->actions,
                                            put->actions_len);
             } else {
                 error = EFBIG;
@@ -1530,16 +1554,14 @@ dpif_netdev_flow_del(struct dpif *dpif, const struct dpif_flow_del *del)
 {
     struct dp_netdev *dp = get_dp_netdev(dpif);
     struct dp_netdev_flow *netdev_flow;
-    struct flow key;
-    int error;
+    int error = 0;
 
-    error = dpif_netdev_flow_from_nlattrs(del->key, del->key_len, &key);
-    if (error) {
-        return error;
+    if (!del->uid) {
+        return EINVAL;
     }
 
     ovs_mutex_lock(&dp->flow_mutex);
-    netdev_flow = dp_netdev_find_flow(dp, &key);
+    netdev_flow = dp_netdev_find_flow(dp, del->uid);
     if (netdev_flow) {
         if (del->stats) {
             get_dpif_flow_stats(netdev_flow, del->stats);
@@ -1557,6 +1579,7 @@ struct dpif_netdev_flow_dump {
     struct dpif_flow_dump up;
     struct cmap_position pos;
     int status;
+    bool terse;
     struct ovs_mutex mutex;
 };
 
@@ -1567,7 +1590,7 @@ dpif_netdev_flow_dump_cast(struct dpif_flow_dump *dump)
 }
 
 static struct dpif_flow_dump *
-dpif_netdev_flow_dump_create(const struct dpif *dpif_)
+dpif_netdev_flow_dump_create(const struct dpif *dpif_, bool terse)
 {
     struct dpif_netdev_flow_dump *dump;
 
@@ -1575,6 +1598,7 @@ dpif_netdev_flow_dump_create(const struct dpif *dpif_)
     dpif_flow_dump_init(&dump->up, dpif_);
     memset(&dump->pos, 0, sizeof dump->pos);
     dump->status = 0;
+    dump->terse = terse;
     ovs_mutex_init(&dump->mutex);
 
     return &dump->up;
@@ -1634,6 +1658,7 @@ dpif_netdev_flow_dump_next(struct dpif_flow_dump_thread *thread_,
     struct dpif_netdev *dpif = dpif_netdev_cast(thread->up.dpif);
     struct dp_netdev_flow *netdev_flows[FLOW_DUMP_MAX_BATCH];
     struct dp_netdev *dp = get_dp_netdev(&dpif->dpif);
+    bool terse_dump;
     int n_flows = 0;
     int i;
 
@@ -1652,6 +1677,7 @@ dpif_netdev_flow_dump_next(struct dpif_flow_dump_thread *thread_,
                                                  node);
         }
     }
+    terse_dump = dump->terse;
     ovs_mutex_unlock(&dump->mutex);
 
     for (i = 0; i < n_flows; i++) {
@@ -1659,34 +1685,11 @@ dpif_netdev_flow_dump_next(struct dpif_flow_dump_thread *thread_,
         struct odputil_keybuf *keybuf = &thread->keybuf[i];
         struct dp_netdev_flow *netdev_flow = netdev_flows[i];
         struct dpif_flow *f = &flows[i];
-        struct dp_netdev_actions *dp_actions;
-        struct flow_wildcards wc;
-        struct ofpbuf buf;
-
-        minimask_expand(&netdev_flow->cr.match.mask, &wc);
-
-        /* Key. */
-        ofpbuf_use_stack(&buf, keybuf, sizeof *keybuf);
-        odp_flow_key_from_flow(&buf, &netdev_flow->flow, &wc.masks,
-                               netdev_flow->flow.in_port.odp_port, true);
-        f->key = ofpbuf_data(&buf);
-        f->key_len = ofpbuf_size(&buf);
-
-        /* Mask. */
-        ofpbuf_use_stack(&buf, maskbuf, sizeof *maskbuf);
-        odp_flow_key_from_mask(&buf, &wc.masks, &netdev_flow->flow,
-                               odp_to_u32(wc.masks.in_port.odp_port),
-                               SIZE_MAX, true);
-        f->mask = ofpbuf_data(&buf);
-        f->mask_len = ofpbuf_size(&buf);
-
-        /* Actions. */
-        dp_actions = dp_netdev_flow_get_actions(netdev_flow);
-        f->actions = dp_actions->actions;
-        f->actions_len = dp_actions->size;
+        struct ofpbuf key, mask;
 
-        /* Stats. */
-        get_dpif_flow_stats(netdev_flow, &f->stats);
+        ofpbuf_use_stack(&key, keybuf, sizeof *keybuf);
+        ofpbuf_use_stack(&mask, maskbuf, sizeof *maskbuf);
+        dp_netdev_flow_to_dpif_flow(netdev_flow, &key, &mask, f, terse_dump);
     }
 
     return n_flows;
@@ -2074,7 +2077,7 @@ dp_netdev_count_packet(struct dp_netdev *dp, enum dp_stat_type type, int cnt)
 
 static int
 dp_netdev_upcall(struct dp_netdev *dp, struct dpif_packet *packet_,
-                 struct flow *flow, struct flow_wildcards *wc,
+                 struct flow *flow, struct flow_wildcards *wc, ovs_u128 *uid,
                  enum dpif_upcall_type type, const struct nlattr *userdata,
                  struct ofpbuf *actions, struct ofpbuf *put_actions)
 {
@@ -2110,7 +2113,7 @@ dp_netdev_upcall(struct dp_netdev *dp, struct dpif_packet *packet_,
         ds_destroy(&ds);
     }
 
-    return dp->upcall_cb(packet, flow, type, userdata, actions, wc,
+    return dp->upcall_cb(packet, flow, uid, type, userdata, actions, wc,
                          put_actions, dp->upcall_aux);
 }
 
@@ -2300,6 +2303,7 @@ fast_path_processing(struct dp_netdev *dp, struct emc_cache *flow_cache,
         uint64_t actions_stub[512 / 8], slow_stub[512 / 8];
         struct ofpbuf actions, put_actions;
         struct match match;
+        ovs_u128 uid;
 
         ofpbuf_use_stub(&actions, actions_stub, sizeof actions_stub);
         ofpbuf_use_stub(&put_actions, slow_stub, sizeof slow_stub);
@@ -2327,8 +2331,9 @@ fast_path_processing(struct dp_netdev *dp, struct emc_cache *flow_cache,
             ofpbuf_clear(&actions);
             ofpbuf_clear(&put_actions);
 
+            dpif_flow_hash(dp->dpif, &match.flow, sizeof match.flow, &uid);
             error = dp_netdev_upcall(dp, packets[i], &match.flow, &match.wc,
-                                      DPIF_UC_MISS, NULL, &actions,
+                                      &uid, DPIF_UC_MISS, NULL, &actions,
                                       &put_actions);
             if (OVS_UNLIKELY(error && error != ENOSPC)) {
                 continue;
@@ -2354,7 +2359,8 @@ fast_path_processing(struct dp_netdev *dp, struct emc_cache *flow_cache,
              * reasonable. */
             if (OVS_LIKELY(error != ENOSPC)
                 && !dp_netdev_lookup_flow(dp, mfs[i])) {
-                dp_netdev_flow_add(dp, &match, ofpbuf_data(add_actions),
+                dp_netdev_flow_add(dp, &match, &uid,
+                                   ofpbuf_data(add_actions),
                                    ofpbuf_size(add_actions));
             }
             ovs_mutex_unlock(&dp->flow_mutex);
@@ -2448,6 +2454,7 @@ dp_execute_cb(void *aux_, struct dpif_packet **packets, int cnt,
             const struct nlattr *userdata;
             struct ofpbuf actions;
             struct flow flow;
+            ovs_u128 uid;
 
             userdata = nl_attr_find_nested(a, OVS_USERSPACE_ATTR_USERDATA);
             ofpbuf_init(&actions, 0);
@@ -2458,8 +2465,9 @@ dp_execute_cb(void *aux_, struct dpif_packet **packets, int cnt,
                 ofpbuf_clear(&actions);
 
                 flow_extract(&packets[i]->ofpbuf, md, &flow);
-                error = dp_netdev_upcall(dp, packets[i], &flow, NULL,
-                                         DPIF_UC_ACTION, userdata, &actions,
+                dpif_flow_hash(dp->dpif, &flow, sizeof flow, &uid);
+                error = dp_netdev_upcall(dp, packets[i], &flow, NULL, &uid,
+                                         DPIF_UC_ACTION, userdata,&actions,
                                          NULL);
                 if (!error || error == ENOSPC) {
                     dp_netdev_execute_actions(dp, &packets[i], 1, false, md,
diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h
index 89b32dd..a88b2ee 100644
--- a/lib/dpif-provider.h
+++ b/lib/dpif-provider.h
@@ -39,6 +39,7 @@ struct dpif {
     char *full_name;
     uint8_t netflow_engine_type;
     uint8_t netflow_engine_id;
+    uint32_t secret;             /* Random seed for upcall hash. */
 };
 
 void dpif_init(struct dpif *, const struct dpif_class *, const char *name,
@@ -256,8 +257,12 @@ struct dpif_class {
      *
      * 'flow_dump_create' and 'flow_dump_thread_create' must initialize the
      * structures that they return with dpif_flow_dump_init() and
-     * dpif_flow_dump_thread_init(), respectively. */
-    struct dpif_flow_dump *(*flow_dump_create)(const struct dpif *dpif);
+     * dpif_flow_dump_thread_init(), respectively.
+     *
+     * If 'terse' is true, then only UID and statistics will
+     * be returned in the dump. Otherwise, all fields will be returned. */
+    struct dpif_flow_dump *(*flow_dump_create)(const struct dpif *dpif,
+                                               bool terse);
     int (*flow_dump_destroy)(struct dpif_flow_dump *dump);
 
     struct dpif_flow_dump_thread *(*flow_dump_thread_create)(
diff --git a/lib/dpif.c b/lib/dpif.c
index bf2c5f9..42c2a49 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -87,6 +87,7 @@ static void log_flow_message(const struct dpif *dpif, int error,
                              const char *operation,
                              const struct nlattr *key, size_t key_len,
                              const struct nlattr *mask, size_t mask_len,
+                             const ovs_u128 *uid,
                              const struct dpif_flow_stats *stats,
                              const struct nlattr *actions, size_t actions_len);
 static void log_operation(const struct dpif *, const char *operation,
@@ -332,6 +333,7 @@ do_open(const char *name, const char *type, bool create, struct dpif **dpifp)
                                                name, create, &dpif);
     if (!error) {
         ovs_assert(dpif->dpif_class == registered_class->dpif_class);
+        dpif->secret = random_uint32();
     } else {
         dp_class_unref(registered_class);
     }
@@ -817,6 +819,14 @@ dpif_flow_stats_format(const struct dpif_flow_stats *stats, struct ds *s)
     }
 }
 
+/* Places the hash of the 'key_len' bytes starting at 'key' into '*hash'. */
+void
+dpif_flow_hash(const struct dpif *dpif, const void *key, size_t key_len,
+               ovs_u128 *hash)
+{
+    hash_words128(key, key_len, dpif->secret, hash);
+}
+
 /* Deletes all flows from 'dpif'.  Returns 0 if successful, otherwise a
  * positive errno value.  */
 int
@@ -834,7 +844,7 @@ dpif_flow_flush(struct dpif *dpif)
 /* A dpif_operate() wrapper for performing a single DPIF_OP_FLOW_GET. */
 int
 dpif_flow_get(struct dpif *dpif,
-              const struct nlattr *key, size_t key_len,
+              const struct nlattr *key, size_t key_len, const ovs_u128 *uid,
               struct ofpbuf *buf, struct dpif_flow *flow)
 {
     struct dpif_op *opp;
@@ -843,10 +853,14 @@ dpif_flow_get(struct dpif *dpif,
     op.type = DPIF_OP_FLOW_GET;
     op.u.flow_get.key = key;
     op.u.flow_get.key_len = key_len;
+    op.u.flow_get.uid = uid;
     op.u.flow_get.buffer = buf;
+
+    memset(flow, 0, sizeof *flow);
     op.u.flow_get.flow = flow;
     op.u.flow_get.flow->key = key;
     op.u.flow_get.flow->key_len = key_len;
+    op.u.flow_get.flow->uid = *uid;
 
     opp = &op;
     dpif_operate(dpif, &opp, 1);
@@ -860,7 +874,7 @@ 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)
+              const ovs_u128 *uid, struct dpif_flow_stats *stats)
 {
     struct dpif_op *opp;
     struct dpif_op op;
@@ -873,6 +887,7 @@ dpif_flow_put(struct dpif *dpif, enum dpif_flow_put_flags flags,
     op.u.flow_put.mask_len = mask_len;
     op.u.flow_put.actions = actions;
     op.u.flow_put.actions_len = actions_len;
+    op.u.flow_put.uid = uid;
     op.u.flow_put.stats = stats;
 
     opp = &op;
@@ -884,8 +899,8 @@ dpif_flow_put(struct dpif *dpif, enum dpif_flow_put_flags flags,
 /* A dpif_operate() wrapper for performing a single DPIF_OP_FLOW_DEL. */
 int
 dpif_flow_del(struct dpif *dpif,
-              const struct nlattr *key, size_t key_len,
-              struct dpif_flow_stats *stats)
+              const struct nlattr *key, size_t key_len, const ovs_u128 *uid,
+              struct dpif_flow_stats *stats, bool terse_dump)
 {
     struct dpif_op *opp;
     struct dpif_op op;
@@ -893,6 +908,8 @@ dpif_flow_del(struct dpif *dpif,
     op.type = DPIF_OP_FLOW_DEL;
     op.u.flow_del.key = key;
     op.u.flow_del.key_len = key_len;
+    op.u.flow_del.uid = uid;
+    op.u.flow_del.terse = terse_dump;
     op.u.flow_del.stats = stats;
 
     opp = &op;
@@ -902,14 +919,15 @@ dpif_flow_del(struct dpif *dpif,
 }
 
 /* Creates and returns a new 'struct dpif_flow_dump' for iterating through the
- * flows in 'dpif'.
+ * flows in 'dpif'. If 'terse' is true, then only UID and statistics will
+ * be returned in the dump. Otherwise, all fields will be returned.
  *
  * This function always successfully returns a dpif_flow_dump.  Error
  * reporting is deferred to dpif_flow_dump_destroy(). */
 struct dpif_flow_dump *
-dpif_flow_dump_create(const struct dpif *dpif)
+dpif_flow_dump_create(const struct dpif *dpif, bool terse)
 {
-    return dpif->dpif_class->flow_dump_create(dpif);
+    return dpif->dpif_class->flow_dump_create(dpif, terse);
 }
 
 /* Destroys 'dump', which must have been created with dpif_flow_dump_create().
@@ -974,7 +992,7 @@ dpif_flow_dump_next(struct dpif_flow_dump_thread *thread,
         for (f = flows; f < &flows[n] && should_log_flow_message(0); f++) {
             log_flow_message(dpif, 0, "flow_dump",
                              f->key, f->key_len, f->mask, f->mask_len,
-                             &f->stats, f->actions, f->actions_len);
+                             &f->uid, &f->stats, f->actions, f->actions_len);
         }
     } else {
         VLOG_DBG_RL(&dpmsg_rl, "%s: dumped all flows", dpif_name(dpif));
@@ -1455,7 +1473,7 @@ static void
 log_flow_message(const struct dpif *dpif, int error, const char *operation,
                  const struct nlattr *key, size_t key_len,
                  const struct nlattr *mask, size_t mask_len,
-                 const struct dpif_flow_stats *stats,
+                 const ovs_u128 *uid, const struct dpif_flow_stats *stats,
                  const struct nlattr *actions, size_t actions_len)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
@@ -1467,6 +1485,10 @@ log_flow_message(const struct dpif *dpif, int error, const char *operation,
     if (error) {
         ds_put_format(&ds, "(%s) ", ovs_strerror(error));
     }
+    if (uid) {
+        odp_format_uid(uid, &ds);
+        ds_put_cstr(&ds, " ");
+    }
     odp_flow_format(key, key_len, mask, mask_len, NULL, &ds, true);
     if (stats) {
         ds_put_cstr(&ds, ", ");
@@ -1500,7 +1522,7 @@ log_flow_put_message(struct dpif *dpif, const struct dpif_flow_put *put,
         }
         log_flow_message(dpif, error, ds_cstr(&s),
                          put->key, put->key_len, put->mask, put->mask_len,
-                         put->stats, put->actions, put->actions_len);
+                         put->uid, put->stats, put->actions, put->actions_len);
         ds_destroy(&s);
     }
 }
@@ -1511,7 +1533,8 @@ log_flow_del_message(struct dpif *dpif, const struct dpif_flow_del *del,
 {
     if (should_log_flow_message(error)) {
         log_flow_message(dpif, error, "flow_del", del->key, del->key_len,
-                         NULL, 0, !error ? del->stats : NULL, NULL, 0);
+                         NULL, 0, del->uid, !error ? del->stats : NULL,
+                         NULL, 0);
     }
 }
 
@@ -1566,7 +1589,7 @@ log_flow_get_message(const struct dpif *dpif, const struct dpif_flow_get *get,
         log_flow_message(dpif, error, "flow_get",
                          get->key, get->key_len,
                          get->flow->mask, get->flow->mask_len,
-                         &get->flow->stats,
+                         get->uid, &get->flow->stats,
                          get->flow->actions, get->flow->actions_len);
     }
 }
diff --git a/lib/dpif.h b/lib/dpif.h
index be1bc4f..e0f90b3 100644
--- a/lib/dpif.h
+++ b/lib/dpif.h
@@ -514,17 +514,22 @@ enum dpif_flow_put_flags {
     DPIF_FP_ZERO_STATS = 1 << 2 /* Zero the stats of an existing flow. */
 };
 
+void dpif_flow_hash(const struct dpif *, const void *key, size_t key_len,
+                    ovs_u128 *hash);
 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 *);
+                  const ovs_u128 *uid, struct dpif_flow_stats *);
+
 int dpif_flow_del(struct dpif *,
                   const struct nlattr *key, size_t key_len,
-                  struct dpif_flow_stats *);
+                  const ovs_u128 *uid, struct dpif_flow_stats *,
+                  bool terse_dump);
 int dpif_flow_get(struct dpif *,
                   const struct nlattr *key, size_t key_len,
+                  const ovs_u128 *uid,
                   struct ofpbuf *, struct dpif_flow *);
 
 /* Flow dumping interface
@@ -555,7 +560,7 @@ int dpif_flow_get(struct dpif *,
  *
  * All error reporting is deferred to the call to dpif_flow_dump_destroy().
  */
-struct dpif_flow_dump *dpif_flow_dump_create(const struct dpif *);
+struct dpif_flow_dump *dpif_flow_dump_create(const struct dpif *, bool terse);
 int dpif_flow_dump_destroy(struct dpif_flow_dump *);
 
 struct dpif_flow_dump_thread *dpif_flow_dump_thread_create(
@@ -570,6 +575,7 @@ struct dpif_flow {
     size_t mask_len;              /* 'mask' length in bytes. */
     const struct nlattr *actions; /* Actions, as OVS_ACTION_ATTR_ */
     size_t actions_len;           /* 'actions' length in bytes. */
+    ovs_u128 uid;                 /* Unique flow identifier. */
     struct dpif_flow_stats stats; /* Flow statistics. */
 };
 int dpif_flow_dump_next(struct dpif_flow_dump_thread *,
@@ -621,6 +627,7 @@ struct dpif_flow_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. */
+    const ovs_u128 *uid;            /* Unique flow identifier. */
 
     /* Output. */
     struct dpif_flow_stats *stats;  /* Optional flow statistics. */
@@ -629,8 +636,10 @@ struct dpif_flow_put {
 /* Delete a flow.
  *
  * The flow is specified by the Netlink attributes with types OVS_KEY_ATTR_* in
- * the 'key_len' bytes starting at 'key'.  Succeeds with status 0 if the flow
- * is deleted, or fails with ENOENT if the dpif does not contain such a flow.
+ * the 'key_len' bytes starting at 'key', or the Netlink attributes with
+ * types OVS_UID_ATTR_* in the 'uid_len' bytes starting at 'uid'. At least one
+ * of 'key' or 'uid' must be specified. Succeeds with status 0 if the flow is
+ * deleted, or fails with ENOENT if the dpif does not contain such a flow.
  *
  * If the operation succeeds, then 'stats', if nonnull, will be set to the
  * flow's statistics before its deletion. */
@@ -638,6 +647,9 @@ struct dpif_flow_del {
     /* Input. */
     const struct nlattr *key;       /* Flow to delete. */
     size_t key_len;                 /* Length of 'key' in bytes. */
+    const ovs_u128 *uid;            /* UID of flow to delete. */
+    bool terse;                     /* Skip unnecessary fields when transacting
+                                       to datapath. */
 
     /* Output. */
     struct dpif_flow_stats *stats;  /* Optional flow statistics. */
@@ -687,6 +699,7 @@ struct dpif_flow_get {
     /* Input. */
     const struct nlattr *key;       /* Flow to get. */
     size_t key_len;                 /* Length of 'key' in bytes. */
+    const ovs_u128 *uid;            /* UID of flow to get. */
     struct ofpbuf *buffer;          /* Storage for output parameters. */
 
     /* Output. */
@@ -733,6 +746,7 @@ struct dpif_upcall {
     struct ofpbuf packet;       /* Packet data. */
     struct nlattr *key;         /* Flow key. */
     size_t key_len;             /* Length of 'key' in bytes. */
+    ovs_u128 uid;               /* Unique flow identifier for 'key'. */
 
     /* DPIF_UC_ACTION only. */
     struct nlattr *userdata;    /* Argument to OVS_ACTION_ATTR_USERSPACE. */
@@ -741,9 +755,9 @@ struct dpif_upcall {
 
 /* A callback to process an upcall, currently implemented only by dpif-netdev.
  *
- * The caller provides the 'packet' and 'flow' to process, the 'type' of the
- * upcall, and if 'type' is DPIF_UC_ACTION then the 'userdata' attached to the
- * action.
+ * The caller provides the 'packet' and 'flow' to process, the corresponding
+ * 'uid' as generated by dpif_flow_hash(), the 'type' of the upcall, and if
+ * 'type' is DPIF_UC_ACTION then the 'userdata' attached to the action.
  *
  * The callback must fill in 'actions' with the datapath actions to apply to
  * 'packet'.  'wc' and 'put_actions' will either be both null or both nonnull.
@@ -758,6 +772,7 @@ struct dpif_upcall {
  * flow should be installed, or some otherwise a positive errno value. */
 typedef int upcall_callback(const struct ofpbuf *packet,
                             const struct flow *flow,
+                            ovs_u128 *uid,
                             enum dpif_upcall_type type,
                             const struct nlattr *userdata,
                             struct ofpbuf *actions,
diff --git a/lib/flow.h b/lib/flow.h
index e4c3b34..537f4b6 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -248,6 +248,12 @@ flow_hash(const struct flow *flow, uint32_t basis)
     return hash_words((const uint32_t *) flow, sizeof *flow / 4, basis);
 }
 
+static inline uint32_t
+flow_uid_hash(const ovs_u128 *uid, uint32_t basis)
+{
+    return hash_words((const uint32_t *) uid, sizeof *uid / 4, basis);
+}
+
 static inline uint16_t
 ofp_to_u16(ofp_port_t ofp_port)
 {
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 77e6ec5..f8bffb2 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -2851,6 +2851,61 @@ odp_flow_key_hash(const struct nlattr *key, size_t key_len)
                       key_len / sizeof(uint32_t), 0);
 }
 
+int
+odp_uid_from_nlattrs(const struct nlattr *nla, size_t nla_len, ovs_u128 *uid,
+                     uint32_t *flags)
+{
+    static const struct nl_policy ovs_uid_policy[] = {
+        [OVS_UID_ATTR_FLAGS] = { .type = NL_A_U32, .optional = true },
+        [OVS_UID_ATTR_ID] = { .type = NL_A_UNSPEC,
+                              .min_len = sizeof *uid }
+    };
+    struct nlattr *a[ARRAY_SIZE(ovs_uid_policy)];
+    const ovs_u128 *uidp;
+    struct ofpbuf buf;
+
+    if (!nla) {
+        return EINVAL;
+    }
+
+    ofpbuf_use_const(&buf, nla, nla_len);
+    if (!nl_policy_parse(&buf, 0, ovs_uid_policy, a, ARRAY_SIZE(a))) {
+        VLOG_WARN("Failed to parse UID_ATTRs (%p, len=%"PRIuSIZE")",
+                  nla, nla_len);
+        return EINVAL;
+    }
+
+    uidp = nl_attr_get_unspec(a[OVS_UID_ATTR_ID], sizeof *uid);
+    memcpy(uid, uidp, sizeof *uid);
+
+    if (flags) {
+        if (a[OVS_UID_ATTR_FLAGS]) {
+            *flags = nl_attr_get_u32(a[OVS_UID_ATTR_FLAGS]);
+        } else {
+            *flags = 0;
+        }
+    }
+    return 0;
+}
+
+void
+odp_uid_to_nlattrs(struct ofpbuf *buf, const ovs_u128 *uid, uint32_t flags)
+{
+    if (flags) {
+        nl_msg_put_u32(buf, OVS_UID_ATTR_FLAGS, flags);
+    }
+    if (uid) {
+        nl_msg_put_unspec(buf, OVS_UID_ATTR_ID, uid, sizeof *uid);
+    }
+}
+
+void
+odp_format_uid(const ovs_u128 *uid, struct ds *ds)
+{
+    ds_put_format(ds, "uid:%"PRIx32"%"PRIx32"%"PRIx32"%"PRIx32,
+                  uid->h[0], uid->h[1], uid->h[2], uid->h[3]);
+}
+
 static void
 log_odp_key_attributes(struct vlog_rate_limit *rl, const char *title,
                        uint64_t attrs, int out_of_range_attr,
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 11b54dd..23dcd07 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -142,6 +142,27 @@ struct odputil_keybuf {
     uint32_t keybuf[DIV_ROUND_UP(ODPUTIL_FLOW_KEY_BYTES, 4)];
 };
 
+/* The maximum number of bytes that odp_uid_from_uid() appends to a buffer.
+ * This is the upper bound on the length of a nlattr-formatted flow key that
+ * ovs-vswitchd fully understands.
+ *
+ * As with ODPUTIL_FLOW_KEY_BYTES, the datapath may have a different idea of a
+ * flow, so this isn't necessarily an upper bound on the length of a UID that
+ * the datapath can pass to ovs-vswitchd.
+ *
+ *                                     struct  pad  nl hdr  total
+ *                                     ------  ---  ------  -----
+ * OVS_UID_ATTR_FLAGS                     4     -      4      8
+ * OVS_UID_ATTR_UID                      16     -      4     20
+ *  ------------------------------------------------------------
+ *  total                                                    28
+ *
+ */
+#define ODPUTIL_FLOW_UID_BYTES 28
+struct odputil_uidbuf {
+    uint32_t uidbuf[DIV_ROUND_UP(ODPUTIL_FLOW_UID_BYTES, 4)];
+};
+
 enum odp_key_fitness odp_tun_key_from_attr(const struct nlattr *,
                                            struct flow_tnl *);
 
@@ -162,6 +183,8 @@ void odp_flow_key_from_mask(struct ofpbuf *, const struct flow *mask,
                             size_t max_mpls_depth, bool recirc);
 
 uint32_t odp_flow_key_hash(const struct nlattr *, size_t);
+void odp_uid_to_nlattrs(struct ofpbuf *, const ovs_u128 *, uint32_t flags);
+void odp_format_uid(const ovs_u128 *uid, struct ds *);
 
 /* Estimated space needed for metadata. */
 enum { ODP_KEY_METADATA_SIZE = 9 * 8 };
@@ -188,6 +211,8 @@ enum odp_key_fitness odp_flow_key_to_mask(const struct nlattr *key, size_t len,
                                           struct flow *mask,
                                           const struct flow *flow);
 const char *odp_key_fitness_to_string(enum odp_key_fitness);
+int odp_uid_from_nlattrs(const struct nlattr *, size_t,
+                         ovs_u128 *uid, uint32_t *flags);
 
 void commit_odp_tunnel_action(const struct flow *, struct flow *base,
                               struct ofpbuf *odp_actions);
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index b3bfaa8..947a225 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -98,8 +98,6 @@ struct udpif {
     struct dpif *dpif;                 /* Datapath handle. */
     struct dpif_backer *backer;        /* Opaque dpif_backer pointer. */
 
-    uint32_t secret;                   /* Random seed for upcall hash. */
-
     struct handler *handlers;          /* Upcall handlers. */
     size_t n_handlers;
 
@@ -115,6 +113,7 @@ struct udpif {
     struct dpif_flow_dump *dump;       /* DPIF flow dump state. */
     long long int dump_duration;       /* Duration of the last flow dump. */
     struct seq *dump_seq;              /* Increments each dump iteration. */
+    atomic_bool terse_dump;            /* If true, skip dumping flow attrs. */
 
     /* There are 'N_UMAPS' maps containing 'struct udpif_key' elements.
      *
@@ -157,6 +156,7 @@ struct upcall {
      * dpif-netdev.  If a modification is absolutely necessary, a const cast
      * may be used with other datapaths. */
     const struct flow *flow;       /* Parsed representation of the packet. */
+    const ovs_u128 *uid;           /* Unique identifier for 'flow'. */
     const struct ofpbuf *packet;   /* Packet associated with this upcall. */
     ofp_port_t in_port;            /* OpenFlow in port, or OFPP_NONE. */
 
@@ -204,6 +204,7 @@ struct udpif_key {
     const struct nlattr *mask;     /* Datapath flow mask. */
     size_t mask_len;               /* Length of 'mask'. */
     struct ofpbuf *actions;        /* Datapath flow actions as nlattrs. */
+    ovs_u128 uid;                  /* Unique flow identifier. */
     uint32_t hash;                 /* Pre-computed hash for 'key'. */
 
     struct ovs_mutex mutex;                   /* Guards the following. */
@@ -228,6 +229,7 @@ struct udpif_key {
 struct ukey_op {
     struct udpif_key *ukey;
     struct dpif_flow_stats stats; /* Stats for 'op'. */
+    struct odputil_uidbuf uid;    /* Storage for Netlink-formatted uid. */
     struct dpif_op dop;           /* Flow operation. */
 };
 
@@ -253,19 +255,21 @@ static void upcall_unixctl_disable_megaflows(struct unixctl_conn *, int argc,
                                              const char *argv[], void *aux);
 static void upcall_unixctl_enable_megaflows(struct unixctl_conn *, int argc,
                                             const char *argv[], void *aux);
+static void upcall_unixctl_disable_terse_dump(struct unixctl_conn *, int argc,
+                                              const char *argv[], void *aux);
+static void upcall_unixctl_enable_terse_dump(struct unixctl_conn *, int argc,
+                                             const char *argv[], void *aux);
 static void upcall_unixctl_set_flow_limit(struct unixctl_conn *conn, int argc,
                                             const char *argv[], void *aux);
 static void upcall_unixctl_dump_wait(struct unixctl_conn *conn, int argc,
                                      const char *argv[], void *aux);
 
-static struct udpif_key *ukey_new(const struct udpif *, struct upcall *);
+static struct udpif_key *ukey_new(struct upcall *);
 static bool ukey_install_start(struct udpif *, struct udpif_key *ukey);
 static void ukey_install_finish(struct udpif_key *ukey, int error);
 static bool ukey_install(struct udpif *udpif, struct udpif_key *ukey);
-static struct udpif_key *ukey_lookup(struct udpif *udpif, uint32_t hash,
-                                     const struct nlattr *key, size_t key_len);
-static int ukey_acquire(struct udpif *, const struct dpif_flow *,
-                        struct udpif_key **result, int *error);
+static struct udpif_key *ukey_lookup(struct udpif *udpif,
+                                     const ovs_u128 *uid);
 static void ukey_delete__(struct udpif_key *);
 static void ukey_delete(struct umap *, struct udpif_key *);
 static enum upcall_type classify_upcall(enum dpif_upcall_type type,
@@ -273,7 +277,8 @@ static enum upcall_type classify_upcall(enum dpif_upcall_type type,
 
 static int upcall_receive(struct upcall *, const struct dpif_backer *,
                           const struct ofpbuf *packet, enum dpif_upcall_type,
-                          const struct nlattr *userdata, const struct flow *);
+                          const struct nlattr *userdata, const struct flow *,
+                          const ovs_u128 *uid);
 static void upcall_uninit(struct upcall *);
 
 static upcall_callback upcall_cb;
@@ -293,6 +298,10 @@ udpif_create(struct dpif_backer *backer, struct dpif *dpif)
                                  upcall_unixctl_disable_megaflows, NULL);
         unixctl_command_register("upcall/enable-megaflows", "", 0, 0,
                                  upcall_unixctl_enable_megaflows, NULL);
+        unixctl_command_register("upcall/disable-terse-dump", "", 0, 0,
+                                 upcall_unixctl_disable_terse_dump, NULL);
+        unixctl_command_register("upcall/enable-terse-dump", "", 0, 0,
+                                 upcall_unixctl_enable_terse_dump, NULL);
         unixctl_command_register("upcall/set-flow-limit", "", 1, 1,
                                  upcall_unixctl_set_flow_limit, NULL);
         unixctl_command_register("revalidator/wait", "", 0, 0,
@@ -303,11 +312,11 @@ udpif_create(struct dpif_backer *backer, struct dpif *dpif)
     udpif->dpif = dpif;
     udpif->backer = backer;
     atomic_init(&udpif->flow_limit, MIN(ofproto_flow_limit, 10000));
-    udpif->secret = random_uint32();
     udpif->reval_seq = seq_create();
     udpif->dump_seq = seq_create();
     latch_init(&udpif->exit_latch);
     list_push_back(&all_udpifs, &udpif->list_node);
+    atomic_init(&udpif->terse_dump, false);
     atomic_init(&udpif->n_flows, 0);
     atomic_init(&udpif->n_flows_timestamp, LLONG_MIN);
     ovs_mutex_init(&udpif->n_flows_mutex);
@@ -423,6 +432,8 @@ udpif_start_threads(struct udpif *udpif, size_t n_handlers,
                 "handler", udpif_upcall_handler, handler);
         }
 
+        atomic_init(&udpif->terse_dump,
+                    dpif_backer_get_enable_uid(udpif->backer));
         dpif_enable_upcall(udpif->dpif);
 
         ovs_barrier_init(&udpif->reval_barrier, udpif->n_revalidators);
@@ -627,7 +638,8 @@ recv_upcalls(struct handler *handler)
         }
 
         error = upcall_receive(upcall, udpif->backer, &dupcall->packet,
-                               dupcall->type, dupcall->userdata, &flow);
+                               dupcall->type, dupcall->userdata, &flow,
+                               &dupcall->uid);
         if (error) {
             if (error == ENODEV) {
                 /* Received packet on datapath port for which we couldn't
@@ -635,7 +647,8 @@ recv_upcalls(struct handler *handler)
                  * while traffic is being received.  Print a rate-limited
                  * message in case it happens frequently. */
                 dpif_flow_put(udpif->dpif, DPIF_FP_CREATE, dupcall->key,
-                              dupcall->key_len, NULL, 0, NULL, 0, NULL);
+                              dupcall->key_len, NULL, 0, NULL, 0,
+                              &dupcall->uid, NULL);
                 VLOG_INFO_RL(&rl, "received packet on unassociated datapath "
                              "port %"PRIu32, flow.in_port.odp_port);
             }
@@ -644,6 +657,7 @@ recv_upcalls(struct handler *handler)
 
         upcall->key = dupcall->key;
         upcall->key_len = dupcall->key_len;
+        upcall->uid = &dupcall->uid;
 
         upcall->out_tun_key = dupcall->out_tun_key;
 
@@ -713,7 +727,10 @@ udpif_revalidator(void *arg)
 
             start_time = time_msec();
             if (!udpif->reval_exit) {
-                udpif->dump = dpif_flow_dump_create(udpif->dpif);
+                bool terse_dump;
+
+                atomic_read_relaxed(&udpif->terse_dump, &terse_dump);
+                udpif->dump = dpif_flow_dump_create(udpif->dpif, terse_dump);
             }
         }
 
@@ -851,7 +868,8 @@ compose_slow_path(struct udpif *udpif, struct xlate_out *xout,
 static int
 upcall_receive(struct upcall *upcall, const struct dpif_backer *backer,
                const struct ofpbuf *packet, enum dpif_upcall_type type,
-               const struct nlattr *userdata, const struct flow *flow)
+               const struct nlattr *userdata, const struct flow *flow,
+               const ovs_u128 *uid)
 {
     int error;
 
@@ -864,6 +882,7 @@ upcall_receive(struct upcall *upcall, const struct dpif_backer *backer,
 
     upcall->flow = flow;
     upcall->packet = packet;
+    upcall->uid = uid;
     upcall->type = type;
     upcall->userdata = userdata;
     ofpbuf_init(&upcall->put_actions, 0);
@@ -936,7 +955,7 @@ upcall_xlate(struct udpif *udpif, struct upcall *upcall,
                           &upcall->put_actions);
     }
 
-    upcall->ukey = ukey_new(udpif, upcall);
+    upcall->ukey = ukey_new(upcall);
 }
 
 static void
@@ -952,7 +971,7 @@ upcall_uninit(struct upcall *upcall)
 }
 
 static int
-upcall_cb(const struct ofpbuf *packet, const struct flow *flow,
+upcall_cb(const struct ofpbuf *packet, const struct flow *flow, ovs_u128 *uid,
           enum dpif_upcall_type type, const struct nlattr *userdata,
           struct ofpbuf *actions, struct flow_wildcards *wc,
           struct ofpbuf *put_actions, void *aux)
@@ -967,7 +986,7 @@ upcall_cb(const struct ofpbuf *packet, const struct flow *flow,
     atomic_read_relaxed(&udpif->flow_limit, &flow_limit);
 
     error = upcall_receive(&upcall, udpif->backer, packet, type, userdata,
-                           flow);
+                           flow, uid);
     if (error) {
         return error;
     }
@@ -992,12 +1011,13 @@ upcall_cb(const struct ofpbuf *packet, const struct flow *flow,
         }
     }
 
+    atomic_read_relaxed(&udpif->flow_limit, &flow_limit);
     if (udpif_get_n_flows(udpif) >= flow_limit) {
         error = ENOSPC;
         goto out;
     }
 
-    if (!ukey_install(udpif, upcall.ukey)) {
+    if (upcall.ukey && !ukey_install(udpif, upcall.ukey)) {
         error = ENOSPC;
     }
 
@@ -1136,6 +1156,7 @@ handle_upcalls(struct udpif *udpif, struct upcall *upcalls,
 
             upcall->ukey = NULL;
             op = &ops[n_ops++];
+
             op->ukey = ukey;
             op->dop.type = DPIF_OP_FLOW_PUT;
             op->dop.u.flow_put.flags = DPIF_FP_CREATE;
@@ -1143,6 +1164,7 @@ handle_upcalls(struct udpif *udpif, struct upcall *upcalls,
             op->dop.u.flow_put.key_len = ukey->key_len;
             op->dop.u.flow_put.mask = ukey->mask;
             op->dop.u.flow_put.mask_len = ukey->mask_len;
+            op->dop.u.flow_put.uid = upcall->uid;
             op->dop.u.flow_put.stats = NULL;
             op->dop.u.flow_put.actions = ofpbuf_data(ukey->actions);
             op->dop.u.flow_put.actions_len = ofpbuf_size(ukey->actions);
@@ -1189,15 +1211,21 @@ handle_upcalls(struct udpif *udpif, struct upcall *upcalls,
     }
 }
 
+static uint32_t
+get_uid_hash(const ovs_u128 *uid)
+{
+    return uid->h[0];
+}
+
 static struct udpif_key *
-ukey_lookup(struct udpif *udpif, uint32_t hash, const struct nlattr *key,
-            size_t key_len)
+ukey_lookup(struct udpif *udpif, const ovs_u128 *uid)
 {
     struct udpif_key *ukey;
-    struct cmap *cmap = &udpif->ukeys[hash % N_UMAPS].cmap;
+    int idx = get_uid_hash(uid) % N_UMAPS;
+    struct cmap *cmap = &udpif->ukeys[idx].cmap;
 
-    CMAP_FOR_EACH_WITH_HASH (ukey, cmap_node, hash, cmap) {
-        if (ukey->key_len == key_len && !memcmp(ukey->key, key, key_len)) {
+    CMAP_FOR_EACH_WITH_HASH (ukey, cmap_node, get_uid_hash(uid), cmap) {
+        if (!memcmp(&ukey->uid, uid, sizeof *uid)) {
             return ukey;
         }
     }
@@ -1205,7 +1233,7 @@ ukey_lookup(struct udpif *udpif, uint32_t hash, const struct nlattr *key,
 }
 
 static struct udpif_key *
-ukey_new(const struct udpif *udpif, struct upcall *upcall)
+ukey_new(struct upcall *upcall)
     OVS_NO_THREAD_SAFETY_ANALYSIS
 {
     struct udpif_key *ukey = xzalloc(sizeof *ukey);
@@ -1220,8 +1248,10 @@ ukey_new(const struct udpif *udpif, struct upcall *upcall)
         odp_flow_key_from_flow(&key, upcall->flow, &upcall->xout.wc.masks,
                                upcall->flow->in_port.odp_port, recirc);
     }
+    ukey->key = ofpbuf_data(&key);
+    ukey->key_len = ofpbuf_size(&key);
 
-    atomic_read(&enable_megaflows, &megaflow);
+    atomic_read_relaxed(&enable_megaflows, &megaflow);
     ofpbuf_use_stack(&mask, &ukey->maskbuf, sizeof ukey->maskbuf);
     if (megaflow) {
         size_t max_mpls;
@@ -1231,12 +1261,10 @@ ukey_new(const struct udpif *udpif, struct upcall *upcall)
                                upcall->flow, UINT32_MAX, max_mpls,
                                recirc);
     }
-
-    ukey->key = ofpbuf_data(&key);
-    ukey->key_len = ofpbuf_size(&key);
     ukey->mask = ofpbuf_data(&mask);
     ukey->mask_len = ofpbuf_size(&mask);
-    ukey->hash = hash_bytes(ukey->key, ukey->key_len, udpif->secret);
+    memcpy(&ukey->uid, upcall->uid, sizeof ukey->uid);
+    ukey->hash = get_uid_hash(&ukey->uid);
     ukey->actions = ofpbuf_clone(&upcall->put_actions);
 
     ovs_mutex_init(&ukey->mutex);
@@ -1263,7 +1291,7 @@ ukey_install_start(struct udpif *udpif, struct udpif_key *ukey)
     idx = ukey->hash % N_UMAPS;
     umap = &udpif->ukeys[idx];
     ovs_mutex_lock(&umap->mutex);
-    if (!ukey_lookup(udpif, ukey->hash, ukey->key, ukey->key_len)) {
+    if (!ukey_lookup(udpif, &ukey->uid)) {
         ovs_mutex_lock(&ukey->mutex);
         cmap_insert(&umap->cmap, &ukey->cmap_node, ukey->hash);
         locked = true;
@@ -1314,15 +1342,18 @@ ukey_acquire(struct udpif *udpif, const struct dpif_flow *flow,
 {
     struct udpif_key *ukey;
     int retval;
-    uint32_t hash;
 
-    hash = hash_bytes(flow->key, flow->key_len, udpif->secret);
-    ukey = ukey_lookup(udpif, hash, flow->key, flow->key_len);
+    ukey = ukey_lookup(udpif, &flow->uid);
     if (ukey) {
         retval = ovs_mutex_trylock(&ukey->mutex);
     } else {
-        VLOG_INFO("Dumped flow from datapath with no corresponding ukey: 0x%"
-                  PRIx32, hash);
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 60);
+        struct ds ds = DS_EMPTY_INITIALIZER;
+
+        ds_put_cstr(&ds, "Dumped flow from datapath with no corresponding "
+                    "ukey: (");
+        odp_format_uid(&flow->uid, &ds);
+        VLOG_INFO_RL(&rl, "%s)", ds_cstr(&ds));
         retval = ENOENT;
     }
 
@@ -1514,25 +1545,40 @@ exit:
     if (ok) {
         ukey->reval_seq = reval_seq;
     }
-    if (netflow) {
-        if (!ok) {
-            netflow_flow_clear(netflow, &flow);
-        }
-        netflow_unref(netflow);
+    if (netflow && !ok) {
+        netflow_flow_clear(netflow, &flow);
     }
     xlate_out_uninit(xoutp);
     return ok;
 }
 
 static void
-delete_op_init(struct ukey_op *op, const struct nlattr *key, size_t key_len,
-               struct udpif_key *ukey)
+delete_op_init(struct ukey_op *op, struct udpif_key *ukey,
+               const struct dpif_flow *flow, bool terse_dump)
 {
+    struct ofpbuf buf;
+
+    ovs_assert(ukey || flow);
+    ofpbuf_use_stack(&buf, &op->uid, sizeof op->uid);
+
     op->ukey = ukey;
     op->dop.type = DPIF_OP_FLOW_DEL;
-    op->dop.u.flow_del.key = key;
-    op->dop.u.flow_del.key_len = key_len;
     op->dop.u.flow_del.stats = &op->stats;
+
+    if (ukey) {
+        op->dop.u.flow_del.key = ukey->key;
+        op->dop.u.flow_del.key_len = ukey->key_len;
+        op->dop.u.flow_del.uid = &ukey->uid;
+        op->dop.u.flow_del.terse = terse_dump;
+    } else {
+        /* Unusual case. Reuse the flow attributes that were passed up from
+         * the datapath. We need the flow key to be returned, so don't
+         * send any skip flags. */
+        op->dop.u.flow_del.key = flow->key;
+        op->dop.u.flow_del.key_len = flow->key_len;
+        op->dop.u.flow_del.uid = &flow->uid;
+        op->dop.u.flow_del.terse = false;
+    }
 }
 
 static void
@@ -1570,8 +1616,12 @@ push_ukey_ops__(struct udpif *udpif, struct ukey_op *ops, size_t n_ops)
             struct netflow *netflow;
             ofp_port_t ofp_in_port;
             struct flow flow;
+            const struct nlattr *key;
+            size_t key_len;
             int error;
 
+            key = op->dop.u.flow_del.key;
+            key_len = op->dop.u.flow_del.key_len;
             if (op->ukey) {
                 ovs_mutex_lock(&op->ukey->mutex);
                 if (op->ukey->xcache) {
@@ -1580,10 +1630,11 @@ push_ukey_ops__(struct udpif *udpif, struct ukey_op *ops, size_t n_ops)
                     continue;
                 }
                 ovs_mutex_unlock(&op->ukey->mutex);
+                key = op->ukey->key;
+                key_len = op->ukey->key_len;
             }
 
-            if (odp_flow_key_to_flow(op->dop.u.flow_del.key,
-                                     op->dop.u.flow_del.key_len, &flow)
+            if (odp_flow_key_to_flow(key, key_len, &flow)
                 == ODP_FIT_ERROR) {
                 continue;
             }
@@ -1629,10 +1680,12 @@ revalidate(struct revalidator *revalidator)
     struct dpif_flow_dump_thread *dump_thread;
     uint64_t dump_seq, reval_seq;
     unsigned int flow_limit;
+    bool terse_dump;
 
     dump_seq = seq_read(udpif->dump_seq);
     reval_seq = seq_read(udpif->reval_seq);
     atomic_read_relaxed(&udpif->flow_limit, &flow_limit);
+    atomic_read_relaxed(&udpif->terse_dump, &terse_dump);
     dump_thread = dpif_flow_dump_thread_create(udpif->dump);
     for (;;) {
         struct ukey_op ops[REVALIDATE_MAX_BATCH];
@@ -1682,7 +1735,7 @@ revalidate(struct revalidator *revalidator)
                      * processing it.*/
                     COVERAGE_INC(upcall_ukey_contention);
                 } else {
-                    delete_op_init(&ops[n_ops++], f->key, f->key_len, ukey);
+                    delete_op_init(&ops[n_ops++], ukey, f, terse_dump);
                 }
                 continue;
             }
@@ -1708,7 +1761,7 @@ revalidate(struct revalidator *revalidator)
             ukey->flow_exists = keep;
 
             if (!keep) {
-                delete_op_init(&ops[n_ops++], ukey->key, ukey->key_len, ukey);
+                delete_op_init(&ops[n_ops++], ukey, NULL, terse_dump);
             }
             ovs_mutex_unlock(&ukey->mutex);
         }
@@ -1744,10 +1797,12 @@ revalidator_sweep__(struct revalidator *revalidator, bool purge)
     struct udpif *udpif;
     uint64_t dump_seq, reval_seq;
     int slice;
+    bool terse_dump;
 
     udpif = revalidator->udpif;
     dump_seq = seq_read(udpif->dump_seq);
     reval_seq = seq_read(udpif->reval_seq);
+    atomic_read_relaxed(&udpif->terse_dump, &terse_dump);
     slice = revalidator - udpif->revalidators;
     ovs_assert(slice < udpif->n_revalidators);
 
@@ -1777,7 +1832,7 @@ revalidator_sweep__(struct revalidator *revalidator, bool purge)
                                                        ukey)))) {
                 struct ukey_op *op = &ops[n_ops++];
 
-                delete_op_init(op, ukey->key, ukey->key_len, ukey);
+                delete_op_init(op, ukey, NULL, terse_dump);
                 if (n_ops == REVALIDATE_MAX_BATCH) {
                     push_ukey_ops(udpif, umap, ops, n_ops);
                     n_ops = 0;
@@ -1817,15 +1872,23 @@ upcall_unixctl_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
 
     LIST_FOR_EACH (udpif, list_node, &all_udpifs) {
         unsigned int flow_limit;
+        bool terse_dump;
         size_t i;
 
         atomic_read_relaxed(&udpif->flow_limit, &flow_limit);
+        atomic_read_relaxed(&udpif->terse_dump, &terse_dump);
 
         ds_put_format(&ds, "%s:\n", dpif_name(udpif->dpif));
         ds_put_format(&ds, "\tflows         : (current %lu)"
             " (avg %u) (max %u) (limit %u)\n", udpif_get_n_flows(udpif),
             udpif->avg_n_flows, udpif->max_n_flows, flow_limit);
         ds_put_format(&ds, "\tdump duration : %lldms\n", udpif->dump_duration);
+        ds_put_format(&ds, "\tterse dump : ");
+        if (terse_dump) {
+            ds_put_format(&ds, "enabled\n");
+        } else {
+            ds_put_format(&ds, "disabled\n");
+        }
         ds_put_char(&ds, '\n');
 
         for (i = 0; i < n_revalidators; i++) {
@@ -1873,6 +1936,42 @@ upcall_unixctl_enable_megaflows(struct unixctl_conn *conn,
     unixctl_command_reply(conn, "megaflows enabled");
 }
 
+/* Disable skipping flow attributes during flow dump/delete.
+ *
+ * This command is only needed for advanced debugging, so it's not
+ * documented in the man page. */
+static void
+upcall_unixctl_disable_terse_dump(struct unixctl_conn *conn,
+                                  int argc OVS_UNUSED,
+                                  const char *argv[] OVS_UNUSED,
+                                  void *aux OVS_UNUSED)
+{
+    struct udpif *udpif;
+
+    LIST_FOR_EACH (udpif, list_node, &all_udpifs) {
+        atomic_store(&udpif->terse_dump, false);
+    }
+    unixctl_command_reply(conn, "Datapath dumping tersely using UID disabled");
+}
+
+/* Re-enable skipping flow attributes during flow dump/delete.
+ *
+ * This command is only needed for advanced debugging, so it's not
+ * documented in the man page. */
+static void
+upcall_unixctl_enable_terse_dump(struct unixctl_conn *conn,
+                                 int argc OVS_UNUSED,
+                                 const char *argv[] OVS_UNUSED,
+                                 void *aux OVS_UNUSED)
+{
+    struct udpif *udpif;
+
+    LIST_FOR_EACH (udpif, list_node, &all_udpifs) {
+        atomic_store(&udpif->terse_dump, true);
+    }
+    unixctl_command_reply(conn, "Datapath dumping tersely using UID enabled");
+}
+
 /* Set the flow limit.
  *
  * This command is only needed for advanced debugging, so it's not
diff --git a/ofproto/ofproto-dpif-upcall.h b/ofproto/ofproto-dpif-upcall.h
index ec19bd0..4d0f382 100644
--- a/ofproto/ofproto-dpif-upcall.h
+++ b/ofproto/ofproto-dpif-upcall.h
@@ -20,6 +20,7 @@
 struct dpif;
 struct dpif_backer;
 struct dpif_upcall;
+struct nlattr;
 struct ofpbuf;
 struct seq;
 struct simap;
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 6a59098..40613bb 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -280,6 +280,9 @@ struct dpif_backer {
     /* Maximum number of MPLS label stack entries that the datapath supports
      * in a match */
     size_t max_mpls_depth;
+
+    /* Support for userspace-generated unique flow ids. */
+    bool enable_uid;
 };
 
 /* All existing ofproto_backer instances, indexed by ofproto->up.type. */
@@ -362,6 +365,18 @@ ofproto_dpif_get_enable_recirc(const struct ofproto_dpif *ofproto)
     return ofproto->backer->enable_recirc;
 }
 
+bool
+ofproto_dpif_get_enable_uid(const struct ofproto_dpif *ofproto)
+{
+    return ofproto->backer->enable_uid;
+}
+
+bool
+dpif_backer_get_enable_uid(const struct dpif_backer *backer)
+{
+    return backer->enable_uid;
+}
+
 static struct ofport_dpif *get_ofp_port(const struct ofproto_dpif *ofproto,
                                         ofp_port_t ofp_port);
 static void ofproto_trace(struct ofproto_dpif *, struct flow *,
@@ -849,6 +864,7 @@ static bool check_variable_length_userdata(struct dpif_backer *backer);
 static size_t check_max_mpls_depth(struct dpif_backer *backer);
 static bool check_recirc(struct dpif_backer *backer);
 static bool check_masked_set_action(struct dpif_backer *backer);
+static bool check_uid(struct dpif_backer *backer);
 
 static int
 open_dpif_backer(const char *type, struct dpif_backer **backerp)
@@ -946,6 +962,7 @@ open_dpif_backer(const char *type, struct dpif_backer **backerp)
     backer->max_mpls_depth = check_max_mpls_depth(backer);
     backer->masked_set_action = check_masked_set_action(backer);
     backer->rid_pool = recirc_id_pool_create();
+    backer->enable_uid = check_uid(backer);
 
     error = dpif_recv_set(backer->dpif, backer->recv_set_enable);
     if (error) {
@@ -980,6 +997,7 @@ check_recirc(struct dpif_backer *backer)
     struct flow flow;
     struct odputil_keybuf keybuf;
     struct ofpbuf key;
+    ovs_u128 uid;
     int error;
     bool enable_recirc = false;
 
@@ -990,9 +1008,10 @@ check_recirc(struct dpif_backer *backer)
     ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
     odp_flow_key_from_flow(&key, &flow, NULL, 0, true);
 
+    dpif_flow_hash(backer->dpif, ofpbuf_data(&key), ofpbuf_size(&key), &uid);
     error = dpif_flow_put(backer->dpif, DPIF_FP_CREATE,
                           ofpbuf_data(&key), ofpbuf_size(&key), NULL, 0, NULL,
-                          0, NULL);
+                          0, &uid, NULL);
     if (error && error != EEXIST) {
         if (error != EINVAL) {
             VLOG_WARN("%s: Reciculation flow probe failed (%s)",
@@ -1002,7 +1021,7 @@ check_recirc(struct dpif_backer *backer)
     }
 
     error = dpif_flow_del(backer->dpif, ofpbuf_data(&key), ofpbuf_size(&key),
-                          NULL);
+                          &uid, NULL, false);
     if (error) {
         VLOG_WARN("%s: failed to delete recirculation feature probe flow",
                   dpif_name(backer->dpif));
@@ -1022,6 +1041,65 @@ done:
     return enable_recirc;
 }
 
+/* Tests whether 'backer''s datapath supports userspace flow ids. Only newer
+ * datapaths support OVS_FLOW_ATTR_UID in flow commands. We can skip
+ * serializing some flow attributes for datapaths that support this feature.
+ *
+ * Returns true if 'backer' supports UID for flow operations.
+ * Returns false if 'backer' does not support UID. */
+static bool
+check_uid(struct dpif_backer *backer)
+{
+    struct flow flow;
+    struct odputil_keybuf keybuf;
+    struct ofpbuf key, replybuf;
+    struct dpif_flow reply;
+    uint64_t stub[DPIF_FLOW_BUFSIZE / 8];
+    ovs_u128 uid;
+    int error;
+    bool enable_uid = false;
+
+    memset(&flow, 0, sizeof flow);
+    flow.dl_type = htons(0x1234);
+
+    ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
+    odp_flow_key_from_flow(&key, &flow, NULL, 0, true);
+    dpif_flow_hash(backer->dpif, ofpbuf_data(&key), ofpbuf_size(&key), &uid);
+    error = dpif_flow_put(backer->dpif, DPIF_FP_CREATE,
+                          ofpbuf_data(&key), ofpbuf_size(&key), NULL, 0, NULL,
+                          0, &uid, NULL);
+
+    if (error && error != EEXIST) {
+        VLOG_WARN("%s: UID feature probe failed (%s).",
+                  dpif_name(backer->dpif), ovs_strerror(error));
+        goto done;
+    }
+
+    ofpbuf_use_stack(&replybuf, &stub, sizeof stub);
+    error = dpif_flow_get(backer->dpif, NULL, 0, &uid, &replybuf, &reply);
+    if (!error && !memcmp(&uid, &reply.uid, sizeof uid)) {
+        enable_uid = true;
+    }
+
+    error = dpif_flow_del(backer->dpif, ofpbuf_data(&key), ofpbuf_size(&key),
+                          &uid, NULL, false);
+    if (error) {
+        VLOG_WARN("%s: failed to delete UID feature probe flow",
+                  dpif_name(backer->dpif));
+    }
+
+done:
+    if (enable_uid) {
+        VLOG_INFO("%s: Datapath supports userspace flow ids",
+                  dpif_name(backer->dpif));
+    } else {
+        VLOG_INFO("%s: Datapath does not support userspace flow ids",
+                  dpif_name(backer->dpif));
+    }
+
+    return enable_uid;
+}
+
 /* Tests whether 'backer''s datapath supports variable-length
  * OVS_USERSPACE_ATTR_USERDATA in OVS_ACTION_ATTR_USERSPACE actions.  We need
  * to disable some features on older datapaths that don't support this
@@ -1109,6 +1187,7 @@ check_max_mpls_depth(struct dpif_backer *backer)
     for (n = 0; n < FLOW_MAX_MPLS_LABELS; n++) {
         struct odputil_keybuf keybuf;
         struct ofpbuf key;
+        ovs_u128 uid;
         int error;
 
         memset(&flow, 0, sizeof flow);
@@ -1117,10 +1196,12 @@ check_max_mpls_depth(struct dpif_backer *backer)
 
         ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
         odp_flow_key_from_flow(&key, &flow, NULL, 0, false);
+        dpif_flow_hash(backer->dpif, ofpbuf_data(&key), ofpbuf_size(&key),
+                       &uid);
 
         error = dpif_flow_put(backer->dpif, DPIF_FP_CREATE,
                               ofpbuf_data(&key), ofpbuf_size(&key), NULL, 0,
-                              NULL, 0, NULL);
+                              NULL, 0, &uid, NULL);
         if (error && error != EEXIST) {
             if (error != EINVAL) {
                 VLOG_WARN("%s: MPLS stack length feature probe failed (%s)",
@@ -1130,7 +1211,7 @@ check_max_mpls_depth(struct dpif_backer *backer)
         }
 
         error = dpif_flow_del(backer->dpif, ofpbuf_data(&key),
-                              ofpbuf_size(&key), NULL);
+                              ofpbuf_size(&key), &uid, NULL, false);
         if (error) {
             VLOG_WARN("%s: failed to delete MPLS feature probe flow",
                       dpif_name(backer->dpif));
@@ -4923,7 +5004,7 @@ ofproto_unixctl_dpif_dump_flows(struct unixctl_conn *conn,
     }
 
     ds_init(&ds);
-    flow_dump = dpif_flow_dump_create(ofproto->backer->dpif);
+    flow_dump = dpif_flow_dump_create(ofproto->backer->dpif, 0);
     flow_dump_thread = dpif_flow_dump_thread_create(flow_dump);
     while (dpif_flow_dump_next(flow_dump_thread, &f, 1)) {
         struct flow flow;
@@ -4933,6 +5014,8 @@ ofproto_unixctl_dpif_dump_flows(struct unixctl_conn *conn,
             continue;
         }
 
+        odp_format_uid(&f.uid, &ds);
+        ds_put_cstr(&ds, " ");
         odp_flow_format(f.key, f.key_len, f.mask, f.mask_len,
                         &portno_names, &ds, verbosity);
         ds_put_cstr(&ds, ", ");
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index a8c5d48..7db18d4 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -87,6 +87,8 @@ enum rule_dpif_lookup_verdict {
 
 size_t ofproto_dpif_get_max_mpls_depth(const struct ofproto_dpif *);
 bool ofproto_dpif_get_enable_recirc(const struct ofproto_dpif *);
+bool ofproto_dpif_get_enable_uid(const struct ofproto_dpif *);
+bool dpif_backer_get_enable_uid(const struct dpif_backer *);
 
 uint8_t rule_dpif_lookup(struct ofproto_dpif *, struct flow *,
                          struct flow_wildcards *, struct rule_dpif **rule,
diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
index 83a4e61..2a353ed 100644
--- a/tests/dpif-netdev.at
+++ b/tests/dpif-netdev.at
@@ -3,6 +3,7 @@ AT_BANNER([dpif-netdev])
 # Strips out uninteresting parts of flow output, as well as parts
 # that vary from one run to another (e.g., timing and bond actions).
 m4_define([STRIP_XOUT], [[sed '
+    s/uid:[0-9a-f]* //
     s/used:[0-9]*\.[0-9]*/used:0.0/
     s/actions:.*/actions: <del>/
     s/packets:[0-9]*/packets:0/
@@ -93,6 +94,8 @@ OVS_VSWITCHD_START(
   [add-port br0 p1 -- set interface p1 type=dummy options:pstream=punix:$OVS_RUNDIR/p0.sock
    set bridge br0 datapath-type=dummy other-config:datapath-id=1234 \
                   fail-mode=secure])
+AT_CHECK([ovs-appctl upcall/disable-terse-dump], [0], [Datapath dumping tersely using UID disabled
+], [])
 AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg])
 
 AT_CHECK([ovs-ofctl add-flow br0 action=normal])
@@ -109,6 +112,8 @@ skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(1),eth(src=50:54
 # Now, the same again without megaflows.
 AT_CHECK([ovs-appctl upcall/disable-megaflows], [0], [megaflows disabled
 ])
+AT_CHECK([ovs-appctl upcall/disable-terse-dump], [0], [Datapath dumping tersely using UID disabled
+], [])
 AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
 sleep 1
 
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 107f363..91fcc4d 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -4626,21 +4626,21 @@ AT_CHECK([ovs-appctl netdev-dummy/receive p1 'in_port(1),eth(src=50:54:00:00:00:
 AT_CHECK([ovs-appctl netdev-dummy/receive p2 'in_port(2),eth(src=50:54:00:00:00:07,dst=50:54:00:00:00:05),eth_type(0x0800),ipv4(src=192.168.0.2,dst=192.168.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=0,code=0)'])
 AT_CHECK([ovs-appctl netdev-dummy/receive p3 'in_port(3),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0)'])
 ovs-appctl revalidator/wait
-AT_CHECK([ovs-appctl dpif/dump-flows br0 | sort | STRIP_USED], [0], [dnl
+AT_CHECK([ovs-appctl dpif/dump-flows br0 | STRIP_UID | STRIP_USED | sort], [0], [dnl
 skb_priority(0),recirc_id(0),in_port(1),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:drop
 skb_priority(0),recirc_id(0),in_port(2),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:drop
 ])
 
-AT_CHECK([ovs-appctl dpif/dump-flows br1 | sort | STRIP_USED], [0], [dnl
+AT_CHECK([ovs-appctl dpif/dump-flows br1 | STRIP_UID | STRIP_USED | sort], [0], [dnl
 skb_priority(0),recirc_id(0),in_port(3),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:drop
 ])
 
-AT_CHECK([ovs-appctl dpif/dump-flows -m br0 | sort | STRIP_USED], [0], [dnl
+AT_CHECK([ovs-appctl dpif/dump-flows -m br0 | STRIP_UID | STRIP_USED | sort], [0], [dnl
 skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(p1),eth(src=50:54:00:00:00:05/00:00:00:00:00:00,dst=50:54:00:00:00:07/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=192.168.0.1/0.0.0.0,dst=192.168.0.2/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no),icmp(type=8/0,code=0/0), packets:0, bytes:0, used:never, actions:drop
 skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(p2),eth(src=50:54:00:00:00:07/00:00:00:00:00:00,dst=50:54:00:00:00:05/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=192.168.0.2/0.0.0.0,dst=192.168.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no),icmp(type=0/0,code=0/0), packets:0, bytes:0, used:never, actions:drop
 ])
 
-AT_CHECK([ovs-appctl dpif/dump-flows -m br1 | sort | STRIP_USED], [0], [dnl
+AT_CHECK([ovs-appctl dpif/dump-flows -m br1 | STRIP_UID | STRIP_USED | sort], [0], [dnl
 skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(p3),eth(src=50:54:00:00:00:09/00:00:00:00:00:00,dst=50:54:00:00:00:0a/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=10.0.0.2/0.0.0.0,dst=10.0.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no),icmp(type=8/0,code=0/0), packets:0, bytes:0, used:never, actions:drop
 ])
 
@@ -4675,7 +4675,7 @@ for dl_src in 00 01; do
 done
 sleep 1  # wait for the datapath flow installed
 for dl_src in 00 01; do
-    AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | grep "$dl_src," | STRIP_USED], [0], [dnl
+    AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_INSTALL | grep "$dl_src," | STRIP_USED], [0], [dnl
 recirc_id=0,skb_priority=0,mpls,in_port=1,dl_src=60:66:66:66:66:$dl_src,mpls_label=20,mpls_tc=0,mpls_ttl=32,mpls_bos=0,mpls_lse1=82208,mpls_lse2=0, actions:userspace(pid=0,slow_path(controller))
 ])
 done
@@ -4715,7 +4715,7 @@ for dl_src in 00 01; do
 done
 sleep 1  # wait for the datapath flow installed
 for dl_src in 00 01; do
-    AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | grep "$dl_src," | STRIP_USED], [0], [dnl
+    AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_INSTALL | grep "$dl_src," | STRIP_USED], [0], [dnl
 recirc_id=0,skb_priority=0,mpls,in_port=1,dl_src=60:66:66:66:66:$dl_src,mpls_label=20,mpls_tc=0,mpls_ttl=32,mpls_bos=0,mpls_lse1=82208,mpls_lse2=0, actions:userspace(pid=0,slow_path(controller))
 ])
 done
@@ -4732,6 +4732,8 @@ OVS_VSWITCHD_START([add-br br1 \
 ADD_OF_PORTS([br0], [2])
 ADD_OF_PORTS([br1], [3])
 
+AT_CHECK([ovs-appctl upcall/disable-terse-dump], [0], [Datapath dumping tersely using UID disabled
+], [])
 AT_CHECK([ovs-appctl time/stop])
 AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg])
 
@@ -4769,15 +4771,15 @@ dummy at ovs-dummy: hit:13 missed:2
 		pbr1 1/none: (patch: peer=pbr0)
 ])
 
-AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 recirc_id=0,skb_priority=0,ip,in_port=100,nw_frag=no, actions:101,3,2
 recirc_id=0,skb_priority=0,ip,in_port=101,nw_frag=no, actions:100,2,3
 ])
 
-AT_CHECK([cat ovs-vswitchd.log | grep -e 'in_port(100).*packets:9' | FILTER_FLOW_DUMP], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | grep -e 'in_port(100).*packets:9' | STRIP_UID | FILTER_FLOW_DUMP], [0], [dnl
 skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(100),eth(src=50:54:00:00:00:05/00:00:00:00:00:00,dst=50:54:00:00:00:07/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=192.168.0.1/0.0.0.0,dst=192.168.0.2/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no),icmp(type=8/0,code=0/0), packets:9, bytes:540, used:0.0s, actions:101,3,2
 ])
-AT_CHECK([cat ovs-vswitchd.log | grep -e 'in_port(101).*packets:4' | FILTER_FLOW_DUMP], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | grep -e 'in_port(101).*packets:4' | STRIP_UID | FILTER_FLOW_DUMP], [0], [dnl
 skb_priority(0),skb_mark(0/0),recirc_id(0),dp_hash(0/0),in_port(101),eth(src=50:54:00:00:00:07/00:00:00:00:00:00,dst=50:54:00:00:00:05/00:00:00:00:00:00),eth_type(0x0800),ipv4(src=192.168.0.2/0.0.0.0,dst=192.168.0.1/0.0.0.0,proto=1/0,tos=0/0,ttl=64/0,frag=no),icmp(type=8/0,code=0/0), packets:4, bytes:240, used:0.0s, actions:100,2,3
 ])
 
@@ -5290,7 +5292,7 @@ sleep 1
 dnl The first packet is essentially a no-op, as the new destination MAC is the
 dnl same as the original.  The second entry actually updates the destination
 dnl MAC.
-AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 recirc_id=0,skb_priority=0,ip,in_port=1,dl_dst=50:54:00:00:00:0a,nw_frag=no, actions:2
 recirc_id=0,skb_priority=0,ip,in_port=1,dl_dst=50:54:00:00:00:0c,nw_frag=no, actions:set(eth(dst=50:54:00:00:00:0a)),2
 ])
@@ -5307,6 +5309,8 @@ table=0 in_port=1,ip,nw_dst=10.0.0.3 actions=drop
 ])
 AT_CHECK([ovs-appctl upcall/disable-megaflows], [0], [megaflows disabled
 ], [])
+AT_CHECK([ovs-appctl upcall/disable-terse-dump], [0], [Datapath dumping tersely using UID disabled
+], [])
 AT_CHECK([ovs-appctl vlog/set dpif_netdev:dbg], [0], [], [])
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
 for i in 1 2 3 4; do
@@ -5317,11 +5321,11 @@ for i in 1 2 3 4; do
     fi
 done
 sleep 1
-AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 pkt_mark=0,recirc_id=0,skb_priority=0,icmp,tun_id=0,tun_src=0.0.0.0,tun_dst=0.0.0.0,tun_tos=0,tun_ttl=0,,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,nw_src=10.0.0.2,nw_dst=10.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,mpls_label=0,mpls_tc=0,mpls_ttl=0,mpls_bos=0,mpls_lse1=0,mpls_lse2=0,icmp_type=8,icmp_code=0, actions:2
 pkt_mark=0,recirc_id=0,skb_priority=0,icmp,tun_id=0,tun_src=0.0.0.0,tun_dst=0.0.0.0,tun_tos=0,tun_ttl=0,,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:0b,dl_dst=50:54:00:00:00:0c,nw_src=10.0.0.4,nw_dst=10.0.0.3,nw_tos=0,nw_ecn=0,nw_ttl=64,mpls_label=0,mpls_tc=0,mpls_ttl=0,mpls_bos=0,mpls_lse1=0,mpls_lse2=0,icmp_type=8,icmp_code=0, actions:drop
 ])
-AT_CHECK([cat ovs-vswitchd.log | FILTER_FLOW_DUMP | grep 'packets:3'], [0], [dnl
+AT_CHECK([cat ovs-vswitchd.log | STRIP_UID | FILTER_FLOW_DUMP | grep 'packets:3'], [0], [dnl
 skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x0800),ipv4(src=10.0.0.2,dst=10.0.0.1,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0), packets:3, bytes:180, used:0.0s, actions:2
 skb_priority(0),skb_mark(0),recirc_id(0),dp_hash(0),in_port(1),eth(src=50:54:00:00:00:0b,dst=50:54:00:00:00:0c),eth_type(0x0800),ipv4(src=10.0.0.4,dst=10.0.0.3,proto=1,tos=0,ttl=64,frag=no),icmp(type=8,code=0), packets:3, bytes:180, used:0.0s, actions:drop
 ])
diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
index 77b9b39..80ec8d3 100644
--- a/tests/ofproto-macros.at
+++ b/tests/ofproto-macros.at
@@ -36,6 +36,7 @@ m4_divert_pop([PREPARE_TESTS])
 m4_define([STRIP_XIDS], [[sed 's/ (xid=0x[0-9a-fA-F]*)//']])
 m4_define([STRIP_DURATION], [[sed 's/\bduration=[0-9.]*s/duration=?s/']])
 m4_define([STRIP_USED], [[sed 's/used:[0-9]\.[0-9]*/used:0.0/']])
+m4_define([STRIP_UID], [[sed 's/uid:[0-9a-f]* //']])
 m4_define([TESTABLE_LOG], [-vPATTERN:ANY:'%c|%p|%m'])
 
 # OVS_VSWITCHD_START([vsctl-args], [vsctl-output], [=override])
-- 
1.7.10.4




More information about the dev mailing list