[ovs-dev] [flow-compat 7/7] ofproto-dpif: Support differing user/kernel packet parsing support.
Ben Pfaff
blp at nicira.com
Fri Nov 4 23:43:19 UTC 2011
Feature #4886.
---
lib/odp-util.c | 465 +++++++++++++++++--------------
lib/odp-util.h | 17 +-
ofproto/ofproto-dpif.c | 724 +++++++++++++++++++++++++++++-------------------
tests/automake.mk | 2 +
4 files changed, 719 insertions(+), 489 deletions(-)
diff --git a/lib/odp-util.c b/lib/odp-util.c
index f348e58..dd64bb0 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -33,6 +33,9 @@
#include "packets.h"
#include "timeval.h"
#include "util.h"
+#include "vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(odp_util);
static void
format_odp_key_attr(const struct nlattr *a, struct ds *ds);
@@ -824,8 +827,7 @@ parse_odp_key_attr(const char *s, struct ofpbuf *key)
*
* On success, the attributes appended to 'key' are individually syntactically
* valid, but they may not be valid as a sequence. 'key' might, for example,
- * be missing an "in_port" key, have duplicated keys, or have keys in the wrong
- * order. odp_flow_key_to_flow() will detect those errors. */
+ * have duplicated keys. odp_flow_key_to_flow() will detect those errors. */
int
odp_flow_key_from_string(const char *s, struct ofpbuf *key)
{
@@ -981,10 +983,52 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
}
}
+uint32_t
+odp_flow_key_hash(const struct nlattr *key, size_t key_len)
+{
+ BUILD_ASSERT_DECL(!(NLA_ALIGNTO % sizeof(uint32_t)));
+ return hash_words((const uint32_t *) key, key_len / sizeof(uint32_t), 0);
+}
+
+static void
+log_odp_key_attributes(struct vlog_rate_limit *rl, const char *title,
+ uint32_t attrs,
+ const struct nlattr *key, size_t key_len)
+{
+ struct ds s;
+ int i;
+
+ if (VLOG_DROP_DBG(rl)) {
+ return;
+ }
+
+ ds_init(&s);
+ for (i = 0; i < 32; i++) {
+ if (attrs & (1u << i)) {
+ const char *name = ovs_key_attr_to_string(i);
+ if (name[0] == '<') {
+ ds_put_format(&s, " %d", i);
+ } else {
+ ds_put_format(&s, " %s", name);
+ }
+ }
+ }
+
+ ds_put_cstr(&s, ": ");
+ odp_flow_key_format(key, key_len, &s);
+
+ VLOG_DBG("%s: %s", title, ds_cstr(&s));
+ ds_destroy(&s);
+}
+
static bool
odp_to_tos_frag(uint8_t odp_tos, uint8_t odp_frag, struct flow *flow)
{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
if (odp_tos & ~IP_DSCP_MASK || odp_frag > OVS_FRAG_TYPE_LATER) {
+ VLOG_ERR_RL(&rl, "invalid TOS %"PRIu8" or frag %"PRIu8,
+ odp_tos, odp_frag);
return false;
}
@@ -998,256 +1042,271 @@ odp_to_tos_frag(uint8_t odp_tos, uint8_t odp_frag, struct flow *flow)
return true;
}
+/* We don't use nl_policy_parse() so the .optional members don't matter. */
+static const struct nl_policy odp_key_policy[] = {
+ [OVS_KEY_ATTR_PRIORITY] = { .type = NL_A_U32 },
+ [OVS_KEY_ATTR_TUN_ID] = { .type = NL_A_BE64 },
+ [OVS_KEY_ATTR_IN_PORT] = { .type = NL_A_U32 },
+ [OVS_KEY_ATTR_ETHERNET] = { NL_POLICY_FOR(struct ovs_key_ethernet) },
+ [OVS_KEY_ATTR_8021Q] = { NL_POLICY_FOR(struct ovs_key_8021q) },
+ [OVS_KEY_ATTR_ETHERTYPE] = { .type = NL_A_BE16 },
+ [OVS_KEY_ATTR_IPV4] = { NL_POLICY_FOR(struct ovs_key_ipv4) },
+ [OVS_KEY_ATTR_IPV6] = { NL_POLICY_FOR(struct ovs_key_ipv6) },
+ [OVS_KEY_ATTR_TCP] = { NL_POLICY_FOR(struct ovs_key_tcp) },
+ [OVS_KEY_ATTR_UDP] = { NL_POLICY_FOR(struct ovs_key_udp) },
+ [OVS_KEY_ATTR_ICMP] = { NL_POLICY_FOR(struct ovs_key_icmp) },
+ [OVS_KEY_ATTR_ICMPV6] = { NL_POLICY_FOR(struct ovs_key_icmpv6) },
+ [OVS_KEY_ATTR_ARP] = { NL_POLICY_FOR(struct ovs_key_arp) },
+ [OVS_KEY_ATTR_ND] = { NL_POLICY_FOR(struct ovs_key_nd) },
+};
+
/* Converts the 'key_len' bytes of OVS_KEY_ATTR_* attributes in 'key' to a flow
- * structure in 'flow'. Returns 0 if successful, otherwise EINVAL. */
-int
+ * structure in 'flow'. Returns an ODP_FIT_* value that indicates how well
+ * 'key' fits our expectations for what a flow key should contain.
+ *
+ * This function doesn't take the packet itself as an argument because none of
+ * the currently understood OVS_KEY_ATTR_* attributes require it. Currently,
+ * it is always possible to infer which additional attribute(s) should appear
+ * by looking at the attributes for lower-level protocols, e.g. if the network
+ * protocol in OVS_KEY_ATTR_IPV4 or OVS_KEY_ATTR_IPV6 is IPPROTO_TCP then we
+ * know that a OVS_KEY_ATTR_TCP attribute must appear and that otherwise it
+ * must be absent. */
+enum odp_key_fitness
odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
struct flow *flow)
{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ const struct nlattr *attrs[ARRAY_SIZE(odp_key_policy)];
const struct nlattr *nla;
- enum ovs_key_attr prev_type;
+ uint32_t expected_attrs;
+ uint32_t present_attrs;
+ uint32_t missing_attrs;
+ uint32_t extra_attrs;
size_t left;
+ BUILD_ASSERT_DECL(ARRAY_SIZE(odp_key_policy) < 32);
+
memset(flow, 0, sizeof *flow);
- flow->dl_type = htons(FLOW_DL_TYPE_NONE);
flow->in_port = OFPP_NONE;
+ flow->dl_type = htons(FLOW_DL_TYPE_NONE);
- prev_type = OVS_KEY_ATTR_UNSPEC;
+ memset(attrs, 0, sizeof attrs);
+
+ present_attrs = 0;
+ expected_attrs = 0;
NL_ATTR_FOR_EACH (nla, left, key, key_len) {
+ uint16_t type = nl_attr_type(nla);
+
+ if (type >= ARRAY_SIZE(odp_key_policy)
+ || odp_key_policy[type].type == NL_A_NO_ATTR) {
+ present_attrs |= 1u << 31;
+ continue;
+ }
+
+ if (!nl_attr_validate(nla, &odp_key_policy[type])) {
+ VLOG_ERR_RL(&rl, "%s attribute violates policy",
+ ovs_key_attr_to_string(type));
+ return ODP_FIT_ERROR;
+ }
+ if (attrs[type]) {
+ VLOG_ERR_RL(&rl, "duplicate %s attribute in flow key",
+ ovs_key_attr_to_string(type));
+ return ODP_FIT_ERROR;
+ }
+
+ present_attrs |= 1u << type;
+ attrs[type] = nla;
+ }
+ if (left) {
+ VLOG_ERR_RL(&rl, "trailing garbage in flow key");
+ return ODP_FIT_ERROR;
+ }
+
+ if (attrs[OVS_KEY_ATTR_PRIORITY]) {
+ flow->priority = nl_attr_get_u32(attrs[OVS_KEY_ATTR_PRIORITY]);
+ expected_attrs |= 1u << OVS_KEY_ATTR_PRIORITY;
+ }
+
+ if (attrs[OVS_KEY_ATTR_TUN_ID]) {
+ flow->tun_id = nl_attr_get_be64(attrs[OVS_KEY_ATTR_TUN_ID]);
+ expected_attrs |= 1u << OVS_KEY_ATTR_TUN_ID;
+ }
+
+ if (attrs[OVS_KEY_ATTR_IN_PORT]) {
+ uint32_t in_port = nl_attr_get_u32(attrs[OVS_KEY_ATTR_IN_PORT]);
+ if (in_port >= UINT16_MAX || in_port >= OFPP_MAX) {
+ VLOG_ERR_RL(&rl, "in_port %"PRIu32" out of supported range",
+ in_port);
+ return ODP_FIT_ERROR;
+ }
+ flow->in_port = odp_port_to_ofp_port(in_port);
+ expected_attrs |= 1u << OVS_KEY_ATTR_IN_PORT;
+ }
+
+ if (attrs[OVS_KEY_ATTR_ETHERNET]) {
const struct ovs_key_ethernet *eth_key;
+
+ eth_key = nl_attr_get(attrs[OVS_KEY_ATTR_ETHERNET]);
+ memcpy(flow->dl_src, eth_key->eth_src, ETH_ADDR_LEN);
+ memcpy(flow->dl_dst, eth_key->eth_dst, ETH_ADDR_LEN);
+ } else {
+ VLOG_ERR_RL(&rl, "missing Ethernet key");
+ return ODP_FIT_ERROR;
+ }
+ expected_attrs |= 1u << OVS_KEY_ATTR_ETHERNET;
+
+ if (attrs[OVS_KEY_ATTR_8021Q]) {
const struct ovs_key_8021q *q_key;
- const struct ovs_key_ipv4 *ipv4_key;
- const struct ovs_key_ipv6 *ipv6_key;
- const struct ovs_key_tcp *tcp_key;
- const struct ovs_key_udp *udp_key;
- const struct ovs_key_icmp *icmp_key;
- const struct ovs_key_icmpv6 *icmpv6_key;
- const struct ovs_key_arp *arp_key;
- const struct ovs_key_nd *nd_key;
- uint16_t type = nl_attr_type(nla);
- int len = odp_flow_key_attr_len(type);
+ q_key = nl_attr_get(attrs[OVS_KEY_ATTR_8021Q]);
+ if (q_key->q_tpid != htons(ETH_TYPE_VLAN)) {
+ VLOG_ERR_RL(&rl, "unsupported 802.1Q TPID %"PRIu16,
+ ntohs(q_key->q_tpid));
+ return ODP_FIT_ERROR;
+ }
+ if (q_key->q_tci & htons(VLAN_CFI)) {
+ VLOG_ERR_RL(&rl, "invalid 802.1Q TCI %"PRIu16,
+ ntohs(q_key->q_tci));
+ return ODP_FIT_ERROR;
+ }
+ flow->vlan_tci = q_key->q_tci | htons(VLAN_CFI);
+ expected_attrs |= 1u << OVS_KEY_ATTR_8021Q;
+ }
- if (nl_attr_get_size(nla) != len && len != -1) {
- return EINVAL;
+ if (attrs[OVS_KEY_ATTR_ETHERTYPE]) {
+ flow->dl_type = nl_attr_get_be16(attrs[OVS_KEY_ATTR_ETHERTYPE]);
+ if (ntohs(flow->dl_type) < 1536) {
+ VLOG_ERR_RL(&rl, "invalid Ethertype %"PRIu16,
+ ntohs(flow->dl_type));
+ return ODP_FIT_ERROR;
}
+ expected_attrs |= 1u << OVS_KEY_ATTR_ETHERTYPE;
+ }
-#define TRANSITION(PREV_TYPE, TYPE) (((PREV_TYPE) << 16) | (TYPE))
- switch (TRANSITION(prev_type, type)) {
- case TRANSITION(OVS_KEY_ATTR_UNSPEC, OVS_KEY_ATTR_PRIORITY):
- flow->priority = nl_attr_get_u32(nla);
- break;
-
- case TRANSITION(OVS_KEY_ATTR_UNSPEC, OVS_KEY_ATTR_TUN_ID):
- case TRANSITION(OVS_KEY_ATTR_PRIORITY, OVS_KEY_ATTR_TUN_ID):
- flow->tun_id = nl_attr_get_be64(nla);
- break;
-
- case TRANSITION(OVS_KEY_ATTR_UNSPEC, OVS_KEY_ATTR_IN_PORT):
- case TRANSITION(OVS_KEY_ATTR_PRIORITY, OVS_KEY_ATTR_IN_PORT):
- case TRANSITION(OVS_KEY_ATTR_TUN_ID, OVS_KEY_ATTR_IN_PORT):
- if (nl_attr_get_u32(nla) >= UINT16_MAX) {
- return EINVAL;
- }
- flow->in_port = odp_port_to_ofp_port(nl_attr_get_u32(nla));
- break;
-
- case TRANSITION(OVS_KEY_ATTR_UNSPEC, OVS_KEY_ATTR_ETHERNET):
- case TRANSITION(OVS_KEY_ATTR_PRIORITY, OVS_KEY_ATTR_ETHERNET):
- case TRANSITION(OVS_KEY_ATTR_TUN_ID, OVS_KEY_ATTR_ETHERNET):
- case TRANSITION(OVS_KEY_ATTR_IN_PORT, OVS_KEY_ATTR_ETHERNET):
- eth_key = nl_attr_get(nla);
- memcpy(flow->dl_src, eth_key->eth_src, ETH_ADDR_LEN);
- memcpy(flow->dl_dst, eth_key->eth_dst, ETH_ADDR_LEN);
- break;
-
- case TRANSITION(OVS_KEY_ATTR_ETHERNET, OVS_KEY_ATTR_8021Q):
- q_key = nl_attr_get(nla);
- if (q_key->q_tpid != htons(ETH_TYPE_VLAN)) {
- /* Only standard 0x8100 VLANs currently supported. */
- return EINVAL;
- }
- if (q_key->q_tci & htons(VLAN_CFI)) {
- return EINVAL;
- }
- flow->vlan_tci = q_key->q_tci | htons(VLAN_CFI);
- break;
-
- case TRANSITION(OVS_KEY_ATTR_8021Q, OVS_KEY_ATTR_ETHERTYPE):
- case TRANSITION(OVS_KEY_ATTR_ETHERNET, OVS_KEY_ATTR_ETHERTYPE):
- flow->dl_type = nl_attr_get_be16(nla);
- if (ntohs(flow->dl_type) < 1536) {
- return EINVAL;
- }
- break;
+ if (flow->dl_type == htons(ETH_TYPE_IP)) {
+ expected_attrs |= 1u << OVS_KEY_ATTR_IPV4;
+ if (attrs[OVS_KEY_ATTR_IPV4]) {
+ const struct ovs_key_ipv4 *ipv4_key;
- case TRANSITION(OVS_KEY_ATTR_ETHERTYPE, OVS_KEY_ATTR_IPV4):
- if (flow->dl_type != htons(ETH_TYPE_IP)) {
- return EINVAL;
- }
- ipv4_key = nl_attr_get(nla);
+ ipv4_key = nl_attr_get(attrs[OVS_KEY_ATTR_IPV4]);
flow->nw_src = ipv4_key->ipv4_src;
flow->nw_dst = ipv4_key->ipv4_dst;
flow->nw_proto = ipv4_key->ipv4_proto;
if (!odp_to_tos_frag(ipv4_key->ipv4_tos, ipv4_key->ipv4_frag,
flow)) {
- return EINVAL;
+ return ODP_FIT_ERROR;
}
- break;
+ }
+ } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+ expected_attrs |= 1u << OVS_KEY_ATTR_IPV6;
+ if (attrs[OVS_KEY_ATTR_IPV6]) {
+ const struct ovs_key_ipv6 *ipv6_key;
- case TRANSITION(OVS_KEY_ATTR_ETHERTYPE, OVS_KEY_ATTR_IPV6):
- if (flow->dl_type != htons(ETH_TYPE_IPV6)) {
- return EINVAL;
- }
- ipv6_key = nl_attr_get(nla);
+ ipv6_key = nl_attr_get(attrs[OVS_KEY_ATTR_IPV6]);
memcpy(&flow->ipv6_src, ipv6_key->ipv6_src, sizeof flow->ipv6_src);
memcpy(&flow->ipv6_dst, ipv6_key->ipv6_dst, sizeof flow->ipv6_dst);
flow->nw_proto = ipv6_key->ipv6_proto;
if (!odp_to_tos_frag(ipv6_key->ipv6_tos, ipv6_key->ipv6_frag,
flow)) {
- return EINVAL;
+ return ODP_FIT_ERROR;
}
- break;
-
- case TRANSITION(OVS_KEY_ATTR_IPV4, OVS_KEY_ATTR_TCP):
- case TRANSITION(OVS_KEY_ATTR_IPV6, OVS_KEY_ATTR_TCP):
- if (flow->nw_proto != IPPROTO_TCP) {
- return EINVAL;
- }
- tcp_key = nl_attr_get(nla);
- flow->tp_src = tcp_key->tcp_src;
- flow->tp_dst = tcp_key->tcp_dst;
- break;
-
- case TRANSITION(OVS_KEY_ATTR_IPV4, OVS_KEY_ATTR_UDP):
- case TRANSITION(OVS_KEY_ATTR_IPV6, OVS_KEY_ATTR_UDP):
- if (flow->nw_proto != IPPROTO_UDP) {
- return EINVAL;
- }
- udp_key = nl_attr_get(nla);
- flow->tp_src = udp_key->udp_src;
- flow->tp_dst = udp_key->udp_dst;
- break;
-
- case TRANSITION(OVS_KEY_ATTR_IPV4, OVS_KEY_ATTR_ICMP):
- if (flow->nw_proto != IPPROTO_ICMP) {
- return EINVAL;
- }
- icmp_key = nl_attr_get(nla);
- flow->tp_src = htons(icmp_key->icmp_type);
- flow->tp_dst = htons(icmp_key->icmp_code);
- break;
-
- case TRANSITION(OVS_KEY_ATTR_IPV6, OVS_KEY_ATTR_ICMPV6):
- if (flow->nw_proto != IPPROTO_ICMPV6) {
- return EINVAL;
- }
- icmpv6_key = nl_attr_get(nla);
- flow->tp_src = htons(icmpv6_key->icmpv6_type);
- flow->tp_dst = htons(icmpv6_key->icmpv6_code);
- break;
+ }
+ } else if (flow->dl_type == htons(ETH_TYPE_ARP)) {
+ expected_attrs |= 1u << OVS_KEY_ATTR_ARP;
+ if (attrs[OVS_KEY_ATTR_ARP]) {
+ const struct ovs_key_arp *arp_key;
- case TRANSITION(OVS_KEY_ATTR_ETHERTYPE, OVS_KEY_ATTR_ARP):
- if (flow->dl_type != htons(ETH_TYPE_ARP)) {
- return EINVAL;
- }
- arp_key = nl_attr_get(nla);
+ arp_key = nl_attr_get(attrs[OVS_KEY_ATTR_ARP]);
flow->nw_src = arp_key->arp_sip;
flow->nw_dst = arp_key->arp_tip;
if (arp_key->arp_op & htons(0xff00)) {
- return EINVAL;
+ VLOG_ERR_RL(&rl, "unsupported ARP opcode %"PRIu16,
+ ntohs(arp_key->arp_op));
+ return ODP_FIT_ERROR;
}
flow->nw_proto = ntohs(arp_key->arp_op);
memcpy(flow->arp_sha, arp_key->arp_sha, ETH_ADDR_LEN);
memcpy(flow->arp_tha, arp_key->arp_tha, ETH_ADDR_LEN);
- break;
-
- case TRANSITION(OVS_KEY_ATTR_ICMPV6, OVS_KEY_ATTR_ND):
- if (flow->tp_src != htons(ND_NEIGHBOR_SOLICIT)
- && flow->tp_src != htons(ND_NEIGHBOR_ADVERT)) {
- return EINVAL;
- }
- nd_key = nl_attr_get(nla);
- memcpy(&flow->nd_target, nd_key->nd_target, sizeof flow->nd_target);
- memcpy(flow->arp_sha, nd_key->nd_sll, ETH_ADDR_LEN);
- memcpy(flow->arp_tha, nd_key->nd_tll, ETH_ADDR_LEN);
- break;
-
- default:
- return EINVAL;
}
-
- prev_type = type;
- }
- if (left) {
- return EINVAL;
}
- switch (prev_type) {
- case OVS_KEY_ATTR_UNSPEC:
- return EINVAL;
-
- case OVS_KEY_ATTR_PRIORITY:
- case OVS_KEY_ATTR_TUN_ID:
- case OVS_KEY_ATTR_IN_PORT:
- return EINVAL;
-
- case OVS_KEY_ATTR_ETHERNET:
- case OVS_KEY_ATTR_8021Q:
- return 0;
-
- case OVS_KEY_ATTR_ETHERTYPE:
- if (flow->dl_type == htons(ETH_TYPE_IP)
- || flow->dl_type == htons(ETH_TYPE_IPV6)
- || flow->dl_type == htons(ETH_TYPE_ARP)) {
- return EINVAL;
- }
- return 0;
-
- case OVS_KEY_ATTR_IPV4:
- if (flow->tos_frag & FLOW_FRAG_LATER) {
- return 0;
- }
- if (flow->nw_proto == IPPROTO_TCP
- || flow->nw_proto == IPPROTO_UDP
- || flow->nw_proto == IPPROTO_ICMP) {
- return EINVAL;
- }
- return 0;
+ if (flow->nw_proto == IPPROTO_TCP
+ && (flow->dl_type == htons(ETH_TYPE_IP) ||
+ flow->dl_type == htons(ETH_TYPE_IPV6))
+ && !(flow->tos_frag & FLOW_FRAG_LATER)) {
+ expected_attrs |= 1u << OVS_KEY_ATTR_TCP;
+ if (attrs[OVS_KEY_ATTR_TCP]) {
+ const struct ovs_key_tcp *tcp_key;
- case OVS_KEY_ATTR_IPV6:
- if (flow->tos_frag & FLOW_FRAG_LATER) {
- return 0;
+ tcp_key = nl_attr_get(attrs[OVS_KEY_ATTR_TCP]);
+ flow->tp_src = tcp_key->tcp_src;
+ flow->tp_dst = tcp_key->tcp_dst;
}
- if (flow->nw_proto == IPPROTO_TCP
- || flow->nw_proto == IPPROTO_UDP
- || flow->nw_proto == IPPROTO_ICMPV6) {
- return EINVAL;
+ } else if (flow->nw_proto == IPPROTO_UDP
+ && (flow->dl_type == htons(ETH_TYPE_IP) ||
+ flow->dl_type == htons(ETH_TYPE_IPV6))
+ && !(flow->tos_frag & FLOW_FRAG_LATER)) {
+ expected_attrs |= 1u << OVS_KEY_ATTR_UDP;
+ if (attrs[OVS_KEY_ATTR_UDP]) {
+ const struct ovs_key_udp *udp_key;
+
+ udp_key = nl_attr_get(attrs[OVS_KEY_ATTR_UDP]);
+ flow->tp_src = udp_key->udp_src;
+ flow->tp_dst = udp_key->udp_dst;
}
- return 0;
-
- case OVS_KEY_ATTR_ICMPV6:
- if (flow->tp_src == htons(ND_NEIGHBOR_SOLICIT)
- || flow->tp_src == htons(ND_NEIGHBOR_ADVERT)
- || flow->tos_frag & FLOW_FRAG_LATER) {
- return EINVAL;
+ } else if (flow->nw_proto == IPPROTO_ICMP
+ && flow->dl_type == htons(ETH_TYPE_IP)
+ && !(flow->tos_frag & FLOW_FRAG_LATER)) {
+ expected_attrs |= 1u << OVS_KEY_ATTR_ICMP;
+ if (attrs[OVS_KEY_ATTR_ICMP]) {
+ const struct ovs_key_icmp *icmp_key;
+
+ icmp_key = nl_attr_get(attrs[OVS_KEY_ATTR_ICMP]);
+ flow->tp_src = htons(icmp_key->icmp_type);
+ flow->tp_dst = htons(icmp_key->icmp_code);
}
- return 0;
+ } else if (flow->nw_proto == IPPROTO_ICMPV6
+ && flow->dl_type == htons(ETH_TYPE_IPV6)
+ && !(flow->tos_frag & FLOW_FRAG_LATER)) {
+ expected_attrs |= 1u << OVS_KEY_ATTR_ICMPV6;
+ if (attrs[OVS_KEY_ATTR_ICMPV6]) {
+ const struct ovs_key_icmpv6 *icmpv6_key;
+
+ icmpv6_key = nl_attr_get(attrs[OVS_KEY_ATTR_ICMPV6]);
+ flow->tp_src = htons(icmpv6_key->icmpv6_type);
+ flow->tp_dst = htons(icmpv6_key->icmpv6_code);
- case OVS_KEY_ATTR_TCP:
- case OVS_KEY_ATTR_UDP:
- case OVS_KEY_ATTR_ICMP:
- case OVS_KEY_ATTR_ND:
- if (flow->tos_frag & FLOW_FRAG_LATER) {
- return EINVAL;
+ if (flow->tp_src == htons(ND_NEIGHBOR_SOLICIT) ||
+ flow->tp_src == htons(ND_NEIGHBOR_ADVERT)) {
+ expected_attrs |= 1u << OVS_KEY_ATTR_ND;
+ if (attrs[OVS_KEY_ATTR_ND]) {
+ const struct ovs_key_nd *nd_key;
+
+ nd_key = nl_attr_get(attrs[OVS_KEY_ATTR_ND]);
+ memcpy(&flow->nd_target, nd_key->nd_target,
+ sizeof flow->nd_target);
+ memcpy(flow->arp_sha, nd_key->nd_sll, ETH_ADDR_LEN);
+ memcpy(flow->arp_tha, nd_key->nd_tll, ETH_ADDR_LEN);
+ }
+ }
}
- return 0;
+ }
- case OVS_KEY_ATTR_ARP:
- return 0;
+ missing_attrs = present_attrs & ~expected_attrs;
+ if (missing_attrs) {
+ static struct vlog_rate_limit miss_rl = VLOG_RATE_LIMIT_INIT(10, 10);
+ log_odp_key_attributes(&miss_rl, "expected but not present",
+ missing_attrs, key, key_len);
+ return ODP_FIT_TOO_LITTLE;
+ }
- case __OVS_KEY_ATTR_MAX:
- default:
- NOT_REACHED();
+ extra_attrs = expected_attrs & ~present_attrs;
+ if (extra_attrs) {
+ static struct vlog_rate_limit extra_rl = VLOG_RATE_LIMIT_INIT(10, 10);
+ log_odp_key_attributes(&extra_rl, "present but not expected",
+ extra_attrs, key, key_len);
+ return ODP_FIT_TOO_MUCH;
}
+
+ return ODP_FIT_PERFECT;
}
diff --git a/lib/odp-util.h b/lib/odp-util.h
index eceeae6..0786088 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -96,7 +96,22 @@ void odp_flow_key_format(const struct nlattr *, size_t, struct ds *);
int odp_flow_key_from_string(const char *s, struct ofpbuf *);
void odp_flow_key_from_flow(struct ofpbuf *, const struct flow *);
-int odp_flow_key_to_flow(const struct nlattr *, size_t, struct flow *);
+
+uint32_t odp_flow_key_hash(const struct nlattr *, size_t);
+
+/* How well a kernel-provided flow key (asequence of OVS_KEY_ATTR_*
+ * attributes) matches OVS userspace expectations.
+ *
+ * A single flow key can fit the descriptions for both ODP_FIT_TOO_LITTLE and
+ * ODP_FIT_TOO_MUCH. Such a key is treated as ODP_FIT_TOO_LITTLE. */
+enum odp_key_fitness {
+ ODP_FIT_PERFECT, /* The key had exactly the fields we expect. */
+ ODP_FIT_TOO_LITTLE, /* The key lacked fields we expected to see. */
+ ODP_FIT_TOO_MUCH, /* The key had fields we don't understand. */
+ ODP_FIT_ERROR, /* The key was invalid. */
+};
+enum odp_key_fitness odp_flow_key_to_flow(const struct nlattr *, size_t,
+ struct flow *);
enum user_action_cookie_type {
USER_ACTION_COOKIE_UNSPEC,
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 33a1c86..420004b 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -88,7 +88,7 @@ struct rule_dpif {
*
* - Do not include packet or bytes that can be obtained from any facet's
* packet_count or byte_count member or that can be obtained from the
- * datapath by, e.g., dpif_flow_get() for any facet.
+ * datapath by, e.g., dpif_flow_get() for any subfacet.
*/
uint64_t packet_count; /* Number of packets received. */
uint64_t byte_count; /* Number of bytes received. */
@@ -106,6 +106,15 @@ static struct rule_dpif *rule_dpif_cast(const struct rule *rule)
static struct rule_dpif *rule_dpif_lookup(struct ofproto_dpif *,
const struct flow *, uint8_t table);
+static void flow_push_stats(const struct rule_dpif *,
+ struct flow *, uint64_t packets, uint64_t bytes,
+ long long int used);
+
+static uint32_t rule_calculate_tag(const struct flow *,
+ const struct flow_wildcards *,
+ uint32_t basis);
+static void rule_invalidate(const struct rule_dpif *);
+
#define MAX_MIRRORS 32
typedef uint32_t mirror_mask_t;
#define MIRROR_MASK_C(X) UINT32_C(X)
@@ -220,44 +229,65 @@ static void action_xlate_ctx_init(struct action_xlate_ctx *,
static struct ofpbuf *xlate_actions(struct action_xlate_ctx *,
const union ofp_action *in, size_t n_in);
-/* An exact-match instantiation of an OpenFlow flow. */
+/* An exact-match instantiation of an OpenFlow flow.
+ *
+ * A facet associates a "struct flow", which represents the Open vSwitch
+ * userspace idea of an exact-match flow, with a set of datapath actions.
+ *
+ * A facet contains one or more subfacets. Each subfacet tracks the datapath's
+ * idea of the exact-match flow equivalent to the facet. When the kernel
+ * module (or other dpif implementation) and Open vSwitch userspace agree on
+ * the definition of a flow key, there is exactly one subfacet per facet. If
+ * the dpif implementation supports more-specific flow matching than userspace,
+ * however, a facet can have more than one subfacet, each of which corresponds
+ * to some distinction in flow that userspace simply doesn't understand.
+ *
+ * Flow expiration works in terms of subfacets, so a facet must have at least
+ * one subfacet or it will never expire, leaking memory. */
struct facet {
+ /* Owners. */
+ struct hmap_node hmap_node; /* In owning ofproto's 'facets' hmap. */
+ struct list list_node; /* In owning rule's 'facets' list. */
+ struct rule_dpif *rule; /* Owning rule. */
+
+ /* Owned data. */
+ struct list subfacets;
long long int used; /* Time last used; time created if not used. */
+ /* Key. */
+ struct flow flow;
+
/* These statistics:
*
* - Do include packets and bytes sent "by hand", e.g. with
* dpif_execute().
*
* - Do include packets and bytes that were obtained from the datapath
- * when its statistics were reset (e.g. dpif_flow_put() with
+ * when a subfacet's statistics were reset (e.g. dpif_flow_put() with
* DPIF_FP_ZERO_STATS).
+ *
+ * - Do not include packets or bytes that can be obtained from the
+ * datapath for any existing subfacet.
*/
uint64_t packet_count; /* Number of packets received. */
uint64_t byte_count; /* Number of bytes received. */
- uint64_t dp_packet_count; /* Last known packet count in the datapath. */
- uint64_t dp_byte_count; /* Last known byte count in the datapath. */
-
+ /* Resubmit statistics. */
uint64_t rs_packet_count; /* Packets pushed to resubmit children. */
uint64_t rs_byte_count; /* Bytes pushed to resubmit children. */
long long int rs_used; /* Used time pushed to resubmit children. */
+ /* Accounting. */
uint64_t accounted_bytes; /* Bytes processed by facet_account(). */
+ struct netflow_flow nf_flow; /* Per-flow NetFlow tracking data. */
- struct hmap_node hmap_node; /* In owning ofproto's 'facets' hmap. */
- struct list list_node; /* In owning rule's 'facets' list. */
- struct rule_dpif *rule; /* Owning rule. */
- struct flow flow; /* Exact-match flow. */
- bool installed; /* Installed in datapath? */
- bool may_install; /* True ordinarily; false if actions must
- * be reassessed for every packet. */
+ /* Datapath actions. */
+ bool may_install; /* Reassess actions for every packet? */
bool has_learn; /* Actions include NXAST_LEARN? */
bool has_normal; /* Actions output to OFPP_NORMAL? */
size_t actions_len; /* Number of bytes in actions[]. */
struct nlattr *actions; /* Datapath actions. */
- tag_type tags; /* Tags. */
- struct netflow_flow nf_flow; /* Per-flow NetFlow tracking data. */
+ tag_type tags; /* Tags that would require revalidation. */
};
static struct facet *facet_create(struct rule_dpif *, const struct flow *);
@@ -274,38 +304,64 @@ static bool execute_controller_action(struct ofproto_dpif *,
const struct nlattr *odp_actions,
size_t actions_len,
struct ofpbuf *packet);
-static void facet_execute(struct ofproto_dpif *, struct facet *,
- struct ofpbuf *packet);
-
-static int facet_put__(struct ofproto_dpif *, struct facet *,
- const struct nlattr *actions, size_t actions_len,
- struct dpif_flow_stats *);
-static void facet_install(struct ofproto_dpif *, struct facet *,
- bool zero_stats);
-static void facet_uninstall(struct ofproto_dpif *, struct facet *);
+
static void facet_flush_stats(struct ofproto_dpif *, struct facet *);
static void facet_make_actions(struct ofproto_dpif *, struct facet *,
const struct ofpbuf *packet);
static void facet_update_time(struct ofproto_dpif *, struct facet *,
- long long int used);
-static void facet_update_stats(struct ofproto_dpif *, struct facet *,
- const struct dpif_flow_stats *);
+ long long int used);
static void facet_reset_counters(struct facet *);
-static void facet_reset_dp_stats(struct facet *, struct dpif_flow_stats *);
static void facet_push_stats(struct facet *);
static void facet_account(struct ofproto_dpif *, struct facet *);
static bool facet_is_controller_flow(struct facet *);
-static void flow_push_stats(const struct rule_dpif *,
- struct flow *, uint64_t packets, uint64_t bytes,
- long long int used);
+/* A dpif flow associated with a facet.
+ *
+ * See also the large comment on struct subfacet. */
+struct subfacet {
+ /* Owners. */
+ struct hmap_node hmap_node; /* In struct ofproto_dpif's 'subfacets' map. */
+ struct list list_node; /* In struct facet's 'facets' list. */
+ struct facet *facet; /* Owning facet. */
+
+ /* Key.
+ *
+ * To save memory in the common case, 'key' is NULL if 'key_fitness' is
+ * ODP_FIT_PERFECT, that is, odp_flow_key_from_flow() can accurately
+ * regenerate the ODP flow key from ->facet->flow. */
+ enum odp_key_fitness key_fitness;
+ struct nlattr *key;
+ int key_len;
-static uint32_t rule_calculate_tag(const struct flow *,
- const struct flow_wildcards *,
- uint32_t basis);
-static void rule_invalidate(const struct rule_dpif *);
+ long long int used; /* Time last used; time created if not used. */
+
+ uint64_t dp_packet_count; /* Last known packet count in the datapath. */
+ uint64_t dp_byte_count; /* Last known byte count in the datapath. */
+
+ bool installed; /* Installed in datapath? */
+};
+
+static struct subfacet *subfacet_create(struct ofproto_dpif *, struct facet *,
+ enum odp_key_fitness,
+ const struct nlattr *key,
+ size_t key_len);
+static struct subfacet *subfacet_find(struct ofproto_dpif *,
+ const struct nlattr *key, size_t key_len,
+ const struct flow *);
+static void subfacet_destroy(struct ofproto_dpif *, struct subfacet *);
+static void subfacet_destroy__(struct ofproto_dpif *, struct subfacet *);
+static void subfacet_reset_dp_stats(struct subfacet *,
+ struct dpif_flow_stats *);
+static void subfacet_update_time(struct ofproto_dpif *, struct subfacet *,
+ long long int used);
+static void subfacet_update_stats(struct ofproto_dpif *, struct subfacet *,
+ const struct dpif_flow_stats *);
+static int subfacet_install(struct ofproto_dpif *, struct subfacet *,
+ const struct nlattr *actions, size_t actions_len,
+ struct dpif_flow_stats *);
+static void subfacet_uninstall(struct ofproto_dpif *, struct subfacet *);
struct ofport_dpif {
struct ofport up;
@@ -371,6 +427,7 @@ struct ofproto_dpif {
/* Facets. */
struct hmap facets;
+ struct hmap subfacets;
/* Revalidation. */
struct table_dpif tables[N_TABLES];
@@ -520,6 +577,7 @@ construct(struct ofproto *ofproto_, int *n_tablesp)
timer_set_duration(&ofproto->next_expiration, 1000);
hmap_init(&ofproto->facets);
+ hmap_init(&ofproto->subfacets);
for (i = 0; i < N_TABLES; i++) {
struct table_dpif *table = &ofproto->tables[i];
@@ -582,6 +640,7 @@ destruct(struct ofproto *ofproto_)
mac_learning_destroy(ofproto->ml);
hmap_destroy(&ofproto->facets);
+ hmap_destroy(&ofproto->subfacets);
dpif_close(ofproto->dpif);
}
@@ -720,9 +779,13 @@ flush(struct ofproto *ofproto_)
* bother trying to uninstall it. There is no point in uninstalling it
* individually since we are about to blow away all the facets with
* dpif_flow_flush(). */
- facet->installed = false;
- facet->dp_packet_count = 0;
- facet->dp_byte_count = 0;
+ struct subfacet *subfacet;
+
+ LIST_FOR_EACH (subfacet, list_node, &facet->subfacets) {
+ subfacet->installed = false;
+ subfacet->dp_packet_count = 0;
+ subfacet->dp_byte_count = 0;
+ }
facet_remove(ofproto, facet);
}
dpif_flow_flush(ofproto->dpif);
@@ -2013,6 +2076,7 @@ port_is_lacp_current(const struct ofport *ofport_)
struct flow_miss {
struct hmap_node hmap_node;
struct flow flow;
+ enum odp_key_fitness key_fitness;
const struct nlattr *key;
size_t key_len;
struct list packets;
@@ -2020,7 +2084,7 @@ struct flow_miss {
struct flow_miss_op {
union dpif_op dpif_op;
- struct facet *facet;
+ struct subfacet *subfacet;
};
/* Sends an OFPT_PACKET_IN message for 'packet' of type OFPR_NO_MATCH to each
@@ -2103,6 +2167,7 @@ process_special(struct ofproto_dpif *ofproto, const struct flow *flow,
static struct flow_miss *
flow_miss_create(struct hmap *todo, const struct flow *flow,
+ enum odp_key_fitness key_fitness,
const struct nlattr *key, size_t key_len)
{
uint32_t hash = flow_hash(flow, 0);
@@ -2117,6 +2182,7 @@ flow_miss_create(struct hmap *todo, const struct flow *flow,
miss = xmalloc(sizeof *miss);
hmap_insert(todo, &miss->hmap_node, hash);
miss->flow = *flow;
+ miss->key_fitness = key_fitness;
miss->key = key;
miss->key_len = key_len;
list_init(&miss->packets);
@@ -2129,6 +2195,7 @@ handle_flow_miss(struct ofproto_dpif *ofproto, struct flow_miss *miss,
{
const struct flow *flow = &miss->flow;
struct ofpbuf *packet, *next_packet;
+ struct subfacet *subfacet;
struct facet *facet;
facet = facet_lookup_valid(ofproto, flow);
@@ -2162,6 +2229,9 @@ handle_flow_miss(struct ofproto_dpif *ofproto, struct flow_miss *miss,
facet = facet_create(rule, flow);
}
+ subfacet = subfacet_create(ofproto, facet,
+ miss->key_fitness, miss->key, miss->key_len);
+
LIST_FOR_EACH_SAFE (packet, next_packet, list_node, &miss->packets) {
list_remove(&packet->list_node);
ofproto->n_matches++;
@@ -2189,7 +2259,7 @@ handle_flow_miss(struct ofproto_dpif *ofproto, struct flow_miss *miss,
struct flow_miss_op *op = &ops[(*n_ops)++];
struct dpif_execute *execute = &op->dpif_op.execute;
- op->facet = facet;
+ op->subfacet = subfacet;
execute->type = DPIF_OP_EXECUTE;
execute->key = miss->key;
execute->key_len = miss->key_len;
@@ -2202,11 +2272,11 @@ handle_flow_miss(struct ofproto_dpif *ofproto, struct flow_miss *miss,
}
}
- if (facet->may_install) {
+ if (facet->may_install && subfacet->key_fitness != ODP_FIT_TOO_LITTLE) {
struct flow_miss_op *op = &ops[(*n_ops)++];
struct dpif_flow_put *put = &op->dpif_op.flow_put;
- op->facet = facet;
+ op->subfacet = subfacet;
put->type = DPIF_OP_FLOW_PUT;
put->flags = DPIF_FP_CREATE | DPIF_FP_MODIFY;
put->key = miss->key;
@@ -2240,12 +2310,16 @@ handle_miss_upcalls(struct ofproto_dpif *ofproto, struct dpif_upcall *upcalls,
* that we can process them together. */
hmap_init(&todo);
for (upcall = upcalls; upcall < &upcalls[n_upcalls]; upcall++) {
+ enum odp_key_fitness fitness;
struct flow_miss *miss;
struct flow flow;
- /* Obtain in_port and tun_id, at least, then set 'flow''s header
- * pointers. */
- odp_flow_key_to_flow(upcall->key, upcall->key_len, &flow);
+ /* Obtain metadata and check userspace/kernel agreement on flow match,
+ * then set 'flow''s header pointers. */
+ fitness = odp_flow_key_to_flow(upcall->key, upcall->key_len, &flow);
+ if (fitness == ODP_FIT_ERROR) {
+ continue;
+ }
flow_extract(upcall->packet, flow.priority, flow.tun_id,
flow.in_port, &flow);
@@ -2257,7 +2331,8 @@ handle_miss_upcalls(struct ofproto_dpif *ofproto, struct dpif_upcall *upcalls,
}
/* Add other packets to a to-do list. */
- miss = flow_miss_create(&todo, &flow, upcall->key, upcall->key_len);
+ miss = flow_miss_create(&todo, &flow, fitness,
+ upcall->key, upcall->key_len);
list_push_back(&miss->packets, &upcall->packet->list_node);
}
@@ -2288,7 +2363,7 @@ handle_miss_upcalls(struct ofproto_dpif *ofproto, struct dpif_upcall *upcalls,
switch (op->dpif_op.type) {
case DPIF_OP_EXECUTE:
execute = &op->dpif_op.execute;
- if (op->facet->actions != execute->actions) {
+ if (op->subfacet->facet->actions != execute->actions) {
free((struct nlattr *) execute->actions);
}
ofpbuf_delete((struct ofpbuf *) execute->packet);
@@ -2297,7 +2372,7 @@ handle_miss_upcalls(struct ofproto_dpif *ofproto, struct dpif_upcall *upcalls,
case DPIF_OP_FLOW_PUT:
put = &op->dpif_op.flow_put;
if (!put->error) {
- op->facet->installed = true;
+ op->subfacet->installed = true;
}
break;
}
@@ -2351,10 +2426,10 @@ handle_upcall(struct ofproto_dpif *ofproto, struct dpif_upcall *upcall)
/* Flow expiration. */
-static int facet_max_idle(const struct ofproto_dpif *);
+static int subfacet_max_idle(const struct ofproto_dpif *);
static void update_stats(struct ofproto_dpif *);
static void rule_expire(struct rule_dpif *);
-static void expire_facets(struct ofproto_dpif *, int dp_max_idle);
+static void expire_subfacets(struct ofproto_dpif *, int dp_max_idle);
/* This function is called periodically by run(). Its job is to collect
* updates for the flows that have been installed into the datapath, most
@@ -2372,9 +2447,9 @@ expire(struct ofproto_dpif *ofproto)
/* Update stats for each flow in the datapath. */
update_stats(ofproto);
- /* Expire facets that have been idle too long. */
- dp_max_idle = facet_max_idle(ofproto);
- expire_facets(ofproto, dp_max_idle);
+ /* Expire subfacets that have been idle too long. */
+ dp_max_idle = subfacet_max_idle(ofproto);
+ expire_subfacets(ofproto, dp_max_idle);
/* Expire OpenFlow flows whose idle_timeout or hard_timeout has passed. */
OFPROTO_FOR_EACH_TABLE (table, &ofproto->up) {
@@ -2422,46 +2497,41 @@ update_stats(struct ofproto_dpif *p)
dpif_flow_dump_start(&dump, p->dpif);
while (dpif_flow_dump_next(&dump, &key, &key_len, NULL, NULL, &stats)) {
- struct facet *facet;
+ enum odp_key_fitness fitness;
+ struct subfacet *subfacet;
struct flow flow;
- if (odp_flow_key_to_flow(key, key_len, &flow)) {
- struct ds s;
-
- ds_init(&s);
- odp_flow_key_format(key, key_len, &s);
- VLOG_WARN_RL(&rl, "failed to convert datapath flow key to flow: %s",
- ds_cstr(&s));
- ds_destroy(&s);
-
+ fitness = odp_flow_key_to_flow(key, key_len, &flow);
+ if (fitness == ODP_FIT_ERROR) {
continue;
}
- facet = facet_find(p, &flow);
- if (facet && facet->installed) {
+ subfacet = subfacet_find(p, key, key_len, &flow);
+ if (subfacet && subfacet->installed) {
+ struct facet *facet = subfacet->facet;
- if (stats->n_packets >= facet->dp_packet_count) {
- uint64_t extra = stats->n_packets - facet->dp_packet_count;
+ if (stats->n_packets >= subfacet->dp_packet_count) {
+ uint64_t extra = stats->n_packets - subfacet->dp_packet_count;
facet->packet_count += extra;
} else {
VLOG_WARN_RL(&rl, "unexpected packet count from the datapath");
}
- if (stats->n_bytes >= facet->dp_byte_count) {
- facet->byte_count += stats->n_bytes - facet->dp_byte_count;
+ if (stats->n_bytes >= subfacet->dp_byte_count) {
+ facet->byte_count += stats->n_bytes - subfacet->dp_byte_count;
} else {
VLOG_WARN_RL(&rl, "unexpected byte count from datapath");
}
- facet->dp_packet_count = stats->n_packets;
- facet->dp_byte_count = stats->n_bytes;
+ subfacet->dp_packet_count = stats->n_packets;
+ subfacet->dp_byte_count = stats->n_bytes;
- facet_update_time(p, facet, stats->used);
+ subfacet_update_time(p, subfacet, stats->used);
facet_account(p, facet);
facet_push_stats(facet);
} else {
- /* There's a flow in the datapath that we know nothing about.
- * Delete it. */
+ /* There's a flow in the datapath that we know nothing about, or a
+ * flow that shouldn't be installed but was anyway. Delete it. */
COVERAGE_INC(facet_unexpected);
dpif_flow_del(p->dpif, key, key_len, NULL);
}
@@ -2470,58 +2540,60 @@ update_stats(struct ofproto_dpif *p)
}
/* Calculates and returns the number of milliseconds of idle time after which
- * facets should expire from the datapath and we should fold their statistics
- * into their parent rules in userspace. */
+ * subfacets should expire from the datapath. When a subfacet expires, we fold
+ * its statistics into its facet, and when a facet's last subfacet expires, we
+ * fold its statistic into its rule. */
static int
-facet_max_idle(const struct ofproto_dpif *ofproto)
+subfacet_max_idle(const struct ofproto_dpif *ofproto)
{
/*
* Idle time histogram.
*
- * Most of the time a switch has a relatively small number of facets. When
- * this is the case we might as well keep statistics for all of them in
- * userspace and to cache them in the kernel datapath for performance as
+ * Most of the time a switch has a relatively small number of subfacets.
+ * When this is the case we might as well keep statistics for all of them
+ * in userspace and to cache them in the kernel datapath for performance as
* well.
*
- * As the number of facets increases, the memory required to maintain
+ * As the number of subfacets increases, the memory required to maintain
* statistics about them in userspace and in the kernel becomes
- * significant. However, with a large number of facets it is likely that
- * only a few of them are "heavy hitters" that consume a large amount of
- * bandwidth. At this point, only heavy hitters are worth caching in the
- * kernel and maintaining in userspaces; other facets we can discard.
+ * significant. However, with a large number of subfacets it is likely
+ * that only a few of them are "heavy hitters" that consume a large amount
+ * of bandwidth. At this point, only heavy hitters are worth caching in
+ * the kernel and maintaining in userspaces; other subfacets we can
+ * discard.
*
* The technique used to compute the idle time is to build a histogram with
- * N_BUCKETS buckets whose width is BUCKET_WIDTH msecs each. Each facet
+ * N_BUCKETS buckets whose width is BUCKET_WIDTH msecs each. Each subfacet
* that is installed in the kernel gets dropped in the appropriate bucket.
* After the histogram has been built, we compute the cutoff so that only
- * the most-recently-used 1% of facets (but at least
+ * the most-recently-used 1% of subfacets (but at least
* ofproto->up.flow_eviction_threshold flows) are kept cached. At least
- * the most-recently-used bucket of facets is kept, so actually an
- * arbitrary number of facets can be kept in any given expiration run
+ * the most-recently-used bucket of subfacets is kept, so actually an
+ * arbitrary number of subfacets can be kept in any given expiration run
* (though the next run will delete most of those unless they receive
* additional data).
*
- * This requires a second pass through the facets, in addition to the pass
- * made by update_stats(), because the former function never looks
- * at uninstallable facets.
+ * This requires a second pass through the subfacets, in addition to the
+ * pass made by update_stats(), because the former function never looks at
+ * uninstallable subfacets.
*/
enum { BUCKET_WIDTH = ROUND_UP(100, TIME_UPDATE_INTERVAL) };
enum { N_BUCKETS = 5000 / BUCKET_WIDTH };
int buckets[N_BUCKETS] = { 0 };
int total, subtotal, bucket;
- struct facet *facet;
+ struct subfacet *subfacet;
long long int now;
int i;
- total = hmap_count(&ofproto->facets);
+ total = hmap_count(&ofproto->subfacets);
if (total <= ofproto->up.flow_eviction_threshold) {
return N_BUCKETS * BUCKET_WIDTH;
}
/* Build histogram. */
now = time_msec();
- HMAP_FOR_EACH (facet, hmap_node, &ofproto->facets) {
- long long int idle = now - facet->used;
+ HMAP_FOR_EACH (subfacet, hmap_node, &ofproto->subfacets) {
+ long long int idle = now - subfacet->used;
int bucket = (idle <= 0 ? 0
: idle >= BUCKET_WIDTH * N_BUCKETS ? N_BUCKETS - 1
: (unsigned int) idle / BUCKET_WIDTH);
@@ -2556,14 +2628,15 @@ facet_max_idle(const struct ofproto_dpif *ofproto)
}
static void
-expire_facets(struct ofproto_dpif *ofproto, int dp_max_idle)
+expire_subfacets(struct ofproto_dpif *ofproto, int dp_max_idle)
{
long long int cutoff = time_msec() - dp_max_idle;
- struct facet *facet, *next_facet;
+ struct subfacet *subfacet, *next_subfacet;
- HMAP_FOR_EACH_SAFE (facet, next_facet, hmap_node, &ofproto->facets) {
- if (facet->used < cutoff) {
- facet_remove(ofproto, facet);
+ HMAP_FOR_EACH_SAFE (subfacet, next_subfacet, hmap_node,
+ &ofproto->subfacets) {
+ if (subfacet->used < cutoff) {
+ subfacet_destroy(ofproto, subfacet);
}
}
}
@@ -2611,7 +2684,10 @@ rule_expire(struct rule_dpif *rule)
* the ofproto's classifier table.
*
* The facet will initially have no ODP actions. The caller should fix that
- * by calling facet_make_actions(). */
+ * by calling facet_make_actions().
+ *
+ * The facet will initially have no subfacets. The caller should create (at
+ * least) one subfacet with subfacet_create(). */
static struct facet *
facet_create(struct rule_dpif *rule, const struct flow *flow)
{
@@ -2624,6 +2700,7 @@ facet_create(struct rule_dpif *rule, const struct flow *flow)
list_push_back(&rule->facets, &facet->list_node);
facet->rule = rule;
facet->flow = *flow;
+ list_init(&facet->subfacets);
netflow_flow_init(&facet->nf_flow);
netflow_flow_update_time(ofproto->netflow, &facet->nf_flow, facet->used);
@@ -2692,45 +2769,23 @@ execute_odp_actions(struct ofproto_dpif *ofproto, const struct flow *flow,
return !error;
}
-/* Executes the actions indicated by 'facet' on 'packet' and credits 'facet''s
- * statistics appropriately. 'packet' must have at least sizeof(struct
- * ofp_packet_in) bytes of headroom.
- *
- * For correct results, 'packet' must actually be in 'facet''s flow; that is,
- * applying flow_extract() to 'packet' would yield the same flow as
- * 'facet->flow'.
- *
- * 'facet' must have accurately composed datapath actions; that is, it must
- * not be in need of revalidation.
- *
- * Takes ownership of 'packet'. */
-static void
-facet_execute(struct ofproto_dpif *ofproto, struct facet *facet,
- struct ofpbuf *packet)
-{
- struct dpif_flow_stats stats;
-
- assert(ofpbuf_headroom(packet) >= sizeof(struct ofp_packet_in));
-
- dpif_flow_stats_extract(&facet->flow, packet, &stats);
- stats.used = time_msec();
- if (execute_odp_actions(ofproto, &facet->flow,
- facet->actions, facet->actions_len, packet)) {
- facet_update_stats(ofproto, facet, &stats);
- }
-}
-
/* Remove 'facet' from 'ofproto' and free up the associated memory:
*
* - If 'facet' was installed in the datapath, uninstalls it and updates its
- * rule's statistics, via facet_uninstall().
+ * rule's statistics, via subfacet_uninstall().
*
* - Removes 'facet' from its rule and from ofproto->facets.
*/
static void
facet_remove(struct ofproto_dpif *ofproto, struct facet *facet)
{
- facet_uninstall(ofproto, facet);
+ struct subfacet *subfacet, *next_subfacet;
+
+ LIST_FOR_EACH_SAFE (subfacet, next_subfacet, list_node,
+ &facet->subfacets) {
+ subfacet_destroy__(ofproto, subfacet);
+ }
+
facet_flush_stats(ofproto, facet);
hmap_remove(&ofproto->facets, &facet->hmap_node);
list_remove(&facet->list_node);
@@ -2764,55 +2819,6 @@ facet_make_actions(struct ofproto_dpif *p, struct facet *facet,
ofpbuf_delete(odp_actions);
}
-/* Updates 'facet''s flow in the datapath setting its actions to 'actions_len'
- * bytes of actions in 'actions'. If 'stats' is non-null, statistics counters
- * in the datapath will be zeroed and 'stats' will be updated with traffic new
- * since 'facet' was last updated.
- *
- * Returns 0 if successful, otherwise a positive errno value.*/
-static int
-facet_put__(struct ofproto_dpif *ofproto, struct facet *facet,
- const struct nlattr *actions, size_t actions_len,
- struct dpif_flow_stats *stats)
-{
- struct odputil_keybuf keybuf;
- enum dpif_flow_put_flags flags;
- struct ofpbuf key;
- int ret;
-
- flags = DPIF_FP_CREATE | DPIF_FP_MODIFY;
- if (stats) {
- flags |= DPIF_FP_ZERO_STATS;
- }
-
- ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
- odp_flow_key_from_flow(&key, &facet->flow);
-
- ret = dpif_flow_put(ofproto->dpif, flags, key.data, key.size,
- actions, actions_len, stats);
-
- if (stats) {
- facet_reset_dp_stats(facet, stats);
- }
-
- return ret;
-}
-
-/* If 'facet' is installable, inserts or re-inserts it into 'p''s datapath. If
- * 'zero_stats' is true, clears any existing statistics from the datapath for
- * 'facet'. */
-static void
-facet_install(struct ofproto_dpif *p, struct facet *facet, bool zero_stats)
-{
- struct dpif_flow_stats stats;
-
- if (facet->may_install
- && !facet_put__(p, facet, facet->actions, facet->actions_len,
- zero_stats ? &stats : NULL)) {
- facet->installed = true;
- }
-}
-
static void
facet_account(struct ofproto_dpif *ofproto, struct facet *facet)
{
@@ -2881,31 +2887,6 @@ facet_account(struct ofproto_dpif *ofproto, struct facet *facet)
}
}
-/* If 'rule' is installed in the datapath, uninstalls it. */
-static void
-facet_uninstall(struct ofproto_dpif *p, struct facet *facet)
-{
- if (facet->installed) {
- struct odputil_keybuf keybuf;
- struct dpif_flow_stats stats;
- struct ofpbuf key;
- int error;
-
- ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
- odp_flow_key_from_flow(&key, &facet->flow);
-
- error = dpif_flow_del(p->dpif, key.data, key.size, &stats);
- facet_reset_dp_stats(facet, &stats);
- if (!error) {
- facet_update_stats(p, facet, &stats);
- }
- facet->installed = false;
- } else {
- assert(facet->dp_packet_count == 0);
- assert(facet->dp_byte_count == 0);
- }
-}
-
/* Returns true if the only action for 'facet' is to send to the controller.
* (We don't report NetFlow expiration messages for such facets because they
* are just part of the control logic for the network, not real traffic). */
@@ -2918,24 +2899,6 @@ facet_is_controller_flow(struct facet *facet)
htons(OFPP_CONTROLLER)));
}
-/* Resets 'facet''s datapath statistics counters. This should be called when
- * 'facet''s statistics are cleared in the datapath. If 'stats' is non-null,
- * it should contain the statistics returned by dpif when 'facet' was reset in
- * the datapath. 'stats' will be modified to only included statistics new
- * since 'facet' was last updated. */
-static void
-facet_reset_dp_stats(struct facet *facet, struct dpif_flow_stats *stats)
-{
- if (stats && facet->dp_packet_count <= stats->n_packets
- && facet->dp_byte_count <= stats->n_bytes) {
- stats->n_packets -= facet->dp_packet_count;
- stats->n_bytes -= facet->dp_byte_count;
- }
-
- facet->dp_packet_count = 0;
- facet->dp_byte_count = 0;
-}
-
/* Folds all of 'facet''s statistics into its rule. Also updates the
* accounting ofhook and emits a NetFlow expiration if appropriate. All of
* 'facet''s statistics in the datapath should have been zeroed and folded into
@@ -2943,8 +2906,12 @@ facet_reset_dp_stats(struct facet *facet, struct dpif_flow_stats *stats)
static void
facet_flush_stats(struct ofproto_dpif *ofproto, struct facet *facet)
{
- assert(!facet->dp_byte_count);
- assert(!facet->dp_packet_count);
+ struct subfacet *subfacet;
+
+ LIST_FOR_EACH (subfacet, list_node, &facet->subfacets) {
+ assert(!subfacet->dp_byte_count);
+ assert(!subfacet->dp_packet_count);
+ }
facet_push_stats(facet);
facet_account(ofproto, facet);
@@ -3027,7 +2994,9 @@ facet_revalidate(struct ofproto_dpif *ofproto, struct facet *facet)
struct action_xlate_ctx ctx;
struct ofpbuf *odp_actions;
struct rule_dpif *new_rule;
+ struct subfacet *subfacet;
bool actions_changed;
+ bool flush_stats;
COVERAGE_INC(facet_revalidate);
@@ -3053,19 +3022,24 @@ facet_revalidate(struct ofproto_dpif *ofproto, struct facet *facet)
/* If the datapath actions changed or the installability changed,
* then we need to talk to the datapath. */
- if (actions_changed || ctx.may_set_up_flow != facet->installed) {
- if (ctx.may_set_up_flow) {
- struct dpif_flow_stats stats;
-
- facet_put__(ofproto, facet,
- odp_actions->data, odp_actions->size, &stats);
- facet_update_stats(ofproto, facet, &stats);
- } else {
- facet_uninstall(ofproto, facet);
+ flush_stats = false;
+ LIST_FOR_EACH (subfacet, list_node, &facet->subfacets) {
+ bool should_install = (ctx.may_set_up_flow
+ && subfacet->key_fitness != ODP_FIT_TOO_LITTLE);
+ if (actions_changed || should_install != subfacet->installed) {
+ if (should_install) {
+ struct dpif_flow_stats stats;
+
+ subfacet_install(ofproto, subfacet,
+ odp_actions->data, odp_actions->size, &stats);
+ subfacet_update_stats(ofproto, subfacet, &stats);
+ } else {
+ subfacet_uninstall(ofproto, subfacet);
+ }
+ flush_stats = true;
}
-
- /* The datapath flow is gone or has zeroed stats, so push stats out of
- * 'facet' into 'rule'. */
+ }
+ if (flush_stats) {
facet_flush_stats(ofproto, facet);
}
@@ -3109,25 +3083,6 @@ facet_update_time(struct ofproto_dpif *ofproto, struct facet *facet,
}
}
-/* Folds the statistics from 'stats' into the counters in 'facet'.
- *
- * Because of the meaning of a facet's counters, it only makes sense to do this
- * if 'stats' are not tracked in the datapath, that is, if 'stats' represents a
- * packet that was sent by hand or if it represents statistics that have been
- * cleared out of the datapath. */
-static void
-facet_update_stats(struct ofproto_dpif *ofproto, struct facet *facet,
- const struct dpif_flow_stats *stats)
-{
- if (stats->n_packets || stats->used > facet->used) {
- facet_update_time(ofproto, facet, stats->used);
- facet->packet_count += stats->n_packets;
- facet->byte_count += stats->n_bytes;
- facet_push_stats(facet);
- netflow_flow_update_flags(&facet->nf_flow, stats->tcp_flags);
- }
-}
-
static void
facet_reset_counters(struct facet *facet)
{
@@ -3199,6 +3154,225 @@ flow_push_stats(const struct rule_dpif *rule,
rule->up.actions, rule->up.n_actions));
}
+/* Subfacets. */
+
+static struct subfacet *
+subfacet_find__(struct ofproto_dpif *ofproto,
+ const struct nlattr *key, size_t key_len, uint32_t key_hash,
+ const struct flow *flow)
+{
+ struct subfacet *subfacet;
+
+ HMAP_FOR_EACH_WITH_HASH (subfacet, hmap_node, key_hash,
+ &ofproto->subfacets) {
+ if (subfacet->key
+ ? (subfacet->key_len == key_len
+ && !memcmp(key, subfacet->key, key_len))
+ : flow_equal(flow, &subfacet->facet->flow)) {
+ return subfacet;
+ }
+ }
+
+ return NULL;
+}
+
+/* Searches 'facet' (within 'ofproto') for a subfacet with the specified
+ * 'key_fitness', 'key', and 'key_len'. Returns the existing subfacet if
+ * there is one, otherwise creates and returns a new subfacet. */
+static struct subfacet *
+subfacet_create(struct ofproto_dpif *ofproto, struct facet *facet,
+ enum odp_key_fitness key_fitness,
+ const struct nlattr *key, size_t key_len)
+{
+ uint32_t key_hash = odp_flow_key_hash(key, key_len);
+ struct subfacet *subfacet;
+
+ subfacet = subfacet_find__(ofproto, key, key_len, key_hash, &facet->flow);
+ if (subfacet) {
+ if (subfacet->facet == facet) {
+ return subfacet;
+ }
+
+ /* This shouldn't happen. */
+ VLOG_ERR_RL(&rl, "subfacet with wrong facet");
+ subfacet_destroy(ofproto, subfacet);
+ }
+
+ subfacet = xzalloc(sizeof *subfacet);
+ hmap_insert(&ofproto->subfacets, &subfacet->hmap_node, key_hash);
+ list_push_back(&facet->subfacets, &subfacet->list_node);
+ subfacet->facet = facet;
+ subfacet->used = time_msec();
+ subfacet->key_fitness = key_fitness;
+ if (key_fitness != ODP_FIT_PERFECT) {
+ subfacet->key = xmemdup(key, key_len);
+ subfacet->key_len = key_len;
+ }
+ subfacet->installed = false;
+
+ return subfacet;
+}
+
+/* Searches 'ofproto' for a subfacet with the given 'key', 'key_len', and
+ * 'flow'. Returns the subfacet if one exists, otherwise NULL. */
+static struct subfacet *
+subfacet_find(struct ofproto_dpif *ofproto,
+ const struct nlattr *key, size_t key_len,
+ const struct flow *flow)
+{
+ uint32_t key_hash = odp_flow_key_hash(key, key_len);
+
+ return subfacet_find__(ofproto, key, key_len, key_hash, flow);
+}
+
+/* Uninstalls 'subfacet' from the datapath, if it is installed, removes it from
+ * its facet within 'ofproto', and frees it. */
+static void
+subfacet_destroy__(struct ofproto_dpif *ofproto, struct subfacet *subfacet)
+{
+ subfacet_uninstall(ofproto, subfacet);
+ hmap_remove(&ofproto->subfacets, &subfacet->hmap_node);
+ list_remove(&subfacet->list_node);
+ free(subfacet->key);
+ free(subfacet);
+}
+
+/* Destroys 'subfacet', as with subfacet_destroy__(), and then if this was the
+ * last remaining subfacet in its facet destroys the facet too. */
+static void
+subfacet_destroy(struct ofproto_dpif *ofproto, struct subfacet *subfacet)
+{
+ struct facet *facet = subfacet->facet;
+
+ subfacet_destroy__(ofproto, subfacet);
+ if (list_is_empty(&facet->subfacets)) {
+ facet_remove(ofproto, facet);
+ }
+}
+
+/* Initializes 'key' with the sequence of OVS_KEY_ATTR_* Netlink attributes
+ * that can be used to refer to 'subfacet'. The caller must provide 'keybuf'
+ * for use as temporary storage. */
+static void
+subfacet_get_key(struct subfacet *subfacet, struct odputil_keybuf *keybuf,
+ struct ofpbuf *key)
+{
+ if (!subfacet->key) {
+ ofpbuf_use_stack(key, keybuf, sizeof *keybuf);
+ odp_flow_key_from_flow(key, &subfacet->facet->flow);
+ } else {
+ ofpbuf_use_const(key, subfacet->key, subfacet->key_len);
+ }
+}
+
+/* Updates 'subfacet''s datapath flow, setting its actions to 'actions_len'
+ * bytes of actions in 'actions'. If 'stats' is non-null, statistics counters
+ * in the datapath will be zeroed and 'stats' will be updated with traffic new
+ * since 'subfacet' was last updated.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+static int
+subfacet_install(struct ofproto_dpif *ofproto, struct subfacet *subfacet,
+ const struct nlattr *actions, size_t actions_len,
+ struct dpif_flow_stats *stats)
+{
+ struct odputil_keybuf keybuf;
+ enum dpif_flow_put_flags flags;
+ struct ofpbuf key;
+ int ret;
+
+ flags = DPIF_FP_CREATE | DPIF_FP_MODIFY;
+ if (stats) {
+ flags |= DPIF_FP_ZERO_STATS;
+ }
+
+ subfacet_get_key(subfacet, &keybuf, &key);
+ ret = dpif_flow_put(ofproto->dpif, flags, key.data, key.size,
+ actions, actions_len, stats);
+
+ if (stats) {
+ subfacet_reset_dp_stats(subfacet, stats);
+ }
+
+ return ret;
+}
+
+/* If 'subfacet' is installed in the datapath, uninstalls it. */
+static void
+subfacet_uninstall(struct ofproto_dpif *p, struct subfacet *subfacet)
+{
+ if (subfacet->installed) {
+ struct odputil_keybuf keybuf;
+ struct dpif_flow_stats stats;
+ struct ofpbuf key;
+ int error;
+
+ subfacet_get_key(subfacet, &keybuf, &key);
+ error = dpif_flow_del(p->dpif, key.data, key.size, &stats);
+ subfacet_reset_dp_stats(subfacet, &stats);
+ if (!error) {
+ subfacet_update_stats(p, subfacet, &stats);
+ }
+ subfacet->installed = false;
+ } else {
+ assert(subfacet->dp_packet_count == 0);
+ assert(subfacet->dp_byte_count == 0);
+ }
+}
+
+/* Resets 'subfacet''s datapath statistics counters. This should be called
+ * when 'subfacet''s statistics are cleared in the datapath. If 'stats' is
+ * non-null, it should contain the statistics returned by dpif when 'subfacet'
+ * was reset in the datapath. 'stats' will be modified to include only
+ * statistics new since 'subfacet' was last updated. */
+static void
+subfacet_reset_dp_stats(struct subfacet *subfacet,
+ struct dpif_flow_stats *stats)
+{
+ if (stats
+ && subfacet->dp_packet_count <= stats->n_packets
+ && subfacet->dp_byte_count <= stats->n_bytes) {
+ stats->n_packets -= subfacet->dp_packet_count;
+ stats->n_bytes -= subfacet->dp_byte_count;
+ }
+
+ subfacet->dp_packet_count = 0;
+ subfacet->dp_byte_count = 0;
+}
+
+/* Updates 'subfacet''s used time. The caller is responsible for calling
+ * facet_push_stats() to update the flows which 'subfacet' resubmits into. */
+static void
+subfacet_update_time(struct ofproto_dpif *ofproto, struct subfacet *subfacet,
+ long long int used)
+{
+ if (used > subfacet->used) {
+ subfacet->used = used;
+ facet_update_time(ofproto, subfacet->facet, used);
+ }
+}
+
+/* Folds the statistics from 'stats' into the counters in 'subfacet'.
+ *
+ * Because of the meaning of a subfacet's counters, it only makes sense to do
+ * this if 'stats' are not tracked in the datapath, that is, if 'stats'
+ * represents a packet that was sent by hand or if it represents statistics
+ * that have been cleared out of the datapath. */
+static void
+subfacet_update_stats(struct ofproto_dpif *ofproto, struct subfacet *subfacet,
+ const struct dpif_flow_stats *stats)
+{
+ if (stats->n_packets || stats->used > subfacet->used) {
+ struct facet *facet = subfacet->facet;
+
+ subfacet_update_time(ofproto, subfacet, stats->used);
+ facet->packet_count += stats->n_packets;
+ facet->byte_count += stats->n_bytes;
+ facet_push_stats(facet);
+ netflow_flow_update_flags(&facet->nf_flow, stats->tcp_flags);
+ }
+}
+
/* Rules. */
static struct rule_dpif *
@@ -3347,31 +3521,8 @@ rule_execute(struct rule *rule_, struct flow *flow, struct ofpbuf *packet)
struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->up.ofproto);
struct action_xlate_ctx ctx;
struct ofpbuf *odp_actions;
- struct facet *facet;
size_t size;
- /* First look for a related facet. If we find one, account it to that. */
- facet = facet_lookup_valid(ofproto, flow);
- if (facet && facet->rule == rule) {
- if (!facet->may_install) {
- facet_make_actions(ofproto, facet, packet);
- }
- facet_execute(ofproto, facet, packet);
- return 0;
- }
-
- /* Otherwise, if 'rule' is in fact the correct rule for 'packet', then
- * create a new facet for it and use that. */
- if (rule_dpif_lookup(ofproto, flow, 0) == rule) {
- facet = facet_create(rule, flow);
- facet_make_actions(ofproto, facet, packet);
- facet_execute(ofproto, facet, packet);
- facet_install(ofproto, facet, true);
- return 0;
- }
-
- /* We can't account anything to a facet. If we were to try, then that
- * facet would have a non-matching rule, busting our invariants. */
action_xlate_ctx_init(&ctx, ofproto, flow, packet);
odp_actions = xlate_actions(&ctx, rule->up.actions, rule->up.n_actions);
size = packet->size;
@@ -5101,14 +5252,17 @@ send_active_timeout(struct ofproto_dpif *ofproto, struct facet *facet)
{
if (!facet_is_controller_flow(facet) &&
netflow_active_timeout_expired(ofproto->netflow, &facet->nf_flow)) {
+ struct subfacet *subfacet;
struct ofexpired expired;
- if (facet->installed) {
- struct dpif_flow_stats stats;
+ LIST_FOR_EACH (subfacet, list_node, &facet->subfacets) {
+ if (subfacet->installed) {
+ struct dpif_flow_stats stats;
- facet_put__(ofproto, facet, facet->actions, facet->actions_len,
- &stats);
- facet_update_stats(ofproto, facet, &stats);
+ subfacet_install(ofproto, subfacet, facet->actions,
+ facet->actions_len, &stats);
+ subfacet_update_stats(ofproto, subfacet, &stats);
+ }
}
expired.flow = facet->flow;
diff --git a/tests/automake.mk b/tests/automake.mk
index f11e291..3764096 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -70,6 +70,7 @@ check-local: tests/atconfig tests/atlocal $(TESTSUITE)
lcov_wrappers = \
tests/lcov/ovs-appctl \
tests/lcov/ovs-vsctl \
+ tests/lcov/ovs-vswitchd \
tests/lcov/ovsdb-client \
tests/lcov/ovsdb-server \
tests/lcov/ovsdb-tool \
@@ -125,6 +126,7 @@ check-lcov: all tests/atconfig tests/atlocal $(TESTSUITE) $(lcov_wrappers)
valgrind_wrappers = \
tests/valgrind/ovs-appctl \
tests/valgrind/ovs-vsctl \
+ tests/valgrind/ovs-vswitchd \
tests/valgrind/ovsdb-client \
tests/valgrind/ovsdb-server \
tests/valgrind/ovsdb-tool \
--
1.7.4.4
More information about the dev
mailing list