Extend the current NX_CT_FLUSH with four additional fields,
that allow to match on CT entry "mark" or "labels". This
is encoded as separate TLV values which is backward compatible.
Versions that do not support them will simply ignore it.

Extend also the ovs-dpctl and ovs-ofctl command line tools with
option to specify those two matching parameters for the "ct-flush"
command.

Reported-at: https://issues.redhat.com/browse/FDP-55
Signed-off-by: Ales Musil <amu...@redhat.com>
---
 include/openflow/nicira-ext.h |   4 +
 include/openvswitch/ofp-ct.h  |  14 ++-
 lib/ct-dpif.c                 |  14 ++-
 lib/dpctl.c                   |  46 ++-------
 lib/ofp-ct.c                  | 181 +++++++++++++++++++++++++++++++++-
 lib/ofp-print.c               |   2 +-
 tests/ofp-print.at            |  56 +++++++++++
 tests/ovs-ofctl.at            |  32 ++++++
 tests/system-traffic.at       | 112 +++++++++++++--------
 utilities/ovs-ofctl.8.in      |  31 +++---
 utilities/ovs-ofctl.c         |  42 ++------
 11 files changed, 398 insertions(+), 136 deletions(-)

diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 768775898..959845ce6 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -1075,6 +1075,10 @@ enum nx_ct_flush_tlv_type {
                                 * by 'enum nx_ct_flush_tuple_tlv_type'*/
     /* Primitive types. */
     NXT_CT_ZONE_ID = 2,        /* be16 zone id. */
+    NXT_CT_MARK = 3,           /* be32 mark. */
+    NXT_CT_MARK_MASK = 4,      /* be32 mark mask. */
+    NXT_CT_LABELS = 5,         /* be128 labels. */
+    NXT_CT_LABELS_MASK = 6,    /* be128 labels mask. */
 };
 
 /* CT flush nested TLVs. */
diff --git a/include/openvswitch/ofp-ct.h b/include/openvswitch/ofp-ct.h
index c8023c309..d57b62678 100644
--- a/include/openvswitch/ofp-ct.h
+++ b/include/openvswitch/ofp-ct.h
@@ -51,15 +51,21 @@ struct ofp_ct_match {
 
     struct ofp_ct_tuple tuple_orig;
     struct ofp_ct_tuple tuple_reply;
+
+    uint32_t mark;
+    uint32_t mark_mask;
+
+    ovs_u128 labels;
+    ovs_u128 labels_mask;
 };
 
 bool ofp_ct_match_is_zero(const struct ofp_ct_match *);
-bool ofp_ct_tuple_is_zero(const struct ofp_ct_tuple *, uint8_t ip_proto);
-bool ofp_ct_tuple_is_five_tuple(const struct ofp_ct_tuple *, uint8_t ip_proto);
+bool ofp_ct_match_is_five_tuple(const struct ofp_ct_match *);
 
 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);
+bool ofp_ct_match_parse(const char **, int argc, struct ds *,
+                        struct ofp_ct_match *, bool *with_zone,
+                        uint16_t *zone_id);
 
 enum ofperr ofp_ct_match_decode(struct ofp_ct_match *, bool *with_zone,
                                 uint16_t *zone_id, const struct ofp_header *);
diff --git a/lib/ct-dpif.c b/lib/ct-dpif.c
index f59c6e560..9ff91c955 100644
--- a/lib/ct-dpif.c
+++ b/lib/ct-dpif.c
@@ -269,6 +269,15 @@ ct_dpif_entry_cmp(const struct ct_dpif_entry *entry,
         return false;
     }
 
+    if ((match->mark & match->mark_mask) != (entry->mark & match->mark_mask)) {
+        return false;
+    }
+
+    if (!ovs_u128_equals(ovs_u128_and(match->labels, match->labels_mask),
+                         ovs_u128_and(entry->labels, match->labels_mask))) {
+        return false;
+    }
+
     return true;
 }
 
