[ovs-dev] [PATCHv9 10/12] dpif: Index flows using unique identifiers.

Joe Stringer joestringer at nicira.com
Fri Oct 31 23:55:44 UTC 2014


This patch modifies the dpif interface to allow flows to be manipulated
using a 128-bit identifier. This allows revalidator threads to perform
datapath operations faster, as they do not need to serialise the entire
flow key for operations like flow_get and flow_delete. In conjunction
with a future patch to simplify the dump interface, this provides a
significant performance benefit for revalidation.

When handlers assemble flow_put operations, they specify a unique
identifier (UFID) for each flow as it is passed down to the datapath to
be stored with the flow. The UFID is currently provided to handlers
by the dpif during upcall processing.

When revalidators assemble flow_get or flow_del operations, they specify
the UFID for the flow along with the key. The dpif will decide whether
to send only the UFID to the datapath, or both the UFID and flow key.
The former is preferred for newer datapaths that support UFID, while the
latter is used for backwards compatibility.

Signed-off-by: Joe Stringer <joestringer at nicira.com>
Acked-by: Ben Pfaff <blp at nicira.com>
---
v9: Fix flow lookup in dpif-netdev when UFID is missing.
    Reduce hashing frequency in dpif-netdev with UFIDs.
    Only print UFIDs in appctl/dpctl output when in verbose mode.
v8: Remove 'struct odputil_uidbuf'.
    Allow flows in dpif-netdev to be looked up by flow key or ufid.
    Don't generate UFIDs for feature probes (only for UFID feature probe).
    Don't generate UFIDs in ovs-dpctl.
    Clarify get/del interface description when ufid is invalid.
    Replace memcpy with assignment in dp_netdev_flow_to_dpif_flow().
    Rebase.
    Add Ack-by.
v7: Shift UID below the dpif layer
    Remove OVS_DP_F_INDEX_BY_UID
    Use ovs_u128_equal().
    Don't use 'static bool' for dpif_netlink_init_uid().
    Rebase.
v6: Split out from "dpif: Add Unique flow identifiers."
    Make sure that actions are fetched if terse=false.
    Add additional dpif.h documentation to describe the interface.
    Rebase.
v5: Always pass flow_key down to dpif, to improve error logging.
    Fix extraneous netflow_unref.
    Fix testsuite failure.
    Rebase.
v4: Skip sending flow key in flow_del.
    Fix race conditions with tests.
    Combine dpif,upcall,dpif-netdev,dpif-linux changes into one patch.
    Log UID in flow_del/flow_get/flow_dump messages.
v3: Rebase.
---
 lib/dpctl.c                   |    8 +-
 lib/dpif-netdev.c             |   86 +++++++++++-------
 lib/dpif-netlink.c            |  195 ++++++++++++++++++++++++++++++++++++-----
 lib/dpif.c                    |   29 ++++--
 lib/dpif.h                    |   32 +++++--
 lib/odp-util.c                |   59 +++++++++++++
 lib/odp-util.h                |    4 +
 ofproto/ofproto-dpif-upcall.c |    9 +-
 ofproto/ofproto-dpif.c        |   12 ++-
 tests/dpif-netdev.at          |    1 +
 tests/ofproto-dpif.at         |   24 ++---
 tests/ofproto-macros.at       |    1 +
 12 files changed, 372 insertions(+), 88 deletions(-)

diff --git a/lib/dpctl.c b/lib/dpctl.c
index 2d9144b..ab77e70 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -771,6 +771,10 @@ dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
             minimatch_destroy(&minimatch);
         }
         ds_clear(&ds);
+        if (dpctl_p->verbosity) {
+            odp_format_ufid(&f.ufid, &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, ", ");
@@ -852,7 +856,7 @@ dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
                           ofpbuf_size(&mask) == 0 ? NULL : ofpbuf_data(&mask),
                           ofpbuf_size(&mask),
                           ofpbuf_data(&actions), ofpbuf_size(&actions),
-                          dpctl_p->print_statistics ? &stats : NULL);
+                          NULL, dpctl_p->print_statistics ? &stats : NULL);
     if (error) {
         dpctl_error(dpctl_p, error, "updating flow table");
         goto out_freeactions;
@@ -938,7 +942,7 @@ dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
     }
 
     error = dpif_flow_del(dpif,
-                          ofpbuf_data(&key), ofpbuf_size(&key),
+                          ofpbuf_data(&key), ofpbuf_size(&key), NULL,
                           dpctl_p->print_statistics ? &stats : NULL);
     if (error) {
         dpctl_error(dpctl_p, error, "deleting flow");
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 96e0e9a..6178c7e 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -301,6 +301,7 @@ struct dp_netdev_flow {
 
     /* Hash table index by unmasked flow. */
     const struct cmap_node node; /* In owning dp_netdev's 'flow_table'. */
+    const ovs_u128 ufid;         /* Unique flow identifier. */
     const struct flow flow;      /* Unmasked flow that created this entry. */
 
     /* Number of references.
@@ -324,6 +325,8 @@ struct dp_netdev_flow {
 
 static void dp_netdev_flow_unref(struct dp_netdev_flow *);
 static bool dp_netdev_flow_ref(struct dp_netdev_flow *);
+static int dpif_netdev_flow_from_nlattrs(const struct nlattr *, uint32_t,
+                                         struct flow *);
 
 /* Contained by struct dp_netdev_flow's 'stats' member.  */
 struct dp_netdev_flow_stats {
@@ -1131,6 +1134,12 @@ static void dp_netdev_flow_unref(struct dp_netdev_flow *flow)
     }
 }
 
+static uint32_t
+dp_netdev_flow_hash(const ovs_u128 *ufid)
+{
+    return ufid->u32[0];
+}
+
 static void
 dp_netdev_remove_flow(struct dp_netdev *dp, struct dp_netdev_flow *flow)
     OVS_REQUIRES(dp->flow_mutex)
@@ -1138,7 +1147,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);
 
     dpcls_remove(&dp->cls, &flow->cr);
