diff options
-rw-r--r-- | NEWS | 6 | ||||
-rw-r--r-- | include/openflow/nicira-ext.h | 37 | ||||
-rw-r--r-- | include/openvswitch/ofp-ct.h | 8 | ||||
-rw-r--r-- | include/openvswitch/ofp-msgs.h | 4 | ||||
-rw-r--r-- | lib/ofp-bundle.c | 1 | ||||
-rw-r--r-- | lib/ofp-ct.c | 196 | ||||
-rw-r--r-- | lib/ofp-print.c | 20 | ||||
-rw-r--r-- | lib/rconn.c | 1 | ||||
-rw-r--r-- | ofproto/ofproto-dpif.c | 9 | ||||
-rw-r--r-- | ofproto/ofproto-provider.h | 7 | ||||
-rw-r--r-- | ofproto/ofproto.c | 29 | ||||
-rw-r--r-- | tests/ofp-print.at | 108 | ||||
-rw-r--r-- | tests/ovs-ofctl.at | 38 | ||||
-rw-r--r-- | tests/system-traffic.at | 38 | ||||
-rw-r--r-- | utilities/ovs-ofctl.8.in | 31 | ||||
-rw-r--r-- | utilities/ovs-ofctl.c | 51 |
16 files changed, 562 insertions, 22 deletions
@@ -21,6 +21,9 @@ Post-v3.0.0 10 Gbps link speed by default in case the actual link speed cannot be determined. Previously it was 10 Mbps. Values can still be overridden by specifying 'max-rate' or '[r]stp-path-cost' accordingly. + - OpenFlow: + * New OpenFlow extension NXT_CT_FLUSH to flush connections matching + the specified fields. - ovs-ctl: * New option '--dump-hugepages' to include hugepages in core dumps. This can assist with postmortem analysis involving DPDK, but may also produce @@ -28,6 +31,9 @@ Post-v3.0.0 - ovs-dpctl and 'ovs-appctl dpctl/' commands: * 'flush-conntrack' is now capable of handling partial 5-tuple, with additional optional parameter to specify the reply direction. + - ovs-ofctl: + * New command 'flush-conntrack' that accepts zone and 5-tuple (or partial + 5-tuple) for both directions. - Support for travis-ci.org based continuous integration builds has been dropped. - Userspace datapath: diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h index b68804991..768775898 100644 --- a/include/openflow/nicira-ext.h +++ b/include/openflow/nicira-ext.h @@ -1064,4 +1064,41 @@ struct nx_zone_id { }; OFP_ASSERT(sizeof(struct nx_zone_id) == 8); +/* CT flush available TLVs. */ +enum nx_ct_flush_tlv_type { + /* Outer types. */ + NXT_CT_ORIG_TUPLE = 0, /* Outer type for original tuple TLV. + * Nested TLVs are specified + * by 'enum nx_ct_flush_tuple_tlv_type'. */ + NXT_CT_REPLY_TUPLE = 1, /* Outer type for reply tuple TLV. * + * Nested TLVs are specified + * by 'enum nx_ct_flush_tuple_tlv_type'*/ + /* Primitive types. */ + NXT_CT_ZONE_ID = 2, /* be16 zone id. */ +}; + +/* CT flush nested TLVs. */ +enum nx_ct_flush_tuple_tlv_type { + NXT_CT_TUPLE_SRC = 0, /* IPv6 or mapped IPv4 address. */ + NXT_CT_TUPLE_DST = 1, /* IPv6 or mapped IPv4 address. */ + NXT_CT_TUPLE_SRC_PORT = 2, /* be16 source port. */ + NXT_CT_TUPLE_DST_PORT = 3, /* be16 destination port. */ + NXT_CT_TUPLE_ICMP_ID = 4, /* be16 ICMP id. */ + NXT_CT_TUPLE_ICMP_TYPE = 5, /* u8 ICMP type. */ + NXT_CT_TUPLE_ICMP_CODE = 6, /* u8 ICMP code. */ +}; + +/* NXT_CT_FLUSH. + * + * Flushes the connection tracking entries specified by 5-tuple. + * The struct should be followed by TLVs specifying the matching parameters. + * Currently there is a limitation for ICMP, in order to partially match on + * ICMP parameters the tuple should include at least SRC/DST. */ +struct nx_ct_flush { + uint8_t ip_proto; /* IP protocol. */ + uint8_t pad[7]; /* Align to 64 bits (must be zero). */ + /* Followed by optional TLVs of type 'enum nx_ct_flush_tlv_type'. */ +}; +OFP_ASSERT(sizeof(struct nx_ct_flush) == 8); + #endif /* openflow/nicira-ext.h */ diff --git a/include/openvswitch/ofp-ct.h b/include/openvswitch/ofp-ct.h index 3d919ddf9..c8023c309 100644 --- a/include/openvswitch/ofp-ct.h +++ b/include/openvswitch/ofp-ct.h @@ -22,6 +22,8 @@ #include <sys/types.h> #include <netinet/in.h> +#include "openflow/nicira-ext.h" + #ifdef __cplusplus extern "C" { #endif @@ -59,6 +61,12 @@ void ofp_ct_match_format(struct ds *, const struct ofp_ct_match *); bool ofp_ct_tuple_parse(struct ofp_ct_tuple *, const char *, struct ds *, uint8_t *ip_proto, uint16_t *l3_type); +enum ofperr ofp_ct_match_decode(struct ofp_ct_match *, bool *with_zone, + uint16_t *zone_id, const struct ofp_header *); +struct ofpbuf *ofp_ct_match_encode(const struct ofp_ct_match *, + uint16_t *zone_id, + enum ofp_version version); + #ifdef __cplusplus } #endif diff --git a/include/openvswitch/ofp-msgs.h b/include/openvswitch/ofp-msgs.h index 921a937e5..708427fc0 100644 --- a/include/openvswitch/ofp-msgs.h +++ b/include/openvswitch/ofp-msgs.h @@ -515,6 +515,9 @@ enum ofpraw { /* NXT 1.0+ (29): struct nx_zone_id. */ OFPRAW_NXT_CT_FLUSH_ZONE, + /* NXT 1.0+ (32): struct nx_ct_flush, uint8_t[8][]. */ + OFPRAW_NXT_CT_FLUSH, + /* NXST 1.0+ (3): void. */ OFPRAW_NXST_IPFIX_BRIDGE_REQUEST, @@ -772,6 +775,7 @@ enum ofptype { OFPTYPE_IPFIX_FLOW_STATS_REQUEST, /* OFPRAW_NXST_IPFIX_FLOW_REQUEST */ OFPTYPE_IPFIX_FLOW_STATS_REPLY, /* OFPRAW_NXST_IPFIX_FLOW_REPLY */ OFPTYPE_CT_FLUSH_ZONE, /* OFPRAW_NXT_CT_FLUSH_ZONE. */ + OFPTYPE_CT_FLUSH, /* OFPRAW_NXT_CT_FLUSH. */ /* Flow monitor extension. */ OFPTYPE_FLOW_MONITOR_CANCEL, /* OFPRAW_NXT_FLOW_MONITOR_CANCEL. diff --git a/lib/ofp-bundle.c b/lib/ofp-bundle.c index 0161c2bc6..941a8370e 100644 --- a/lib/ofp-bundle.c +++ b/lib/ofp-bundle.c @@ -292,6 +292,7 @@ ofputil_is_bundlable(enum ofptype type) case OFPTYPE_IPFIX_FLOW_STATS_REQUEST: case OFPTYPE_IPFIX_FLOW_STATS_REPLY: case OFPTYPE_CT_FLUSH_ZONE: + case OFPTYPE_CT_FLUSH: break; } diff --git a/lib/ofp-ct.c b/lib/ofp-ct.c index 150caa9b3..85a9d8bec 100644 --- a/lib/ofp-ct.c +++ b/lib/ofp-ct.c @@ -23,8 +23,12 @@ #include "ct-dpif.h" #include "openvswitch/ofp-ct.h" +#include "openflow/nicira-ext.h" #include "openvswitch/dynamic-string.h" +#include "openvswitch/ofp-msgs.h" #include "openvswitch/ofp-parse.h" +#include "openvswitch/ofp-errors.h" +#include "openvswitch/ofp-prop.h" #include "openvswitch/ofp-util.h" #include "openvswitch/packets.h" @@ -211,3 +215,195 @@ error: free(copy); return false; } + +static enum ofperr +ofpprop_pull_ipv6(struct ofpbuf *property, struct in6_addr *addr, + uint16_t *l3_type) +{ + if (ofpbuf_msgsize(property) < sizeof *addr) { + return OFPERR_OFPBPC_BAD_LEN; + } + + memcpy(addr, property->msg, sizeof *addr); + + uint16_t l3 = 0; + if (!ipv6_is_zero(addr)) { + l3 = IN6_IS_ADDR_V4MAPPED(addr) ? AF_INET : AF_INET6; + } + + if (*l3_type && l3 && *l3_type != l3) { + return OFPERR_OFPBPC_BAD_VALUE; + } + + *l3_type = l3; + + return 0; +} + +static enum ofperr +ofp_ct_tuple_decode_nested(struct ofpbuf *property, struct ofp_ct_tuple *tuple, + uint16_t *l3_type) +{ + struct ofpbuf nested; + enum ofperr error = ofpprop_parse_nested(property, &nested); + if (error) { + return error; + } + + while (nested.size) { + struct ofpbuf inner; + uint64_t type; + + error = ofpprop_pull(&nested, &inner, &type); + if (error) { + return error; + } + switch (type) { + case NXT_CT_TUPLE_SRC: + error = ofpprop_pull_ipv6(&inner, &tuple->src, l3_type); + break; + + case NXT_CT_TUPLE_DST: + error = ofpprop_pull_ipv6(&inner, &tuple->dst, l3_type); + break; + + case NXT_CT_TUPLE_SRC_PORT: + error = ofpprop_parse_be16(&inner, &tuple->src_port); + break; + + case NXT_CT_TUPLE_DST_PORT: + error = ofpprop_parse_be16(&inner, &tuple->dst_port); + break; + + case NXT_CT_TUPLE_ICMP_ID: + error = ofpprop_parse_be16(&inner, &tuple->icmp_id); + break; + + case NXT_CT_TUPLE_ICMP_TYPE: + error = ofpprop_parse_u8(&inner, &tuple->icmp_type); + break; + + case NXT_CT_TUPLE_ICMP_CODE: + error = ofpprop_parse_u8(&inner, &tuple->icmp_code); + break; + } + + if (error) { + return error; + } + } + + return 0; +} + +static void +ofp_ct_tuple_encode(const struct ofp_ct_tuple *tuple, struct ofpbuf *buf, + enum nx_ct_flush_tlv_type type, uint8_t ip_proto) +{ + /* 128 B is enough to hold the whole tuple. */ + uint8_t stub[128]; + struct ofpbuf nested = OFPBUF_STUB_INITIALIZER(stub); + + if (!ipv6_is_zero(&tuple->src)) { + ofpprop_put(&nested, NXT_CT_TUPLE_SRC, &tuple->src, sizeof tuple->src); + } + + if (!ipv6_is_zero(&tuple->dst)) { + ofpprop_put(&nested, NXT_CT_TUPLE_DST, &tuple->dst, sizeof tuple->dst); + } + + if (ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6) { + ofpprop_put_be16(&nested, NXT_CT_TUPLE_ICMP_ID, tuple->icmp_id); + ofpprop_put_u8(&nested, NXT_CT_TUPLE_ICMP_TYPE, tuple->icmp_type); + ofpprop_put_u8(&nested, NXT_CT_TUPLE_ICMP_CODE, tuple->icmp_code); + } else { + if (tuple->src_port) { + ofpprop_put_be16(&nested, NXT_CT_TUPLE_SRC_PORT, tuple->src_port); + } + + if (tuple->dst_port) { + ofpprop_put_be16(&nested, NXT_CT_TUPLE_DST_PORT, tuple->dst_port); + } + } + + if (nested.size) { + ofpprop_put_nested(buf, type, &nested); + } + + ofpbuf_uninit(&nested); +} + +enum ofperr +ofp_ct_match_decode(struct ofp_ct_match *match, bool *with_zone, + uint16_t *zone_id, const struct ofp_header *oh) +{ + struct ofpbuf msg = ofpbuf_const_initializer(oh, ntohs(oh->length)); + ofpraw_pull_assert(&msg); + + const struct nx_ct_flush *nx_flush = ofpbuf_pull(&msg, sizeof *nx_flush); + + if (!is_all_zeros(nx_flush->pad, sizeof nx_flush->pad)) { + return OFPERR_NXBRC_MUST_BE_ZERO; + } + + match->ip_proto = nx_flush->ip_proto; + + struct ofp_ct_tuple *orig = &match->tuple_orig; + struct ofp_ct_tuple *reply = &match->tuple_reply; + + while (msg.size) { + struct ofpbuf property; + uint64_t type; + + enum ofperr error = ofpprop_pull(&msg, &property, &type); + if (error) { + return error; + } + + switch (type) { + case NXT_CT_ORIG_TUPLE: + error = ofp_ct_tuple_decode_nested(&property, orig, + &match->l3_type); + break; + + case NXT_CT_REPLY_TUPLE: + error = ofp_ct_tuple_decode_nested(&property, reply, + &match->l3_type); + break; + + case NXT_CT_ZONE_ID: + if (with_zone) { + *with_zone = true; + } + error = ofpprop_parse_u16(&property, zone_id); + break; + } + + if (error) { + return error; + } + } + + return 0; +} + +struct ofpbuf * +ofp_ct_match_encode(const struct ofp_ct_match *match, uint16_t *zone_id, + enum ofp_version version) +{ + struct ofpbuf *msg = ofpraw_alloc(OFPRAW_NXT_CT_FLUSH, version, 0); + struct nx_ct_flush *nx_flush = ofpbuf_put_zeros(msg, sizeof *nx_flush); + const struct ofp_ct_tuple *orig = &match->tuple_orig; + const struct ofp_ct_tuple *reply = &match->tuple_reply; + + nx_flush->ip_proto = match->ip_proto; + + ofp_ct_tuple_encode(orig, msg, NXT_CT_ORIG_TUPLE,match->ip_proto); + ofp_ct_tuple_encode(reply, msg, NXT_CT_REPLY_TUPLE, match->ip_proto); + + if (zone_id) { + ofpprop_put_u16(msg, NXT_CT_ZONE_ID, *zone_id); + } + + return msg; +} diff --git a/lib/ofp-print.c b/lib/ofp-print.c index bd37fa17a..874079b84 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -45,6 +45,7 @@ #include "openvswitch/ofp-actions.h" #include "openvswitch/ofp-bundle.h" #include "openvswitch/ofp-connection.h" +#include "openvswitch/ofp-ct.h" #include "openvswitch/ofp-errors.h" #include "openvswitch/ofp-group.h" #include "openvswitch/ofp-ipfix.h" @@ -950,6 +951,23 @@ ofp_print_nxt_ct_flush_zone(struct ds *string, const struct nx_zone_id *nzi) } static enum ofperr +ofp_print_nxt_ct_flush(struct ds *string, const struct ofp_header *oh) +{ + uint16_t zone_id = 0; + struct ofp_ct_match match = {0}; + + enum ofperr error = ofp_ct_match_decode(&match, NULL, &zone_id, oh); + if (error) { + return error; + } + + ds_put_format(string, " zone=%"PRIu16" ", zone_id); + ofp_ct_match_format(string, &match); + + return 0; +} + +static enum ofperr ofp_to_string__(const struct ofp_header *oh, const struct ofputil_port_map *port_map, const struct ofputil_table_map *table_map, enum ofpraw raw, @@ -1184,6 +1202,8 @@ ofp_to_string__(const struct ofp_header *oh, case OFPTYPE_CT_FLUSH_ZONE: return ofp_print_nxt_ct_flush_zone(string, ofpmsg_body(oh)); + case OFPTYPE_CT_FLUSH: + return ofp_print_nxt_ct_flush(string, oh); } return 0; diff --git a/lib/rconn.c b/lib/rconn.c index a96b2eb8b..4afa21515 100644 --- a/lib/rconn.c +++ b/lib/rconn.c @@ -1426,6 +1426,7 @@ is_admitted_msg(const struct ofpbuf *b) case OFPTYPE_IPFIX_FLOW_STATS_REQUEST: case OFPTYPE_IPFIX_FLOW_STATS_REPLY: case OFPTYPE_CT_FLUSH_ZONE: + case OFPTYPE_CT_FLUSH: default: return true; } diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index f9562dee8..f87e27a8c 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -5358,11 +5358,12 @@ type_set_config(const char *type, const struct smap *other_config) } static void -ct_flush(const struct ofproto *ofproto_, const uint16_t *zone) +ct_flush(const struct ofproto *ofproto_, const uint16_t *zone, + const struct ofp_ct_match *match) { struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_); - ct_dpif_flush(ofproto->backer->dpif, zone, NULL); + ct_dpif_flush(ofproto->backer->dpif, zone, match); } static struct ct_timeout_policy * @@ -5674,6 +5675,10 @@ get_datapath_cap(const char *datapath_type, struct smap *cap) smap_add(cap, "lb_output_action", s.lb_output_action ? "true" : "false"); smap_add(cap, "ct_zero_snat", s.ct_zero_snat ? "true" : "false"); smap_add(cap, "add_mpls", s.add_mpls ? "true" : "false"); + + /* The ct_tuple_flush is implemented on dpif level, so it is supported + * for all backers. */ + smap_add(cap, "ct_flush", "true"); } /* Gets timeout policy name in 'backer' based on 'zone', 'dl_type' and diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h index 7e3fb6698..a84ddc1d0 100644 --- a/ofproto/ofproto-provider.h +++ b/ofproto/ofproto-provider.h @@ -42,6 +42,7 @@ #include "ofproto/ofproto.h" #include "openvswitch/list.h" #include "openvswitch/ofp-actions.h" +#include "openvswitch/ofp-ct.h" #include "openvswitch/ofp-errors.h" #include "openvswitch/ofp-flow.h" #include "openvswitch/ofp-group.h" @@ -1902,8 +1903,10 @@ struct ofproto_class { /* ## Connection tracking ## */ /* ## ------------------- ## */ /* Flushes the connection tracking tables. If 'zone' is not NULL, - * only deletes connections in '*zone'. */ - void (*ct_flush)(const struct ofproto *, const uint16_t *zone); + * only deletes connections in '*zone'. If 'match' is not NULL, + * deletes connections specified by the match. */ + void (*ct_flush)(const struct ofproto *, const uint16_t *zone, + const struct ofp_ct_match *match); /* Sets conntrack timeout policy specified by 'timeout_policy' to 'zone' * in datapath type 'dp_type'. */ diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index 3a527683c..17f636ed9 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -42,6 +42,7 @@ #include "openvswitch/meta-flow.h" #include "openvswitch/ofp-actions.h" #include "openvswitch/ofp-bundle.h" +#include "openvswitch/ofp-ct.h" #include "openvswitch/ofp-errors.h" #include "openvswitch/ofp-match.h" #include "openvswitch/ofp-msgs.h" @@ -934,7 +935,30 @@ handle_nxt_ct_flush_zone(struct ofconn *ofconn, const struct ofp_header *oh) uint16_t zone = ntohs(nzi->zone_id); if (ofproto->ofproto_class->ct_flush) { - ofproto->ofproto_class->ct_flush(ofproto, &zone); + ofproto->ofproto_class->ct_flush(ofproto, &zone, NULL); + } else { + return EOPNOTSUPP; + } + + return 0; +} + +static enum ofperr +handle_nxt_ct_flush(struct ofconn *ofconn, const struct ofp_header *oh) +{ + struct ofproto *ofproto = ofconn_get_ofproto(ofconn); + struct ofp_ct_match match = {0}; + bool with_zone = false; + uint16_t zone_id = 0; + + enum ofperr error = ofp_ct_match_decode(&match, &with_zone, &zone_id, oh); + if (error) { + return error; + } + + if (ofproto->ofproto_class->ct_flush) { + ofproto->ofproto_class->ct_flush(ofproto, with_zone ? &zone_id : NULL, + &match); } else { return EOPNOTSUPP; } @@ -8787,6 +8811,9 @@ handle_single_part_openflow(struct ofconn *ofconn, const struct ofp_header *oh, case OFPTYPE_CT_FLUSH_ZONE: return handle_nxt_ct_flush_zone(ofconn, oh); + case OFPTYPE_CT_FLUSH: + return handle_nxt_ct_flush(ofconn, oh); + case OFPTYPE_HELLO: case OFPTYPE_ERROR: case OFPTYPE_FEATURES_REPLY: diff --git a/tests/ofp-print.at b/tests/ofp-print.at index fe41cc42c..14aa55416 100644 --- a/tests/ofp-print.at +++ b/tests/ofp-print.at @@ -4073,3 +4073,111 @@ AT_CHECK([ovs-ofctl ofp-print "\ NXT_CT_FLUSH_ZONE (xid=0x3): zone_id=13 ]) AT_CLEANUP + +AT_SETUP([NXT_CT_FLUSH]) +AT_KEYWORDS([ofp-print]) +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 18 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +00 00 00 00 00 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): zone=0 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0' +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 20 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +00 00 00 00 00 00 00 \ +00 02 00 08 00 0d 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): zone=13 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0' +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 68 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +00 00 00 00 00 00 00 \ +00 02 00 08 00 0d 00 00 \ +00 00 00 48 00 00 00 00 \ +00 00 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \ +00 01 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \ +00 02 00 08 00 50 00 00 \ +00 03 00 08 1f 90 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): zone=13 'ct_nw_src=10.10.0.1,ct_nw_dst=10.10.0.2,ct_tp_src=80,ct_tp_dst=8080,ct_nw_proto=6' 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0' +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 68 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +00 00 00 00 00 00 00 \ +00 02 00 08 00 0d 00 00 \ +00 01 00 48 00 00 00 00 \ +00 01 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \ +00 00 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \ +00 03 00 08 00 50 00 00 \ +00 02 00 08 1f 90 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): zone=13 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6' 'ct_nw_src=10.10.0.2,ct_nw_dst=10.10.0.1,ct_tp_src=8080,ct_tp_dst=80' +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 b0 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +00 00 00 00 00 00 00 \ +00 02 00 08 00 0d 00 00 \ +00 00 00 48 00 00 00 00 \ +00 00 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \ +00 01 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \ +00 02 00 08 00 50 00 00 \ +00 03 00 08 1f 90 00 00 \ +00 01 00 48 00 00 00 00 \ +00 01 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \ +00 00 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \ +00 03 00 08 00 50 00 00 \ +00 02 00 08 1f 90 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): zone=13 'ct_nw_src=10.10.0.1,ct_nw_dst=10.10.0.2,ct_tp_src=80,ct_tp_dst=8080,ct_nw_proto=6' 'ct_nw_src=10.10.0.2,ct_nw_dst=10.10.0.1,ct_tp_src=8080,ct_tp_dst=80' +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 b8 00 00 00 03 00 00 23 20 00 00 00 20 \ +01 \ +00 00 00 00 00 00 00 \ +00 00 00 50 00 00 00 00 \ +00 00 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 01 00 00 00 00 \ +00 01 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 02 00 00 00 00 \ +00 04 00 08 00 0a 00 00 \ +00 05 00 05 01 00 00 00 \ +00 06 00 05 02 00 00 00 \ +00 01 00 50 00 00 00 00 \ +00 01 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 02 00 00 00 00 \ +00 00 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 01 00 00 00 00 \ +00 04 00 08 00 0a 00 00 \ +00 05 00 05 03 00 00 00 \ +00 06 00 05 04 00 00 00 \ +"], [0], [dnl +NXT_CT_FLUSH (xid=0x3): zone=0 'ct_ipv6_src=fd18::ffff:abcd:1,ct_ipv6_dst=fd18::ffff:abcd:2,icmp_id=10,icmp_type=1,icmp_code=2,ct_nw_proto=1' 'ct_ipv6_src=fd18::ffff:abcd:1,ct_ipv6_dst=fd18::ffff:abcd:2,icmp_id=10,icmp_type=3,icmp_code=4' +]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 58 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +00 00 00 00 00 00 00 \ +00 02 00 08 00 0d 00 00 \ +00 00 00 38 00 00 00 00 \ +00 00 00 14 00 0a 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \ +00 01 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \ +" | grep -q OFPBPC_BAD_VALUE], [0]) + +AT_CHECK([ovs-ofctl ofp-print "\ +01 04 00 60 00 00 00 03 00 00 23 20 00 00 00 20 \ +06 \ +00 00 00 00 00 00 00 \ +00 02 00 08 00 0d 00 00 \ +00 00 00 20 00 00 00 00 \ +00 00 00 14 00 0a 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \ +00 01 00 20 00 00 00 00 \ +00 00 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \ +" | grep -q OFPBPC_BAD_VALUE], [0]) +AT_CLEANUP diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at index a8934051e..8531b2e2e 100644 --- a/tests/ovs-ofctl.at +++ b/tests/ovs-ofctl.at @@ -3271,3 +3271,41 @@ AT_CHECK([ovs-ofctl -O OpenFlow15 dump-flows br0 | ofctl_strip | sed '/OFPST_FLO OVS_VSWITCHD_STOP(["/Flow exceeded the maximum flow statistics reply size and was excluded from the response set/d"]) AT_CLEANUP + +AT_SETUP([ovs-ofctl ct-flush]) +OVS_VSWITCHD_START + +AT_CHECK([ovs-appctl vlog/set ct_dpif:dbg]) + +# Check flush conntrack with both zone and tuple +AT_CHECK([ovs-ofctl ct-flush br0 zone=5 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1']) + +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 1]) +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=5 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_tp_src=1,ct_tp_dst=0,ct_nw_proto=17' 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0'" ovs-vswitchd.log]) + +# Check flush-conntrack just with tuple +AT_CHECK([ovs-ofctl ct-flush br0 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_nw_proto=17,ct_tp_src=1']) + +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 2]) +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=0 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_tp_src=1,ct_tp_dst=0,ct_nw_proto=17' 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0'" ovs-vswitchd.log]) + +# Check flush-conntrack with reply tuple +AT_CHECK([ovs-ofctl ct-flush br0 '' 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_nw_proto=17,ct_tp_src=1']) + +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 3]) +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=0 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=17' 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_tp_src=1,ct_tp_dst=0'" ovs-vswitchd.log]) + +# Check flush-conntrack with zone and reply tuple +AT_CHECK([ovs-ofctl ct-flush br0 zone=5 '' 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_nw_proto=17,ct_tp_src=1']) + +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 4]) +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=5 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=17' 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_tp_src=1,ct_tp_dst=0'" ovs-vswitchd.log]) + +# Check flush-conntrack without any tuple and zone +AT_CHECK([ovs-ofctl ct-flush br0]) + +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 5]) +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: <all>" ovs-vswitchd.log]) + +OVS_VSWITCHD_STOP +AT_CLEANUP diff --git a/tests/system-traffic.at b/tests/system-traffic.at index e7ec1d96b..503455cc6 100644 --- a/tests/system-traffic.at +++ b/tests/system-traffic.at @@ -2298,6 +2298,10 @@ priority=100,in_port=2,icmp,action=ct(zone=5,commit),1 AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt]) +m4_foreach([FLUSH_CMD], [[ovs-appctl dpctl/flush-conntrack], + [ovs-ofctl ct-flush br0]], [ +AS_BOX([Testing with FLUSH_CMD]) + dnl Test UDP from port 1 AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000 actions=resubmit(,0)"]) @@ -2305,10 +2309,10 @@ AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.1,"], [], udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1) ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1']) +AT_CHECK([FLUSH_CMD 'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1']) + +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.1,"], [1]) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.1,"], [1], [dnl -]) dnl Test UDP from port 2 AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000 actions=resubmit(,0)"]) @@ -2317,10 +2321,9 @@ AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], [0], udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=5 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2']) +AT_CHECK([FLUSH_CMD zone=5 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2']) -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0], [dnl -]) +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0]) dnl Test ICMP traffic NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3 -w 2 10.1.1.1 | FORMAT_PING], [0], [dnl @@ -2334,7 +2337,7 @@ icmp,orig=(src=10.1.1.2,dst=10.1.1.1,id=<cleared>,type=8,code=0),reply=(src=10.1 ICMP_ID=`cat stdout | cut -d ',' -f4 | cut -d '=' -f2` ICMP_TUPLE=ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=1,icmp_id=$ICMP_ID,icmp_type=8,icmp_code=0 -AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=5 $ICMP_TUPLE]) +AT_CHECK([FLUSH_CMD zone=5 $ICMP_TUPLE]) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], [1], [dnl ]) @@ -2349,13 +2352,13 @@ udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10. udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_src=1']) +AT_CHECK([FLUSH_CMD 'ct_nw_proto=17,ct_tp_src=1']) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_src=2']) +AT_CHECK([FLUSH_CMD 'ct_nw_proto=17,ct_tp_src=2']) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) @@ -2369,13 +2372,13 @@ udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10. udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_dst=2']) +AT_CHECK([FLUSH_CMD 'ct_nw_proto=17,ct_tp_dst=2']) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_dst=1']) +AT_CHECK([FLUSH_CMD 'ct_nw_proto=17,ct_tp_dst=1']) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) @@ -2389,13 +2392,13 @@ udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10. udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.1']) +AT_CHECK([FLUSH_CMD 'ct_nw_src=10.1.1.1']) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.2']) +AT_CHECK([FLUSH_CMD 'ct_nw_src=10.1.1.2']) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) @@ -2409,13 +2412,13 @@ udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10. udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_dst=10.1.1.2']) +AT_CHECK([FLUSH_CMD 'ct_nw_dst=10.1.1.2']) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_dst=10.1.1.1']) +AT_CHECK([FLUSH_CMD 'ct_nw_dst=10.1.1.1']) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) @@ -2429,15 +2432,16 @@ udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10. udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack '' 'ct_nw_src=10.1.1.2']) +AT_CHECK([FLUSH_CMD '' 'ct_nw_src=10.1.1.2']) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5 ]) -AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=5 '' 'ct_nw_src=10.1.1.1']) +AT_CHECK([FLUSH_CMD zone=5 '' 'ct_nw_src=10.1.1.1']) AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1]) +]) OVS_TRAFFIC_VSWITCHD_STOP AT_CLEANUP diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index 10a6a64de..0a611b2ee 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -296,6 +296,37 @@ Flushes the connection tracking entries in \fIzone\fR on \fIswitch\fR. This command uses an Open vSwitch extension that is only in Open vSwitch 2.6 and later. . +.IP "\fBct\-flush \fIswitch [zone=N] [ct-orig-tuple [ct-reply-tuple]]\fR +Flushes the connection entries on \fIswitch\fR based on \fIzone\fR and +connection tracking tuples \fIct-[orig|reply]-tuple\fR. +.IP +If \fIct-[orig|reply]-tuple\fR is not provided, flushes all the connection +entries. If \fIzone\fR is specified, only flushes the connections in +\fIzone\fR. +.IP +If \fIct-[orig|reply]-tuple\fR is provided, flushes the connection entry +specified by \fIct-[orig|reply]-tuple\fR in \fIzone\fR. The zone defaults +to 0 if it is not provided. The userspace connection tracker requires flushing +with the original pre-NATed tuple and a warning log will be otherwise +generated. The tuple can be partial and will remove all connections that are +matching on the specified fields. In order to specify only +\fIct-reply-tuple\fR, provide empty string as \fIct-orig-tuple\fR. +.IP +Note: Currently there is limitation for matching on ICMP, in order to partially +match on ICMP parameters the \fIct-[orig|reply]-tuple\fR has to include +either source or destination IP. +.IP +An example of an IPv4 ICMP \fIct-[orig|reply]-tuple\fR: +.IP +"ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=1,icmp_type=8,icmp_code=0,icmp_id=10" +.IP +An example of an IPv6 TCP \fIct-[orig|reply]-tuple\fR: +.IP +"ct_ipv6_src=fc00::1,ct_ipv6_dst=fc00::2,ct_nw_proto=6,ct_tp_src=1,ct_tp_dst=2" +.IP +This command uses an Open vSwitch extension that is only in Open vSwitch 3.1 +and later. +. .SS "OpenFlow Switch Flow Table Commands" . These commands manage the flow table in an OpenFlow switch. In each diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c index fe9114580..eabec18a3 100644 --- a/utilities/ovs-ofctl.c +++ b/utilities/ovs-ofctl.c @@ -48,6 +48,7 @@ #include "openvswitch/meta-flow.h" #include "openvswitch/ofp-actions.h" #include "openvswitch/ofp-bundle.h" +#include "openvswitch/ofp-ct.h" #include "openvswitch/ofp-errors.h" #include "openvswitch/ofp-group.h" #include "openvswitch/ofp-match.h" @@ -485,6 +486,9 @@ usage(void) " dump-ipfix-bridge SWITCH print ipfix stats of bridge\n" " dump-ipfix-flow SWITCH print flow ipfix of a bridge\n" " ct-flush-zone SWITCH ZONE flush conntrack entries in ZONE\n" + " ct-flush SWITCH [ZONE] [CT_ORIG_TUPLE [CT_REPLY_TUPLE]]\n" + " flush conntrack entries specified\n" + " by CT_ORIG/REPLY_TUPLE and ZONE\n" "\nFor OpenFlow switches and controllers:\n" " probe TARGET probe whether TARGET is up\n" " ping TARGET [N] latency of N-byte echos\n" @@ -3051,6 +3055,50 @@ ofctl_ct_flush_zone(struct ovs_cmdl_context *ctx) } static void +ofctl_ct_flush(struct ovs_cmdl_context *ctx) +{ + struct vconn *vconn; + struct ofp_ct_match match = {0}; + struct ds ds = DS_EMPTY_INITIALIZER; + uint16_t zone, *pzone = NULL; + int args = ctx->argc - 2; + + /* Parse zone. */ + if (args && !strncmp(ctx->argv[2], "zone=", 5)) { + if (!ovs_scan(ctx->argv[2], "zone=%"SCNu16, &zone)) { + ovs_fatal(0, "Failed to parse zone"); + } + pzone = &zone; + args--; + } + + /* Parse ct tuples. */ + for (int i = 0; i < 2; i++) { + if (!args) { + break; + } + + struct ofp_ct_tuple *tuple = + i ? &match.tuple_reply : &match.tuple_orig; + const char *arg = ctx->argv[ctx->argc - args]; + + if (arg[0] && !ofp_ct_tuple_parse(tuple, arg, &ds, &match.ip_proto, + &match.l3_type)) { + ovs_fatal(0, "Failed to parse ct-tuple: %s", ds_cstr(&ds)); + } + args--; + } + + open_vconn(ctx->argv[1], &vconn); + enum ofp_version version = vconn_get_version(vconn); + struct ofpbuf *msg = ofp_ct_match_encode(&match, pzone, version); + + ds_destroy(&ds); + transact_noreply(vconn, msg); + vconn_close(vconn); +} + +static void ofctl_dump_ipfix_flow(struct ovs_cmdl_context *ctx) { dump_trivial_transaction(ctx->argv[1], OFPRAW_NXST_IPFIX_FLOW_REQUEST); @@ -5063,6 +5111,9 @@ static const struct ovs_cmdl_command all_commands[] = { { "ct-flush-zone", "switch zone", 2, 2, ofctl_ct_flush_zone, OVS_RO }, + { "ct-flush", "switch [zone=N] [ct-orig-tuple [ct-reply-tuple]]", + 1, 4, ofctl_ct_flush, OVS_RO }, + { "ofp-parse", "file", 1, 1, ofctl_ofp_parse, OVS_RW }, { "ofp-parse-pcap", "pcap", |