summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS6
-rw-r--r--include/openflow/nicira-ext.h37
-rw-r--r--include/openvswitch/ofp-ct.h8
-rw-r--r--include/openvswitch/ofp-msgs.h4
-rw-r--r--lib/ofp-bundle.c1
-rw-r--r--lib/ofp-ct.c196
-rw-r--r--lib/ofp-print.c20
-rw-r--r--lib/rconn.c1
-rw-r--r--ofproto/ofproto-dpif.c9
-rw-r--r--ofproto/ofproto-provider.h7
-rw-r--r--ofproto/ofproto.c29
-rw-r--r--tests/ofp-print.at108
-rw-r--r--tests/ovs-ofctl.at38
-rw-r--r--tests/system-traffic.at38
-rw-r--r--utilities/ovs-ofctl.8.in31
-rw-r--r--utilities/ovs-ofctl.c51
16 files changed, 562 insertions, 22 deletions
diff --git a/NEWS b/NEWS
index 3685d7c15..e31b092be 100644
--- a/NEWS
+++ b/NEWS
@@ -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",