-    cmap_remove(&dp->flow_table, node, flow_hash(&flow->flow, 0));
+    cmap_remove(&dp->flow_table, node, dp_netdev_flow_hash(&flow->ufid));
     flow->dead = true;
 
     dp_netdev_flow_unref(flow);
@@ -1506,14 +1515,26 @@ dp_netdev_lookup_flow(const struct dp_netdev *dp,
 }
 
 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 *ufidp,
+                    const struct nlattr *key, size_t key_len)
 {
     struct dp_netdev_flow *netdev_flow;
+    struct flow flow;
+    ovs_u128 ufid;
+
+    /* If a UFID is not provided, determine one based on the key. */
+    if (!ufidp && key && key_len
+        && !dpif_netdev_flow_from_nlattrs(key, key_len, &flow)) {
+        dpif_flow_hash(dp->dpif, &flow, sizeof flow, &ufid);
+        ufidp = &ufid;
+    }
 
-    CMAP_FOR_EACH_WITH_HASH (netdev_flow, node, flow_hash(flow, 0),
-                             &dp->flow_table) {
-        if (flow_equal(&netdev_flow->flow, flow)) {
-            return netdev_flow;
+    if (ufidp) {
+        CMAP_FOR_EACH_WITH_HASH (netdev_flow, node, dp_netdev_flow_hash(ufidp),
+                                 &dp->flow_table) {
+            if (ovs_u128_equal(&netdev_flow->ufid, ufidp)) {
+                return netdev_flow;
+            }
         }
     }
 
@@ -1543,8 +1564,7 @@ get_dpif_flow_stats(const struct dp_netdev_flow *netdev_flow,
  * 'mask_buf'. Actions will be returned without copying, by relying on RCU to
  * protect them. */
 static void
-dp_netdev_flow_to_dpif_flow(const struct dpif *dpif,
-                            const struct dp_netdev_flow *netdev_flow,
+dp_netdev_flow_to_dpif_flow(const struct dp_netdev_flow *netdev_flow,
                             struct ofpbuf *key_buf, struct ofpbuf *mask_buf,
                             struct dpif_flow *flow)
 {
@@ -1574,8 +1594,7 @@ dp_netdev_flow_to_dpif_flow(const struct dpif *dpif,
     flow->actions = actions->actions;
     flow->actions_len = actions->size;
 
-    dpif_flow_hash(dpif, &netdev_flow->flow, sizeof netdev_flow->flow,
-                   &flow->ufid);
+    flow->ufid = netdev_flow->ufid;
     get_dpif_flow_stats(netdev_flow, &flow->stats);
 }
 
@@ -1676,20 +1695,13 @@ 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;
-
-    error = dpif_netdev_flow_from_nlattrs(get->key, get->key_len, &key);
-    if (error) {
-        return error;
-    }
-
-    netdev_flow = dp_netdev_find_flow(dp, &key);
+    int error = 0;
 
+    netdev_flow = dp_netdev_find_flow(dp, get->ufid, get->key, get->key_len);
     if (netdev_flow) {
-        dp_netdev_flow_to_dpif_flow(dpif, netdev_flow, get->buffer,
-                                    get->buffer, get->flow);
-     } else {
+        dp_netdev_flow_to_dpif_flow(netdev_flow, get->buffer, get->buffer,
+                                    get->flow);
+    } else {
         error = ENOENT;
     }
 
@@ -1698,6 +1710,7 @@ dpif_netdev_flow_get(const struct dpif *dpif, const struct dpif_flow_get *get)
 
 static struct dp_netdev_flow *
 dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
+                   const ovs_u128 *ufid,
                    const struct nlattr *actions, size_t actions_len)
     OVS_REQUIRES(dp->flow_mutex)
 {
@@ -1712,13 +1725,14 @@ dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
     flow = xmalloc(sizeof *flow - sizeof flow->cr.flow.mf + mask.len);
     flow->dead = false;
     *CONST_CAST(struct flow *, &flow->flow) = match->flow;
+    *CONST_CAST(ovs_u128 *, &flow->ufid) = *ufid;
     ovs_refcount_init(&flow->ref_cnt);
     ovsthread_stats_init(&flow->stats);
     ovsrcu_set(&flow->actions, dp_netdev_actions_create(actions, actions_len));
 
     cmap_insert(&dp->flow_table,
                 CONST_CAST(struct cmap_node *, &flow->node),
-                flow_hash(&flow->flow, 0));
+                dp_netdev_flow_hash(&flow->ufid));
     netdev_flow_key_init_masked(&flow->cr.flow, &match->flow, &mask);
     dpcls_insert(&dp->cls, &flow->cr, &mask);
 
@@ -1730,6 +1744,8 @@ dp_netdev_flow_add(struct dp_netdev *dp, struct match *match,
         miniflow_expand(&flow->cr.mask->mf, &match.wc.masks);
 
         ds_put_cstr(&ds, "flow_add: ");
+        odp_format_ufid(ufid, &ds);
+        ds_put_cstr(&ds, " ");
         match_format(&match, &ds, OFP_DEFAULT_PRIORITY);
         ds_put_cstr(&ds, ", actions:");
         format_odp_actions(&ds, actions, actions_len);
@@ -1765,6 +1781,7 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
     struct dp_netdev_flow *netdev_flow;
     struct netdev_flow_key key;
     struct match match;
+    ovs_u128 ufid;
     int error;
 
     error = dpif_netdev_flow_from_nlattrs(put->key, put->key_len, &match.flow);
@@ -1783,6 +1800,12 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
      * for upcall processing any more. */
     netdev_flow_key_from_flow(&key, &match.flow);
 
+    if (put->ufid) {
+        ufid = *put->ufid;
+    } else {
+        dpif_flow_hash(dpif, &match.flow, sizeof match.flow, &ufid);
+    }
+
     ovs_mutex_lock(&dp->flow_mutex);
     netdev_flow = dp_netdev_lookup_flow(dp, &key);
     if (!netdev_flow) {
@@ -1791,7 +1814,8 @@ dpif_netdev_flow_put(struct dpif *dpif, const struct dpif_flow_put *put)
                 if (put->stats) {
                     memset(put->stats, 0, sizeof *put->stats);
                 }
-                dp_netdev_flow_add(dp, &match, put->actions, put->actions_len);
+                dp_netdev_flow_add(dp, &match, &ufid, put->actions,
+                                   put->actions_len);
                 error = 0;
             } else {
                 error = EFBIG;
@@ -1836,16 +1860,10 @@ 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;
-
-    error = dpif_netdev_flow_from_nlattrs(del->key, del->key_len, &key);
-    if (error) {
-        return error;
-    }
+    int error = 0;
 
     ovs_mutex_lock(&dp->flow_mutex);
-    netdev_flow = dp_netdev_find_flow(dp, &key);
+    netdev_flow = dp_netdev_find_flow(dp, del->ufid, del->key, del->key_len);
     if (netdev_flow) {
         if (del->stats) {
             get_dpif_flow_stats(netdev_flow, del->stats);
@@ -1969,7 +1987,7 @@ dpif_netdev_flow_dump_next(struct dpif_flow_dump_thread *thread_,
 
         ofpbuf_use_stack(&key, keybuf, sizeof *keybuf);
         ofpbuf_use_stack(&mask, maskbuf, sizeof *maskbuf);
-        dp_netdev_flow_to_dpif_flow(&dpif->dpif, netdev_flow, &key, &mask, f);
+        dp_netdev_flow_to_dpif_flow(netdev_flow, &key, &mask, f);
     }
 
     return n_flows;
@@ -2837,7 +2855,7 @@ fast_path_processing(struct dp_netdev_pmd_thread *pmd,
                 ovs_mutex_lock(&dp->flow_mutex);
                 netdev_flow = dp_netdev_lookup_flow(dp, &keys[i]);
                 if (OVS_LIKELY(!netdev_flow)) {
-                    netdev_flow = dp_netdev_flow_add(dp, &match,
+                    netdev_flow = dp_netdev_flow_add(dp, &match, &ufid,
                                                      ofpbuf_data(add_actions),
                                                      ofpbuf_size(add_actions));
                 }
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index fafc810..def7f6d 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -116,6 +116,9 @@ struct dpif_netlink_flow {
     size_t mask_len;
     const struct nlattr *actions;       /* OVS_FLOW_ATTR_ACTIONS. */
     size_t actions_len;
+    ovs_u128 ufid;                      /* OVS_FLOW_ATTR_FLOW_ID. */
+    bool ufid_present;                  /* Is there a UFID? */
+    bool ufid_terse;                    /* Skip serializing key/mask/acts? */
     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. */
@@ -135,6 +138,7 @@ static void dpif_netlink_flow_get_stats(const struct dpif_netlink_flow *,
                                         struct dpif_flow_stats *);
 static void dpif_netlink_flow_to_dpif_flow(struct dpif *, struct dpif_flow *,
                                            const struct dpif_netlink_flow *);
+static bool dpif_netlink_check_ufid(struct dpif *dpif);
 
 /* One of the dpif channels between the kernel and userspace. */
 struct dpif_channel {
@@ -185,6 +189,11 @@ struct dpif_netlink {
     /* Change notification. */
     struct nl_sock *port_notifier; /* vport multicast group subscriber. */
     bool refresh_channels;
+
+    /* If the datapath supports indexing flows using unique identifiers, then
+     * we can reduce the size of netlink messages by omitting fields like the
+     * flow key during flow operations. */
+    bool ufid_supported;
 };
 
 static void report_loss(struct dpif_netlink *, struct dpif_channel *,
@@ -301,6 +310,7 @@ open_dpif(const struct dpif_netlink_dp *dp, struct dpif **dpifp)
               dp->dp_ifindex, dp->dp_ifindex);
 
     dpif->dp_ifindex = dp->dp_ifindex;
+    dpif->ufid_supported = dpif_netlink_check_ufid(&dpif->dpif);
     *dpifp = &dpif->dpif;
 
     return 0;
@@ -1209,28 +1219,63 @@ dpif_netlink_port_poll_wait(const struct dpif *dpif_)
 }
 
 static void
-dpif_netlink_init_flow_get(const struct dpif_netlink *dpif,
-                           const struct nlattr *key, size_t key_len,
-                           struct dpif_netlink_flow *request)
+dpif_netlink_flow_init_ufid(struct dpif_netlink_flow *request,
+                            const ovs_u128 *ufid, bool terse)
+{
+    if (ufid) {
+        request->ufid = *ufid;
+        request->ufid_present = true;
+    } else {
+        request->ufid_present = false;
+    }
+    request->ufid_terse = terse;
+}
+
+static void
+dpif_netlink_init_flow_get__(const struct dpif_netlink *dpif,
+                             const struct nlattr *key, size_t key_len,
+                             const ovs_u128 *ufid, bool terse,
+                             struct dpif_netlink_flow *request)
 {
     dpif_netlink_flow_init(request);
     request->cmd = OVS_FLOW_CMD_GET;
     request->dp_ifindex = dpif->dp_ifindex;
     request->key = key;
     request->key_len = key_len;
+    dpif_netlink_flow_init_ufid(request, ufid, terse);
+}
+
+static void
+dpif_netlink_init_flow_get(const struct dpif_netlink *dpif,
+                           const struct dpif_flow_get *get,
+                           struct dpif_netlink_flow *request)
+{
+    dpif_netlink_init_flow_get__(dpif, get->key, get->key_len, get->ufid,
+                                 false, request);
 }
 
 static int
-dpif_netlink_flow_get(const struct dpif_netlink *dpif,
-                      const struct nlattr *key, size_t key_len,
-                      struct dpif_netlink_flow *reply, struct ofpbuf **bufp)
+dpif_netlink_flow_get__(const struct dpif_netlink *dpif,
+                        const struct nlattr *key, size_t key_len,
+                        const ovs_u128 *ufid, bool terse,
+                        struct dpif_netlink_flow *reply, struct ofpbuf **bufp)
 {
     struct dpif_netlink_flow request;
 
-    dpif_netlink_init_flow_get(dpif, key, key_len, &request);
+    dpif_netlink_init_flow_get__(dpif, key, key_len, ufid, terse, &request);
     return dpif_netlink_flow_transact(&request, reply, bufp);
 }
 
+static int
+dpif_netlink_flow_get(const struct dpif_netlink *dpif,
+                      const struct dpif_netlink_flow *flow,
+                      struct dpif_netlink_flow *reply, struct ofpbuf **bufp)
+{
+    return dpif_netlink_flow_get__(dpif, flow->key, flow->key_len,
+                                   flow->ufid_present ? &flow->ufid : NULL,
+                                   false, reply, bufp);
+}
+
 static void
 dpif_netlink_init_flow_put(struct dpif_netlink *dpif,
                            const struct dpif_flow_put *put,
@@ -1246,6 +1291,8 @@ dpif_netlink_init_flow_put(struct dpif_netlink *dpif,
     request->key_len = put->key_len;
     request->mask = put->mask;
     request->mask_len = put->mask_len;
+    dpif_netlink_flow_init_ufid(request, put->ufid, false);
+
     /* Ensure that OVS_FLOW_ATTR_ACTIONS will always be included. */
     request->actions = (put->actions
                         ? put->actions
@@ -1261,15 +1308,40 @@ dpif_netlink_init_flow_put(struct dpif_netlink *dpif,
 }
 
 static void
-dpif_netlink_init_flow_del(struct dpif_netlink *dpif,
-                           const struct dpif_flow_del *del,
-                           struct dpif_netlink_flow *request)
+dpif_netlink_init_flow_del__(struct dpif_netlink *dpif,
+                             const struct nlattr *key, size_t key_len,
+                             const ovs_u128 *ufid, bool terse,
+                             struct dpif_netlink_flow *request)
 {
     dpif_netlink_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;
+    request->key = key;
+    request->key_len = key_len;
+    dpif_netlink_flow_init_ufid(request, ufid, terse);
+}
+
+static void
+dpif_netlink_init_flow_del(struct dpif_netlink *dpif,
+                           const struct dpif_flow_del *del,
+                           struct dpif_netlink_flow *request)
+{
+    return dpif_netlink_init_flow_del__(dpif, del->key, del->key_len,
+                                        del->ufid, dpif->ufid_supported,
+                                        request);
+}
+
+static int
+dpif_netlink_flow_del(struct dpif_netlink *dpif,
+                      const struct nlattr *key, size_t key_len,
+                      const ovs_u128 *ufid, bool terse)
+{
+    struct dpif_netlink_flow request;
+
+    dpif_netlink_init_flow_del__(dpif, key, key_len, ufid, terse, &request);
+
+    /* Ignore stats */
+    return dpif_netlink_flow_transact(&request, NULL, NULL);
 }
 
 struct dpif_netlink_flow_dump {
@@ -1372,8 +1444,13 @@ dpif_netlink_flow_to_dpif_flow(struct dpif *dpif, struct dpif_flow *dpif_flow,
     dpif_flow->mask_len = datapath_flow->mask_len;
     dpif_flow->actions = datapath_flow->actions;
     dpif_flow->actions_len = datapath_flow->actions_len;
-    dpif_flow_hash(dpif, datapath_flow->key, datapath_flow->key_len,
-                   &dpif_flow->ufid);
+    if (datapath_flow->ufid_present) {
+        dpif_flow->ufid = datapath_flow->ufid;
+    } else {
+        ovs_assert(datapath_flow->key && datapath_flow->key_len);
+        dpif_flow_hash(dpif, datapath_flow->key, datapath_flow->key_len,
+                       &dpif_flow->ufid);
+    }
     dpif_netlink_flow_get_stats(datapath_flow, &dpif_flow->stats);
 }
 
@@ -1416,8 +1493,7 @@ dpif_netlink_flow_dump_next(struct dpif_flow_dump_thread *thread_,
         } else {
             /* Rare case: the flow does not include actions.  Retrieve this
              * individual flow again to get the actions. */
-            error = dpif_netlink_flow_get(dpif, datapath_flow.key,
-                                          datapath_flow.key_len,
+            error = dpif_netlink_flow_get(dpif, &datapath_flow,
                                           &datapath_flow, &thread->nl_actions);
             if (error == ENOENT) {
                 VLOG_DBG("dumped flow disappeared on get");
@@ -1537,7 +1613,7 @@ dpif_netlink_operate__(struct dpif_netlink *dpif,
 
         case DPIF_OP_FLOW_GET:
             get = &op->u.flow_get;
-            dpif_netlink_init_flow_get(dpif, get->key, get->key_len, &flow);
+            dpif_netlink_init_flow_get(dpif, get, &flow);
             aux->txn.reply = get->buffer;
             dpif_netlink_flow_to_ofpbuf(&flow, &aux->request);
             break;
@@ -1659,6 +1735,61 @@ dpif_netlink_handler_uninit(struct dpif_handler *handler)
 }
 #endif
 
+/* Checks support for unique flow identifiers. */
+static bool
+dpif_netlink_check_ufid(struct dpif *dpif_)
+{
+    struct dpif_netlink *dpif = dpif_netlink_cast(dpif_);
+    struct flow flow;
+    struct odputil_keybuf keybuf;
+    struct ofpbuf key, *replybuf;
+    struct dpif_netlink_flow reply;
+    ovs_u128 ufid;
+    int error;
+    bool enable_ufid = 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(dpif_, ofpbuf_data(&key), ofpbuf_size(&key), &ufid);
+    error = dpif_flow_put(dpif_, DPIF_FP_CREATE | DPIF_FP_PROBE,
+                          ofpbuf_data(&key), ofpbuf_size(&key), NULL, 0, NULL,
+                          0, &ufid, NULL);
+
+    if (error && error != EEXIST) {
+        VLOG_WARN("%s: UFID feature probe failed (%s).",
+                  dpif_name(dpif_), ovs_strerror(error));
+        goto done;
+    }
+
+    error = dpif_netlink_flow_get__(dpif, NULL, 0, &ufid, true, &reply,
+                                    &replybuf);
+    if (!error && reply.ufid_present && ovs_u128_equal(&ufid, &reply.ufid)) {
+        enable_ufid = true;
+    }
+    ofpbuf_delete(replybuf);
+
+    error = dpif_netlink_flow_del(dpif, ofpbuf_data(&key), ofpbuf_size(&key),
+                                  &ufid, false);
+    if (error) {
+        VLOG_WARN("%s: failed to delete UFID feature probe flow",
+                  dpif_name(dpif_));
+    }
+
+done:
+    if (enable_ufid) {
+        VLOG_INFO("%s: Datapath supports userspace flow ids",
+                  dpif_name(dpif_));
+    } else {
+        VLOG_INFO("%s: Datapath does not support userspace flow ids",
+                  dpif_name(dpif_));
+    }
+
+    return enable_ufid;
+}
+
 /* Synchronizes 'channels' in 'dpif->handlers'  with the set of vports
  * currently in 'dpif' in the kernel, by adding a new set of channels for
  * any kernel vport that lacks one and deleting any channels that have no
@@ -2570,14 +2701,15 @@ static int
 dpif_netlink_flow_from_ofpbuf(struct dpif_netlink_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_UFID] = { .type = NL_A_NESTED, .optional = true },
         /* The kernel never uses OVS_FLOW_ATTR_CLEAR. */
         /* The kernel never uses OVS_FLOW_ATTR_PROBE. */
     };
@@ -2600,12 +2732,27 @@ dpif_netlink_flow_from_ofpbuf(struct dpif_netlink_flow *flow,
                             ARRAY_SIZE(ovs_flow_policy))) {
         return EINVAL;
     }
+    if (!a[OVS_FLOW_ATTR_KEY] && !a[OVS_FLOW_ATTR_UFID]) {
+        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_UFID]) {
+        int err = odp_ufid_from_nlattrs(nl_attr_get(a[OVS_FLOW_ATTR_UFID]),
+                                       nl_attr_get_size(a[OVS_FLOW_ATTR_UFID]),
+                                       &flow->ufid, NULL);
+        if (err) {
+            return err;
+        } else {
+            flow->ufid_present = true;
+        }
+    }
     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]);
@@ -2649,6 +2796,12 @@ dpif_netlink_flow_to_ofpbuf(const struct dpif_netlink_flow *flow,
         nl_msg_put_unspec(buf, OVS_FLOW_ATTR_MASK, flow->mask, flow->mask_len);
     }
 
+    if (flow->ufid_present) {
+        const uint32_t flags = OVS_UFID_F_SKIP_KEY | OVS_UFID_F_SKIP_MASK
+                               | OVS_UFID_F_SKIP_ACTIONS;
+
+        odp_ufid_to_nlattrs(buf, &flow->ufid, flow->ufid_terse ? flags : 0);
+    }
     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.c b/lib/dpif.c
index 9c8bf33..22c793b 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 *ufid,
                              const struct dpif_flow_stats *stats,
                              const struct nlattr *actions, size_t actions_len);
 static void log_operation(const struct dpif *, const char *operation,
@@ -849,7 +850,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 *ufid,
               struct ofpbuf *buf, struct dpif_flow *flow)
 {
     struct dpif_op *opp;
@@ -858,10 +859,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.ufid = ufid;
     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->ufid = *ufid;
 
     opp = &op;
     dpif_operate(dpif, &opp, 1);
@@ -875,7 +880,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 *ufid, struct dpif_flow_stats *stats)
 {
     struct dpif_op *opp;
     struct dpif_op op;
@@ -888,6 +893,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.ufid = ufid;
     op.u.flow_put.stats = stats;
 
     opp = &op;
@@ -899,7 +905,7 @@ 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,
+              const struct nlattr *key, size_t key_len, const ovs_u128 *ufid,
               struct dpif_flow_stats *stats)
 {
     struct dpif_op *opp;
@@ -908,6 +914,7 @@ 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.ufid = ufid;
     op.u.flow_del.stats = stats;
 
     opp = &op;
@@ -989,7 +996,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->ufid, &f->stats, f->actions, f->actions_len);
         }
     } else {
         VLOG_DBG_RL(&dpmsg_rl, "%s: dumped all flows", dpif_name(dpif));
@@ -1491,7 +1498,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 *ufid, const struct dpif_flow_stats *stats,
                  const struct nlattr *actions, size_t actions_len)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
@@ -1503,6 +1510,10 @@ log_flow_message(const struct dpif *dpif, int error, const char *operation,
     if (error) {
         ds_put_format(&ds, "(%s) ", ovs_strerror(error));
     }
+    if (ufid) {
+        odp_format_ufid(ufid, &ds);
+        ds_put_cstr(&ds, " ");
+    }
     odp_flow_format(key, key_len, mask, mask_len, NULL, &ds, true);
     if (stats) {
         ds_put_cstr(&ds, ", ");
@@ -1536,7 +1547,8 @@ 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->ufid, put->stats, put->actions,
+                         put->actions_len);
         ds_destroy(&s);
     }
 }
@@ -1547,7 +1559,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->ufid, !error ? del->stats : NULL,
+                         NULL, 0);
     }
 }
 
@@ -1603,7 +1616,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->ufid, &get->flow->stats,
                          get->flow->actions, get->flow->actions_len);
     }
 }
diff --git a/lib/dpif.h b/lib/dpif.h
index 95888f6..901eeca 100644
--- a/lib/dpif.h
+++ b/lib/dpif.h
@@ -522,12 +522,14 @@ 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 *ufid, 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 *ufid, struct dpif_flow_stats *);
 int dpif_flow_get(struct dpif *,
                   const struct nlattr *key, size_t key_len,
+                  const ovs_u128 *ufid,
                   struct ofpbuf *, struct dpif_flow *);
 
 /* Flow dumping interface
@@ -594,6 +596,9 @@ enum dpif_op_type {
     DPIF_OP_FLOW_GET,
 };
 
+/* Enable performing flow operations using UIDs only. */
+void dpif_set_enable_ufid_ops(struct dpif *, bool enable);
+
 /* Add or modify a flow.
  *
  * The flow is specified by the Netlink attributes with types OVS_KEY_ATTR_* in
@@ -625,6 +630,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 *ufid;           /* Unique flow identifier. */
 
     /* Output. */
     struct dpif_flow_stats *stats;  /* Optional flow statistics. */
@@ -633,8 +639,14 @@ 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 unique identifier 'ufid'. At
+ * least one of 'key' or 'ufid' must be specified. If both are specified,
+ * 'ufid' will be used first, then if that fails, 'key' and 'key_len' will be
+ * used. Succeeds with status 0 if the flow is deleted, or fails with ENOENT
+ * if the dpif does not contain such a flow.
+ *
+ * Callers should provide both 'key' and 'ufid' to improve dpif logging in the
+ * event of errors or unexpected behaviour.
  *
  * If the operation succeeds, then 'stats', if nonnull, will be set to the
  * flow's statistics before its deletion. */
@@ -642,6 +654,7 @@ struct dpif_flow_del {
     /* Input. */
     const struct nlattr *key;       /* Flow to delete. */
     size_t key_len;                 /* Length of 'key' in bytes. */
+    const ovs_u128 *ufid;           /* UID of flow to delete. */
 
     /* Output. */
     struct dpif_flow_stats *stats;  /* Optional flow statistics. */
@@ -676,8 +689,11 @@ struct dpif_execute {
 /* Queries the dpif for a flow entry.
  *
  * The flow is specified by the Netlink attributes with types OVS_KEY_ATTR_* in
- * the 'key_len' bytes starting at 'key'. 'buffer' must point to an initialized
- * buffer, with a recommended size of DPIF_FLOW_BUFSIZE bytes.
+ * the 'key_len' bytes starting at 'key', or the unique identifier 'ufid'. At
+ * least one of 'key' or 'ufid' must be specified. If both are specified,
+ * 'ufid' will be used first, then if that fails, 'key' and 'key_len' will be
+ * used. 'buffer' must point to an initialized buffer, with a recommended size
+ * of DPIF_FLOW_BUFSIZE bytes.
  *
  * On success, 'flow' will be populated with the mask, actions and stats for
  * the datapath flow corresponding to 'key'. The mask and actions may point
@@ -685,6 +701,9 @@ struct dpif_execute {
  * that wish to hold these over quiescent periods must make a copy of these
  * fields before quiescing.
  *
+ * Callers should provide both 'key' and 'ufid' to improve dpif logging in the
+ * event of errors or unexpected behaviour.
+ *
  * Succeeds with status 0 if the flow is fetched, or fails with ENOENT if no
  * such flow exists. Other failures are indicated with a positive errno value.
  */
@@ -692,6 +711,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 *ufid;            /* UID of flow to get. */
     struct ofpbuf *buffer;          /* Storage for output parameters. */
 
     /* Output. */
diff --git a/lib/odp-util.c b/lib/odp-util.c
index b89d74b..4901deb 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -2861,6 +2861,65 @@ odp_flow_key_hash(const struct nlattr *key, size_t key_len)
                       key_len / sizeof(uint32_t), 0);
 }
 
+int
+odp_ufid_from_nlattrs(const struct nlattr *nla, size_t nla_len, ovs_u128 *ufid,
+                      uint32_t *flags)
+{
+    static const struct nl_policy ovs_ufid_policy[] = {
+        [OVS_UFID_ATTR_FLAGS] = { .type = NL_A_U32, .optional = true },
+        [OVS_UFID_ATTR_ID] = { .type = NL_A_UNSPEC,
+                              .min_len = sizeof *ufid }
+    };
+    struct nlattr *a[ARRAY_SIZE(ovs_ufid_policy)];
+    const ovs_u128 *ufidp;
+    struct ofpbuf buf;
+
+    if (!nla) {
+        return EINVAL;
+    }
+
+    ofpbuf_use_const(&buf, nla, nla_len);
+    if (!nl_policy_parse(&buf, 0, ovs_ufid_policy, a, ARRAY_SIZE(a))) {
+        VLOG_WARN("Failed to parse UFID_ATTRs (%p, len=%"PRIuSIZE")",
+                  nla, nla_len);
+        return EINVAL;
+    }
+
+    ufidp = nl_attr_get_unspec(a[OVS_UFID_ATTR_ID], sizeof *ufid);
+    memcpy(ufid, ufidp, sizeof *ufid);
+
+    if (flags) {
+        if (a[OVS_UFID_ATTR_FLAGS]) {
+            *flags = nl_attr_get_u32(a[OVS_UFID_ATTR_FLAGS]);
+        } else {
+            *flags = 0;
+        }
+    }
+    return 0;
+}
+
+void
+odp_ufid_to_nlattrs(struct ofpbuf *buf, const ovs_u128 *ufid, uint32_t flags)
+{
+    size_t offset;
+
+    offset = nl_msg_start_nested(buf, OVS_FLOW_ATTR_UFID);
+    if (flags) {
+        nl_msg_put_u32(buf, OVS_UFID_ATTR_FLAGS, flags);
+    }
+    if (ufid) {
+        nl_msg_put_unspec(buf, OVS_UFID_ATTR_ID, ufid, sizeof *ufid);
+    }
+    nl_msg_end_nested(buf, offset);
+}
+
+void
+odp_format_ufid(const ovs_u128 *ufid, struct ds *ds)
+{
+    ds_put_format(ds, "ufid:%016"PRIx64"%016"PRIx64, ufid->u64.lo,
+                  ufid->u64.hi);
+}
+
 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..30f7773 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -162,6 +162,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_ufid_to_nlattrs(struct ofpbuf *, const ovs_u128 *, uint32_t flags);
+void odp_format_ufid(const ovs_u128 *ufid, struct ds *);
 
 /* Estimated space needed for metadata. */
 enum { ODP_KEY_METADATA_SIZE = 9 * 8 };
@@ -188,6 +190,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_ufid_from_nlattrs(const struct nlattr *, size_t,
+                          ovs_u128 *ufid, 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 82c7a43..b50ca00 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -641,7 +641,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->ufid, NULL);
                 VLOG_INFO_RL(&rl, "received packet on unassociated datapath "
                              "port %"PRIu32, flow->in_port.odp_port);
             }
@@ -1163,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.ufid = upcall->ufid;
             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);
@@ -1336,8 +1338,12 @@ ukey_install_start(struct udpif *udpif, struct udpif_key *new_ukey)
         } else {
             struct ds ds = DS_EMPTY_INITIALIZER;
 
+            odp_format_ufid(&old_ukey->ufid, &ds);
+            ds_put_cstr(&ds, " ");
             odp_flow_key_format(old_ukey->key, old_ukey->key_len, &ds);
             ds_put_cstr(&ds, "\n");
+            odp_format_ufid(&new_ukey->ufid, &ds);
+            ds_put_cstr(&ds, " ");
             odp_flow_key_format(new_ukey->key, new_ukey->key_len, &ds);
 
             VLOG_WARN("Conflicting ukey for flows:\n%s", ds_cstr(&ds));
@@ -1625,6 +1631,7 @@ delete_op_init(struct ukey_op *op, struct udpif_key *ukey)
     op->dop.type = DPIF_OP_FLOW_DEL;
     op->dop.u.flow_del.key = ukey->key;
     op->dop.u.flow_del.key_len = ukey->key_len;
+    op->dop.u.flow_del.ufid = &ukey->ufid;
     op->dop.u.flow_del.stats = &op->stats;
 }
 
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index d965d38..d8957ed 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -994,7 +994,7 @@ check_recirc(struct dpif_backer *backer)
 
     error = dpif_flow_put(backer->dpif, DPIF_FP_CREATE | DPIF_FP_PROBE,
                           ofpbuf_data(&key), ofpbuf_size(&key), NULL, 0, NULL,
-                          0, NULL);
+                          0, NULL, NULL);
     if (error && error != EEXIST) {
         if (error != EINVAL) {
             VLOG_WARN("%s: Reciculation flow probe failed (%s)",
@@ -1004,7 +1004,7 @@ check_recirc(struct dpif_backer *backer)
     }
 
     error = dpif_flow_del(backer->dpif, ofpbuf_data(&key), ofpbuf_size(&key),
-                          NULL);
+                          NULL, NULL);
     if (error) {
         VLOG_WARN("%s: failed to delete recirculation feature probe flow",
                   dpif_name(backer->dpif));
@@ -1123,7 +1123,7 @@ check_max_mpls_depth(struct dpif_backer *backer)
 
         error = dpif_flow_put(backer->dpif, DPIF_FP_CREATE | DPIF_FP_PROBE,
                               ofpbuf_data(&key), ofpbuf_size(&key), NULL, 0,
-                              NULL, 0, NULL);
+                              NULL, 0, NULL, NULL);
         if (error && error != EEXIST) {
             if (error != EINVAL) {
                 VLOG_WARN("%s: MPLS stack length feature probe failed (%s)",
@@ -1133,7 +1133,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), NULL, NULL);
         if (error) {
             VLOG_WARN("%s: failed to delete MPLS feature probe flow",
                       dpif_name(backer->dpif));
@@ -4940,6 +4940,10 @@ ofproto_unixctl_dpif_dump_flows(struct unixctl_conn *conn,
             continue;
         }
 
+        if (verbosity) {
+            odp_format_ufid(&f.ufid, &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/tests/dpif-netdev.at b/tests/dpif-netdev.at
index b68e43b..69903cb 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/ufid:[0-9a-f]* //
     s/used:[0-9]*\.[0-9]*/used:0.0/
     s/actions:.*/actions: <del>/
     s/packets:[0-9]*/packets:0/
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 652a2a3..e4a7ad3 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -4648,21 +4648,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_UFID | STRIP_USED | sort], [0], [dnl
 recirc_id(0),in_port(1),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:drop
 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_UFID | STRIP_USED | sort], [0], [dnl
 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_UFID | STRIP_USED | sort], [0], [dnl
 skb_priority(0/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/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_UFID | STRIP_USED | sort], [0], [dnl
 skb_priority(0/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
 ])
 
@@ -4696,7 +4696,7 @@ for dl_src in 00 01; do
     AT_CHECK([ovs-appctl netdev-dummy/receive p1 "505400000007 6066666666$dl_src 8847 00014020 00014120 45 00 00 2c 00 00 00 00 40 06 3b 78 c0 a8 00 01 c0 a8 00 02 00 50 00 00 00 00 00 2a 00 00 00 2a 50 00 27 10 77 44 00 00 48 4f 47 45"])
 done
 sleep 1  # wait for the datapath flow installed
-AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
+AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | STRIP_UFID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 recirc_id=0,mpls,in_port=1,dl_src=60:66:66:66:66:00,mpls_label=20,mpls_tc=0,mpls_ttl=32,mpls_bos=0,mpls_lse1=82208, actions:userspace(pid=0,slow_path(controller))
 recirc_id=0,mpls,in_port=1,dl_src=60:66:66:66:66:01,mpls_bos=0,mpls_lse1=82208, actions:userspace(pid=0,slow_path(controller))
 ])
@@ -4735,7 +4735,7 @@ for dl_src in 00 01; do
     AT_CHECK([ovs-appctl netdev-dummy/receive p1 "505400000007 6066666666$dl_src 8847 00014020 00014120 45 00 00 2c 00 00 00 00 40 06 3b 78 c0 a8 00 01 c0 a8 00 02 00 50 00 00 00 00 00 2a 00 00 00 2a 50 00 27 10 77 44 00 00 48 4f 47 45"])
 done
 sleep 1  # wait for the datapath flow installed
-AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
+AT_CHECK_UNQUOTED([cat ovs-vswitchd.log | STRIP_UFID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 recirc_id=0,mpls,in_port=1,dl_src=60:66:66:66:66:00,mpls_label=20,mpls_tc=0,mpls_ttl=32,mpls_bos=0,mpls_lse1=82208, actions:userspace(pid=0,slow_path(controller))
 recirc_id=0,mpls,in_port=1,dl_src=60:66:66:66:66:01,mpls_bos=0,mpls_lse1=82208, actions:userspace(pid=0,slow_path(controller))
 ])
@@ -4789,15 +4789,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_UFID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 recirc_id=0,ip,in_port=100,nw_frag=no, actions:101,3,2
 recirc_id=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_UFID | FILTER_FLOW_DUMP], [0], [dnl
 skb_priority(0/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_UFID | FILTER_FLOW_DUMP], [0], [dnl
 skb_priority(0/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
 ])
 
@@ -5310,7 +5310,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_UFID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 recirc_id=0,ip,in_port=1,dl_dst=50:54:00:00:00:0a,nw_frag=no, actions:2
 recirc_id=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
 ])
@@ -5337,11 +5337,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_UFID | FILTER_FLOW_INSTALL | STRIP_USED], [0], [dnl
 pkt_mark=0,recirc_id=0,skb_priority=0,icmp,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,icmp_type=8,icmp_code=0, actions:2
 pkt_mark=0,recirc_id=0,skb_priority=0,icmp,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,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_UFID | 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..377a50e 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_UFID], [[sed 's/ufid:[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