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>
---
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  |  41 ++++++++
 include/openvswitch/ofp-msgs.h |   4 +
 include/openvswitch/ofp-util.h |   4 +
 lib/ofp-bundle.c               |   1 +
 lib/ofp-ct-util.c              | 174 +++++++++++++++++++++++++++++++++
 lib/ofp-ct-util.h              |   9 ++
 lib/ofp-print.c                |  20 ++++
 lib/ofp-util.c                 |  24 +++++
 lib/rconn.c                    |   1 +
 ofproto/ofproto-dpif.c         |   8 +-
 ofproto/ofproto-provider.h     |   7 +-
 ofproto/ofproto.c              |  30 +++++-
 tests/ofp-print.at             | 108 ++++++++++++++++++++
 tests/ovs-ofctl.at             |  38 +++++++
 tests/system-traffic.at        |  38 +++----
 utilities/ovs-ofctl.8.in       |  22 +++++
 utilities/ovs-ofctl.c          |  51 ++++++++++
 18 files changed, 564 insertions(+), 22 deletions(-)

diff --git a/NEWS b/NEWS
index d1f749d69..d7ab3cd99 100644
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,12 @@ Post-v3.0.0
    - ovs-dpctl and 'ovs-appctl dpctl/' commands:
      * "flush-conntrack" is 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..cc4725b5f 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -1064,4 +1064,45 @@ 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_DIRECTION = 0,/* CT orig direction outer type. */
+    NXT_CT_REPLY_DIRECTION,   /* CT reply direction outer type. */
+
+    /* Primitive types. */
+    NXT_CT_ZONE_ID,           /* CT zone id. */
+};
+
+/* CT flush nested TLVs. */
+enum nx_ct_flush_nested_tlv_type {
+    NXT_CT_SRC = 0,           /* CT source IPv6 or mapped IPv4 address. */
+    NXT_CT_DST,               /* CT destination IPv6 or mapped IPv4 address. */
+    NXT_CT_SRC_PORT,          /* CT source port. */
+    NXT_CT_DST_PORT,          /* CT destination port. */
+    NXT_CT_ICMP_ID,           /* CT ICMP id. */
+    NXT_CT_ICMP_TYPE,         /* CT ICMP type. */
+    NXT_CT_ICMP_CODE,         /* CT ICMP code. */
+};
+
+/* NXT_CT_FLUSH.
+ *
+ * Flushes the connection tracking specified by 5-tuple.
+ * The struct should be followed by TLVs specifying the matching parameters. */
+struct nx_ct_flush {
+    uint8_t ip_proto;          /* IP protocol. */
+    uint8_t pad[7];           /* Must be zero. */
+    /* Followed by optional TLVs specifying the matching parameters.
+     *  - Regular TLV containing be16.
+     *  - Nested TLV (original/reply) direction containing on or more:
+     *      - IPv6 or mapped IPv4 source IP.
+     *      - IPv6 or mapped IPv4 destination IP.
+     *      - be16 source port.
+     *      - be16 destination port.
+     *      - be16 ICMP id.
+     *      - u8 ICMP type.
+     *      - u8 ICMP code. */
+};
+OFP_ASSERT(sizeof(struct nx_ct_flush) == 8);
+
 #endif /* openflow/nicira-ext.h */
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/include/openvswitch/ofp-util.h b/include/openvswitch/ofp-util.h
index 84937ae26..e10d90b9f 100644
--- a/include/openvswitch/ofp-util.h
+++ b/include/openvswitch/ofp-util.h
@@ -65,6 +65,10 @@ struct ofpbuf *ofputil_encode_echo_reply(const struct 
ofp_header *);
 
 struct ofpbuf *ofputil_encode_barrier_request(enum ofp_version);
 
+struct ofpbuf *ofputil_ct_match_encode(const struct ofputil_ct_match *match,
+                                       uint16_t *zone_id,
+                                       enum ofp_version version);
+
 #ifdef __cplusplus
 }
 #endif
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-util.c b/lib/ofp-ct-util.c
index 790d5dbec..5baa48ca0 100644
--- a/lib/ofp-ct-util.c
+++ b/lib/ofp-ct-util.c
@@ -23,8 +23,12 @@
 
 #include "ct-dpif.h"
 #include "ofp-ct-util.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"
 
