Add extension that allows to flush connections from CT
by specifying fields that the connections should be
matched against. This allows to match only some fields
of the connection e.g. source address for orig direction.

Reported-at: https://bugzilla.redhat.com/2120546
Signed-off-by: Ales Musil <amu...@redhat.com>
---
v7: Rebase on top of current master.
    Address comments from Ilya:
    * Use the public header for encode/decode functions.
    * Adjust the code accordingly to the new header file.
    * Address some style related comments.
    * Rename the nested TLV, so they do not overlap with
      the outer types.
    * Document the ICMP partial match limitation.

v6: Rebase on top of current master.
    Address comments from Ilya.
v5: Add missing usage and man for ovs-ofctl command.
v4: Allow ovs-ofctl flush/conntrack without any zone/tuple.
v3: Rebase on top of master.
v2: Rebase on top of master.
    Use suggestion from Ilya.
---
 NEWS                           |   6 +
 include/openflow/nicira-ext.h  |  37 +++++++
 include/openvswitch/ofp-ct.h   |  10 ++
 include/openvswitch/ofp-msgs.h |   4 +
 lib/ofp-bundle.c               |   1 +
 lib/ofp-ct.c                   | 196 +++++++++++++++++++++++++++++++++
 lib/ofp-print.c                |  20 ++++
 lib/rconn.c                    |   1 +
 ofproto/ofproto-dpif.c         |   9 +-
 ofproto/ofproto-provider.h     |   7 +-
 ofproto/ofproto.c              |  29 ++++-
 tests/ofp-print.at             | 108 ++++++++++++++++++
 tests/ovs-ofctl.at             |  38 +++++++
 tests/system-traffic.at        |  38 ++++---
 utilities/ovs-ofctl.8.in       |  27 +++++
 utilities/ovs-ofctl.c          |  51 +++++++++
 16 files changed, 560 insertions(+), 22 deletions(-)

diff --git a/NEWS b/NEWS
index 035fcb0ee..22840402c 100644
--- a/NEWS
+++ b/NEWS
@@ -36,6 +36,12 @@ 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.
+   - OpenFlow:
+     * New OpenFlow extension NXT_CT_FLUSH to flush connections matching
+       the specified fields.
+   - ovs-ofctl:
+     * New command "flush-conntrack" that accepts zone and 5-tuple or partial
+       5-tuple for both directions.
 
 
 v3.0.0 - 15 Aug 2022
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index b68804991..35f66a76d 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.
+                                * The nested TLV are specified
+                                * by 'enum nx_ct_flush_tuple_tlv_type'. */
+    NXT_CT_REPLY_TUPLE = 1,    /* Outer type for reply tuple TLC. *
+                                * The nested TLV 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 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];           /* 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 e7af45337..094da0a97 100644
--- a/include/openvswitch/ofp-ct.h
+++ b/include/openvswitch/ofp-ct.h
@@ -21,6 +21,8 @@
 #include <sys/types.h>
 #include <netinet/in.h>
 
+#include "openflow/nicira-ext.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -63,6 +65,14 @@ bool ofp_ct_tuple_parse(struct ofp_ct_tuple *tuple, const 
char *s,
 
 bool ofp_ct_is_match_zero(const struct ofp_ct_match *match);
 
+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 * ofp_ct_match_encode(const struct ofp_ct_match *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 61f0be953..a81d9f0bc 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"
 
@@ -212,3 +216,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"
@@ -949,6 +950,23 @@ ofp_print_nxt_ct_flush_zone(struct ds *string, const 
struct nx_zone_id *nzi)
     return 0;
 }
 
+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,
@@ -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..6f02a9134 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -296,6 +296,33 @@ 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-origin-tuple [ct-reply-tuple]]\fR
+Flushes the connection entries in the tracker \fIzone\fR and
+connection tracking tuple \fIct-tuple\fR.
+If \fIct-[origin|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-[origin|reply]-tuple\fR is provided, flushes the connection entry
+specified by \fIct-[origin|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-origin-tuple\fR.
+.IP
+Note: Currently there is limitation for matching on ICMP, in order to partially
+match on ICMP parameters the \fIct-[origin|reply]-tuple\fR has to include
+either source or destination IP.
+.IP
+An example of an IPv4 ICMP \fIct-[origin|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-[origin|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"
+.
 .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"
@@ -3050,6 +3054,50 @@ ofctl_ct_flush_zone(struct ovs_cmdl_context *ctx)
     vconn_close(vconn);
 }
 
+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)
 {
@@ -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",
-- 
2.39.0


_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to