@@ -288,15 +297,14 @@ ct_dpif_flush_tuple(struct dpif *dpif, const uint16_t 
*zone,
     if (VLOG_IS_DBG_ENABLED()) {
         struct ds ds = DS_EMPTY_INITIALIZER;
         ofp_ct_match_format(&ds, match);
-        VLOG_DBG("%s: ct_flush: zone=%d %s", dpif_name(dpif), zone ? *zone : 0,
+        VLOG_DBG("%s: ct_flush: zone=%d%s", dpif_name(dpif), zone ? *zone : 0,
                  ds_cstr(&ds));
         ds_destroy(&ds);
     }
 
     /* If we have full five tuple in original and empty reply tuple just
      * do the flush over original tuple directly. */
-    if (ofp_ct_tuple_is_five_tuple(&match->tuple_orig, match->ip_proto) &&
-        ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto)) {
+    if (ofp_ct_match_is_five_tuple(match)) {
         struct ct_dpif_tuple tuple;
 
         ct_dpif_tuple_from_ofp_ct_tuple(&match->tuple_orig, &tuple,
diff --git a/lib/dpctl.c b/lib/dpctl.c
index cd12625a1..15b04b7a2 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -1773,48 +1773,17 @@ dpctl_flush_conntrack(int argc, const char *argv[],
     struct dpif *dpif = NULL;
     struct ofp_ct_match match = {0};
     struct ds ds = DS_EMPTY_INITIALIZER;
-    uint16_t zone, *pzone = NULL;
+    uint16_t zone;
     int error;
     int args = argc - 1;
-    int zone_pos = 1;
+    bool with_zone = false;
 
     if (dp_arg_exists(argc, argv)) {
         args--;
-        zone_pos = 2;
-    }
-
-    /* Parse zone. */
-    if (args && !strncmp(argv[zone_pos], "zone=", 5)) {
-        if (!ovs_scan(argv[zone_pos], "zone=%"SCNu16, &zone)) {
-            ds_put_cstr(&ds, "failed to parse zone");
-            error = EINVAL;
-            goto error;
-        }
-        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 = argv[argc - args];
-
-        if (arg[0] && !ofp_ct_tuple_parse(tuple, arg, &ds, &match.ip_proto,
-                                          &match.l3_type)) {
-            error = EINVAL;
-            goto error;
-        }
-        args--;
     }
 
-    /* Report error if there is more than one unparsed argument. */
-    if (args > 0) {
-        ds_put_cstr(&ds, "invalid arguments");
+    if (args && !ofp_ct_match_parse(&argv[argc - args], args, &ds, &match,
+                                    &with_zone ,&zone)) {
         error = EINVAL;
         goto error;
     }
@@ -1825,7 +1794,7 @@ dpctl_flush_conntrack(int argc, const char *argv[],
         return error;
     }
 
-    error = ct_dpif_flush(dpif, pzone, &match);
+    error = ct_dpif_flush(dpif, with_zone ? &zone : NULL, &match);
     if (!error) {
         dpif_close(dpif);
         return 0;
@@ -3012,8 +2981,9 @@ static const struct dpctl_command all_commands[] = {
       0, 4, dpctl_dump_conntrack, DP_RO },
     { "dump-conntrack-exp", "[dp] [zone=N]",
       0, 2, dpctl_dump_conntrack_exp, DP_RO },
-    { "flush-conntrack", "[dp] [zone=N] [ct-orig-tuple] [ct-reply-tuple]",
-      0, 4, dpctl_flush_conntrack, DP_RW },
+    { "flush-conntrack", "[dp] [zone=N] [mark=X[/M]] [labels=Y[/N]] "
+                         "[ct-orig-tuple [ct-reply-tuple]]",
+      0, 6, dpctl_flush_conntrack, DP_RW },
     { "cache-get-size", "[dp]", 0, 1, dpctl_cache_get_size, DP_RO },
     { "cache-set-size", "dp cache <size>", 3, 3, dpctl_cache_set_size, DP_RW },
     { "ct-stats-show", "[dp] [zone=N]",
diff --git a/lib/ofp-ct.c b/lib/ofp-ct.c
index 85a9d8bec..d9c57cf82 100644
--- a/lib/ofp-ct.c
+++ b/lib/ofp-ct.c
@@ -50,7 +50,7 @@ ofp_ct_tuple_format(struct ds *ds, const struct ofp_ct_tuple 
*tuple,
     }
 }
 
-bool
+static bool
 ofp_ct_tuple_is_zero(const struct ofp_ct_tuple *tuple, uint8_t ip_proto)
 {
     bool is_zero = ipv6_is_zero(&tuple->src) && ipv6_is_zero(&tuple->dst);
@@ -62,7 +62,7 @@ ofp_ct_tuple_is_zero(const struct ofp_ct_tuple *tuple, 
uint8_t ip_proto)
     return is_zero;
 }
 
-bool
+static bool
 ofp_ct_tuple_is_five_tuple(const struct ofp_ct_tuple *tuple, uint8_t ip_proto)
 {
     /* First check if we have address. */
@@ -75,18 +75,62 @@ ofp_ct_tuple_is_five_tuple(const struct ofp_ct_tuple 
*tuple, uint8_t ip_proto)
     return five_tuple;
 }
 
+static bool
+ofp_ct_match_mark_is_zero(const struct ofp_ct_match *match)
+{
+    return !match->mark && !match->mark_mask;
+}
+
+static bool
+ofp_ct_match_labels_is_zero(const struct ofp_ct_match *match)
+{
+    return ovs_u128_is_zero(match->labels) &&
+           ovs_u128_is_zero(match->labels_mask);
+}
+
+bool
+ofp_ct_match_is_five_tuple(const struct ofp_ct_match *match)
+{
+    return ofp_ct_tuple_is_five_tuple(&match->tuple_orig, match->ip_proto) &&
+           ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto) &&
+           ofp_ct_match_mark_is_zero(match) &&
+           ofp_ct_match_labels_is_zero(match);
+}
+
 bool
 ofp_ct_match_is_zero(const struct ofp_ct_match *match)
 {
     return !match->ip_proto && !match->l3_type &&
            ofp_ct_tuple_is_zero(&match->tuple_orig, match->ip_proto) &&
-           ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto);
+           ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto) &&
+           ofp_ct_match_mark_is_zero(match) &&
+           ofp_ct_match_labels_is_zero(match);
 }
 
 void
 ofp_ct_match_format(struct ds *ds, const struct ofp_ct_match *match)
 {
-    ds_put_cstr(ds, "'");
+    if (!ofp_ct_match_mark_is_zero(match)) {
+        ds_put_format(ds, " mark=%#"PRIx32, match->mark);
+        if (match->mark_mask != UINT32_MAX) {
+            ds_put_format(ds, "/%#"PRIx32, match->mark_mask);
+        }
+    }
+
+    if (!ofp_ct_match_labels_is_zero(match)) {
+        ovs_be128 be_value = hton128(match->labels);
+        ovs_be128 be_mask = hton128(match->labels_mask);
+
+        ds_put_cstr(ds, " labels=");
+        ds_put_hex(ds, &be_value, sizeof be_value);
+
+        if (!ovs_u128_is_ones(match->labels_mask)) {
+            ds_put_char(ds, '/');
+            ds_put_hex(ds, &be_mask, sizeof be_mask);
+        }
+    }
+
+    ds_put_cstr(ds, " '");
     ofp_ct_tuple_format(ds, &match->tuple_orig, match->ip_proto,
                         match->l3_type);
     ds_put_format(ds, ",ct_nw_proto=%u' '", match->ip_proto);
@@ -95,10 +139,27 @@ ofp_ct_match_format(struct ds *ds, const struct 
ofp_ct_match *match)
     ds_put_cstr(ds, "'");
 }
 
+static inline bool
+ofp_ct_masked_parse(const char *s, uint8_t *val, size_t val_len,
+                    uint8_t *mask, size_t mask_len)
+{
+    char *tail;
+    if (!parse_int_string(s, val, val_len, &tail)) {
+        if (*tail != '/' || parse_int_string(tail + 1, mask,
+                                             mask_len, &tail)) {
+            memset(mask, UINT8_MAX, mask_len);
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
 /* Parses a specification of a conntrack 5-tuple from 's' into 'tuple'.
  * Returns true on success.  Otherwise, returns false and puts the error
  * message in 'ds'. */
-bool
+static bool
 ofp_ct_tuple_parse(struct ofp_ct_tuple *tuple, const char *s,
                    struct ds *ds, uint8_t *ip_proto, uint16_t *l3_type)
 {
@@ -216,6 +277,85 @@ error:
     return false;
 }
 
+/* Parses a specification of a conntrack match from 'argv' into 'match'.
+ * Returns true on success. Otherwise, returns false and puts the error
+ * message in 'ds'. */
+bool
+ofp_ct_match_parse(const char **argv, int argc, struct ds *ds,
+                   struct ofp_ct_match *match, bool *with_zone,
+                   uint16_t *zone_id)
+{
+    int args = argc;
+
+    /* Parse zone. */
+    if (args && !strncmp(argv[argc - args], "zone=", 5)) {
+        if (!ovs_scan(argv[argc - args], "zone=%"SCNu16, zone_id)) {
+            ds_put_cstr(ds, "failed to parse zone");
+            return false;
+        }
+        *with_zone = true;
+        args--;
+    }
+
+    /* Parse mark. */
+    if (args && !strncmp(argv[argc - args], "mark=", 5)) {
+        const char *s = argv[argc - args] + 5;
+        ovs_be32 mark_be;
+        ovs_be32 mask_be;
+
+        if (ofp_ct_masked_parse(s, (uint8_t *) &mark_be, sizeof mark_be,
+                                (uint8_t *) &mask_be, sizeof mask_be)) {
+            match->mark = ntohl(mark_be);
+            match->mark_mask = ntohl(mask_be);
+        } else {
+            ds_put_cstr(ds, "failed to parse mark");
+            return false;
+        }
+        args--;
+    }
+
+    /* Parse labels. */
+    if (args && !strncmp(argv[argc - args], "labels=", 7)) {
+        const char *s = argv[argc - args] + 7;
+        ovs_be128 labels_be;
+        ovs_be128 mask_be;
+
+        if (ofp_ct_masked_parse(s, (uint8_t *) &labels_be, sizeof labels_be,
+                                 (uint8_t *) &mask_be, sizeof mask_be)) {
+            match->labels = ntoh128(labels_be);
+            match->labels_mask = ntoh128(mask_be);
+        } else {
+            ds_put_cstr(ds, "failed to parse labels");
+            return false;
+        }
+        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 = argv[argc - args];
+
+        if (arg[0] && !ofp_ct_tuple_parse(tuple, arg, ds, &match->ip_proto,
+                                          &match->l3_type)) {
+            return false;
+        }
+        args--;
+    }
+
+    if (args > 0) {
+        ds_put_cstr(ds, "invalid arguments");
+        return false;
+    }
+
+    return true;
+}
+
 static enum ofperr
 ofpprop_pull_ipv6(struct ofpbuf *property, struct in6_addr *addr,
                   uint16_t *l3_type)
@@ -377,6 +517,22 @@ ofp_ct_match_decode(struct ofp_ct_match *match, bool 
*with_zone,
             }
             error = ofpprop_parse_u16(&property, zone_id);
             break;
+
+        case NXT_CT_MARK:
+            error = ofpprop_parse_u32(&property, &match->mark);
+            break;
+
+        case NXT_CT_MARK_MASK:
+            error = ofpprop_parse_u32(&property, &match->mark_mask);
+            break;
+
+        case NXT_CT_LABELS:
+            error = ofpprop_parse_u128(&property, &match->labels);
+            break;
+
+        case NXT_CT_LABELS_MASK:
+            error = ofpprop_parse_u128(&property, &match->labels_mask);
+            break;
         }
 
         if (error) {
@@ -405,5 +561,20 @@ ofp_ct_match_encode(const struct ofp_ct_match *match, 
uint16_t *zone_id,
         ofpprop_put_u16(msg, NXT_CT_ZONE_ID, *zone_id);
     }
 
+    if (match->mark) {
+        ofpprop_put_u32(msg, NXT_CT_MARK, match->mark);
+    }
+    if (match->mark_mask) {
+        ofpprop_put_u32(msg, NXT_CT_MARK_MASK, match->mark_mask);
+    }
+
+    if (!ovs_u128_is_zero(match->labels)) {
+        ofpprop_put_u128(msg, NXT_CT_LABELS, match->labels);
+    }
+
+    if (!ovs_u128_is_zero(match->labels_mask)) {
+        ofpprop_put_u128(msg, NXT_CT_LABELS_MASK, match->labels_mask);
+    }
+
     return msg;
 }
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 874079b84..ee767aa42 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -961,7 +961,7 @@ ofp_print_nxt_ct_flush(struct ds *string, const struct 
ofp_header *oh)
         return error;
     }
 
-    ds_put_format(string, " zone=%"PRIu16" ", zone_id);
+    ds_put_format(string, " zone=%"PRIu16, zone_id);
     ofp_ct_match_format(string, &match);
 
     return 0;
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index 14aa55416..474e2deeb 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -4093,6 +4093,62 @@ AT_CHECK([ovs-ofctl ofp-print "\
 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 20 00 00 00 03 00 00 23 20 00 00 00 20 \
+06 \
+00 00 00 00 00 00 00 \
+00 03 00 08 00 00 00 ab \
+"], [0], [dnl
+NXT_CT_FLUSH (xid=0x3): zone=0 mark=0xab/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 04 00 08 00 00 00 cd \
+"], [0], [dnl
+NXT_CT_FLUSH (xid=0x3): zone=0 mark=0/0xcd 
'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 28 00 00 00 03 00 00 23 20 00 00 00 20 \
+06 \
+00 00 00 00 00 00 00 \
+00 03 00 08 00 00 00 ab \
+00 04 00 08 00 00 00 cd \
+"], [0], [dnl
+NXT_CT_FLUSH (xid=0x3): zone=0 mark=0xab/0xcd 
'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 30 00 00 00 03 00 00 23 20 00 00 00 20 \
+06 \
+00 00 00 00 00 00 00 \
+00 05 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ab 00 00 00 00 00 \
+"], [0], [dnl
+NXT_CT_FLUSH (xid=0x3): zone=0 labels=0xffab00/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 30 00 00 00 03 00 00 23 20 00 00 00 20 \
+06 \
+00 00 00 00 00 00 00 \
+00 06 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff cd 00 00 00 00 00 \
+"], [0], [dnl
+NXT_CT_FLUSH (xid=0x3): zone=0 labels=0/0xffcd00 
'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 48 00 00 00 03 00 00 23 20 00 00 00 20 \
+06 \
+00 00 00 00 00 00 00 \
+00 05 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ab 00 00 00 00 00 \
+00 06 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 ff cd 00 00 00 00 00 \
+"], [0], [dnl
+NXT_CT_FLUSH (xid=0x3): zone=0 labels=0xffab00/0xffcd00 
'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 \
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index 8531b2e2e..39afdb1ab 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -3307,5 +3307,37 @@ 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])
 
+AT_CHECK([ovs-ofctl ct-flush br0 mark=0])
+OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 
6])
+AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=0 mark=0" ovs-vswitchd.log])
+
+AT_CHECK([ovs-ofctl ct-flush br0 mark=0/0x5])
+OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 
7])
+AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=0 mark=0/0x5" 
ovs-vswitchd.log])
+
+AT_CHECK([ovs-ofctl ct-flush br0 mark=0xabc/0xdef])
+OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 
8])
+AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=0 mark=0xabc/0xdef" 
ovs-vswitchd.log])
+
+AT_CHECK([ovs-ofctl ct-flush br0 labels=0])
+OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 
9])
+AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=0 labels=0" ovs-vswitchd.log])
+
+AT_CHECK([ovs-ofctl ct-flush br0 labels=0/0x5])
+OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 
10])
+AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=0 labels=0/0x5" 
ovs-vswitchd.log])
+
+AT_CHECK([ovs-ofctl ct-flush br0 labels=0xabc/0xdef])
+OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 
11])
+AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=0 labels=0xabc/0xdef" 
ovs-vswitchd.log])
+
+AT_CHECK([ovs-ofctl ct-flush br0 zone=5 mark=25 labels=25])
+OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 
12])
+AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=5 mark=0x19 labels=0x19" 
ovs-vswitchd.log])
+
+AT_CHECK([ovs-ofctl ct-flush br0 zone=5 mark=30/25 labels=30/25])
+OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush" ovs-vswitchd.log) -eq 
13])
+AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=5 mark=0x1e/0x19 
labels=0x1e/0x19" ovs-vswitchd.log])
+
 OVS_VSWITCHD_STOP
 AT_CLEANUP
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index 418cd32fe..c71e25eac 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -2527,8 +2527,8 @@ ADD_VETH(p1, at_ns1, br0, "10.1.1.2/24")
 AT_DATA([flows.txt], [dnl
 priority=1,action=drop
 priority=10,arp,action=normal
-priority=100,in_port=1,ip,action=ct(commit),2
-priority=100,in_port=2,ip,action=ct(zone=5,commit),1
+priority=100,in_port=1,ip,action=ct(commit,exec(set_field:0xaa->ct_mark)),2
+priority=100,in_port=2,ip,action=ct(zone=5,commit,exec(set_field:0xaa00000000->ct_label)),1
 ])
 
 AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
