diff mbox series

[ovs-dev,v3,3/3] openflow: Allow CT flush to match on mark and labels.

Message ID 20231018062850.67677-3-amusil@redhat.com
State Changes Requested
Delegated to: Ilya Maximets
Headers show
Series [ovs-dev,v3,1/3] ofp-prop: Add helper for parsing and storing of ovs_u128. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/intel-ovs-compilation success test: success

Commit Message

Ales Musil Oct. 18, 2023, 6:28 a.m. UTC
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 <amusil@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(-)

Comments

Ilya Maximets Oct. 24, 2023, 10:25 p.m. UTC | #1
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 <amusil@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 },
Ales Musil Nov. 2, 2023, 9:37 a.m. UTC | #2
On Wed, Oct 25, 2023 at 12:25 AM Ilya Maximets <i.maximets@ovn.org> wrote:

> 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.
>

Hi Ilya,

thank you for the review. It makes sense to report unknown values, it's a
bit unfortunate because now we will probably need an additional feature
flag to indicate that this is supported WDYT?
I'll wait with v4 until the feature flag is resolved.


>
> >
> > 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 <amusil@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?
>

We don't, but I don't see any harm in checking both.


>
> > +}
> > +
> > +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.
>

I'll address that in a separate patch possible with the UNKNOWN error to
make it easily backportable.


> Also, you missed the update for usage() function.
>
> >
> >      { "ofp-parse", "file",
> >        1, 1, ofctl_ofp_parse, OVS_RW },
>
>
Thanks,
Ales
Ilya Maximets Nov. 16, 2023, 2:30 p.m. UTC | #3
On 11/2/23 10:37, Ales Musil wrote:
> 
> 
> On Wed, Oct 25, 2023 at 12:25 AM Ilya Maximets <i.maximets@ovn.org <mailto:i.maximets@ovn.org>> wrote:
> 
>     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.
> 
> 
> Hi Ilya,
> 
> thank you for the review. It makes sense to report unknown values, it's a bit unfortunate because now we will probably need an additional feature flag to indicate that this is supported WDYT?

But it's not new.  If we don't fail than the requested fields will
just be silently ignored, so the controller can't really use the
feature anyway.  You'll need a way to detect support regardless.
If we fail though, the failure itself can be used as a probe.

> I'll wait with v4 until the feature flag is resolved.
>  
> 
> 
>     >
>     > 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 <https://issues.redhat.com/browse/FDP-55>
>     > Signed-off-by: Ales Musil <amusil@redhat.com <mailto:amusil@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 <http://ofp-print.at>            |  56 +++++++++++++
>     >  tests/ovs-ofctl.at <http://ovs-ofctl.at>            |  32 +++++++
>     >  tests/system-traffic.at <http://system-traffic.at>       | 112 ++++++++++++++++---------
>     >  utilities/ovs-ofctl.8.in <http://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?
> 
> 
> We don't, but I don't see any harm in checking both.

These checks do nothing useful and we intriduce 2 extra functions
in order to have them.  If we do not check the value we may remove
these functions.

Best regards, Ilya Maximets.
diff mbox series

Patch

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;
+}
+
+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)
 {
+    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);
+        }
+    }
+
+    /* 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);
+    }
+
     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'
+])
+
 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 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
+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
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 },
 
     { "ofp-parse", "file",
       1, 1, ofctl_ofp_parse, OVS_RW },