On 10/18/23 08:28, Ales Musil wrote:
> 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.

Hmm.  Just noticed that.  This doesn't seem right.  If unknown
property is passed, OVS should fail with OFPPROP_UNKNOWN().
This probably should be a separate fix that we'll need to
backport to stable versions.  If user requests flushing a
specific label, we should not flush everything just because
we do not understand the request.

Some more comments inline.

Best regards, Ilya Maximets.

> 
> 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>
> ---
> v3: Rebase on top of current master.
> v2: Make sure that the mask decoding matches the dpctl/ovs-ofctl interface.
> ---
>  include/openflow/nicira-ext.h |   4 +
>  include/openvswitch/ofp-ct.h  |   9 +-
>  lib/ct-dpif.c                 |  12 ++-
>  lib/dpctl.c                   |   5 +-
>  lib/ofp-ct.c                  | 151 +++++++++++++++++++++++++++++++++-
>  tests/ofp-print.at            |  56 +++++++++++++
>  tests/ovs-ofctl.at            |  32 +++++++
>  tests/system-traffic.at       | 112 ++++++++++++++++---------
>  utilities/ovs-ofctl.8.in      |  13 +--
>  utilities/ovs-ofctl.c         |   5 +-
>  10 files changed, 344 insertions(+), 55 deletions(-)

The change needs a NEWS entry.

> 
> 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 cd6192e6f..d57b62678 100644
> --- a/include/openvswitch/ofp-ct.h
> +++ b/include/openvswitch/ofp-ct.h
> @@ -51,11 +51,16 @@ 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_match_parse(const char **, int argc, struct ds *,
> diff --git a/lib/ct-dpif.c b/lib/ct-dpif.c
> index f59c6e560..0fd14b99f 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;
>  }
>  
> @@ -295,8 +304,7 @@ ct_dpif_flush_tuple(struct dpif *dpif, const uint16_t 
> *zone,
>  
>      /* 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 bbab5881e..9d28a91ba 100644
> --- a/lib/dpctl.c
> +++ b/lib/dpctl.c
> @@ -2981,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 32aeb5455..344f7a0b2 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,17 +75,63 @@ 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;

If the mask is zero, why do we care about the value?

> +}
> +
> +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);

ditto.

> +}
> +
> +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)
>  {
> +    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);
> +        }
> +        ds_put_char(ds, ' ');
> +    }
> +
> +    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_char(ds, ' ');
> +    }
> +
>      ds_put_cstr(ds, "'");
>      ofp_ct_tuple_format(ds, &match->tuple_orig, match->ip_proto,
>                          match->l3_type);
> @@ -95,6 +141,23 @@ 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'. */
> @@ -236,6 +299,40 @@ ofp_ct_match_parse(const char **argv, int argc, struct 
> ds *ds,
>          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) {
> @@ -382,6 +479,7 @@ enum ofperr
>  ofp_ct_match_decode(struct ofp_ct_match *match, bool *with_zone,
>                      uint16_t *zone_id, const struct ofp_header *oh)
>  {
> +    uint32_t tlv_flags = 0;
>      struct ofpbuf msg = ofpbuf_const_initializer(oh, ntohs(oh->length));
>      ofpraw_pull_assert(&msg);
>  
> @@ -422,11 +520,43 @@ 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) {
>              return error;
>          }
> +
> +        if (type < (sizeof tlv_flags * CHAR_BIT)) {
> +            tlv_flags |= (1 << type);

The right size of this expression will have the type 'int', which is
signed.  Left side is unsigned.  Better use UINT32_C(1).
Same below.

> +        }
> +    }
> +
> +    /* Consider the mask being all ones if it's not present but the value
> +     * is specified. */
> +    if (tlv_flags & (1 << NXT_CT_MARK) &&
> +        !(tlv_flags & (1 << NXT_CT_MARK_MASK))) {
> +        match->mark_mask = UINT32_MAX;
> +    }
> +
> +    if (tlv_flags & (1 << NXT_CT_LABELS) &&
> +        !(tlv_flags & (1 << NXT_CT_LABELS_MASK))) {
> +        match->labels_mask = OVS_U128_MAX;
>      }
>  
>      return 0;
> @@ -450,5 +580,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);
> +    }

These seem to be not very user-friendly.  There is no way to distinguish
a zero value for a non-provided one.  Users should, probbaly, always
provide a mask here, and so the function will only check for a mask
to be non-zero and put both fields.  In case of a full mask, the mask
itself can probably be omitted from the mesage.

> +
>      return msg;
>  }
> diff --git a/tests/ofp-print.at b/tests/ofp-print.at
> index 14aa55416..b96ad1fba 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 
> '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 
> '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'
> +])
> +

Maybe add a case with both mark and labeles together?
Might also make sense to have some inproperly fomatted message.

>  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])
> +

Same here.

>  OVS_VSWITCHD_STOP
>  AT_CLEANUP
> diff --git a/tests/system-traffic.at b/tests/system-traffic.at
> index ec65ca5de..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
> +])
> +
> +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-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
>  ])
>  
> -dnl Test flush with invalid arguments
> +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([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([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([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-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([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..9995895ca 100644
> --- a/utilities/ovs-ofctl.8.in
> +++ b/utilities/ovs-ofctl.8.in
> @@ -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

The zone is repeated twice.

> +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"

label*s*

> +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
> diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
> index 79d42dd0b..de734e9f5 100644
> --- a/utilities/ovs-ofctl.c
> +++ b/utilities/ovs-ofctl.c
> @@ -5092,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 },

Not an issue of this patch, but these commands should be RW.

Also, you missed the update for usage() function.

>  
>      { "ofp-parse", "file",
>        1, 1, ofctl_ofp_parse, OVS_RW },


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

Reply via email to