@@ -2543,7 +2543,7 @@ dnl Test UDP from port 1
 AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 
packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000
 actions=resubmit(,0)"])
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.1,"], 
[], [dnl
-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)
+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),mark=170
 ])
 
 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'])
@@ -2555,7 +2555,7 @@ dnl Test UDP from port 2
 AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 
packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000
 actions=resubmit(,0)"])
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], 
[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
+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,labels=0xaa00000000
 ])
 
 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'])
@@ -2569,7 +2569,7 @@ NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3 -w 2 
10.1.1.1 | FORMAT_PING], [0],
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "orig=.src=10\.1\.1\.2,"], 
[0], [stdout])
 AT_CHECK([cat stdout | FORMAT_CT(10.1.1.1)], [0],[dnl
-icmp,orig=(src=10.1.1.2,dst=10.1.1.1,id=<cleared>,type=8,code=0),reply=(src=10.1.1.1,dst=10.1.1.2,id=<cleared>,type=0,code=0),zone=5
+icmp,orig=(src=10.1.1.2,dst=10.1.1.1,id=<cleared>,type=8,code=0),reply=(src=10.1.1.1,dst=10.1.1.2,id=<cleared>,type=0,code=0),zone=5,labels=0xaa00000000
 ])
 
 ICMP_ID=`cat stdout | cut -d ',' -f4 | cut -d '=' -f2`
