> On Sep 9, 2015, at 7:00 PM, Joe Stringer <joestrin...@nicira.com> wrote: > >
(snip) > diff --git a/datapath/linux/compat/include/linux/openvswitch.h > b/datapath/linux/compat/include/linux/openvswitch.h > index 578cd88..69bdf32 100644 > --- a/datapath/linux/compat/include/linux/openvswitch.h > +++ b/datapath/linux/compat/include/linux/openvswitch.h > @@ -343,6 +343,8 @@ enum ovs_key_attr { > OVS_KEY_ATTR_MPLS, /* array of struct ovs_key_mpls. > * The implementation may restrict > * the accepted length of the array. */ > + OVS_KEY_ATTR_CT_STATE, /* u8 bitmask of OVS_CS_F_* */ > + OVS_KEY_ATTR_CT_ZONE, /* u16 connection tracking zone. */ > > #ifdef __KERNEL__ > /* Only used within kernel data path. */ > @@ -456,6 +458,15 @@ struct ovs_key_nd { > __u8 nd_tll[ETH_ALEN]; > }; > > +/* OVS_KEY_ATTR_CT_STATE flags */ > +#define OVS_CS_F_NEW 0x01 /* Beginning of a new connection. */ > +#define OVS_CS_F_ESTABLISHED 0x02 /* Part of an existing connection. */ > +#define OVS_CS_F_RELATED 0x04 /* Related to an established > + * connection. */ > +#define OVS_CS_F_INVALID 0x20 /* Could not track connection. */ > +#define OVS_CS_F_REPLY_DIR 0x40 /* Flow is in the reply direction. */ > +#define OVS_CS_F_TRACKED 0x80 /* Conntrack has occurred. */ > + > /** > * enum ovs_flow_attr - attributes for %OVS_FLOW_* commands. > * @OVS_FLOW_ATTR_KEY: Nested %OVS_KEY_ATTR_* attributes specifying the flow > @@ -642,6 +653,28 @@ struct ovs_action_push_tnl { > #endif > > /** > + * enum ovs_ct_attr - Attributes for %OVS_ACTION_ATTR_CT action. > + * @OVS_CT_ATTR_FLAGS: u32 connection tracking flags. > + * @OVS_CT_ATTR_ZONE: u16 connection tracking zone. > + */ > +enum ovs_ct_attr { > + OVS_CT_ATTR_UNSPEC, > + OVS_CT_ATTR_FLAGS, /* u32 bitmask of OVS_CT_F_*. */ > + OVS_CT_ATTR_ZONE, /* u16 zone id. */ > + __OVS_CT_ATTR_MAX > +}; > + > +#define OVS_CT_ATTR_MAX (__OVS_CT_ATTR_MAX - 1) > + > +/* > + * OVS_CT_ATTR_FLAGS flags - bitmask of %OVS_CT_F_* > + * @OVS_CT_F_COMMIT: Commits the flow to the conntrack table. This allows > + * future packets for the same connection to be identified as 'established' > + * or 'related'. > + */ > +#define OVS_CT_F_COMMIT 0x01 > + > +/** > * enum ovs_action_attr - Action types. > * > * @OVS_ACTION_ATTR_OUTPUT: Output packet to port. > @@ -672,6 +705,8 @@ struct ovs_action_push_tnl { > * indicate the new packet contents. This could potentially still be > * %ETH_P_MPLS if the resulting MPLS label stack is not empty. If there > * is no MPLS label stack, as determined by ethertype, no action is taken. > + * @OVS_ACTION_ATTR_CT: Track the connection. Populate the conntrack-related > + * entries in the flow key. > * > * Only a single header can be set with a single %OVS_ACTION_ATTR_SET. Not > all > * fields within a header are modifiable, e.g. the IPv4 protocol and fragment > @@ -702,6 +737,7 @@ enum ovs_action_attr { > * data immediately followed by a mask. > * The data must be zero for the unmasked > * bits. */ > + OVS_ACTION_ATTR_CT, /* One nested OVS_CT_ATTR_* . */ > I guess this should be “Nested OVS_CT_ATTR_*”, as there can be multiple nested attributes? > #ifndef __KERNEL__ > OVS_ACTION_ATTR_TUNNEL_PUSH, /* struct ovs_action_push_tnl*/ > (snip) > diff --git a/lib/flow.h b/lib/flow.h > index d8632ff..2d6d3ee 100644 > --- a/lib/flow.h > +++ b/lib/flow.h > @@ -103,8 +103,10 @@ struct flow { > union flow_in_port in_port; /* Input port.*/ > uint32_t recirc_id; /* Must be exact match. */ > uint32_t conj_id; /* Conjunction ID. */ > + uint16_t ct_zone; /* Connection Zone. */ > + uint8_t ct_state; /* Connection state. */ > + uint8_t pad1[3]; /* Pad to 64 bits. */ > ofp_port_t actset_output; /* Output port in action set. */ > - uint8_t pad1[6]; /* Pad to 64 bits. */ > It would be slightly better to put ct_zone and ct_state right after recirc_id. This would make miniflows smaller, as the recirc_id, ct_zone, and ct_state would all be in the same miniflow data unit (conj_id and actset_output are userspace-only fields, so they would be zeroes for datapath miniflows). We should probably make ct_state a 16 bit field (uint16_t) in struct flow already, for future-proofness, even if the kernel interface is still 8 bits. Also, you should bump FLOW_WC_SEQ to make sure all the relevant places are updated for the new fields. > /* L2, Order the same as in the Ethernet header! (64-bit aligned) */ > struct eth_addr dl_dst; /* Ethernet destination address. */ > @@ -980,6 +982,8 @@ pkt_metadata_from_flow(struct pkt_metadata *md, const > struct flow *flow) > md->skb_priority = flow->skb_priority; > md->pkt_mark = flow->pkt_mark; > md->in_port = flow->in_port; > + md->ct_state = flow->ct_state; > + md->ct_zone = flow->ct_zone; > } > > static inline bool is_ip_any(const struct flow *flow) > (snip) > diff --git a/lib/meta-flow.h b/lib/meta-flow.h > index 02272ef..44a29e9 100644 > --- a/lib/meta-flow.h > +++ b/lib/meta-flow.h > @@ -703,6 +703,51 @@ enum OVS_PACKED_ENUM mf_field_id { > */ > MFF_PKT_MARK, > > + /* "ct_state". > + * > + * Connection tracking state. The field is populated by the NXAST_CT > + * action. The following bit values describe the state of the connection: > + * > + * - New (0x01): This is the beginning of a new connection. > + * - Established (0x02): This is part of an already existing > connection. > + * - Related (0x04): This is a separate connection that is related to > an > + * existing connection. > + * - Invalid (0x20): This flow could not be associated with a > connection. > + * This could be set for a variety of reasons, > + * including (but not limited to): > + * - L3/L4 protocol handler is not > loaded/unavailable. > + * - L3/L4 protocol handler determines that the > packet > + * is malformed or invalid for the current FSM > stage. > + * - Packets are unexpected length for protocol. > + * - Reply (0x40): This flow is in the reply direction, ie it did not > + * initiate the connection. > + * - Tracked (0x80): Connection tracking has occurred. > + * Have we documented anywhere which of the bits are mutually exclusive and how. E.g., - If -trk, all the other bits are also zeroes (also invalid?) - new and established are mutually exclusive (+new+est is not possible) - new and reply are mutually exclusive (reply direction is defined as opposite of the initial +new) - related and new can be set at the same time (but does related remain set for the duration of the connection, or is it also mutually exclusive with established?) - apparently related and reply are not mutually exclusive, but when are they set at the same time? - are invalid and tracked mutually exclusive? Also, what are the possible transitions from one state to another? IMO this information is invaluable for anyone crafting the flows using these bits. > + * Type: u8. > + * Maskable: bitwise. > + * Formatting: conn state. > + * Prerequisites: none. > + * Access: read-only. > + * NXM: NXM_NX_CT_STATE(105) since v2.5. > + * OXM: none. > + */ > + MFF_CT_STATE, > + > + /* "ct_zone". > + * > + * Connection tracking zone. The field is populated by the > + * NXAST_CT action. > + * > + * Type: be16. > + * Maskable: no. > + * Formatting: hexadecimal. > + * Prerequisites: none. > + * Access: read-only. > + * NXM: NXM_NX_CT_ZONE(106) since v2.5. > + * OXM: none. > + */ > + MFF_CT_ZONE, > + (snip) > diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c > index c1e46b5..6ea421f 100644 > --- a/lib/ofp-actions.c > +++ b/lib/ofp-actions.c > @@ -286,6 +286,9 @@ enum ofp_raw_action_type { > /* NX1.0+(34): struct nx_action_conjunction. */ > NXAST_RAW_CONJUNCTION, > > + /* NX1.0+(35): struct nx_action_conntrack, ... */ > + NXAST_RAW_CT, > + > /* ## ------------------ ## */ > /* ## Debugging actions. ## */ > /* ## ------------------ ## */ > @@ -346,6 +349,10 @@ static void *ofpact_put_raw(struct ofpbuf *, enum > ofp_version, > static char *OVS_WARN_UNUSED_RESULT ofpacts_parse( > char *str, struct ofpbuf *ofpacts, enum ofputil_protocol > *usable_protocols, > bool allow_instructions, enum ofpact_type outer_action); > +static enum ofperr ofpacts_pull_openflow_actions__( > + struct ofpbuf *openflow, unsigned int actions_len, > + enum ofp_version version, uint32_t allowed_ovsinsts, > + struct ofpbuf *ofpacts, enum ofpact_type outer_action); > > /* Pull off existing actions or instructions. Used by nesting actions to keep > * ofpacts_parse() oblivious of actions nesting. */ > @@ -4448,6 +4455,245 @@ format_DEBUG_RECIRC(const struct ofpact_null *a > OVS_UNUSED, struct ds *s) > { > ds_put_cstr(s, "debug_recirc"); > } > + > +/* Action structure for NXAST_CT. > + * > + * Pass traffic to the connection tracker. > + * > + * The "zone" specifies a context within which the tracking is done: > + * > + * If 'zone_src' is nonzero, this specifies that the zone should be > + * sourced from a field zone_src[ofs:ofs+nbits]. The format and > semantics > + * of 'zone_src' and 'zone_ofs_nbits' are similar to those for the > + * NXAST_REG_LOAD action. The acceptable nxm_header values for > 'zone_src' > + * are the same as the acceptable nxm_header values for the 'src' field > of > + * NXAST_REG_MOVE. > + * > + * If 'zone_src' is zero, then the value of 'zone_imm' will be used as > the > + * connection tracking zone. > + * > + * If "recirc_table" has a value other than NX_CT_RECIRC_NONE, then this > field > + * specifies that the packet should be logically cloned after the packet is The packet is logically cloned *before* it is sent through the connection tracker, as only the cloned packet gets the ct_state and ct_zone, right? > + * sent through the connection tracker, and pipeline processing for the > cloned > + * packet will continue in the OpenFlow table specified by this value. The > + * NXM_NX_CT_* fields will be populated for the subsequent processing. It is > + * strongly recommended that this table is later than the current table, to > + * prevent loops. > + * ‘alg’ and ofpacts are introduced in later patches, and I guess it is too much work to not deal with them here yet? Anyway, a thought about the nested actions just popped in my mind: The fact that the actions are nested means that they all must be produced by a single OpenFlow rule. I.e., the same rule that does conntrack must also set the labels, etc. This means that either there will be a potentially huge number of rules with conntrack actions, or the values used must be passed through registers (which is kind of redundant). Also, a register may not be wide enough for the data in question (e.g., labels). > + * A standard "resubmit" action is not sufficient to populate NXM_NX_CT_* > + * fields, since connection tracking occurs outside of the classifier. > + */ > +struct nx_action_conntrack { > + ovs_be16 type; /* OFPAT_VENDOR. */ > + ovs_be16 len; /* At least 24. */ > + ovs_be32 vendor; /* NX_VENDOR_ID. */ > + ovs_be16 subtype; /* NXAST_CT. */ > + ovs_be16 flags; /* Zero or more NX_CT_F_* flags. > + * Unspecified flag bits must be zero. */ > + ovs_be32 zone_src; /* Connection tracking context. */ > + union { > + ovs_be16 zone_ofs_nbits;/* Range to use from source field. */ > + ovs_be16 zone_imm; /* Immediate value for zone. */ > + }; > + uint8_t recirc_table; /* Recirculate to a specific table, or > + NX_CT_RECIRC_NONE for no recirculation. */ > + uint8_t pad[3]; /* Zeroes */ > + ovs_be16 alg; /* Well-known port number for the protocol. > + * 0 indicates no ALG is required. */ > + /* Followed by a sequence of ofpact elements, until the end of the action > + * is reached. */ > +}; > +OFP_ASSERT(sizeof(struct nx_action_conntrack) == 24); > + > +static enum ofperr > +decode_ct_zone(const struct nx_action_conntrack *nac, > + struct ofpact_conntrack *out) > +{ > + if (nac->zone_src) { > + enum ofperr error; > + > + out->zone_src.field = mf_from_nxm_header(ntohl(nac->zone_src)); > + out->zone_src.ofs = nxm_decode_ofs(nac->zone_ofs_nbits); > + out->zone_src.n_bits = nxm_decode_n_bits(nac->zone_ofs_nbits); > + error = mf_check_src(&out->zone_src, NULL); > + if (error) { > + return error; > + } > + > + if (out->zone_src.n_bits != 16) { > + VLOG_WARN_RL(&rl, "zone n_bits %d not within valid range > [16..16]", > + out->zone_src.n_bits); > + return OFPERR_OFPBAC_BAD_SET_LEN; > + } > + } else { > + out->zone_src.field = NULL; > + out->zone_imm = ntohs(nac->zone_imm); > + } > + > + return 0; > +} > + > +static enum ofperr > +decode_NXAST_RAW_CT(const struct nx_action_conntrack *nac, struct ofpbuf > *out) > +{ > + const size_t ct_offset = ofpacts_pull(out); > + struct ofpact_conntrack *conntrack; > + struct ofpbuf openflow; > + int error = 0; > + > + conntrack = ofpact_put_CT(out); > + conntrack->flags = ntohs(nac->flags); > + error = decode_ct_zone(nac, conntrack); > + if (error) { > + goto out; > + } > + conntrack->recirc_table = nac->recirc_table; > + conntrack->alg = ntohs(nac->alg); > + > + ofpbuf_pull(out, sizeof(*conntrack)); > + > + /* XXX: version */ What about the version? > + ofpbuf_use_const(&openflow, nac + 1, ntohs(nac->len) - sizeof(*nac)); > + error = ofpacts_pull_openflow_actions__(&openflow, openflow.size, > + OFP10_VERSION, > + 1u << > OVSINST_OFPIT11_APPLY_ACTIONS, > + out, OFPACT_CT); > + if (error) { > + goto out; > + } > + > + conntrack = ofpbuf_push_uninit(out, sizeof(*conntrack)); > + out->header = &conntrack->ofpact; > + ofpact_update_len(out, &conntrack->ofpact); > + > +out: > + ofpbuf_push_uninit(out, ct_offset); > + return error; > +} > + > +static void > +encode_CT(const struct ofpact_conntrack *conntrack, > + enum ofp_version ofp_version, struct ofpbuf *out) > +{ > + struct nx_action_conntrack *nac; > + const size_t ofs = out->size; > + size_t len; > + > + nac = put_NXAST_CT(out); > + nac->flags = htons(conntrack->flags); > + if (conntrack->zone_src.field) { > + nac->zone_src = htonl(mf_nxm_header(conntrack->zone_src.field->id)); > + nac->zone_ofs_nbits = nxm_encode_ofs_nbits(conntrack->zone_src.ofs, > + > conntrack->zone_src.n_bits); > + } else { > + nac->zone_src = htonl(0); > + nac->zone_imm = htons(conntrack->zone_imm); > + } > + nac->recirc_table = conntrack->recirc_table; > + nac->alg = htons(conntrack->alg); > + > + len = ofpacts_put_openflow_actions(conntrack->actions, > + ofpact_ct_get_action_len(conntrack), > + out, ofp_version); > + len += sizeof(*nac); > + nac = ofpbuf_at(out, ofs, sizeof(*nac)); > + nac->len = htons(len); nac->len should include the conntrack action itself, too, not just the length of the actions? > +} > + > +/* Parses 'arg' as the argument to a "ct" action, and appends such an > + * action to 'ofpacts'. > + * (snip) > diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h > index 818f5c8..38a9547 100644 > --- a/lib/ofp-actions.h > +++ b/lib/ofp-actions.h > @@ -106,6 +106,7 @@ > OFPACT(EXIT, ofpact_null, ofpact, "exit") \ > OFPACT(SAMPLE, ofpact_sample, ofpact, "sample") \ > OFPACT(UNROLL_XLATE, ofpact_unroll_xlate, ofpact, "unroll_xlate") \ > + OFPACT(CT, ofpact_conntrack, ofpact, "ct") \ > \ > /* Debugging actions. \ > * \ > @@ -471,6 +472,52 @@ BUILD_ASSERT_DECL(offsetof(struct ofpact_nest, actions) > % OFPACT_ALIGNTO == 0); > BUILD_ASSERT_DECL(offsetof(struct ofpact_nest, actions) > == sizeof(struct ofpact_nest)); > > +/* Bits for 'flags' in struct nx_action_conntrack. > + * > + * If NX_CT_F_COMMIT is set, then the connection entry is moved from the > + * unconfirmed to confirmed list in the tracker. */ > +enum nx_conntrack_flags { > + NX_CT_F_COMMIT = 1 << 0, > +}; > + > +/* Magic value for struct nx_action_conntrack 'recirc_table' field, to > specify > + * that the packet should not be recirculated. This value should commonly be > + * used in conjunction with the NX_CT_F_COMMIT flag above. */ > +#define NX_CT_RECIRC_NONE 0xFF > + Maybe this should be defined as OFPTT_ALL, as that is the only invalid table number (0xff), i.e., NX_CT_RECIRC_NONE may not be defined as anything else, right? > +#define CT_MEMBERS \ > + uint16_t flags; \ > + uint16_t zone_imm; \ > + struct mf_subfield zone_src; \ > + uint8_t recirc_table; \ > + uint16_t alg > + Maybe put the struct member first to avoid internal padding (mf_subfield has a pointer, so it will be 64-bit aligned on 64-bit systems)? > +/* Used to perform build-time size checking for ofpact_conntrack. */ > +struct ofpact_ct_size { > + CT_MEMBERS; > +}; > + > +/* OFPACT_CT. > + * > + * Used for NXAST_CT. */ > +struct ofpact_conntrack { > + struct ofpact ofpact; > + CT_MEMBERS; This is slightly ugly, seems to be necessary. > + uint8_t pad[PAD_SIZE(sizeof(struct ofpact) + sizeof(struct > ofpact_ct_size), > + OFPACT_ALIGNTO)]; > + struct ofpact actions[]; > +}; > +BUILD_ASSERT_DECL(offsetof(struct ofpact_conntrack, actions) > + % OFPACT_ALIGNTO == 0); > +BUILD_ASSERT_DECL(offsetof(struct ofpact_conntrack, actions) > + == sizeof(struct ofpact_conntrack)); > + > (snip) > bool dpid_from_string(const char *s, uint64_t *dpidp); > @@ -710,6 +717,18 @@ struct tcp_header { > }; > BUILD_ASSERT_DECL(TCP_HEADER_LEN == sizeof(struct tcp_header)); > > +/* Connection states */ > +#define CS_NEW 0x01 > +#define CS_ESTABLISHED 0x02 > +#define CS_RELATED 0x04 > +#define CS_INVALID 0x20 > +#define CS_REPLY_DIR 0x40 > +#define CS_TRACKED 0x80 > + These seem to match the datapath interface flags. Is that an invariant? Which ones we use in struct flow? > +/* Undefined connection state bits. */ > +#define CS_UNSUPPORTED_MASK 0x18 > +#define CS_SUPPORTED_MASK (~CS_UNSUPPORTED_MASK & 0xFF) > + > (snip) > diff --git a/ofproto/ofproto-dpif-rid.h b/ofproto/ofproto-dpif-rid.h > index e7d95bf..7754e68 100644 > --- a/ofproto/ofproto-dpif-rid.h > +++ b/ofproto/ofproto-dpif-rid.h > @@ -142,6 +142,7 @@ struct recirc_state { > struct recirc_metadata metadata; /* Flow metadata. */ > struct ofpbuf *stack; /* Stack if any. */ > mirror_mask_t mirrors; /* Mirrors already output. */ > + bool conntrack; /* Conntrack occurred prior to recirc. */ > Should this be also called ‘conntracked’? > /* Actions to be translated on recirculation. */ > uint32_t action_set_len; /* How much of 'ofpacts' consists of an (snip) > static void > check_support(struct dpif_backer *backer) > { > @@ -1239,6 +1278,9 @@ check_support(struct dpif_backer *backer) > backer->support.masked_set_action = check_masked_set_action(backer); > backer->support.ufid = check_ufid(backer); > backer->support.tnl_push_pop = dpif_supports_tnl_push_pop(backer->dpif); > + > + backer->support.odp.ct_state = check_ct_state(backer); > + backer->support.odp.ct_zone = check_ct_zone(backer); > } > > static int > @@ -3935,10 +3977,38 @@ rule_dealloc(struct rule *rule_) > } > > static enum ofperr > +rule_check(struct rule *rule) > +{ > + struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->ofproto); > + const struct odp_support *support; > + struct match match; > + > + minimatch_expand(&rule->cr.match, &match); > + support = &ofproto_dpif_get_support(ofproto)->odp; > + > + if ((match.wc.masks.ct_state && !support->ct_state) > + || (match.wc.masks.ct_zone && !support->ct_zone)) { > + return OFPERR_OFPBMC_BAD_FIELD; > + } > + if (match.wc.masks.ct_state & CS_UNSUPPORTED_MASK) { > + return OFPERR_OFPBMC_BAD_MASK; > + } > + > + return 0; > +} This check seems somewhat expensive. Maybe get the masks from the minimatch without expanding it (using MINIFLOW_GET_U8() and MINIFLOW_GET_U16()), like this: static enum ofperr rule_check(struct rule *rule) { ct_state_mask = MINIFLOW_GET_U8(&rule->cr.match.mask.masks, ct_state); ct_zone_mask = MINIFLOW_GET_U16(&rule->cr.match.mask.masks, ct_zone); if (ct_state_mask || ct_zone_mask) { struct ofproto_dpif *ofproto = ofproto_dpif_cast(rule->ofproto); const struct odp_support *support = &ofproto_dpif_get_support(ofproto)->odp; if ((ct_state_mask && !support->ct_state) || (ct_zone_mask && !support->ct_zone)) { return OFPERR_OFPBMC_BAD_FIELD; } if (ct_state_mask & CS_UNSUPPORTED_MASK) { return OFPERR_OFPBMC_BAD_MASK; } } return 0; } > + > +static enum ofperr > rule_construct(struct rule *rule_) > OVS_NO_THREAD_SAFETY_ANALYSIS > { > struct rule_dpif *rule = rule_dpif_cast(rule_); > + int error; > + > + error = rule_check(rule_); > + if (error) { > + return error; > + } > + > ovs_mutex_init_adaptive(&rule->stats_mutex); > rule->stats.n_packets = 0; > rule->stats.n_bytes = 0; (snip) > --- a/tests/system-traffic.at > +++ b/tests/system-traffic.at > @@ -139,3 +139,472 @@ NS_CHECK_EXEC([at_ns0], [ping -s 3200 -q -c 3 -i 0.3 -w > 2 10.1.1.100 | FORMAT_PI > > OVS_TRAFFIC_VSWITCHD_STOP > AT_CLEANUP > + > +AT_SETUP([conntrack - controller]) > +CHECK_CONNTRACK() > +OVS_TRAFFIC_VSWITCHD_START( > + [set-fail-mode br0 standalone -- ]) > + > +ADD_NAMESPACES(at_ns0, at_ns1) > + > +ADD_VETH(p0, at_ns0, br0, "10.1.1.1/24") > +ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24") > + > +dnl Allow any traffic from ns0->ns1. Only allow nd, return traffic from > ns1->ns0. > +AT_DATA([flows.txt], [dnl > +priority=1,action=drop > +priority=10,arp,action=normal > +in_port=1,udp,action=ct(commit),controller > +in_port=2,ct_state=-trk,udp,action=ct(table=0) > +in_port=2,ct_state=+trk+est-new,udp,action=controller I forgot the default priority value, would be useful to specify the priority of all the flows to make this more readable. I’ll check the rest later. However, I was not able to run any of the tests with the net-next openvswitch kernel module, so I need to look at that before going on. Jarno _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev