On Mon, Jan 16, 2023 at 12:45 PM Ales Musil <amu...@redhat.com> wrote:

> Add extension that allows to flush connections from CT
> by specifying fields that the connections should be
> matched against. This allows to match only some fields
> of the connection e.g. source address for orig direction.
>
> Reported-at: https://bugzilla.redhat.com/2120546
> Signed-off-by: Ales Musil <amu...@redhat.com>
> ---
> v7: Rebase on top of current master.
>     Address comments from Ilya:
>     * Use the public header for encode/decode functions.
>     * Adjust the code accordingly to the new header file.
>     * Address some style related comments.
>     * Rename the nested TLV, so they do not overlap with
>       the outer types.
>     * Document the ICMP partial match limitation.
>
> v6: Rebase on top of current master.
>     Address comments from Ilya.
> v5: Add missing usage and man for ovs-ofctl command.
> v4: Allow ovs-ofctl flush/conntrack without any zone/tuple.
> v3: Rebase on top of master.
> v2: Rebase on top of master.
>     Use suggestion from Ilya.
> ---
>  NEWS                           |   6 +
>  include/openflow/nicira-ext.h  |  37 +++++++
>  include/openvswitch/ofp-ct.h   |  10 ++
>  include/openvswitch/ofp-msgs.h |   4 +
>  lib/ofp-bundle.c               |   1 +
>  lib/ofp-ct.c                   | 196 +++++++++++++++++++++++++++++++++
>  lib/ofp-print.c                |  20 ++++
>  lib/rconn.c                    |   1 +
>  ofproto/ofproto-dpif.c         |   9 +-
>  ofproto/ofproto-provider.h     |   7 +-
>  ofproto/ofproto.c              |  29 ++++-
>  tests/ofp-print.at             | 108 ++++++++++++++++++
>  tests/ovs-ofctl.at             |  38 +++++++
>  tests/system-traffic.at        |  38 ++++---
>  utilities/ovs-ofctl.8.in       |  27 +++++
>  utilities/ovs-ofctl.c          |  51 +++++++++
>  16 files changed, 560 insertions(+), 22 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 035fcb0ee..22840402c 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -36,6 +36,12 @@ Post-v3.0.0
>     - ovs-dpctl and 'ovs-appctl dpctl/' commands:
>       * "flush-conntrack" is now capable of handling partial 5-tuple,
>          with additional optional parameter to specify the reply direction.
> +   - OpenFlow:
> +     * New OpenFlow extension NXT_CT_FLUSH to flush connections matching
> +       the specified fields.
> +   - ovs-ofctl:
> +     * New command "flush-conntrack" that accepts zone and 5-tuple or
> partial
> +       5-tuple for both directions.
>
>
>  v3.0.0 - 15 Aug 2022
> diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
> index b68804991..35f66a76d 100644
> --- a/include/openflow/nicira-ext.h
> +++ b/include/openflow/nicira-ext.h
> @@ -1064,4 +1064,41 @@ struct nx_zone_id {
>  };
>  OFP_ASSERT(sizeof(struct nx_zone_id) == 8);
>
> +/* CT flush available TLVs. */
> +enum nx_ct_flush_tlv_type {
> +    /* Outer types. */
> +    NXT_CT_ORIG_TUPLE = 0,     /* Outer type for original tuple TLV.
> +                                * The nested TLV are specified
> +                                * by 'enum nx_ct_flush_tuple_tlv_type'. */
> +    NXT_CT_REPLY_TUPLE = 1,    /* Outer type for reply tuple TLC. *
> +                                * The nested TLV are specified
> +                                * by 'enum nx_ct_flush_tuple_tlv_type'*/
> +    /* Primitive types. */
> +    NXT_CT_ZONE_ID = 2,        /* be16 zone id. */
> +};
> +
> +/* CT flush nested TLVs. */
> +enum nx_ct_flush_tuple_tlv_type {
> +    NXT_CT_TUPLE_SRC = 0,            /* IPv6 or mapped IPv4 address. */
> +    NXT_CT_TUPLE_DST = 1,            /* IPv6 or mapped IPv4 address. */
> +    NXT_CT_TUPLE_SRC_PORT = 2,       /* be16 source port. */
> +    NXT_CT_TUPLE_DST_PORT = 3,       /* be16 destination port. */
> +    NXT_CT_TUPLE_ICMP_ID = 4,       /* be16 ICMP id. */
> +    NXT_CT_TUPLE_ICMP_TYPE = 5,     /* u8 ICMP type. */
> +    NXT_CT_TUPLE_ICMP_CODE = 6,     /* u8 ICMP code. */
> +};
> +
> +/* NXT_CT_FLUSH.
> + *
> + * Flushes the connection tracking specified by 5-tuple.
> + * The struct should be followed by TLVs specifying the matching
> parameters.
> + * Currently there is a limitation for ICMP, in order to partially match
> on
> + * ICMP parameters the tuple should include at least SRC/DST. */
> +struct nx_ct_flush {
> +    uint8_t ip_proto;          /* IP protocol. */
> +    uint8_t pad[7];           /* Must be zero. */
> +    /* Followed by optional TLVs of type 'enum nx_ct_flush_tlv_type'. */
> +};
> +OFP_ASSERT(sizeof(struct nx_ct_flush) == 8);
> +
>  #endif /* openflow/nicira-ext.h */
> diff --git a/include/openvswitch/ofp-ct.h b/include/openvswitch/ofp-ct.h
> index e7af45337..094da0a97 100644
> --- a/include/openvswitch/ofp-ct.h
> +++ b/include/openvswitch/ofp-ct.h
> @@ -21,6 +21,8 @@
>  #include <sys/types.h>
>  #include <netinet/in.h>
>
> +#include "openflow/nicira-ext.h"
> +
>  #ifdef __cplusplus
>  extern "C" {
>  #endif
> @@ -63,6 +65,14 @@ bool ofp_ct_tuple_parse(struct ofp_ct_tuple *tuple,
> const char *s,
>
>  bool ofp_ct_is_match_zero(const struct ofp_ct_match *match);
>
> +enum ofperr ofp_ct_match_decode(struct ofp_ct_match *match, bool
> *with_zone,
> +                                uint16_t *zone_id,
> +                                const struct ofp_header *oh);
> +
> +struct ofpbuf * ofp_ct_match_encode(const struct ofp_ct_match *match,
> +                                    uint16_t *zone_id,
> +                                    enum ofp_version version);
> +
>  #ifdef __cplusplus
>  }
>  #endif
> diff --git a/include/openvswitch/ofp-msgs.h
> b/include/openvswitch/ofp-msgs.h
> index 921a937e5..708427fc0 100644
> --- a/include/openvswitch/ofp-msgs.h
> +++ b/include/openvswitch/ofp-msgs.h
> @@ -515,6 +515,9 @@ enum ofpraw {
>      /* NXT 1.0+ (29): struct nx_zone_id. */
>      OFPRAW_NXT_CT_FLUSH_ZONE,
>
> +    /* NXT 1.0+ (32): struct nx_ct_flush, uint8_t[8][]. */
> +    OFPRAW_NXT_CT_FLUSH,
> +
>      /* NXST 1.0+ (3): void. */
>      OFPRAW_NXST_IPFIX_BRIDGE_REQUEST,
>
> @@ -772,6 +775,7 @@ enum ofptype {
>      OFPTYPE_IPFIX_FLOW_STATS_REQUEST, /* OFPRAW_NXST_IPFIX_FLOW_REQUEST */
>      OFPTYPE_IPFIX_FLOW_STATS_REPLY,   /* OFPRAW_NXST_IPFIX_FLOW_REPLY */
>      OFPTYPE_CT_FLUSH_ZONE,            /* OFPRAW_NXT_CT_FLUSH_ZONE. */
> +    OFPTYPE_CT_FLUSH,                 /* OFPRAW_NXT_CT_FLUSH. */
>
>      /* Flow monitor extension. */
>      OFPTYPE_FLOW_MONITOR_CANCEL,  /* OFPRAW_NXT_FLOW_MONITOR_CANCEL.
> diff --git a/lib/ofp-bundle.c b/lib/ofp-bundle.c
> index 0161c2bc6..941a8370e 100644
> --- a/lib/ofp-bundle.c
> +++ b/lib/ofp-bundle.c
> @@ -292,6 +292,7 @@ ofputil_is_bundlable(enum ofptype type)
>      case OFPTYPE_IPFIX_FLOW_STATS_REQUEST:
>      case OFPTYPE_IPFIX_FLOW_STATS_REPLY:
>      case OFPTYPE_CT_FLUSH_ZONE:
> +    case OFPTYPE_CT_FLUSH:
>          break;
>      }
>
> diff --git a/lib/ofp-ct.c b/lib/ofp-ct.c
> index 61f0be953..a81d9f0bc 100644
> --- a/lib/ofp-ct.c
> +++ b/lib/ofp-ct.c
> @@ -23,8 +23,12 @@
>
>  #include "ct-dpif.h"
>  #include "openvswitch/ofp-ct.h"
> +#include "openflow/nicira-ext.h"
>  #include "openvswitch/dynamic-string.h"
> +#include "openvswitch/ofp-msgs.h"
>  #include "openvswitch/ofp-parse.h"
> +#include "openvswitch/ofp-errors.h"
> +#include "openvswitch/ofp-prop.h"
>  #include "openvswitch/ofp-util.h"
>  #include "openvswitch/packets.h"
>
> @@ -212,3 +216,195 @@ error:
>      free(copy);
>      return false;
>  }
> +
> +static enum ofperr
> +ofpprop_pull_ipv6(struct ofpbuf *property, struct in6_addr *addr,
> +                  uint16_t *l3_type)
> +{
> +    if (ofpbuf_msgsize(property) < sizeof *addr) {
> +        return OFPERR_OFPBPC_BAD_LEN;
> +    }
> +
> +    memcpy(addr, property->msg, sizeof *addr);
> +
> +    uint16_t l3 = 0;
> +    if (!ipv6_is_zero(addr)) {
> +        l3 = IN6_IS_ADDR_V4MAPPED(addr) ? AF_INET : AF_INET6;
> +    }
> +
> +    if (*l3_type && l3 && *l3_type != l3) {
> +        return OFPERR_OFPBPC_BAD_VALUE;
> +    }
> +
> +    *l3_type = l3;
> +
> +    return 0;
> +}
> +
> +static enum ofperr
> +ofp_ct_tuple_decode_nested(struct ofpbuf *property, struct ofp_ct_tuple
> *tuple,
> +                           uint16_t *l3_type)
> +{
> +    struct ofpbuf nested;
> +    enum ofperr error = ofpprop_parse_nested(property, &nested);
> +    if (error) {
> +        return error;
> +    }
> +
> +    while (nested.size) {
> +        struct ofpbuf inner;
> +        uint64_t type;
> +
> +        error = ofpprop_pull(&nested, &inner, &type);
> +        if (error) {
> +            return error;
> +        }
> +        switch (type) {
> +        case NXT_CT_TUPLE_SRC:
> +            error = ofpprop_pull_ipv6(&inner, &tuple->src, l3_type);
> +            break;
> +
> +        case NXT_CT_TUPLE_DST:
> +            error = ofpprop_pull_ipv6(&inner, &tuple->dst, l3_type);
> +            break;
> +
> +        case NXT_CT_TUPLE_SRC_PORT:
> +            error = ofpprop_parse_be16(&inner, &tuple->src_port);
> +            break;
> +
> +        case NXT_CT_TUPLE_DST_PORT:
> +            error = ofpprop_parse_be16(&inner, &tuple->dst_port);
> +            break;
> +
> +        case NXT_CT_TUPLE_ICMP_ID:
> +            error = ofpprop_parse_be16(&inner, &tuple->icmp_id);
> +            break;
> +
> +        case NXT_CT_TUPLE_ICMP_TYPE:
> +            error = ofpprop_parse_u8(&inner, &tuple->icmp_type);
> +            break;
> +
> +        case NXT_CT_TUPLE_ICMP_CODE:
> +            error = ofpprop_parse_u8(&inner, &tuple->icmp_code);
> +            break;
> +        }
> +
> +        if (error) {
> +            return error;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +static void
> +ofp_ct_tuple_encode(const struct ofp_ct_tuple *tuple, struct ofpbuf *buf,
> +                    enum nx_ct_flush_tlv_type type, uint8_t ip_proto)
> +{
> +    /* 128 B is enough to hold the whole tuple. */
> +    uint8_t stub[128];
> +    struct ofpbuf nested = OFPBUF_STUB_INITIALIZER(stub);
> +
> +    if (!ipv6_is_zero(&tuple->src)) {
> +        ofpprop_put(&nested, NXT_CT_TUPLE_SRC, &tuple->src, sizeof
> tuple->src);
> +    }
> +
> +    if (!ipv6_is_zero(&tuple->dst)) {
> +        ofpprop_put(&nested, NXT_CT_TUPLE_DST, &tuple->dst, sizeof
> tuple->dst);
> +    }
> +
> +    if (ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6) {
> +        ofpprop_put_be16(&nested, NXT_CT_TUPLE_ICMP_ID, tuple->icmp_id);
> +        ofpprop_put_u8(&nested, NXT_CT_TUPLE_ICMP_TYPE, tuple->icmp_type);
> +        ofpprop_put_u8(&nested, NXT_CT_TUPLE_ICMP_CODE, tuple->icmp_code);
> +    } else {
> +        if (tuple->src_port) {
> +            ofpprop_put_be16(&nested, NXT_CT_TUPLE_SRC_PORT,
> tuple->src_port);
> +        }
> +
> +        if (tuple->dst_port) {
> +            ofpprop_put_be16(&nested, NXT_CT_TUPLE_DST_PORT,
> tuple->dst_port);
> +        }
> +    }
> +
> +    if (nested.size) {
> +        ofpprop_put_nested(buf, type, &nested);
> +    }
> +
> +    ofpbuf_uninit(&nested);
> +}
> +
> +enum ofperr
> +ofp_ct_match_decode(struct ofp_ct_match *match, bool *with_zone,
> +                    uint16_t *zone_id, const struct ofp_header *oh)
> +{
> +    struct ofpbuf msg = ofpbuf_const_initializer(oh, ntohs(oh->length));
> +    ofpraw_pull_assert(&msg);
> +
> +    const struct nx_ct_flush *nx_flush = ofpbuf_pull(&msg, sizeof
> *nx_flush);
> +
> +    if (!is_all_zeros(nx_flush->pad, sizeof nx_flush->pad)) {
> +        return OFPERR_NXBRC_MUST_BE_ZERO;
> +    }
> +
> +    match->ip_proto = nx_flush->ip_proto;
> +
> +    struct ofp_ct_tuple *orig = &match->tuple_orig;
> +    struct ofp_ct_tuple *reply = &match->tuple_reply;
> +
> +    while (msg.size) {
> +        struct ofpbuf property;
> +        uint64_t type;
> +
> +        enum ofperr error = ofpprop_pull(&msg, &property, &type);
> +        if (error) {
> +            return error;
> +        }
> +
> +        switch (type) {
> +        case NXT_CT_ORIG_TUPLE:
> +            error = ofp_ct_tuple_decode_nested(&property, orig,
> +                                               &match->l3_type);
> +            break;
> +
> +        case NXT_CT_REPLY_TUPLE:
> +            error = ofp_ct_tuple_decode_nested(&property, reply,
> +                                               &match->l3_type);
> +            break;
> +
> +        case NXT_CT_ZONE_ID:
> +            if (with_zone) {
> +                *with_zone = true;
> +            }
> +            error = ofpprop_parse_u16(&property, zone_id);
> +            break;
> +        }
> +
> +        if (error) {
> +            return error;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +struct ofpbuf *
> +ofp_ct_match_encode(const struct ofp_ct_match *match, uint16_t *zone_id,
> +                    enum ofp_version version)
> +{
> +    struct ofpbuf *msg = ofpraw_alloc(OFPRAW_NXT_CT_FLUSH, version, 0);
> +    struct nx_ct_flush *nx_flush = ofpbuf_put_zeros(msg, sizeof
> *nx_flush);
> +    const struct ofp_ct_tuple *orig = &match->tuple_orig;
> +    const struct ofp_ct_tuple *reply = &match->tuple_reply;
> +
> +    nx_flush->ip_proto = match->ip_proto;
> +
> +    ofp_ct_tuple_encode(orig, msg, NXT_CT_ORIG_TUPLE,match->ip_proto);
> +    ofp_ct_tuple_encode(reply, msg, NXT_CT_REPLY_TUPLE, match->ip_proto);
> +
> +    if (zone_id) {
> +        ofpprop_put_u16(msg, NXT_CT_ZONE_ID, *zone_id);
> +    }
> +
> +    return msg;
> +}
> diff --git a/lib/ofp-print.c b/lib/ofp-print.c
> index bd37fa17a..874079b84 100644
> --- a/lib/ofp-print.c
> +++ b/lib/ofp-print.c
> @@ -45,6 +45,7 @@
>  #include "openvswitch/ofp-actions.h"
>  #include "openvswitch/ofp-bundle.h"
>  #include "openvswitch/ofp-connection.h"
> +#include "openvswitch/ofp-ct.h"
>  #include "openvswitch/ofp-errors.h"
>  #include "openvswitch/ofp-group.h"
>  #include "openvswitch/ofp-ipfix.h"
> @@ -949,6 +950,23 @@ ofp_print_nxt_ct_flush_zone(struct ds *string, const
> struct nx_zone_id *nzi)
>      return 0;
>  }
>
> +static enum ofperr
> +ofp_print_nxt_ct_flush(struct ds *string, const struct ofp_header *oh)
> +{
> +    uint16_t zone_id = 0;
> +    struct ofp_ct_match match = {0};
> +
> +    enum ofperr error = ofp_ct_match_decode(&match, NULL, &zone_id, oh);
> +    if (error) {
> +        return error;
> +    }
> +
> +    ds_put_format(string, " zone=%"PRIu16" ", zone_id);
> +    ofp_ct_match_format(string, &match);
> +
> +    return 0;
> +}
> +
>  static enum ofperr
>  ofp_to_string__(const struct ofp_header *oh,
>                  const struct ofputil_port_map *port_map,
> @@ -1184,6 +1202,8 @@ ofp_to_string__(const struct ofp_header *oh,
>
>      case OFPTYPE_CT_FLUSH_ZONE:
>          return ofp_print_nxt_ct_flush_zone(string, ofpmsg_body(oh));
> +    case OFPTYPE_CT_FLUSH:
> +        return ofp_print_nxt_ct_flush(string, oh);
>      }
>
>      return 0;
> diff --git a/lib/rconn.c b/lib/rconn.c
> index a96b2eb8b..4afa21515 100644
> --- a/lib/rconn.c
> +++ b/lib/rconn.c
> @@ -1426,6 +1426,7 @@ is_admitted_msg(const struct ofpbuf *b)
>      case OFPTYPE_IPFIX_FLOW_STATS_REQUEST:
>      case OFPTYPE_IPFIX_FLOW_STATS_REPLY:
>      case OFPTYPE_CT_FLUSH_ZONE:
> +    case OFPTYPE_CT_FLUSH:
>      default:
>          return true;
>      }
> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> index f9562dee8..f87e27a8c 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -5358,11 +5358,12 @@ type_set_config(const char *type, const struct
> smap *other_config)
>  }
>
>  static void
> -ct_flush(const struct ofproto *ofproto_, const uint16_t *zone)
> +ct_flush(const struct ofproto *ofproto_, const uint16_t *zone,
> +         const struct ofp_ct_match *match)
>  {
>      struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
>
> -    ct_dpif_flush(ofproto->backer->dpif, zone, NULL);
> +    ct_dpif_flush(ofproto->backer->dpif, zone, match);
>  }
>
>  static struct ct_timeout_policy *
> @@ -5674,6 +5675,10 @@ get_datapath_cap(const char *datapath_type, struct
> smap *cap)
>      smap_add(cap, "lb_output_action", s.lb_output_action ? "true" :
> "false");
>      smap_add(cap, "ct_zero_snat", s.ct_zero_snat ? "true" : "false");
>      smap_add(cap, "add_mpls", s.add_mpls ? "true" : "false");
> +
> +    /* The ct_tuple_flush is implemented on dpif level, so it is supported
> +     * for all backers. */
> +    smap_add(cap, "ct_flush", "true");
>  }
>
>  /* Gets timeout policy name in 'backer' based on 'zone', 'dl_type' and
> diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
> index 7e3fb6698..a84ddc1d0 100644
> --- a/ofproto/ofproto-provider.h
> +++ b/ofproto/ofproto-provider.h
> @@ -42,6 +42,7 @@
>  #include "ofproto/ofproto.h"
>  #include "openvswitch/list.h"
>  #include "openvswitch/ofp-actions.h"
> +#include "openvswitch/ofp-ct.h"
>  #include "openvswitch/ofp-errors.h"
>  #include "openvswitch/ofp-flow.h"
>  #include "openvswitch/ofp-group.h"
> @@ -1902,8 +1903,10 @@ struct ofproto_class {
>  /* ## Connection tracking ## */
>  /* ## ------------------- ## */
>      /* Flushes the connection tracking tables. If 'zone' is not NULL,
> -     * only deletes connections in '*zone'. */
> -    void (*ct_flush)(const struct ofproto *, const uint16_t *zone);
> +     * only deletes connections in '*zone'. If 'match' is not NULL,
> +     * deletes connections specified by the match. */
> +    void (*ct_flush)(const struct ofproto *, const uint16_t *zone,
> +                     const struct ofp_ct_match *match);
>
>      /* Sets conntrack timeout policy specified by 'timeout_policy' to
> 'zone'
>       * in datapath type 'dp_type'. */
> diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
> index 3a527683c..17f636ed9 100644
> --- a/ofproto/ofproto.c
> +++ b/ofproto/ofproto.c
> @@ -42,6 +42,7 @@
>  #include "openvswitch/meta-flow.h"
>  #include "openvswitch/ofp-actions.h"
>  #include "openvswitch/ofp-bundle.h"
> +#include "openvswitch/ofp-ct.h"
>  #include "openvswitch/ofp-errors.h"
>  #include "openvswitch/ofp-match.h"
>  #include "openvswitch/ofp-msgs.h"
> @@ -934,7 +935,30 @@ handle_nxt_ct_flush_zone(struct ofconn *ofconn, const
> struct ofp_header *oh)
>
>      uint16_t zone = ntohs(nzi->zone_id);
>      if (ofproto->ofproto_class->ct_flush) {
> -        ofproto->ofproto_class->ct_flush(ofproto, &zone);
> +        ofproto->ofproto_class->ct_flush(ofproto, &zone, NULL);
> +    } else {
> +        return EOPNOTSUPP;
> +    }
> +
> +    return 0;
> +}
> +
> +static enum ofperr
> +handle_nxt_ct_flush(struct ofconn *ofconn, const struct ofp_header *oh)
> +{
> +    struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
> +    struct ofp_ct_match match = {0};
> +    bool with_zone = false;
> +    uint16_t zone_id = 0;
> +
> +    enum ofperr error = ofp_ct_match_decode(&match, &with_zone, &zone_id,
> oh);
> +    if (error) {
> +        return error;
> +    }
> +
> +    if (ofproto->ofproto_class->ct_flush) {
> +        ofproto->ofproto_class->ct_flush(ofproto, with_zone ? &zone_id :
> NULL,
> +                                         &match);
>      } else {
>          return EOPNOTSUPP;
>      }
> @@ -8787,6 +8811,9 @@ handle_single_part_openflow(struct ofconn *ofconn,
> const struct ofp_header *oh,
>      case OFPTYPE_CT_FLUSH_ZONE:
>          return handle_nxt_ct_flush_zone(ofconn, oh);
>
> +    case OFPTYPE_CT_FLUSH:
> +        return handle_nxt_ct_flush(ofconn, oh);
> +
>      case OFPTYPE_HELLO:
>      case OFPTYPE_ERROR:
>      case OFPTYPE_FEATURES_REPLY:
> diff --git a/tests/ofp-print.at b/tests/ofp-print.at
> index fe41cc42c..14aa55416 100644
> --- a/tests/ofp-print.at
> +++ b/tests/ofp-print.at
> @@ -4073,3 +4073,111 @@ AT_CHECK([ovs-ofctl ofp-print "\
>  NXT_CT_FLUSH_ZONE (xid=0x3): zone_id=13
>  ])
>  AT_CLEANUP
> +
> +AT_SETUP([NXT_CT_FLUSH])
> +AT_KEYWORDS([ofp-print])
> +AT_CHECK([ovs-ofctl ofp-print "\
> +01 04 00 18 00 00 00 03 00 00 23 20 00 00 00 20 \
> +06 \
> +00 00 00 00 00 00 00 \
> +"], [0], [dnl
> +NXT_CT_FLUSH (xid=0x3): zone=0
> 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6'
> 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
> +])
> +
> +AT_CHECK([ovs-ofctl ofp-print "\
> +01 04 00 20 00 00 00 03 00 00 23 20 00 00 00 20 \
> +06 \
> +00 00 00 00 00 00 00 \
> +00 02 00 08 00 0d 00 00 \
> +"], [0], [dnl
> +NXT_CT_FLUSH (xid=0x3): zone=13
> 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6'
> 'ct_ipv6_src=::,ct_ipv6_dst=::,ct_tp_src=0,ct_tp_dst=0'
> +])
> +
> +AT_CHECK([ovs-ofctl ofp-print "\
> +01 04 00 68 00 00 00 03 00 00 23 20 00 00 00 20 \
> +06 \
> +00 00 00 00 00 00 00 \
> +00 02 00 08 00 0d 00 00 \
> +00 00 00 48 00 00 00 00 \
> +00 00 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \
> +00 01 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \
> +00 02 00 08 00 50 00 00 \
> +00 03 00 08 1f 90 00 00 \
> +"], [0], [dnl
> +NXT_CT_FLUSH (xid=0x3): zone=13
> 'ct_nw_src=10.10.0.1,ct_nw_dst=10.10.0.2,ct_tp_src=80,ct_tp_dst=8080,ct_nw_proto=6'
> 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0'
> +])
> +
> +AT_CHECK([ovs-ofctl ofp-print "\
> +01 04 00 68 00 00 00 03 00 00 23 20 00 00 00 20 \
> +06 \
> +00 00 00 00 00 00 00 \
> +00 02 00 08 00 0d 00 00 \
> +00 01 00 48 00 00 00 00 \
> +00 01 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \
> +00 00 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \
> +00 03 00 08 00 50 00 00 \
> +00 02 00 08 1f 90 00 00 \
> +"], [0], [dnl
> +NXT_CT_FLUSH (xid=0x3): zone=13
> 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=6'
> 'ct_nw_src=10.10.0.2,ct_nw_dst=10.10.0.1,ct_tp_src=8080,ct_tp_dst=80'
> +])
> +
> +AT_CHECK([ovs-ofctl ofp-print "\
> +01 04 00 b0 00 00 00 03 00 00 23 20 00 00 00 20 \
> +06 \
> +00 00 00 00 00 00 00 \
> +00 02 00 08 00 0d 00 00 \
> +00 00 00 48 00 00 00 00 \
> +00 00 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \
> +00 01 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \
> +00 02 00 08 00 50 00 00 \
> +00 03 00 08 1f 90 00 00 \
> +00 01 00 48 00 00 00 00 \
> +00 01 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \
> +00 00 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \
> +00 03 00 08 00 50 00 00 \
> +00 02 00 08 1f 90 00 00 \
> +"], [0], [dnl
> +NXT_CT_FLUSH (xid=0x3): zone=13
> 'ct_nw_src=10.10.0.1,ct_nw_dst=10.10.0.2,ct_tp_src=80,ct_tp_dst=8080,ct_nw_proto=6'
> 'ct_nw_src=10.10.0.2,ct_nw_dst=10.10.0.1,ct_tp_src=8080,ct_tp_dst=80'
> +])
> +
> +AT_CHECK([ovs-ofctl ofp-print "\
> +01 04 00 b8 00 00 00 03 00 00 23 20 00 00 00 20 \
> +01 \
> +00 00 00 00 00 00 00 \
> +00 00 00 50 00 00 00 00 \
> +00 00 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 01 00 00 00 00 \
> +00 01 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 02 00 00 00 00 \
> +00 04 00 08 00 0a 00 00 \
> +00 05 00 05 01 00 00 00 \
> +00 06 00 05 02 00 00 00 \
> +00 01 00 50 00 00 00 00 \
> +00 01 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 02 00 00 00 00 \
> +00 00 00 14 fd 18 00 00 00 00 00 00 00 00 ff ff ab cd 00 01 00 00 00 00 \
> +00 04 00 08 00 0a 00 00 \
> +00 05 00 05 03 00 00 00 \
> +00 06 00 05 04 00 00 00 \
> +"], [0], [dnl
> +NXT_CT_FLUSH (xid=0x3): zone=0
> 'ct_ipv6_src=fd18::ffff:abcd:1,ct_ipv6_dst=fd18::ffff:abcd:2,icmp_id=10,icmp_type=1,icmp_code=2,ct_nw_proto=1'
> 'ct_ipv6_src=fd18::ffff:abcd:1,ct_ipv6_dst=fd18::ffff:abcd:2,icmp_id=10,icmp_type=3,icmp_code=4'
> +])
> +
> +AT_CHECK([ovs-ofctl ofp-print "\
> +01 04 00 58 00 00 00 03 00 00 23 20 00 00 00 20 \
> +06 \
> +00 00 00 00 00 00 00 \
> +00 02 00 08 00 0d 00 00 \
> +00 00 00 38 00 00 00 00 \
> +00 00 00 14 00 0a 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \
> +00 01 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \
> +" | grep -q OFPBPC_BAD_VALUE], [0])
> +
> +AT_CHECK([ovs-ofctl ofp-print "\
> +01 04 00 60 00 00 00 03 00 00 23 20 00 00 00 20 \
> +06 \
> +00 00 00 00 00 00 00 \
> +00 02 00 08 00 0d 00 00 \
> +00 00 00 20 00 00 00 00 \
> +00 00 00 14 00 0a 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 01 00 00 00 00 \
> +00 01 00 20 00 00 00 00 \
> +00 00 00 14 00 00 00 00 00 00 00 00 00 00 ff ff 0a 0a 00 02 00 00 00 00 \
> +" | grep -q OFPBPC_BAD_VALUE], [0])
> +AT_CLEANUP
> diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
> index a8934051e..8531b2e2e 100644
> --- a/tests/ovs-ofctl.at
> +++ b/tests/ovs-ofctl.at
> @@ -3271,3 +3271,41 @@ AT_CHECK([ovs-ofctl -O OpenFlow15 dump-flows br0 |
> ofctl_strip | sed '/OFPST_FLO
>
>  OVS_VSWITCHD_STOP(["/Flow exceeded the maximum flow statistics reply size
> and was excluded from the response set/d"])
>  AT_CLEANUP
> +
> +AT_SETUP([ovs-ofctl ct-flush])
> +OVS_VSWITCHD_START
> +
> +AT_CHECK([ovs-appctl vlog/set ct_dpif:dbg])
> +
> +# Check flush conntrack with both zone and tuple
> +AT_CHECK([ovs-ofctl ct-flush br0 zone=5
> 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1'])
> +
> +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush"
> ovs-vswitchd.log) -eq 1])
> +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=5
> 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_tp_src=1,ct_tp_dst=0,ct_nw_proto=17'
> 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0'" ovs-vswitchd.log])
> +
> +# Check flush-conntrack just with tuple
> +AT_CHECK([ovs-ofctl ct-flush br0
> 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_nw_proto=17,ct_tp_src=1'])
> +
> +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush"
> ovs-vswitchd.log) -eq 2])
> +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=0
> 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_tp_src=1,ct_tp_dst=0,ct_nw_proto=17'
> 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0'" ovs-vswitchd.log])
> +
> +# Check flush-conntrack with reply tuple
> +AT_CHECK([ovs-ofctl ct-flush br0 ''
> 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_nw_proto=17,ct_tp_src=1'])
> +
> +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush"
> ovs-vswitchd.log) -eq 3])
> +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=0
> 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=17'
> 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_tp_src=1,ct_tp_dst=0'"
> ovs-vswitchd.log])
> +
> +# Check flush-conntrack with zone and reply tuple
> +AT_CHECK([ovs-ofctl ct-flush br0 zone=5 ''
> 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_nw_proto=17,ct_tp_src=1'])
> +
> +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush"
> ovs-vswitchd.log) -eq 4])
> +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: zone=5
> 'ct_nw_src=::,ct_nw_dst=::,ct_tp_src=0,ct_tp_dst=0,ct_nw_proto=17'
> 'ct_nw_src=10.1.1.3,ct_nw_dst=10.1.1.4,ct_tp_src=1,ct_tp_dst=0'"
> ovs-vswitchd.log])
> +
> +# Check flush-conntrack without any tuple and zone
> +AT_CHECK([ovs-ofctl ct-flush br0])
> +
> +OVS_WAIT_UNTIL([test $(grep -c "|ct_dpif|DBG|.*ct_flush"
> ovs-vswitchd.log) -eq 5])
> +AT_CHECK([grep -q "ct_dpif|DBG|.*ct_flush: <all>" ovs-vswitchd.log])
> +
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> diff --git a/tests/system-traffic.at b/tests/system-traffic.at
> index e7ec1d96b..503455cc6 100644
> --- a/tests/system-traffic.at
> +++ b/tests/system-traffic.at
> @@ -2298,6 +2298,10 @@
> priority=100,in_port=2,icmp,action=ct(zone=5,commit),1
>
>  AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
>
> +m4_foreach([FLUSH_CMD], [[ovs-appctl dpctl/flush-conntrack],
> +                         [ovs-ofctl ct-flush br0]], [
> +AS_BOX([Testing with FLUSH_CMD])
> +
>  dnl Test UDP from port 1
>  AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=1
> packet=50540000000a50540000000908004500001c000000000011a4cd0a0101010a0101020001000200080000
> actions=resubmit(,0)"])
>
> @@ -2305,10 +2309,10 @@ AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep
> "orig=.src=10\.1\.1\.1,"], [],
>
>  
> udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1)
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack
> 'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'])
> +AT_CHECK([FLUSH_CMD
> 'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'])
> +
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep
> "orig=.src=10\.1\.1\.1,"], [1])
>
> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep
> "orig=.src=10\.1\.1\.1,"], [1], [dnl
> -])
>
>  dnl Test UDP from port 2
>  AT_CHECK([ovs-ofctl -O OpenFlow13 packet-out br0 "in_port=2
> packet=50540000000a50540000000908004500001c000000000011a4cd0a0101020a0101010002000100080000
> actions=resubmit(,0)"])
> @@ -2317,10 +2321,9 @@ AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep
> "orig=.src=10\.1\.1\.2,"], [0],
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=5
> 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2'])
> +AT_CHECK([FLUSH_CMD zone=5
> 'ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=17,ct_tp_src=1,ct_tp_dst=2'])
>
> -AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0],
> [dnl
> -])
> +AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.1.1.2)], [0])
>
>  dnl Test ICMP traffic
>  NS_CHECK_EXEC([at_ns1], [ping -q -c 3 -i 0.3 -w 2 10.1.1.1 |
> FORMAT_PING], [0], [dnl
> @@ -2334,7 +2337,7 @@
> icmp,orig=(src=10.1.1.2,dst=10.1.1.1,id=<cleared>,type=8,code=0),reply=(src=10.1
>
>  ICMP_ID=`cat stdout | cut -d ',' -f4 | cut -d '=' -f2`
>
>  
> ICMP_TUPLE=ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=1,icmp_id=$ICMP_ID,icmp_type=8,icmp_code=0
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=5 $ICMP_TUPLE])
> +AT_CHECK([FLUSH_CMD zone=5 $ICMP_TUPLE])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep
> "orig=.src=10\.1\.1\.2,"], [1], [dnl
>  ])
> @@ -2349,13 +2352,13 @@
> udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_src=1'])
> +AT_CHECK([FLUSH_CMD 'ct_nw_proto=17,ct_tp_src=1'])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_src=2'])
> +AT_CHECK([FLUSH_CMD 'ct_nw_proto=17,ct_tp_src=2'])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
>
> @@ -2369,13 +2372,13 @@
> udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_dst=2'])
> +AT_CHECK([FLUSH_CMD 'ct_nw_proto=17,ct_tp_dst=2'])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_proto=17,ct_tp_dst=1'])
> +AT_CHECK([FLUSH_CMD 'ct_nw_proto=17,ct_tp_dst=1'])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
>
> @@ -2389,13 +2392,13 @@
> udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.1'])
> +AT_CHECK([FLUSH_CMD 'ct_nw_src=10.1.1.1'])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_src=10.1.1.2'])
> +AT_CHECK([FLUSH_CMD 'ct_nw_src=10.1.1.2'])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
>
> @@ -2409,13 +2412,13 @@
> udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_dst=10.1.1.2'])
> +AT_CHECK([FLUSH_CMD 'ct_nw_dst=10.1.1.2'])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack 'ct_nw_dst=10.1.1.1'])
> +AT_CHECK([FLUSH_CMD 'ct_nw_dst=10.1.1.1'])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
>
> @@ -2429,15 +2432,16 @@
> udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),reply=(src=10.1.1.2,dst=10.
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack '' 'ct_nw_src=10.1.1.2'])
> +AT_CHECK([FLUSH_CMD '' 'ct_nw_src=10.1.1.2'])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [0], [dnl
>
>  
> udp,orig=(src=10.1.1.2,dst=10.1.1.1,sport=2,dport=1),reply=(src=10.1.1.1,dst=10.1.1.2,sport=1,dport=2),zone=5
>  ])
>
> -AT_CHECK([ovs-appctl dpctl/flush-conntrack zone=5 ''
> 'ct_nw_src=10.1.1.1'])
> +AT_CHECK([FLUSH_CMD zone=5 '' 'ct_nw_src=10.1.1.1'])
>
>  AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep "10\.1\.1\.1"], [1])
> +])
>
>  OVS_TRAFFIC_VSWITCHD_STOP
>  AT_CLEANUP
> diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
> index 10a6a64de..6f02a9134 100644
> --- a/utilities/ovs-ofctl.8.in
> +++ b/utilities/ovs-ofctl.8.in
> @@ -296,6 +296,33 @@ Flushes the connection tracking entries in \fIzone\fR
> on \fIswitch\fR.
>  This command uses an Open vSwitch extension that is only in Open
>  vSwitch 2.6 and later.
>  .
> +.IP "\fBct\-flush \fIswitch [zone=N] [ct-origin-tuple [ct-reply-tuple]]\fR
> +Flushes the connection entries in the tracker \fIzone\fR and
> +connection tracking tuple \fIct-tuple\fR.
> +If \fIct-[origin|reply]-tuple\fR is not provided, flushes all the
> connection entries.
> +If \fIzone\fR is specified, only flushes the connections in
> +\fIzone\fR.
> +.IP
> +If \fIct-[origin|reply]-tuple\fR is provided, flushes the connection entry
> +specified by \fIct-[origin|reply]-tuple\fR in \fIzone\fR. The zone
> defaults
> +to 0 if it is not provided. The userspace connection tracker requires
> flushing
> +with the original pre-NATed tuple and a warning log will be otherwise
> +generated. The tuple can be partial and will remove all connections that
> are
> +matching on the specified fields. In order to specify only
> +\fIct-reply-tuple\fR provide empty string as \fIct-origin-tuple\fR.
> +.IP
> +Note: Currently there is limitation for matching on ICMP, in order to
> partially
> +match on ICMP parameters the \fIct-[origin|reply]-tuple\fR has to include
> +either source or destination IP.
> +.IP
> +An example of an IPv4 ICMP \fIct-[origin|reply]-tuple\fR:
> +.IP
>
> +"ct_nw_src=10.1.1.1,ct_nw_dst=10.1.1.2,ct_nw_proto=1,icmp_type=8,icmp_code=0,icmp_id=10"
> +.IP
> +An example of an IPv6 TCP \fIct-[origin|reply]-tuple\fR:
> +.IP
>
> +"ct_ipv6_src=fc00::1,ct_ipv6_dst=fc00::2,ct_nw_proto=6,ct_tp_src=1,ct_tp_dst=2"
> +.
>  .SS "OpenFlow Switch Flow Table Commands"
>  .
>  These commands manage the flow table in an OpenFlow switch.  In each
> diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
> index fe9114580..eabec18a3 100644
> --- a/utilities/ovs-ofctl.c
> +++ b/utilities/ovs-ofctl.c
> @@ -48,6 +48,7 @@
>  #include "openvswitch/meta-flow.h"
>  #include "openvswitch/ofp-actions.h"
>  #include "openvswitch/ofp-bundle.h"
> +#include "openvswitch/ofp-ct.h"
>  #include "openvswitch/ofp-errors.h"
>  #include "openvswitch/ofp-group.h"
>  #include "openvswitch/ofp-match.h"
> @@ -485,6 +486,9 @@ usage(void)
>             "  dump-ipfix-bridge SWITCH    print ipfix stats of bridge\n"
>             "  dump-ipfix-flow SWITCH      print flow ipfix of a bridge\n"
>             "  ct-flush-zone SWITCH ZONE   flush conntrack entries in
> ZONE\n"
> +           "  ct-flush SWITCH [ZONE] [CT_ORIG_TUPLE [CT_REPLY_TUPLE]]\n"
> +           "                              flush conntrack entries
> specified\n"
> +           "                              by CT_ORIG/REPLY_TUPLE and
> ZONE\n"
>             "\nFor OpenFlow switches and controllers:\n"
>             "  probe TARGET                probe whether TARGET is up\n"
>             "  ping TARGET [N]             latency of N-byte echos\n"
> @@ -3050,6 +3054,50 @@ ofctl_ct_flush_zone(struct ovs_cmdl_context *ctx)
>      vconn_close(vconn);
>  }
>
> +static void
> +ofctl_ct_flush(struct ovs_cmdl_context *ctx)
> +{
> +    struct vconn *vconn;
> +    struct ofp_ct_match match = {0};
> +    struct ds ds = DS_EMPTY_INITIALIZER;
> +    uint16_t zone, *pzone = NULL;
> +    int args = ctx->argc - 2;
> +
> +    /* Parse zone. */
> +    if (args && !strncmp(ctx->argv[2], "zone=", 5)) {
> +        if (!ovs_scan(ctx->argv[2], "zone=%"SCNu16, &zone)) {
> +            ovs_fatal(0, "Failed to parse zone");
> +        }
> +        pzone = &zone;
> +        args--;
> +    }
> +
> +    /* Parse ct tuples. */
> +    for (int i = 0; i < 2; i++) {
> +        if (!args) {
> +            break;
> +        }
> +
> +        struct ofp_ct_tuple *tuple =
> +            i ? &match.tuple_reply : &match.tuple_orig;
> +        const char *arg = ctx->argv[ctx->argc - args];
> +
> +        if (arg[0] && !ofp_ct_tuple_parse(tuple, arg, &ds,
> &match.ip_proto,
> +                                          &match.l3_type)) {
> +            ovs_fatal(0, "Failed to parse ct-tuple: %s", ds_cstr(&ds));
> +        }
> +        args--;
> +    }
> +
> +    open_vconn(ctx->argv[1], &vconn);
> +    enum ofp_version version = vconn_get_version(vconn);
> +    struct ofpbuf *msg = ofp_ct_match_encode(&match, pzone, version);
> +
> +    ds_destroy(&ds);
> +    transact_noreply(vconn, msg);
> +    vconn_close(vconn);
> +}
> +
>  static void
>  ofctl_dump_ipfix_flow(struct ovs_cmdl_context *ctx)
>  {
> @@ -5063,6 +5111,9 @@ static const struct ovs_cmdl_command all_commands[]
> = {
>      { "ct-flush-zone", "switch zone",
>        2, 2, ofctl_ct_flush_zone, OVS_RO },
>
> +    { "ct-flush", "switch [zone=N] [ct-orig-tuple [ct-reply-tuple]]",
> +      1, 4, ofctl_ct_flush, OVS_RO },
> +
>      { "ofp-parse", "file",
>        1, 1, ofctl_ofp_parse, OVS_RW },
>      { "ofp-parse-pcap", "pcap",
> --
> 2.39.0
>
>

Argh I missed the comment alignment again...

With that being said:

diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 35f66a76d..645ad656a 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -1083,9 +1083,9 @@ enum nx_ct_flush_tuple_tlv_type {
     NXT_CT_TUPLE_DST = 1,            /* IPv6 or mapped IPv4 address. */
     NXT_CT_TUPLE_SRC_PORT = 2,       /* be16 source port. */
     NXT_CT_TUPLE_DST_PORT = 3,       /* be16 destination port. */
-    NXT_CT_TUPLE_ICMP_ID = 4,       /* be16 ICMP id. */
-    NXT_CT_TUPLE_ICMP_TYPE = 5,     /* u8 ICMP type. */
-    NXT_CT_TUPLE_ICMP_CODE = 6,     /* u8 ICMP code. */
+    NXT_CT_TUPLE_ICMP_ID = 4,        /* be16 ICMP id. */
+    NXT_CT_TUPLE_ICMP_TYPE = 5,      /* u8 ICMP type. */
+    NXT_CT_TUPLE_ICMP_CODE = 6,      /* u8 ICMP code. */
 };

 /* NXT_CT_FLUSH.

Should be added on top of this patch.

Thanks,
Ales

-- 

Ales Musil

Senior Software Engineer - OVN Core

Red Hat EMEA <https://www.redhat.com>

amu...@redhat.com    IM: amusil
<https://red.ht/sig>
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to