@@ -2585,14 +2585,14 @@ AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 
"in_port=2 packet=50540000000a5
 
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], 
[dnl
-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)
-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
+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),mark=170
+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,labels=0xaa00000000
 ])
 
 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
+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,labels=0xaa00000000
 ])
 
 AT_CHECK([FLUSH_CMD 'ct_nw_proto=17,ct_tp_src=2'])
@@ -2605,14 +2605,14 @@ AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 
"in_port=2 packet=50540000000a5
 
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], 
[dnl
-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)
-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
+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),mark=170
+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,labels=0xaa00000000
 ])
 
 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
+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,labels=0xaa00000000
 ])
 
 AT_CHECK([FLUSH_CMD 'ct_nw_proto=17,ct_tp_dst=1'])
@@ -2625,14 +2625,14 @@ AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 
"in_port=2 packet=50540000000a5
 
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], 
[dnl
-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)
-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
+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),mark=170
+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,labels=0xaa00000000
 ])
 
 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
+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,labels=0xaa00000000
 ])
 
 AT_CHECK([FLUSH_CMD 'ct_nw_src=10.1.1.2'])
@@ -2645,14 +2645,14 @@ AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 
"in_port=2 packet=50540000000a5
 
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], 
[dnl
-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)
-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
+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),mark=170
+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,labels=0xaa00000000
 ])
 
 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
+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,labels=0xaa00000000
 ])
 
 AT_CHECK([FLUSH_CMD 'ct_nw_dst=10.1.1.1'])
@@ -2665,14 +2665,14 @@ AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 
"in_port=2 packet=50540000000a5
 
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], 
[dnl
-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)
-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
+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),mark=170
+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,labels=0xaa00000000
 ])
 
 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
+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,labels=0xaa00000000
 ])
 
 AT_CHECK([FLUSH_CMD zone=5 '' 'ct_nw_src=10.1.1.1'])
@@ -2685,8 +2685,8 @@ AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 
"in_port=2 packet=50540000000a5
 
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], 
[dnl
-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)
-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
+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),mark=170
+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,labels=0xaa00000000
 ])
 
 AT_CHECK([FLUSH_CMD])
@@ -2698,46 +2698,80 @@ AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 
"in_port=1 packet=50540000000a5
 AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 
packet=50540000000950540000000a08004500003400010000408464410a0101020a010101000200010000000098f29e470100001470e18ccc00000000000a000a00000000
 actions=resubmit(,0)"])
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sed 
"s/,protoinfo=.*$//" | sort], [0], [dnl
-sctp,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)
-sctp,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
+sctp,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),mark=170
+sctp,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,labels=0xaa00000000
 ])
 
 AT_CHECK([FLUSH_CMD 
'ct_nw_src=10.1.1.1,ct_nw_proto=132,ct_tp_src=1,ct_tp_dst=2'])
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sed 
"s/,protoinfo=.*$//" | sort], [0], [dnl
-sctp,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
+sctp,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,labels=0xaa00000000
 ])
 
 AT_CHECK([FLUSH_CMD 
'ct_nw_src=10.1.1.2,ct_nw_proto=132,ct_tp_src=2,ct_tp_dst=1'])
 
 AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
+
+dnl Test UDP from port 1 and 2, partial flush by mark and labels
+AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 
packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000
 actions=resubmit(,0)"])
+AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 
packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000
 actions=resubmit(,0)"])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], 
[dnl
+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),mark=170
+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,labels=0xaa00000000
 ])
 
-dnl Test flush with invalid arguments
+AT_CHECK([FLUSH_CMD mark=0xaa])
+
+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,labels=0xaa00000000
+])
+
+AT_CHECK([FLUSH_CMD labels=0xaa00000000])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
 
-AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=invalid 'ct_nw_src=10.1.1.1' 
'ct_nw_dst=10.1.1.1'], [2], [ignore], [stderr])
+AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1 
packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000
 actions=resubmit(,0)"])
+AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2 
packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000
 actions=resubmit(,0)"])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1" | sort], [0], 
[dnl
+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),mark=170
+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,labels=0xaa00000000
+])
+
+AT_CHECK([FLUSH_CMD mark=2/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,labels=0xaa00000000
+])
+
+AT_CHECK([FLUSH_CMD labels=0x0200000000/0x0200000000])
+
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
+
+dnl Test flush with invalid arguments.
+
+AT_CHECK([FLUSH_CMD zone=invalid 'ct_nw_src=10.1.1.1' 'ct_nw_dst=10.1.1.1'], 
[ignore], [ignore], [stderr])
 AT_CHECK([grep -q "failed to parse zone" stderr])
 
-AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=1 
'ct_nw_src=10.1.1.1,invalid=invalid' 'ct_nw_dst=10.1.1.1'], [2], [ignore], 
[stderr])
+AT_CHECK([FLUSH_CMD zone=1 'ct_nw_src=10.1.1.1,invalid=invalid' 
'ct_nw_dst=10.1.1.1'], [ignore], [ignore], [stderr])
 AT_CHECK([grep -q "invalid conntrack tuple field: invalid" stderr])
 
-AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=1 'ct_nw_src=invalid' 
'ct_nw_dst=10.1.1.1'], [2], [ignore], [stderr])
+AT_CHECK([FLUSH_CMD zone=1 'ct_nw_src=invalid' 'ct_nw_dst=10.1.1.1'], 
[ignore], [ignore], [stderr])
 AT_CHECK([grep -q "failed to parse field ct_nw_src" stderr])
 
-AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=1 'ct_nw_src=10.1.1.1' 
'ct_nw_dst=10.1.1.1' invalid], [2], [ignore], [stderr])
+AT_CHECK([FLUSH_CMD zone=1 'ct_nw_src=10.1.1.1' 'ct_nw_dst=10.1.1.1' invalid], 
[ignore], [ignore], [stderr])
 AT_CHECK([grep -q "invalid arguments" stderr])
 
-AT_CHECK([ovs-appctl dpctl/flush-conntrack $dp zone=1 'ct_nw_src=10.1.1.1' 
'ct_nw_dst=10.1.1.1' invalid], [2], [ignore], [stderr])
-AT_CHECK([grep -q "command takes at most 4 arguments" stderr])
-
-AT_CHECK([ovs-appctl dpctl/flush-conntrack $dp 'ct_nw_src=10.1.1.1' 
'ct_nw_dst=10.1.1.1' invalid], [2], [ignore], [stderr])
-AT_CHECK([grep -q "invalid arguments" stderr])
+AT_CHECK([FLUSH_CMD zone=1 mark=1 labels=1 'ct_nw_src=10.1.1.1' 
'ct_nw_dst=10.1.1.1' invalid invalid], [ignore], [ignore], [stderr])
+AT_CHECK([grep -q "command takes at most 6 arguments" stderr])
 
-AT_CHECK([ovs-ofctl ct-flush br0 zone=1 'ct_nw_src=10.1.1.1' 
'ct_nw_dst=10.1.1.1' invalid], [1], [ignore], [stderr])
-AT_CHECK([grep -q "command takes at most 4 arguments" stderr])
+AT_CHECK([FLUSH_CMD mark=invalid], [ignore], [ignore], [stderr])
+AT_CHECK([grep -q "failed to parse mark" stderr])
 
-AT_CHECK([ovs-ofctl ct-flush br0 'ct_nw_src=10.1.1.1' 'ct_nw_dst=10.1.1.1' 
invalid], [1], [ignore], [stderr])
-AT_CHECK([grep -q "Invalid arguments" stderr])
+AT_CHECK([FLUSH_CMD labels=invalid], [ignore], [ignore], [stderr])
+AT_CHECK([grep -q "failed to parse labels" stderr])
+])
 
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 0a611b2ee..6344134f3 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -118,7 +118,7 @@ Disables vacancy events.
 .
 .TP
 \fBdump\-ports \fIswitch\fR [\fInetdev\fR]
-Prints to the console statistics for network devices associated with 
+Prints to the console statistics for network devices associated with
 \fIswitch\fR.  If \fInetdev\fR is specified, only the statistics
 associated with that device will be printed.  \fInetdev\fR can be an
 OpenFlow assigned port number or device name, e.g. \fBeth0\fR.