@@ -301,3 +305,173 @@ 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 = ipv6_is_zero(addr)
+                  ? 0
+                  : 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
+ofputil_ct_tuple_decode_nested(struct ofpbuf *property,
+                               struct ofputil_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_SRC:
+            error = ofpprop_pull_ipv6(&inner, &tuple->src, l3_type);
+            break;
+        case NXT_CT_DST:
+            error = ofpprop_pull_ipv6(&inner, &tuple->dst, l3_type);
+            break;
+
+        case NXT_CT_SRC_PORT:
+            error = ofpprop_parse_be16(&inner, &tuple->src_port);
+            break;
+
+        case NXT_CT_DST_PORT:
+            error = ofpprop_parse_be16(&inner, &tuple->dst_port);
+            break;
+
+        case NXT_CT_ICMP_ID:
+            error = ofpprop_parse_be16(&inner, &tuple->icmp_id);
+            break;
+
+        case NXT_CT_ICMP_TYPE:
+            error = ofpprop_parse_u8(&inner, &tuple->icmp_type);
+            break;
+
+        case NXT_CT_ICMP_CODE:
+            error = ofpprop_parse_u8(&inner, &tuple->icmp_code);
+        }
+
+        if (error) {
+            return error;
+        }
+    }
+
+    return 0;
+}
+
+enum ofperr
+ofputil_ct_match_decode(struct ofputil_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 ofputil_ct_tuple *orig = &match->tuple_orig;
+    struct ofputil_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_DIRECTION:
+            error = ofputil_ct_tuple_decode_nested(&property, orig,
+                                                   &match->l3_type);
+            break;
+
+        case NXT_CT_REPLY_DIRECTION:
+            error = ofputil_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;
+}
+
+void
+ofputil_ct_tuple_encode(const struct ofputil_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_SRC, &tuple->src, sizeof tuple->src);
+    }
+
+    if (!ipv6_is_zero(&tuple->dst)) {
+        ofpprop_put(&nested, NXT_CT_DST, &tuple->dst, sizeof tuple->dst);
+    }
+
+    if (ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6) {
+        ofpprop_put_be16(&nested, NXT_CT_ICMP_ID, tuple->icmp_id);
+        ofpprop_put_u8(&nested, NXT_CT_ICMP_TYPE, tuple->icmp_type);
+        ofpprop_put_u8(&nested, NXT_CT_ICMP_CODE, tuple->icmp_code);
+    } else {
+        if (tuple->src_port) {
+            ofpprop_put_be16(&nested, NXT_CT_SRC_PORT, tuple->src_port);
+        }
+
+        if (tuple->dst_port) {
+            ofpprop_put_be16(&nested, NXT_CT_DST_PORT, tuple->dst_port);
+        }
+    }
+
+    if (nested.size) {
+        ofpprop_put_nested(buf, type, &nested);
+    }
+
+    ofpbuf_uninit(&nested);
+}
diff --git a/lib/ofp-ct-util.h b/lib/ofp-ct-util.h
index 1a219787d..80b771840 100644
--- a/lib/ofp-ct-util.h
+++ b/lib/ofp-ct-util.h
@@ -17,6 +17,7 @@
 #define OVS_OFP_CT_UTIL_H
 
 #include "ct-dpif.h"
