minimatch_destroy(&minimatch);
}
ds_clear(&ds);
+ if (dpctl_p->verbosity) {
+ if (f.ufid_present) {
+ odp_format_ufid(&f.ufid, &ds);
+ ds_put_cstr(&ds, ", ");
+ } else {
+ ds_put_cstr(&ds, "ufid:<empty>, ");
+ }
+ }
odp_flow_format(f.key, f.key_len, f.mask, f.mask_len,
&portno_names, &ds, dpctl_p->verbosity);
ds_put_cstr(&ds, ", ");
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;
}
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");
/* 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.
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 {
}
}
+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)
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);
}
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;
+ }
}
}
* '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)
{
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;
+ flow->ufid_present = true;
get_dpif_flow_stats(netdev_flow, &flow->stats);
}
{
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;
}
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)
{
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);
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);
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);
* 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) {
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;
{
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);
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;
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));
}
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. */
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 {
/* 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 *,
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;
}
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,
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
}
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 {
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);
+ dpif_flow->ufid_present = datapath_flow->ufid_present;
+ 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);
}
} 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");
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;
}
#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
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_UNSPEC, .optional = true,
+ .min_len = sizeof(ovs_u128) },
/* The kernel never uses OVS_FLOW_ATTR_CLEAR. */
/* The kernel never uses OVS_FLOW_ATTR_PROBE. */
+ /* The kernel never uses OVS_FLOW_ATTR_UFID_FLAGS. */
};
struct nlattr *a[ARRAY_SIZE(ovs_flow_policy)];
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]) {
+ const ovs_u128 *ufid;
+
+ ufid = nl_attr_get_unspec(a[OVS_FLOW_ATTR_UFID],
+ nl_attr_get_size(a[OVS_FLOW_ATTR_UFID]));
+ flow->ufid = *ufid;
+ 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]);
ovs_header = ofpbuf_put_uninit(buf, sizeof *ovs_header);
ovs_header->dp_ifindex = flow->dp_ifindex;
+ if (flow->ufid_present) {
+ nl_msg_put_unspec(buf, OVS_FLOW_ATTR_UFID, &flow->ufid,
+ sizeof flow->ufid);
+ }
+ if (flow->ufid_terse) {
+ nl_msg_put_u32(buf, OVS_FLOW_ATTR_UFID_FLAGS,
+ OVS_UFID_F_OMIT_KEY | OVS_UFID_F_OMIT_MASK
+ | OVS_UFID_F_OMIT_ACTIONS);
+ }
if (flow->key_len) {
nl_msg_put_unspec(buf, OVS_FLOW_ATTR_KEY, flow->key, flow->key_len);
}
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,
/* 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;
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);
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;
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;
/* 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;
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;
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));
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;
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, ", ");
}
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);
}
}
{
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);
}
}
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);
}
}
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 *);
\f
/* Flow dumping interface
const struct nlattr *actions; /* Actions, as OVS_ACTION_ATTR_ */
size_t actions_len; /* 'actions' length in bytes. */
ovs_u128 ufid; /* Unique flow identifier. */
+ bool ufid_present; /* True if 'ufid' was provided by datapath.*/
struct dpif_flow_stats stats; /* Flow statistics. */
};
int dpif_flow_dump_next(struct dpif_flow_dump_thread *,
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; /* Optional unique flow identifier. */
/* Output. */
struct dpif_flow_stats *stats; /* Optional flow statistics. */
/* 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'. If
+ * the flow was created using 'ufid', then 'ufid' must be specified to delete
+ * the flow. If both are specified, 'key' will be ignored for flow deletion.
+ * Succeeds with status 0 if the flow is deleted, or fails with ENOENT if the
+ * dpif does not contain such a flow.
+ *
+ * Callers should always provide the 'key' 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. */
/* 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. */
/* 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'. If
+ * the flow was created using 'ufid', then 'ufid' must be specified to fetch
+ * the flow. If both are specified, 'key' will be ignored for the flow query.
+ * '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
* that wish to hold these over quiescent periods must make a copy of these
* fields before quiescing.
*
+ * Callers should always provide 'key' 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.
*/
/* 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. */
return ofpbuf_base(ofp);
}
+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);
+}
+
/* Appends to 'ds' a string representation of the 'key_len' bytes of
* OVS_KEY_ATTR_* attributes in 'key'. If non-null, additionally formats the
* 'mask_len' bytes of 'mask' which apply to 'key'. If 'portno_names' is
enum odp_key_fitness odp_tun_key_from_attr(const struct nlattr *,
struct flow_tnl *);
+void odp_format_ufid(const ovs_u128 *ufid, struct ds *);
void odp_flow_format(const struct nlattr *key, size_t key_len,
const struct nlattr *mask, size_t mask_len,
const struct hmap *portno_names, struct ds *,
size_t mask_len; /* Length of 'mask'. */
struct ofpbuf *actions; /* Datapath flow actions as nlattrs. */
ovs_u128 ufid; /* Unique flow identifier. */
+ bool ufid_present; /* True if 'ufid' is in datapath. */
uint32_t hash; /* Pre-computed hash for 'key'. */
struct ovs_mutex mutex; /* Guards the following. */
* 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);
}
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);
static struct udpif_key *
ukey_create__(const struct nlattr *key, size_t key_len,
const struct nlattr *mask, size_t mask_len,
- const ovs_u128 *ufid, const struct ofpbuf *actions,
+ bool ufid_present, const ovs_u128 *ufid,
+ const struct ofpbuf *actions,
uint64_t dump_seq, uint64_t reval_seq, long long int used)
OVS_NO_THREAD_SAFETY_ANALYSIS
{
memcpy(&ukey->maskbuf, mask, mask_len);
ukey->mask = &ukey->maskbuf.nla;
ukey->mask_len = mask_len;
+ ukey->ufid_present = ufid_present;
ukey->ufid = *ufid;
ukey->hash = get_ufid_hash(&ukey->ufid);
ukey->actions = ofpbuf_clone(actions);
return ukey_create__(ofpbuf_data(&keybuf), ofpbuf_size(&keybuf),
ofpbuf_data(&maskbuf), ofpbuf_size(&maskbuf),
- upcall->ufid, &upcall->put_actions, upcall->dump_seq,
- upcall->reval_seq, 0);
+ true, upcall->ufid, &upcall->put_actions,
+ upcall->dump_seq, upcall->reval_seq, 0);
}
static struct udpif_key *
reval_seq = seq_read(udpif->reval_seq);
ofpbuf_use_const(&actions, &flow->actions, flow->actions_len);
return ukey_create__(flow->key, flow->key_len,
- flow->mask, flow->mask_len, &flow->ufid, &actions,
- dump_seq, reval_seq, flow->stats.used);
+ flow->mask, flow->mask_len, flow->ufid_present,
+ &flow->ufid, &actions, dump_seq, reval_seq,
+ flow->stats.used);
}
/* Attempts to insert a ukey into the shared ukey maps.
} 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_RL(&rl, "Conflicting ukey for flows:\n%s", ds_cstr(&ds));
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_present ? &ukey->ufid : NULL;
op->dop.u.flow_del.stats = &op->stats;
}
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)",
}
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));
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)",
}
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));
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, ", ");
# 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/
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
])
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))
])
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))
])
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
])
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
])
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
])
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])