@@ -242,7 +242,7 @@ formatting.  See the descriptions of these options, under
 .TP
 \fBdump\-aggregate \fIswitch \fR[\fIflows\fR]
 Prints to the console aggregate statistics for flows in
-\fIswitch\fR's tables that match \fIflows\fR.  If \fIflows\fR is omitted, 
+\fIswitch\fR's tables that match \fIflows\fR.  If \fIflows\fR is omitted,
 the statistics are aggregated across all flows in the switch's flow
 tables.  See \fBFlow Syntax\fR, below, for the syntax of \fIflows\fR.
 The output format is described in \fBTable Entry Output\fR.
@@ -296,17 +296,20 @@ 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 "\fBct\-flush \fIswitch [zone=N] [zone=N] [mark=X[/M]] [labels=Y[/N]] 
[ct-orig-tuple [ct-reply-tuple]]\fR
+Flushes the connection entries on \fIswitch\fR based on \fIzone\fR,
+\fImark\fR, \fIlabels\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.
+\fIzone\fR. if \fImark\fR or \fIlabels\fR is provided, it will flush
+only entries that are matching specific \fImark/labels\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
+to 0 if it is not provided. The \fImark\fR and \fIlabel\fR defaults to "0/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
@@ -866,10 +869,10 @@ allow priority to be specified.
 .
 .IP \fBpriority=\fIvalue\fR
 The priority at which a wildcarded entry will match in comparison to
-others.  \fIvalue\fR is a number between 0 and 65535, inclusive.  A higher 
-\fIvalue\fR will match before a lower one.  An exact-match entry will always 
-have priority over an entry containing wildcards, so it has an implicit 
-priority value of 65535.  When adding a flow, if the field is not specified, 
+others.  \fIvalue\fR is a number between 0 and 65535, inclusive.  A higher
+\fIvalue\fR will match before a lower one.  An exact-match entry will always
+have priority over an entry containing wildcards, so it has an implicit
+priority value of 65535.  When adding a flow, if the field is not specified,
 the flow's priority will default to 32768.
 .IP
 OpenFlow leaves behavior undefined when two or more flows with the
@@ -941,7 +944,7 @@ Open vSwitch 1.10 added support for \fBno_packet_counts\fR 
and
 \fBno_byte_counts\fR.
 .
 .PP
-The \fBdump\-flows\fR, \fBdump\-aggregate\fR, \fBdel\-flow\fR 
+The \fBdump\-flows\fR, \fBdump\-aggregate\fR, \fBdel\-flow\fR
 and \fBdel\-flows\fR commands support these additional optional fields:
 .
 .TP
@@ -958,8 +961,8 @@ or later.
 .
 .SS "Table Entry Output"
 .
-The \fBdump\-tables\fR and \fBdump\-aggregate\fR commands print information 
-about the entries in a datapath's tables.  Each line of output is a 
+The \fBdump\-tables\fR and \fBdump\-aggregate\fR commands print information
+about the entries in a datapath's tables.  Each line of output is a
 flow entry as described in \fBFlow Syntax\fR, above, plus some
 additional fields:
 .
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 24d0941cf..c5303b49a 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -3060,42 +3060,19 @@ 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;
+    uint16_t zone;
     int args = ctx->argc - 2;
+    bool with_zone = false;
 
-    /* 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--;
-    }
-
-    if (args > 0) {
-        ovs_fatal(0, "Invalid arguments");
+    if (args && !ofp_ct_match_parse((const char **) &ctx->argv[2],
+                                    args, &ds, &match, &with_zone , &zone)) {
+        ovs_fatal(0, "Failed to parse CT match: %s", ds_cstr(&ds));
     }
 
     open_vconn(ctx->argv[1], &vconn);
     enum ofp_version version = vconn_get_version(vconn);
-    struct ofpbuf *msg = ofp_ct_match_encode(&match, pzone, version);
+    struct ofpbuf *msg =
+            ofp_ct_match_encode(&match, with_zone ? &zone : NULL, version);
 
     ds_destroy(&ds);
     transact_noreply(vconn, msg);
@@ -5115,8 +5092,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 },
+    { "ct-flush", "switch [zone=N] [mark=X[/M]] [labels=Y[/N]] "
+                  "[ct-orig-tuple [ct-reply-tuple]]",
+      1, 6, ofctl_ct_flush, OVS_RO },
 
     { "ofp-parse", "file",
       1, 1, ofctl_ofp_parse, OVS_RW },
-- 
2.41.0


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

Reply via email to