+#include "openflow/nicira-ext.h"
 #include "openvswitch/ofp-util.h"
 
 bool ofputil_ct_match_cmp(const struct ofputil_ct_match *match,
@@ -37,4 +38,12 @@ bool ofputil_ct_tuple_parse(struct ofputil_ct_tuple *tuple, 
const char *s,
 
 bool ofputil_is_ct_match_zero(const struct ofputil_ct_match *match);
 
+enum ofperr ofputil_ct_match_decode(struct ofputil_ct_match *match,
+                                    bool *with_zone, uint16_t *zone_id,
+                                    const struct ofp_header *oh);
+
+void ofputil_ct_tuple_encode(const struct ofputil_ct_tuple *tuple,
+                             struct ofpbuf *buf,
+                             enum nx_ct_flush_tlv_type type, uint8_t ip_proto);
+
 #endif /* lib/ofp-ct-util.h */
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index bd37fa17a..139a264d6 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -36,6 +36,7 @@
 #include "learn.h"
 #include "multipath.h"
 #include "netdev.h"
+#include "ofp-ct-util.h"
 #include "nx-match.h"
 #include "odp-util.h"
 #include "openflow/nicira-ext.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 ofputil_ct_match match = {0};
+
+    enum ofperr error = ofputil_ct_match_decode(&match, NULL, &zone_id, oh);
+    if (error) {
+        return error;
+    }
+
+    ds_put_format(string, "zone=%"PRIu16" ", zone_id);
+    ofputil_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/ofp-util.c b/lib/ofp-util.c
index a324ceeea..91c7d805d 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -31,6 +31,7 @@
 #include "multipath.h"
 #include "netdev.h"
 #include "nx-match.h"
+#include "ofp-ct-util.h"
 #include "id-pool.h"
 #include "openflow/netronome-ext.h"
 #include "openvswitch/dynamic-string.h"
@@ -237,3 +238,26 @@ ofputil_encode_barrier_request(enum ofp_version 
ofp_version)
 
     return ofpraw_alloc(type, ofp_version, 0);
 }
+
+struct ofpbuf *
+ofputil_ct_match_encode(const struct ofputil_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 ofputil_ct_tuple *orig = &match->tuple_orig;
+    const struct ofputil_ct_tuple *reply = &match->tuple_reply;
+
+    nx_flush->ip_proto = match->ip_proto;
+
+    ofputil_ct_tuple_encode(orig, msg, NXT_CT_ORIG_DIRECTION,
+                            match->ip_proto);
+    ofputil_ct_tuple_encode(reply, msg, NXT_CT_REPLY_DIRECTION,
+                            match->ip_proto);
+
+    if (zone_id) {
+        ofpprop_put_u16(msg, NXT_CT_ZONE_ID, *zone_id);
+    }
+
+    return msg;
+}
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..29174a585 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 ofputil_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,9 @@ 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..5e39234f9 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -49,6 +49,7 @@
 #include "openvswitch/ofp-port.h"
 #include "openvswitch/ofp-switch.h"
 #include "openvswitch/ofp-table.h"
+#include "openvswitch/ofp-util.h"
 #include "ovs-atomic.h"
 #include "ovs-rcu.h"
 #include "ovs-thread.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 ofputil_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..c9b222994 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -34,6 +34,7 @@
 #include "openvswitch/hmap.h"
 #include "netdev.h"
 #include "nx-match.h"
+#include "ofp-ct-util.h"
 #include "ofproto.h"
 #include "ofproto-provider.h"
 #include "openflow/nicira-ext.h"
@@ -934,7 +935,31 @@ 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 ofputil_ct_match match = {0};
+    bool with_zone = false;
+    uint16_t zone_id = 0;
+
+    enum ofperr error =
+        ofputil_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 +8812,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..7e6d5e6d3 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..359df84db 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..426708497 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -296,6 +296,28 @@ 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-tuple\fR is provided, flushes the connection entry specified by
+\fIct-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.
+An example of an IPv4 ICMP \fIct-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-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..4fb4853ff 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -40,6 +40,7 @@
 #include "fatal-signal.h"
 #include "nx-match.h"
 #include "odp-util.h"
+#include "ofp-ct-util.h"
 #include "ofp-version-opt.h"
 #include "ofproto/ofproto.h"
 #include "openflow/nicira-ext.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=N] CT_ORIG_TUPLE [CT_REPLY_TUPLE]]"
+           " flush conntrack entries specified by CT_ORIG_TUPLE,"
+           " CT_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 ofputil_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 ofputil_ct_tuple *tuple =
+            i ? &match.tuple_reply : &match.tuple_orig;
+        const char *arg = ctx->argv[ctx->argc - args];
+
+        if (arg[0] && !ofputil_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 = ofputil_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