diff mbox series

[ovs-dev,4/4] ofp-flow: Reduce memory consumption for ofputil_flow_mod, using minimatch.

Message ID 20180320204632.16781-4-blp@ovn.org
State Accepted
Headers show
Series [ovs-dev,1/4] match: Add 'tun_md' member to struct minimatch. | expand

Commit Message

Ben Pfaff March 20, 2018, 8:46 p.m. UTC
Until now, struct ofputil_flow_mod, which represents an OpenFlow flow table
modification request, has incorporated a struct match, which made the
overall ofputil_flow_mod about 2.5 kB.  This is OK for a small number of
flows, but absurdly inflates memory requirements when there are hundreds of
thousands of flows.  This commit fixes the problem by changing struct match
to struct minimatch inside ofputil_flow_mod, which reduces its size to
about 100 bytes plus the actual size of the flow match (usually a few dozen
bytes).

This affects memory usage of ovs-ofctl (when it adds a large number of
flows) more than ovs-vswitchd.

Reported-by: Michael Ben-Ami <mbenami@digitalocean.com>
Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 AUTHORS.rst                    |  1 +
 include/openvswitch/ofp-flow.h |  2 +-
 lib/learn.c                    | 12 ++++--
 lib/learning-switch.c          | 14 +++++--
 lib/ofp-bundle.c               |  1 +
 lib/ofp-flow.c                 | 89 ++++++++++++++++++++++++++----------------
 ofproto/ofproto-dpif-xlate.c   |  6 ++-
 ofproto/ofproto-dpif.c         | 17 ++++----
 ofproto/ofproto-dpif.h         |  2 +-
 ofproto/ofproto-provider.h     |  2 +-
 ofproto/ofproto.c              | 49 ++++++++++++++---------
 ovn/controller/ofctrl.c        | 22 ++++++-----
 utilities/ovs-ofctl.c          | 81 +++++++++++++++++++++++---------------
 13 files changed, 185 insertions(+), 113 deletions(-)

Comments

Yifeng Sun March 22, 2018, 5:33 p.m. UTC | #1
Thanks for these changes, looks good to me.

Tested-by: Yifeng Sun <pkusunyifeng@gmail.com>

Reviewed-by: Yifeng Sun <pkusunyifeng@gmail.com>

On Tue, Mar 20, 2018 at 1:46 PM, Ben Pfaff <blp@ovn.org> wrote:

> Until now, struct ofputil_flow_mod, which represents an OpenFlow flow table
> modification request, has incorporated a struct match, which made the
> overall ofputil_flow_mod about 2.5 kB.  This is OK for a small number of
> flows, but absurdly inflates memory requirements when there are hundreds of
> thousands of flows.  This commit fixes the problem by changing struct match
> to struct minimatch inside ofputil_flow_mod, which reduces its size to
> about 100 bytes plus the actual size of the flow match (usually a few dozen
> bytes).
>
> This affects memory usage of ovs-ofctl (when it adds a large number of
> flows) more than ovs-vswitchd.
>
> Reported-by: Michael Ben-Ami <mbenami@digitalocean.com>
> Signed-off-by: Ben Pfaff <blp@ovn.org>
> ---
>  AUTHORS.rst                    |  1 +
>  include/openvswitch/ofp-flow.h |  2 +-
>  lib/learn.c                    | 12 ++++--
>  lib/learning-switch.c          | 14 +++++--
>  lib/ofp-bundle.c               |  1 +
>  lib/ofp-flow.c                 | 89 ++++++++++++++++++++++++++----
> ------------
>  ofproto/ofproto-dpif-xlate.c   |  6 ++-
>  ofproto/ofproto-dpif.c         | 17 ++++----
>  ofproto/ofproto-dpif.h         |  2 +-
>  ofproto/ofproto-provider.h     |  2 +-
>  ofproto/ofproto.c              | 49 ++++++++++++++---------
>  ovn/controller/ofctrl.c        | 22 ++++++-----
>  utilities/ovs-ofctl.c          | 81 +++++++++++++++++++++++-------
> --------
>  13 files changed, 185 insertions(+), 113 deletions(-)
>
> diff --git a/AUTHORS.rst b/AUTHORS.rst
> index dc69ba3f3c1f..81f9b6f28b88 100644
> --- a/AUTHORS.rst
> +++ b/AUTHORS.rst
> @@ -506,6 +506,7 @@ Marvin Pascual                  marvin@pascual.com.ph
>  Maxime Brun                     m.brun@alphalink.fr
>  Madhu Venugopal                 mavenugo@gmail.com
>  Michael A. Collins              mike.a.collins@ark-net.org
> +Michael Ben-Ami                 mbenami@digitalocean.com
>  Michael Hu                      mhu@nicira.com
>  Michael J. Smalley              michaeljsmalley@gmail.com
>  Michael Mao                     mmao@nicira.com
> diff --git a/include/openvswitch/ofp-flow.h b/include/openvswitch/ofp-
> flow.h
> index 17d48f12e060..2ff2e45b66c4 100644
> --- a/include/openvswitch/ofp-flow.h
> +++ b/include/openvswitch/ofp-flow.h
> @@ -73,7 +73,7 @@ void ofputil_flow_mod_flags_format(struct ds *, enum
> ofputil_flow_mod_flags);
>  struct ofputil_flow_mod {
>      struct ovs_list list_node; /* For queuing flow_mods. */
>
> -    struct match match;
> +    struct minimatch match;
>      int priority;
>
>      /* Cookie matching.  The flow_mod affects only flows that have
> cookies that
> diff --git a/lib/learn.c b/lib/learn.c
> index f5d15503fe79..c4d5b3e0c449 100644
> --- a/lib/learn.c
> +++ b/lib/learn.c
> @@ -91,14 +91,17 @@ learn_check(const struct ofpact_learn *learn, const
> struct match *src_match)
>   * 'ofpacts' and retains ownership of it.  'fm->ofpacts' will point into
> the
>   * 'ofpacts' buffer.
>   *
> + * The caller must eventually destroy fm->match.
> + *
>   * The caller has to actually execute 'fm'. */
>  void
>  learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
>                struct ofputil_flow_mod *fm, struct ofpbuf *ofpacts)
>  {
>      const struct ofpact_learn_spec *spec;
> +    struct match match;
>
> -    match_init_catchall(&fm->match);
> +    match_init_catchall(&match);
>      fm->priority = learn->priority;
>      fm->cookie = htonll(0);
>      fm->cookie_mask = htonll(0);
> @@ -140,10 +143,10 @@ learn_execute(const struct ofpact_learn *learn,
> const struct flow *flow,
>
>          switch (spec->dst_type) {
>          case NX_LEARN_DST_MATCH:
> -            mf_write_subfield(&spec->dst, &value, &fm->match);
> -            match_add_ethernet_prereq(&fm->match, spec->dst.field);
> +            mf_write_subfield(&spec->dst, &value, &match);
> +            match_add_ethernet_prereq(&match, spec->dst.field);
>              mf_vl_mff_set_tlv_bitmap(
> -                spec->dst.field, &fm->match.flow.tunnel.
> metadata.present.map);
> +                spec->dst.field, &match.flow.tunnel.metadata.
> present.map);
>              break;
>
>          case NX_LEARN_DST_LOAD:
> @@ -173,6 +176,7 @@ learn_execute(const struct ofpact_learn *learn, const
> struct flow *flow,
>          }
>      }
>
> +    minimatch_init(&fm->match, &match);
>      fm->ofpacts = ofpacts->data;
>      fm->ofpacts_len = ofpacts->size;
>  }
> diff --git a/lib/learning-switch.c b/lib/learning-switch.c
> index 3a9e015bbe9c..04eaa6e8e73f 100644
> --- a/lib/learning-switch.c
> +++ b/lib/learning-switch.c
> @@ -206,7 +206,6 @@ lswitch_handshake(struct lswitch *sw)
>          output.max_len = OFP_DEFAULT_MISS_SEND_LEN;
>
>          struct ofputil_flow_mod fm = {
> -            .match = MATCH_CATCHALL_INITIALIZER,
>              .priority = 0,
>              .table_id = 0,
>              .command = OFPFC_ADD,
> @@ -216,8 +215,10 @@ lswitch_handshake(struct lswitch *sw)
>              .ofpacts = &output.ofpact,
>              .ofpacts_len = sizeof output,
>          };
> -
> +        minimatch_init_catchall(&fm.match);
>          msg = ofputil_encode_flow_mod(&fm, protocol);
> +        minimatch_destroy(&fm.match);
> +
>          error = rconn_send(sw->rconn, msg, NULL);
>          if (error) {
>              VLOG_INFO_RL(&rl, "%s: failed to add default flow (%s)",
> @@ -595,11 +596,16 @@ process_packet_in(struct lswitch *sw, const struct
> ofp_header *oh)
>              .ofpacts = ofpacts.data,
>              .ofpacts_len = ofpacts.size,
>          };
> -        match_init(&fm.match, &flow, &sw->wc);
> -        ofputil_normalize_match_quiet(&fm.match);
> +
> +        struct match match;
> +        match_init(&match, &flow, &sw->wc);
> +        ofputil_normalize_match_quiet(&match);
> +        minimatch_init(&fm.match, &match);
>
>          struct ofpbuf *buffer = ofputil_encode_flow_mod(&fm,
> sw->protocol);
>
> +        minimatch_destroy(&fm.match);
> +
>          queue_tx(sw, buffer);
>
>          /* If the switch didn't buffer the packet, we need to send a
> copy. */
> diff --git a/lib/ofp-bundle.c b/lib/ofp-bundle.c
> index 0921c81bfb15..8f07a30c51f7 100644
> --- a/lib/ofp-bundle.c
> +++ b/lib/ofp-bundle.c
> @@ -33,6 +33,7 @@ ofputil_free_bundle_msgs(struct ofputil_bundle_msg
> *bms, size_t n_bms)
>          switch ((int)bms[i].type) {
>          case OFPTYPE_FLOW_MOD:
>              free(CONST_CAST(struct ofpact *, bms[i].fm.ofpacts));
> +            minimatch_destroy(&bms[i].fm.match);
>              break;
>          case OFPTYPE_GROUP_MOD:
>              ofputil_uninit_group_mod(&bms[i].gm);
> diff --git a/lib/ofp-flow.c b/lib/ofp-flow.c
> index cffadb30381b..d07556245b1f 100644
> --- a/lib/ofp-flow.c
> +++ b/lib/ofp-flow.c
> @@ -139,6 +139,8 @@ ofputil_flow_mod_flags_format(struct ds *s, enum
> ofputil_flow_mod_flags flags)
>   * The caller must initialize 'ofpacts' and retains ownership of it.
>   * 'fm->ofpacts' will point into the 'ofpacts' buffer.
>   *
> + * On success, the caller must eventually destroy fm->match.
> + *
>   * Does not validate the flow_mod actions.  The caller should do that,
> with
>   * ofpacts_check(). */
>  enum ofperr
> @@ -152,6 +154,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>  {
>      ovs_be16 raw_flags;
>      enum ofperr error;
> +    struct match match;
>      struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
>      enum ofpraw raw = ofpraw_pull_assert(&b);
>      if (raw == OFPRAW_OFPT11_FLOW_MOD) {
> @@ -160,7 +163,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>
>          ofm = ofpbuf_pull(&b, sizeof *ofm);
>
> -        error = ofputil_pull_ofp11_match(&b, tun_table, vl_mff_map,
> &fm->match,
> +        error = ofputil_pull_ofp11_match(&b, tun_table, vl_mff_map,
> &match,
>                                           NULL);
>          if (error) {
>              return error;
> @@ -228,8 +231,8 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>              ofm = ofpbuf_pull(&b, sizeof *ofm);
>
>              /* Translate the rule. */
> -            ofputil_match_from_ofp10_match(&ofm->match, &fm->match);
> -            ofputil_normalize_match(&fm->match);
> +            ofputil_match_from_ofp10_match(&ofm->match, &match);
> +            ofputil_normalize_match(&match);
>
>              /* OpenFlow 1.0 says that exact-match rules have to have the
>               * highest possible priority. */
> @@ -256,7 +259,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>              /* Dissect the message. */
>              nfm = ofpbuf_pull(&b, sizeof *nfm);
>              error = nx_pull_match(&b, ntohs(nfm->match_len),
> -                                  &fm->match, &fm->cookie,
> &fm->cookie_mask,
> +                                  &match, &fm->cookie, &fm->cookie_mask,
>                                    false, tun_table, vl_mff_map);
>              if (error) {
>                  return error;
> @@ -269,6 +272,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>                   * existing cookie. */
>                  return OFPERR_NXBRC_NXM_INVALID;
>              }
> +            minimatch_init(&fm->match, &match);
>              fm->priority = ntohs(nfm->priority);
>              fm->new_cookie = nfm->cookie;
>              fm->idle_timeout = ntohs(nfm->idle_timeout);
> @@ -298,11 +302,11 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>
>      /* Check for mismatched conntrack original direction tuple address
> fields
>       * w.r.t. the IP version of the match. */
> -    if (((fm->match.wc.masks.ct_nw_src || fm->match.wc.masks.ct_nw_dst)
> -         && fm->match.flow.dl_type != htons(ETH_TYPE_IP))
> -        || ((ipv6_addr_is_set(&fm->match.wc.masks.ct_ipv6_src)
> -             || ipv6_addr_is_set(&fm->match.wc.masks.ct_ipv6_dst))
> -            && fm->match.flow.dl_type != htons(ETH_TYPE_IPV6))) {
> +    if (((match.wc.masks.ct_nw_src || match.wc.masks.ct_nw_dst)
> +         && match.flow.dl_type != htons(ETH_TYPE_IP))
> +        || ((ipv6_addr_is_set(&match.wc.masks.ct_ipv6_src)
> +             || ipv6_addr_is_set(&match.wc.masks.ct_ipv6_dst))
> +            && match.flow.dl_type != htons(ETH_TYPE_IPV6))) {
>          return OFPERR_OFPBAC_MATCH_INCONSISTENT;
>      }
>
> @@ -339,9 +343,13 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>                  : OFPERR_OFPFMFC_TABLE_FULL);
>      }
>
> -    return ofpacts_check_consistency(fm->ofpacts, fm->ofpacts_len,
> -                                     &fm->match, max_port,
> -                                     fm->table_id, max_table, protocol);
> +    error = ofpacts_check_consistency(fm->ofpacts, fm->ofpacts_len,
> +                                      &match, max_port,
> +                                      fm->table_id, max_table, protocol);
> +    if (!error) {
> +        minimatch_init(&fm->match, &match);
> +    }
> +    return error;
>  }
>
>  static ovs_be16
> @@ -363,6 +371,9 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod
> *fm,
>      ovs_be16 raw_flags = ofputil_encode_flow_mod_flags(fm->flags,
> version);
>      struct ofpbuf *msg;
>
> +    struct match match;
> +    minimatch_expand(&fm->match, &match);
> +
>      switch (protocol) {
>      case OFPUTIL_P_OF11_STD:
>      case OFPUTIL_P_OF12_OXM:
> @@ -407,7 +418,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod
> *fm,
>          } else {
>              ofm->importance = 0;
>          }
> -        ofputil_put_ofp11_match(msg, &fm->match, protocol);
> +        ofputil_put_ofp11_match(msg, &match, protocol);
>          ofpacts_put_openflow_instructions(fm->ofpacts, fm->ofpacts_len,
> msg,
>                                            version);
>          break;
> @@ -420,7 +431,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod
> *fm,
>          msg = ofpraw_alloc(OFPRAW_OFPT10_FLOW_MOD, OFP10_VERSION,
>                             fm->ofpacts_len);
>          ofm = ofpbuf_put_zeros(msg, sizeof *ofm);
> -        ofputil_match_to_ofp10_match(&fm->match, &ofm->match);
> +        ofputil_match_to_ofp10_match(&match, &ofm->match);
>          ofm->cookie = fm->new_cookie;
>          ofm->command = ofputil_tid_command(fm, protocol);
>          ofm->idle_timeout = htons(fm->idle_timeout);
> @@ -444,7 +455,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod
> *fm,
>          nfm = ofpbuf_put_zeros(msg, sizeof *nfm);
>          nfm->command = ofputil_tid_command(fm, protocol);
>          nfm->cookie = fm->new_cookie;
> -        match_len = nx_put_match(msg, &fm->match, fm->cookie,
> fm->cookie_mask);
> +        match_len = nx_put_match(msg, &match, fm->cookie,
> fm->cookie_mask);
>          nfm = msg->msg;
>          nfm->idle_timeout = htons(fm->idle_timeout);
>          nfm->hard_timeout = htons(fm->hard_timeout);
> @@ -536,7 +547,9 @@ ofputil_flow_mod_format(struct ds *s, const struct
> ofp_header *oh,
>          /* nx_match_to_string() doesn't print priority. */
>          need_priority = true;
>      } else {
> -        match_format(&fm.match, port_map, s, fm.priority);
> +        struct match match;
> +        minimatch_expand(&fm.match, &match);
> +        match_format(&match, port_map, s, fm.priority);
>
>          /* match_format() does print priority. */
>          need_priority = false;
> @@ -589,6 +602,7 @@ ofputil_flow_mod_format(struct ds *s, const struct
> ofp_header *oh,
>      };
>      ofpacts_format(fm.ofpacts, fm.ofpacts_len, &fp);
>      ofpbuf_uninit(&ofpacts);
> +    minimatch_destroy(&fm.match);
>
>      return 0;
>  }
> @@ -831,10 +845,13 @@ parse_ofp_flow_stats_request_str(struct
> ofputil_flow_stats_request *fsr,
>      fsr->aggregate = aggregate;
>      fsr->cookie = fm.cookie;
>      fsr->cookie_mask = fm.cookie_mask;
> -    fsr->match = fm.match;
> +    minimatch_expand(&fm.match, &fsr->match);
>      fsr->out_port = fm.out_port;
>      fsr->out_group = fm.out_group;
>      fsr->table_id = fm.table_id;
> +
> +    minimatch_destroy(&fm.match);
> +
>      return NULL;
>  }
>
> @@ -1393,7 +1410,6 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>      }
>
>      *fm = (struct ofputil_flow_mod) {
> -        .match = MATCH_CATCHALL_INITIALIZER,
>          .priority = OFP_DEFAULT_PRIORITY,
>          .table_id = 0xff,
>          .command = command,
> @@ -1413,19 +1429,20 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>          }
>      }
>
> +    struct match match = MATCH_CATCHALL_INITIALIZER;
>      while (ofputil_parse_key_value(&string, &name, &value)) {
>          const struct ofp_protocol *p;
>          const struct mf_field *mf;
>          char *error = NULL;
>
>          if (ofp_parse_protocol(name, &p)) {
> -            match_set_dl_type(&fm->match, htons(p->dl_type));
> +            match_set_dl_type(&match, htons(p->dl_type));
>              if (p->nw_proto) {
> -                match_set_nw_proto(&fm->match, p->nw_proto);
> +                match_set_nw_proto(&match, p->nw_proto);
>              }
> -            match_set_default_packet_type(&fm->match);
> +            match_set_default_packet_type(&match);
>          } else if (!strcmp(name, "eth")) {
> -            match_set_packet_type(&fm->match, htonl(PT_ETH));
> +            match_set_packet_type(&match, htonl(PT_ETH));
>          } else if (fields & F_FLAGS && !strcmp(name, "send_flow_rem")) {
>              fm->flags |= OFPUTIL_FF_SEND_FLOW_REM;
>          } else if (fields & F_FLAGS && !strcmp(name, "check_overlap")) {
> @@ -1444,9 +1461,9 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>               /* ignore these fields. */
>          } else if ((mf = mf_from_name(name)) != NULL) {
>              error = ofp_parse_field(mf, value, port_map,
> -                                    &fm->match, usable_protocols);
> +                                    &match, usable_protocols);
>          } else if (strchr(name, '[')) {
> -            error = parse_subfield(name, value, &fm->match,
> usable_protocols);
> +            error = parse_subfield(name, value, &match, usable_protocols);
>          } else {
>              if (!*value) {
>                  return xasprintf("field %s missing value", name);
> @@ -1532,13 +1549,13 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>      }
>      /* Copy ethertype to flow->dl_type for matches on packet_type
>       * (OFPHTN_ETHERTYPE, ethertype). */
> -    if (fm->match.wc.masks.packet_type == OVS_BE32_MAX &&
> -            pt_ns(fm->match.flow.packet_type) == OFPHTN_ETHERTYPE) {
> -        fm->match.flow.dl_type = pt_ns_type_be(fm->match.flow.
> packet_type);
> +    if (match.wc.masks.packet_type == OVS_BE32_MAX &&
> +            pt_ns(match.flow.packet_type) == OFPHTN_ETHERTYPE) {
> +        match.flow.dl_type = pt_ns_type_be(match.flow.packet_type);
>      }
>      /* Check for usable protocol interdependencies between match fields.
> */
> -    if (fm->match.flow.dl_type == htons(ETH_TYPE_IPV6)) {
> -        const struct flow_wildcards *wc = &fm->match.wc;
> +    if (match.flow.dl_type == htons(ETH_TYPE_IPV6)) {
> +        const struct flow_wildcards *wc = &match.wc;
>          /* Only NXM and OXM support matching L3 and L4 fields within IPv6.
>           *
>           * (IPv6 specific fields as well as arp_sha, arp_tha, nw_frag, and
> @@ -1574,7 +1591,7 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>          if (!error) {
>              enum ofperr err;
>
> -            err = ofpacts_check(ofpacts.data, ofpacts.size, &fm->match,
> +            err = ofpacts_check(ofpacts.data, ofpacts.size, &match,
>                                  OFPP_MAX, fm->table_id, 255,
> usable_protocols);
>              if (!err && !*usable_protocols) {
>                  err = OFPERR_OFPBAC_MATCH_INCONSISTENT;
> @@ -1596,6 +1613,7 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>          fm->ofpacts_len = 0;
>          fm->ofpacts = NULL;
>      }
> +    minimatch_init(&fm->match, &match);
>
>      return NULL;
>  }
> @@ -1613,7 +1631,10 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>   * name is treated as "add".
>   *
>   * Returns NULL if successful, otherwise a malloc()'d string describing
> the
> - * error.  The caller is responsible for freeing the returned string. */
> + * error.  The caller is responsible for freeing the returned string.
> + *
> + * On success, the caller is responsible for freeing fm->ofpacts and
> + * fm->match. */
>  char * OVS_WARN_UNUSED_RESULT
>  parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
>                const struct ofputil_port_map *port_map,
> @@ -1657,8 +1678,9 @@ parse_ofp_flow_mod_str(struct ofputil_flow_mod *fm,
> const char *string,
>          /* Normalize a copy of the match.  This ensures that
> non-normalized
>           * flows get logged but doesn't affect what gets sent to the
> switch, so
>           * that the switch can do whatever it likes with the flow. */
> -        struct match match_copy = fm->match;
> -        ofputil_normalize_match(&match_copy);
> +        struct match match;
> +        minimatch_expand(&fm->match, &match);
> +        ofputil_normalize_match(&match);
>      }
>
>      return error;
> @@ -1716,6 +1738,7 @@ parse_ofp_flow_mod_file(const char *file_name,
>
>              for (i = 0; i < *n_fms; i++) {
>                  free(CONST_CAST(struct ofpact *, (*fms)[i].ofpacts));
> +                minimatch_destroy(&(*fms)[i].match);
>              }
>              free(*fms);
>              *fms = NULL;
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index bc6429c25346..a00bed704806 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -5019,7 +5019,9 @@ xlate_learn_action(struct xlate_ctx *ctx, const
> struct ofpact_learn *learn)
>          if (OVS_UNLIKELY(ctx->xin->trace)) {
>              struct ds s = DS_EMPTY_INITIALIZER;
>              ds_put_format(&s, "table=%"PRIu8" ", fm.table_id);
> -            match_format(&fm.match, NULL, &s, OFP_DEFAULT_PRIORITY);
> +            minimatch_format(&fm.match,
> +                             ofproto_get_tun_tab(&ctx->xin->ofproto->up),
> +                             NULL, &s, OFP_DEFAULT_PRIORITY);
>              ds_chomp(&s, ' ');
>              ds_put_format(&s, " priority=%d", fm.priority);
>              if (fm.new_cookie) {
> @@ -5090,6 +5092,8 @@ xlate_learn_action(struct xlate_ctx *ctx, const
> struct ofpact_learn *learn)
>              xlate_report_error(ctx, "LEARN action execution failed (%s).",
>                                 ofperr_to_string(error));
>          }
> +
> +        minimatch_destroy(&fm.match);
>      } else {
>          xlate_report(ctx, OFT_WARN,
>                       "suppressing side effects, so learn action ignored");
> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> index c92c5bea1725..1ed82d036528 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -5582,9 +5582,11 @@ odp_port_to_ofp_port(const struct ofproto_dpif
> *ofproto, odp_port_t odp_port)
>      }
>  }
>
> +/* 'match' is non-const to allow for temporary modifications.  Any
> changes are
> + * restored before returning. */
>  int
>  ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
> -                               const struct match *match, int priority,
> +                               struct match *match, int priority,
>                                 uint16_t idle_timeout,
>                                 const struct ofpbuf *ofpacts,
>                                 struct rule **rulep)
> @@ -5595,7 +5597,6 @@ ofproto_dpif_add_internal_flow(struct ofproto_dpif
> *ofproto,
>
>      fm = (struct ofputil_flow_mod) {
>          .buffer_id = UINT32_MAX,
> -        .match = *match,
>          .priority = priority,
>          .table_id = TBL_INTERNAL,
>          .command = OFPFC_ADD,
> @@ -5604,8 +5605,10 @@ ofproto_dpif_add_internal_flow(struct ofproto_dpif
> *ofproto,
>          .ofpacts = ofpacts->data,
>          .ofpacts_len = ofpacts->size,
>      };
> -
> +    minimatch_init(&fm.match, match);
>      error = ofproto_flow_mod(&ofproto->up, &fm);
> +    minimatch_destroy(&fm.match);
> +
>      if (error) {
>          VLOG_ERR_RL(&rl, "failed to add internal flow (%s)",
>                      ofperr_to_string(error));
> @@ -5615,8 +5618,7 @@ ofproto_dpif_add_internal_flow(struct ofproto_dpif
> *ofproto,
>
>      rule = rule_dpif_lookup_in_table(ofproto,
>                                       ofproto_dpif_get_tables_
> version(ofproto),
> -                                     TBL_INTERNAL, &fm.match.flow,
> -                                     &fm.match.wc);
> +                                     TBL_INTERNAL, &match->flow,
> &match->wc);
>      if (rule) {
>          *rulep = &rule->up;
>      } else {
> @@ -5634,7 +5636,6 @@ ofproto_dpif_delete_internal_flow(struct
> ofproto_dpif *ofproto,
>
>      fm = (struct ofputil_flow_mod) {
>          .buffer_id = UINT32_MAX,
> -        .match = *match,
>          .priority = priority,
>          .table_id = TBL_INTERNAL,
>          .out_port = OFPP_ANY,
> @@ -5642,8 +5643,10 @@ ofproto_dpif_delete_internal_flow(struct
> ofproto_dpif *ofproto,
>          .flags = OFPUTIL_FF_HIDDEN_FIELDS | OFPUTIL_FF_NO_READONLY,
>          .command = OFPFC_DELETE_STRICT,
>      };
> -
> +    minimatch_init(&fm.match, match);
>      error = ofproto_flow_mod(&ofproto->up, &fm);
> +    minimatch_destroy(&fm.match);
> +
>      if (error) {
>          VLOG_ERR_RL(&rl, "failed to delete internal flow (%s)",
>                      ofperr_to_string(error));
> diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
> index 90432fa2678b..47bf7f9f0ac2 100644
> --- a/ofproto/ofproto-dpif.h
> +++ b/ofproto/ofproto-dpif.h
> @@ -335,7 +335,7 @@ struct ofport_dpif *ofp_port_to_ofport(const struct
> ofproto_dpif *,
>                                         ofp_port_t);
>
>  int ofproto_dpif_add_internal_flow(struct ofproto_dpif *,
> -                                   const struct match *, int priority,
> +                                   struct match *, int priority,
>                                     uint16_t idle_timeout,
>                                     const struct ofpbuf *ofpacts,
>                                     struct rule **rulep);
> diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
> index 92d4622b3290..d636fb35c4f9 100644
> --- a/ofproto/ofproto-provider.h
> +++ b/ofproto/ofproto-provider.h
> @@ -1199,7 +1199,7 @@ struct ofproto_class {
>       *
>       * If this function is NULL then table 0 is always chosen. */
>      enum ofperr (*rule_choose_table)(const struct ofproto *ofproto,
> -                                     const struct match *match,
> +                                     const struct minimatch *match,
>                                       uint8_t *table_idp);
>
>      /* Life-cycle functions for a "struct rule".
> diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
> index 57ce3c81fd22..36f4c0b16d48 100644
> --- a/ofproto/ofproto.c
> +++ b/ofproto/ofproto.c
> @@ -133,7 +133,7 @@ static void eviction_group_remove_rule(struct rule *)
>      OVS_REQUIRES(ofproto_mutex);
>
>  static void rule_criteria_init(struct rule_criteria *, uint8_t table_id,
> -                               const struct match *match, int priority,
> +                               const struct minimatch *match, int
> priority,
>                                 ovs_version_t version,
>                                 ovs_be64 cookie, ovs_be64 cookie_mask,
>                                 ofp_port_t out_port, uint32_t out_group);
> @@ -2104,7 +2104,6 @@ flow_mod_init(struct ofputil_flow_mod *fm,
>                enum ofp_flow_mod_command command)
>  {
>      *fm = (struct ofputil_flow_mod) {
> -        .match = *match,
>          .priority = priority,
>          .table_id = 0,
>          .command = command,
> @@ -2114,6 +2113,7 @@ flow_mod_init(struct ofputil_flow_mod *fm,
>          .ofpacts = CONST_CAST(struct ofpact *, ofpacts),
>          .ofpacts_len = ofpacts_len,
>      };
> +    minimatch_init(&fm->match, match);
>  }
>
>  static int
> @@ -2123,10 +2123,10 @@ simple_flow_mod(struct ofproto *ofproto,
>                  enum ofp_flow_mod_command command)
>  {
>      struct ofputil_flow_mod fm;
> -
>      flow_mod_init(&fm, match, priority, ofpacts, ofpacts_len, command);
> -
> -    return handle_flow_mod__(ofproto, &fm, NULL);
> +    enum ofperr error = handle_flow_mod__(ofproto, &fm, NULL);
> +    minimatch_destroy(&fm.match);
> +    return error;
>  }
>
>  /* Adds a flow to OpenFlow flow table 0 in 'p' that matches 'cls_rule' and
> @@ -3173,12 +3173,12 @@ learned_cookies_flush(struct ofproto *ofproto,
> struct ovs_list *dead_cookies)
>  {
>      struct learned_cookie *c;
>
> +    struct minimatch match;
> +
> +    minimatch_init_catchall(&match);
>      LIST_FOR_EACH_POP (c, u.list_node, dead_cookies) {
>          struct rule_criteria criteria;
>          struct rule_collection rules;
> -        struct match match;
> -
> -        match_init_catchall(&match);
>          rule_criteria_init(&criteria, c->table_id, &match, 0,
> OVS_VERSION_MAX,
>                             c->cookie, OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
>          rule_criteria_require_rw(&criteria, false);
> @@ -3188,6 +3188,7 @@ learned_cookies_flush(struct ofproto *ofproto,
> struct ovs_list *dead_cookies)
>
>          free(c);
>      }
> +    minimatch_destroy(&match);
>  }
>
>  static enum ofperr
> @@ -4051,13 +4052,13 @@ next_matching_table(const struct ofproto *ofproto,
>   * supplied as 0. */
>  static void
>  rule_criteria_init(struct rule_criteria *criteria, uint8_t table_id,
> -                   const struct match *match, int priority,
> +                   const struct minimatch *match, int priority,
>                     ovs_version_t version, ovs_be64 cookie,
>                     ovs_be64 cookie_mask, ofp_port_t out_port,
>                     uint32_t out_group)
>  {
>      criteria->table_id = table_id;
> -    cls_rule_init(&criteria->cr, match, priority);
> +    cls_rule_init_from_minimatch(&criteria->cr, match, priority);
>      criteria->version = version;
>      criteria->cookie = cookie;
>      criteria->cookie_mask = cookie_mask;
> @@ -4303,9 +4304,12 @@ handle_flow_stats_request(struct ofconn *ofconn,
>          return error;
>      }
>
> -    rule_criteria_init(&criteria, fsr.table_id, &fsr.match, 0,
> OVS_VERSION_MAX,
> +    struct minimatch match;
> +    minimatch_init(&match, &fsr.match);
> +    rule_criteria_init(&criteria, fsr.table_id, &match, 0,
> OVS_VERSION_MAX,
>                         fsr.cookie, fsr.cookie_mask, fsr.out_port,
>                         fsr.out_group);
> +    minimatch_destroy(&match);
>
>      ovs_mutex_lock(&ofproto_mutex);
>      error = collect_rules_loose(ofproto, &criteria, &rules);
> @@ -4470,9 +4474,12 @@ handle_aggregate_stats_request(struct ofconn
> *ofconn,
>          return error;
>      }
>
> -    rule_criteria_init(&criteria, request.table_id, &request.match, 0,
> +    struct minimatch match;
> +    minimatch_init(&match, &request.match);
> +    rule_criteria_init(&criteria, request.table_id, &match, 0,
>                         OVS_VERSION_MAX, request.cookie,
> request.cookie_mask,
>                         request.out_port, request.out_group);
> +    minimatch_destroy(&match);
>
>      ovs_mutex_lock(&ofproto_mutex);
>      error = collect_rules_loose(ofproto, &criteria, &rules);
> @@ -4746,22 +4753,22 @@ add_flow_init(struct ofproto *ofproto, struct
> ofproto_flow_mod *ofm,
>      }
>
>      if (!(fm->flags & OFPUTIL_FF_HIDDEN_FIELDS)
> -        && !match_has_default_hidden_fields(&fm->match)) {
> +        && !minimatch_has_default_hidden_fields(&fm->match)) {
>          VLOG_WARN_RL(&rl, "%s: (add_flow) only internal flows can set "
>                       "non-default values to hidden fields",
> ofproto->name);
>          return OFPERR_OFPBRC_EPERM;
>      }
>
>      if (!ofm->temp_rule) {
> -        cls_rule_init(&cr, &fm->match, fm->priority);
> +        cls_rule_init_from_minimatch(&cr, &fm->match, fm->priority);
>
>          /* Allocate new rule.  Destroys 'cr'. */
> +        uint64_t map = miniflow_get_tun_metadata_
> present_map(fm->match.flow);
>          error = ofproto_rule_create(ofproto, &cr, table - ofproto->tables,
>                                      fm->new_cookie, fm->idle_timeout,
>                                      fm->hard_timeout, fm->flags,
>                                      fm->importance, fm->ofpacts,
> -                                    fm->ofpacts_len,
> -                                    fm->match.flow.tunnel.
> metadata.present.map,
> +                                    fm->ofpacts_len, map,
>                                      fm->ofpacts_tlv_bitmap,
> &ofm->temp_rule);
>          if (error) {
>              return error;
> @@ -4958,7 +4965,7 @@ ofproto_flow_mod_init_for_learn(struct ofproto
> *ofproto,
>      struct oftable *table = &ofproto->tables[fm->table_id];
>      struct rule *rule;
>
> -    rule = rule_from_cls_rule(classifier_find_match_exactly(
> +    rule = rule_from_cls_rule(classifier_find_minimatch_exactly(
>                                    &table->cls, &fm->match, fm->priority,
>                                    OVS_VERSION_MAX));
>      if (rule) {
> @@ -5108,12 +5115,14 @@ ofproto_flow_mod_learn(struct ofproto_flow_mod
> *ofm, bool keep_ref,
>          if (limit) {
>              struct rule_criteria criteria;
>              struct rule_collection rules;
> -            struct match match;
> +            struct minimatch match;
>
> -            match_init_catchall(&match);
> +            minimatch_init_catchall(&match);
>              rule_criteria_init(&criteria, rule->table_id, &match, 0,
>                                 OVS_VERSION_MAX, rule->flow_cookie,
>                                 OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
> +            minimatch_destroy(&match);
> +
>              rule_criteria_require_rw(&criteria, false);
>              collect_rules_loose(rule->ofproto, &criteria, &rules);
>              if (rule_collection_n(&rules) >= limit) {
> @@ -5797,6 +5806,7 @@ handle_flow_mod(struct ofconn *ofconn, const struct
> ofp_header *oh)
>      if (!error) {
>          struct openflow_mod_requester req = { ofconn, oh };
>          error = handle_flow_mod__(ofproto, &fm, &req);
> +        minimatch_destroy(&fm.match);
>      }
>
>      ofpbuf_uninit(&ofpacts);
> @@ -7921,6 +7931,7 @@ handle_bundle_add(struct ofconn *ofconn, const
> struct ofp_header *oh)
>                                          ofproto->n_tables);
>          if (!error) {
>              error = ofproto_flow_mod_init(ofproto, &bmsg->ofm, &fm, NULL);
> +            minimatch_destroy(&fm.match);
>          }
>      } else if (type == OFPTYPE_GROUP_MOD) {
>          error = ofputil_decode_group_mod(badd.msg, &bmsg->ogm.gm);
> diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
> index 8d6d1b6ae8c0..83340bbf2b30 100644
> --- a/ovn/controller/ofctrl.c
> +++ b/ovn/controller/ofctrl.c
> @@ -58,7 +58,7 @@ struct ovn_flow {
>      /* Key. */
>      uint8_t table_id;
>      uint16_t priority;
> -    struct match match;
> +    struct minimatch match;
>
>      /* Data. */
>      struct ofpact *ofpacts;
> @@ -373,14 +373,16 @@ error:
>  static void
>  run_S_CLEAR_FLOWS(void)
>  {
> +    VLOG_DBG("clearing all flows");
> +
>      /* Send a flow_mod to delete all flows. */
>      struct ofputil_flow_mod fm = {
> -        .match = MATCH_CATCHALL_INITIALIZER,
>          .table_id = OFPTT_ALL,
>          .command = OFPFC_DELETE,
>      };
> +    minimatch_init_catchall(&fm.match);
>      queue_msg(encode_flow_mod(&fm));
> -    VLOG_DBG("clearing all flows");
> +    minimatch_destroy(&fm.match);
>
>      /* Clear installed_flows, to match the state of the switch. */
>      ovn_flow_table_clear(&installed_flows);
> @@ -630,13 +632,12 @@ ofctrl_recv(const struct ofp_header *oh, enum
> ofptype type)
>  void
>  ofctrl_add_flow(struct hmap *desired_flows,
>                  uint8_t table_id, uint16_t priority, uint64_t cookie,
> -                const struct match *match,
> -                const struct ofpbuf *actions)
> +                const struct match *match, const struct ofpbuf *actions)
>  {
>      struct ovn_flow *f = xmalloc(sizeof *f);
>      f->table_id = table_id;
>      f->priority = priority;
> -    f->match = *match;
> +    minimatch_init(&f->match, match);
>      f->ofpacts = xmemdup(actions->data, actions->size);
>      f->ofpacts_len = actions->size;
>      f->hmap_node.hash = ovn_flow_hash(f);
> @@ -665,7 +666,7 @@ static uint32_t
>  ovn_flow_hash(const struct ovn_flow *f)
>  {
>      return hash_2words((f->table_id << 16) | f->priority,
> -                       match_hash(&f->match, 0));
> +                       minimatch_hash(&f->match, 0));
>
>  }
>
> @@ -676,7 +677,7 @@ ofctrl_dup_flow(struct ovn_flow *src)
>      struct ovn_flow *dst = xmalloc(sizeof *dst);
>      dst->table_id = src->table_id;
>      dst->priority = src->priority;
> -    dst->match = src->match;
> +    minimatch_clone(&dst->match, &src->match);
>      dst->ofpacts = xmemdup(src->ofpacts, src->ofpacts_len);
>      dst->ofpacts_len = src->ofpacts_len;
>      dst->hmap_node.hash = ovn_flow_hash(dst);
> @@ -694,7 +695,7 @@ ovn_flow_lookup(struct hmap *flow_table, const struct
> ovn_flow *target)
>                               flow_table) {
>          if (f->table_id == target->table_id
>              && f->priority == target->priority
> -            && match_equal(&f->match, &target->match)) {
> +            && minimatch_equal(&f->match, &target->match)) {
>              return f;
>          }
>      }
> @@ -707,7 +708,7 @@ ovn_flow_to_string(const struct ovn_flow *f)
>      struct ds s = DS_EMPTY_INITIALIZER;
>      ds_put_format(&s, "table_id=%"PRIu8", ", f->table_id);
>      ds_put_format(&s, "priority=%"PRIu16", ", f->priority);
> -    match_format(&f->match, NULL, &s, OFP_DEFAULT_PRIORITY);
> +    minimatch_format(&f->match, NULL, NULL, &s, OFP_DEFAULT_PRIORITY);
>      ds_put_cstr(&s, ", actions=");
>      struct ofpact_format_params fp = { .s = &s };
>      ofpacts_format(f->ofpacts, f->ofpacts_len, &fp);
> @@ -728,6 +729,7 @@ static void
>  ovn_flow_destroy(struct ovn_flow *f)
>  {
>      if (f) {
> +        minimatch_destroy(&f->match);
>          free(f->ofpacts);
>          free(f);
>      }
> diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
> index b5e2b409328e..4d9e8743e885 100644
> --- a/utilities/ovs-ofctl.c
> +++ b/utilities/ovs-ofctl.c
> @@ -1747,6 +1747,7 @@ ofctl_flow_mod__(const char *remote, struct
> ofputil_flow_mod *fms,
>
>          transact_noreply(vconn, ofputil_encode_flow_mod(fm, protocol));
>          free(CONST_CAST(struct ofpact *, fm->ofpacts));
> +        minimatch_destroy(&fm->match);
>      }
>      vconn_close(vconn);
>  }
> @@ -3309,7 +3310,7 @@ struct fte_version {
>  /* A FTE entry that has been queued for later insertion after all
>   * flows have been scanned to correctly allocation tunnel metadata. */
>  struct fte_pending {
> -    struct match *match;
> +    struct minimatch match;
>      int priority;
>      struct fte_version *version;
>      int index;
> @@ -3442,14 +3443,14 @@ fte_free_all(struct flow_tables *tables)
>   *
>   * Takes ownership of 'version'. */
>  static void
> -fte_insert(struct flow_tables *tables, const struct match *match,
> +fte_insert(struct flow_tables *tables, const struct minimatch *match,
>             int priority, struct fte_version *version, int index)
>  {
>      struct classifier *cls = &tables->tables[version->table_id];
>      struct fte *old, *fte;
>
>      fte = xzalloc(sizeof *fte);
> -    cls_rule_init(&fte->rule, match, priority);
> +    cls_rule_init_from_minimatch(&fte->rule, match, priority);
>      fte->versions[index] = version;
>
>      old = fte_from_cls_rule(classifier_replace(cls, &fte->rule,
> @@ -3498,40 +3499,45 @@ generate_tun_metadata(struct fte_state *state)
>   * can just read the data from the match and rewrite it. On rewrite, it
>   * will use the new table. */
>  static void
> -remap_match(struct fte_state *state, struct match *match)
> +remap_match(struct fte_state *state, struct minimatch *minimatch)
>  {
>      int i;
>
> -    if (!match->tun_md.valid) {
> +    if (!minimatch->tun_md || !minimatch->tun_md->valid) {
>          return;
>      }
>
> -    struct tun_metadata flow = match->flow.tunnel.metadata;
> -    struct tun_metadata flow_mask = match->wc.masks.tunnel.metadata;
> -    memset(&match->flow.tunnel.metadata, 0, sizeof
> match->flow.tunnel.metadata);
> -    memset(&match->wc.masks.tunnel.metadata, 0,
> -           sizeof match->wc.masks.tunnel.metadata);
> -    match->tun_md.valid = false;
> +    struct match match;
> +    minimatch_expand(minimatch, &match);
> +
> +    struct tun_metadata flow = match.flow.tunnel.metadata;
> +    struct tun_metadata flow_mask = match.wc.masks.tunnel.metadata;
> +    memset(&match.flow.tunnel.metadata, 0, sizeof
> match.flow.tunnel.metadata);
> +    memset(&match.wc.masks.tunnel.metadata, 0,
> +           sizeof match.wc.masks.tunnel.metadata);
> +    match.tun_md.valid = false;
>
> -    match->flow.tunnel.metadata.tab = state->tun_tab;
> -    match->wc.masks.tunnel.metadata.tab = match->flow.tunnel.metadata.
> tab;
> +    match.flow.tunnel.metadata.tab = state->tun_tab;
> +    match.wc.masks.tunnel.metadata.tab = match.flow.tunnel.metadata.tab;
>
>      ULLONG_FOR_EACH_1 (i, flow_mask.present.map) {
>          const struct mf_field *field = mf_from_id(MFF_TUN_METADATA0 + i);
> -        int offset = match->tun_md.entry[i].loc.c.offset;
> -        int len = match->tun_md.entry[i].loc.len;
> +        int offset = match.tun_md.entry[i].loc.c.offset;
> +        int len = match.tun_md.entry[i].loc.len;
>          union mf_value value, mask;
>
>          memset(&value, 0, field->n_bytes - len);
> -        memset(&mask, match->tun_md.entry[i].masked ? 0 : 0xff,
> +        memset(&mask, match.tun_md.entry[i].masked ? 0 : 0xff,
>                 field->n_bytes - len);
>
>          memcpy(value.tun_metadata + field->n_bytes - len,
>                 flow.opts.u8 + offset, len);
>          memcpy(mask.tun_metadata + field->n_bytes - len,
>                 flow_mask.opts.u8 + offset, len);
> -        mf_set(field, &value, &mask, match, NULL);
> +        mf_set(field, &value, &mask, &match, NULL);
>      }
> +    minimatch_destroy(minimatch);
> +    minimatch_init(minimatch, &match);
>  }
>
>  /* In order to correctly handle tunnel metadata, we need to have
> @@ -3578,25 +3584,26 @@ fte_state_destroy(struct fte_state *state)
>   * fte_state_init(). fte_queue() is the first pass to be called as each
>   * flow is read from its source. */
>  static void
> -fte_queue(struct fte_state *state, const struct match *match,
> +fte_queue(struct fte_state *state, const struct minimatch *match,
>            int priority, struct fte_version *version, int index)
>  {
>      struct fte_pending *pending = xmalloc(sizeof *pending);
>      int i;
>
> -    pending->match = xmemdup(match, sizeof *match);
> +    minimatch_clone(&pending->match, match);
>      pending->priority = priority;
>      pending->version = version;
>      pending->index = index;
>      ovs_list_push_back(&state->fte_pending_list, &pending->list_node);
>
> -    if (!match->tun_md.valid) {
> +    if (!match->tun_md || !match->tun_md->valid) {
>          return;
>      }
>
> -    ULLONG_FOR_EACH_1 (i, match->wc.masks.tunnel.metadata.present.map) {
> -        if (match->tun_md.entry[i].loc.len >
> state->tun_metadata_size[i]) {
> -            state->tun_metadata_size[i] = match->tun_md.entry[i].loc.len;
> +    uint64_t map = miniflow_get_tun_metadata_present_map(&match->mask->
> masks);
> +    ULLONG_FOR_EACH_1 (i, map) {
> +        if (match->tun_md->entry[i].loc.len >
> state->tun_metadata_size[i]) {
> +            state->tun_metadata_size[i] = match->tun_md->entry[i].loc.
> len;
>          }
>      }
>  }
> @@ -3615,10 +3622,10 @@ fte_fill(struct fte_state *state, struct
> flow_tables *tables)
>      flow_tables_defer(tables);
>
>      LIST_FOR_EACH_POP(pending, list_node, &state->fte_pending_list) {
> -        remap_match(state, pending->match);
> -        fte_insert(tables, pending->match, pending->priority,
> pending->version,
> -                   pending->index);
> -        free(pending->match);
> +        remap_match(state, &pending->match);
> +        fte_insert(tables, &pending->match, pending->priority,
> +                   pending->version, pending->index);
> +        minimatch_destroy(&pending->match);
>          free(pending);
>      }
>
> @@ -3669,6 +3676,8 @@ read_flows_from_file(const char *filename, struct
> fte_state *state, int index)
>          version->table_id = fm.table_id != OFPTT_ALL ? fm.table_id : 0;
>
>          fte_queue(state, &fm.match, fm.priority, version, index);
> +
> +        minimatch_destroy(&fm.match);
>      }
>      ds_destroy(&s);
>
> @@ -3714,7 +3723,10 @@ read_flows_from_switch(struct vconn *vconn,
>          version->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len);
>          version->table_id = fs->table_id;
>
> -        fte_queue(state, &fs->match, fs->priority, version, index);
> +        struct minimatch match;
> +        minimatch_init(&match, &fs->match);
> +        fte_queue(state, &match, fs->priority, version, index);
> +        minimatch_destroy(&match);
>      }
>
>      for (size_t i = 0; i < n_fses; i++) {
> @@ -3744,7 +3756,7 @@ fte_make_flow_mod(const struct fte *fte, int index,
> uint16_t command,
>          .out_group = OFPG_ANY,
>          .flags = version->flags,
>      };
> -    minimatch_expand(&fte->rule.match, &fm.match);
> +    minimatch_clone(&fm.match, &fte->rule.match);
>      if (command == OFPFC_ADD || command == OFPFC_MODIFY ||
>          command == OFPFC_MODIFY_STRICT) {
>          fm.ofpacts = version->ofpacts;
> @@ -3753,8 +3765,9 @@ fte_make_flow_mod(const struct fte *fte, int index,
> uint16_t command,
>          fm.ofpacts = NULL;
>          fm.ofpacts_len = 0;
>      }
> -
>      ofm = ofputil_encode_flow_mod(&fm, protocol);
> +    minimatch_destroy(&fm.match);
> +
>      ovs_list_push_back(packets, &ofm->list_node);
>  }
>
> @@ -4028,6 +4041,7 @@ ofctl_parse_flows__(struct ofputil_flow_mod *fms,
> size_t n_fms,
>          ofpbuf_delete(msg);
>
>          free(CONST_CAST(struct ofpact *, fm->ofpacts));
> +        minimatch_destroy(&fm->match);
>      }
>  }
>
> @@ -4504,10 +4518,13 @@ ofctl_check_vlan(struct ovs_cmdl_context *ctx)
>      if (error_s) {
>          ovs_fatal(0, "%s", error_s);
>      }
> +    struct match fm_match;
> +    minimatch_expand(&fm.match, &fm_match);
>      printf("%04"PRIx16"/%04"PRIx16"\n",
> -           ntohs(fm.match.flow.vlans[0].tci),
> -           ntohs(fm.match.wc.masks.vlans[0].tci));
> +           ntohs(fm_match.flow.vlans[0].tci),
> +           ntohs(fm_match.wc.masks.vlans[0].tci));
>      free(string_s);
> +    minimatch_destroy(&fm.match);
>
>      /* Convert to and from NXM. */
>      ofpbuf_init(&nxm, 0);
> --
> 2.16.1
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
Jan Scheurich March 23, 2018, 5:15 p.m. UTC | #2
Looks good to me and greatly reduces the memory footprint of ovs-ofctl.

It seems that Jarno's bundle handling optimizations already did the conversion to minimatch internally when buffering the bundle in ovs-vswitchd. There is no significant memory savings there.

Reviewed-by: Jan Scheurich <jan.scheurich@ericsson.com>
Tested-by: Jan Scheurich <jan.scheurich@ericsson.com>

BR, Jan

> -----Original Message-----
> From: ovs-dev-bounces@openvswitch.org [mailto:ovs-dev-bounces@openvswitch.org] On Behalf Of Ben Pfaff
> Sent: Tuesday, 20 March, 2018 21:47
> To: dev@openvswitch.org
> Cc: Ben Pfaff <blp@ovn.org>; Michael Ben-Ami <mbenami@digitalocean.com>
> Subject: [ovs-dev] [PATCH 4/4] ofp-flow: Reduce memory consumption for ofputil_flow_mod, using minimatch.
> 
> Until now, struct ofputil_flow_mod, which represents an OpenFlow flow table
> modification request, has incorporated a struct match, which made the
> overall ofputil_flow_mod about 2.5 kB.  This is OK for a small number of
> flows, but absurdly inflates memory requirements when there are hundreds of
> thousands of flows.  This commit fixes the problem by changing struct match
> to struct minimatch inside ofputil_flow_mod, which reduces its size to
> about 100 bytes plus the actual size of the flow match (usually a few dozen
> bytes).
> 
> This affects memory usage of ovs-ofctl (when it adds a large number of
> flows) more than ovs-vswitchd.
> 
> Reported-by: Michael Ben-Ami <mbenami@digitalocean.com>
> Signed-off-by: Ben Pfaff <blp@ovn.org>
> ---
>  AUTHORS.rst                    |  1 +
>  include/openvswitch/ofp-flow.h |  2 +-
>  lib/learn.c                    | 12 ++++--
>  lib/learning-switch.c          | 14 +++++--
>  lib/ofp-bundle.c               |  1 +
>  lib/ofp-flow.c                 | 89 ++++++++++++++++++++++++++----------------
>  ofproto/ofproto-dpif-xlate.c   |  6 ++-
>  ofproto/ofproto-dpif.c         | 17 ++++----
>  ofproto/ofproto-dpif.h         |  2 +-
>  ofproto/ofproto-provider.h     |  2 +-
>  ofproto/ofproto.c              | 49 ++++++++++++++---------
>  ovn/controller/ofctrl.c        | 22 ++++++-----
>  utilities/ovs-ofctl.c          | 81 +++++++++++++++++++++++---------------
>  13 files changed, 185 insertions(+), 113 deletions(-)
> 
> diff --git a/AUTHORS.rst b/AUTHORS.rst
> index dc69ba3f3c1f..81f9b6f28b88 100644
> --- a/AUTHORS.rst
> +++ b/AUTHORS.rst
> @@ -506,6 +506,7 @@ Marvin Pascual                  marvin@pascual.com.ph
>  Maxime Brun                     m.brun@alphalink.fr
>  Madhu Venugopal                 mavenugo@gmail.com
>  Michael A. Collins              mike.a.collins@ark-net.org
> +Michael Ben-Ami                 mbenami@digitalocean.com
>  Michael Hu                      mhu@nicira.com
>  Michael J. Smalley              michaeljsmalley@gmail.com
>  Michael Mao                     mmao@nicira.com
> diff --git a/include/openvswitch/ofp-flow.h b/include/openvswitch/ofp-flow.h
> index 17d48f12e060..2ff2e45b66c4 100644
> --- a/include/openvswitch/ofp-flow.h
> +++ b/include/openvswitch/ofp-flow.h
> @@ -73,7 +73,7 @@ void ofputil_flow_mod_flags_format(struct ds *, enum ofputil_flow_mod_flags);
>  struct ofputil_flow_mod {
>      struct ovs_list list_node; /* For queuing flow_mods. */
> 
> -    struct match match;
> +    struct minimatch match;
>      int priority;
> 
>      /* Cookie matching.  The flow_mod affects only flows that have cookies that
> diff --git a/lib/learn.c b/lib/learn.c
> index f5d15503fe79..c4d5b3e0c449 100644
> --- a/lib/learn.c
> +++ b/lib/learn.c
> @@ -91,14 +91,17 @@ learn_check(const struct ofpact_learn *learn, const struct match *src_match)
>   * 'ofpacts' and retains ownership of it.  'fm->ofpacts' will point into the
>   * 'ofpacts' buffer.
>   *
> + * The caller must eventually destroy fm->match.
> + *
>   * The caller has to actually execute 'fm'. */
>  void
>  learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
>                struct ofputil_flow_mod *fm, struct ofpbuf *ofpacts)
>  {
>      const struct ofpact_learn_spec *spec;
> +    struct match match;
> 
> -    match_init_catchall(&fm->match);
> +    match_init_catchall(&match);
>      fm->priority = learn->priority;
>      fm->cookie = htonll(0);
>      fm->cookie_mask = htonll(0);
> @@ -140,10 +143,10 @@ learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
> 
>          switch (spec->dst_type) {
>          case NX_LEARN_DST_MATCH:
> -            mf_write_subfield(&spec->dst, &value, &fm->match);
> -            match_add_ethernet_prereq(&fm->match, spec->dst.field);
> +            mf_write_subfield(&spec->dst, &value, &match);
> +            match_add_ethernet_prereq(&match, spec->dst.field);
>              mf_vl_mff_set_tlv_bitmap(
> -                spec->dst.field, &fm->match.flow.tunnel.metadata.present.map);
> +                spec->dst.field, &match.flow.tunnel.metadata.present.map);
>              break;
> 
>          case NX_LEARN_DST_LOAD:
> @@ -173,6 +176,7 @@ learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
>          }
>      }
> 
> +    minimatch_init(&fm->match, &match);
>      fm->ofpacts = ofpacts->data;
>      fm->ofpacts_len = ofpacts->size;
>  }
> diff --git a/lib/learning-switch.c b/lib/learning-switch.c
> index 3a9e015bbe9c..04eaa6e8e73f 100644
> --- a/lib/learning-switch.c
> +++ b/lib/learning-switch.c
> @@ -206,7 +206,6 @@ lswitch_handshake(struct lswitch *sw)
>          output.max_len = OFP_DEFAULT_MISS_SEND_LEN;
> 
>          struct ofputil_flow_mod fm = {
> -            .match = MATCH_CATCHALL_INITIALIZER,
>              .priority = 0,
>              .table_id = 0,
>              .command = OFPFC_ADD,
> @@ -216,8 +215,10 @@ lswitch_handshake(struct lswitch *sw)
>              .ofpacts = &output.ofpact,
>              .ofpacts_len = sizeof output,
>          };
> -
> +        minimatch_init_catchall(&fm.match);
>          msg = ofputil_encode_flow_mod(&fm, protocol);
> +        minimatch_destroy(&fm.match);
> +
>          error = rconn_send(sw->rconn, msg, NULL);
>          if (error) {
>              VLOG_INFO_RL(&rl, "%s: failed to add default flow (%s)",
> @@ -595,11 +596,16 @@ process_packet_in(struct lswitch *sw, const struct ofp_header *oh)
>              .ofpacts = ofpacts.data,
>              .ofpacts_len = ofpacts.size,
>          };
> -        match_init(&fm.match, &flow, &sw->wc);
> -        ofputil_normalize_match_quiet(&fm.match);
> +
> +        struct match match;
> +        match_init(&match, &flow, &sw->wc);
> +        ofputil_normalize_match_quiet(&match);
> +        minimatch_init(&fm.match, &match);
> 
>          struct ofpbuf *buffer = ofputil_encode_flow_mod(&fm, sw->protocol);
> 
> +        minimatch_destroy(&fm.match);
> +
>          queue_tx(sw, buffer);
> 
>          /* If the switch didn't buffer the packet, we need to send a copy. */
> diff --git a/lib/ofp-bundle.c b/lib/ofp-bundle.c
> index 0921c81bfb15..8f07a30c51f7 100644
> --- a/lib/ofp-bundle.c
> +++ b/lib/ofp-bundle.c
> @@ -33,6 +33,7 @@ ofputil_free_bundle_msgs(struct ofputil_bundle_msg *bms, size_t n_bms)
>          switch ((int)bms[i].type) {
>          case OFPTYPE_FLOW_MOD:
>              free(CONST_CAST(struct ofpact *, bms[i].fm.ofpacts));
> +            minimatch_destroy(&bms[i].fm.match);
>              break;
>          case OFPTYPE_GROUP_MOD:
>              ofputil_uninit_group_mod(&bms[i].gm);
> diff --git a/lib/ofp-flow.c b/lib/ofp-flow.c
> index cffadb30381b..d07556245b1f 100644
> --- a/lib/ofp-flow.c
> +++ b/lib/ofp-flow.c
> @@ -139,6 +139,8 @@ ofputil_flow_mod_flags_format(struct ds *s, enum ofputil_flow_mod_flags flags)
>   * The caller must initialize 'ofpacts' and retains ownership of it.
>   * 'fm->ofpacts' will point into the 'ofpacts' buffer.
>   *
> + * On success, the caller must eventually destroy fm->match.
> + *
>   * Does not validate the flow_mod actions.  The caller should do that, with
>   * ofpacts_check(). */
>  enum ofperr
> @@ -152,6 +154,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>  {
>      ovs_be16 raw_flags;
>      enum ofperr error;
> +    struct match match;
>      struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
>      enum ofpraw raw = ofpraw_pull_assert(&b);
>      if (raw == OFPRAW_OFPT11_FLOW_MOD) {
> @@ -160,7 +163,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
> 
>          ofm = ofpbuf_pull(&b, sizeof *ofm);
> 
> -        error = ofputil_pull_ofp11_match(&b, tun_table, vl_mff_map, &fm->match,
> +        error = ofputil_pull_ofp11_match(&b, tun_table, vl_mff_map, &match,
>                                           NULL);
>          if (error) {
>              return error;
> @@ -228,8 +231,8 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>              ofm = ofpbuf_pull(&b, sizeof *ofm);
> 
>              /* Translate the rule. */
> -            ofputil_match_from_ofp10_match(&ofm->match, &fm->match);
> -            ofputil_normalize_match(&fm->match);
> +            ofputil_match_from_ofp10_match(&ofm->match, &match);
> +            ofputil_normalize_match(&match);
> 
>              /* OpenFlow 1.0 says that exact-match rules have to have the
>               * highest possible priority. */
> @@ -256,7 +259,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>              /* Dissect the message. */
>              nfm = ofpbuf_pull(&b, sizeof *nfm);
>              error = nx_pull_match(&b, ntohs(nfm->match_len),
> -                                  &fm->match, &fm->cookie, &fm->cookie_mask,
> +                                  &match, &fm->cookie, &fm->cookie_mask,
>                                    false, tun_table, vl_mff_map);
>              if (error) {
>                  return error;
> @@ -269,6 +272,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>                   * existing cookie. */
>                  return OFPERR_NXBRC_NXM_INVALID;
>              }
> +            minimatch_init(&fm->match, &match);
>              fm->priority = ntohs(nfm->priority);
>              fm->new_cookie = nfm->cookie;
>              fm->idle_timeout = ntohs(nfm->idle_timeout);
> @@ -298,11 +302,11 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
> 
>      /* Check for mismatched conntrack original direction tuple address fields
>       * w.r.t. the IP version of the match. */
> -    if (((fm->match.wc.masks.ct_nw_src || fm->match.wc.masks.ct_nw_dst)
> -         && fm->match.flow.dl_type != htons(ETH_TYPE_IP))
> -        || ((ipv6_addr_is_set(&fm->match.wc.masks.ct_ipv6_src)
> -             || ipv6_addr_is_set(&fm->match.wc.masks.ct_ipv6_dst))
> -            && fm->match.flow.dl_type != htons(ETH_TYPE_IPV6))) {
> +    if (((match.wc.masks.ct_nw_src || match.wc.masks.ct_nw_dst)
> +         && match.flow.dl_type != htons(ETH_TYPE_IP))
> +        || ((ipv6_addr_is_set(&match.wc.masks.ct_ipv6_src)
> +             || ipv6_addr_is_set(&match.wc.masks.ct_ipv6_dst))
> +            && match.flow.dl_type != htons(ETH_TYPE_IPV6))) {
>          return OFPERR_OFPBAC_MATCH_INCONSISTENT;
>      }
> 
> @@ -339,9 +343,13 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>                  : OFPERR_OFPFMFC_TABLE_FULL);
>      }
> 
> -    return ofpacts_check_consistency(fm->ofpacts, fm->ofpacts_len,
> -                                     &fm->match, max_port,
> -                                     fm->table_id, max_table, protocol);
> +    error = ofpacts_check_consistency(fm->ofpacts, fm->ofpacts_len,
> +                                      &match, max_port,
> +                                      fm->table_id, max_table, protocol);
> +    if (!error) {
> +        minimatch_init(&fm->match, &match);
> +    }
> +    return error;
>  }
> 
>  static ovs_be16
> @@ -363,6 +371,9 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
>      ovs_be16 raw_flags = ofputil_encode_flow_mod_flags(fm->flags, version);
>      struct ofpbuf *msg;
> 
> +    struct match match;
> +    minimatch_expand(&fm->match, &match);
> +
>      switch (protocol) {
>      case OFPUTIL_P_OF11_STD:
>      case OFPUTIL_P_OF12_OXM:
> @@ -407,7 +418,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
>          } else {
>              ofm->importance = 0;
>          }
> -        ofputil_put_ofp11_match(msg, &fm->match, protocol);
> +        ofputil_put_ofp11_match(msg, &match, protocol);
>          ofpacts_put_openflow_instructions(fm->ofpacts, fm->ofpacts_len, msg,
>                                            version);
>          break;
> @@ -420,7 +431,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
>          msg = ofpraw_alloc(OFPRAW_OFPT10_FLOW_MOD, OFP10_VERSION,
>                             fm->ofpacts_len);
>          ofm = ofpbuf_put_zeros(msg, sizeof *ofm);
> -        ofputil_match_to_ofp10_match(&fm->match, &ofm->match);
> +        ofputil_match_to_ofp10_match(&match, &ofm->match);
>          ofm->cookie = fm->new_cookie;
>          ofm->command = ofputil_tid_command(fm, protocol);
>          ofm->idle_timeout = htons(fm->idle_timeout);
> @@ -444,7 +455,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
>          nfm = ofpbuf_put_zeros(msg, sizeof *nfm);
>          nfm->command = ofputil_tid_command(fm, protocol);
>          nfm->cookie = fm->new_cookie;
> -        match_len = nx_put_match(msg, &fm->match, fm->cookie, fm->cookie_mask);
> +        match_len = nx_put_match(msg, &match, fm->cookie, fm->cookie_mask);
>          nfm = msg->msg;
>          nfm->idle_timeout = htons(fm->idle_timeout);
>          nfm->hard_timeout = htons(fm->hard_timeout);
> @@ -536,7 +547,9 @@ ofputil_flow_mod_format(struct ds *s, const struct ofp_header *oh,
>          /* nx_match_to_string() doesn't print priority. */
>          need_priority = true;
>      } else {
> -        match_format(&fm.match, port_map, s, fm.priority);
> +        struct match match;
> +        minimatch_expand(&fm.match, &match);
> +        match_format(&match, port_map, s, fm.priority);
> 
>          /* match_format() does print priority. */
>          need_priority = false;
> @@ -589,6 +602,7 @@ ofputil_flow_mod_format(struct ds *s, const struct ofp_header *oh,
>      };
>      ofpacts_format(fm.ofpacts, fm.ofpacts_len, &fp);
>      ofpbuf_uninit(&ofpacts);
> +    minimatch_destroy(&fm.match);
> 
>      return 0;
>  }
> @@ -831,10 +845,13 @@ parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr,
>      fsr->aggregate = aggregate;
>      fsr->cookie = fm.cookie;
>      fsr->cookie_mask = fm.cookie_mask;
> -    fsr->match = fm.match;
> +    minimatch_expand(&fm.match, &fsr->match);
>      fsr->out_port = fm.out_port;
>      fsr->out_group = fm.out_group;
>      fsr->table_id = fm.table_id;
> +
> +    minimatch_destroy(&fm.match);
> +
>      return NULL;
>  }
> 
> @@ -1393,7 +1410,6 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
>      }
> 
>      *fm = (struct ofputil_flow_mod) {
> -        .match = MATCH_CATCHALL_INITIALIZER,
>          .priority = OFP_DEFAULT_PRIORITY,
>          .table_id = 0xff,
>          .command = command,
> @@ -1413,19 +1429,20 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
>          }
>      }
> 
> +    struct match match = MATCH_CATCHALL_INITIALIZER;
>      while (ofputil_parse_key_value(&string, &name, &value)) {
>          const struct ofp_protocol *p;
>          const struct mf_field *mf;
>          char *error = NULL;
> 
>          if (ofp_parse_protocol(name, &p)) {
> -            match_set_dl_type(&fm->match, htons(p->dl_type));
> +            match_set_dl_type(&match, htons(p->dl_type));
>              if (p->nw_proto) {
> -                match_set_nw_proto(&fm->match, p->nw_proto);
> +                match_set_nw_proto(&match, p->nw_proto);
>              }
> -            match_set_default_packet_type(&fm->match);
> +            match_set_default_packet_type(&match);
>          } else if (!strcmp(name, "eth")) {
> -            match_set_packet_type(&fm->match, htonl(PT_ETH));
> +            match_set_packet_type(&match, htonl(PT_ETH));
>          } else if (fields & F_FLAGS && !strcmp(name, "send_flow_rem")) {
>              fm->flags |= OFPUTIL_FF_SEND_FLOW_REM;
>          } else if (fields & F_FLAGS && !strcmp(name, "check_overlap")) {
> @@ -1444,9 +1461,9 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
>               /* ignore these fields. */
>          } else if ((mf = mf_from_name(name)) != NULL) {
>              error = ofp_parse_field(mf, value, port_map,
> -                                    &fm->match, usable_protocols);
> +                                    &match, usable_protocols);
>          } else if (strchr(name, '[')) {
> -            error = parse_subfield(name, value, &fm->match, usable_protocols);
> +            error = parse_subfield(name, value, &match, usable_protocols);
>          } else {
>              if (!*value) {
>                  return xasprintf("field %s missing value", name);
> @@ -1532,13 +1549,13 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
>      }
>      /* Copy ethertype to flow->dl_type for matches on packet_type
>       * (OFPHTN_ETHERTYPE, ethertype). */
> -    if (fm->match.wc.masks.packet_type == OVS_BE32_MAX &&
> -            pt_ns(fm->match.flow.packet_type) == OFPHTN_ETHERTYPE) {
> -        fm->match.flow.dl_type = pt_ns_type_be(fm->match.flow.packet_type);
> +    if (match.wc.masks.packet_type == OVS_BE32_MAX &&
> +            pt_ns(match.flow.packet_type) == OFPHTN_ETHERTYPE) {
> +        match.flow.dl_type = pt_ns_type_be(match.flow.packet_type);
>      }
>      /* Check for usable protocol interdependencies between match fields. */
> -    if (fm->match.flow.dl_type == htons(ETH_TYPE_IPV6)) {
> -        const struct flow_wildcards *wc = &fm->match.wc;
> +    if (match.flow.dl_type == htons(ETH_TYPE_IPV6)) {
> +        const struct flow_wildcards *wc = &match.wc;
>          /* Only NXM and OXM support matching L3 and L4 fields within IPv6.
>           *
>           * (IPv6 specific fields as well as arp_sha, arp_tha, nw_frag, and
> @@ -1574,7 +1591,7 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
>          if (!error) {
>              enum ofperr err;
> 
> -            err = ofpacts_check(ofpacts.data, ofpacts.size, &fm->match,
> +            err = ofpacts_check(ofpacts.data, ofpacts.size, &match,
>                                  OFPP_MAX, fm->table_id, 255, usable_protocols);
>              if (!err && !*usable_protocols) {
>                  err = OFPERR_OFPBAC_MATCH_INCONSISTENT;
> @@ -1596,6 +1613,7 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
>          fm->ofpacts_len = 0;
>          fm->ofpacts = NULL;
>      }
> +    minimatch_init(&fm->match, &match);
> 
>      return NULL;
>  }
> @@ -1613,7 +1631,10 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
>   * name is treated as "add".
>   *
>   * Returns NULL if successful, otherwise a malloc()'d string describing the
> - * error.  The caller is responsible for freeing the returned string. */
> + * error.  The caller is responsible for freeing the returned string.
> + *
> + * On success, the caller is responsible for freeing fm->ofpacts and
> + * fm->match. */
>  char * OVS_WARN_UNUSED_RESULT
>  parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
>                const struct ofputil_port_map *port_map,
> @@ -1657,8 +1678,9 @@ parse_ofp_flow_mod_str(struct ofputil_flow_mod *fm, const char *string,
>          /* Normalize a copy of the match.  This ensures that non-normalized
>           * flows get logged but doesn't affect what gets sent to the switch, so
>           * that the switch can do whatever it likes with the flow. */
> -        struct match match_copy = fm->match;
> -        ofputil_normalize_match(&match_copy);
> +        struct match match;
> +        minimatch_expand(&fm->match, &match);
> +        ofputil_normalize_match(&match);
>      }
> 
>      return error;
> @@ -1716,6 +1738,7 @@ parse_ofp_flow_mod_file(const char *file_name,
> 
>              for (i = 0; i < *n_fms; i++) {
>                  free(CONST_CAST(struct ofpact *, (*fms)[i].ofpacts));
> +                minimatch_destroy(&(*fms)[i].match);
>              }
>              free(*fms);
>              *fms = NULL;
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index bc6429c25346..a00bed704806 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -5019,7 +5019,9 @@ xlate_learn_action(struct xlate_ctx *ctx, const struct ofpact_learn *learn)
>          if (OVS_UNLIKELY(ctx->xin->trace)) {
>              struct ds s = DS_EMPTY_INITIALIZER;
>              ds_put_format(&s, "table=%"PRIu8" ", fm.table_id);
> -            match_format(&fm.match, NULL, &s, OFP_DEFAULT_PRIORITY);
> +            minimatch_format(&fm.match,
> +                             ofproto_get_tun_tab(&ctx->xin->ofproto->up),
> +                             NULL, &s, OFP_DEFAULT_PRIORITY);
>              ds_chomp(&s, ' ');
>              ds_put_format(&s, " priority=%d", fm.priority);
>              if (fm.new_cookie) {
> @@ -5090,6 +5092,8 @@ xlate_learn_action(struct xlate_ctx *ctx, const struct ofpact_learn *learn)
>              xlate_report_error(ctx, "LEARN action execution failed (%s).",
>                                 ofperr_to_string(error));
>          }
> +
> +        minimatch_destroy(&fm.match);
>      } else {
>          xlate_report(ctx, OFT_WARN,
>                       "suppressing side effects, so learn action ignored");
> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> index c92c5bea1725..1ed82d036528 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -5582,9 +5582,11 @@ odp_port_to_ofp_port(const struct ofproto_dpif *ofproto, odp_port_t odp_port)
>      }
>  }
> 
> +/* 'match' is non-const to allow for temporary modifications.  Any changes are
> + * restored before returning. */
>  int
>  ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
> -                               const struct match *match, int priority,
> +                               struct match *match, int priority,
>                                 uint16_t idle_timeout,
>                                 const struct ofpbuf *ofpacts,
>                                 struct rule **rulep)
> @@ -5595,7 +5597,6 @@ ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
> 
>      fm = (struct ofputil_flow_mod) {
>          .buffer_id = UINT32_MAX,
> -        .match = *match,
>          .priority = priority,
>          .table_id = TBL_INTERNAL,
>          .command = OFPFC_ADD,
> @@ -5604,8 +5605,10 @@ ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
>          .ofpacts = ofpacts->data,
>          .ofpacts_len = ofpacts->size,
>      };
> -
> +    minimatch_init(&fm.match, match);
>      error = ofproto_flow_mod(&ofproto->up, &fm);
> +    minimatch_destroy(&fm.match);
> +
>      if (error) {
>          VLOG_ERR_RL(&rl, "failed to add internal flow (%s)",
>                      ofperr_to_string(error));
> @@ -5615,8 +5618,7 @@ ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
> 
>      rule = rule_dpif_lookup_in_table(ofproto,
>                                       ofproto_dpif_get_tables_version(ofproto),
> -                                     TBL_INTERNAL, &fm.match.flow,
> -                                     &fm.match.wc);
> +                                     TBL_INTERNAL, &match->flow, &match->wc);
>      if (rule) {
>          *rulep = &rule->up;
>      } else {
> @@ -5634,7 +5636,6 @@ ofproto_dpif_delete_internal_flow(struct ofproto_dpif *ofproto,
> 
>      fm = (struct ofputil_flow_mod) {
>          .buffer_id = UINT32_MAX,
> -        .match = *match,
>          .priority = priority,
>          .table_id = TBL_INTERNAL,
>          .out_port = OFPP_ANY,
> @@ -5642,8 +5643,10 @@ ofproto_dpif_delete_internal_flow(struct ofproto_dpif *ofproto,
>          .flags = OFPUTIL_FF_HIDDEN_FIELDS | OFPUTIL_FF_NO_READONLY,
>          .command = OFPFC_DELETE_STRICT,
>      };
> -
> +    minimatch_init(&fm.match, match);
>      error = ofproto_flow_mod(&ofproto->up, &fm);
> +    minimatch_destroy(&fm.match);
> +
>      if (error) {
>          VLOG_ERR_RL(&rl, "failed to delete internal flow (%s)",
>                      ofperr_to_string(error));
> diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
> index 90432fa2678b..47bf7f9f0ac2 100644
> --- a/ofproto/ofproto-dpif.h
> +++ b/ofproto/ofproto-dpif.h
> @@ -335,7 +335,7 @@ struct ofport_dpif *ofp_port_to_ofport(const struct ofproto_dpif *,
>                                         ofp_port_t);
> 
>  int ofproto_dpif_add_internal_flow(struct ofproto_dpif *,
> -                                   const struct match *, int priority,
> +                                   struct match *, int priority,
>                                     uint16_t idle_timeout,
>                                     const struct ofpbuf *ofpacts,
>                                     struct rule **rulep);
> diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
> index 92d4622b3290..d636fb35c4f9 100644
> --- a/ofproto/ofproto-provider.h
> +++ b/ofproto/ofproto-provider.h
> @@ -1199,7 +1199,7 @@ struct ofproto_class {
>       *
>       * If this function is NULL then table 0 is always chosen. */
>      enum ofperr (*rule_choose_table)(const struct ofproto *ofproto,
> -                                     const struct match *match,
> +                                     const struct minimatch *match,
>                                       uint8_t *table_idp);
> 
>      /* Life-cycle functions for a "struct rule".
> diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
> index 57ce3c81fd22..36f4c0b16d48 100644
> --- a/ofproto/ofproto.c
> +++ b/ofproto/ofproto.c
> @@ -133,7 +133,7 @@ static void eviction_group_remove_rule(struct rule *)
>      OVS_REQUIRES(ofproto_mutex);
> 
>  static void rule_criteria_init(struct rule_criteria *, uint8_t table_id,
> -                               const struct match *match, int priority,
> +                               const struct minimatch *match, int priority,
>                                 ovs_version_t version,
>                                 ovs_be64 cookie, ovs_be64 cookie_mask,
>                                 ofp_port_t out_port, uint32_t out_group);
> @@ -2104,7 +2104,6 @@ flow_mod_init(struct ofputil_flow_mod *fm,
>                enum ofp_flow_mod_command command)
>  {
>      *fm = (struct ofputil_flow_mod) {
> -        .match = *match,
>          .priority = priority,
>          .table_id = 0,
>          .command = command,
> @@ -2114,6 +2113,7 @@ flow_mod_init(struct ofputil_flow_mod *fm,
>          .ofpacts = CONST_CAST(struct ofpact *, ofpacts),
>          .ofpacts_len = ofpacts_len,
>      };
> +    minimatch_init(&fm->match, match);
>  }
> 
>  static int
> @@ -2123,10 +2123,10 @@ simple_flow_mod(struct ofproto *ofproto,
>                  enum ofp_flow_mod_command command)
>  {
>      struct ofputil_flow_mod fm;
> -
>      flow_mod_init(&fm, match, priority, ofpacts, ofpacts_len, command);
> -
> -    return handle_flow_mod__(ofproto, &fm, NULL);
> +    enum ofperr error = handle_flow_mod__(ofproto, &fm, NULL);
> +    minimatch_destroy(&fm.match);
> +    return error;
>  }
> 
>  /* Adds a flow to OpenFlow flow table 0 in 'p' that matches 'cls_rule' and
> @@ -3173,12 +3173,12 @@ learned_cookies_flush(struct ofproto *ofproto, struct ovs_list *dead_cookies)
>  {
>      struct learned_cookie *c;
> 
> +    struct minimatch match;
> +
> +    minimatch_init_catchall(&match);
>      LIST_FOR_EACH_POP (c, u.list_node, dead_cookies) {
>          struct rule_criteria criteria;
>          struct rule_collection rules;
> -        struct match match;
> -
> -        match_init_catchall(&match);
>          rule_criteria_init(&criteria, c->table_id, &match, 0, OVS_VERSION_MAX,
>                             c->cookie, OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
>          rule_criteria_require_rw(&criteria, false);
> @@ -3188,6 +3188,7 @@ learned_cookies_flush(struct ofproto *ofproto, struct ovs_list *dead_cookies)
> 
>          free(c);
>      }
> +    minimatch_destroy(&match);
>  }
>  

>  static enum ofperr
> @@ -4051,13 +4052,13 @@ next_matching_table(const struct ofproto *ofproto,
>   * supplied as 0. */
>  static void
>  rule_criteria_init(struct rule_criteria *criteria, uint8_t table_id,
> -                   const struct match *match, int priority,
> +                   const struct minimatch *match, int priority,
>                     ovs_version_t version, ovs_be64 cookie,
>                     ovs_be64 cookie_mask, ofp_port_t out_port,
>                     uint32_t out_group)
>  {
>      criteria->table_id = table_id;
> -    cls_rule_init(&criteria->cr, match, priority);
> +    cls_rule_init_from_minimatch(&criteria->cr, match, priority);
>      criteria->version = version;
>      criteria->cookie = cookie;
>      criteria->cookie_mask = cookie_mask;
> @@ -4303,9 +4304,12 @@ handle_flow_stats_request(struct ofconn *ofconn,
>          return error;
>      }
> 
> -    rule_criteria_init(&criteria, fsr.table_id, &fsr.match, 0, OVS_VERSION_MAX,
> +    struct minimatch match;
> +    minimatch_init(&match, &fsr.match);
> +    rule_criteria_init(&criteria, fsr.table_id, &match, 0, OVS_VERSION_MAX,
>                         fsr.cookie, fsr.cookie_mask, fsr.out_port,
>                         fsr.out_group);
> +    minimatch_destroy(&match);
> 
>      ovs_mutex_lock(&ofproto_mutex);
>      error = collect_rules_loose(ofproto, &criteria, &rules);
> @@ -4470,9 +4474,12 @@ handle_aggregate_stats_request(struct ofconn *ofconn,
>          return error;
>      }
> 
> -    rule_criteria_init(&criteria, request.table_id, &request.match, 0,
> +    struct minimatch match;
> +    minimatch_init(&match, &request.match);
> +    rule_criteria_init(&criteria, request.table_id, &match, 0,
>                         OVS_VERSION_MAX, request.cookie, request.cookie_mask,
>                         request.out_port, request.out_group);
> +    minimatch_destroy(&match);
> 
>      ovs_mutex_lock(&ofproto_mutex);
>      error = collect_rules_loose(ofproto, &criteria, &rules);
> @@ -4746,22 +4753,22 @@ add_flow_init(struct ofproto *ofproto, struct ofproto_flow_mod *ofm,
>      }
> 
>      if (!(fm->flags & OFPUTIL_FF_HIDDEN_FIELDS)
> -        && !match_has_default_hidden_fields(&fm->match)) {
> +        && !minimatch_has_default_hidden_fields(&fm->match)) {
>          VLOG_WARN_RL(&rl, "%s: (add_flow) only internal flows can set "
>                       "non-default values to hidden fields", ofproto->name);
>          return OFPERR_OFPBRC_EPERM;
>      }
> 
>      if (!ofm->temp_rule) {
> -        cls_rule_init(&cr, &fm->match, fm->priority);
> +        cls_rule_init_from_minimatch(&cr, &fm->match, fm->priority);
> 
>          /* Allocate new rule.  Destroys 'cr'. */
> +        uint64_t map = miniflow_get_tun_metadata_present_map(fm->match.flow);
>          error = ofproto_rule_create(ofproto, &cr, table - ofproto->tables,
>                                      fm->new_cookie, fm->idle_timeout,
>                                      fm->hard_timeout, fm->flags,
>                                      fm->importance, fm->ofpacts,
> -                                    fm->ofpacts_len,
> -                                    fm->match.flow.tunnel.metadata.present.map,
> +                                    fm->ofpacts_len, map,
>                                      fm->ofpacts_tlv_bitmap, &ofm->temp_rule);
>          if (error) {
>              return error;
> @@ -4958,7 +4965,7 @@ ofproto_flow_mod_init_for_learn(struct ofproto *ofproto,
>      struct oftable *table = &ofproto->tables[fm->table_id];
>      struct rule *rule;
> 
> -    rule = rule_from_cls_rule(classifier_find_match_exactly(
> +    rule = rule_from_cls_rule(classifier_find_minimatch_exactly(
>                                    &table->cls, &fm->match, fm->priority,
>                                    OVS_VERSION_MAX));
>      if (rule) {
> @@ -5108,12 +5115,14 @@ ofproto_flow_mod_learn(struct ofproto_flow_mod *ofm, bool keep_ref,
>          if (limit) {
>              struct rule_criteria criteria;
>              struct rule_collection rules;
> -            struct match match;
> +            struct minimatch match;
> 
> -            match_init_catchall(&match);
> +            minimatch_init_catchall(&match);
>              rule_criteria_init(&criteria, rule->table_id, &match, 0,
>                                 OVS_VERSION_MAX, rule->flow_cookie,
>                                 OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
> +            minimatch_destroy(&match);
> +
>              rule_criteria_require_rw(&criteria, false);
>              collect_rules_loose(rule->ofproto, &criteria, &rules);
>              if (rule_collection_n(&rules) >= limit) {
> @@ -5797,6 +5806,7 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
>      if (!error) {
>          struct openflow_mod_requester req = { ofconn, oh };
>          error = handle_flow_mod__(ofproto, &fm, &req);
> +        minimatch_destroy(&fm.match);
>      }
> 
>      ofpbuf_uninit(&ofpacts);
> @@ -7921,6 +7931,7 @@ handle_bundle_add(struct ofconn *ofconn, const struct ofp_header *oh)
>                                          ofproto->n_tables);
>          if (!error) {
>              error = ofproto_flow_mod_init(ofproto, &bmsg->ofm, &fm, NULL);
> +            minimatch_destroy(&fm.match);
>          }
>      } else if (type == OFPTYPE_GROUP_MOD) {
>          error = ofputil_decode_group_mod(badd.msg, &bmsg->ogm.gm);
> diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
> index 8d6d1b6ae8c0..83340bbf2b30 100644
> --- a/ovn/controller/ofctrl.c
> +++ b/ovn/controller/ofctrl.c
> @@ -58,7 +58,7 @@ struct ovn_flow {
>      /* Key. */
>      uint8_t table_id;
>      uint16_t priority;
> -    struct match match;
> +    struct minimatch match;
> 
>      /* Data. */
>      struct ofpact *ofpacts;
> @@ -373,14 +373,16 @@ error:
>  static void
>  run_S_CLEAR_FLOWS(void)
>  {
> +    VLOG_DBG("clearing all flows");
> +
>      /* Send a flow_mod to delete all flows. */
>      struct ofputil_flow_mod fm = {
> -        .match = MATCH_CATCHALL_INITIALIZER,
>          .table_id = OFPTT_ALL,
>          .command = OFPFC_DELETE,
>      };
> +    minimatch_init_catchall(&fm.match);
>      queue_msg(encode_flow_mod(&fm));
> -    VLOG_DBG("clearing all flows");
> +    minimatch_destroy(&fm.match);
> 
>      /* Clear installed_flows, to match the state of the switch. */
>      ovn_flow_table_clear(&installed_flows);
> @@ -630,13 +632,12 @@ ofctrl_recv(const struct ofp_header *oh, enum ofptype type)
>  void
>  ofctrl_add_flow(struct hmap *desired_flows,
>                  uint8_t table_id, uint16_t priority, uint64_t cookie,
> -                const struct match *match,
> -                const struct ofpbuf *actions)
> +                const struct match *match, const struct ofpbuf *actions)
>  {
>      struct ovn_flow *f = xmalloc(sizeof *f);
>      f->table_id = table_id;
>      f->priority = priority;
> -    f->match = *match;
> +    minimatch_init(&f->match, match);
>      f->ofpacts = xmemdup(actions->data, actions->size);
>      f->ofpacts_len = actions->size;
>      f->hmap_node.hash = ovn_flow_hash(f);
> @@ -665,7 +666,7 @@ static uint32_t
>  ovn_flow_hash(const struct ovn_flow *f)
>  {
>      return hash_2words((f->table_id << 16) | f->priority,
> -                       match_hash(&f->match, 0));
> +                       minimatch_hash(&f->match, 0));
> 
>  }
> 
> @@ -676,7 +677,7 @@ ofctrl_dup_flow(struct ovn_flow *src)
>      struct ovn_flow *dst = xmalloc(sizeof *dst);
>      dst->table_id = src->table_id;
>      dst->priority = src->priority;
> -    dst->match = src->match;
> +    minimatch_clone(&dst->match, &src->match);
>      dst->ofpacts = xmemdup(src->ofpacts, src->ofpacts_len);
>      dst->ofpacts_len = src->ofpacts_len;
>      dst->hmap_node.hash = ovn_flow_hash(dst);
> @@ -694,7 +695,7 @@ ovn_flow_lookup(struct hmap *flow_table, const struct ovn_flow *target)
>                               flow_table) {
>          if (f->table_id == target->table_id
>              && f->priority == target->priority
> -            && match_equal(&f->match, &target->match)) {
> +            && minimatch_equal(&f->match, &target->match)) {
>              return f;
>          }
>      }
> @@ -707,7 +708,7 @@ ovn_flow_to_string(const struct ovn_flow *f)
>      struct ds s = DS_EMPTY_INITIALIZER;
>      ds_put_format(&s, "table_id=%"PRIu8", ", f->table_id);
>      ds_put_format(&s, "priority=%"PRIu16", ", f->priority);
> -    match_format(&f->match, NULL, &s, OFP_DEFAULT_PRIORITY);
> +    minimatch_format(&f->match, NULL, NULL, &s, OFP_DEFAULT_PRIORITY);
>      ds_put_cstr(&s, ", actions=");
>      struct ofpact_format_params fp = { .s = &s };
>      ofpacts_format(f->ofpacts, f->ofpacts_len, &fp);
> @@ -728,6 +729,7 @@ static void
>  ovn_flow_destroy(struct ovn_flow *f)
>  {
>      if (f) {
> +        minimatch_destroy(&f->match);
>          free(f->ofpacts);
>          free(f);
>      }
> diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
> index b5e2b409328e..4d9e8743e885 100644
> --- a/utilities/ovs-ofctl.c
> +++ b/utilities/ovs-ofctl.c
> @@ -1747,6 +1747,7 @@ ofctl_flow_mod__(const char *remote, struct ofputil_flow_mod *fms,
> 
>          transact_noreply(vconn, ofputil_encode_flow_mod(fm, protocol));
>          free(CONST_CAST(struct ofpact *, fm->ofpacts));
> +        minimatch_destroy(&fm->match);
>      }
>      vconn_close(vconn);
>  }
> @@ -3309,7 +3310,7 @@ struct fte_version {
>  /* A FTE entry that has been queued for later insertion after all
>   * flows have been scanned to correctly allocation tunnel metadata. */
>  struct fte_pending {
> -    struct match *match;
> +    struct minimatch match;
>      int priority;
>      struct fte_version *version;
>      int index;
> @@ -3442,14 +3443,14 @@ fte_free_all(struct flow_tables *tables)
>   *
>   * Takes ownership of 'version'. */
>  static void
> -fte_insert(struct flow_tables *tables, const struct match *match,
> +fte_insert(struct flow_tables *tables, const struct minimatch *match,
>             int priority, struct fte_version *version, int index)
>  {
>      struct classifier *cls = &tables->tables[version->table_id];
>      struct fte *old, *fte;
> 
>      fte = xzalloc(sizeof *fte);
> -    cls_rule_init(&fte->rule, match, priority);
> +    cls_rule_init_from_minimatch(&fte->rule, match, priority);
>      fte->versions[index] = version;
> 
>      old = fte_from_cls_rule(classifier_replace(cls, &fte->rule,
> @@ -3498,40 +3499,45 @@ generate_tun_metadata(struct fte_state *state)
>   * can just read the data from the match and rewrite it. On rewrite, it
>   * will use the new table. */
>  static void
> -remap_match(struct fte_state *state, struct match *match)
> +remap_match(struct fte_state *state, struct minimatch *minimatch)
>  {
>      int i;
> 
> -    if (!match->tun_md.valid) {
> +    if (!minimatch->tun_md || !minimatch->tun_md->valid) {
>          return;
>      }
> 
> -    struct tun_metadata flow = match->flow.tunnel.metadata;
> -    struct tun_metadata flow_mask = match->wc.masks.tunnel.metadata;
> -    memset(&match->flow.tunnel.metadata, 0, sizeof match->flow.tunnel.metadata);
> -    memset(&match->wc.masks.tunnel.metadata, 0,
> -           sizeof match->wc.masks.tunnel.metadata);
> -    match->tun_md.valid = false;
> +    struct match match;
> +    minimatch_expand(minimatch, &match);
> +
> +    struct tun_metadata flow = match.flow.tunnel.metadata;
> +    struct tun_metadata flow_mask = match.wc.masks.tunnel.metadata;
> +    memset(&match.flow.tunnel.metadata, 0, sizeof match.flow.tunnel.metadata);
> +    memset(&match.wc.masks.tunnel.metadata, 0,
> +           sizeof match.wc.masks.tunnel.metadata);
> +    match.tun_md.valid = false;
> 
> -    match->flow.tunnel.metadata.tab = state->tun_tab;
> -    match->wc.masks.tunnel.metadata.tab = match->flow.tunnel.metadata.tab;
> +    match.flow.tunnel.metadata.tab = state->tun_tab;
> +    match.wc.masks.tunnel.metadata.tab = match.flow.tunnel.metadata.tab;
> 
>      ULLONG_FOR_EACH_1 (i, flow_mask.present.map) {
>          const struct mf_field *field = mf_from_id(MFF_TUN_METADATA0 + i);
> -        int offset = match->tun_md.entry[i].loc.c.offset;
> -        int len = match->tun_md.entry[i].loc.len;
> +        int offset = match.tun_md.entry[i].loc.c.offset;
> +        int len = match.tun_md.entry[i].loc.len;
>          union mf_value value, mask;
> 
>          memset(&value, 0, field->n_bytes - len);
> -        memset(&mask, match->tun_md.entry[i].masked ? 0 : 0xff,
> +        memset(&mask, match.tun_md.entry[i].masked ? 0 : 0xff,
>                 field->n_bytes - len);
> 
>          memcpy(value.tun_metadata + field->n_bytes - len,
>                 flow.opts.u8 + offset, len);
>          memcpy(mask.tun_metadata + field->n_bytes - len,
>                 flow_mask.opts.u8 + offset, len);
> -        mf_set(field, &value, &mask, match, NULL);
> +        mf_set(field, &value, &mask, &match, NULL);
>      }
> +    minimatch_destroy(minimatch);
> +    minimatch_init(minimatch, &match);
>  }
> 
>  /* In order to correctly handle tunnel metadata, we need to have
> @@ -3578,25 +3584,26 @@ fte_state_destroy(struct fte_state *state)
>   * fte_state_init(). fte_queue() is the first pass to be called as each
>   * flow is read from its source. */
>  static void
> -fte_queue(struct fte_state *state, const struct match *match,
> +fte_queue(struct fte_state *state, const struct minimatch *match,
>            int priority, struct fte_version *version, int index)
>  {
>      struct fte_pending *pending = xmalloc(sizeof *pending);
>      int i;
> 
> -    pending->match = xmemdup(match, sizeof *match);
> +    minimatch_clone(&pending->match, match);
>      pending->priority = priority;
>      pending->version = version;
>      pending->index = index;
>      ovs_list_push_back(&state->fte_pending_list, &pending->list_node);
> 
> -    if (!match->tun_md.valid) {
> +    if (!match->tun_md || !match->tun_md->valid) {
>          return;
>      }
> 
> -    ULLONG_FOR_EACH_1 (i, match->wc.masks.tunnel.metadata.present.map) {
> -        if (match->tun_md.entry[i].loc.len > state->tun_metadata_size[i]) {
> -            state->tun_metadata_size[i] = match->tun_md.entry[i].loc.len;
> +    uint64_t map = miniflow_get_tun_metadata_present_map(&match->mask->masks);
> +    ULLONG_FOR_EACH_1 (i, map) {
> +        if (match->tun_md->entry[i].loc.len > state->tun_metadata_size[i]) {
> +            state->tun_metadata_size[i] = match->tun_md->entry[i].loc.len;
>          }
>      }
>  }
> @@ -3615,10 +3622,10 @@ fte_fill(struct fte_state *state, struct flow_tables *tables)
>      flow_tables_defer(tables);
> 
>      LIST_FOR_EACH_POP(pending, list_node, &state->fte_pending_list) {
> -        remap_match(state, pending->match);
> -        fte_insert(tables, pending->match, pending->priority, pending->version,
> -                   pending->index);
> -        free(pending->match);
> +        remap_match(state, &pending->match);
> +        fte_insert(tables, &pending->match, pending->priority,
> +                   pending->version, pending->index);
> +        minimatch_destroy(&pending->match);
>          free(pending);
>      }
> 
> @@ -3669,6 +3676,8 @@ read_flows_from_file(const char *filename, struct fte_state *state, int index)
>          version->table_id = fm.table_id != OFPTT_ALL ? fm.table_id : 0;
> 
>          fte_queue(state, &fm.match, fm.priority, version, index);
> +
> +        minimatch_destroy(&fm.match);
>      }
>      ds_destroy(&s);
> 
> @@ -3714,7 +3723,10 @@ read_flows_from_switch(struct vconn *vconn,
>          version->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len);
>          version->table_id = fs->table_id;
> 
> -        fte_queue(state, &fs->match, fs->priority, version, index);
> +        struct minimatch match;
> +        minimatch_init(&match, &fs->match);
> +        fte_queue(state, &match, fs->priority, version, index);
> +        minimatch_destroy(&match);
>      }
> 
>      for (size_t i = 0; i < n_fses; i++) {
> @@ -3744,7 +3756,7 @@ fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
>          .out_group = OFPG_ANY,
>          .flags = version->flags,
>      };
> -    minimatch_expand(&fte->rule.match, &fm.match);
> +    minimatch_clone(&fm.match, &fte->rule.match);
>      if (command == OFPFC_ADD || command == OFPFC_MODIFY ||
>          command == OFPFC_MODIFY_STRICT) {
>          fm.ofpacts = version->ofpacts;
> @@ -3753,8 +3765,9 @@ fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
>          fm.ofpacts = NULL;
>          fm.ofpacts_len = 0;
>      }
> -
>      ofm = ofputil_encode_flow_mod(&fm, protocol);
> +    minimatch_destroy(&fm.match);
> +
>      ovs_list_push_back(packets, &ofm->list_node);
>  }
> 
> @@ -4028,6 +4041,7 @@ ofctl_parse_flows__(struct ofputil_flow_mod *fms, size_t n_fms,
>          ofpbuf_delete(msg);
> 
>          free(CONST_CAST(struct ofpact *, fm->ofpacts));
> +        minimatch_destroy(&fm->match);
>      }
>  }
> 
> @@ -4504,10 +4518,13 @@ ofctl_check_vlan(struct ovs_cmdl_context *ctx)
>      if (error_s) {
>          ovs_fatal(0, "%s", error_s);
>      }
> +    struct match fm_match;
> +    minimatch_expand(&fm.match, &fm_match);
>      printf("%04"PRIx16"/%04"PRIx16"\n",
> -           ntohs(fm.match.flow.vlans[0].tci),
> -           ntohs(fm.match.wc.masks.vlans[0].tci));
> +           ntohs(fm_match.flow.vlans[0].tci),
> +           ntohs(fm_match.wc.masks.vlans[0].tci));
>      free(string_s);
> +    minimatch_destroy(&fm.match);
> 
>      /* Convert to and from NXM. */
>      ofpbuf_init(&nxm, 0);
> --
> 2.16.1
> 
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Armando M. March 29, 2018, 6:58 a.m. UTC | #3
On 20 March 2018 at 13:46, Ben Pfaff <blp@ovn.org> wrote:

> Until now, struct ofputil_flow_mod, which represents an OpenFlow flow table
> modification request, has incorporated a struct match, which made the
> overall ofputil_flow_mod about 2.5 kB.  This is OK for a small number of
> flows, but absurdly inflates memory requirements when there are hundreds of
> thousands of flows.  This commit fixes the problem by changing struct match
> to struct minimatch inside ofputil_flow_mod, which reduces its size to
> about 100 bytes plus the actual size of the flow match (usually a few dozen
> bytes).
>
> This affects memory usage of ovs-ofctl (when it adds a large number of
> flows) more than ovs-vswitchd.
>
> Reported-by: Michael Ben-Ami <mbenami@digitalocean.com>
> Signed-off-by: Ben Pfaff <blp@ovn.org>
> ---
>  AUTHORS.rst                    |  1 +
>  include/openvswitch/ofp-flow.h |  2 +-
>  lib/learn.c                    | 12 ++++--
>  lib/learning-switch.c          | 14 +++++--
>  lib/ofp-bundle.c               |  1 +
>  lib/ofp-flow.c                 | 89 ++++++++++++++++++++++++++----
> ------------
>  ofproto/ofproto-dpif-xlate.c   |  6 ++-
>  ofproto/ofproto-dpif.c         | 17 ++++----
>  ofproto/ofproto-dpif.h         |  2 +-
>  ofproto/ofproto-provider.h     |  2 +-
>  ofproto/ofproto.c              | 49 ++++++++++++++---------
>  ovn/controller/ofctrl.c        | 22 ++++++-----
>  utilities/ovs-ofctl.c          | 81 +++++++++++++++++++++++-------
> --------
>  13 files changed, 185 insertions(+), 113 deletions(-)
>
> diff --git a/AUTHORS.rst b/AUTHORS.rst
> index dc69ba3f3c1f..81f9b6f28b88 100644
> --- a/AUTHORS.rst
> +++ b/AUTHORS.rst
> @@ -506,6 +506,7 @@ Marvin Pascual                  marvin@pascual.com.ph
>  Maxime Brun                     m.brun@alphalink.fr
>  Madhu Venugopal                 mavenugo@gmail.com
>  Michael A. Collins              mike.a.collins@ark-net.org
> +Michael Ben-Ami                 mbenami@digitalocean.com
>  Michael Hu                      mhu@nicira.com
>  Michael J. Smalley              michaeljsmalley@gmail.com
>  Michael Mao                     mmao@nicira.com
> diff --git a/include/openvswitch/ofp-flow.h b/include/openvswitch/ofp-flow
> .h
> index 17d48f12e060..2ff2e45b66c4 100644
> --- a/include/openvswitch/ofp-flow.h
> +++ b/include/openvswitch/ofp-flow.h
> @@ -73,7 +73,7 @@ void ofputil_flow_mod_flags_format(struct ds *, enum
> ofputil_flow_mod_flags);
>  struct ofputil_flow_mod {
>      struct ovs_list list_node; /* For queuing flow_mods. */
>
> -    struct match match;
> +    struct minimatch match;
>      int priority;
>
>      /* Cookie matching.  The flow_mod affects only flows that have
> cookies that
> diff --git a/lib/learn.c b/lib/learn.c
> index f5d15503fe79..c4d5b3e0c449 100644
> --- a/lib/learn.c
> +++ b/lib/learn.c
> @@ -91,14 +91,17 @@ learn_check(const struct ofpact_learn *learn, const
> struct match *src_match)
>   * 'ofpacts' and retains ownership of it.  'fm->ofpacts' will point into
> the
>   * 'ofpacts' buffer.
>   *
> + * The caller must eventually destroy fm->match.
> + *
>   * The caller has to actually execute 'fm'. */
>  void
>  learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
>                struct ofputil_flow_mod *fm, struct ofpbuf *ofpacts)
>  {
>      const struct ofpact_learn_spec *spec;
> +    struct match match;
>
> -    match_init_catchall(&fm->match);
> +    match_init_catchall(&match);
>      fm->priority = learn->priority;
>      fm->cookie = htonll(0);
>      fm->cookie_mask = htonll(0);
> @@ -140,10 +143,10 @@ learn_execute(const struct ofpact_learn *learn,
> const struct flow *flow,
>
>          switch (spec->dst_type) {
>          case NX_LEARN_DST_MATCH:
> -            mf_write_subfield(&spec->dst, &value, &fm->match);
> -            match_add_ethernet_prereq(&fm->match, spec->dst.field);
> +            mf_write_subfield(&spec->dst, &value, &match);
> +            match_add_ethernet_prereq(&match, spec->dst.field);
>              mf_vl_mff_set_tlv_bitmap(
> -                spec->dst.field, &fm->match.flow.tunnel.metadat
> a.present.map);
> +                spec->dst.field, &match.flow.tunnel.metadata.pr
> esent.map);
>              break;
>
>          case NX_LEARN_DST_LOAD:
> @@ -173,6 +176,7 @@ learn_execute(const struct ofpact_learn *learn, const
> struct flow *flow,
>          }
>      }
>
> +    minimatch_init(&fm->match, &match);
>      fm->ofpacts = ofpacts->data;
>      fm->ofpacts_len = ofpacts->size;
>  }
> diff --git a/lib/learning-switch.c b/lib/learning-switch.c
> index 3a9e015bbe9c..04eaa6e8e73f 100644
> --- a/lib/learning-switch.c
> +++ b/lib/learning-switch.c
> @@ -206,7 +206,6 @@ lswitch_handshake(struct lswitch *sw)
>          output.max_len = OFP_DEFAULT_MISS_SEND_LEN;
>
>          struct ofputil_flow_mod fm = {
> -            .match = MATCH_CATCHALL_INITIALIZER,
>              .priority = 0,
>              .table_id = 0,
>              .command = OFPFC_ADD,
> @@ -216,8 +215,10 @@ lswitch_handshake(struct lswitch *sw)
>              .ofpacts = &output.ofpact,
>              .ofpacts_len = sizeof output,
>          };
> -
> +        minimatch_init_catchall(&fm.match);
>          msg = ofputil_encode_flow_mod(&fm, protocol);
> +        minimatch_destroy(&fm.match);
> +
>          error = rconn_send(sw->rconn, msg, NULL);
>          if (error) {
>              VLOG_INFO_RL(&rl, "%s: failed to add default flow (%s)",
> @@ -595,11 +596,16 @@ process_packet_in(struct lswitch *sw, const struct
> ofp_header *oh)
>              .ofpacts = ofpacts.data,
>              .ofpacts_len = ofpacts.size,
>          };
> -        match_init(&fm.match, &flow, &sw->wc);
> -        ofputil_normalize_match_quiet(&fm.match);
> +
> +        struct match match;
> +        match_init(&match, &flow, &sw->wc);
> +        ofputil_normalize_match_quiet(&match);
> +        minimatch_init(&fm.match, &match);
>
>          struct ofpbuf *buffer = ofputil_encode_flow_mod(&fm,
> sw->protocol);
>
> +        minimatch_destroy(&fm.match);
> +
>          queue_tx(sw, buffer);
>
>          /* If the switch didn't buffer the packet, we need to send a
> copy. */
> diff --git a/lib/ofp-bundle.c b/lib/ofp-bundle.c
> index 0921c81bfb15..8f07a30c51f7 100644
> --- a/lib/ofp-bundle.c
> +++ b/lib/ofp-bundle.c
> @@ -33,6 +33,7 @@ ofputil_free_bundle_msgs(struct ofputil_bundle_msg
> *bms, size_t n_bms)
>          switch ((int)bms[i].type) {
>          case OFPTYPE_FLOW_MOD:
>              free(CONST_CAST(struct ofpact *, bms[i].fm.ofpacts));
> +            minimatch_destroy(&bms[i].fm.match);
>              break;
>          case OFPTYPE_GROUP_MOD:
>              ofputil_uninit_group_mod(&bms[i].gm);
> diff --git a/lib/ofp-flow.c b/lib/ofp-flow.c
> index cffadb30381b..d07556245b1f 100644
> --- a/lib/ofp-flow.c
> +++ b/lib/ofp-flow.c
> @@ -139,6 +139,8 @@ ofputil_flow_mod_flags_format(struct ds *s, enum
> ofputil_flow_mod_flags flags)
>   * The caller must initialize 'ofpacts' and retains ownership of it.
>   * 'fm->ofpacts' will point into the 'ofpacts' buffer.
>   *
> + * On success, the caller must eventually destroy fm->match.
> + *
>   * Does not validate the flow_mod actions.  The caller should do that,
> with
>   * ofpacts_check(). */
>  enum ofperr
> @@ -152,6 +154,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>  {
>      ovs_be16 raw_flags;
>      enum ofperr error;
> +    struct match match;
>      struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
>      enum ofpraw raw = ofpraw_pull_assert(&b);
>      if (raw == OFPRAW_OFPT11_FLOW_MOD) {
> @@ -160,7 +163,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>
>          ofm = ofpbuf_pull(&b, sizeof *ofm);
>
> -        error = ofputil_pull_ofp11_match(&b, tun_table, vl_mff_map,
> &fm->match,
> +        error = ofputil_pull_ofp11_match(&b, tun_table, vl_mff_map,
> &match,
>                                           NULL);
>          if (error) {
>              return error;
> @@ -228,8 +231,8 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>              ofm = ofpbuf_pull(&b, sizeof *ofm);
>
>              /* Translate the rule. */
> -            ofputil_match_from_ofp10_match(&ofm->match, &fm->match);
> -            ofputil_normalize_match(&fm->match);
> +            ofputil_match_from_ofp10_match(&ofm->match, &match);
> +            ofputil_normalize_match(&match);
>
>              /* OpenFlow 1.0 says that exact-match rules have to have the
>               * highest possible priority. */
> @@ -256,7 +259,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>              /* Dissect the message. */
>              nfm = ofpbuf_pull(&b, sizeof *nfm);
>              error = nx_pull_match(&b, ntohs(nfm->match_len),
> -                                  &fm->match, &fm->cookie,
> &fm->cookie_mask,
> +                                  &match, &fm->cookie, &fm->cookie_mask,
>                                    false, tun_table, vl_mff_map);
>              if (error) {
>                  return error;
> @@ -269,6 +272,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>                   * existing cookie. */
>                  return OFPERR_NXBRC_NXM_INVALID;
>              }
> +            minimatch_init(&fm->match, &match);
>              fm->priority = ntohs(nfm->priority);
>              fm->new_cookie = nfm->cookie;
>              fm->idle_timeout = ntohs(nfm->idle_timeout);
> @@ -298,11 +302,11 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>
>      /* Check for mismatched conntrack original direction tuple address
> fields
>       * w.r.t. the IP version of the match. */
> -    if (((fm->match.wc.masks.ct_nw_src || fm->match.wc.masks.ct_nw_dst)
> -         && fm->match.flow.dl_type != htons(ETH_TYPE_IP))
> -        || ((ipv6_addr_is_set(&fm->match.wc.masks.ct_ipv6_src)
> -             || ipv6_addr_is_set(&fm->match.wc.masks.ct_ipv6_dst))
> -            && fm->match.flow.dl_type != htons(ETH_TYPE_IPV6))) {
> +    if (((match.wc.masks.ct_nw_src || match.wc.masks.ct_nw_dst)
> +         && match.flow.dl_type != htons(ETH_TYPE_IP))
> +        || ((ipv6_addr_is_set(&match.wc.masks.ct_ipv6_src)
> +             || ipv6_addr_is_set(&match.wc.masks.ct_ipv6_dst))
> +            && match.flow.dl_type != htons(ETH_TYPE_IPV6))) {
>          return OFPERR_OFPBAC_MATCH_INCONSISTENT;
>      }
>
> @@ -339,9 +343,13 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
>                  : OFPERR_OFPFMFC_TABLE_FULL);
>      }
>
> -    return ofpacts_check_consistency(fm->ofpacts, fm->ofpacts_len,
> -                                     &fm->match, max_port,
> -                                     fm->table_id, max_table, protocol);
> +    error = ofpacts_check_consistency(fm->ofpacts, fm->ofpacts_len,
> +                                      &match, max_port,
> +                                      fm->table_id, max_table, protocol);
> +    if (!error) {
> +        minimatch_init(&fm->match, &match);
> +    }
> +    return error;
>  }
>
>  static ovs_be16
> @@ -363,6 +371,9 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod
> *fm,
>      ovs_be16 raw_flags = ofputil_encode_flow_mod_flags(fm->flags,
> version);
>      struct ofpbuf *msg;
>
> +    struct match match;
> +    minimatch_expand(&fm->match, &match);
> +
>      switch (protocol) {
>      case OFPUTIL_P_OF11_STD:
>      case OFPUTIL_P_OF12_OXM:
> @@ -407,7 +418,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod
> *fm,
>          } else {
>              ofm->importance = 0;
>          }
> -        ofputil_put_ofp11_match(msg, &fm->match, protocol);
> +        ofputil_put_ofp11_match(msg, &match, protocol);
>          ofpacts_put_openflow_instructions(fm->ofpacts, fm->ofpacts_len,
> msg,
>                                            version);
>          break;
> @@ -420,7 +431,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod
> *fm,
>          msg = ofpraw_alloc(OFPRAW_OFPT10_FLOW_MOD, OFP10_VERSION,
>                             fm->ofpacts_len);
>          ofm = ofpbuf_put_zeros(msg, sizeof *ofm);
> -        ofputil_match_to_ofp10_match(&fm->match, &ofm->match);
> +        ofputil_match_to_ofp10_match(&match, &ofm->match);
>          ofm->cookie = fm->new_cookie;
>          ofm->command = ofputil_tid_command(fm, protocol);
>          ofm->idle_timeout = htons(fm->idle_timeout);
> @@ -444,7 +455,7 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod
> *fm,
>          nfm = ofpbuf_put_zeros(msg, sizeof *nfm);
>          nfm->command = ofputil_tid_command(fm, protocol);
>          nfm->cookie = fm->new_cookie;
> -        match_len = nx_put_match(msg, &fm->match, fm->cookie,
> fm->cookie_mask);
> +        match_len = nx_put_match(msg, &match, fm->cookie,
> fm->cookie_mask);
>          nfm = msg->msg;
>          nfm->idle_timeout = htons(fm->idle_timeout);
>          nfm->hard_timeout = htons(fm->hard_timeout);
> @@ -536,7 +547,9 @@ ofputil_flow_mod_format(struct ds *s, const struct
> ofp_header *oh,
>          /* nx_match_to_string() doesn't print priority. */
>          need_priority = true;
>      } else {
> -        match_format(&fm.match, port_map, s, fm.priority);
> +        struct match match;
> +        minimatch_expand(&fm.match, &match);
> +        match_format(&match, port_map, s, fm.priority);
>
>          /* match_format() does print priority. */
>          need_priority = false;
> @@ -589,6 +602,7 @@ ofputil_flow_mod_format(struct ds *s, const struct
> ofp_header *oh,
>      };
>      ofpacts_format(fm.ofpacts, fm.ofpacts_len, &fp);
>      ofpbuf_uninit(&ofpacts);
> +    minimatch_destroy(&fm.match);
>
>      return 0;
>  }
> @@ -831,10 +845,13 @@ parse_ofp_flow_stats_request_str(struct
> ofputil_flow_stats_request *fsr,
>      fsr->aggregate = aggregate;
>      fsr->cookie = fm.cookie;
>      fsr->cookie_mask = fm.cookie_mask;
> -    fsr->match = fm.match;
> +    minimatch_expand(&fm.match, &fsr->match);
>      fsr->out_port = fm.out_port;
>      fsr->out_group = fm.out_group;
>      fsr->table_id = fm.table_id;
> +
> +    minimatch_destroy(&fm.match);
> +
>      return NULL;
>  }
>
> @@ -1393,7 +1410,6 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>      }
>
>      *fm = (struct ofputil_flow_mod) {
> -        .match = MATCH_CATCHALL_INITIALIZER,
>          .priority = OFP_DEFAULT_PRIORITY,
>          .table_id = 0xff,
>          .command = command,
> @@ -1413,19 +1429,20 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>          }
>      }
>
> +    struct match match = MATCH_CATCHALL_INITIALIZER;
>      while (ofputil_parse_key_value(&string, &name, &value)) {
>          const struct ofp_protocol *p;
>          const struct mf_field *mf;
>          char *error = NULL;
>
>          if (ofp_parse_protocol(name, &p)) {
> -            match_set_dl_type(&fm->match, htons(p->dl_type));
> +            match_set_dl_type(&match, htons(p->dl_type));
>              if (p->nw_proto) {
> -                match_set_nw_proto(&fm->match, p->nw_proto);
> +                match_set_nw_proto(&match, p->nw_proto);
>              }
> -            match_set_default_packet_type(&fm->match);
> +            match_set_default_packet_type(&match);
>          } else if (!strcmp(name, "eth")) {
> -            match_set_packet_type(&fm->match, htonl(PT_ETH));
> +            match_set_packet_type(&match, htonl(PT_ETH));
>          } else if (fields & F_FLAGS && !strcmp(name, "send_flow_rem")) {
>              fm->flags |= OFPUTIL_FF_SEND_FLOW_REM;
>          } else if (fields & F_FLAGS && !strcmp(name, "check_overlap")) {
> @@ -1444,9 +1461,9 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>               /* ignore these fields. */
>          } else if ((mf = mf_from_name(name)) != NULL) {
>              error = ofp_parse_field(mf, value, port_map,
> -                                    &fm->match, usable_protocols);
> +                                    &match, usable_protocols);
>          } else if (strchr(name, '[')) {
> -            error = parse_subfield(name, value, &fm->match,
> usable_protocols);
> +            error = parse_subfield(name, value, &match, usable_protocols);
>          } else {
>              if (!*value) {
>                  return xasprintf("field %s missing value", name);
> @@ -1532,13 +1549,13 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>      }
>      /* Copy ethertype to flow->dl_type for matches on packet_type
>       * (OFPHTN_ETHERTYPE, ethertype). */
> -    if (fm->match.wc.masks.packet_type == OVS_BE32_MAX &&
> -            pt_ns(fm->match.flow.packet_type) == OFPHTN_ETHERTYPE) {
> -        fm->match.flow.dl_type = pt_ns_type_be(fm->match.flow.p
> acket_type);
> +    if (match.wc.masks.packet_type == OVS_BE32_MAX &&
> +            pt_ns(match.flow.packet_type) == OFPHTN_ETHERTYPE) {
> +        match.flow.dl_type = pt_ns_type_be(match.flow.packet_type);
>      }
>      /* Check for usable protocol interdependencies between match fields.
> */
> -    if (fm->match.flow.dl_type == htons(ETH_TYPE_IPV6)) {
> -        const struct flow_wildcards *wc = &fm->match.wc;
> +    if (match.flow.dl_type == htons(ETH_TYPE_IPV6)) {
> +        const struct flow_wildcards *wc = &match.wc;
>          /* Only NXM and OXM support matching L3 and L4 fields within IPv6.
>           *
>           * (IPv6 specific fields as well as arp_sha, arp_tha, nw_frag, and
> @@ -1574,7 +1591,7 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>          if (!error) {
>              enum ofperr err;
>
> -            err = ofpacts_check(ofpacts.data, ofpacts.size, &fm->match,
> +            err = ofpacts_check(ofpacts.data, ofpacts.size, &match,
>                                  OFPP_MAX, fm->table_id, 255,
> usable_protocols);
>              if (!err && !*usable_protocols) {
>                  err = OFPERR_OFPBAC_MATCH_INCONSISTENT;
> @@ -1596,6 +1613,7 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>          fm->ofpacts_len = 0;
>          fm->ofpacts = NULL;
>      }
> +    minimatch_init(&fm->match, &match);
>
>      return NULL;
>  }
> @@ -1613,7 +1631,10 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int
> command, char *string,
>   * name is treated as "add".
>   *
>   * Returns NULL if successful, otherwise a malloc()'d string describing
> the
> - * error.  The caller is responsible for freeing the returned string. */
> + * error.  The caller is responsible for freeing the returned string.
> + *
> + * On success, the caller is responsible for freeing fm->ofpacts and
> + * fm->match. */
>  char * OVS_WARN_UNUSED_RESULT
>  parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
>                const struct ofputil_port_map *port_map,
> @@ -1657,8 +1678,9 @@ parse_ofp_flow_mod_str(struct ofputil_flow_mod *fm,
> const char *string,
>          /* Normalize a copy of the match.  This ensures that
> non-normalized
>           * flows get logged but doesn't affect what gets sent to the
> switch, so
>           * that the switch can do whatever it likes with the flow. */
> -        struct match match_copy = fm->match;
> -        ofputil_normalize_match(&match_copy);
> +        struct match match;
> +        minimatch_expand(&fm->match, &match);
> +        ofputil_normalize_match(&match);
>      }
>
>      return error;
> @@ -1716,6 +1738,7 @@ parse_ofp_flow_mod_file(const char *file_name,
>
>              for (i = 0; i < *n_fms; i++) {
>                  free(CONST_CAST(struct ofpact *, (*fms)[i].ofpacts));
> +                minimatch_destroy(&(*fms)[i].match);
>              }
>              free(*fms);
>              *fms = NULL;
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index bc6429c25346..a00bed704806 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -5019,7 +5019,9 @@ xlate_learn_action(struct xlate_ctx *ctx, const
> struct ofpact_learn *learn)
>          if (OVS_UNLIKELY(ctx->xin->trace)) {
>              struct ds s = DS_EMPTY_INITIALIZER;
>              ds_put_format(&s, "table=%"PRIu8" ", fm.table_id);
> -            match_format(&fm.match, NULL, &s, OFP_DEFAULT_PRIORITY);
> +            minimatch_format(&fm.match,
> +                             ofproto_get_tun_tab(&ctx->xin->ofproto->up),
> +                             NULL, &s, OFP_DEFAULT_PRIORITY);
>              ds_chomp(&s, ' ');
>              ds_put_format(&s, " priority=%d", fm.priority);
>              if (fm.new_cookie) {
> @@ -5090,6 +5092,8 @@ xlate_learn_action(struct xlate_ctx *ctx, const
> struct ofpact_learn *learn)
>              xlate_report_error(ctx, "LEARN action execution failed (%s).",
>                                 ofperr_to_string(error));
>          }
> +
> +        minimatch_destroy(&fm.match);
>      } else {
>          xlate_report(ctx, OFT_WARN,
>                       "suppressing side effects, so learn action ignored");
> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> index c92c5bea1725..1ed82d036528 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -5582,9 +5582,11 @@ odp_port_to_ofp_port(const struct ofproto_dpif
> *ofproto, odp_port_t odp_port)
>      }
>  }
>
> +/* 'match' is non-const to allow for temporary modifications.  Any
> changes are
> + * restored before returning. */
>  int
>  ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
> -                               const struct match *match, int priority,
> +                               struct match *match, int priority,
>                                 uint16_t idle_timeout,
>                                 const struct ofpbuf *ofpacts,
>                                 struct rule **rulep)
> @@ -5595,7 +5597,6 @@ ofproto_dpif_add_internal_flow(struct ofproto_dpif
> *ofproto,
>
>      fm = (struct ofputil_flow_mod) {
>          .buffer_id = UINT32_MAX,
> -        .match = *match,
>          .priority = priority,
>          .table_id = TBL_INTERNAL,
>          .command = OFPFC_ADD,
> @@ -5604,8 +5605,10 @@ ofproto_dpif_add_internal_flow(struct ofproto_dpif
> *ofproto,
>          .ofpacts = ofpacts->data,
>          .ofpacts_len = ofpacts->size,
>      };
> -
> +    minimatch_init(&fm.match, match);
>      error = ofproto_flow_mod(&ofproto->up, &fm);
> +    minimatch_destroy(&fm.match);
> +
>      if (error) {
>          VLOG_ERR_RL(&rl, "failed to add internal flow (%s)",
>                      ofperr_to_string(error));
> @@ -5615,8 +5618,7 @@ ofproto_dpif_add_internal_flow(struct ofproto_dpif
> *ofproto,
>
>      rule = rule_dpif_lookup_in_table(ofproto,
>                                       ofproto_dpif_get_tables_versio
> n(ofproto),
> -                                     TBL_INTERNAL, &fm.match.flow,
> -                                     &fm.match.wc);
> +                                     TBL_INTERNAL, &match->flow,
> &match->wc);
>      if (rule) {
>          *rulep = &rule->up;
>      } else {
> @@ -5634,7 +5636,6 @@ ofproto_dpif_delete_internal_flow(struct
> ofproto_dpif *ofproto,
>
>      fm = (struct ofputil_flow_mod) {
>          .buffer_id = UINT32_MAX,
> -        .match = *match,
>          .priority = priority,
>          .table_id = TBL_INTERNAL,
>          .out_port = OFPP_ANY,
> @@ -5642,8 +5643,10 @@ ofproto_dpif_delete_internal_flow(struct
> ofproto_dpif *ofproto,
>          .flags = OFPUTIL_FF_HIDDEN_FIELDS | OFPUTIL_FF_NO_READONLY,
>          .command = OFPFC_DELETE_STRICT,
>      };
> -
> +    minimatch_init(&fm.match, match);
>      error = ofproto_flow_mod(&ofproto->up, &fm);
> +    minimatch_destroy(&fm.match);
> +
>      if (error) {
>          VLOG_ERR_RL(&rl, "failed to delete internal flow (%s)",
>                      ofperr_to_string(error));
> diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
> index 90432fa2678b..47bf7f9f0ac2 100644
> --- a/ofproto/ofproto-dpif.h
> +++ b/ofproto/ofproto-dpif.h
> @@ -335,7 +335,7 @@ struct ofport_dpif *ofp_port_to_ofport(const struct
> ofproto_dpif *,
>                                         ofp_port_t);
>
>  int ofproto_dpif_add_internal_flow(struct ofproto_dpif *,
> -                                   const struct match *, int priority,
> +                                   struct match *, int priority,
>                                     uint16_t idle_timeout,
>                                     const struct ofpbuf *ofpacts,
>                                     struct rule **rulep);
> diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
> index 92d4622b3290..d636fb35c4f9 100644
> --- a/ofproto/ofproto-provider.h
> +++ b/ofproto/ofproto-provider.h
> @@ -1199,7 +1199,7 @@ struct ofproto_class {
>       *
>       * If this function is NULL then table 0 is always chosen. */
>      enum ofperr (*rule_choose_table)(const struct ofproto *ofproto,
> -                                     const struct match *match,
> +                                     const struct minimatch *match,
>                                       uint8_t *table_idp);
>
>      /* Life-cycle functions for a "struct rule".
> diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
> index 57ce3c81fd22..36f4c0b16d48 100644
> --- a/ofproto/ofproto.c
> +++ b/ofproto/ofproto.c
> @@ -133,7 +133,7 @@ static void eviction_group_remove_rule(struct rule *)
>      OVS_REQUIRES(ofproto_mutex);
>
>  static void rule_criteria_init(struct rule_criteria *, uint8_t table_id,
> -                               const struct match *match, int priority,
> +                               const struct minimatch *match, int
> priority,
>                                 ovs_version_t version,
>                                 ovs_be64 cookie, ovs_be64 cookie_mask,
>                                 ofp_port_t out_port, uint32_t out_group);
> @@ -2104,7 +2104,6 @@ flow_mod_init(struct ofputil_flow_mod *fm,
>                enum ofp_flow_mod_command command)
>  {
>      *fm = (struct ofputil_flow_mod) {
> -        .match = *match,
>          .priority = priority,
>          .table_id = 0,
>          .command = command,
> @@ -2114,6 +2113,7 @@ flow_mod_init(struct ofputil_flow_mod *fm,
>          .ofpacts = CONST_CAST(struct ofpact *, ofpacts),
>          .ofpacts_len = ofpacts_len,
>      };
> +    minimatch_init(&fm->match, match);
>  }
>
>  static int
> @@ -2123,10 +2123,10 @@ simple_flow_mod(struct ofproto *ofproto,
>                  enum ofp_flow_mod_command command)
>  {
>      struct ofputil_flow_mod fm;
> -
>      flow_mod_init(&fm, match, priority, ofpacts, ofpacts_len, command);
> -
> -    return handle_flow_mod__(ofproto, &fm, NULL);
> +    enum ofperr error = handle_flow_mod__(ofproto, &fm, NULL);
> +    minimatch_destroy(&fm.match);
> +    return error;
>  }
>
>  /* Adds a flow to OpenFlow flow table 0 in 'p' that matches 'cls_rule' and
> @@ -3173,12 +3173,12 @@ learned_cookies_flush(struct ofproto *ofproto,
> struct ovs_list *dead_cookies)
>  {
>      struct learned_cookie *c;
>
> +    struct minimatch match;
> +
> +    minimatch_init_catchall(&match);
>      LIST_FOR_EACH_POP (c, u.list_node, dead_cookies) {
>          struct rule_criteria criteria;
>          struct rule_collection rules;
> -        struct match match;
> -
> -        match_init_catchall(&match);
>          rule_criteria_init(&criteria, c->table_id, &match, 0,
> OVS_VERSION_MAX,
>                             c->cookie, OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
>          rule_criteria_require_rw(&criteria, false);
> @@ -3188,6 +3188,7 @@ learned_cookies_flush(struct ofproto *ofproto,
> struct ovs_list *dead_cookies)
>
>          free(c);
>      }
> +    minimatch_destroy(&match);
>  }
>
>  static enum ofperr
> @@ -4051,13 +4052,13 @@ next_matching_table(const struct ofproto *ofproto,
>   * supplied as 0. */
>  static void
>  rule_criteria_init(struct rule_criteria *criteria, uint8_t table_id,
> -                   const struct match *match, int priority,
> +                   const struct minimatch *match, int priority,
>                     ovs_version_t version, ovs_be64 cookie,
>                     ovs_be64 cookie_mask, ofp_port_t out_port,
>                     uint32_t out_group)
>  {
>      criteria->table_id = table_id;
> -    cls_rule_init(&criteria->cr, match, priority);
> +    cls_rule_init_from_minimatch(&criteria->cr, match, priority);
>      criteria->version = version;
>      criteria->cookie = cookie;
>      criteria->cookie_mask = cookie_mask;
> @@ -4303,9 +4304,12 @@ handle_flow_stats_request(struct ofconn *ofconn,
>          return error;
>      }
>
> -    rule_criteria_init(&criteria, fsr.table_id, &fsr.match, 0,
> OVS_VERSION_MAX,
> +    struct minimatch match;
> +    minimatch_init(&match, &fsr.match);
> +    rule_criteria_init(&criteria, fsr.table_id, &match, 0,
> OVS_VERSION_MAX,
>                         fsr.cookie, fsr.cookie_mask, fsr.out_port,
>                         fsr.out_group);
> +    minimatch_destroy(&match);
>
>      ovs_mutex_lock(&ofproto_mutex);
>      error = collect_rules_loose(ofproto, &criteria, &rules);
> @@ -4470,9 +4474,12 @@ handle_aggregate_stats_request(struct ofconn
> *ofconn,
>          return error;
>      }
>
> -    rule_criteria_init(&criteria, request.table_id, &request.match, 0,
> +    struct minimatch match;
> +    minimatch_init(&match, &request.match);
> +    rule_criteria_init(&criteria, request.table_id, &match, 0,
>                         OVS_VERSION_MAX, request.cookie,
> request.cookie_mask,
>                         request.out_port, request.out_group);
> +    minimatch_destroy(&match);
>
>      ovs_mutex_lock(&ofproto_mutex);
>      error = collect_rules_loose(ofproto, &criteria, &rules);
> @@ -4746,22 +4753,22 @@ add_flow_init(struct ofproto *ofproto, struct
> ofproto_flow_mod *ofm,
>      }
>
>      if (!(fm->flags & OFPUTIL_FF_HIDDEN_FIELDS)
> -        && !match_has_default_hidden_fields(&fm->match)) {
> +        && !minimatch_has_default_hidden_fields(&fm->match)) {
>          VLOG_WARN_RL(&rl, "%s: (add_flow) only internal flows can set "
>                       "non-default values to hidden fields",
> ofproto->name);
>          return OFPERR_OFPBRC_EPERM;
>      }
>
>      if (!ofm->temp_rule) {
> -        cls_rule_init(&cr, &fm->match, fm->priority);
> +        cls_rule_init_from_minimatch(&cr, &fm->match, fm->priority);
>
>          /* Allocate new rule.  Destroys 'cr'. */
> +        uint64_t map = miniflow_get_tun_metadata_pres
> ent_map(fm->match.flow);
>          error = ofproto_rule_create(ofproto, &cr, table - ofproto->tables,
>                                      fm->new_cookie, fm->idle_timeout,
>                                      fm->hard_timeout, fm->flags,
>                                      fm->importance, fm->ofpacts,
> -                                    fm->ofpacts_len,
> -                                    fm->match.flow.tunnel.metadata
> .present.map,
> +                                    fm->ofpacts_len, map,
>                                      fm->ofpacts_tlv_bitmap,
> &ofm->temp_rule);
>          if (error) {
>              return error;
> @@ -4958,7 +4965,7 @@ ofproto_flow_mod_init_for_learn(struct ofproto
> *ofproto,
>      struct oftable *table = &ofproto->tables[fm->table_id];
>      struct rule *rule;
>
> -    rule = rule_from_cls_rule(classifier_find_match_exactly(
> +    rule = rule_from_cls_rule(classifier_find_minimatch_exactly(
>                                    &table->cls, &fm->match, fm->priority,
>                                    OVS_VERSION_MAX));
>      if (rule) {
> @@ -5108,12 +5115,14 @@ ofproto_flow_mod_learn(struct ofproto_flow_mod
> *ofm, bool keep_ref,
>          if (limit) {
>              struct rule_criteria criteria;
>              struct rule_collection rules;
> -            struct match match;
> +            struct minimatch match;
>
> -            match_init_catchall(&match);
> +            minimatch_init_catchall(&match);
>              rule_criteria_init(&criteria, rule->table_id, &match, 0,
>                                 OVS_VERSION_MAX, rule->flow_cookie,
>                                 OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
> +            minimatch_destroy(&match);
> +
>              rule_criteria_require_rw(&criteria, false);
>              collect_rules_loose(rule->ofproto, &criteria, &rules);
>              if (rule_collection_n(&rules) >= limit) {
> @@ -5797,6 +5806,7 @@ handle_flow_mod(struct ofconn *ofconn, const struct
> ofp_header *oh)
>      if (!error) {
>          struct openflow_mod_requester req = { ofconn, oh };
>          error = handle_flow_mod__(ofproto, &fm, &req);
> +        minimatch_destroy(&fm.match);
>      }
>
>      ofpbuf_uninit(&ofpacts);
> @@ -7921,6 +7931,7 @@ handle_bundle_add(struct ofconn *ofconn, const
> struct ofp_header *oh)
>                                          ofproto->n_tables);
>          if (!error) {
>              error = ofproto_flow_mod_init(ofproto, &bmsg->ofm, &fm, NULL);
> +            minimatch_destroy(&fm.match);
>          }
>      } else if (type == OFPTYPE_GROUP_MOD) {
>          error = ofputil_decode_group_mod(badd.msg, &bmsg->ogm.gm);
> diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
> index 8d6d1b6ae8c0..83340bbf2b30 100644
> --- a/ovn/controller/ofctrl.c
> +++ b/ovn/controller/ofctrl.c
> @@ -58,7 +58,7 @@ struct ovn_flow {
>      /* Key. */
>      uint8_t table_id;
>      uint16_t priority;
> -    struct match match;
> +    struct minimatch match;
>
>      /* Data. */
>      struct ofpact *ofpacts;
> @@ -373,14 +373,16 @@ error:
>  static void
>  run_S_CLEAR_FLOWS(void)
>  {
> +    VLOG_DBG("clearing all flows");
> +
>      /* Send a flow_mod to delete all flows. */
>      struct ofputil_flow_mod fm = {
> -        .match = MATCH_CATCHALL_INITIALIZER,
>          .table_id = OFPTT_ALL,
>          .command = OFPFC_DELETE,
>      };
> +    minimatch_init_catchall(&fm.match);
>      queue_msg(encode_flow_mod(&fm));
> -    VLOG_DBG("clearing all flows");
> +    minimatch_destroy(&fm.match);
>
>      /* Clear installed_flows, to match the state of the switch. */
>      ovn_flow_table_clear(&installed_flows);
> @@ -630,13 +632,12 @@ ofctrl_recv(const struct ofp_header *oh, enum
> ofptype type)
>  void
>  ofctrl_add_flow(struct hmap *desired_flows,
>                  uint8_t table_id, uint16_t priority, uint64_t cookie,
> -                const struct match *match,
> -                const struct ofpbuf *actions)
> +                const struct match *match, const struct ofpbuf *actions)
>  {
>      struct ovn_flow *f = xmalloc(sizeof *f);
>      f->table_id = table_id;
>      f->priority = priority;
> -    f->match = *match;
> +    minimatch_init(&f->match, match);
>      f->ofpacts = xmemdup(actions->data, actions->size);
>      f->ofpacts_len = actions->size;
>      f->hmap_node.hash = ovn_flow_hash(f);
> @@ -665,7 +666,7 @@ static uint32_t
>  ovn_flow_hash(const struct ovn_flow *f)
>  {
>      return hash_2words((f->table_id << 16) | f->priority,
> -                       match_hash(&f->match, 0));
> +                       minimatch_hash(&f->match, 0));
>
>  }
>
> @@ -676,7 +677,7 @@ ofctrl_dup_flow(struct ovn_flow *src)
>      struct ovn_flow *dst = xmalloc(sizeof *dst);
>      dst->table_id = src->table_id;
>      dst->priority = src->priority;
> -    dst->match = src->match;
> +    minimatch_clone(&dst->match, &src->match);
>      dst->ofpacts = xmemdup(src->ofpacts, src->ofpacts_len);
>      dst->ofpacts_len = src->ofpacts_len;
>      dst->hmap_node.hash = ovn_flow_hash(dst);
> @@ -694,7 +695,7 @@ ovn_flow_lookup(struct hmap *flow_table, const struct
> ovn_flow *target)
>                               flow_table) {
>          if (f->table_id == target->table_id
>              && f->priority == target->priority
> -            && match_equal(&f->match, &target->match)) {
> +            && minimatch_equal(&f->match, &target->match)) {
>              return f;
>          }
>      }
> @@ -707,7 +708,7 @@ ovn_flow_to_string(const struct ovn_flow *f)
>      struct ds s = DS_EMPTY_INITIALIZER;
>      ds_put_format(&s, "table_id=%"PRIu8", ", f->table_id);
>      ds_put_format(&s, "priority=%"PRIu16", ", f->priority);
> -    match_format(&f->match, NULL, &s, OFP_DEFAULT_PRIORITY);
> +    minimatch_format(&f->match, NULL, NULL, &s, OFP_DEFAULT_PRIORITY);
>      ds_put_cstr(&s, ", actions=");
>      struct ofpact_format_params fp = { .s = &s };
>      ofpacts_format(f->ofpacts, f->ofpacts_len, &fp);
> @@ -728,6 +729,7 @@ static void
>  ovn_flow_destroy(struct ovn_flow *f)
>  {
>      if (f) {
> +        minimatch_destroy(&f->match);
>          free(f->ofpacts);
>          free(f);
>      }
> diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
> index b5e2b409328e..4d9e8743e885 100644
> --- a/utilities/ovs-ofctl.c
> +++ b/utilities/ovs-ofctl.c
> @@ -1747,6 +1747,7 @@ ofctl_flow_mod__(const char *remote, struct
> ofputil_flow_mod *fms,
>
>          transact_noreply(vconn, ofputil_encode_flow_mod(fm, protocol));
>          free(CONST_CAST(struct ofpact *, fm->ofpacts));
> +        minimatch_destroy(&fm->match);
>      }
>      vconn_close(vconn);
>  }
> @@ -3309,7 +3310,7 @@ struct fte_version {
>  /* A FTE entry that has been queued for later insertion after all
>   * flows have been scanned to correctly allocation tunnel metadata. */
>  struct fte_pending {
> -    struct match *match;
> +    struct minimatch match;
>      int priority;
>      struct fte_version *version;
>      int index;
> @@ -3442,14 +3443,14 @@ fte_free_all(struct flow_tables *tables)
>   *
>   * Takes ownership of 'version'. */
>  static void
> -fte_insert(struct flow_tables *tables, const struct match *match,
> +fte_insert(struct flow_tables *tables, const struct minimatch *match,
>             int priority, struct fte_version *version, int index)
>  {
>      struct classifier *cls = &tables->tables[version->table_id];
>      struct fte *old, *fte;
>
>      fte = xzalloc(sizeof *fte);
> -    cls_rule_init(&fte->rule, match, priority);
> +    cls_rule_init_from_minimatch(&fte->rule, match, priority);
>      fte->versions[index] = version;
>
>      old = fte_from_cls_rule(classifier_replace(cls, &fte->rule,
> @@ -3498,40 +3499,45 @@ generate_tun_metadata(struct fte_state *state)
>   * can just read the data from the match and rewrite it. On rewrite, it
>   * will use the new table. */
>  static void
> -remap_match(struct fte_state *state, struct match *match)
> +remap_match(struct fte_state *state, struct minimatch *minimatch)
>  {
>      int i;
>
> -    if (!match->tun_md.valid) {
> +    if (!minimatch->tun_md || !minimatch->tun_md->valid) {
>          return;
>      }
>
> -    struct tun_metadata flow = match->flow.tunnel.metadata;
> -    struct tun_metadata flow_mask = match->wc.masks.tunnel.metadata;
> -    memset(&match->flow.tunnel.metadata, 0, sizeof
> match->flow.tunnel.metadata);
> -    memset(&match->wc.masks.tunnel.metadata, 0,
> -           sizeof match->wc.masks.tunnel.metadata);
> -    match->tun_md.valid = false;
> +    struct match match;
> +    minimatch_expand(minimatch, &match);
> +
> +    struct tun_metadata flow = match.flow.tunnel.metadata;
> +    struct tun_metadata flow_mask = match.wc.masks.tunnel.metadata;
> +    memset(&match.flow.tunnel.metadata, 0, sizeof
> match.flow.tunnel.metadata);
> +    memset(&match.wc.masks.tunnel.metadata, 0,
> +           sizeof match.wc.masks.tunnel.metadata);
> +    match.tun_md.valid = false;
>
> -    match->flow.tunnel.metadata.tab = state->tun_tab;
> -    match->wc.masks.tunnel.metadata.tab = match->flow.tunnel.metadata.ta
> b;
> +    match.flow.tunnel.metadata.tab = state->tun_tab;
> +    match.wc.masks.tunnel.metadata.tab = match.flow.tunnel.metadata.tab;
>
>      ULLONG_FOR_EACH_1 (i, flow_mask.present.map) {
>          const struct mf_field *field = mf_from_id(MFF_TUN_METADATA0 + i);
> -        int offset = match->tun_md.entry[i].loc.c.offset;
> -        int len = match->tun_md.entry[i].loc.len;
> +        int offset = match.tun_md.entry[i].loc.c.offset;
> +        int len = match.tun_md.entry[i].loc.len;
>          union mf_value value, mask;
>
>          memset(&value, 0, field->n_bytes - len);
> -        memset(&mask, match->tun_md.entry[i].masked ? 0 : 0xff,
> +        memset(&mask, match.tun_md.entry[i].masked ? 0 : 0xff,
>                 field->n_bytes - len);
>
>          memcpy(value.tun_metadata + field->n_bytes - len,
>                 flow.opts.u8 + offset, len);
>          memcpy(mask.tun_metadata + field->n_bytes - len,
>                 flow_mask.opts.u8 + offset, len);
> -        mf_set(field, &value, &mask, match, NULL);
> +        mf_set(field, &value, &mask, &match, NULL);
>      }
> +    minimatch_destroy(minimatch);
> +    minimatch_init(minimatch, &match);
>  }
>
>  /* In order to correctly handle tunnel metadata, we need to have
> @@ -3578,25 +3584,26 @@ fte_state_destroy(struct fte_state *state)
>   * fte_state_init(). fte_queue() is the first pass to be called as each
>   * flow is read from its source. */
>  static void
> -fte_queue(struct fte_state *state, const struct match *match,
> +fte_queue(struct fte_state *state, const struct minimatch *match,
>            int priority, struct fte_version *version, int index)
>  {
>      struct fte_pending *pending = xmalloc(sizeof *pending);
>      int i;
>
> -    pending->match = xmemdup(match, sizeof *match);
> +    minimatch_clone(&pending->match, match);
>      pending->priority = priority;
>      pending->version = version;
>      pending->index = index;
>      ovs_list_push_back(&state->fte_pending_list, &pending->list_node);
>
> -    if (!match->tun_md.valid) {
> +    if (!match->tun_md || !match->tun_md->valid) {
>          return;
>      }
>
> -    ULLONG_FOR_EACH_1 (i, match->wc.masks.tunnel.metadata.present.map) {
> -        if (match->tun_md.entry[i].loc.len >
> state->tun_metadata_size[i]) {
> -            state->tun_metadata_size[i] = match->tun_md.entry[i].loc.len;
> +    uint64_t map = miniflow_get_tun_metadata_pres
> ent_map(&match->mask->masks);
> +    ULLONG_FOR_EACH_1 (i, map) {
> +        if (match->tun_md->entry[i].loc.len >
> state->tun_metadata_size[i]) {
> +            state->tun_metadata_size[i] = match->tun_md->entry[i].loc.le
> n;
>          }
>      }
>  }
> @@ -3615,10 +3622,10 @@ fte_fill(struct fte_state *state, struct
> flow_tables *tables)
>      flow_tables_defer(tables);
>
>      LIST_FOR_EACH_POP(pending, list_node, &state->fte_pending_list) {
> -        remap_match(state, pending->match);
> -        fte_insert(tables, pending->match, pending->priority,
> pending->version,
> -                   pending->index);
> -        free(pending->match);
> +        remap_match(state, &pending->match);
> +        fte_insert(tables, &pending->match, pending->priority,
> +                   pending->version, pending->index);
> +        minimatch_destroy(&pending->match);
>          free(pending);
>      }
>
> @@ -3669,6 +3676,8 @@ read_flows_from_file(const char *filename, struct
> fte_state *state, int index)
>          version->table_id = fm.table_id != OFPTT_ALL ? fm.table_id : 0;
>
>          fte_queue(state, &fm.match, fm.priority, version, index);
> +
> +        minimatch_destroy(&fm.match);
>      }
>      ds_destroy(&s);
>
> @@ -3714,7 +3723,10 @@ read_flows_from_switch(struct vconn *vconn,
>          version->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len);
>          version->table_id = fs->table_id;
>
> -        fte_queue(state, &fs->match, fs->priority, version, index);
> +        struct minimatch match;
> +        minimatch_init(&match, &fs->match);
> +        fte_queue(state, &match, fs->priority, version, index);
> +        minimatch_destroy(&match);
>      }
>
>      for (size_t i = 0; i < n_fses; i++) {
> @@ -3744,7 +3756,7 @@ fte_make_flow_mod(const struct fte *fte, int index,
> uint16_t command,
>          .out_group = OFPG_ANY,
>          .flags = version->flags,
>      };
> -    minimatch_expand(&fte->rule.match, &fm.match);
> +    minimatch_clone(&fm.match, &fte->rule.match);
>      if (command == OFPFC_ADD || command == OFPFC_MODIFY ||
>          command == OFPFC_MODIFY_STRICT) {
>          fm.ofpacts = version->ofpacts;
> @@ -3753,8 +3765,9 @@ fte_make_flow_mod(const struct fte *fte, int index,
> uint16_t command,
>          fm.ofpacts = NULL;
>          fm.ofpacts_len = 0;
>      }
> -
>      ofm = ofputil_encode_flow_mod(&fm, protocol);
> +    minimatch_destroy(&fm.match);
> +
>      ovs_list_push_back(packets, &ofm->list_node);
>  }
>
> @@ -4028,6 +4041,7 @@ ofctl_parse_flows__(struct ofputil_flow_mod *fms,
> size_t n_fms,
>          ofpbuf_delete(msg);
>
>          free(CONST_CAST(struct ofpact *, fm->ofpacts));
> +        minimatch_destroy(&fm->match);
>      }
>  }
>
> @@ -4504,10 +4518,13 @@ ofctl_check_vlan(struct ovs_cmdl_context *ctx)
>      if (error_s) {
>          ovs_fatal(0, "%s", error_s);
>      }
> +    struct match fm_match;
> +    minimatch_expand(&fm.match, &fm_match);
>      printf("%04"PRIx16"/%04"PRIx16"\n",
> -           ntohs(fm.match.flow.vlans[0].tci),
> -           ntohs(fm.match.wc.masks.vlans[0].tci));
> +           ntohs(fm_match.flow.vlans[0].tci),
> +           ntohs(fm_match.wc.masks.vlans[0].tci));
>      free(string_s);
> +    minimatch_destroy(&fm.match);
>
>      /* Convert to and from NXM. */
>      ofpbuf_init(&nxm, 0);
> --
> 2.16.1
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>

I can't claim that my review would be enough to spot any potential issue,
but for what it's worth I did test the patches: all 150K+ flows were
processed correctly, and the memory savings were substantial. I did test
against master and 2.7.3.

Thanks,
Armando

Reviewed-by: Armando Migliaccio <armamig@gmail.com>
Tested-by: Armando Migliaccio <armamig@gmail.com>
diff mbox series

Patch

diff --git a/AUTHORS.rst b/AUTHORS.rst
index dc69ba3f3c1f..81f9b6f28b88 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -506,6 +506,7 @@  Marvin Pascual                  marvin@pascual.com.ph
 Maxime Brun                     m.brun@alphalink.fr
 Madhu Venugopal                 mavenugo@gmail.com
 Michael A. Collins              mike.a.collins@ark-net.org
+Michael Ben-Ami                 mbenami@digitalocean.com
 Michael Hu                      mhu@nicira.com
 Michael J. Smalley              michaeljsmalley@gmail.com
 Michael Mao                     mmao@nicira.com
diff --git a/include/openvswitch/ofp-flow.h b/include/openvswitch/ofp-flow.h
index 17d48f12e060..2ff2e45b66c4 100644
--- a/include/openvswitch/ofp-flow.h
+++ b/include/openvswitch/ofp-flow.h
@@ -73,7 +73,7 @@  void ofputil_flow_mod_flags_format(struct ds *, enum ofputil_flow_mod_flags);
 struct ofputil_flow_mod {
     struct ovs_list list_node; /* For queuing flow_mods. */
 
-    struct match match;
+    struct minimatch match;
     int priority;
 
     /* Cookie matching.  The flow_mod affects only flows that have cookies that
diff --git a/lib/learn.c b/lib/learn.c
index f5d15503fe79..c4d5b3e0c449 100644
--- a/lib/learn.c
+++ b/lib/learn.c
@@ -91,14 +91,17 @@  learn_check(const struct ofpact_learn *learn, const struct match *src_match)
  * 'ofpacts' and retains ownership of it.  'fm->ofpacts' will point into the
  * 'ofpacts' buffer.
  *
+ * The caller must eventually destroy fm->match.
+ *
  * The caller has to actually execute 'fm'. */
 void
 learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
               struct ofputil_flow_mod *fm, struct ofpbuf *ofpacts)
 {
     const struct ofpact_learn_spec *spec;
+    struct match match;
 
-    match_init_catchall(&fm->match);
+    match_init_catchall(&match);
     fm->priority = learn->priority;
     fm->cookie = htonll(0);
     fm->cookie_mask = htonll(0);
@@ -140,10 +143,10 @@  learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
 
         switch (spec->dst_type) {
         case NX_LEARN_DST_MATCH:
-            mf_write_subfield(&spec->dst, &value, &fm->match);
-            match_add_ethernet_prereq(&fm->match, spec->dst.field);
+            mf_write_subfield(&spec->dst, &value, &match);
+            match_add_ethernet_prereq(&match, spec->dst.field);
             mf_vl_mff_set_tlv_bitmap(
-                spec->dst.field, &fm->match.flow.tunnel.metadata.present.map);
+                spec->dst.field, &match.flow.tunnel.metadata.present.map);
             break;
 
         case NX_LEARN_DST_LOAD:
@@ -173,6 +176,7 @@  learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
         }
     }
 
+    minimatch_init(&fm->match, &match);
     fm->ofpacts = ofpacts->data;
     fm->ofpacts_len = ofpacts->size;
 }
diff --git a/lib/learning-switch.c b/lib/learning-switch.c
index 3a9e015bbe9c..04eaa6e8e73f 100644
--- a/lib/learning-switch.c
+++ b/lib/learning-switch.c
@@ -206,7 +206,6 @@  lswitch_handshake(struct lswitch *sw)
         output.max_len = OFP_DEFAULT_MISS_SEND_LEN;
 
         struct ofputil_flow_mod fm = {
-            .match = MATCH_CATCHALL_INITIALIZER,
             .priority = 0,
             .table_id = 0,
             .command = OFPFC_ADD,
@@ -216,8 +215,10 @@  lswitch_handshake(struct lswitch *sw)
             .ofpacts = &output.ofpact,
             .ofpacts_len = sizeof output,
         };
-
+        minimatch_init_catchall(&fm.match);
         msg = ofputil_encode_flow_mod(&fm, protocol);
+        minimatch_destroy(&fm.match);
+
         error = rconn_send(sw->rconn, msg, NULL);
         if (error) {
             VLOG_INFO_RL(&rl, "%s: failed to add default flow (%s)",
@@ -595,11 +596,16 @@  process_packet_in(struct lswitch *sw, const struct ofp_header *oh)
             .ofpacts = ofpacts.data,
             .ofpacts_len = ofpacts.size,
         };
-        match_init(&fm.match, &flow, &sw->wc);
-        ofputil_normalize_match_quiet(&fm.match);
+
+        struct match match;
+        match_init(&match, &flow, &sw->wc);
+        ofputil_normalize_match_quiet(&match);
+        minimatch_init(&fm.match, &match);
 
         struct ofpbuf *buffer = ofputil_encode_flow_mod(&fm, sw->protocol);
 
+        minimatch_destroy(&fm.match);
+
         queue_tx(sw, buffer);
 
         /* If the switch didn't buffer the packet, we need to send a copy. */
diff --git a/lib/ofp-bundle.c b/lib/ofp-bundle.c
index 0921c81bfb15..8f07a30c51f7 100644
--- a/lib/ofp-bundle.c
+++ b/lib/ofp-bundle.c
@@ -33,6 +33,7 @@  ofputil_free_bundle_msgs(struct ofputil_bundle_msg *bms, size_t n_bms)
         switch ((int)bms[i].type) {
         case OFPTYPE_FLOW_MOD:
             free(CONST_CAST(struct ofpact *, bms[i].fm.ofpacts));
+            minimatch_destroy(&bms[i].fm.match);
             break;
         case OFPTYPE_GROUP_MOD:
             ofputil_uninit_group_mod(&bms[i].gm);
diff --git a/lib/ofp-flow.c b/lib/ofp-flow.c
index cffadb30381b..d07556245b1f 100644
--- a/lib/ofp-flow.c
+++ b/lib/ofp-flow.c
@@ -139,6 +139,8 @@  ofputil_flow_mod_flags_format(struct ds *s, enum ofputil_flow_mod_flags flags)
  * The caller must initialize 'ofpacts' and retains ownership of it.
  * 'fm->ofpacts' will point into the 'ofpacts' buffer.
  *
+ * On success, the caller must eventually destroy fm->match.
+ *
  * Does not validate the flow_mod actions.  The caller should do that, with
  * ofpacts_check(). */
 enum ofperr
@@ -152,6 +154,7 @@  ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
 {
     ovs_be16 raw_flags;
     enum ofperr error;
+    struct match match;
     struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
     enum ofpraw raw = ofpraw_pull_assert(&b);
     if (raw == OFPRAW_OFPT11_FLOW_MOD) {
@@ -160,7 +163,7 @@  ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
 
         ofm = ofpbuf_pull(&b, sizeof *ofm);
 
-        error = ofputil_pull_ofp11_match(&b, tun_table, vl_mff_map, &fm->match,
+        error = ofputil_pull_ofp11_match(&b, tun_table, vl_mff_map, &match,
                                          NULL);
         if (error) {
             return error;
@@ -228,8 +231,8 @@  ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
             ofm = ofpbuf_pull(&b, sizeof *ofm);
 
             /* Translate the rule. */
-            ofputil_match_from_ofp10_match(&ofm->match, &fm->match);
-            ofputil_normalize_match(&fm->match);
+            ofputil_match_from_ofp10_match(&ofm->match, &match);
+            ofputil_normalize_match(&match);
 
             /* OpenFlow 1.0 says that exact-match rules have to have the
              * highest possible priority. */
@@ -256,7 +259,7 @@  ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
             /* Dissect the message. */
             nfm = ofpbuf_pull(&b, sizeof *nfm);
             error = nx_pull_match(&b, ntohs(nfm->match_len),
-                                  &fm->match, &fm->cookie, &fm->cookie_mask,
+                                  &match, &fm->cookie, &fm->cookie_mask,
                                   false, tun_table, vl_mff_map);
             if (error) {
                 return error;
@@ -269,6 +272,7 @@  ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
                  * existing cookie. */
                 return OFPERR_NXBRC_NXM_INVALID;
             }
+            minimatch_init(&fm->match, &match);
             fm->priority = ntohs(nfm->priority);
             fm->new_cookie = nfm->cookie;
             fm->idle_timeout = ntohs(nfm->idle_timeout);
@@ -298,11 +302,11 @@  ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
 
     /* Check for mismatched conntrack original direction tuple address fields
      * w.r.t. the IP version of the match. */
-    if (((fm->match.wc.masks.ct_nw_src || fm->match.wc.masks.ct_nw_dst)
-         && fm->match.flow.dl_type != htons(ETH_TYPE_IP))
-        || ((ipv6_addr_is_set(&fm->match.wc.masks.ct_ipv6_src)
-             || ipv6_addr_is_set(&fm->match.wc.masks.ct_ipv6_dst))
-            && fm->match.flow.dl_type != htons(ETH_TYPE_IPV6))) {
+    if (((match.wc.masks.ct_nw_src || match.wc.masks.ct_nw_dst)
+         && match.flow.dl_type != htons(ETH_TYPE_IP))
+        || ((ipv6_addr_is_set(&match.wc.masks.ct_ipv6_src)
+             || ipv6_addr_is_set(&match.wc.masks.ct_ipv6_dst))
+            && match.flow.dl_type != htons(ETH_TYPE_IPV6))) {
         return OFPERR_OFPBAC_MATCH_INCONSISTENT;
     }
 
@@ -339,9 +343,13 @@  ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
                 : OFPERR_OFPFMFC_TABLE_FULL);
     }
 
-    return ofpacts_check_consistency(fm->ofpacts, fm->ofpacts_len,
-                                     &fm->match, max_port,
-                                     fm->table_id, max_table, protocol);
+    error = ofpacts_check_consistency(fm->ofpacts, fm->ofpacts_len,
+                                      &match, max_port,
+                                      fm->table_id, max_table, protocol);
+    if (!error) {
+        minimatch_init(&fm->match, &match);
+    }
+    return error;
 }
 
 static ovs_be16
@@ -363,6 +371,9 @@  ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
     ovs_be16 raw_flags = ofputil_encode_flow_mod_flags(fm->flags, version);
     struct ofpbuf *msg;
 
+    struct match match;
+    minimatch_expand(&fm->match, &match);
+
     switch (protocol) {
     case OFPUTIL_P_OF11_STD:
     case OFPUTIL_P_OF12_OXM:
@@ -407,7 +418,7 @@  ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
         } else {
             ofm->importance = 0;
         }
-        ofputil_put_ofp11_match(msg, &fm->match, protocol);
+        ofputil_put_ofp11_match(msg, &match, protocol);
         ofpacts_put_openflow_instructions(fm->ofpacts, fm->ofpacts_len, msg,
                                           version);
         break;
@@ -420,7 +431,7 @@  ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
         msg = ofpraw_alloc(OFPRAW_OFPT10_FLOW_MOD, OFP10_VERSION,
                            fm->ofpacts_len);
         ofm = ofpbuf_put_zeros(msg, sizeof *ofm);
-        ofputil_match_to_ofp10_match(&fm->match, &ofm->match);
+        ofputil_match_to_ofp10_match(&match, &ofm->match);
         ofm->cookie = fm->new_cookie;
         ofm->command = ofputil_tid_command(fm, protocol);
         ofm->idle_timeout = htons(fm->idle_timeout);
@@ -444,7 +455,7 @@  ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
         nfm = ofpbuf_put_zeros(msg, sizeof *nfm);
         nfm->command = ofputil_tid_command(fm, protocol);
         nfm->cookie = fm->new_cookie;
-        match_len = nx_put_match(msg, &fm->match, fm->cookie, fm->cookie_mask);
+        match_len = nx_put_match(msg, &match, fm->cookie, fm->cookie_mask);
         nfm = msg->msg;
         nfm->idle_timeout = htons(fm->idle_timeout);
         nfm->hard_timeout = htons(fm->hard_timeout);
@@ -536,7 +547,9 @@  ofputil_flow_mod_format(struct ds *s, const struct ofp_header *oh,
         /* nx_match_to_string() doesn't print priority. */
         need_priority = true;
     } else {
-        match_format(&fm.match, port_map, s, fm.priority);
+        struct match match;
+        minimatch_expand(&fm.match, &match);
+        match_format(&match, port_map, s, fm.priority);
 
         /* match_format() does print priority. */
         need_priority = false;
@@ -589,6 +602,7 @@  ofputil_flow_mod_format(struct ds *s, const struct ofp_header *oh,
     };
     ofpacts_format(fm.ofpacts, fm.ofpacts_len, &fp);
     ofpbuf_uninit(&ofpacts);
+    minimatch_destroy(&fm.match);
 
     return 0;
 }
@@ -831,10 +845,13 @@  parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr,
     fsr->aggregate = aggregate;
     fsr->cookie = fm.cookie;
     fsr->cookie_mask = fm.cookie_mask;
-    fsr->match = fm.match;
+    minimatch_expand(&fm.match, &fsr->match);
     fsr->out_port = fm.out_port;
     fsr->out_group = fm.out_group;
     fsr->table_id = fm.table_id;
+
+    minimatch_destroy(&fm.match);
+
     return NULL;
 }
 
@@ -1393,7 +1410,6 @@  parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
     }
 
     *fm = (struct ofputil_flow_mod) {
-        .match = MATCH_CATCHALL_INITIALIZER,
         .priority = OFP_DEFAULT_PRIORITY,
         .table_id = 0xff,
         .command = command,
@@ -1413,19 +1429,20 @@  parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
         }
     }
 
+    struct match match = MATCH_CATCHALL_INITIALIZER;
     while (ofputil_parse_key_value(&string, &name, &value)) {
         const struct ofp_protocol *p;
         const struct mf_field *mf;
         char *error = NULL;
 
         if (ofp_parse_protocol(name, &p)) {
-            match_set_dl_type(&fm->match, htons(p->dl_type));
+            match_set_dl_type(&match, htons(p->dl_type));
             if (p->nw_proto) {
-                match_set_nw_proto(&fm->match, p->nw_proto);
+                match_set_nw_proto(&match, p->nw_proto);
             }
-            match_set_default_packet_type(&fm->match);
+            match_set_default_packet_type(&match);
         } else if (!strcmp(name, "eth")) {
-            match_set_packet_type(&fm->match, htonl(PT_ETH));
+            match_set_packet_type(&match, htonl(PT_ETH));
         } else if (fields & F_FLAGS && !strcmp(name, "send_flow_rem")) {
             fm->flags |= OFPUTIL_FF_SEND_FLOW_REM;
         } else if (fields & F_FLAGS && !strcmp(name, "check_overlap")) {
@@ -1444,9 +1461,9 @@  parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
              /* ignore these fields. */
         } else if ((mf = mf_from_name(name)) != NULL) {
             error = ofp_parse_field(mf, value, port_map,
-                                    &fm->match, usable_protocols);
+                                    &match, usable_protocols);
         } else if (strchr(name, '[')) {
-            error = parse_subfield(name, value, &fm->match, usable_protocols);
+            error = parse_subfield(name, value, &match, usable_protocols);
         } else {
             if (!*value) {
                 return xasprintf("field %s missing value", name);
@@ -1532,13 +1549,13 @@  parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
     }
     /* Copy ethertype to flow->dl_type for matches on packet_type
      * (OFPHTN_ETHERTYPE, ethertype). */
-    if (fm->match.wc.masks.packet_type == OVS_BE32_MAX &&
-            pt_ns(fm->match.flow.packet_type) == OFPHTN_ETHERTYPE) {
-        fm->match.flow.dl_type = pt_ns_type_be(fm->match.flow.packet_type);
+    if (match.wc.masks.packet_type == OVS_BE32_MAX &&
+            pt_ns(match.flow.packet_type) == OFPHTN_ETHERTYPE) {
+        match.flow.dl_type = pt_ns_type_be(match.flow.packet_type);
     }
     /* Check for usable protocol interdependencies between match fields. */
-    if (fm->match.flow.dl_type == htons(ETH_TYPE_IPV6)) {
-        const struct flow_wildcards *wc = &fm->match.wc;
+    if (match.flow.dl_type == htons(ETH_TYPE_IPV6)) {
+        const struct flow_wildcards *wc = &match.wc;
         /* Only NXM and OXM support matching L3 and L4 fields within IPv6.
          *
          * (IPv6 specific fields as well as arp_sha, arp_tha, nw_frag, and
@@ -1574,7 +1591,7 @@  parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
         if (!error) {
             enum ofperr err;
 
-            err = ofpacts_check(ofpacts.data, ofpacts.size, &fm->match,
+            err = ofpacts_check(ofpacts.data, ofpacts.size, &match,
                                 OFPP_MAX, fm->table_id, 255, usable_protocols);
             if (!err && !*usable_protocols) {
                 err = OFPERR_OFPBAC_MATCH_INCONSISTENT;
@@ -1596,6 +1613,7 @@  parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
         fm->ofpacts_len = 0;
         fm->ofpacts = NULL;
     }
+    minimatch_init(&fm->match, &match);
 
     return NULL;
 }
@@ -1613,7 +1631,10 @@  parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
  * name is treated as "add".
  *
  * Returns NULL if successful, otherwise a malloc()'d string describing the
- * error.  The caller is responsible for freeing the returned string. */
+ * error.  The caller is responsible for freeing the returned string.
+ *
+ * On success, the caller is responsible for freeing fm->ofpacts and
+ * fm->match. */
 char * OVS_WARN_UNUSED_RESULT
 parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
               const struct ofputil_port_map *port_map,
@@ -1657,8 +1678,9 @@  parse_ofp_flow_mod_str(struct ofputil_flow_mod *fm, const char *string,
         /* Normalize a copy of the match.  This ensures that non-normalized
          * flows get logged but doesn't affect what gets sent to the switch, so
          * that the switch can do whatever it likes with the flow. */
-        struct match match_copy = fm->match;
-        ofputil_normalize_match(&match_copy);
+        struct match match;
+        minimatch_expand(&fm->match, &match);
+        ofputil_normalize_match(&match);
     }
 
     return error;
@@ -1716,6 +1738,7 @@  parse_ofp_flow_mod_file(const char *file_name,
 
             for (i = 0; i < *n_fms; i++) {
                 free(CONST_CAST(struct ofpact *, (*fms)[i].ofpacts));
+                minimatch_destroy(&(*fms)[i].match);
             }
             free(*fms);
             *fms = NULL;
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index bc6429c25346..a00bed704806 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -5019,7 +5019,9 @@  xlate_learn_action(struct xlate_ctx *ctx, const struct ofpact_learn *learn)
         if (OVS_UNLIKELY(ctx->xin->trace)) {
             struct ds s = DS_EMPTY_INITIALIZER;
             ds_put_format(&s, "table=%"PRIu8" ", fm.table_id);
-            match_format(&fm.match, NULL, &s, OFP_DEFAULT_PRIORITY);
+            minimatch_format(&fm.match,
+                             ofproto_get_tun_tab(&ctx->xin->ofproto->up),
+                             NULL, &s, OFP_DEFAULT_PRIORITY);
             ds_chomp(&s, ' ');
             ds_put_format(&s, " priority=%d", fm.priority);
             if (fm.new_cookie) {
@@ -5090,6 +5092,8 @@  xlate_learn_action(struct xlate_ctx *ctx, const struct ofpact_learn *learn)
             xlate_report_error(ctx, "LEARN action execution failed (%s).",
                                ofperr_to_string(error));
         }
+
+        minimatch_destroy(&fm.match);
     } else {
         xlate_report(ctx, OFT_WARN,
                      "suppressing side effects, so learn action ignored");
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index c92c5bea1725..1ed82d036528 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -5582,9 +5582,11 @@  odp_port_to_ofp_port(const struct ofproto_dpif *ofproto, odp_port_t odp_port)
     }
 }
 
+/* 'match' is non-const to allow for temporary modifications.  Any changes are
+ * restored before returning. */
 int
 ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
-                               const struct match *match, int priority,
+                               struct match *match, int priority,
                                uint16_t idle_timeout,
                                const struct ofpbuf *ofpacts,
                                struct rule **rulep)
@@ -5595,7 +5597,6 @@  ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
 
     fm = (struct ofputil_flow_mod) {
         .buffer_id = UINT32_MAX,
-        .match = *match,
         .priority = priority,
         .table_id = TBL_INTERNAL,
         .command = OFPFC_ADD,
@@ -5604,8 +5605,10 @@  ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
         .ofpacts = ofpacts->data,
         .ofpacts_len = ofpacts->size,
     };
-
+    minimatch_init(&fm.match, match);
     error = ofproto_flow_mod(&ofproto->up, &fm);
+    minimatch_destroy(&fm.match);
+
     if (error) {
         VLOG_ERR_RL(&rl, "failed to add internal flow (%s)",
                     ofperr_to_string(error));
@@ -5615,8 +5618,7 @@  ofproto_dpif_add_internal_flow(struct ofproto_dpif *ofproto,
 
     rule = rule_dpif_lookup_in_table(ofproto,
                                      ofproto_dpif_get_tables_version(ofproto),
-                                     TBL_INTERNAL, &fm.match.flow,
-                                     &fm.match.wc);
+                                     TBL_INTERNAL, &match->flow, &match->wc);
     if (rule) {
         *rulep = &rule->up;
     } else {
@@ -5634,7 +5636,6 @@  ofproto_dpif_delete_internal_flow(struct ofproto_dpif *ofproto,
 
     fm = (struct ofputil_flow_mod) {
         .buffer_id = UINT32_MAX,
-        .match = *match,
         .priority = priority,
         .table_id = TBL_INTERNAL,
         .out_port = OFPP_ANY,
@@ -5642,8 +5643,10 @@  ofproto_dpif_delete_internal_flow(struct ofproto_dpif *ofproto,
         .flags = OFPUTIL_FF_HIDDEN_FIELDS | OFPUTIL_FF_NO_READONLY,
         .command = OFPFC_DELETE_STRICT,
     };
-
+    minimatch_init(&fm.match, match);
     error = ofproto_flow_mod(&ofproto->up, &fm);
+    minimatch_destroy(&fm.match);
+
     if (error) {
         VLOG_ERR_RL(&rl, "failed to delete internal flow (%s)",
                     ofperr_to_string(error));
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index 90432fa2678b..47bf7f9f0ac2 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -335,7 +335,7 @@  struct ofport_dpif *ofp_port_to_ofport(const struct ofproto_dpif *,
                                        ofp_port_t);
 
 int ofproto_dpif_add_internal_flow(struct ofproto_dpif *,
-                                   const struct match *, int priority,
+                                   struct match *, int priority,
                                    uint16_t idle_timeout,
                                    const struct ofpbuf *ofpacts,
                                    struct rule **rulep);
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 92d4622b3290..d636fb35c4f9 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -1199,7 +1199,7 @@  struct ofproto_class {
      *
      * If this function is NULL then table 0 is always chosen. */
     enum ofperr (*rule_choose_table)(const struct ofproto *ofproto,
-                                     const struct match *match,
+                                     const struct minimatch *match,
                                      uint8_t *table_idp);
 
     /* Life-cycle functions for a "struct rule".
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 57ce3c81fd22..36f4c0b16d48 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -133,7 +133,7 @@  static void eviction_group_remove_rule(struct rule *)
     OVS_REQUIRES(ofproto_mutex);
 
 static void rule_criteria_init(struct rule_criteria *, uint8_t table_id,
-                               const struct match *match, int priority,
+                               const struct minimatch *match, int priority,
                                ovs_version_t version,
                                ovs_be64 cookie, ovs_be64 cookie_mask,
                                ofp_port_t out_port, uint32_t out_group);
@@ -2104,7 +2104,6 @@  flow_mod_init(struct ofputil_flow_mod *fm,
               enum ofp_flow_mod_command command)
 {
     *fm = (struct ofputil_flow_mod) {
-        .match = *match,
         .priority = priority,
         .table_id = 0,
         .command = command,
@@ -2114,6 +2113,7 @@  flow_mod_init(struct ofputil_flow_mod *fm,
         .ofpacts = CONST_CAST(struct ofpact *, ofpacts),
         .ofpacts_len = ofpacts_len,
     };
+    minimatch_init(&fm->match, match);
 }
 
 static int
@@ -2123,10 +2123,10 @@  simple_flow_mod(struct ofproto *ofproto,
                 enum ofp_flow_mod_command command)
 {
     struct ofputil_flow_mod fm;
-
     flow_mod_init(&fm, match, priority, ofpacts, ofpacts_len, command);
-
-    return handle_flow_mod__(ofproto, &fm, NULL);
+    enum ofperr error = handle_flow_mod__(ofproto, &fm, NULL);
+    minimatch_destroy(&fm.match);
+    return error;
 }
 
 /* Adds a flow to OpenFlow flow table 0 in 'p' that matches 'cls_rule' and
@@ -3173,12 +3173,12 @@  learned_cookies_flush(struct ofproto *ofproto, struct ovs_list *dead_cookies)
 {
     struct learned_cookie *c;
 
+    struct minimatch match;
+
+    minimatch_init_catchall(&match);
     LIST_FOR_EACH_POP (c, u.list_node, dead_cookies) {
         struct rule_criteria criteria;
         struct rule_collection rules;
-        struct match match;
-
-        match_init_catchall(&match);
         rule_criteria_init(&criteria, c->table_id, &match, 0, OVS_VERSION_MAX,
                            c->cookie, OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
         rule_criteria_require_rw(&criteria, false);
@@ -3188,6 +3188,7 @@  learned_cookies_flush(struct ofproto *ofproto, struct ovs_list *dead_cookies)
 
         free(c);
     }
+    minimatch_destroy(&match);
 }
 
 static enum ofperr
@@ -4051,13 +4052,13 @@  next_matching_table(const struct ofproto *ofproto,
  * supplied as 0. */
 static void
 rule_criteria_init(struct rule_criteria *criteria, uint8_t table_id,
-                   const struct match *match, int priority,
+                   const struct minimatch *match, int priority,
                    ovs_version_t version, ovs_be64 cookie,
                    ovs_be64 cookie_mask, ofp_port_t out_port,
                    uint32_t out_group)
 {
     criteria->table_id = table_id;
-    cls_rule_init(&criteria->cr, match, priority);
+    cls_rule_init_from_minimatch(&criteria->cr, match, priority);
     criteria->version = version;
     criteria->cookie = cookie;
     criteria->cookie_mask = cookie_mask;
@@ -4303,9 +4304,12 @@  handle_flow_stats_request(struct ofconn *ofconn,
         return error;
     }
 
-    rule_criteria_init(&criteria, fsr.table_id, &fsr.match, 0, OVS_VERSION_MAX,
+    struct minimatch match;
+    minimatch_init(&match, &fsr.match);
+    rule_criteria_init(&criteria, fsr.table_id, &match, 0, OVS_VERSION_MAX,
                        fsr.cookie, fsr.cookie_mask, fsr.out_port,
                        fsr.out_group);
+    minimatch_destroy(&match);
 
     ovs_mutex_lock(&ofproto_mutex);
     error = collect_rules_loose(ofproto, &criteria, &rules);
@@ -4470,9 +4474,12 @@  handle_aggregate_stats_request(struct ofconn *ofconn,
         return error;
     }
 
-    rule_criteria_init(&criteria, request.table_id, &request.match, 0,
+    struct minimatch match;
+    minimatch_init(&match, &request.match);
+    rule_criteria_init(&criteria, request.table_id, &match, 0,
                        OVS_VERSION_MAX, request.cookie, request.cookie_mask,
                        request.out_port, request.out_group);
+    minimatch_destroy(&match);
 
     ovs_mutex_lock(&ofproto_mutex);
     error = collect_rules_loose(ofproto, &criteria, &rules);
@@ -4746,22 +4753,22 @@  add_flow_init(struct ofproto *ofproto, struct ofproto_flow_mod *ofm,
     }
 
     if (!(fm->flags & OFPUTIL_FF_HIDDEN_FIELDS)
-        && !match_has_default_hidden_fields(&fm->match)) {
+        && !minimatch_has_default_hidden_fields(&fm->match)) {
         VLOG_WARN_RL(&rl, "%s: (add_flow) only internal flows can set "
                      "non-default values to hidden fields", ofproto->name);
         return OFPERR_OFPBRC_EPERM;
     }
 
     if (!ofm->temp_rule) {
-        cls_rule_init(&cr, &fm->match, fm->priority);
+        cls_rule_init_from_minimatch(&cr, &fm->match, fm->priority);
 
         /* Allocate new rule.  Destroys 'cr'. */
+        uint64_t map = miniflow_get_tun_metadata_present_map(fm->match.flow);
         error = ofproto_rule_create(ofproto, &cr, table - ofproto->tables,
                                     fm->new_cookie, fm->idle_timeout,
                                     fm->hard_timeout, fm->flags,
                                     fm->importance, fm->ofpacts,
-                                    fm->ofpacts_len,
-                                    fm->match.flow.tunnel.metadata.present.map,
+                                    fm->ofpacts_len, map,
                                     fm->ofpacts_tlv_bitmap, &ofm->temp_rule);
         if (error) {
             return error;
@@ -4958,7 +4965,7 @@  ofproto_flow_mod_init_for_learn(struct ofproto *ofproto,
     struct oftable *table = &ofproto->tables[fm->table_id];
     struct rule *rule;
 
-    rule = rule_from_cls_rule(classifier_find_match_exactly(
+    rule = rule_from_cls_rule(classifier_find_minimatch_exactly(
                                   &table->cls, &fm->match, fm->priority,
                                   OVS_VERSION_MAX));
     if (rule) {
@@ -5108,12 +5115,14 @@  ofproto_flow_mod_learn(struct ofproto_flow_mod *ofm, bool keep_ref,
         if (limit) {
             struct rule_criteria criteria;
             struct rule_collection rules;
-            struct match match;
+            struct minimatch match;
 
-            match_init_catchall(&match);
+            minimatch_init_catchall(&match);
             rule_criteria_init(&criteria, rule->table_id, &match, 0,
                                OVS_VERSION_MAX, rule->flow_cookie,
                                OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
+            minimatch_destroy(&match);
+
             rule_criteria_require_rw(&criteria, false);
             collect_rules_loose(rule->ofproto, &criteria, &rules);
             if (rule_collection_n(&rules) >= limit) {
@@ -5797,6 +5806,7 @@  handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
     if (!error) {
         struct openflow_mod_requester req = { ofconn, oh };
         error = handle_flow_mod__(ofproto, &fm, &req);
+        minimatch_destroy(&fm.match);
     }
 
     ofpbuf_uninit(&ofpacts);
@@ -7921,6 +7931,7 @@  handle_bundle_add(struct ofconn *ofconn, const struct ofp_header *oh)
                                         ofproto->n_tables);
         if (!error) {
             error = ofproto_flow_mod_init(ofproto, &bmsg->ofm, &fm, NULL);
+            minimatch_destroy(&fm.match);
         }
     } else if (type == OFPTYPE_GROUP_MOD) {
         error = ofputil_decode_group_mod(badd.msg, &bmsg->ogm.gm);
diff --git a/ovn/controller/ofctrl.c b/ovn/controller/ofctrl.c
index 8d6d1b6ae8c0..83340bbf2b30 100644
--- a/ovn/controller/ofctrl.c
+++ b/ovn/controller/ofctrl.c
@@ -58,7 +58,7 @@  struct ovn_flow {
     /* Key. */
     uint8_t table_id;
     uint16_t priority;
-    struct match match;
+    struct minimatch match;
 
     /* Data. */
     struct ofpact *ofpacts;
@@ -373,14 +373,16 @@  error:
 static void
 run_S_CLEAR_FLOWS(void)
 {
+    VLOG_DBG("clearing all flows");
+
     /* Send a flow_mod to delete all flows. */
     struct ofputil_flow_mod fm = {
-        .match = MATCH_CATCHALL_INITIALIZER,
         .table_id = OFPTT_ALL,
         .command = OFPFC_DELETE,
     };
+    minimatch_init_catchall(&fm.match);
     queue_msg(encode_flow_mod(&fm));
-    VLOG_DBG("clearing all flows");
+    minimatch_destroy(&fm.match);
 
     /* Clear installed_flows, to match the state of the switch. */
     ovn_flow_table_clear(&installed_flows);
@@ -630,13 +632,12 @@  ofctrl_recv(const struct ofp_header *oh, enum ofptype type)
 void
 ofctrl_add_flow(struct hmap *desired_flows,
                 uint8_t table_id, uint16_t priority, uint64_t cookie,
-                const struct match *match,
-                const struct ofpbuf *actions)
+                const struct match *match, const struct ofpbuf *actions)
 {
     struct ovn_flow *f = xmalloc(sizeof *f);
     f->table_id = table_id;
     f->priority = priority;
-    f->match = *match;
+    minimatch_init(&f->match, match);
     f->ofpacts = xmemdup(actions->data, actions->size);
     f->ofpacts_len = actions->size;
     f->hmap_node.hash = ovn_flow_hash(f);
@@ -665,7 +666,7 @@  static uint32_t
 ovn_flow_hash(const struct ovn_flow *f)
 {
     return hash_2words((f->table_id << 16) | f->priority,
-                       match_hash(&f->match, 0));
+                       minimatch_hash(&f->match, 0));
 
 }
 
@@ -676,7 +677,7 @@  ofctrl_dup_flow(struct ovn_flow *src)
     struct ovn_flow *dst = xmalloc(sizeof *dst);
     dst->table_id = src->table_id;
     dst->priority = src->priority;
-    dst->match = src->match;
+    minimatch_clone(&dst->match, &src->match);
     dst->ofpacts = xmemdup(src->ofpacts, src->ofpacts_len);
     dst->ofpacts_len = src->ofpacts_len;
     dst->hmap_node.hash = ovn_flow_hash(dst);
@@ -694,7 +695,7 @@  ovn_flow_lookup(struct hmap *flow_table, const struct ovn_flow *target)
                              flow_table) {
         if (f->table_id == target->table_id
             && f->priority == target->priority
-            && match_equal(&f->match, &target->match)) {
+            && minimatch_equal(&f->match, &target->match)) {
             return f;
         }
     }
@@ -707,7 +708,7 @@  ovn_flow_to_string(const struct ovn_flow *f)
     struct ds s = DS_EMPTY_INITIALIZER;
     ds_put_format(&s, "table_id=%"PRIu8", ", f->table_id);
     ds_put_format(&s, "priority=%"PRIu16", ", f->priority);
-    match_format(&f->match, NULL, &s, OFP_DEFAULT_PRIORITY);
+    minimatch_format(&f->match, NULL, NULL, &s, OFP_DEFAULT_PRIORITY);
     ds_put_cstr(&s, ", actions=");
     struct ofpact_format_params fp = { .s = &s };
     ofpacts_format(f->ofpacts, f->ofpacts_len, &fp);
@@ -728,6 +729,7 @@  static void
 ovn_flow_destroy(struct ovn_flow *f)
 {
     if (f) {
+        minimatch_destroy(&f->match);
         free(f->ofpacts);
         free(f);
     }
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index b5e2b409328e..4d9e8743e885 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -1747,6 +1747,7 @@  ofctl_flow_mod__(const char *remote, struct ofputil_flow_mod *fms,
 
         transact_noreply(vconn, ofputil_encode_flow_mod(fm, protocol));
         free(CONST_CAST(struct ofpact *, fm->ofpacts));
+        minimatch_destroy(&fm->match);
     }
     vconn_close(vconn);
 }
@@ -3309,7 +3310,7 @@  struct fte_version {
 /* A FTE entry that has been queued for later insertion after all
  * flows have been scanned to correctly allocation tunnel metadata. */
 struct fte_pending {
-    struct match *match;
+    struct minimatch match;
     int priority;
     struct fte_version *version;
     int index;
@@ -3442,14 +3443,14 @@  fte_free_all(struct flow_tables *tables)
  *
  * Takes ownership of 'version'. */
 static void
-fte_insert(struct flow_tables *tables, const struct match *match,
+fte_insert(struct flow_tables *tables, const struct minimatch *match,
            int priority, struct fte_version *version, int index)
 {
     struct classifier *cls = &tables->tables[version->table_id];
     struct fte *old, *fte;
 
     fte = xzalloc(sizeof *fte);
-    cls_rule_init(&fte->rule, match, priority);
+    cls_rule_init_from_minimatch(&fte->rule, match, priority);
     fte->versions[index] = version;
 
     old = fte_from_cls_rule(classifier_replace(cls, &fte->rule,
@@ -3498,40 +3499,45 @@  generate_tun_metadata(struct fte_state *state)
  * can just read the data from the match and rewrite it. On rewrite, it
  * will use the new table. */
 static void
-remap_match(struct fte_state *state, struct match *match)
+remap_match(struct fte_state *state, struct minimatch *minimatch)
 {
     int i;
 
-    if (!match->tun_md.valid) {
+    if (!minimatch->tun_md || !minimatch->tun_md->valid) {
         return;
     }
 
-    struct tun_metadata flow = match->flow.tunnel.metadata;
-    struct tun_metadata flow_mask = match->wc.masks.tunnel.metadata;
-    memset(&match->flow.tunnel.metadata, 0, sizeof match->flow.tunnel.metadata);
-    memset(&match->wc.masks.tunnel.metadata, 0,
-           sizeof match->wc.masks.tunnel.metadata);
-    match->tun_md.valid = false;
+    struct match match;
+    minimatch_expand(minimatch, &match);
+
+    struct tun_metadata flow = match.flow.tunnel.metadata;
+    struct tun_metadata flow_mask = match.wc.masks.tunnel.metadata;
+    memset(&match.flow.tunnel.metadata, 0, sizeof match.flow.tunnel.metadata);
+    memset(&match.wc.masks.tunnel.metadata, 0,
+           sizeof match.wc.masks.tunnel.metadata);
+    match.tun_md.valid = false;
 
-    match->flow.tunnel.metadata.tab = state->tun_tab;
-    match->wc.masks.tunnel.metadata.tab = match->flow.tunnel.metadata.tab;
+    match.flow.tunnel.metadata.tab = state->tun_tab;
+    match.wc.masks.tunnel.metadata.tab = match.flow.tunnel.metadata.tab;
 
     ULLONG_FOR_EACH_1 (i, flow_mask.present.map) {
         const struct mf_field *field = mf_from_id(MFF_TUN_METADATA0 + i);
-        int offset = match->tun_md.entry[i].loc.c.offset;
-        int len = match->tun_md.entry[i].loc.len;
+        int offset = match.tun_md.entry[i].loc.c.offset;
+        int len = match.tun_md.entry[i].loc.len;
         union mf_value value, mask;
 
         memset(&value, 0, field->n_bytes - len);
-        memset(&mask, match->tun_md.entry[i].masked ? 0 : 0xff,
+        memset(&mask, match.tun_md.entry[i].masked ? 0 : 0xff,
                field->n_bytes - len);
 
         memcpy(value.tun_metadata + field->n_bytes - len,
                flow.opts.u8 + offset, len);
         memcpy(mask.tun_metadata + field->n_bytes - len,
                flow_mask.opts.u8 + offset, len);
-        mf_set(field, &value, &mask, match, NULL);
+        mf_set(field, &value, &mask, &match, NULL);
     }
+    minimatch_destroy(minimatch);
+    minimatch_init(minimatch, &match);
 }
 
 /* In order to correctly handle tunnel metadata, we need to have
@@ -3578,25 +3584,26 @@  fte_state_destroy(struct fte_state *state)
  * fte_state_init(). fte_queue() is the first pass to be called as each
  * flow is read from its source. */
 static void
-fte_queue(struct fte_state *state, const struct match *match,
+fte_queue(struct fte_state *state, const struct minimatch *match,
           int priority, struct fte_version *version, int index)
 {
     struct fte_pending *pending = xmalloc(sizeof *pending);
     int i;
 
-    pending->match = xmemdup(match, sizeof *match);
+    minimatch_clone(&pending->match, match);
     pending->priority = priority;
     pending->version = version;
     pending->index = index;
     ovs_list_push_back(&state->fte_pending_list, &pending->list_node);
 
-    if (!match->tun_md.valid) {
+    if (!match->tun_md || !match->tun_md->valid) {
         return;
     }
 
-    ULLONG_FOR_EACH_1 (i, match->wc.masks.tunnel.metadata.present.map) {
-        if (match->tun_md.entry[i].loc.len > state->tun_metadata_size[i]) {
-            state->tun_metadata_size[i] = match->tun_md.entry[i].loc.len;
+    uint64_t map = miniflow_get_tun_metadata_present_map(&match->mask->masks);
+    ULLONG_FOR_EACH_1 (i, map) {
+        if (match->tun_md->entry[i].loc.len > state->tun_metadata_size[i]) {
+            state->tun_metadata_size[i] = match->tun_md->entry[i].loc.len;
         }
     }
 }
@@ -3615,10 +3622,10 @@  fte_fill(struct fte_state *state, struct flow_tables *tables)
     flow_tables_defer(tables);
 
     LIST_FOR_EACH_POP(pending, list_node, &state->fte_pending_list) {
-        remap_match(state, pending->match);
-        fte_insert(tables, pending->match, pending->priority, pending->version,
-                   pending->index);
-        free(pending->match);
+        remap_match(state, &pending->match);
+        fte_insert(tables, &pending->match, pending->priority,
+                   pending->version, pending->index);
+        minimatch_destroy(&pending->match);
         free(pending);
     }
 
@@ -3669,6 +3676,8 @@  read_flows_from_file(const char *filename, struct fte_state *state, int index)
         version->table_id = fm.table_id != OFPTT_ALL ? fm.table_id : 0;
 
         fte_queue(state, &fm.match, fm.priority, version, index);
+
+        minimatch_destroy(&fm.match);
     }
     ds_destroy(&s);
 
@@ -3714,7 +3723,10 @@  read_flows_from_switch(struct vconn *vconn,
         version->ofpacts = xmemdup(fs->ofpacts, fs->ofpacts_len);
         version->table_id = fs->table_id;
 
-        fte_queue(state, &fs->match, fs->priority, version, index);
+        struct minimatch match;
+        minimatch_init(&match, &fs->match);
+        fte_queue(state, &match, fs->priority, version, index);
+        minimatch_destroy(&match);
     }
 
     for (size_t i = 0; i < n_fses; i++) {
@@ -3744,7 +3756,7 @@  fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
         .out_group = OFPG_ANY,
         .flags = version->flags,
     };
-    minimatch_expand(&fte->rule.match, &fm.match);
+    minimatch_clone(&fm.match, &fte->rule.match);
     if (command == OFPFC_ADD || command == OFPFC_MODIFY ||
         command == OFPFC_MODIFY_STRICT) {
         fm.ofpacts = version->ofpacts;
@@ -3753,8 +3765,9 @@  fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
         fm.ofpacts = NULL;
         fm.ofpacts_len = 0;
     }
-
     ofm = ofputil_encode_flow_mod(&fm, protocol);
+    minimatch_destroy(&fm.match);
+
     ovs_list_push_back(packets, &ofm->list_node);
 }
 
@@ -4028,6 +4041,7 @@  ofctl_parse_flows__(struct ofputil_flow_mod *fms, size_t n_fms,
         ofpbuf_delete(msg);
 
         free(CONST_CAST(struct ofpact *, fm->ofpacts));
+        minimatch_destroy(&fm->match);
     }
 }
 
@@ -4504,10 +4518,13 @@  ofctl_check_vlan(struct ovs_cmdl_context *ctx)
     if (error_s) {
         ovs_fatal(0, "%s", error_s);
     }
+    struct match fm_match;
+    minimatch_expand(&fm.match, &fm_match);
     printf("%04"PRIx16"/%04"PRIx16"\n",
-           ntohs(fm.match.flow.vlans[0].tci),
-           ntohs(fm.match.wc.masks.vlans[0].tci));
+           ntohs(fm_match.flow.vlans[0].tci),
+           ntohs(fm_match.wc.masks.vlans[0].tci));
     free(string_s);
+    minimatch_destroy(&fm.match);
 
     /* Convert to and from NXM. */
     ofpbuf_init(&nxm, 0);