[ovs-dev] [classifier-opt 25/28] classifier: Break cls_rule 'flow' and 'wc' members into new "struct match".

Ben Pfaff blp at nicira.com
Fri Jul 20 23:25:22 UTC 2012


Signed-off-by: Ben Pfaff <blp at nicira.com>
---
 lib/automake.mk            |    2 +
 lib/classifier.c           |  771 +++-----------------------------------------
 lib/classifier.h           |   95 +-----
 lib/learn.c                |   32 +-
 lib/learning-switch.c      |    3 +-
 lib/match.c                |  750 ++++++++++++++++++++++++++++++++++++++++++
 lib/match.h                |  107 ++++++
 lib/meta-flow.c            |  237 +++++++-------
 lib/meta-flow.h            |   14 +-
 lib/nx-match.c             |  132 ++++----
 lib/nx-match.h             |   10 +-
 lib/ofp-parse.c            |   33 +-
 lib/ofp-print.c            |   24 +-
 lib/ofp-util.c             |  459 +++++++++++++--------------
 lib/ofp-util.h             |   39 ++-
 ofproto/connmgr.c          |    8 +-
 ofproto/connmgr.h          |    3 +-
 ofproto/fail-open.c        |   13 +-
 ofproto/in-band.c          |  134 ++++----
 ofproto/ofproto-dpif.c     |   10 +-
 ofproto/ofproto-provider.h |   19 +-
 ofproto/ofproto.c          |   99 ++++---
 tests/ofp-print.at         |    8 +-
 tests/test-classifier.c    |   92 +++---
 tests/test-flows.c         |    8 +-
 utilities/ovs-ofctl.c      |  137 ++++----
 26 files changed, 1677 insertions(+), 1562 deletions(-)
 create mode 100644 lib/match.c
 create mode 100644 lib/match.h

diff --git a/lib/automake.mk b/lib/automake.mk
index b727fed..fcf0707 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -79,6 +79,8 @@ lib_libopenvswitch_a_SOURCES = \
 	lib/lockfile.h \
 	lib/mac-learning.c \
 	lib/mac-learning.h \
+	lib/match.c \
+	lib/match.h \
 	lib/memory.c \
 	lib/memory.h \
 	lib/meta-flow.c \
diff --git a/lib/classifier.c b/lib/classifier.c
index fdd242a..cb66295 100644
--- a/lib/classifier.c
+++ b/lib/classifier.c
@@ -50,738 +50,45 @@ static struct cls_rule *insert_rule(struct cls_table *, struct cls_rule *);
 
 static struct cls_rule *next_rule_in_list__(struct cls_rule *);
 static struct cls_rule *next_rule_in_list(struct cls_rule *);
+
+/* cls_rule. */
 
-/* Converts the flow in 'flow' into a cls_rule in 'rule', with the given
- * 'wildcards' and 'priority'. */
-void
-cls_rule_init(const struct flow *flow, const struct flow_wildcards *wildcards,
-              unsigned int priority, struct cls_rule *rule)
-{
-    rule->flow = *flow;
-    rule->wc = *wildcards;
-    rule->priority = priority;
-    cls_rule_zero_wildcarded_fields(rule);
-}
-
-/* Converts the flow in 'flow' into an exact-match cls_rule in 'rule', with the
- * given 'priority'.  (For OpenFlow 1.0, exact-match rule are always highest
- * priority, so 'priority' should be at least 65535.) */
-void
-cls_rule_init_exact(const struct flow *flow,
-                    unsigned int priority, struct cls_rule *rule)
-{
-    rule->flow = *flow;
-    rule->flow.skb_priority = 0;
-    flow_wildcards_init_exact(&rule->wc);
-    rule->priority = priority;
-}
-
-/* Initializes 'rule' as a "catch-all" rule that matches every packet, with
- * priority 'priority'. */
-void
-cls_rule_init_catchall(struct cls_rule *rule, unsigned int priority)
-{
-    memset(&rule->flow, 0, sizeof rule->flow);
-    flow_wildcards_init_catchall(&rule->wc);
-    rule->priority = priority;
-}
-
-/* For each bit or field wildcarded in 'rule', sets the corresponding bit or
- * field in 'flow' to all-0-bits.  It is important to maintain this invariant
- * in a clr_rule that might be inserted into a classifier.
- *
- * It is never necessary to call this function directly for a cls_rule that is
- * initialized or modified only by cls_rule_*() functions.  It is useful to
- * restore the invariant in a cls_rule whose 'wc' member is modified by hand.
- */
-void
-cls_rule_zero_wildcarded_fields(struct cls_rule *rule)
-{
-    flow_zero_wildcards(&rule->flow, &rule->wc);
-}
-
-void
-cls_rule_set_reg(struct cls_rule *rule, unsigned int reg_idx, uint32_t value)
-{
-    cls_rule_set_reg_masked(rule, reg_idx, value, UINT32_MAX);
-}
-
-void
-cls_rule_set_reg_masked(struct cls_rule *rule, unsigned int reg_idx,
-                        uint32_t value, uint32_t mask)
-{
-    assert(reg_idx < FLOW_N_REGS);
-    flow_wildcards_set_reg_mask(&rule->wc, reg_idx, mask);
-    rule->flow.regs[reg_idx] = value & mask;
-}
-
-void
-cls_rule_set_metadata(struct cls_rule *rule, ovs_be64 metadata)
-{
-    cls_rule_set_metadata_masked(rule, metadata, htonll(UINT64_MAX));
-}
-
-void
-cls_rule_set_metadata_masked(struct cls_rule *rule, ovs_be64 metadata,
-                             ovs_be64 mask)
-{
-    rule->wc.masks.metadata = mask;
-    rule->flow.metadata = metadata & mask;
-}
-
-void
-cls_rule_set_tun_id(struct cls_rule *rule, ovs_be64 tun_id)
-{
-    cls_rule_set_tun_id_masked(rule, tun_id, htonll(UINT64_MAX));
-}
-
-void
-cls_rule_set_tun_id_masked(struct cls_rule *rule,
-                           ovs_be64 tun_id, ovs_be64 mask)
-{
-    rule->wc.masks.tun_id = mask;
-    rule->flow.tun_id = tun_id & mask;
-}
-
-void
-cls_rule_set_in_port(struct cls_rule *rule, uint16_t ofp_port)
-{
-    rule->wc.masks.in_port = UINT16_MAX;
-    rule->flow.in_port = ofp_port;
-}
-
-void
-cls_rule_set_dl_type(struct cls_rule *rule, ovs_be16 dl_type)
-{
-    rule->wc.masks.dl_type = htons(UINT16_MAX);
-    rule->flow.dl_type = dl_type;
-}
-
-/* Modifies 'value_src' so that the Ethernet address must match
- * 'value_dst' exactly. 'mask_dst' is set to all 1s */
-static void
-cls_rule_set_eth(const uint8_t value_src[ETH_ADDR_LEN],
-                 uint8_t value_dst[ETH_ADDR_LEN],
-                 uint8_t mask_dst[ETH_ADDR_LEN])
-{
-    memcpy(value_dst, value_src, ETH_ADDR_LEN);
-    memset(mask_dst, 0xff, ETH_ADDR_LEN);
-}
-
-/* Modifies 'value_src' so that the Ethernet address must match
- * 'value_src' after each byte is ANDed with the appropriate byte in
- * 'mask_src'. 'mask_dst' is set to 'mask_src' */
-static void
-cls_rule_set_eth_masked(const uint8_t value_src[ETH_ADDR_LEN],
-                        const uint8_t mask_src[ETH_ADDR_LEN],
-                        uint8_t value_dst[ETH_ADDR_LEN],
-                        uint8_t mask_dst[ETH_ADDR_LEN])
-{
-    size_t i;
-
-    for (i = 0; i < ETH_ADDR_LEN; i++) {
-        value_dst[i] = value_src[i] & mask_src[i];
-        mask_dst[i] = mask_src[i];
-    }
-}
-
-/* Modifies 'rule' so that the source Ethernet address
- * must match 'dl_src' exactly. */
-void
-cls_rule_set_dl_src(struct cls_rule *rule, const uint8_t dl_src[ETH_ADDR_LEN])
-{
-    cls_rule_set_eth(dl_src, rule->flow.dl_src, rule->wc.masks.dl_src);
-}
-
-/* Modifies 'rule' so that the source Ethernet address
- * must match 'dl_src' after each byte is ANDed with
- * the appropriate byte in 'mask'. */
-void
-cls_rule_set_dl_src_masked(struct cls_rule *rule,
-                           const uint8_t dl_src[ETH_ADDR_LEN],
-                           const uint8_t mask[ETH_ADDR_LEN])
-{
-    cls_rule_set_eth_masked(dl_src, mask,
-                            rule->flow.dl_src, rule->wc.masks.dl_src);
-}
-
-/* Modifies 'rule' so that the destination Ethernet address
- * must match 'dl_dst' exactly. */
-void
-cls_rule_set_dl_dst(struct cls_rule *rule, const uint8_t dl_dst[ETH_ADDR_LEN])
-{
-    cls_rule_set_eth(dl_dst, rule->flow.dl_dst, rule->wc.masks.dl_dst);
-}
-
-/* Modifies 'rule' so that the destination Ethernet address
- * must match 'dl_src' after each byte is ANDed with
- * the appropriate byte in 'mask'. */
-void
-cls_rule_set_dl_dst_masked(struct cls_rule *rule,
-                           const uint8_t dl_dst[ETH_ADDR_LEN],
-                           const uint8_t mask[ETH_ADDR_LEN])
-{
-    cls_rule_set_eth_masked(dl_dst, mask,
-                            rule->flow.dl_dst, rule->wc.masks.dl_dst);
-}
-
-void
-cls_rule_set_dl_tci(struct cls_rule *rule, ovs_be16 tci)
-{
-    cls_rule_set_dl_tci_masked(rule, tci, htons(0xffff));
-}
-
-void
-cls_rule_set_dl_tci_masked(struct cls_rule *rule, ovs_be16 tci, ovs_be16 mask)
-{
-    rule->flow.vlan_tci = tci & mask;
-    rule->wc.masks.vlan_tci = mask;
-}
-
-/* Modifies 'rule' so that the VLAN VID is wildcarded.  If the PCP is already
- * wildcarded, then 'rule' will match a packet regardless of whether it has an
- * 802.1Q header or not. */
-void
-cls_rule_set_any_vid(struct cls_rule *rule)
-{
-    if (rule->wc.masks.vlan_tci & htons(VLAN_PCP_MASK)) {
-        rule->wc.masks.vlan_tci &= ~htons(VLAN_VID_MASK);
-        rule->flow.vlan_tci &= ~htons(VLAN_VID_MASK);
-    } else {
-        cls_rule_set_dl_tci_masked(rule, htons(0), htons(0));
-    }
-}
-
-/* Modifies 'rule' depending on 'dl_vlan':
+/* Initializes 'rule' to match packets specified by 'match' at the given
+ * 'priority'.
  *
- *   - If 'dl_vlan' is htons(OFP_VLAN_NONE), makes 'rule' match only packets
- *     without an 802.1Q header.
+ * 'match' must satisfy the invariant described in the comment at the
+ * definition of struct match.
  *
- *   - Otherwise, makes 'rule' match only packets with an 802.1Q header whose
- *     VID equals the low 12 bits of 'dl_vlan'.
- */
-void
-cls_rule_set_dl_vlan(struct cls_rule *rule, ovs_be16 dl_vlan)
-{
-    flow_set_vlan_vid(&rule->flow, dl_vlan);
-    if (dl_vlan == htons(OFP10_VLAN_NONE)) {
-        rule->wc.masks.vlan_tci = htons(UINT16_MAX);
-    } else {
-        rule->wc.masks.vlan_tci |= htons(VLAN_VID_MASK | VLAN_CFI);
-    }
-}
-
-/* Modifies 'rule' so that the VLAN PCP is wildcarded.  If the VID is already
- * wildcarded, then 'rule' will match a packet regardless of whether it has an
- * 802.1Q header or not. */
-void
-cls_rule_set_any_pcp(struct cls_rule *rule)
-{
-    if (rule->wc.masks.vlan_tci & htons(VLAN_VID_MASK)) {
-        rule->wc.masks.vlan_tci &= ~htons(VLAN_PCP_MASK);
-        rule->flow.vlan_tci &= ~htons(VLAN_PCP_MASK);
-    } else {
-        cls_rule_set_dl_tci_masked(rule, htons(0), htons(0));
-    }
-}
-
-/* Modifies 'rule' so that it matches only packets with an 802.1Q header whose
- * PCP equals the low 3 bits of 'dl_vlan_pcp'. */
-void
-cls_rule_set_dl_vlan_pcp(struct cls_rule *rule, uint8_t dl_vlan_pcp)
-{
-    flow_set_vlan_pcp(&rule->flow, dl_vlan_pcp);
-    rule->wc.masks.vlan_tci |= htons(VLAN_CFI | VLAN_PCP_MASK);
-}
-
-void
-cls_rule_set_tp_src(struct cls_rule *rule, ovs_be16 tp_src)
-{
-    cls_rule_set_tp_src_masked(rule, tp_src, htons(UINT16_MAX));
-}
-
-void
-cls_rule_set_tp_src_masked(struct cls_rule *rule, ovs_be16 port, ovs_be16 mask)
-{
-    rule->flow.tp_src = port & mask;
-    rule->wc.masks.tp_src = mask;
-}
-
-void
-cls_rule_set_tp_dst(struct cls_rule *rule, ovs_be16 tp_dst)
-{
-    cls_rule_set_tp_dst_masked(rule, tp_dst, htons(UINT16_MAX));
-}
-
-void
-cls_rule_set_tp_dst_masked(struct cls_rule *rule, ovs_be16 port, ovs_be16 mask)
-{
-    rule->flow.tp_dst = port & mask;
-    rule->wc.masks.tp_dst = mask;
-}
-
-void
-cls_rule_set_nw_proto(struct cls_rule *rule, uint8_t nw_proto)
-{
-    rule->flow.nw_proto = nw_proto;
-    rule->wc.masks.nw_proto = UINT8_MAX;
-}
-
-void
-cls_rule_set_nw_src(struct cls_rule *rule, ovs_be32 nw_src)
-{
-    rule->flow.nw_src = nw_src;
-    rule->wc.masks.nw_src = htonl(UINT32_MAX);
-}
-
-void
-cls_rule_set_nw_src_masked(struct cls_rule *rule,
-                           ovs_be32 nw_src, ovs_be32 mask)
-{
-    rule->flow.nw_src = nw_src & mask;
-    rule->wc.masks.nw_src = mask;
-}
-
-void
-cls_rule_set_nw_dst(struct cls_rule *rule, ovs_be32 nw_dst)
-{
-    rule->flow.nw_dst = nw_dst;
-    rule->wc.masks.nw_dst = htonl(UINT32_MAX);
-}
-
+ * (OpenFlow uses priorities between 0 and UINT16_MAX, inclusive, but
+ * internally Open vSwitch supports a wider range.) */
 void
-cls_rule_set_nw_dst_masked(struct cls_rule *rule, ovs_be32 ip, ovs_be32 mask)
+cls_rule_init(struct cls_rule *rule,
+              const struct match *match, unsigned int priority)
 {
-    rule->flow.nw_dst = ip & mask;
-    rule->wc.masks.nw_dst = mask;
-}
-
-void
-cls_rule_set_nw_dscp(struct cls_rule *rule, uint8_t nw_dscp)
-{
-    rule->wc.masks.nw_tos |= IP_DSCP_MASK;
-    rule->flow.nw_tos &= ~IP_DSCP_MASK;
-    rule->flow.nw_tos |= nw_dscp & IP_DSCP_MASK;
-}
-
-void
-cls_rule_set_nw_ecn(struct cls_rule *rule, uint8_t nw_ecn)
-{
-    rule->wc.masks.nw_tos |= IP_ECN_MASK;
-    rule->flow.nw_tos &= ~IP_ECN_MASK;
-    rule->flow.nw_tos |= nw_ecn & IP_ECN_MASK;
-}
-
-void
-cls_rule_set_nw_ttl(struct cls_rule *rule, uint8_t nw_ttl)
-{
-    rule->wc.masks.nw_ttl = UINT8_MAX;
-    rule->flow.nw_ttl = nw_ttl;
-}
-
-void
-cls_rule_set_nw_frag(struct cls_rule *rule, uint8_t nw_frag)
-{
-    rule->wc.masks.nw_frag |= FLOW_NW_FRAG_MASK;
-    rule->flow.nw_frag = nw_frag;
-}
-
-void
-cls_rule_set_nw_frag_masked(struct cls_rule *rule,
-                            uint8_t nw_frag, uint8_t mask)
-{
-    rule->flow.nw_frag = nw_frag & mask;
-    rule->wc.masks.nw_frag = mask;
-}
-
-void
-cls_rule_set_icmp_type(struct cls_rule *rule, uint8_t icmp_type)
-{
-    cls_rule_set_tp_src(rule, htons(icmp_type));
-}
-
-void
-cls_rule_set_icmp_code(struct cls_rule *rule, uint8_t icmp_code)
-{
-    cls_rule_set_tp_dst(rule, htons(icmp_code));
-}
-
-void
-cls_rule_set_arp_sha(struct cls_rule *rule, const uint8_t sha[ETH_ADDR_LEN])
-{
-    cls_rule_set_eth(sha, rule->flow.arp_sha, rule->wc.masks.arp_sha);
-}
-
-void
-cls_rule_set_arp_sha_masked(struct cls_rule *rule,
-                           const uint8_t arp_sha[ETH_ADDR_LEN],
-                           const uint8_t mask[ETH_ADDR_LEN])
-{
-    cls_rule_set_eth_masked(arp_sha, mask,
-                            rule->flow.arp_sha, rule->wc.masks.arp_sha);
-}
-
-void
-cls_rule_set_arp_tha(struct cls_rule *rule, const uint8_t tha[ETH_ADDR_LEN])
-{
-    cls_rule_set_eth(tha, rule->flow.arp_tha, rule->wc.masks.arp_tha);
-}
-
-void
-cls_rule_set_arp_tha_masked(struct cls_rule *rule,
-                           const uint8_t arp_tha[ETH_ADDR_LEN],
-                           const uint8_t mask[ETH_ADDR_LEN])
-{
-    cls_rule_set_eth_masked(arp_tha, mask,
-                            rule->flow.arp_tha, rule->wc.masks.arp_tha);
-}
-
-void
-cls_rule_set_ipv6_src(struct cls_rule *rule, const struct in6_addr *src)
-{
-    rule->flow.ipv6_src = *src;
-    rule->wc.masks.ipv6_src = in6addr_exact;
-}
-
-void
-cls_rule_set_ipv6_src_masked(struct cls_rule *rule, const struct in6_addr *src,
-                             const struct in6_addr *mask)
-{
-    rule->flow.ipv6_src = ipv6_addr_bitand(src, mask);
-    rule->wc.masks.ipv6_src = *mask;
-}
-
-void
-cls_rule_set_ipv6_dst(struct cls_rule *rule, const struct in6_addr *dst)
-{
-    rule->flow.ipv6_dst = *dst;
-    rule->wc.masks.ipv6_dst = in6addr_exact;
-}
-
-void
-cls_rule_set_ipv6_dst_masked(struct cls_rule *rule, const struct in6_addr *dst,
-                             const struct in6_addr *mask)
-{
-    rule->flow.ipv6_dst = ipv6_addr_bitand(dst, mask);
-    rule->wc.masks.ipv6_dst = *mask;
-}
-
-void
-cls_rule_set_ipv6_label(struct cls_rule *rule, ovs_be32 ipv6_label)
-{
-    cls_rule_set_ipv6_label_masked(rule, ipv6_label, htonl(UINT32_MAX));
-}
-
-void
-cls_rule_set_ipv6_label_masked(struct cls_rule *rule, ovs_be32 ipv6_label,
-                               ovs_be32 mask)
-{
-    rule->flow.ipv6_label = ipv6_label & mask;
-    rule->wc.masks.ipv6_label = mask;
-}
-
-void
-cls_rule_set_nd_target(struct cls_rule *rule, const struct in6_addr *target)
-{
-    rule->flow.nd_target = *target;
-    rule->wc.masks.nd_target = in6addr_exact;
-}
-
-void
-cls_rule_set_nd_target_masked(struct cls_rule *rule,
-                              const struct in6_addr *target,
-                              const struct in6_addr *mask)
-{
-    rule->flow.nd_target = ipv6_addr_bitand(target, mask);
-    rule->wc.masks.nd_target = *mask;
+    rule->match = *match;
+    rule->priority = priority;
 }
 
-/* Returns true if 'a' and 'b' have the same priority, wildcard the same
- * fields, and have the same values for fixed fields, otherwise false. */
+/* Returns true if 'a' and 'b' match the same packets at the same priority,
+ * false if they differ in some way. */
 bool
 cls_rule_equal(const struct cls_rule *a, const struct cls_rule *b)
 {
-    return (a->priority == b->priority
-            && flow_wildcards_equal(&a->wc, &b->wc)
-            && flow_equal(&a->flow, &b->flow));
+    return a->priority == b->priority && match_equal(&a->match, &b->match);
 }
 
-/* Returns a hash value for the flow, wildcards, and priority in 'rule',
- * starting from 'basis'. */
+/* Returns a hash value for 'rule', folding in 'basis'. */
 uint32_t
 cls_rule_hash(const struct cls_rule *rule, uint32_t basis)
 {
-    uint32_t h0 = flow_hash(&rule->flow, basis);
-    uint32_t h1 = flow_wildcards_hash(&rule->wc, h0);
-    return hash_int(rule->priority, h1);
-}
-
-static void
-format_eth_masked(struct ds *s, const char *name, const uint8_t eth[6],
-                  const uint8_t mask[6])
-{
-    if (!eth_addr_is_zero(mask)) {
-        ds_put_format(s, "%s=", name);
-        eth_format_masked(eth, mask, s);
-        ds_put_char(s, ',');
-    }
-}
-
-static void
-format_ip_netmask(struct ds *s, const char *name, ovs_be32 ip,
-                  ovs_be32 netmask)
-{
-    if (netmask) {
-        ds_put_format(s, "%s=", name);
-        ip_format_masked(ip, netmask, s);
-        ds_put_char(s, ',');
-    }
-}
-
-static void
-format_ipv6_netmask(struct ds *s, const char *name,
-                    const struct in6_addr *addr,
-                    const struct in6_addr *netmask)
-{
-    if (!ipv6_mask_is_any(netmask)) {
-        ds_put_format(s, "%s=", name);
-        print_ipv6_masked(s, addr, netmask);
-        ds_put_char(s, ',');
-    }
-}
-
-
-static void
-format_be16_masked(struct ds *s, const char *name,
-                   ovs_be16 value, ovs_be16 mask)
-{
-    if (mask != htons(0)) {
-        ds_put_format(s, "%s=", name);
-        if (mask == htons(UINT16_MAX)) {
-            ds_put_format(s, "%"PRIu16, ntohs(value));
-        } else {
-            ds_put_format(s, "0x%"PRIx16"/0x%"PRIx16,
-                          ntohs(value), ntohs(mask));
-        }
-        ds_put_char(s, ',');
-    }
+    return match_hash(&rule->match, hash_int(rule->priority, basis));
 }
 
+/* Appends a string describing 'rule' to 's'. */
 void
 cls_rule_format(const struct cls_rule *rule, struct ds *s)
 {
-    const struct flow_wildcards *wc = &rule->wc;
-    size_t start_len = s->length;
-    const struct flow *f = &rule->flow;
-    bool skip_type = false;
-    bool skip_proto = false;
-
-    int i;
-
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
-
-    if (rule->priority != OFP_DEFAULT_PRIORITY) {
-        ds_put_format(s, "priority=%d,", rule->priority);
-    }
-
-    if (wc->masks.dl_type) {
-        skip_type = true;
-        if (f->dl_type == htons(ETH_TYPE_IP)) {
-            if (wc->masks.nw_proto) {
-                skip_proto = true;
-                if (f->nw_proto == IPPROTO_ICMP) {
-                    ds_put_cstr(s, "icmp,");
-                } else if (f->nw_proto == IPPROTO_TCP) {
-                    ds_put_cstr(s, "tcp,");
-                } else if (f->nw_proto == IPPROTO_UDP) {
-                    ds_put_cstr(s, "udp,");
-                } else {
-                    ds_put_cstr(s, "ip,");
-                    skip_proto = false;
-                }
-            } else {
-                ds_put_cstr(s, "ip,");
-            }
-        } else if (f->dl_type == htons(ETH_TYPE_IPV6)) {
-            if (wc->masks.nw_proto) {
-                skip_proto = true;
-                if (f->nw_proto == IPPROTO_ICMPV6) {
-                    ds_put_cstr(s, "icmp6,");
-                } else if (f->nw_proto == IPPROTO_TCP) {
-                    ds_put_cstr(s, "tcp6,");
-                } else if (f->nw_proto == IPPROTO_UDP) {
-                    ds_put_cstr(s, "udp6,");
-                } else {
-                    ds_put_cstr(s, "ipv6,");
-                    skip_proto = false;
-                }
-            } else {
-                ds_put_cstr(s, "ipv6,");
-            }
-        } else if (f->dl_type == htons(ETH_TYPE_ARP)) {
-            ds_put_cstr(s, "arp,");
-        } else {
-            skip_type = false;
-        }
-    }
-    for (i = 0; i < FLOW_N_REGS; i++) {
-        switch (wc->masks.regs[i]) {
-        case 0:
-            break;
-        case UINT32_MAX:
-            ds_put_format(s, "reg%d=0x%"PRIx32",", i, f->regs[i]);
-            break;
-        default:
-            ds_put_format(s, "reg%d=0x%"PRIx32"/0x%"PRIx32",",
-                          i, f->regs[i], wc->masks.regs[i]);
-            break;
-        }
-    }
-    switch (wc->masks.tun_id) {
-    case 0:
-        break;
-    case CONSTANT_HTONLL(UINT64_MAX):
-        ds_put_format(s, "tun_id=%#"PRIx64",", ntohll(f->tun_id));
-        break;
-    default:
-        ds_put_format(s, "tun_id=%#"PRIx64"/%#"PRIx64",",
-                      ntohll(f->tun_id), ntohll(wc->masks.tun_id));
-        break;
-    }
-    switch (wc->masks.metadata) {
-    case 0:
-        break;
-    case CONSTANT_HTONLL(UINT64_MAX):
-        ds_put_format(s, "metadata=%#"PRIx64",", ntohll(f->metadata));
-        break;
-    default:
-        ds_put_format(s, "metadata=%#"PRIx64"/%#"PRIx64",",
-                      ntohll(f->metadata), ntohll(wc->masks.metadata));
-        break;
-    }
-    if (wc->masks.in_port) {
-        ds_put_format(s, "in_port=%"PRIu16",", f->in_port);
-    }
-    if (wc->masks.vlan_tci) {
-        ovs_be16 vid_mask = wc->masks.vlan_tci & htons(VLAN_VID_MASK);
-        ovs_be16 pcp_mask = wc->masks.vlan_tci & htons(VLAN_PCP_MASK);
-        ovs_be16 cfi = wc->masks.vlan_tci & htons(VLAN_CFI);
-
-        if (cfi && f->vlan_tci & htons(VLAN_CFI)
-            && (!vid_mask || vid_mask == htons(VLAN_VID_MASK))
-            && (!pcp_mask || pcp_mask == htons(VLAN_PCP_MASK))
-            && (vid_mask || pcp_mask)) {
-            if (vid_mask) {
-                ds_put_format(s, "dl_vlan=%"PRIu16",",
-                              vlan_tci_to_vid(f->vlan_tci));
-            }
-            if (pcp_mask) {
-                ds_put_format(s, "dl_vlan_pcp=%d,",
-                              vlan_tci_to_pcp(f->vlan_tci));
-            }
-        } else if (wc->masks.vlan_tci == htons(0xffff)) {
-            ds_put_format(s, "vlan_tci=0x%04"PRIx16",", ntohs(f->vlan_tci));
-        } else {
-            ds_put_format(s, "vlan_tci=0x%04"PRIx16"/0x%04"PRIx16",",
-                          ntohs(f->vlan_tci), ntohs(wc->masks.vlan_tci));
-        }
-    }
-    format_eth_masked(s, "dl_src", f->dl_src, wc->masks.dl_src);
-    format_eth_masked(s, "dl_dst", f->dl_dst, wc->masks.dl_dst);
-    if (!skip_type && wc->masks.dl_type) {
-        ds_put_format(s, "dl_type=0x%04"PRIx16",", ntohs(f->dl_type));
-    }
-    if (f->dl_type == htons(ETH_TYPE_IPV6)) {
-        format_ipv6_netmask(s, "ipv6_src", &f->ipv6_src, &wc->masks.ipv6_src);
-        format_ipv6_netmask(s, "ipv6_dst", &f->ipv6_dst, &wc->masks.ipv6_dst);
-        if (wc->masks.ipv6_label) {
-            if (wc->masks.ipv6_label == htonl(UINT32_MAX)) {
-                ds_put_format(s, "ipv6_label=0x%05"PRIx32",",
-                              ntohl(f->ipv6_label));
-            } else {
-                ds_put_format(s, "ipv6_label=0x%05"PRIx32"/0x%05"PRIx32",",
-                              ntohl(f->ipv6_label),
-                              ntohl(wc->masks.ipv6_label));
-            }
-        }
-    } else {
-        format_ip_netmask(s, "nw_src", f->nw_src, wc->masks.nw_src);
-        format_ip_netmask(s, "nw_dst", f->nw_dst, wc->masks.nw_dst);
-    }
-    if (!skip_proto && wc->masks.nw_proto) {
-        if (f->dl_type == htons(ETH_TYPE_ARP)) {
-            ds_put_format(s, "arp_op=%"PRIu8",", f->nw_proto);
-        } else {
-            ds_put_format(s, "nw_proto=%"PRIu8",", f->nw_proto);
-        }
-    }
-    if (f->dl_type == htons(ETH_TYPE_ARP)) {
-        format_eth_masked(s, "arp_sha", f->arp_sha, wc->masks.arp_sha);
-        format_eth_masked(s, "arp_tha", f->arp_tha, wc->masks.arp_tha);
-    }
-    if (wc->masks.nw_tos & IP_DSCP_MASK) {
-        ds_put_format(s, "nw_tos=%"PRIu8",", f->nw_tos & IP_DSCP_MASK);
-    }
-    if (wc->masks.nw_tos & IP_ECN_MASK) {
-        ds_put_format(s, "nw_ecn=%"PRIu8",", f->nw_tos & IP_ECN_MASK);
-    }
-    if (wc->masks.nw_ttl) {
-        ds_put_format(s, "nw_ttl=%"PRIu8",", f->nw_ttl);
-    }
-    switch (wc->masks.nw_frag) {
-    case FLOW_NW_FRAG_ANY | FLOW_NW_FRAG_LATER:
-        ds_put_format(s, "nw_frag=%s,",
-                      f->nw_frag & FLOW_NW_FRAG_ANY
-                      ? (f->nw_frag & FLOW_NW_FRAG_LATER ? "later" : "first")
-                      : (f->nw_frag & FLOW_NW_FRAG_LATER ? "<error>" : "no"));
-        break;
-
-    case FLOW_NW_FRAG_ANY:
-        ds_put_format(s, "nw_frag=%s,",
-                      f->nw_frag & FLOW_NW_FRAG_ANY ? "yes" : "no");
-        break;
-
-    case FLOW_NW_FRAG_LATER:
-        ds_put_format(s, "nw_frag=%s,",
-                      f->nw_frag & FLOW_NW_FRAG_LATER ? "later" : "not_later");
-        break;
-    }
-    if (f->nw_proto == IPPROTO_ICMP) {
-        format_be16_masked(s, "icmp_type", f->tp_src, wc->masks.tp_src);
-        format_be16_masked(s, "icmp_code", f->tp_dst, wc->masks.tp_dst);
-    } else if (f->nw_proto == IPPROTO_ICMPV6) {
-        format_be16_masked(s, "icmp_type", f->tp_src, wc->masks.tp_src);
-        format_be16_masked(s, "icmp_code", f->tp_dst, wc->masks.tp_dst);
-        format_ipv6_netmask(s, "nd_target", &f->nd_target,
-                            &wc->masks.nd_target);
-        format_eth_masked(s, "nd_sll", f->arp_sha, wc->masks.arp_sha);
-        format_eth_masked(s, "nd_tll", f->arp_tha, wc->masks.arp_tha);
-   } else {
-        format_be16_masked(s, "tp_src", f->tp_src, wc->masks.tp_src);
-        format_be16_masked(s, "tp_dst", f->tp_dst, wc->masks.tp_dst);
-    }
-
-    if (s->length > start_len && ds_last(s) == ',') {
-        s->length--;
-    }
-}
-
-/* Converts 'rule' to a string and returns the string.  The caller must free
- * the string (with free()). */
-char *
-cls_rule_to_string(const struct cls_rule *rule)
-{
-    struct ds s = DS_EMPTY_INITIALIZER;
-    cls_rule_format(rule, &s);
-    return ds_steal_cstr(&s);
-}
-
-void
-cls_rule_print(const struct cls_rule *rule)
-{
-    char *s = cls_rule_to_string(rule);
-    puts(s);
-    free(s);
+    match_format(&rule->match, s, rule->priority);
 }
 
 /* Initializes 'cls' as a classifier that initially contains no classification
@@ -842,9 +149,9 @@ classifier_replace(struct classifier *cls, struct cls_rule *rule)
     struct cls_rule *old_rule;
     struct cls_table *table;
 
-    table = find_table(cls, &rule->wc);
+    table = find_table(cls, &rule->match.wc);
     if (!table) {
-        table = insert_table(cls, &rule->wc);
+        table = insert_table(cls, &rule->match.wc);
     }
 
     old_rule = insert_rule(table, rule);
@@ -876,8 +183,8 @@ classifier_remove(struct classifier *cls, struct cls_rule *rule)
     struct cls_rule *head;
     struct cls_table *table;
 
-    table = find_table(cls, &rule->wc);
-    head = find_equal(table, &rule->flow, rule->hmap_node.hash);
+    table = find_table(cls, &rule->match.wc);
+    head = find_equal(table, &rule->match.flow, rule->hmap_node.hash);
     if (head != rule) {
         list_remove(&rule->list);
     } else if (list_is_empty(&rule->list)) {
@@ -926,12 +233,13 @@ classifier_find_rule_exactly(const struct classifier *cls,
     struct cls_rule *head, *rule;
     struct cls_table *table;
 
-    table = find_table(cls, &target->wc);
+    table = find_table(cls, &target->match.wc);
     if (!table) {
         return NULL;
     }
 
-    head = find_equal(table, &target->flow, flow_hash(&target->flow, 0));
+    head = find_equal(table, &target->match.flow,
+                      flow_hash(&target->match.flow, 0));
     FOR_EACH_RULE_IN_LIST (rule, head) {
         if (target->priority >= rule->priority) {
             return target->priority == rule->priority ? rule : NULL;
@@ -953,13 +261,14 @@ classifier_rule_overlaps(const struct classifier *cls,
         struct flow_wildcards wc;
         struct cls_rule *head;
 
-        flow_wildcards_combine(&wc, &target->wc, &table->wc);
+        flow_wildcards_combine(&wc, &target->match.wc, &table->wc);
         HMAP_FOR_EACH (head, hmap_node, &table->rules) {
             struct cls_rule *rule;
 
             FOR_EACH_RULE_IN_LIST (rule, head) {
                 if (rule->priority == target->priority
-                    && flow_equal_except(&target->flow, &rule->flow, &wc)) {
+                    && flow_equal_except(&target->match.flow,
+                                         &rule->match.flow, &wc)) {
                     return true;
                 }
             }
@@ -1001,13 +310,14 @@ classifier_rule_overlaps(const struct classifier *cls,
  * This is the matching rule used by OpenFlow 1.0 non-strict OFPT_FLOW_MOD
  * commands and by OpenFlow 1.0 aggregate and flow stats.
  *
- * Ignores rule->priority and criteria->priority. */
+ * Ignores rule->priority. */
 bool
 cls_rule_is_loose_match(const struct cls_rule *rule,
-                        const struct cls_rule *criteria)
+                        const struct match *criteria)
 {
-    return (!flow_wildcards_has_extra(&rule->wc, &criteria->wc)
-            && flow_equal_except(&rule->flow, &criteria->flow, &criteria->wc));
+    return (!flow_wildcards_has_extra(&rule->match.wc, &criteria->wc)
+            && flow_equal_except(&rule->match.flow, &criteria->flow,
+                                 &criteria->wc));
 }
 
 /* Iteration. */
@@ -1016,13 +326,14 @@ static bool
 rule_matches(const struct cls_rule *rule, const struct cls_rule *target)
 {
     return (!target
-            || flow_equal_except(&rule->flow, &target->flow, &target->wc));
+            || flow_equal_except(&rule->match.flow, &target->match.flow,
+                                 &target->match.wc));
 }
 
 static struct cls_rule *
 search_table(const struct cls_table *table, const struct cls_rule *target)
 {
-    if (!target || !flow_wildcards_has_extra(&table->wc, &target->wc)) {
+    if (!target || !flow_wildcards_has_extra(&table->wc, &target->match.wc)) {
         struct cls_rule *rule;
 
         HMAP_FOR_EACH (rule, hmap_node, &table->rules) {
@@ -1155,7 +466,7 @@ find_match(const struct cls_table *table, const struct flow *flow)
         flow_zero_wildcards(&f, &table->wc);
         HMAP_FOR_EACH_WITH_HASH (rule, hmap_node, flow_hash(&f, 0),
                                  &table->rules) {
-            if (flow_equal(&f, &rule->flow)) {
+            if (flow_equal(&f, &rule->match.flow)) {
                 return rule;
             }
         }
@@ -1170,7 +481,7 @@ find_equal(struct cls_table *table, const struct flow *flow, uint32_t hash)
     struct cls_rule *head;
 
     HMAP_FOR_EACH_WITH_HASH (head, hmap_node, hash, &table->rules) {
-        if (flow_equal(&head->flow, flow)) {
+        if (flow_equal(&head->match.flow, flow)) {
             return head;
         }
     }
@@ -1182,9 +493,9 @@ insert_rule(struct cls_table *table, struct cls_rule *new)
 {
     struct cls_rule *head;
 
-    new->hmap_node.hash = flow_hash(&new->flow, 0);
+    new->hmap_node.hash = flow_hash(&new->match.flow, 0);
 
-    head = find_equal(table, &new->flow, new->hmap_node.hash);
+    head = find_equal(table, &new->match.flow, new->hmap_node.hash);
     if (!head) {
         hmap_insert(&table->rules, &new->hmap_node, new->hmap_node.hash);
         list_init(&new->list);
diff --git a/lib/classifier.h b/lib/classifier.h
index b76b957..8a11f05 100644
--- a/lib/classifier.h
+++ b/lib/classifier.h
@@ -29,6 +29,7 @@
 #include "flow.h"
 #include "hmap.h"
 #include "list.h"
+#include "match.h"
 #include "openflow/nicira-ext.h"
 #include "openflow/openflow.h"
 
@@ -59,104 +60,24 @@ cls_table_is_catchall(const struct cls_table *table)
     return table->is_catchall;
 }
 
-/* A flow classification rule.
- *
- * Use one of the cls_rule_*() functions to initialize a cls_rule.
- *
- * The cls_rule_*() functions below maintain the following important
- * invariant that the classifier depends on:
- *
- *   - If a bit or a field is wildcarded in 'wc', then the corresponding bit or
- *     field in 'flow' is set to all-0-bits.  (The
- *     cls_rule_zero_wildcarded_fields() function can be used to restore this
- *     invariant after adding wildcards.)
- */
+/* A rule in a "struct classifier" */
 struct cls_rule {
     struct hmap_node hmap_node; /* Within struct cls_table 'rules'. */
     struct list list;           /* List of identical, lower-priority rules. */
-    struct flow flow;           /* All field values. */
-    struct flow_wildcards wc;   /* Wildcards for fields. */
+    struct match match;         /* Matching rule. */
     unsigned int priority;      /* Larger numbers are higher priorities. */
 };
 
-void cls_rule_init(const struct flow *, const struct flow_wildcards *,
-                   unsigned int priority, struct cls_rule *);
-void cls_rule_init_exact(const struct flow *, unsigned int priority,
-                         struct cls_rule *);
-void cls_rule_init_catchall(struct cls_rule *, unsigned int priority);
-
-void cls_rule_zero_wildcarded_fields(struct cls_rule *);
-
-bool cls_rule_is_loose_match(const struct cls_rule *rule,
-                             const struct cls_rule *criteria);
-
-void cls_rule_set_reg(struct cls_rule *, unsigned int reg_idx, uint32_t value);
-void cls_rule_set_reg_masked(struct cls_rule *, unsigned int reg_idx,
-                             uint32_t value, uint32_t mask);
-void cls_rule_set_metadata(struct cls_rule *, ovs_be64 metadata);
-void cls_rule_set_metadata_masked(struct cls_rule *, ovs_be64 metadata,
-                                  ovs_be64 mask);
-void cls_rule_set_tun_id(struct cls_rule *, ovs_be64 tun_id);
-void cls_rule_set_tun_id_masked(struct cls_rule *,
-                                ovs_be64 tun_id, ovs_be64 mask);
-void cls_rule_set_in_port(struct cls_rule *, uint16_t ofp_port);
-void cls_rule_set_dl_type(struct cls_rule *, ovs_be16);
-void cls_rule_set_dl_src(struct cls_rule *, const uint8_t[6]);
-void cls_rule_set_dl_src_masked(struct cls_rule *, const uint8_t dl_src[6],
-                                const uint8_t mask[6]);
-void cls_rule_set_dl_dst(struct cls_rule *, const uint8_t[6]);
-void cls_rule_set_dl_dst_masked(struct cls_rule *, const uint8_t dl_dst[6],
-                                const uint8_t mask[6]);
-void cls_rule_set_dl_tci(struct cls_rule *, ovs_be16 tci);
-void cls_rule_set_dl_tci_masked(struct cls_rule *,
-                                ovs_be16 tci, ovs_be16 mask);
-void cls_rule_set_any_vid(struct cls_rule *);
-void cls_rule_set_dl_vlan(struct cls_rule *, ovs_be16);
-void cls_rule_set_any_pcp(struct cls_rule *);
-void cls_rule_set_dl_vlan_pcp(struct cls_rule *, uint8_t);
-void cls_rule_set_tp_src(struct cls_rule *, ovs_be16);
-void cls_rule_set_tp_src_masked(struct cls_rule *,
-                                ovs_be16 port, ovs_be16 mask);
-void cls_rule_set_tp_dst(struct cls_rule *, ovs_be16);
-void cls_rule_set_tp_dst_masked(struct cls_rule *,
-                                ovs_be16 port, ovs_be16 mask);
-void cls_rule_set_nw_proto(struct cls_rule *, uint8_t);
-void cls_rule_set_nw_src(struct cls_rule *, ovs_be32);
-void cls_rule_set_nw_src_masked(struct cls_rule *, ovs_be32 ip, ovs_be32 mask);
-void cls_rule_set_nw_dst(struct cls_rule *, ovs_be32);
-void cls_rule_set_nw_dst_masked(struct cls_rule *, ovs_be32 ip, ovs_be32 mask);
-void cls_rule_set_nw_dscp(struct cls_rule *, uint8_t);
-void cls_rule_set_nw_ecn(struct cls_rule *, uint8_t);
-void cls_rule_set_nw_ttl(struct cls_rule *, uint8_t);
-void cls_rule_set_nw_frag(struct cls_rule *, uint8_t nw_frag);
-void cls_rule_set_nw_frag_masked(struct cls_rule *,
-                                 uint8_t nw_frag, uint8_t mask);
-void cls_rule_set_icmp_type(struct cls_rule *, uint8_t);
-void cls_rule_set_icmp_code(struct cls_rule *, uint8_t);
-void cls_rule_set_arp_sha(struct cls_rule *, const uint8_t[6]);
-void cls_rule_set_arp_sha_masked(struct cls_rule *, const uint8_t[6],
-                                 const uint8_t [6]);
-void cls_rule_set_arp_tha(struct cls_rule *, const uint8_t[6]);
-void cls_rule_set_arp_tha_masked(struct cls_rule *, const uint8_t[6],
-                                 const uint8_t [6]);
-void cls_rule_set_ipv6_src(struct cls_rule *, const struct in6_addr *);
-void cls_rule_set_ipv6_src_masked(struct cls_rule *, const struct in6_addr *,
-                                  const struct in6_addr *);
-void cls_rule_set_ipv6_dst(struct cls_rule *, const struct in6_addr *);
-void cls_rule_set_ipv6_dst_masked(struct cls_rule *, const struct in6_addr *,
-                                  const struct in6_addr *);
-void cls_rule_set_ipv6_label(struct cls_rule *, ovs_be32);
-void cls_rule_set_ipv6_label_masked(struct cls_rule *, ovs_be32, ovs_be32);
-void cls_rule_set_nd_target(struct cls_rule *, const struct in6_addr *);
-void cls_rule_set_nd_target_masked(struct cls_rule *, const struct in6_addr *,
-                                   const struct in6_addr *);
+void cls_rule_init(struct cls_rule *,
+                   const struct match *, unsigned int priority);
 
 bool cls_rule_equal(const struct cls_rule *, const struct cls_rule *);
 uint32_t cls_rule_hash(const struct cls_rule *, uint32_t basis);
 
 void cls_rule_format(const struct cls_rule *, struct ds *);
-char *cls_rule_to_string(const struct cls_rule *);
-void cls_rule_print(const struct cls_rule *);
+
+bool cls_rule_is_loose_match(const struct cls_rule *rule,
+                             const struct match *criteria);
 
 void classifier_init(struct classifier *);
 void classifier_destroy(struct classifier *);
diff --git a/lib/learn.c b/lib/learn.c
index 28b8012..cd75321 100644
--- a/lib/learn.c
+++ b/lib/learn.c
@@ -20,6 +20,7 @@
 
 #include "byte-order.h"
 #include "dynamic-string.h"
+#include "match.h"
 #include "meta-flow.h"
 #include "nx-match.h"
 #include "ofp-actions.h"
@@ -170,9 +171,9 @@ enum ofperr
 learn_check(const struct ofpact_learn *learn, const struct flow *flow)
 {
     const struct ofpact_learn_spec *spec;
-    struct cls_rule rule;
+    struct match match;
 
-    cls_rule_init_catchall(&rule, 0);
+    match_init_catchall(&match);
     for (spec = learn->specs; spec < &learn->specs[learn->n_specs]; spec++) {
         enum ofperr error;
 
@@ -187,16 +188,16 @@ learn_check(const struct ofpact_learn *learn, const struct flow *flow)
         /* Check the destination. */
         switch (spec->dst_type) {
         case NX_LEARN_DST_MATCH:
-            error = mf_check_src(&spec->dst, &rule.flow);
+            error = mf_check_src(&spec->dst, &match.flow);
             if (error) {
                 return error;
             }
 
-            mf_write_subfield(&spec->dst, &spec->src_imm, &rule);
+            mf_write_subfield(&spec->dst, &spec->src_imm, &match);
             break;
 
         case NX_LEARN_DST_LOAD:
-            error = mf_check_dst(&spec->dst, &rule.flow);
+            error = mf_check_dst(&spec->dst, &match.flow);
             if (error) {
                 return error;
             }
@@ -297,7 +298,8 @@ learn_execute(const struct ofpact_learn *learn, const struct flow *flow,
 {
     const struct ofpact_learn_spec *spec;
 
-    cls_rule_init_catchall(&fm->cr, learn->priority);
+    match_init_catchall(&fm->match);
+    fm->priority = learn->priority;
     fm->cookie = htonll(0);
     fm->cookie_mask = htonll(0);
     fm->new_cookie = htonll(learn->cookie);
@@ -331,7 +333,7 @@ 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->cr);
+            mf_write_subfield(&spec->dst, &value, &fm->match);
             break;
 
         case NX_LEARN_DST_LOAD:
@@ -508,7 +510,7 @@ learn_parse_spec(const char *orig, char *name, char *value,
  *
  * Prints an error on stderr and aborts the program if 'arg' syntax is invalid.
  *
- * If 'flow' is nonnull, then it should be the flow from a cls_rule that is
+ * If 'flow' is nonnull, then it should be the flow from a struct match that is
  * the matching rule for the learning action.  This helps to better validate
  * the action's arguments.
  *
@@ -520,7 +522,7 @@ learn_parse(char *arg, const struct flow *flow, struct ofpbuf *ofpacts)
     char *name, *value;
 
     struct ofpact_learn *learn;
-    struct cls_rule rule;
+    struct match match;
     enum ofperr error;
 
     learn = ofpact_put_LEARN(ofpacts);
@@ -529,7 +531,7 @@ learn_parse(char *arg, const struct flow *flow, struct ofpbuf *ofpacts)
     learn->priority = OFP_DEFAULT_PRIORITY;
     learn->table_id = 1;
 
-    cls_rule_init_catchall(&rule, 0);
+    match_init_catchall(&match);
     while (ofputil_parse_key_value(&arg, &name, &value)) {
         if (!strcmp(name, "table")) {
             learn->table_id = atoi(value);
@@ -567,17 +569,17 @@ learn_parse(char *arg, const struct flow *flow, struct ofpbuf *ofpacts)
             }
             if ((spec->dst_type == NX_LEARN_DST_MATCH
                  || spec->dst_type == NX_LEARN_DST_LOAD)
-                && !mf_are_prereqs_ok(spec->dst.field, &rule.flow)) {
+                && !mf_are_prereqs_ok(spec->dst.field, &match.flow)) {
                 ovs_fatal(0, "%s: cannot specify destination field %s because "
                           "prerequisites are not satisfied",
                           orig, spec->dst.field->name);
             }
 
-            /* Update 'rule' to allow for satisfying destination
+            /* Update 'match' to allow for satisfying destination
              * prerequisites. */
             if (spec->src_type == NX_LEARN_SRC_IMMEDIATE
                 && spec->dst_type == NX_LEARN_DST_MATCH) {
-                mf_write_subfield(&spec->dst, &spec->src_imm, &rule);
+                mf_write_subfield(&spec->dst, &spec->src_imm, &match);
             }
         }
     }
@@ -616,9 +618,9 @@ void
 learn_format(const struct ofpact_learn *learn, struct ds *s)
 {
     const struct ofpact_learn_spec *spec;
-    struct cls_rule rule;
+    struct match match;
 
-    cls_rule_init_catchall(&rule, 0);
+    match_init_catchall(&match);
 
     ds_put_format(s, "learn(table=%"PRIu8, learn->table_id);
     if (learn->idle_timeout != OFP_FLOW_PERMANENT) {
diff --git a/lib/learning-switch.c b/lib/learning-switch.c
index b41bea0..dd74226 100644
--- a/lib/learning-switch.c
+++ b/lib/learning-switch.c
@@ -514,7 +514,8 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn,
         /* The output port is known, or we always flood everything, so add a
          * new flow. */
         memset(&fm, 0, sizeof fm);
-        cls_rule_init(&flow, &sw->wc, 0, &fm.cr);
+        match_init(&fm.match, &flow, &sw->wc);
+        fm.priority = 0;
         fm.table_id = 0xff;
         fm.command = OFPFC_ADD;
         fm.idle_timeout = sw->max_idle;
diff --git a/lib/match.c b/lib/match.c
new file mode 100644
index 0000000..d3f4e84
--- /dev/null
+++ b/lib/match.c
@@ -0,0 +1,750 @@
+/*
+ * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "match.h"
+#include <assert.h>
+#include <stdlib.h>
+#include "byte-order.h"
+#include "dynamic-string.h"
+#include "packets.h"
+
+/* Converts the flow in 'flow' into a match in 'match', with the given
+ * 'wildcards'. */
+void
+match_init(struct match *match,
+           const struct flow *flow, const struct flow_wildcards *wc)
+{
+    match->flow = *flow;
+    match->wc = *wc;
+    match_zero_wildcarded_fields(match);
+}
+
+/* Converts the flow in 'flow' into an exact-match match in 'match'. */
+void
+match_init_exact(struct match *match, const struct flow *flow)
+{
+    match->flow = *flow;
+    match->flow.skb_priority = 0;
+    flow_wildcards_init_exact(&match->wc);
+}
+
+/* Initializes 'match' as a "catch-all" match that matches every packet. */
+void
+match_init_catchall(struct match *match)
+{
+    memset(&match->flow, 0, sizeof match->flow);
+    flow_wildcards_init_catchall(&match->wc);
+}
+
+/* For each bit or field wildcarded in 'match', sets the corresponding bit or
+ * field in 'flow' to all-0-bits.  It is important to maintain this invariant
+ * in a clr_match that might be inserted into a classifier.
+ *
+ * It is never necessary to call this function directly for a match that is
+ * initialized or modified only by match_*() functions.  It is useful to
+ * restore the invariant in a match whose 'wc' member is modified by hand.
+ */
+void
+match_zero_wildcarded_fields(struct match *match)
+{
+    flow_zero_wildcards(&match->flow, &match->wc);
+}
+
+void
+match_set_reg(struct match *match, unsigned int reg_idx, uint32_t value)
+{
+    match_set_reg_masked(match, reg_idx, value, UINT32_MAX);
+}
+
+void
+match_set_reg_masked(struct match *match, unsigned int reg_idx,
+                     uint32_t value, uint32_t mask)
+{
+    assert(reg_idx < FLOW_N_REGS);
+    flow_wildcards_set_reg_mask(&match->wc, reg_idx, mask);
+    match->flow.regs[reg_idx] = value & mask;
+}
+
+void
+match_set_metadata(struct match *match, ovs_be64 metadata)
+{
+    match_set_metadata_masked(match, metadata, htonll(UINT64_MAX));
+}
+
+void
+match_set_metadata_masked(struct match *match, ovs_be64 metadata, ovs_be64 mask)
+{
+    match->wc.masks.metadata = mask;
+    match->flow.metadata = metadata & mask;
+}
+
+void
+match_set_tun_id(struct match *match, ovs_be64 tun_id)
+{
+    match_set_tun_id_masked(match, tun_id, htonll(UINT64_MAX));
+}
+
+void
+match_set_tun_id_masked(struct match *match, ovs_be64 tun_id, ovs_be64 mask)
+{
+    match->wc.masks.tun_id = mask;
+    match->flow.tun_id = tun_id & mask;
+}
+
+void
+match_set_in_port(struct match *match, uint16_t ofp_port)
+{
+    match->wc.masks.in_port = UINT16_MAX;
+    match->flow.in_port = ofp_port;
+}
+
+void
+match_set_dl_type(struct match *match, ovs_be16 dl_type)
+{
+    match->wc.masks.dl_type = htons(UINT16_MAX);
+    match->flow.dl_type = dl_type;
+}
+
+/* Modifies 'value_src' so that the Ethernet address must match 'value_dst'
+ * exactly.  'mask_dst' is set to all 1s. */
+static void
+set_eth(const uint8_t value_src[ETH_ADDR_LEN],
+        uint8_t value_dst[ETH_ADDR_LEN],
+        uint8_t mask_dst[ETH_ADDR_LEN])
+{
+    memcpy(value_dst, value_src, ETH_ADDR_LEN);
+    memset(mask_dst, 0xff, ETH_ADDR_LEN);
+}
+
+/* Modifies 'value_src' so that the Ethernet address must match 'value_src'
+ * after each byte is ANDed with the appropriate byte in 'mask_src'.
+ * 'mask_dst' is set to 'mask_src' */
+static void
+set_eth_masked(const uint8_t value_src[ETH_ADDR_LEN],
+               const uint8_t mask_src[ETH_ADDR_LEN],
+               uint8_t value_dst[ETH_ADDR_LEN],
+               uint8_t mask_dst[ETH_ADDR_LEN])
+{
+    size_t i;
+
+    for (i = 0; i < ETH_ADDR_LEN; i++) {
+        value_dst[i] = value_src[i] & mask_src[i];
+        mask_dst[i] = mask_src[i];
+    }
+}
+
+/* Modifies 'rule' so that the source Ethernet address must match 'dl_src'
+ * exactly. */
+void
+match_set_dl_src(struct match *match, const uint8_t dl_src[ETH_ADDR_LEN])
+{
+    set_eth(dl_src, match->flow.dl_src, match->wc.masks.dl_src);
+}
+
+/* Modifies 'rule' so that the source Ethernet address must match 'dl_src'
+ * after each byte is ANDed with the appropriate byte in 'mask'. */
+void
+match_set_dl_src_masked(struct match *match,
+                        const uint8_t dl_src[ETH_ADDR_LEN],
+                        const uint8_t mask[ETH_ADDR_LEN])
+{
+    set_eth_masked(dl_src, mask, match->flow.dl_src, match->wc.masks.dl_src);
+}
+
+/* Modifies 'match' so that the Ethernet address must match 'dl_dst'
+ * exactly. */
+void
+match_set_dl_dst(struct match *match, const uint8_t dl_dst[ETH_ADDR_LEN])
+{
+    set_eth(dl_dst, match->flow.dl_dst, match->wc.masks.dl_dst);
+}
+
+/* Modifies 'match' so that the Ethernet address must match 'dl_dst' after each
+ * byte is ANDed with the appropriate byte in 'mask'.
+ *
+ * This function will assert-fail if 'mask' is invalid.  Only 'mask' values
+ * accepted by flow_wildcards_is_dl_dst_mask_valid() are allowed. */
+void
+match_set_dl_dst_masked(struct match *match,
+                        const uint8_t dl_dst[ETH_ADDR_LEN],
+                        const uint8_t mask[ETH_ADDR_LEN])
+{
+    set_eth_masked(dl_dst, mask, match->flow.dl_dst, match->wc.masks.dl_dst);
+}
+
+void
+match_set_dl_tci(struct match *match, ovs_be16 tci)
+{
+    match_set_dl_tci_masked(match, tci, htons(0xffff));
+}
+
+void
+match_set_dl_tci_masked(struct match *match, ovs_be16 tci, ovs_be16 mask)
+{
+    match->flow.vlan_tci = tci & mask;
+    match->wc.masks.vlan_tci = mask;
+}
+
+/* Modifies 'match' so that the VLAN VID is wildcarded.  If the PCP is already
+ * wildcarded, then 'match' will match a packet regardless of whether it has an
+ * 802.1Q header or not. */
+void
+match_set_any_vid(struct match *match)
+{
+    if (match->wc.masks.vlan_tci & htons(VLAN_PCP_MASK)) {
+        match->wc.masks.vlan_tci &= ~htons(VLAN_VID_MASK);
+        match->flow.vlan_tci &= ~htons(VLAN_VID_MASK);
+    } else {
+        match_set_dl_tci_masked(match, htons(0), htons(0));
+    }
+}
+
+/* Modifies 'match' depending on 'dl_vlan':
+ *
+ *   - If 'dl_vlan' is htons(OFP_VLAN_NONE), makes 'match' match only packets
+ *     without an 802.1Q header.
+ *
+ *   - Otherwise, makes 'match' match only packets with an 802.1Q header whose
+ *     VID equals the low 12 bits of 'dl_vlan'.
+ */
+void
+match_set_dl_vlan(struct match *match, ovs_be16 dl_vlan)
+{
+    flow_set_vlan_vid(&match->flow, dl_vlan);
+    if (dl_vlan == htons(OFP10_VLAN_NONE)) {
+        match->wc.masks.vlan_tci = htons(UINT16_MAX);
+    } else {
+        match->wc.masks.vlan_tci |= htons(VLAN_VID_MASK | VLAN_CFI);
+    }
+}
+
+/* Modifies 'match' so that the VLAN PCP is wildcarded.  If the VID is already
+ * wildcarded, then 'match' will match a packet regardless of whether it has an
+ * 802.1Q header or not. */
+void
+match_set_any_pcp(struct match *match)
+{
+    if (match->wc.masks.vlan_tci & htons(VLAN_VID_MASK)) {
+        match->wc.masks.vlan_tci &= ~htons(VLAN_PCP_MASK);
+        match->flow.vlan_tci &= ~htons(VLAN_PCP_MASK);
+    } else {
+        match_set_dl_tci_masked(match, htons(0), htons(0));
+    }
+}
+
+/* Modifies 'match' so that it matches only packets with an 802.1Q header whose
+ * PCP equals the low 3 bits of 'dl_vlan_pcp'. */
+void
+match_set_dl_vlan_pcp(struct match *match, uint8_t dl_vlan_pcp)
+{
+    flow_set_vlan_pcp(&match->flow, dl_vlan_pcp);
+    match->wc.masks.vlan_tci |= htons(VLAN_CFI | VLAN_PCP_MASK);
+}
+
+void
+match_set_tp_src(struct match *match, ovs_be16 tp_src)
+{
+    match_set_tp_src_masked(match, tp_src, htons(UINT16_MAX));
+}
+
+void
+match_set_tp_src_masked(struct match *match, ovs_be16 port, ovs_be16 mask)
+{
+    match->flow.tp_src = port & mask;
+    match->wc.masks.tp_src = mask;
+}
+
+void
+match_set_tp_dst(struct match *match, ovs_be16 tp_dst)
+{
+    match_set_tp_dst_masked(match, tp_dst, htons(UINT16_MAX));
+}
+
+void
+match_set_tp_dst_masked(struct match *match, ovs_be16 port, ovs_be16 mask)
+{
+    match->flow.tp_dst = port & mask;
+    match->wc.masks.tp_dst = mask;
+}
+
+void
+match_set_nw_proto(struct match *match, uint8_t nw_proto)
+{
+    match->flow.nw_proto = nw_proto;
+    match->wc.masks.nw_proto = UINT8_MAX;
+}
+
+void
+match_set_nw_src(struct match *match, ovs_be32 nw_src)
+{
+    match->flow.nw_src = nw_src;
+    match->wc.masks.nw_src = htonl(UINT32_MAX);
+}
+
+void
+match_set_nw_src_masked(struct match *match,
+                        ovs_be32 nw_src, ovs_be32 mask)
+{
+    match->flow.nw_src = nw_src & mask;
+    match->wc.masks.nw_src = mask;
+}
+
+void
+match_set_nw_dst(struct match *match, ovs_be32 nw_dst)
+{
+    match->flow.nw_dst = nw_dst;
+    match->wc.masks.nw_dst = htonl(UINT32_MAX);
+}
+
+void
+match_set_nw_dst_masked(struct match *match, ovs_be32 ip, ovs_be32 mask)
+{
+    match->flow.nw_dst = ip & mask;
+    match->wc.masks.nw_dst = mask;
+}
+
+void
+match_set_nw_dscp(struct match *match, uint8_t nw_dscp)
+{
+    match->wc.masks.nw_tos |= IP_DSCP_MASK;
+    match->flow.nw_tos &= ~IP_DSCP_MASK;
+    match->flow.nw_tos |= nw_dscp & IP_DSCP_MASK;
+}
+
+void
+match_set_nw_ecn(struct match *match, uint8_t nw_ecn)
+{
+    match->wc.masks.nw_tos |= IP_ECN_MASK;
+    match->flow.nw_tos &= ~IP_ECN_MASK;
+    match->flow.nw_tos |= nw_ecn & IP_ECN_MASK;
+}
+
+void
+match_set_nw_ttl(struct match *match, uint8_t nw_ttl)
+{
+    match->wc.masks.nw_ttl = UINT8_MAX;
+    match->flow.nw_ttl = nw_ttl;
+}
+
+void
+match_set_nw_frag(struct match *match, uint8_t nw_frag)
+{
+    match->wc.masks.nw_frag |= FLOW_NW_FRAG_MASK;
+    match->flow.nw_frag = nw_frag;
+}
+
+void
+match_set_nw_frag_masked(struct match *match,
+                         uint8_t nw_frag, uint8_t mask)
+{
+    match->flow.nw_frag = nw_frag & mask;
+    match->wc.masks.nw_frag = mask;
+}
+
+void
+match_set_icmp_type(struct match *match, uint8_t icmp_type)
+{
+    match_set_tp_src(match, htons(icmp_type));
+}
+
+void
+match_set_icmp_code(struct match *match, uint8_t icmp_code)
+{
+    match_set_tp_dst(match, htons(icmp_code));
+}
+
+void
+match_set_arp_sha(struct match *match, const uint8_t sha[ETH_ADDR_LEN])
+{
+    memcpy(match->flow.arp_sha, sha, ETH_ADDR_LEN);
+    memset(match->wc.masks.arp_sha, UINT8_MAX, ETH_ADDR_LEN);
+}
+
+void
+match_set_arp_sha_masked(struct match *match,
+                         const uint8_t arp_sha[ETH_ADDR_LEN],
+                         const uint8_t mask[ETH_ADDR_LEN])
+{
+    set_eth_masked(arp_sha, mask,
+                   match->flow.arp_sha, match->wc.masks.arp_sha);
+}
+
+void
+match_set_arp_tha(struct match *match, const uint8_t tha[ETH_ADDR_LEN])
+{
+    memcpy(match->flow.arp_tha, tha, ETH_ADDR_LEN);
+    memset(match->wc.masks.arp_tha, UINT8_MAX, ETH_ADDR_LEN);
+}
+
+void
+match_set_arp_tha_masked(struct match *match,
+                         const uint8_t arp_tha[ETH_ADDR_LEN],
+                         const uint8_t mask[ETH_ADDR_LEN])
+{
+    set_eth_masked(arp_tha, mask,
+                   match->flow.arp_tha, match->wc.masks.arp_tha);
+}
+
+void
+match_set_ipv6_src(struct match *match, const struct in6_addr *src)
+{
+    match->flow.ipv6_src = *src;
+    match->wc.masks.ipv6_src = in6addr_exact;
+}
+
+void
+match_set_ipv6_src_masked(struct match *match, const struct in6_addr *src,
+                          const struct in6_addr *mask)
+{
+    match->flow.ipv6_src = ipv6_addr_bitand(src, mask);
+    match->wc.masks.ipv6_src = *mask;
+}
+
+void
+match_set_ipv6_dst(struct match *match, const struct in6_addr *dst)
+{
+    match->flow.ipv6_dst = *dst;
+    match->wc.masks.ipv6_dst = in6addr_exact;
+}
+
+void
+match_set_ipv6_dst_masked(struct match *match, const struct in6_addr *dst,
+                          const struct in6_addr *mask)
+{
+    match->flow.ipv6_dst = ipv6_addr_bitand(dst, mask);
+    match->wc.masks.ipv6_dst = *mask;
+}
+
+void
+match_set_ipv6_label(struct match *match, ovs_be32 ipv6_label)
+{
+    match->wc.masks.ipv6_label = htonl(UINT32_MAX);
+    match->flow.ipv6_label = ipv6_label;
+}
+
+
+void
+match_set_ipv6_label_masked(struct match *match, ovs_be32 ipv6_label,
+                            ovs_be32 mask)
+{
+    match->flow.ipv6_label = ipv6_label & mask;
+    match->wc.masks.ipv6_label = mask;
+}
+
+void
+match_set_nd_target(struct match *match, const struct in6_addr *target)
+{
+    match->flow.nd_target = *target;
+    match->wc.masks.nd_target = in6addr_exact;
+}
+
+void
+match_set_nd_target_masked(struct match *match,
+                           const struct in6_addr *target,
+                           const struct in6_addr *mask)
+{
+    match->flow.nd_target = ipv6_addr_bitand(target, mask);
+    match->wc.masks.nd_target = *mask;
+}
+
+/* Returns true if 'a' and 'b' wildcard the same fields and have the same
+ * values for fixed fields, otherwise false. */
+bool
+match_equal(const struct match *a, const struct match *b)
+{
+    return (flow_wildcards_equal(&a->wc, &b->wc)
+            && flow_equal(&a->flow, &b->flow));
+}
+
+/* Returns a hash value for the flow and wildcards in 'match', starting from
+ * 'basis'. */
+uint32_t
+match_hash(const struct match *match, uint32_t basis)
+{
+    return flow_wildcards_hash(&match->wc, flow_hash(&match->flow, basis));
+}
+
+static void
+format_eth_masked(struct ds *s, const char *name, const uint8_t eth[6],
+                  const uint8_t mask[6])
+{
+    if (!eth_addr_is_zero(mask)) {
+        ds_put_format(s, "%s=", name);
+        eth_format_masked(eth, mask, s);
+        ds_put_char(s, ',');
+    }
+}
+
+static void
+format_ip_netmask(struct ds *s, const char *name, ovs_be32 ip,
+                  ovs_be32 netmask)
+{
+    if (netmask) {
+        ds_put_format(s, "%s=", name);
+        ip_format_masked(ip, netmask, s);
+        ds_put_char(s, ',');
+    }
+}
+
+static void
+format_ipv6_netmask(struct ds *s, const char *name,
+                    const struct in6_addr *addr,
+                    const struct in6_addr *netmask)
+{
+    if (!ipv6_mask_is_any(netmask)) {
+        ds_put_format(s, "%s=", name);
+        print_ipv6_masked(s, addr, netmask);
+        ds_put_char(s, ',');
+    }
+}
+
+
+static void
+format_be16_masked(struct ds *s, const char *name,
+                   ovs_be16 value, ovs_be16 mask)
+{
+    if (mask != htons(0)) {
+        ds_put_format(s, "%s=", name);
+        if (mask == htons(UINT16_MAX)) {
+            ds_put_format(s, "%"PRIu16, ntohs(value));
+        } else {
+            ds_put_format(s, "0x%"PRIx16"/0x%"PRIx16,
+                          ntohs(value), ntohs(mask));
+        }
+        ds_put_char(s, ',');
+    }
+}
+
+/* Appends a string representation of 'match' to 's'.  If 'priority' is
+ * different from OFP_DEFAULT_PRIORITY, includes it in 's'. */
+void
+match_format(const struct match *match, struct ds *s, unsigned int priority)
+{
+    const struct flow_wildcards *wc = &match->wc;
+    size_t start_len = s->length;
+    const struct flow *f = &match->flow;
+    bool skip_type = false;
+    bool skip_proto = false;
+
+    int i;
+
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
+
+    if (priority != OFP_DEFAULT_PRIORITY) {
+        ds_put_format(s, "priority=%u,", priority);
+    }
+
+    if (wc->masks.dl_type) {
+        skip_type = true;
+        if (f->dl_type == htons(ETH_TYPE_IP)) {
+            if (wc->masks.nw_proto) {
+                skip_proto = true;
+                if (f->nw_proto == IPPROTO_ICMP) {
+                    ds_put_cstr(s, "icmp,");
+                } else if (f->nw_proto == IPPROTO_TCP) {
+                    ds_put_cstr(s, "tcp,");
+                } else if (f->nw_proto == IPPROTO_UDP) {
+                    ds_put_cstr(s, "udp,");
+                } else {
+                    ds_put_cstr(s, "ip,");
+                    skip_proto = false;
+                }
+            } else {
+                ds_put_cstr(s, "ip,");
+            }
+        } else if (f->dl_type == htons(ETH_TYPE_IPV6)) {
+            if (wc->masks.nw_proto) {
+                skip_proto = true;
+                if (f->nw_proto == IPPROTO_ICMPV6) {
+                    ds_put_cstr(s, "icmp6,");
+                } else if (f->nw_proto == IPPROTO_TCP) {
+                    ds_put_cstr(s, "tcp6,");
+                } else if (f->nw_proto == IPPROTO_UDP) {
+                    ds_put_cstr(s, "udp6,");
+                } else {
+                    ds_put_cstr(s, "ipv6,");
+                    skip_proto = false;
+                }
+            } else {
+                ds_put_cstr(s, "ipv6,");
+            }
+        } else if (f->dl_type == htons(ETH_TYPE_ARP)) {
+            ds_put_cstr(s, "arp,");
+        } else {
+            skip_type = false;
+        }
+    }
+    for (i = 0; i < FLOW_N_REGS; i++) {
+        switch (wc->masks.regs[i]) {
+        case 0:
+            break;
+        case UINT32_MAX:
+            ds_put_format(s, "reg%d=0x%"PRIx32",", i, f->regs[i]);
+            break;
+        default:
+            ds_put_format(s, "reg%d=0x%"PRIx32"/0x%"PRIx32",",
+                          i, f->regs[i], wc->masks.regs[i]);
+            break;
+        }
+    }
+    switch (wc->masks.tun_id) {
+    case 0:
+        break;
+    case CONSTANT_HTONLL(UINT64_MAX):
+        ds_put_format(s, "tun_id=%#"PRIx64",", ntohll(f->tun_id));
+        break;
+    default:
+        ds_put_format(s, "tun_id=%#"PRIx64"/%#"PRIx64",",
+                      ntohll(f->tun_id), ntohll(wc->masks.tun_id));
+        break;
+    }
+    switch (wc->masks.metadata) {
+    case 0:
+        break;
+    case CONSTANT_HTONLL(UINT64_MAX):
+        ds_put_format(s, "metadata=%#"PRIx64",", ntohll(f->metadata));
+        break;
+    default:
+        ds_put_format(s, "metadata=%#"PRIx64"/%#"PRIx64",",
+                      ntohll(f->metadata), ntohll(wc->masks.metadata));
+        break;
+    }
+    if (wc->masks.in_port) {
+        ds_put_format(s, "in_port=%"PRIu16",", f->in_port);
+    }
+    if (wc->masks.vlan_tci) {
+        ovs_be16 vid_mask = wc->masks.vlan_tci & htons(VLAN_VID_MASK);
+        ovs_be16 pcp_mask = wc->masks.vlan_tci & htons(VLAN_PCP_MASK);
+        ovs_be16 cfi = wc->masks.vlan_tci & htons(VLAN_CFI);
+
+        if (cfi && f->vlan_tci & htons(VLAN_CFI)
+            && (!vid_mask || vid_mask == htons(VLAN_VID_MASK))
+            && (!pcp_mask || pcp_mask == htons(VLAN_PCP_MASK))
+            && (vid_mask || pcp_mask)) {
+            if (vid_mask) {
+                ds_put_format(s, "dl_vlan=%"PRIu16",",
+                              vlan_tci_to_vid(f->vlan_tci));
+            }
+            if (pcp_mask) {
+                ds_put_format(s, "dl_vlan_pcp=%d,",
+                              vlan_tci_to_pcp(f->vlan_tci));
+            }
+        } else if (wc->masks.vlan_tci == htons(0xffff)) {
+            ds_put_format(s, "vlan_tci=0x%04"PRIx16",", ntohs(f->vlan_tci));
+        } else {
+            ds_put_format(s, "vlan_tci=0x%04"PRIx16"/0x%04"PRIx16",",
+                          ntohs(f->vlan_tci), ntohs(wc->masks.vlan_tci));
+        }
+    }
+    format_eth_masked(s, "dl_src", f->dl_src, wc->masks.dl_src);
+    format_eth_masked(s, "dl_dst", f->dl_dst, wc->masks.dl_dst);
+    if (!skip_type && wc->masks.dl_type) {
+        ds_put_format(s, "dl_type=0x%04"PRIx16",", ntohs(f->dl_type));
+    }
+    if (f->dl_type == htons(ETH_TYPE_IPV6)) {
+        format_ipv6_netmask(s, "ipv6_src", &f->ipv6_src, &wc->masks.ipv6_src);
+        format_ipv6_netmask(s, "ipv6_dst", &f->ipv6_dst, &wc->masks.ipv6_dst);
+        if (wc->masks.ipv6_label) {
+            if (wc->masks.ipv6_label == htonl(UINT32_MAX)) {
+                ds_put_format(s, "ipv6_label=0x%05"PRIx32",",
+                              ntohl(f->ipv6_label));
+            } else {
+                ds_put_format(s, "ipv6_label=0x%05"PRIx32"/0x%05"PRIx32",",
+                              ntohl(f->ipv6_label),
+                              ntohl(wc->masks.ipv6_label));
+            }
+        }
+    } else {
+        format_ip_netmask(s, "nw_src", f->nw_src, wc->masks.nw_src);
+        format_ip_netmask(s, "nw_dst", f->nw_dst, wc->masks.nw_dst);
+    }
+    if (!skip_proto && wc->masks.nw_proto) {
+        if (f->dl_type == htons(ETH_TYPE_ARP)) {
+            ds_put_format(s, "arp_op=%"PRIu8",", f->nw_proto);
+        } else {
+            ds_put_format(s, "nw_proto=%"PRIu8",", f->nw_proto);
+        }
+    }
+    if (f->dl_type == htons(ETH_TYPE_ARP)) {
+        format_eth_masked(s, "arp_sha", f->arp_sha, wc->masks.arp_sha);
+        format_eth_masked(s, "arp_tha", f->arp_tha, wc->masks.arp_tha);
+    }
+    if (wc->masks.nw_tos & IP_DSCP_MASK) {
+        ds_put_format(s, "nw_tos=%"PRIu8",", f->nw_tos & IP_DSCP_MASK);
+    }
+    if (wc->masks.nw_tos & IP_ECN_MASK) {
+        ds_put_format(s, "nw_ecn=%"PRIu8",", f->nw_tos & IP_ECN_MASK);
+    }
+    if (wc->masks.nw_ttl) {
+        ds_put_format(s, "nw_ttl=%"PRIu8",", f->nw_ttl);
+    }
+    switch (wc->masks.nw_frag) {
+    case FLOW_NW_FRAG_ANY | FLOW_NW_FRAG_LATER:
+        ds_put_format(s, "nw_frag=%s,",
+                      f->nw_frag & FLOW_NW_FRAG_ANY
+                      ? (f->nw_frag & FLOW_NW_FRAG_LATER ? "later" : "first")
+                      : (f->nw_frag & FLOW_NW_FRAG_LATER ? "<error>" : "no"));
+        break;
+
+    case FLOW_NW_FRAG_ANY:
+        ds_put_format(s, "nw_frag=%s,",
+                      f->nw_frag & FLOW_NW_FRAG_ANY ? "yes" : "no");
+        break;
+
+    case FLOW_NW_FRAG_LATER:
+        ds_put_format(s, "nw_frag=%s,",
+                      f->nw_frag & FLOW_NW_FRAG_LATER ? "later" : "not_later");
+        break;
+    }
+    if (f->nw_proto == IPPROTO_ICMP) {
+        format_be16_masked(s, "icmp_type", f->tp_src, wc->masks.tp_src);
+        format_be16_masked(s, "icmp_code", f->tp_dst, wc->masks.tp_dst);
+    } else if (f->nw_proto == IPPROTO_ICMPV6) {
+        format_be16_masked(s, "icmp_type", f->tp_src, wc->masks.tp_src);
+        format_be16_masked(s, "icmp_code", f->tp_dst, wc->masks.tp_dst);
+        format_ipv6_netmask(s, "nd_target", &f->nd_target,
+                            &wc->masks.nd_target);
+        format_eth_masked(s, "nd_sll", f->arp_sha, wc->masks.arp_sha);
+        format_eth_masked(s, "nd_tll", f->arp_tha, wc->masks.arp_tha);
+    } else {
+        format_be16_masked(s, "tp_src", f->tp_src, wc->masks.tp_src);
+        format_be16_masked(s, "tp_dst", f->tp_dst, wc->masks.tp_dst);
+    }
+
+    if (s->length > start_len && ds_last(s) == ',') {
+        s->length--;
+    }
+}
+
+/* Converts 'match' to a string and returns the string.  If 'priority' is
+ * different from OFP_DEFAULT_PRIORITY, includes it in the string.  The caller
+ * must free the string (with free()). */
+char *
+match_to_string(const struct match *match, unsigned int priority)
+{
+    struct ds s = DS_EMPTY_INITIALIZER;
+    match_format(match, &s, priority);
+    return ds_steal_cstr(&s);
+}
+
+void
+match_print(const struct match *match)
+{
+    char *s = match_to_string(match, OFP_DEFAULT_PRIORITY);
+    puts(s);
+    free(s);
+}
diff --git a/lib/match.h b/lib/match.h
new file mode 100644
index 0000000..8761dc9
--- /dev/null
+++ b/lib/match.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MATCH_H
+#define MATCH_H 1
+
+#include "flow.h"
+
+/* A flow classification match.
+ *
+ * Use one of the match_*() functions to initialize a "struct match".
+ *
+ * The match_*() functions below maintain the following important invariant.
+ * If a bit or a field is wildcarded in 'wc', then the corresponding bit or
+ * field in 'flow' is set to all-0-bits.  (The match_zero_wildcarded_fields()
+ * function can be used to restore this invariant after adding wildcards.) */
+struct match {
+    struct flow flow;
+    struct flow_wildcards wc;
+};
+
+void match_init(struct match *,
+                const struct flow *, const struct flow_wildcards *);
+void match_init_catchall(struct match *);
+void match_init_exact(struct match *, const struct flow *);
+
+void match_zero_wildcarded_fields(struct match *);
+
+void match_set_reg(struct match *, unsigned int reg_idx, uint32_t value);
+void match_set_reg_masked(struct match *, unsigned int reg_idx,
+                          uint32_t value, uint32_t mask);
+void match_set_metadata(struct match *, ovs_be64 metadata);
+void match_set_metadata_masked(struct match *,
+                               ovs_be64 metadata, ovs_be64 mask);
+void match_set_tun_id(struct match *, ovs_be64 tun_id);
+void match_set_tun_id_masked(struct match *, ovs_be64 tun_id, ovs_be64 mask);
+void match_set_in_port(struct match *, uint16_t ofp_port);
+void match_set_dl_type(struct match *, ovs_be16);
+void match_set_dl_src(struct match *, const uint8_t[6]);
+void match_set_dl_src_masked(struct match *, const uint8_t dl_src[6],
+                             const uint8_t mask[6]);
+void match_set_dl_dst(struct match *, const uint8_t[6]);
+void match_set_dl_dst_masked(struct match *, const uint8_t dl_dst[6],
+                             const uint8_t mask[6]);
+void match_set_dl_tci(struct match *, ovs_be16 tci);
+void match_set_dl_tci_masked(struct match *, ovs_be16 tci, ovs_be16 mask);
+void match_set_any_vid(struct match *);
+void match_set_dl_vlan(struct match *, ovs_be16);
+void match_set_any_pcp(struct match *);
+void match_set_dl_vlan_pcp(struct match *, uint8_t);
+void match_set_tp_src(struct match *, ovs_be16);
+void match_set_tp_src_masked(struct match *, ovs_be16 port, ovs_be16 mask);
+void match_set_tp_dst(struct match *, ovs_be16);
+void match_set_tp_dst_masked(struct match *, ovs_be16 port, ovs_be16 mask);
+void match_set_nw_proto(struct match *, uint8_t);
+void match_set_nw_src(struct match *, ovs_be32);
+void match_set_nw_src_masked(struct match *, ovs_be32 ip, ovs_be32 mask);
+void match_set_nw_dst(struct match *, ovs_be32);
+void match_set_nw_dst_masked(struct match *, ovs_be32 ip, ovs_be32 mask);
+void match_set_nw_dscp(struct match *, uint8_t);
+void match_set_nw_ecn(struct match *, uint8_t);
+void match_set_nw_ttl(struct match *, uint8_t);
+void match_set_nw_frag(struct match *, uint8_t nw_frag);
+void match_set_nw_frag_masked(struct match *, uint8_t nw_frag, uint8_t mask);
+void match_set_icmp_type(struct match *, uint8_t);
+void match_set_icmp_code(struct match *, uint8_t);
+void match_set_arp_sha(struct match *, const uint8_t[6]);
+void match_set_arp_sha_masked(struct match *,
+                              const uint8_t arp_sha[6],
+                              const uint8_t mask[6]);
+void match_set_arp_tha(struct match *, const uint8_t[6]);
+void match_set_arp_tha_masked(struct match *,
+                              const uint8_t arp_tha[6],
+                              const uint8_t mask[6]);
+void match_set_ipv6_src(struct match *, const struct in6_addr *);
+void match_set_ipv6_src_masked(struct match *, const struct in6_addr *,
+                               const struct in6_addr *);
+void match_set_ipv6_dst(struct match *, const struct in6_addr *);
+void match_set_ipv6_dst_masked(struct match *, const struct in6_addr *,
+                               const struct in6_addr *);
+void match_set_ipv6_label(struct match *, ovs_be32);
+void match_set_ipv6_label_masked(struct match *, ovs_be32, ovs_be32);
+void match_set_nd_target(struct match *, const struct in6_addr *);
+void match_set_nd_target_masked(struct match *, const struct in6_addr *,
+                                const struct in6_addr *);
+
+bool match_equal(const struct match *, const struct match *);
+uint32_t match_hash(const struct match *, uint32_t basis);
+
+void match_format(const struct match *, struct ds *, unsigned int priority);
+char *match_to_string(const struct match *, unsigned int priority);
+void match_print(const struct match *);
+
+#endif /* match.h */
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index b7aae9a..7f8cd88 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -1052,137 +1052,137 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
     }
 }
 
-/* Makes 'rule' match field 'mf' exactly, with the value matched taken from
- * 'value'.  The caller is responsible for ensuring that 'rule' meets 'mf''s
+/* Makes 'match' match field 'mf' exactly, with the value matched taken from
+ * 'value'.  The caller is responsible for ensuring that 'match' meets 'mf''s
  * prerequisites. */
 void
 mf_set_value(const struct mf_field *mf,
-             const union mf_value *value, struct cls_rule *rule)
+             const union mf_value *value, struct match *match)
 {
     switch (mf->id) {
     case MFF_TUN_ID:
-        cls_rule_set_tun_id(rule, value->be64);
+        match_set_tun_id(match, value->be64);
         break;
     case MFF_METADATA:
-        cls_rule_set_metadata(rule, value->be64);
+        match_set_metadata(match, value->be64);
         break;
 
     case MFF_IN_PORT:
-        cls_rule_set_in_port(rule, ntohs(value->be16));
+        match_set_in_port(match, ntohs(value->be16));
         break;
 
     CASE_MFF_REGS:
-        cls_rule_set_reg(rule, mf->id - MFF_REG0, ntohl(value->be32));
+        match_set_reg(match, mf->id - MFF_REG0, ntohl(value->be32));
         break;
 
     case MFF_ETH_SRC:
-        cls_rule_set_dl_src(rule, value->mac);
+        match_set_dl_src(match, value->mac);
         break;
 
     case MFF_ETH_DST:
-        cls_rule_set_dl_dst(rule, value->mac);
+        match_set_dl_dst(match, value->mac);
         break;
 
     case MFF_ETH_TYPE:
-        cls_rule_set_dl_type(rule, value->be16);
+        match_set_dl_type(match, value->be16);
         break;
 
     case MFF_VLAN_TCI:
-        cls_rule_set_dl_tci(rule, value->be16);
+        match_set_dl_tci(match, value->be16);
         break;
 
     case MFF_VLAN_VID:
-        cls_rule_set_dl_vlan(rule, value->be16);
+        match_set_dl_vlan(match, value->be16);
         break;
 
     case MFF_VLAN_PCP:
-        cls_rule_set_dl_vlan_pcp(rule, value->u8);
+        match_set_dl_vlan_pcp(match, value->u8);
         break;
 
     case MFF_IPV4_SRC:
-        cls_rule_set_nw_src(rule, value->be32);
+        match_set_nw_src(match, value->be32);
         break;
 
     case MFF_IPV4_DST:
-        cls_rule_set_nw_dst(rule, value->be32);
+        match_set_nw_dst(match, value->be32);
         break;
 
     case MFF_IPV6_SRC:
-        cls_rule_set_ipv6_src(rule, &value->ipv6);
+        match_set_ipv6_src(match, &value->ipv6);
         break;
 
     case MFF_IPV6_DST:
-        cls_rule_set_ipv6_dst(rule, &value->ipv6);
+        match_set_ipv6_dst(match, &value->ipv6);
         break;
 
     case MFF_IPV6_LABEL:
-        cls_rule_set_ipv6_label(rule, value->be32);
+        match_set_ipv6_label(match, value->be32);
         break;
 
     case MFF_IP_PROTO:
-        cls_rule_set_nw_proto(rule, value->u8);
+        match_set_nw_proto(match, value->u8);
         break;
 
     case MFF_IP_DSCP:
-        cls_rule_set_nw_dscp(rule, value->u8);
+        match_set_nw_dscp(match, value->u8);
         break;
 
     case MFF_IP_ECN:
-        cls_rule_set_nw_ecn(rule, value->u8);
+        match_set_nw_ecn(match, value->u8);
         break;
 
     case MFF_IP_TTL:
-        cls_rule_set_nw_ttl(rule, value->u8);
+        match_set_nw_ttl(match, value->u8);
         break;
 
     case MFF_IP_FRAG:
-        cls_rule_set_nw_frag(rule, value->u8);
+        match_set_nw_frag(match, value->u8);
         break;
 
     case MFF_ARP_OP:
-        cls_rule_set_nw_proto(rule, ntohs(value->be16));
+        match_set_nw_proto(match, ntohs(value->be16));
         break;
 
     case MFF_ARP_SPA:
-        cls_rule_set_nw_src(rule, value->be32);
+        match_set_nw_src(match, value->be32);
         break;
 
     case MFF_ARP_TPA:
-        cls_rule_set_nw_dst(rule, value->be32);
+        match_set_nw_dst(match, value->be32);
         break;
 
     case MFF_ARP_SHA:
     case MFF_ND_SLL:
-        cls_rule_set_arp_sha(rule, value->mac);
+        match_set_arp_sha(match, value->mac);
         break;
 
     case MFF_ARP_THA:
     case MFF_ND_TLL:
-        cls_rule_set_arp_tha(rule, value->mac);
+        match_set_arp_tha(match, value->mac);
         break;
 
     case MFF_TCP_SRC:
     case MFF_UDP_SRC:
-        cls_rule_set_tp_src(rule, value->be16);
+        match_set_tp_src(match, value->be16);
         break;
 
     case MFF_TCP_DST:
     case MFF_UDP_DST:
-        cls_rule_set_tp_dst(rule, value->be16);
+        match_set_tp_dst(match, value->be16);
         break;
 
     case MFF_ICMPV4_TYPE:
     case MFF_ICMPV6_TYPE:
-        cls_rule_set_icmp_type(rule, value->u8);
+        match_set_icmp_type(match, value->u8);
         break;
 
     case MFF_ICMPV4_CODE:
     case MFF_ICMPV6_CODE:
-        cls_rule_set_icmp_code(rule, value->u8);
+        match_set_icmp_code(match, value->u8);
         break;
 
     case MFF_ND_TARGET:
-        cls_rule_set_nd_target(rule, &value->ipv6);
+        match_set_nd_target(match, &value->ipv6);
         break;
 
     case MFF_N_IDS:
@@ -1191,8 +1191,8 @@ mf_set_value(const struct mf_field *mf,
     }
 }
 
-/* Makes 'rule' match field 'mf' exactly, with the value matched taken from
- * 'value'.  The caller is responsible for ensuring that 'rule' meets 'mf''s
+/* Makes 'match' match field 'mf' exactly, with the value matched taken from
+ * 'value'.  The caller is responsible for ensuring that 'match' meets 'mf''s
  * prerequisites. */
 void
 mf_set_flow_value(const struct mf_field *mf,
@@ -1345,142 +1345,143 @@ mf_is_zero(const struct mf_field *mf, const struct flow *flow)
     return is_all_zeros((const uint8_t *) &value, mf->n_bytes);
 }
 
-/* Makes 'rule' wildcard field 'mf'.
+/* Makes 'match' wildcard field 'mf'.
  *
- * The caller is responsible for ensuring that 'rule' meets 'mf''s
+ * The caller is responsible for ensuring that 'match' meets 'mf''s
  * prerequisites. */
 void
-mf_set_wild(const struct mf_field *mf, struct cls_rule *rule)
+mf_set_wild(const struct mf_field *mf, struct match *match)
 {
     switch (mf->id) {
     case MFF_TUN_ID:
-        cls_rule_set_tun_id_masked(rule, htonll(0), htonll(0));
+        match_set_tun_id_masked(match, htonll(0), htonll(0));
         break;
     case MFF_METADATA:
-        cls_rule_set_metadata_masked(rule, htonll(0), htonll(0));
+        match_set_metadata_masked(match, htonll(0), htonll(0));
 
     case MFF_IN_PORT:
-        rule->flow.in_port = 0;
-        rule->wc.masks.in_port = 0;
+        match->flow.in_port = 0;
+        match->wc.masks.in_port = 0;
         break;
 
     CASE_MFF_REGS:
-        cls_rule_set_reg_masked(rule, mf->id - MFF_REG0, 0, 0);
+        match_set_reg_masked(match, mf->id - MFF_REG0, 0, 0);
         break;
 
     case MFF_ETH_SRC:
-        memset(rule->flow.dl_src, 0, ETH_ADDR_LEN);
-        memset(rule->wc.masks.dl_src, 0, ETH_ADDR_LEN);
+        memset(match->flow.dl_src, 0, ETH_ADDR_LEN);
+        memset(match->wc.masks.dl_src, 0, ETH_ADDR_LEN);
         break;
 
     case MFF_ETH_DST:
-        memset(rule->flow.dl_dst, 0, ETH_ADDR_LEN);
-        memset(rule->wc.masks.dl_dst, 0, ETH_ADDR_LEN);
+        memset(match->flow.dl_dst, 0, ETH_ADDR_LEN);
+        memset(match->wc.masks.dl_dst, 0, ETH_ADDR_LEN);
         break;
 
     case MFF_ETH_TYPE:
-        rule->flow.dl_type = htons(0);
-        rule->wc.masks.dl_type = htons(0);
+        match->flow.dl_type = htons(0);
+        match->wc.masks.dl_type = htons(0);
         break;
 
     case MFF_VLAN_TCI:
-        cls_rule_set_dl_tci_masked(rule, htons(0), htons(0));
+        match_set_dl_tci_masked(match, htons(0), htons(0));
         break;
 
     case MFF_VLAN_VID:
-        cls_rule_set_any_vid(rule);
+        match_set_any_vid(match);
         break;
 
     case MFF_VLAN_PCP:
-        cls_rule_set_any_pcp(rule);
+        match_set_any_pcp(match);
         break;
 
     case MFF_IPV4_SRC:
     case MFF_ARP_SPA:
-        cls_rule_set_nw_src_masked(rule, htonl(0), htonl(0));
+        match_set_nw_src_masked(match, htonl(0), htonl(0));
         break;
 
     case MFF_IPV4_DST:
     case MFF_ARP_TPA:
-        cls_rule_set_nw_dst_masked(rule, htonl(0), htonl(0));
+        match_set_nw_dst_masked(match, htonl(0), htonl(0));
         break;
 
     case MFF_IPV6_SRC:
-        memset(&rule->wc.masks.ipv6_src, 0, sizeof rule->wc.masks.ipv6_src);
-        memset(&rule->flow.ipv6_src, 0, sizeof rule->flow.ipv6_src);
+        memset(&match->wc.masks.ipv6_src, 0, sizeof match->wc.masks.ipv6_src);
+        memset(&match->flow.ipv6_src, 0, sizeof match->flow.ipv6_src);
         break;
 
     case MFF_IPV6_DST:
-        memset(&rule->wc.masks.ipv6_dst, 0, sizeof rule->wc.masks.ipv6_dst);
-        memset(&rule->flow.ipv6_dst, 0, sizeof rule->flow.ipv6_dst);
+        memset(&match->wc.masks.ipv6_dst, 0, sizeof match->wc.masks.ipv6_dst);
+        memset(&match->flow.ipv6_dst, 0, sizeof match->flow.ipv6_dst);
         break;
 
     case MFF_IPV6_LABEL:
-        rule->wc.masks.ipv6_label = htonl(0);
-        rule->flow.ipv6_label = htonl(0);
+        match->wc.masks.ipv6_label = htonl(0);
+        match->flow.ipv6_label = htonl(0);
         break;
 
     case MFF_IP_PROTO:
-        rule->wc.masks.nw_proto = 0;
-        rule->flow.nw_proto = 0;
+        match->wc.masks.nw_proto = 0;
+        match->flow.nw_proto = 0;
         break;
 
     case MFF_IP_DSCP:
-        rule->wc.masks.nw_tos &= ~IP_DSCP_MASK;
-        rule->flow.nw_tos &= ~IP_DSCP_MASK;
+        match->wc.masks.nw_tos &= ~IP_DSCP_MASK;
+        match->flow.nw_tos &= ~IP_DSCP_MASK;
         break;
 
     case MFF_IP_ECN:
-        rule->wc.masks.nw_tos &= ~IP_ECN_MASK;
-        rule->flow.nw_tos &= ~IP_ECN_MASK;
+        match->wc.masks.nw_tos &= ~IP_ECN_MASK;
+        match->flow.nw_tos &= ~IP_ECN_MASK;
         break;
 
     case MFF_IP_TTL:
-        rule->wc.masks.nw_ttl = 0;
-        rule->flow.nw_ttl = 0;
+        match->wc.masks.nw_ttl = 0;
+        match->flow.nw_ttl = 0;
         break;
 
     case MFF_IP_FRAG:
-        rule->wc.masks.nw_frag |= FLOW_NW_FRAG_MASK;
-        rule->flow.nw_frag &= ~FLOW_NW_FRAG_MASK;
+        match->wc.masks.nw_frag |= FLOW_NW_FRAG_MASK;
+        match->flow.nw_frag &= ~FLOW_NW_FRAG_MASK;
         break;
 
     case MFF_ARP_OP:
-        rule->wc.masks.nw_proto = 0;
-        rule->flow.nw_proto = 0;
+        match->wc.masks.nw_proto = 0;
+        match->flow.nw_proto = 0;
         break;
 
     case MFF_ARP_SHA:
     case MFF_ND_SLL:
-        memset(rule->flow.arp_sha, 0, ETH_ADDR_LEN);
-        memset(rule->wc.masks.arp_sha, 0, ETH_ADDR_LEN);
+        memset(match->flow.arp_sha, 0, ETH_ADDR_LEN);
+        memset(match->wc.masks.arp_sha, 0, ETH_ADDR_LEN);
         break;
 
     case MFF_ARP_THA:
     case MFF_ND_TLL:
-        memset(rule->flow.arp_tha, 0, ETH_ADDR_LEN);
-        memset(rule->wc.masks.arp_tha, 0, ETH_ADDR_LEN);
+        memset(match->flow.arp_tha, 0, ETH_ADDR_LEN);
+        memset(match->wc.masks.arp_tha, 0, ETH_ADDR_LEN);
         break;
 
     case MFF_TCP_SRC:
     case MFF_UDP_SRC:
     case MFF_ICMPV4_TYPE:
     case MFF_ICMPV6_TYPE:
-        rule->wc.masks.tp_src = htons(0);
-        rule->flow.tp_src = htons(0);
+        match->wc.masks.tp_src = htons(0);
+        match->flow.tp_src = htons(0);
         break;
 
     case MFF_TCP_DST:
     case MFF_UDP_DST:
     case MFF_ICMPV4_CODE:
     case MFF_ICMPV6_CODE:
-        rule->wc.masks.tp_dst = htons(0);
-        rule->flow.tp_dst = htons(0);
+        match->wc.masks.tp_dst = htons(0);
+        match->flow.tp_dst = htons(0);
         break;
 
     case MFF_ND_TARGET:
-        memset(&rule->wc.masks.nd_target, 0, sizeof rule->wc.masks.nd_target);
-        memset(&rule->flow.nd_target, 0, sizeof rule->flow.nd_target);
+        memset(&match->wc.masks.nd_target, 0,
+               sizeof match->wc.masks.nd_target);
+        memset(&match->flow.nd_target, 0, sizeof match->flow.nd_target);
         break;
 
     case MFF_N_IDS:
@@ -1489,27 +1490,27 @@ mf_set_wild(const struct mf_field *mf, struct cls_rule *rule)
     }
 }
 
-/* Makes 'rule' match field 'mf' with the specified 'value' and 'mask'.
+/* Makes 'match' match field 'mf' with the specified 'value' and 'mask'.
  * 'value' specifies a value to match and 'mask' specifies a wildcard pattern,
  * with a 1-bit indicating that the corresponding value bit must match and a
  * 0-bit indicating a don't-care.
  *
  * If 'mask' is NULL or points to all-1-bits, then this call is equivalent to
- * mf_set_value(mf, value, rule).  If 'mask' points to all-0-bits, then this
- * call is equivalent to mf_set_wild(mf, rule).
+ * mf_set_value(mf, value, match).  If 'mask' points to all-0-bits, then this
+ * call is equivalent to mf_set_wild(mf, match).
  *
  * 'mask' must be a valid mask for 'mf' (see mf_is_mask_valid()).  The caller
- * is responsible for ensuring that 'rule' meets 'mf''s prerequisites. */
+ * is responsible for ensuring that 'match' meets 'mf''s prerequisites. */
 void
 mf_set(const struct mf_field *mf,
        const union mf_value *value, const union mf_value *mask,
-       struct cls_rule *rule)
+       struct match *match)
 {
     if (!mask || is_all_ones((const uint8_t *) mask, mf->n_bytes)) {
-        mf_set_value(mf, value, rule);
+        mf_set_value(mf, value, match);
         return;
     } else if (is_all_zeros((const uint8_t *) mask, mf->n_bytes)) {
-        mf_set_wild(mf, rule);
+        mf_set_wild(mf, match);
         return;
     }
 
@@ -1530,87 +1531,87 @@ mf_set(const struct mf_field *mf,
         NOT_REACHED();
 
     case MFF_TUN_ID:
-        cls_rule_set_tun_id_masked(rule, value->be64, mask->be64);
+        match_set_tun_id_masked(match, value->be64, mask->be64);
         break;
     case MFF_METADATA:
-        cls_rule_set_metadata_masked(rule, value->be64, mask->be64);
+        match_set_metadata_masked(match, value->be64, mask->be64);
         break;
 
     CASE_MFF_REGS:
-        cls_rule_set_reg_masked(rule, mf->id - MFF_REG0,
-                                ntohl(value->be32), ntohl(mask->be32));
+        match_set_reg_masked(match, mf->id - MFF_REG0,
+                             ntohl(value->be32), ntohl(mask->be32));
         break;
 
     case MFF_ETH_DST:
-        cls_rule_set_dl_dst_masked(rule, value->mac, mask->mac);
+        match_set_dl_dst_masked(match, value->mac, mask->mac);
         break;
 
     case MFF_ETH_SRC:
-        cls_rule_set_dl_src_masked(rule, value->mac, mask->mac);
+        match_set_dl_src_masked(match, value->mac, mask->mac);
         break;
 
     case MFF_ARP_SHA:
     case MFF_ND_SLL:
-        cls_rule_set_arp_sha_masked(rule, value->mac, mask->mac);
+        match_set_arp_sha_masked(match, value->mac, mask->mac);
         break;
 
     case MFF_ARP_THA:
     case MFF_ND_TLL:
-        cls_rule_set_arp_tha_masked(rule, value->mac, mask->mac);
+        match_set_arp_tha_masked(match, value->mac, mask->mac);
         break;
 
     case MFF_VLAN_TCI:
-        cls_rule_set_dl_tci_masked(rule, value->be16, mask->be16);
+        match_set_dl_tci_masked(match, value->be16, mask->be16);
         break;
 
     case MFF_IPV4_SRC:
-        cls_rule_set_nw_src_masked(rule, value->be32, mask->be32);
+        match_set_nw_src_masked(match, value->be32, mask->be32);
         break;
 
     case MFF_IPV4_DST:
-        cls_rule_set_nw_dst_masked(rule, value->be32, mask->be32);
+        match_set_nw_dst_masked(match, value->be32, mask->be32);
         break;
 
     case MFF_IPV6_SRC:
-        cls_rule_set_ipv6_src_masked(rule, &value->ipv6, &mask->ipv6);
+        match_set_ipv6_src_masked(match, &value->ipv6, &mask->ipv6);
         break;
 
     case MFF_IPV6_DST:
-        cls_rule_set_ipv6_dst_masked(rule, &value->ipv6, &mask->ipv6);
+        match_set_ipv6_dst_masked(match, &value->ipv6, &mask->ipv6);
         break;
 
     case MFF_IPV6_LABEL:
         if ((mask->be32 & htonl(IPV6_LABEL_MASK)) == htonl(IPV6_LABEL_MASK)) {
-            mf_set_value(mf, value, rule);
+            mf_set_value(mf, value, match);
         } else {
-            cls_rule_set_ipv6_label_masked(rule, value->be32, mask->be32);
+            match_set_ipv6_label_masked(match, value->be32, mask->be32);
         }
         break;
 
     case MFF_ND_TARGET:
-        cls_rule_set_nd_target_masked(rule, &value->ipv6, &mask->ipv6);
+        match_set_nd_target_masked(match, &value->ipv6, &mask->ipv6);
         break;
 
     case MFF_IP_FRAG:
-        cls_rule_set_nw_frag_masked(rule, value->u8, mask->u8);
+        match_set_nw_frag_masked(match, value->u8, mask->u8);
         break;
 
     case MFF_ARP_SPA:
-        cls_rule_set_nw_src_masked(rule, value->be32, mask->be32);
+        match_set_nw_src_masked(match, value->be32, mask->be32);
         break;
 
     case MFF_ARP_TPA:
-        cls_rule_set_nw_dst_masked(rule, value->be32, mask->be32);
+        match_set_nw_dst_masked(match, value->be32, mask->be32);
         break;
 
     case MFF_TCP_SRC:
     case MFF_UDP_SRC:
-        cls_rule_set_tp_src_masked(rule, value->be16, mask->be16);
+        match_set_tp_src_masked(match, value->be16, mask->be16);
         break;
 
     case MFF_TCP_DST:
     case MFF_UDP_DST:
-        cls_rule_set_tp_dst_masked(rule, value->be16, mask->be16);
+        match_set_tp_dst_masked(match, value->be16, mask->be16);
         break;
 
     case MFF_N_IDS:
@@ -1668,14 +1669,14 @@ mf_check_dst(const struct mf_subfield *sf, const struct flow *flow)
     return error;
 }
 
-/* Copies the value and wildcard bit pattern for 'mf' from 'rule' into the
+/* Copies the value and wildcard bit pattern for 'mf' from 'match' into the
  * 'value' and 'mask', respectively. */
 void
-mf_get(const struct mf_field *mf, const struct cls_rule *rule,
+mf_get(const struct mf_field *mf, const struct match *match,
        union mf_value *value, union mf_value *mask)
 {
-    mf_get_value(mf, &rule->flow, value);
-    mf_get_mask(mf, &rule->wc, mask);
+    mf_get_value(mf, &match->flow, value);
+    mf_get_mask(mf, &match->wc, mask);
 }
 
 /* Assigns a random value for field 'mf' to 'value'. */
@@ -2099,20 +2100,20 @@ mf_format(const struct mf_field *mf,
     }
 }
 
-/* Makes subfield 'sf' within 'rule' exactly match the 'sf->n_bits'
+/* Makes subfield 'sf' within 'match' exactly match the 'sf->n_bits'
  * least-significant bits in 'x'.
  */
 void
 mf_write_subfield(const struct mf_subfield *sf, const union mf_subvalue *x,
-                  struct cls_rule *rule)
+                  struct match *match)
 {
     const struct mf_field *field = sf->field;
     union mf_value value, mask;
 
-    mf_get(field, rule, &value, &mask);
+    mf_get(field, match, &value, &mask);
     bitwise_copy(x, sizeof *x, 0, &value, field->n_bytes, sf->ofs, sf->n_bits);
     bitwise_one (                 &mask,  field->n_bytes, sf->ofs, sf->n_bits);
-    mf_set(field, &value, &mask, rule);
+    mf_set(field, &value, &mask, match);
 }
 
 /* Initializes 'x' to the value of 'sf' within 'flow'.  'sf' must be valid for
diff --git a/lib/meta-flow.h b/lib/meta-flow.h
index 977c08d..eefcac9 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -24,8 +24,8 @@
 #include "ofp-errors.h"
 #include "packets.h"
 
-struct cls_rule;
 struct ds;
+struct match;
 
 /* The comment on each of these indicates the member in "union mf_value" used
  * to represent its value. */
@@ -291,7 +291,7 @@ void mf_get_mask(const struct mf_field *, const struct flow_wildcards *,
 
 /* Prerequisites. */
 bool mf_are_prereqs_ok(const struct mf_field *, const struct flow *);
-void mf_force_prereqs(const struct mf_field *, struct cls_rule *);
+void mf_force_prereqs(const struct mf_field *, struct match *);
 
 /* Field values. */
 bool mf_is_value_valid(const struct mf_field *, const union mf_value *value);
@@ -299,24 +299,24 @@ bool mf_is_value_valid(const struct mf_field *, const union mf_value *value);
 void mf_get_value(const struct mf_field *, const struct flow *,
                   union mf_value *value);
 void mf_set_value(const struct mf_field *, const union mf_value *value,
-                  struct cls_rule *);
+                  struct match *);
 void mf_set_flow_value(const struct mf_field *, const union mf_value *value,
                        struct flow *);
 bool mf_is_zero(const struct mf_field *, const struct flow *);
 
-void mf_get(const struct mf_field *, const struct cls_rule *,
+void mf_get(const struct mf_field *, const struct match *,
             union mf_value *value, union mf_value *mask);
 void mf_set(const struct mf_field *,
             const union mf_value *value, const union mf_value *mask,
-            struct cls_rule *);
+            struct match *);
 
-void mf_set_wild(const struct mf_field *, struct cls_rule *);
+void mf_set_wild(const struct mf_field *, struct match *);
 
 void mf_random_value(const struct mf_field *, union mf_value *value);
 
 /* Subfields. */
 void mf_write_subfield(const struct mf_subfield *, const union mf_subvalue *,
-                       struct cls_rule *);
+                       struct match *);
 
 void mf_read_subfield(const struct mf_subfield *, const struct flow *,
                       union mf_subvalue *);
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 22f344f..22f667f 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -92,15 +92,14 @@ nx_entry_ok(const void *p, unsigned int match_len)
 
 static enum ofperr
 nx_pull_match__(struct ofpbuf *b, unsigned int match_len, bool strict,
-                uint16_t priority, struct cls_rule *rule,
-                ovs_be64 *cookie, ovs_be64 *cookie_mask)
+                struct match *match, ovs_be64 *cookie, ovs_be64 *cookie_mask)
 {
     uint32_t header;
     uint8_t *p;
 
     assert((cookie != NULL) == (cookie_mask != NULL));
 
-    cls_rule_init_catchall(rule, priority);
+    match_init_catchall(match);
     if (cookie) {
         *cookie = *cookie_mask = htonll(0);
     }
@@ -129,9 +128,9 @@ nx_pull_match__(struct ofpbuf *b, unsigned int match_len, bool strict,
             } else {
                 continue;
             }
-        } else if (!mf_are_prereqs_ok(mf, &rule->flow)) {
+        } else if (!mf_are_prereqs_ok(mf, &match->flow)) {
             error = OFPERR_OFPBMC_BAD_PREREQ;
-        } else if (!mf_is_all_wild(mf, &rule->wc)) {
+        } else if (!mf_is_all_wild(mf, &match->wc)) {
             error = OFPERR_OFPBMC_DUP_FIELD;
         } else if (header != OXM_OF_IN_PORT) {
             unsigned int width = mf->n_bytes;
@@ -142,7 +141,7 @@ nx_pull_match__(struct ofpbuf *b, unsigned int match_len, bool strict,
                 error = OFPERR_OFPBMC_BAD_VALUE;
             } else if (!NXM_HASMASK(header)) {
                 error = 0;
-                mf_set_value(mf, &value, rule);
+                mf_set_value(mf, &value, match);
             } else {
                 union mf_value mask;
 
@@ -151,7 +150,7 @@ nx_pull_match__(struct ofpbuf *b, unsigned int match_len, bool strict,
                     error = OFPERR_OFPBMC_BAD_MASK;
                 } else {
                     error = 0;
-                    mf_set(mf, &value, &mask, rule);
+                    mf_set(mf, &value, &mask, match);
                 }
             }
         } else {
@@ -163,7 +162,7 @@ nx_pull_match__(struct ofpbuf *b, unsigned int match_len, bool strict,
             memcpy(&port_of11, p + 4, sizeof port_of11);
             error = ofputil_port_from_ofp11(port_of11, &port);
             if (!error) {
-                cls_rule_set_in_port(rule, port);
+                match_set_in_port(match, port);
             }
         }
 
@@ -199,32 +198,28 @@ nx_pull_match__(struct ofpbuf *b, unsigned int match_len, bool strict,
 }
 
 /* Parses the nx_match formatted match description in 'b' with length
- * 'match_len'.  The results are stored in 'rule', which is initialized with
- * 'priority'.  If 'cookie' and 'cookie_mask' contain valid pointers, then the
- * cookie and mask will be stored in them if a "NXM_NX_COOKIE*" match is
- * defined.  Otherwise, 0 is stored in both.
+ * 'match_len'.  Stores the results in 'match'.  If 'cookie' and 'cookie_mask'
+ * are valid pointers, then stores the cookie and mask in them if 'b' contains
+ * a "NXM_NX_COOKIE*" match.  Otherwise, stores 0 in both.
  *
- * Fails with an error when encountering unknown NXM headers.
+ * Fails with an error upon encountering an unknown NXM header.
  *
  * Returns 0 if successful, otherwise an OpenFlow error code. */
 enum ofperr
-nx_pull_match(struct ofpbuf *b, unsigned int match_len,
-              uint16_t priority, struct cls_rule *rule,
+nx_pull_match(struct ofpbuf *b, unsigned int match_len, struct match *match,
               ovs_be64 *cookie, ovs_be64 *cookie_mask)
 {
-    return nx_pull_match__(b, match_len, true, priority, rule, cookie,
-                           cookie_mask);
+    return nx_pull_match__(b, match_len, true, match, cookie, cookie_mask);
 }
 
-/* Behaves the same as nx_pull_match() with one exception.  Skips over unknown
- * NXM headers instead of failing with an error when they are encountered. */
+/* Behaves the same as nx_pull_match(), but skips over unknown NXM headers,
+ * instead of failing with an error. */
 enum ofperr
 nx_pull_match_loose(struct ofpbuf *b, unsigned int match_len,
-                    uint16_t priority, struct cls_rule *rule,
+                    struct match *match,
                     ovs_be64 *cookie, ovs_be64 *cookie_mask)
 {
-    return nx_pull_match__(b, match_len, false, priority, rule, cookie,
-                           cookie_mask);
+    return nx_pull_match__(b, match_len, false, match, cookie, cookie_mask);
 }
 
 /* nx_put_match() and helpers.
@@ -403,10 +398,10 @@ nxm_put_ipv6(struct ofpbuf *b, uint32_t header,
 }
 
 static void
-nxm_put_frag(struct ofpbuf *b, const struct cls_rule *cr)
+nxm_put_frag(struct ofpbuf *b, const struct match *match)
 {
-    uint8_t nw_frag = cr->flow.nw_frag;
-    uint8_t nw_frag_mask = cr->wc.masks.nw_frag;
+    uint8_t nw_frag = match->flow.nw_frag;
+    uint8_t nw_frag_mask = match->wc.masks.nw_frag;
 
     switch (nw_frag_mask) {
     case 0:
@@ -424,69 +419,68 @@ nxm_put_frag(struct ofpbuf *b, const struct cls_rule *cr)
 }
 
 static void
-nxm_put_ip(struct ofpbuf *b, const struct cls_rule *cr,
+nxm_put_ip(struct ofpbuf *b, const struct match *match,
            uint8_t icmp_proto, uint32_t icmp_type, uint32_t icmp_code,
            bool oxm)
 {
-    const struct flow *flow = &cr->flow;
+    const struct flow *flow = &match->flow;
 
-    nxm_put_frag(b, cr);
+    nxm_put_frag(b, match);
 
-    if (cr->wc.masks.nw_tos & IP_DSCP_MASK) {
+    if (match->wc.masks.nw_tos & IP_DSCP_MASK) {
         nxm_put_8(b, oxm ? OXM_OF_IP_DSCP : NXM_OF_IP_TOS,
                   flow->nw_tos & IP_DSCP_MASK);
     }
 
-    if (cr->wc.masks.nw_tos & IP_ECN_MASK) {
+    if (match->wc.masks.nw_tos & IP_ECN_MASK) {
         nxm_put_8(b, oxm ? OXM_OF_IP_ECN : NXM_NX_IP_ECN,
                   flow->nw_tos & IP_ECN_MASK);
     }
 
-    if (!oxm && cr->wc.masks.nw_ttl) {
+    if (!oxm && match->wc.masks.nw_ttl) {
         nxm_put_8(b, NXM_NX_IP_TTL, flow->nw_ttl);
     }
 
-    if (cr->wc.masks.nw_proto) {
+    if (match->wc.masks.nw_proto) {
         nxm_put_8(b, oxm ? OXM_OF_IP_PROTO : NXM_OF_IP_PROTO, flow->nw_proto);
 
         if (flow->nw_proto == IPPROTO_TCP) {
             nxm_put_16m(b, oxm ? OXM_OF_TCP_SRC : NXM_OF_TCP_SRC,
-                        flow->tp_src, cr->wc.masks.tp_src);
+                        flow->tp_src, match->wc.masks.tp_src);
             nxm_put_16m(b, oxm ? OXM_OF_TCP_DST : NXM_OF_TCP_DST,
-                        flow->tp_dst, cr->wc.masks.tp_dst);
+                        flow->tp_dst, match->wc.masks.tp_dst);
         } else if (flow->nw_proto == IPPROTO_UDP) {
             nxm_put_16m(b, oxm ? OXM_OF_UDP_SRC : NXM_OF_UDP_SRC,
-                        flow->tp_src, cr->wc.masks.tp_src);
+                        flow->tp_src, match->wc.masks.tp_src);
             nxm_put_16m(b, oxm ? OXM_OF_UDP_DST : NXM_OF_UDP_DST,
-                        flow->tp_dst, cr->wc.masks.tp_dst);
+                        flow->tp_dst, match->wc.masks.tp_dst);
         } else if (flow->nw_proto == icmp_proto) {
-            if (cr->wc.masks.tp_src) {
+            if (match->wc.masks.tp_src) {
                 nxm_put_8(b, icmp_type, ntohs(flow->tp_src));
             }
-            if (cr->wc.masks.tp_dst) {
+            if (match->wc.masks.tp_dst) {
                 nxm_put_8(b, icmp_code, ntohs(flow->tp_dst));
             }
         }
     }
 }
 
-/* Appends to 'b' the nx_match format that expresses 'cr' (except for
- * 'cr->priority', because priority is not part of nx_match), plus enough
- * zero bytes to pad the nx_match out to a multiple of 8.  For Flow Mod
- * and Flow Stats Requests messages, a 'cookie' and 'cookie_mask' may be
- * supplied.  Otherwise, 'cookie_mask' should be zero.
+/* Appends to 'b' the nx_match format that expresses 'match', plus enough zero
+ * bytes to pad the nx_match out to a multiple of 8.  For Flow Mod and Flow
+ * Stats Requests messages, a 'cookie' and 'cookie_mask' may be supplied.
+ * Otherwise, 'cookie_mask' should be zero.
  *
  * This function can cause 'b''s data to be reallocated.
  *
  * Returns the number of bytes appended to 'b', excluding padding.
  *
- * If 'cr' is a catch-all rule that matches every packet, then this function
+ * If 'match' is a catch-all rule that matches every packet, then this function
  * appends nothing to 'b' and returns 0. */
 int
-nx_put_match(struct ofpbuf *b, bool oxm, const struct cls_rule *cr,
+nx_put_match(struct ofpbuf *b, bool oxm, const struct match *match,
              ovs_be64 cookie, ovs_be64 cookie_mask)
 {
-    const struct flow *flow = &cr->flow;
+    const struct flow *flow = &match->flow;
     const size_t start_len = b->size;
     int match_len;
     int i;
@@ -494,7 +488,7 @@ nx_put_match(struct ofpbuf *b, bool oxm, const struct cls_rule *cr,
     BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
 
     /* Metadata. */
-    if (cr->wc.masks.in_port) {
+    if (match->wc.masks.in_port) {
         uint16_t in_port = flow->in_port;
         if (oxm) {
             nxm_put_32(b, OXM_OF_IN_PORT, ofputil_port_to_ofp11(in_port));
@@ -505,82 +499,82 @@ nx_put_match(struct ofpbuf *b, bool oxm, const struct cls_rule *cr,
 
     /* Ethernet. */
     nxm_put_eth_masked(b, oxm ? OXM_OF_ETH_SRC : NXM_OF_ETH_SRC,
-                       flow->dl_src, cr->wc.masks.dl_src);
+                       flow->dl_src, match->wc.masks.dl_src);
     nxm_put_eth_masked(b, oxm ? OXM_OF_ETH_DST : NXM_OF_ETH_DST,
-                       flow->dl_dst, cr->wc.masks.dl_dst);
+                       flow->dl_dst, match->wc.masks.dl_dst);
     nxm_put_16m(b, oxm ? OXM_OF_ETH_TYPE : NXM_OF_ETH_TYPE,
                 ofputil_dl_type_to_openflow(flow->dl_type),
-                cr->wc.masks.dl_type);
+                match->wc.masks.dl_type);
 
     /* 802.1Q.
      *
      * XXX missing OXM support */
-    nxm_put_16m(b, NXM_OF_VLAN_TCI, flow->vlan_tci, cr->wc.masks.vlan_tci);
+    nxm_put_16m(b, NXM_OF_VLAN_TCI, flow->vlan_tci, match->wc.masks.vlan_tci);
 
     /* L3. */
     if (flow->dl_type == htons(ETH_TYPE_IP)) {
         /* IP. */
         nxm_put_32m(b, oxm ? OXM_OF_IPV4_SRC : NXM_OF_IP_SRC,
-                    flow->nw_src, cr->wc.masks.nw_src);
+                    flow->nw_src, match->wc.masks.nw_src);
         nxm_put_32m(b, oxm ? OXM_OF_IPV4_DST : NXM_OF_IP_DST,
-                    flow->nw_dst, cr->wc.masks.nw_dst);
-        nxm_put_ip(b, cr, IPPROTO_ICMP,
+                    flow->nw_dst, match->wc.masks.nw_dst);
+        nxm_put_ip(b, match, IPPROTO_ICMP,
                    oxm ? OXM_OF_ICMPV4_TYPE : NXM_OF_ICMP_TYPE,
                    oxm ? OXM_OF_ICMPV4_CODE : NXM_OF_ICMP_CODE, oxm);
     } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
         /* IPv6. */
         nxm_put_ipv6(b, oxm ? OXM_OF_IPV6_SRC : NXM_NX_IPV6_SRC,
-                     &flow->ipv6_src, &cr->wc.masks.ipv6_src);
+                     &flow->ipv6_src, &match->wc.masks.ipv6_src);
         nxm_put_ipv6(b, oxm ? OXM_OF_IPV6_DST : NXM_NX_IPV6_DST,
-                     &flow->ipv6_dst, &cr->wc.masks.ipv6_dst);
-        nxm_put_ip(b, cr, IPPROTO_ICMPV6,
+                     &flow->ipv6_dst, &match->wc.masks.ipv6_dst);
+        nxm_put_ip(b, match, IPPROTO_ICMPV6,
                    oxm ? OXM_OF_ICMPV6_TYPE : NXM_NX_ICMPV6_TYPE,
                    oxm ? OXM_OF_ICMPV6_CODE : NXM_NX_ICMPV6_CODE, oxm);
 
         nxm_put_32m(b, oxm ? OXM_OF_IPV6_FLABEL : NXM_NX_IPV6_LABEL,
-                    flow->ipv6_label, cr->wc.masks.ipv6_label);
+                    flow->ipv6_label, match->wc.masks.ipv6_label);
 
         if (flow->nw_proto == IPPROTO_ICMPV6
             && (flow->tp_src == htons(ND_NEIGHBOR_SOLICIT) ||
                 flow->tp_src == htons(ND_NEIGHBOR_ADVERT))) {
             nxm_put_ipv6(b, oxm ? OXM_OF_IPV6_ND_TARGET : NXM_NX_ND_TARGET,
-                         &flow->nd_target, &cr->wc.masks.nd_target);
+                         &flow->nd_target, &match->wc.masks.nd_target);
             if (flow->tp_src == htons(ND_NEIGHBOR_SOLICIT)) {
                 nxm_put_eth_masked(b, oxm ? OXM_OF_IPV6_ND_SLL : NXM_NX_ND_SLL,
-                                   flow->arp_sha, cr->wc.masks.arp_sha);
+                                   flow->arp_sha, match->wc.masks.arp_sha);
             }
             if (flow->tp_src == htons(ND_NEIGHBOR_ADVERT)) {
                 nxm_put_eth_masked(b, oxm ? OXM_OF_IPV6_ND_TLL : NXM_NX_ND_TLL,
-                                   flow->arp_tha, cr->wc.masks.arp_tha);
+                                   flow->arp_tha, match->wc.masks.arp_tha);
             }
         }
     } else if (flow->dl_type == htons(ETH_TYPE_ARP)) {
         /* ARP. */
-        if (cr->wc.masks.nw_proto) {
+        if (match->wc.masks.nw_proto) {
             nxm_put_16(b, oxm ? OXM_OF_ARP_OP : NXM_OF_ARP_OP,
                        htons(flow->nw_proto));
         }
         nxm_put_32m(b, oxm ? OXM_OF_ARP_SPA : NXM_OF_ARP_SPA,
-                    flow->nw_src, cr->wc.masks.nw_src);
+                    flow->nw_src, match->wc.masks.nw_src);
         nxm_put_32m(b, oxm ? OXM_OF_ARP_TPA : NXM_OF_ARP_TPA,
-                    flow->nw_dst, cr->wc.masks.nw_dst);
+                    flow->nw_dst, match->wc.masks.nw_dst);
         nxm_put_eth_masked(b, oxm ? OXM_OF_ARP_SHA : NXM_NX_ARP_SHA,
-                           flow->arp_sha, cr->wc.masks.arp_sha);
+                           flow->arp_sha, match->wc.masks.arp_sha);
         nxm_put_eth_masked(b, oxm ? OXM_OF_ARP_THA : NXM_NX_ARP_THA,
-                           flow->arp_tha, cr->wc.masks.arp_tha);
+                           flow->arp_tha, match->wc.masks.arp_tha);
     }
 
     /* Tunnel ID. */
-    nxm_put_64m(b, NXM_NX_TUN_ID, flow->tun_id, cr->wc.masks.tun_id);
+    nxm_put_64m(b, NXM_NX_TUN_ID, flow->tun_id, match->wc.masks.tun_id);
 
     /* Registers. */
     for (i = 0; i < FLOW_N_REGS; i++) {
         nxm_put_32m(b, NXM_NX_REG(i),
-                    htonl(flow->regs[i]), htonl(cr->wc.masks.regs[i]));
+                    htonl(flow->regs[i]), htonl(match->wc.masks.regs[i]));
     }
 
     /* OpenFlow 1.1+ Metadata. */
-    nxm_put_64m(b, OXM_OF_METADATA, flow->metadata, cr->wc.masks.metadata);
+    nxm_put_64m(b, OXM_OF_METADATA, flow->metadata, match->wc.masks.metadata);
 
     /* Cookie. */
     nxm_put_64m(b, NXM_NX_COOKIE, cookie, cookie_mask);
diff --git a/lib/nx-match.h b/lib/nx-match.h
index 161733f..0513610 100644
--- a/lib/nx-match.h
+++ b/lib/nx-match.h
@@ -23,11 +23,9 @@
 #include "flow.h"
 #include "ofp-errors.h"
 #include "openvswitch/types.h"
-#include "ofp-errors.h"
 
-struct cls_rule;
 struct ds;
-struct flow;
+struct match;
 struct mf_subfield;
 struct ofpact_reg_move;
 struct ofpact_reg_load;
@@ -41,12 +39,12 @@ struct nx_action_reg_move;
  */
 
 enum ofperr nx_pull_match(struct ofpbuf *, unsigned int match_len,
-                          uint16_t priority, struct cls_rule *,
+                          struct match *,
                           ovs_be64 *cookie, ovs_be64 *cookie_mask);
 enum ofperr nx_pull_match_loose(struct ofpbuf *, unsigned int match_len,
-                                uint16_t priority, struct cls_rule *,
+                                struct match *,
                                 ovs_be64 *cookie, ovs_be64 *cookie_mask);
-int nx_put_match(struct ofpbuf *, bool oxm, const struct cls_rule *,
+int nx_put_match(struct ofpbuf *, bool oxm, const struct match *,
                  ovs_be64 cookie, ovs_be64 cookie_mask);
 
 char *nx_match_to_string(const uint8_t *, unsigned int match_len);
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 32d3836..dd68dce 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -518,7 +518,7 @@ ofp_fatal(const char *flow, bool verbose, const char *format, ...)
 }
 
 static void
-parse_field(const struct mf_field *mf, const char *s, struct cls_rule *rule)
+parse_field(const struct mf_field *mf, const char *s, struct match *match)
 {
     union mf_value value, mask;
     char *error;
@@ -528,7 +528,7 @@ parse_field(const struct mf_field *mf, const char *s, struct cls_rule *rule)
         ovs_fatal(0, "%s", error);
     }
 
-    mf_set(mf, &value, &mask, rule);
+    mf_set(mf, &value, &mask, match);
 }
 
 /* Convert 'str_' (as described in the Flow Syntax section of the ovs-ofctl man
@@ -584,7 +584,8 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
         NOT_REACHED();
     }
 
-    cls_rule_init_catchall(&fm->cr, OFP_DEFAULT_PRIORITY);
+    match_init_catchall(&fm->match);
+    fm->priority = OFP_DEFAULT_PRIORITY;
     fm->cookie = htonll(0);
     fm->cookie_mask = htonll(0);
     if (command == OFPFC_MODIFY || command == OFPFC_MODIFY_STRICT) {
@@ -619,9 +620,9 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
         const struct protocol *p;
 
         if (parse_protocol(name, &p)) {
-            cls_rule_set_dl_type(&fm->cr, htons(p->dl_type));
+            match_set_dl_type(&fm->match, htons(p->dl_type));
             if (p->nw_proto) {
-                cls_rule_set_nw_proto(&fm->cr, p->nw_proto);
+                match_set_nw_proto(&fm->match, p->nw_proto);
             }
         } else if (fields & F_FLAGS && !strcmp(name, "send_flow_rem")) {
             fm->flags |= OFPFF_SEND_FLOW_REM;
@@ -640,7 +641,7 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
             } else if (!strcmp(name, "out_port")) {
                 fm->out_port = atoi(value);
             } else if (fields & F_PRIORITY && !strcmp(name, "priority")) {
-                fm->cr.priority = str_to_u16(value, name);
+                fm->priority = str_to_u16(value, name);
             } else if (fields & F_TIMEOUT && !strcmp(name, "idle_timeout")) {
                 fm->idle_timeout = str_to_u16(value, name);
             } else if (fields & F_TIMEOUT && !strcmp(name, "hard_timeout")) {
@@ -666,7 +667,7 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
                     fm->new_cookie = htonll(str_to_u64(value));
                 }
             } else if (mf_from_name(name)) {
-                parse_field(mf_from_name(name), value, &fm->cr);
+                parse_field(mf_from_name(name), value, &fm->match);
             } else if (!strcmp(name, "duration")
                        || !strcmp(name, "n_packets")
                        || !strcmp(name, "n_bytes")) {
@@ -689,7 +690,7 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
         struct ofpbuf ofpacts;
 
         ofpbuf_init(&ofpacts, 32);
-        str_to_ofpacts(&fm->cr.flow, act_str, &ofpacts);
+        str_to_ofpacts(&fm->match.flow, act_str, &ofpacts);
         fm->ofpacts_len = ofpacts.size;
         fm->ofpacts = ofpbuf_steal_data(&ofpacts);
     } else {
@@ -717,7 +718,7 @@ parse_flow_monitor_request(struct ofputil_flow_monitor_request *fmr,
                   | NXFMF_OWN | NXFMF_ACTIONS);
     fmr->out_port = OFPP_NONE;
     fmr->table_id = 0xff;
-    cls_rule_init_catchall(&fmr->match, 0);
+    match_init_catchall(&fmr->match);
 
     for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name;
          name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) {
@@ -736,9 +737,9 @@ parse_flow_monitor_request(struct ofputil_flow_monitor_request *fmr,
         } else if (!strcmp(name, "!own")) {
             fmr->flags &= ~NXFMF_OWN;
         } else if (parse_protocol(name, &p)) {
-            cls_rule_set_dl_type(&fmr->match, htons(p->dl_type));
+            match_set_dl_type(&fmr->match, htons(p->dl_type));
             if (p->nw_proto) {
-                cls_rule_set_nw_proto(&fmr->match, p->nw_proto);
+                match_set_nw_proto(&fmr->match, p->nw_proto);
             }
         } else {
             char *value;
@@ -781,15 +782,15 @@ void
 parse_ofp_flow_mod_str(struct ofputil_flow_mod *fm, const char *string,
                        uint16_t command, bool verbose)
 {
-    struct cls_rule rule_copy;
+    struct match match_copy;
 
     parse_ofp_str(fm, command, string, verbose);
 
-    /* Normalize a copy of the rule.  This ensures that non-normalized flows
+    /* 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. */
-    rule_copy = fm->cr;
-    ofputil_normalize_rule(&rule_copy);
+    match_copy = fm->match;
+    ofputil_normalize_match(&match_copy);
 }
 
 void
@@ -831,7 +832,7 @@ 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.cr;
+    fsr->match = fm.match;
     fsr->out_port = fm.out_port;
     fsr->table_id = fm.table_id;
 }
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 3d3b919..1b75d2b 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -727,9 +727,9 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
         /* nx_match_to_string() doesn't print priority. */
         need_priority = true;
     } else {
-        cls_rule_format(&fm.cr, s);
+        match_format(&fm.match, s, fm.priority);
 
-        /* cls_rule_format() does print priority. */
+        /* match_format() does print priority. */
         need_priority = false;
     }
 
@@ -749,8 +749,8 @@ ofp_print_flow_mod(struct ds *s, const struct ofp_header *oh,
     if (fm.hard_timeout != OFP_FLOW_PERMANENT) {
         ds_put_format(s, "hard:%"PRIu16" ", fm.hard_timeout);
     }
-    if (fm.cr.priority != OFP_DEFAULT_PRIORITY && need_priority) {
-        ds_put_format(s, "pri:%"PRIu16" ", fm.cr.priority);
+    if (fm.priority != OFP_DEFAULT_PRIORITY && need_priority) {
+        ds_put_format(s, "pri:%"PRIu16" ", fm.priority);
     }
     if (fm.buffer_id != UINT32_MAX) {
         ds_put_format(s, "buf:0x%"PRIx32" ", fm.buffer_id);
@@ -831,7 +831,7 @@ ofp_print_flow_removed(struct ds *string, const struct ofp_header *oh)
     }
 
     ds_put_char(string, ' ');
-    cls_rule_format(&fr.rule, string);
+    match_format(&fr.match, string, fr.priority);
 
     ds_put_format(string, " reason=%s",
                   ofp_flow_removed_reason_to_string(fr.reason));
@@ -973,12 +973,8 @@ ofp_print_flow_stats_request(struct ds *string,
         ofputil_format_port(fsr.out_port, string);
     }
 
-    /* A flow stats request doesn't include a priority, but cls_rule_format()
-     * will print one unless it is OFP_DEFAULT_PRIORITY. */
-    fsr.match.priority = OFP_DEFAULT_PRIORITY;
-
     ds_put_char(string, ' ');
-    cls_rule_format(&fsr.match, string);
+    match_format(&fsr.match, string, OFP_DEFAULT_PRIORITY);
 }
 
 void
@@ -1004,7 +1000,7 @@ ofp_print_flow_stats(struct ds *string, struct ofputil_flow_stats *fs)
         ds_put_format(string, "hard_age=%d, ", fs->hard_age);
     }
 
-    cls_rule_format(&fs->rule, string);
+    match_format(&fs->match, string, fs->priority);
     if (string->string[string->length - 1] != ' ') {
         ds_put_char(string, ' ');
     }
@@ -1426,7 +1422,7 @@ ofp_print_nxst_flow_monitor_request(struct ds *string,
         }
 
         ds_put_char(string, ' ');
-        cls_rule_format(&request.match, string);
+        match_format(&request.match, string, OFP_DEFAULT_PRIORITY);
         ds_chomp(string, ' ');
     }
 }
@@ -1443,7 +1439,7 @@ ofp_print_nxst_flow_monitor_reply(struct ds *string,
     ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
     for (;;) {
         struct ofputil_flow_update update;
-        struct cls_rule match;
+        struct match match;
         int retval;
 
         update.match = &match;
@@ -1488,7 +1484,7 @@ ofp_print_nxst_flow_monitor_reply(struct ds *string,
         ds_put_format(string, " cookie=%#"PRIx64, ntohll(update.cookie));
 
         ds_put_char(string, ' ');
-        cls_rule_format(update.match, string);
+        match_format(update.match, string, OFP_DEFAULT_PRIORITY);
 
         if (update.ofpacts_len) {
             if (string->string[string->length - 1] != ' ') {
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index aa3525b..7f12517 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -130,34 +130,33 @@ ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *wc)
     }
 }
 
-/* Converts the ofp10_match in 'match' into a cls_rule in 'rule', with the
- * given 'priority'. */
-void
-ofputil_cls_rule_from_ofp10_match(const struct ofp10_match *match,
-                                  unsigned int priority, struct cls_rule *rule)
-{
-    uint32_t ofpfw = ntohl(match->wildcards) & OFPFW10_ALL;
-
-    /* Initialize rule->priority, rule->wc. */
-    memset(rule->flow.zeros, 0, sizeof rule->flow.zeros);
-    rule->priority = !ofpfw ? UINT16_MAX : priority;
-    ofputil_wildcard_from_ofpfw10(ofpfw, &rule->wc);
-
-    /* Initialize most of rule->flow. */
-    rule->flow.nw_src = match->nw_src;
-    rule->flow.nw_dst = match->nw_dst;
-    rule->flow.in_port = ntohs(match->in_port);
-    rule->flow.dl_type = ofputil_dl_type_from_openflow(match->dl_type);
-    rule->flow.tp_src = match->tp_src;
-    rule->flow.tp_dst = match->tp_dst;
-    memcpy(rule->flow.dl_src, match->dl_src, ETH_ADDR_LEN);
-    memcpy(rule->flow.dl_dst, match->dl_dst, ETH_ADDR_LEN);
-    rule->flow.nw_tos = match->nw_tos & IP_DSCP_MASK;
-    rule->flow.nw_proto = match->nw_proto;
+/* Converts the ofp10_match in 'ofmatch' into a struct match in 'match'. */
+unsigned int
+ofputil_match_from_ofp10_match(const struct ofp10_match *ofmatch,
+                               unsigned int priority,
+                               struct match *match)
+{
+    uint32_t ofpfw = ntohl(ofmatch->wildcards) & OFPFW10_ALL;
+
+    /* Initialize match->wc. */
+    memset(match->flow.zeros, 0, sizeof match->flow.zeros);
+    ofputil_wildcard_from_ofpfw10(ofpfw, &match->wc);
+
+    /* Initialize most of match->flow. */
+    match->flow.nw_src = ofmatch->nw_src;
+    match->flow.nw_dst = ofmatch->nw_dst;
+    match->flow.in_port = ntohs(ofmatch->in_port);
+    match->flow.dl_type = ofputil_dl_type_from_openflow(ofmatch->dl_type);
+    match->flow.tp_src = ofmatch->tp_src;
+    match->flow.tp_dst = ofmatch->tp_dst;
+    memcpy(match->flow.dl_src, ofmatch->dl_src, ETH_ADDR_LEN);
+    memcpy(match->flow.dl_dst, ofmatch->dl_dst, ETH_ADDR_LEN);
+    match->flow.nw_tos = ofmatch->nw_tos & IP_DSCP_MASK;
+    match->flow.nw_proto = ofmatch->nw_proto;
 
     /* Translate VLANs. */
     if (!(ofpfw & OFPFW10_DL_VLAN) &&
-        match->dl_vlan == htons(OFP10_VLAN_NONE)) {
+        ofmatch->dl_vlan == htons(OFP10_VLAN_NONE)) {
         /* Match only packets without 802.1Q header.
          *
          * When OFPFW10_DL_VLAN_PCP is wildcarded, this is obviously correct.
@@ -166,27 +165,29 @@ ofputil_cls_rule_from_ofp10_match(const struct ofp10_match *match,
          * because we can't have a specific PCP without an 802.1Q header.
          * However, older versions of OVS treated this as matching packets
          * withut an 802.1Q header, so we do here too. */
-        rule->flow.vlan_tci = htons(0);
-        rule->wc.masks.vlan_tci = htons(0xffff);
+        match->flow.vlan_tci = htons(0);
+        match->wc.masks.vlan_tci = htons(0xffff);
     } else {
         ovs_be16 vid, pcp, tci;
 
-        vid = match->dl_vlan & htons(VLAN_VID_MASK);
-        pcp = htons((match->dl_vlan_pcp << VLAN_PCP_SHIFT) & VLAN_PCP_MASK);
+        vid = ofmatch->dl_vlan & htons(VLAN_VID_MASK);
+        pcp = htons((ofmatch->dl_vlan_pcp << VLAN_PCP_SHIFT) & VLAN_PCP_MASK);
         tci = vid | pcp | htons(VLAN_CFI);
-        rule->flow.vlan_tci = tci & rule->wc.masks.vlan_tci;
+        match->flow.vlan_tci = tci & match->wc.masks.vlan_tci;
     }
 
     /* Clean up. */
-    cls_rule_zero_wildcarded_fields(rule);
+    match_zero_wildcarded_fields(match);
+
+    return !ofpfw ? UINT16_MAX : priority;
 }
 
-/* Convert 'rule' into the OpenFlow 1.0 match structure 'match'. */
+/* Convert 'match' into the OpenFlow 1.0 match structure 'ofmatch'. */
 void
-ofputil_cls_rule_to_ofp10_match(const struct cls_rule *rule,
-                                struct ofp10_match *match)
+ofputil_match_to_ofp10_match(const struct match *match,
+                             struct ofp10_match *ofmatch)
 {
-    const struct flow_wildcards *wc = &rule->wc;
+    const struct flow_wildcards *wc = &match->wc;
     uint32_t ofpfw;
 
     /* Figure out most OpenFlow wildcards. */
@@ -221,104 +222,103 @@ ofputil_cls_rule_to_ofp10_match(const struct cls_rule *rule,
     }
 
     /* Translate VLANs. */
-    match->dl_vlan = htons(0);
-    match->dl_vlan_pcp = 0;
-    if (rule->wc.masks.vlan_tci == htons(0)) {
+    ofmatch->dl_vlan = htons(0);
+    ofmatch->dl_vlan_pcp = 0;
+    if (match->wc.masks.vlan_tci == htons(0)) {
         ofpfw |= OFPFW10_DL_VLAN | OFPFW10_DL_VLAN_PCP;
-    } else if (rule->wc.masks.vlan_tci & htons(VLAN_CFI)
-               && !(rule->flow.vlan_tci & htons(VLAN_CFI))) {
-        match->dl_vlan = htons(OFP10_VLAN_NONE);
+    } else if (match->wc.masks.vlan_tci & htons(VLAN_CFI)
+               && !(match->flow.vlan_tci & htons(VLAN_CFI))) {
+        ofmatch->dl_vlan = htons(OFP10_VLAN_NONE);
         ofpfw |= OFPFW10_DL_VLAN_PCP;
     } else {
-        if (!(rule->wc.masks.vlan_tci & htons(VLAN_VID_MASK))) {
+        if (!(match->wc.masks.vlan_tci & htons(VLAN_VID_MASK))) {
             ofpfw |= OFPFW10_DL_VLAN;
         } else {
-            match->dl_vlan = htons(vlan_tci_to_vid(rule->flow.vlan_tci));
+            ofmatch->dl_vlan = htons(vlan_tci_to_vid(match->flow.vlan_tci));
         }
 
-        if (!(rule->wc.masks.vlan_tci & htons(VLAN_PCP_MASK))) {
+        if (!(match->wc.masks.vlan_tci & htons(VLAN_PCP_MASK))) {
             ofpfw |= OFPFW10_DL_VLAN_PCP;
         } else {
-            match->dl_vlan_pcp = vlan_tci_to_pcp(rule->flow.vlan_tci);
+            ofmatch->dl_vlan_pcp = vlan_tci_to_pcp(match->flow.vlan_tci);
         }
     }
 
-    /* Compose most of the match structure. */
-    match->wildcards = htonl(ofpfw);
-    match->in_port = htons(rule->flow.in_port);
-    memcpy(match->dl_src, rule->flow.dl_src, ETH_ADDR_LEN);
-    memcpy(match->dl_dst, rule->flow.dl_dst, ETH_ADDR_LEN);
-    match->dl_type = ofputil_dl_type_to_openflow(rule->flow.dl_type);
-    match->nw_src = rule->flow.nw_src;
-    match->nw_dst = rule->flow.nw_dst;
-    match->nw_tos = rule->flow.nw_tos & IP_DSCP_MASK;
-    match->nw_proto = rule->flow.nw_proto;
-    match->tp_src = rule->flow.tp_src;
-    match->tp_dst = rule->flow.tp_dst;
-    memset(match->pad1, '\0', sizeof match->pad1);
-    memset(match->pad2, '\0', sizeof match->pad2);
-}
-
-/* Converts the ofp11_match in 'match' into a cls_rule in 'rule', with the
- * given 'priority'.  Returns 0 if successful, otherwise an OFPERR_* value. */
+    /* Compose most of the ofmatch structure. */
+    ofmatch->wildcards = htonl(ofpfw);
+    ofmatch->in_port = htons(match->flow.in_port);
+    memcpy(ofmatch->dl_src, match->flow.dl_src, ETH_ADDR_LEN);
+    memcpy(ofmatch->dl_dst, match->flow.dl_dst, ETH_ADDR_LEN);
+    ofmatch->dl_type = ofputil_dl_type_to_openflow(match->flow.dl_type);
+    ofmatch->nw_src = match->flow.nw_src;
+    ofmatch->nw_dst = match->flow.nw_dst;
+    ofmatch->nw_tos = match->flow.nw_tos & IP_DSCP_MASK;
+    ofmatch->nw_proto = match->flow.nw_proto;
+    ofmatch->tp_src = match->flow.tp_src;
+    ofmatch->tp_dst = match->flow.tp_dst;
+    memset(ofmatch->pad1, '\0', sizeof ofmatch->pad1);
+    memset(ofmatch->pad2, '\0', sizeof ofmatch->pad2);
+}
+
+/* Converts the ofp11_match in 'ofmatch' into a struct match in 'match'.
+ * Returns 0 if successful, otherwise an OFPERR_* value. */
 enum ofperr
-ofputil_cls_rule_from_ofp11_match(const struct ofp11_match *match,
-                                  unsigned int priority,
-                                  struct cls_rule *rule)
+ofputil_match_from_ofp11_match(const struct ofp11_match *ofmatch,
+                               struct match *match)
 {
-    uint16_t wc = ntohl(match->wildcards);
+    uint16_t wc = ntohl(ofmatch->wildcards);
     uint8_t dl_src_mask[ETH_ADDR_LEN];
     uint8_t dl_dst_mask[ETH_ADDR_LEN];
     bool ipv4, arp;
     int i;
 
-    cls_rule_init_catchall(rule, priority);
+    match_init_catchall(match);
 
     if (!(wc & OFPFW11_IN_PORT)) {
         uint16_t ofp_port;
         enum ofperr error;
 
-        error = ofputil_port_from_ofp11(match->in_port, &ofp_port);
+        error = ofputil_port_from_ofp11(ofmatch->in_port, &ofp_port);
         if (error) {
             return OFPERR_OFPBMC_BAD_VALUE;
         }
-        cls_rule_set_in_port(rule, ofp_port);
+        match_set_in_port(match, ofp_port);
     }
 
     for (i = 0; i < ETH_ADDR_LEN; i++) {
-        dl_src_mask[i] = ~match->dl_src_mask[i];
+        dl_src_mask[i] = ~ofmatch->dl_src_mask[i];
     }
-    cls_rule_set_dl_src_masked(rule, match->dl_src, dl_src_mask);
+    match_set_dl_src_masked(match, ofmatch->dl_src, dl_src_mask);
 
     for (i = 0; i < ETH_ADDR_LEN; i++) {
-        dl_dst_mask[i] = ~match->dl_dst_mask[i];
+        dl_dst_mask[i] = ~ofmatch->dl_dst_mask[i];
     }
-    cls_rule_set_dl_dst_masked(rule, match->dl_dst, dl_dst_mask);
+    match_set_dl_dst_masked(match, ofmatch->dl_dst, dl_dst_mask);
 
     if (!(wc & OFPFW11_DL_VLAN)) {
-        if (match->dl_vlan == htons(OFPVID11_NONE)) {
+        if (ofmatch->dl_vlan == htons(OFPVID11_NONE)) {
             /* Match only packets without a VLAN tag. */
-            rule->flow.vlan_tci = htons(0);
-            rule->wc.masks.vlan_tci = htons(UINT16_MAX);
+            match->flow.vlan_tci = htons(0);
+            match->wc.masks.vlan_tci = htons(UINT16_MAX);
         } else {
-            if (match->dl_vlan == htons(OFPVID11_ANY)) {
+            if (ofmatch->dl_vlan == htons(OFPVID11_ANY)) {
                 /* Match any packet with a VLAN tag regardless of VID. */
-                rule->flow.vlan_tci = htons(VLAN_CFI);
-                rule->wc.masks.vlan_tci = htons(VLAN_CFI);
-            } else if (ntohs(match->dl_vlan) < 4096) {
+                match->flow.vlan_tci = htons(VLAN_CFI);
+                match->wc.masks.vlan_tci = htons(VLAN_CFI);
+            } else if (ntohs(ofmatch->dl_vlan) < 4096) {
                 /* Match only packets with the specified VLAN VID. */
-                rule->flow.vlan_tci = htons(VLAN_CFI) | match->dl_vlan;
-                rule->wc.masks.vlan_tci = htons(VLAN_CFI | VLAN_VID_MASK);
+                match->flow.vlan_tci = htons(VLAN_CFI) | ofmatch->dl_vlan;
+                match->wc.masks.vlan_tci = htons(VLAN_CFI | VLAN_VID_MASK);
             } else {
                 /* Invalid VID. */
                 return OFPERR_OFPBMC_BAD_VALUE;
             }
 
             if (!(wc & OFPFW11_DL_VLAN_PCP)) {
-                if (match->dl_vlan_pcp <= 7) {
-                    rule->flow.vlan_tci |= htons(match->dl_vlan_pcp
+                if (ofmatch->dl_vlan_pcp <= 7) {
+                    match->flow.vlan_tci |= htons(ofmatch->dl_vlan_pcp
                                                  << VLAN_PCP_SHIFT);
-                    rule->wc.masks.vlan_tci |= htons(VLAN_PCP_MASK);
+                    match->wc.masks.vlan_tci |= htons(VLAN_PCP_MASK);
                 } else {
                     /* Invalid PCP. */
                     return OFPERR_OFPBMC_BAD_VALUE;
@@ -328,33 +328,33 @@ ofputil_cls_rule_from_ofp11_match(const struct ofp11_match *match,
     }
 
     if (!(wc & OFPFW11_DL_TYPE)) {
-        cls_rule_set_dl_type(rule,
-                             ofputil_dl_type_from_openflow(match->dl_type));
+        match_set_dl_type(match,
+                             ofputil_dl_type_from_openflow(ofmatch->dl_type));
     }
 
-    ipv4 = rule->flow.dl_type == htons(ETH_TYPE_IP);
-    arp = rule->flow.dl_type == htons(ETH_TYPE_ARP);
+    ipv4 = match->flow.dl_type == htons(ETH_TYPE_IP);
+    arp = match->flow.dl_type == htons(ETH_TYPE_ARP);
 
     if (ipv4 && !(wc & OFPFW11_NW_TOS)) {
-        if (match->nw_tos & ~IP_DSCP_MASK) {
+        if (ofmatch->nw_tos & ~IP_DSCP_MASK) {
             /* Invalid TOS. */
             return OFPERR_OFPBMC_BAD_VALUE;
         }
 
-        cls_rule_set_nw_dscp(rule, match->nw_tos);
+        match_set_nw_dscp(match, ofmatch->nw_tos);
     }
 
     if (ipv4 || arp) {
         if (!(wc & OFPFW11_NW_PROTO)) {
-            cls_rule_set_nw_proto(rule, match->nw_proto);
+            match_set_nw_proto(match, ofmatch->nw_proto);
         }
-        cls_rule_set_nw_src_masked(rule, match->nw_src, ~match->nw_src_mask);
-        cls_rule_set_nw_dst_masked(rule, match->nw_dst, ~match->nw_dst_mask);
+        match_set_nw_src_masked(match, ofmatch->nw_src, ~ofmatch->nw_src_mask);
+        match_set_nw_dst_masked(match, ofmatch->nw_dst, ~ofmatch->nw_dst_mask);
     }
 
 #define OFPFW11_TP_ALL (OFPFW11_TP_SRC | OFPFW11_TP_DST)
     if (ipv4 && (wc & OFPFW11_TP_ALL) != OFPFW11_TP_ALL) {
-        switch (rule->flow.nw_proto) {
+        switch (match->flow.nw_proto) {
         case IPPROTO_ICMP:
             /* "A.2.3 Flow Match Structures" in OF1.1 says:
              *
@@ -364,17 +364,17 @@ ofputil_cls_rule_from_ofp11_match(const struct ofp11_match *match,
              * but I'm pretty sure we should support ICMP too, otherwise
              * that's a regression from OF1.0. */
             if (!(wc & OFPFW11_TP_SRC)) {
-                uint16_t icmp_type = ntohs(match->tp_src);
+                uint16_t icmp_type = ntohs(ofmatch->tp_src);
                 if (icmp_type < 0x100) {
-                    cls_rule_set_icmp_type(rule, icmp_type);
+                    match_set_icmp_type(match, icmp_type);
                 } else {
                     return OFPERR_OFPBMC_BAD_FIELD;
                 }
             }
             if (!(wc & OFPFW11_TP_DST)) {
-                uint16_t icmp_code = ntohs(match->tp_dst);
+                uint16_t icmp_code = ntohs(ofmatch->tp_dst);
                 if (icmp_code < 0x100) {
-                    cls_rule_set_icmp_code(rule, icmp_code);
+                    match_set_icmp_code(match, icmp_code);
                 } else {
                     return OFPERR_OFPBMC_BAD_FIELD;
                 }
@@ -384,10 +384,10 @@ ofputil_cls_rule_from_ofp11_match(const struct ofp11_match *match,
         case IPPROTO_TCP:
         case IPPROTO_UDP:
             if (!(wc & (OFPFW11_TP_SRC))) {
-                cls_rule_set_tp_src(rule, match->tp_src);
+                match_set_tp_src(match, ofmatch->tp_src);
             }
             if (!(wc & (OFPFW11_TP_DST))) {
-                cls_rule_set_tp_dst(rule, match->tp_dst);
+                match_set_tp_dst(match, ofmatch->tp_dst);
             }
             break;
 
@@ -402,8 +402,8 @@ ofputil_cls_rule_from_ofp11_match(const struct ofp11_match *match,
         }
     }
 
-    if (rule->flow.dl_type == htons(ETH_TYPE_MPLS) ||
-        rule->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
+    if (match->flow.dl_type == htons(ETH_TYPE_MPLS) ||
+        match->flow.dl_type == htons(ETH_TYPE_MPLS_MCAST)) {
         enum { OFPFW11_MPLS_ALL = OFPFW11_MPLS_LABEL | OFPFW11_MPLS_TC };
 
         if ((wc & OFPFW11_MPLS_ALL) != OFPFW11_MPLS_ALL) {
@@ -412,105 +412,103 @@ ofputil_cls_rule_from_ofp11_match(const struct ofp11_match *match,
         }
     }
 
-    if (match->metadata_mask != htonll(UINT64_MAX)) {
-        cls_rule_set_metadata_masked(rule, match->metadata,
-                                     ~match->metadata_mask);
-    }
+    match_set_metadata_masked(match, ofmatch->metadata,
+                              ~ofmatch->metadata_mask);
 
     return 0;
 }
 
-/* Convert 'rule' into the OpenFlow 1.1 match structure 'match'. */
+/* Convert 'match' into the OpenFlow 1.1 match structure 'ofmatch'. */
 void
-ofputil_cls_rule_to_ofp11_match(const struct cls_rule *rule,
-                                struct ofp11_match *match)
+ofputil_match_to_ofp11_match(const struct match *match,
+                             struct ofp11_match *ofmatch)
 {
     uint32_t wc = 0;
     int i;
 
-    memset(match, 0, sizeof *match);
-    match->omh.type = htons(OFPMT_STANDARD);
-    match->omh.length = htons(OFPMT11_STANDARD_LENGTH);
+    memset(ofmatch, 0, sizeof *ofmatch);
+    ofmatch->omh.type = htons(OFPMT_STANDARD);
+    ofmatch->omh.length = htons(OFPMT11_STANDARD_LENGTH);
 
-    if (!rule->wc.masks.in_port) {
+    if (!match->wc.masks.in_port) {
         wc |= OFPFW11_IN_PORT;
     } else {
-        match->in_port = ofputil_port_to_ofp11(rule->flow.in_port);
+        ofmatch->in_port = ofputil_port_to_ofp11(match->flow.in_port);
     }
 
-    memcpy(match->dl_src, rule->flow.dl_src, ETH_ADDR_LEN);
+    memcpy(ofmatch->dl_src, match->flow.dl_src, ETH_ADDR_LEN);
     for (i = 0; i < ETH_ADDR_LEN; i++) {
-        match->dl_src_mask[i] = ~rule->wc.masks.dl_src[i];
+        ofmatch->dl_src_mask[i] = ~match->wc.masks.dl_src[i];
     }
 
-    memcpy(match->dl_dst, rule->flow.dl_dst, ETH_ADDR_LEN);
+    memcpy(ofmatch->dl_dst, match->flow.dl_dst, ETH_ADDR_LEN);
     for (i = 0; i < ETH_ADDR_LEN; i++) {
-        match->dl_dst_mask[i] = ~rule->wc.masks.dl_dst[i];
+        ofmatch->dl_dst_mask[i] = ~match->wc.masks.dl_dst[i];
     }
 
-    if (rule->wc.masks.vlan_tci == htons(0)) {
+    if (match->wc.masks.vlan_tci == htons(0)) {
         wc |= OFPFW11_DL_VLAN | OFPFW11_DL_VLAN_PCP;
-    } else if (rule->wc.masks.vlan_tci & htons(VLAN_CFI)
-               && !(rule->flow.vlan_tci & htons(VLAN_CFI))) {
-        match->dl_vlan = htons(OFPVID11_NONE);
+    } else if (match->wc.masks.vlan_tci & htons(VLAN_CFI)
+               && !(match->flow.vlan_tci & htons(VLAN_CFI))) {
+        ofmatch->dl_vlan = htons(OFPVID11_NONE);
         wc |= OFPFW11_DL_VLAN_PCP;
     } else {
-        if (!(rule->wc.masks.vlan_tci & htons(VLAN_VID_MASK))) {
-            match->dl_vlan = htons(OFPVID11_ANY);
+        if (!(match->wc.masks.vlan_tci & htons(VLAN_VID_MASK))) {
+            ofmatch->dl_vlan = htons(OFPVID11_ANY);
         } else {
-            match->dl_vlan = htons(vlan_tci_to_vid(rule->flow.vlan_tci));
+            ofmatch->dl_vlan = htons(vlan_tci_to_vid(match->flow.vlan_tci));
         }
 
-        if (!(rule->wc.masks.vlan_tci & htons(VLAN_PCP_MASK))) {
+        if (!(match->wc.masks.vlan_tci & htons(VLAN_PCP_MASK))) {
             wc |= OFPFW11_DL_VLAN_PCP;
         } else {
-            match->dl_vlan_pcp = vlan_tci_to_pcp(rule->flow.vlan_tci);
+            ofmatch->dl_vlan_pcp = vlan_tci_to_pcp(match->flow.vlan_tci);
         }
     }
 
-    if (!rule->wc.masks.dl_type) {
+    if (!match->wc.masks.dl_type) {
         wc |= OFPFW11_DL_TYPE;
     } else {
-        match->dl_type = ofputil_dl_type_to_openflow(rule->flow.dl_type);
+        ofmatch->dl_type = ofputil_dl_type_to_openflow(match->flow.dl_type);
     }
 
-    if (!(rule->wc.masks.nw_tos & IP_DSCP_MASK)) {
+    if (!(match->wc.masks.nw_tos & IP_DSCP_MASK)) {
         wc |= OFPFW11_NW_TOS;
     } else {
-        match->nw_tos = rule->flow.nw_tos & IP_DSCP_MASK;
+        ofmatch->nw_tos = match->flow.nw_tos & IP_DSCP_MASK;
     }
 
-    if (!rule->wc.masks.nw_proto) {
+    if (!match->wc.masks.nw_proto) {
         wc |= OFPFW11_NW_PROTO;
     } else {
-        match->nw_proto = rule->flow.nw_proto;
+        ofmatch->nw_proto = match->flow.nw_proto;
     }
 
-    match->nw_src = rule->flow.nw_src;
-    match->nw_src_mask = ~rule->wc.masks.nw_src;
-    match->nw_dst = rule->flow.nw_dst;
-    match->nw_dst_mask = ~rule->wc.masks.nw_dst;
+    ofmatch->nw_src = match->flow.nw_src;
+    ofmatch->nw_src_mask = ~match->wc.masks.nw_src;
+    ofmatch->nw_dst = match->flow.nw_dst;
+    ofmatch->nw_dst_mask = ~match->wc.masks.nw_dst;
 
-    if (!rule->wc.masks.tp_src) {
+    if (!match->wc.masks.tp_src) {
         wc |= OFPFW11_TP_SRC;
     } else {
-        match->tp_src = rule->flow.tp_src;
+        ofmatch->tp_src = match->flow.tp_src;
     }
 
-    if (!rule->wc.masks.tp_dst) {
+    if (!match->wc.masks.tp_dst) {
         wc |= OFPFW11_TP_DST;
     } else {
-        match->tp_dst = rule->flow.tp_dst;
+        ofmatch->tp_dst = match->flow.tp_dst;
     }
 
     /* MPLS not supported. */
     wc |= OFPFW11_MPLS_LABEL;
     wc |= OFPFW11_MPLS_TC;
 
-    match->metadata = rule->flow.metadata;
-    match->metadata_mask = ~rule->wc.masks.metadata;
+    ofmatch->metadata = match->flow.metadata;
+    ofmatch->metadata_mask = ~match->wc.masks.metadata;
 
-    match->wildcards = htonl(wc);
+    ofmatch->wildcards = htonl(wc);
 }
 
 /* Given a 'dl_type' value in the format used in struct flow, returns the
@@ -1448,14 +1446,14 @@ regs_fully_wildcarded(const struct flow_wildcards *wc)
     return true;
 }
 
-/* Returns a bit-mask of ofputil_protocols that can be used for sending 'rule'
+/* Returns a bit-mask of ofputil_protocols that can be used for sending 'match'
  * to a switch (e.g. to add or remove a flow).  Only NXM can handle tunnel IDs,
  * registers, or fixing the Ethernet multicast bit.  Otherwise, it's better to
  * use OpenFlow 1.0 protocol for backward compatibility. */
 enum ofputil_protocol
-ofputil_usable_protocols(const struct cls_rule *rule)
+ofputil_usable_protocols(const struct match *match)
 {
-    const struct flow_wildcards *wc = &rule->wc;
+    const struct flow_wildcards *wc = &match->wc;
 
     BUILD_ASSERT_DECL(FLOW_WC_SEQ == 17);
 
@@ -1481,7 +1479,7 @@ ofputil_usable_protocols(const struct cls_rule *rule)
     }
 
     /* Only NXM supports matching IPv6 traffic. */
-    if (rule->flow.dl_type == htons(ETH_TYPE_IPV6)) {
+    if (match->flow.dl_type == htons(ETH_TYPE_IPV6)) {
         return OFPUTIL_P_NXM_ANY;
     }
 
@@ -1682,24 +1680,16 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
     if (ofputil_msg_type_code(type) == OFPUTIL_OFPT_FLOW_MOD) {
         /* Standard OpenFlow flow_mod. */
         const struct ofp_flow_mod *ofm;
-        uint16_t priority;
         enum ofperr error;
 
         /* Get the ofp_flow_mod. */
         ofm = ofpbuf_pull(&b, sizeof *ofm);
 
-        /* Set priority based on original wildcards.  Normally we'd allow
-         * ofputil_cls_rule_from_match() to do this for us, but
-         * ofputil_normalize_rule() can put wildcards where the original flow
-         * didn't have them. */
-        priority = ntohs(ofm->priority);
-        if (!(ofm->match.wildcards & htonl(OFPFW10_ALL))) {
-            priority = UINT16_MAX;
-        }
-
         /* Translate the rule. */
-        ofputil_cls_rule_from_ofp10_match(&ofm->match, priority, &fm->cr);
-        ofputil_normalize_rule(&fm->cr);
+        fm->priority = ofputil_match_from_ofp10_match(&ofm->match,
+                                                      ntohs(ofm->priority),
+                                                      &fm->match);
+        ofputil_normalize_match(&fm->match);
 
         /* Now get the actions. */
         error = ofpacts_pull_openflow10(&b, b.size, ofpacts);
@@ -1724,8 +1714,8 @@ 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), ntohs(nfm->priority),
-                              &fm->cr, &fm->cookie, &fm->cookie_mask);
+        error = nx_pull_match(&b, ntohs(nfm->match_len), &fm->match,
+                              &fm->cookie, &fm->cookie_mask);
         if (error) {
             return error;
         }
@@ -1735,6 +1725,7 @@ ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
         }
 
         /* Translate the message. */
+        fm->priority = ntohs(nfm->priority);
         command = ntohs(nfm->command);
         if ((command & 0xff) == OFPFC_ADD && fm->cookie_mask) {
             /* Flow additions may only set a new cookie, not match an
@@ -1785,12 +1776,12 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
     case OFPUTIL_P_OF10_TID:
         msg = ofpbuf_new(sizeof *ofm + fm->ofpacts_len);
         ofm = put_openflow(sizeof *ofm, OFPT_FLOW_MOD, msg);
-        ofputil_cls_rule_to_ofp10_match(&fm->cr, &ofm->match);
+        ofputil_match_to_ofp10_match(&fm->match, &ofm->match);
         ofm->cookie = fm->new_cookie;
         ofm->command = htons(command);
         ofm->idle_timeout = htons(fm->idle_timeout);
         ofm->hard_timeout = htons(fm->hard_timeout);
-        ofm->priority = htons(fm->cr.priority);
+        ofm->priority = htons(fm->priority);
         ofm->buffer_id = htonl(fm->buffer_id);
         ofm->out_port = htons(fm->out_port);
         ofm->flags = htons(fm->flags);
@@ -1803,12 +1794,12 @@ ofputil_encode_flow_mod(const struct ofputil_flow_mod *fm,
         nfm = msg->data;
         nfm->command = htons(command);
         nfm->cookie = fm->new_cookie;
-        match_len = nx_put_match(msg, false, &fm->cr,
+        match_len = nx_put_match(msg, false, &fm->match,
                                  fm->cookie, fm->cookie_mask);
         nfm = msg->data;
         nfm->idle_timeout = htons(fm->idle_timeout);
         nfm->hard_timeout = htons(fm->hard_timeout);
-        nfm->priority = htons(fm->cr.priority);
+        nfm->priority = htons(fm->priority);
         nfm->buffer_id = htonl(fm->buffer_id);
         nfm->out_port = htons(fm->out_port);
         nfm->flags = htons(fm->flags);
@@ -1842,7 +1833,7 @@ ofputil_flow_mod_usable_protocols(const struct ofputil_flow_mod *fms,
     for (i = 0; i < n_fms; i++) {
         const struct ofputil_flow_mod *fm = &fms[i];
 
-        usable_protocols &= ofputil_usable_protocols(&fm->cr);
+        usable_protocols &= ofputil_usable_protocols(&fm->match);
         if (fm->table_id != 0xff) {
             usable_protocols &= OFPUTIL_P_TID;
         }
@@ -1866,7 +1857,7 @@ ofputil_decode_ofpst_flow_request(struct ofputil_flow_stats_request *fsr,
         (const struct ofp_flow_stats_request *) oh;
 
     fsr->aggregate = aggregate;
-    ofputil_cls_rule_from_ofp10_match(&ofsr->match, 0, &fsr->match);
+    ofputil_match_from_ofp10_match(&ofsr->match, 0, &fsr->match);
     fsr->out_port = ntohs(ofsr->out_port);
     fsr->table_id = ofsr->table_id;
     fsr->cookie = fsr->cookie_mask = htonll(0);
@@ -1886,7 +1877,7 @@ ofputil_decode_nxst_flow_request(struct ofputil_flow_stats_request *fsr,
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
 
     nfsr = ofpbuf_pull(&b, sizeof *nfsr);
-    error = nx_pull_match(&b, ntohs(nfsr->match_len), 0, &fsr->match,
+    error = nx_pull_match(&b, ntohs(nfsr->match_len), &fsr->match,
                           &fsr->cookie, &fsr->cookie_mask);
     if (error) {
         return error;
@@ -1953,7 +1944,7 @@ ofputil_encode_flow_stats_request(const struct ofputil_flow_stats_request *fsr,
 
         type = fsr->aggregate ? OFPST_AGGREGATE : OFPST_FLOW;
         ofsr = ofputil_make_stats_request(sizeof *ofsr, type, 0, &msg);
-        ofputil_cls_rule_to_ofp10_match(&fsr->match, &ofsr->match);
+        ofputil_match_to_ofp10_match(&fsr->match, &ofsr->match);
         ofsr->table_id = fsr->table_id;
         ofsr->out_port = htons(fsr->out_port);
         break;
@@ -2068,8 +2059,9 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
         }
 
         fs->cookie = get_32aligned_be64(&ofs->cookie);
-        ofputil_cls_rule_from_ofp10_match(&ofs->match, ntohs(ofs->priority),
-                                          &fs->rule);
+        fs->priority = ofputil_match_from_ofp10_match(&ofs->match,
+                                                      ntohs(ofs->priority),
+                                                      &fs->match);
         fs->table_id = ofs->table_id;
         fs->duration_sec = ntohl(ofs->duration_sec);
         fs->duration_nsec = ntohl(ofs->duration_nsec);
@@ -2097,8 +2089,7 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
                          "claims invalid length %zu", match_len, length);
             return EINVAL;
         }
-        if (nx_pull_match(msg, match_len, ntohs(nfs->priority), &fs->rule,
-                          NULL, NULL)) {
+        if (nx_pull_match(msg, match_len, &fs->match, NULL, NULL)) {
             return EINVAL;
         }
 
@@ -2111,6 +2102,7 @@ ofputil_decode_flow_stats_reply(struct ofputil_flow_stats *fs,
         fs->table_id = nfs->table_id;
         fs->duration_sec = ntohl(nfs->duration_sec);
         fs->duration_nsec = ntohl(nfs->duration_nsec);
+        fs->priority = ntohs(nfs->priority);
         fs->idle_timeout = ntohs(nfs->idle_timeout);
         fs->hard_timeout = ntohs(nfs->hard_timeout);
         fs->idle_age = -1;
@@ -2166,10 +2158,10 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
         ofs->length = htons(reply->size - start_ofs);
         ofs->table_id = fs->table_id;
         ofs->pad = 0;
-        ofputil_cls_rule_to_ofp10_match(&fs->rule, &ofs->match);
+        ofputil_match_to_ofp10_match(&fs->match, &ofs->match);
         ofs->duration_sec = htonl(fs->duration_sec);
         ofs->duration_nsec = htonl(fs->duration_nsec);
-        ofs->priority = htons(fs->rule.priority);
+        ofs->priority = htons(fs->priority);
         ofs->idle_timeout = htons(fs->idle_timeout);
         ofs->hard_timeout = htons(fs->hard_timeout);
         memset(ofs->pad2, 0, sizeof ofs->pad2);
@@ -2183,7 +2175,7 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
         int match_len;
 
         ofpbuf_put_uninit(reply, sizeof *nfs);
-        match_len = nx_put_match(reply, false, &fs->rule, 0, 0);
+        match_len = nx_put_match(reply, false, &fs->match, 0, 0);
         ofpacts_put_openflow10(fs->ofpacts, fs->ofpacts_len, reply);
 
         nfs = ofpbuf_at_assert(reply, start_ofs, sizeof *nfs);
@@ -2192,7 +2184,7 @@ ofputil_append_flow_stats_reply(const struct ofputil_flow_stats *fs,
         nfs->pad = 0;
         nfs->duration_sec = htonl(fs->duration_sec);
         nfs->duration_nsec = htonl(fs->duration_nsec);
-        nfs->priority = htons(fs->rule.priority);
+        nfs->priority = htons(fs->priority);
         nfs->idle_timeout = htons(fs->idle_timeout);
         nfs->hard_timeout = htons(fs->hard_timeout);
         nfs->idle_age = htons(fs->idle_age < 0 ? 0
@@ -2261,8 +2253,9 @@ ofputil_decode_flow_removed(struct ofputil_flow_removed *fr,
         const struct ofp_flow_removed *ofr;
 
         ofr = (const struct ofp_flow_removed *) oh;
-        ofputil_cls_rule_from_ofp10_match(&ofr->match, ntohs(ofr->priority),
-                                          &fr->rule);
+        fr->priority = ofputil_match_from_ofp10_match(&ofr->match,
+                                                      ntohs(ofr->priority),
+                                                      &fr->match);
         fr->cookie = ofr->cookie;
         fr->reason = ofr->reason;
         fr->duration_sec = ntohl(ofr->duration_sec);
@@ -2278,8 +2271,8 @@ ofputil_decode_flow_removed(struct ofputil_flow_removed *fr,
         ofpbuf_use_const(&b, oh, ntohs(oh->length));
 
         nfr = ofpbuf_pull(&b, sizeof *nfr);
-        error = nx_pull_match(&b, ntohs(nfr->match_len), ntohs(nfr->priority),
-                              &fr->rule, NULL, NULL);
+        error = nx_pull_match(&b, ntohs(nfr->match_len), &fr->match,
+                              NULL, NULL);
         if (error) {
             return error;
         }
@@ -2287,6 +2280,7 @@ ofputil_decode_flow_removed(struct ofputil_flow_removed *fr,
             return OFPERR_OFPBRC_BAD_LEN;
         }
 
+        fr->priority = ntohs(nfr->priority);
         fr->cookie = nfr->cookie;
         fr->reason = nfr->reason;
         fr->duration_sec = ntohl(nfr->duration_sec);
@@ -2317,9 +2311,9 @@ ofputil_encode_flow_removed(const struct ofputil_flow_removed *fr,
 
         ofr = make_openflow_xid(sizeof *ofr, OFPT_FLOW_REMOVED, htonl(0),
                                 &msg);
-        ofputil_cls_rule_to_ofp10_match(&fr->rule, &ofr->match);
+        ofputil_match_to_ofp10_match(&fr->match, &ofr->match);
         ofr->cookie = fr->cookie;
-        ofr->priority = htons(fr->rule.priority);
+        ofr->priority = htons(fr->priority);
         ofr->reason = fr->reason;
         ofr->duration_sec = htonl(fr->duration_sec);
         ofr->duration_nsec = htonl(fr->duration_nsec);
@@ -2335,11 +2329,11 @@ ofputil_encode_flow_removed(const struct ofputil_flow_removed *fr,
         int match_len;
 
         make_nxmsg_xid(sizeof *nfr, NXT_FLOW_REMOVED, htonl(0), &msg);
-        match_len = nx_put_match(msg, false, &fr->rule, 0, 0);
+        match_len = nx_put_match(msg, false, &fr->match, 0, 0);
 
         nfr = msg->data;
         nfr->cookie = fr->cookie;
-        nfr->priority = htons(fr->rule.priority);
+        nfr->priority = htons(fr->priority);
         nfr->reason = fr->reason;
         nfr->duration_sec = htonl(fr->duration_sec);
         nfr->duration_nsec = htonl(fr->duration_nsec);
@@ -2381,14 +2375,14 @@ ofputil_decode_packet_in(struct ofputil_packet_in *pin,
         pin->total_len = ntohs(opi->total_len);
     } else if (code == OFPUTIL_NXT_PACKET_IN) {
         const struct nx_packet_in *npi;
-        struct cls_rule rule;
+        struct match match;
         struct ofpbuf b;
         int error;
 
         ofpbuf_use_const(&b, oh, ntohs(oh->length));
 
         npi = ofpbuf_pull(&b, sizeof *npi);
-        error = nx_pull_match_loose(&b, ntohs(npi->match_len), 0, &rule, NULL,
+        error = nx_pull_match_loose(&b, ntohs(npi->match_len), &match, NULL,
                                     NULL);
         if (error) {
             return error;
@@ -2404,16 +2398,16 @@ ofputil_decode_packet_in(struct ofputil_packet_in *pin,
         pin->table_id = npi->table_id;
         pin->cookie = npi->cookie;
 
-        pin->fmd.in_port = rule.flow.in_port;
+        pin->fmd.in_port = match.flow.in_port;
 
-        pin->fmd.tun_id = rule.flow.tun_id;
-        pin->fmd.tun_id_mask = rule.wc.masks.tun_id;
+        pin->fmd.tun_id = match.flow.tun_id;
+        pin->fmd.tun_id_mask = match.wc.masks.tun_id;
 
-        pin->fmd.metadata = rule.flow.metadata;
-        pin->fmd.metadata_mask = rule.wc.masks.metadata;
+        pin->fmd.metadata = match.flow.metadata;
+        pin->fmd.metadata_mask = match.wc.masks.metadata;
 
-        memcpy(pin->fmd.regs, rule.flow.regs, sizeof pin->fmd.regs);
-        memcpy(pin->fmd.reg_masks, rule.wc.masks.regs,
+        memcpy(pin->fmd.regs, match.flow.regs, sizeof pin->fmd.regs);
+        memcpy(pin->fmd.reg_masks, match.wc.masks.regs,
                sizeof pin->fmd.reg_masks);
 
         pin->buffer_id = ntohl(npi->buffer_id);
@@ -2451,7 +2445,7 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
         ofpbuf_put(packet, pin->packet, send_len);
     } else if (packet_in_format == NXPIF_NXM) {
         struct nx_packet_in *npi;
-        struct cls_rule rule;
+        struct match match;
         size_t match_len;
         size_t i;
 
@@ -2461,22 +2455,20 @@ ofputil_encode_packet_in(const struct ofputil_packet_in *pin,
         packet = ofpbuf_new(sizeof *npi + sizeof(struct flow_metadata) * 2
                             + 2 + send_len);
 
-        cls_rule_init_catchall(&rule, 0);
-        cls_rule_set_tun_id_masked(&rule, pin->fmd.tun_id,
-                                   pin->fmd.tun_id_mask);
-        cls_rule_set_metadata_masked(&rule, pin->fmd.metadata,
-                                   pin->fmd.metadata_mask);
-
+        match_init_catchall(&match);
+        match_set_tun_id_masked(&match, pin->fmd.tun_id, pin->fmd.tun_id_mask);
+        match_set_metadata_masked(&match, pin->fmd.metadata,
+                                  pin->fmd.metadata_mask);
 
         for (i = 0; i < FLOW_N_REGS; i++) {
-            cls_rule_set_reg_masked(&rule, i, pin->fmd.regs[i],
-                                    pin->fmd.reg_masks[i]);
+            match_set_reg_masked(&match, i, pin->fmd.regs[i],
+                                 pin->fmd.reg_masks[i]);
         }
 
-        cls_rule_set_in_port(&rule, pin->fmd.in_port);
+        match_set_in_port(&match, pin->fmd.in_port);
 
         ofpbuf_put_zeros(packet, sizeof *npi);
-        match_len = nx_put_match(packet, false, &rule, 0, 0);
+        match_len = nx_put_match(packet, false, &match, 0, 0);
         ofpbuf_put_zeros(packet, 2);
         ofpbuf_put(packet, pin->packet, send_len);
 
@@ -3163,8 +3155,7 @@ ofputil_decode_flow_monitor_request(struct ofputil_flow_monitor_request *rq,
     rq->out_port = ntohs(nfmr->out_port);
     rq->table_id = nfmr->table_id;
 
-    return nx_pull_match(msg, ntohs(nfmr->match_len), OFP_DEFAULT_PRIORITY,
-                         &rq->match, NULL, NULL);
+    return nx_pull_match(msg, ntohs(nfmr->match_len), &rq->match, NULL, NULL);
 }
 
 void
@@ -3269,9 +3260,9 @@ ofputil_decode_flow_update(struct ofputil_flow_update *update,
         update->hard_timeout = ntohs(nfuf->hard_timeout);
         update->table_id = nfuf->table_id;
         update->cookie = nfuf->cookie;
+        update->priority = ntohs(nfuf->priority);
 
-        error = nx_pull_match(msg, match_len, ntohs(nfuf->priority),
-                              update->match, NULL, NULL);
+        error = nx_pull_match(msg, match_len, update->match, NULL, NULL);
         if (error) {
             return error;
         }
@@ -3356,7 +3347,7 @@ ofputil_append_flow_update(const struct ofputil_flow_update *update,
 
         nfuf = ofpbuf_at_assert(msg, start_ofs, sizeof *nfuf);
         nfuf->reason = htons(update->reason);
-        nfuf->priority = htons(update->match->priority);
+        nfuf->priority = htons(update->priority);
         nfuf->idle_timeout = htons(update->idle_timeout);
         nfuf->hard_timeout = htons(update->hard_timeout);
         nfuf->match_len = htons(match_len);
@@ -4032,19 +4023,19 @@ ofputil_put_action(enum ofputil_action_code code, struct ofpbuf *buf)
  *
  *    1. If the type of level N is known, then only the valid fields for that
  *       level may be specified.  For example, ARP does not have a TOS field,
- *       so nw_tos must be wildcarded if 'rule' specifies an ARP flow.
+ *       so nw_tos must be wildcarded if 'match' specifies an ARP flow.
  *       Similarly, IPv4 does not have any IPv6 addresses, so ipv6_src and
- *       ipv6_dst (and other fields) must be wildcarded if 'rule' specifies an
+ *       ipv6_dst (and other fields) must be wildcarded if 'match' specifies an
  *       IPv4 flow.
  *
  *    2. If the type of level N is not known (or not understood by Open
  *       vSwitch), then no fields at all for that level may be specified.  For
  *       example, Open vSwitch does not understand SCTP, an L4 protocol, so the
- *       L4 fields tp_src and tp_dst must be wildcarded if 'rule' specifies an
+ *       L4 fields tp_src and tp_dst must be wildcarded if 'match' specifies an
  *       SCTP flow.
  */
 void
-ofputil_normalize_rule(struct cls_rule *rule)
+ofputil_normalize_match(struct match *match)
 {
     enum {
         MAY_NW_ADDR     = 1 << 0, /* nw_src, nw_dst */
@@ -4060,34 +4051,34 @@ ofputil_normalize_rule(struct cls_rule *rule)
     struct flow_wildcards wc;
 
     /* Figure out what fields may be matched. */
-    if (rule->flow.dl_type == htons(ETH_TYPE_IP)) {
+    if (match->flow.dl_type == htons(ETH_TYPE_IP)) {
         may_match = MAY_NW_PROTO | MAY_IPVx | MAY_NW_ADDR;
-        if (rule->flow.nw_proto == IPPROTO_TCP ||
-            rule->flow.nw_proto == IPPROTO_UDP ||
-            rule->flow.nw_proto == IPPROTO_ICMP) {
+        if (match->flow.nw_proto == IPPROTO_TCP ||
+            match->flow.nw_proto == IPPROTO_UDP ||
+            match->flow.nw_proto == IPPROTO_ICMP) {
             may_match |= MAY_TP_ADDR;
         }
-    } else if (rule->flow.dl_type == htons(ETH_TYPE_IPV6)) {
+    } else if (match->flow.dl_type == htons(ETH_TYPE_IPV6)) {
         may_match = MAY_NW_PROTO | MAY_IPVx | MAY_IPV6;
-        if (rule->flow.nw_proto == IPPROTO_TCP ||
-            rule->flow.nw_proto == IPPROTO_UDP) {
+        if (match->flow.nw_proto == IPPROTO_TCP ||
+            match->flow.nw_proto == IPPROTO_UDP) {
             may_match |= MAY_TP_ADDR;
-        } else if (rule->flow.nw_proto == IPPROTO_ICMPV6) {
+        } else if (match->flow.nw_proto == IPPROTO_ICMPV6) {
             may_match |= MAY_TP_ADDR;
-            if (rule->flow.tp_src == htons(ND_NEIGHBOR_SOLICIT)) {
+            if (match->flow.tp_src == htons(ND_NEIGHBOR_SOLICIT)) {
                 may_match |= MAY_ND_TARGET | MAY_ARP_SHA;
-            } else if (rule->flow.tp_src == htons(ND_NEIGHBOR_ADVERT)) {
+            } else if (match->flow.tp_src == htons(ND_NEIGHBOR_ADVERT)) {
                 may_match |= MAY_ND_TARGET | MAY_ARP_THA;
             }
         }
-    } else if (rule->flow.dl_type == htons(ETH_TYPE_ARP)) {
+    } else if (match->flow.dl_type == htons(ETH_TYPE_ARP)) {
         may_match = MAY_NW_PROTO | MAY_NW_ADDR | MAY_ARP_SHA | MAY_ARP_THA;
     } else {
         may_match = 0;
     }
 
     /* Clear the fields that may not be matched. */
-    wc = rule->wc;
+    wc = match->wc;
     if (!(may_match & MAY_NW_ADDR)) {
         wc.masks.nw_src = wc.masks.nw_dst = htonl(0);
     }
@@ -4116,15 +4107,15 @@ ofputil_normalize_rule(struct cls_rule *rule)
     }
 
     /* Log any changes. */
-    if (!flow_wildcards_equal(&wc, &rule->wc)) {
+    if (!flow_wildcards_equal(&wc, &match->wc)) {
         bool log = !VLOG_DROP_INFO(&bad_ofmsg_rl);
-        char *pre = log ? cls_rule_to_string(rule) : NULL;
+        char *pre = log ? match_to_string(match, OFP_DEFAULT_PRIORITY) : NULL;
 
-        rule->wc = wc;
-        cls_rule_zero_wildcarded_fields(rule);
+        match->wc = wc;
+        match_zero_wildcarded_fields(match);
 
         if (log) {
-            char *post = cls_rule_to_string(rule);
+            char *post = match_to_string(match, OFP_DEFAULT_PRIORITY);
             VLOG_INFO("normalization changed ofp_match, details:");
             VLOG_INFO(" pre: %s", pre);
             VLOG_INFO("post: %s", post);
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index f7d3307..2aec75b 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -24,6 +24,7 @@
 #include "classifier.h"
 #include "compiler.h"
 #include "flow.h"
+#include "match.h"
 #include "netdev.h"
 #include "openflow/nicira-ext.h"
 #include "openvswitch/types.h"
@@ -171,7 +172,7 @@ enum ofputil_protocol ofputil_protocol_set_base(
 const char *ofputil_protocol_to_string(enum ofputil_protocol);
 char *ofputil_protocols_to_string(enum ofputil_protocol);
 enum ofputil_protocol ofputil_protocols_from_string(const char *);
-enum ofputil_protocol ofputil_usable_protocols(const struct cls_rule *);
+enum ofputil_protocol ofputil_usable_protocols(const struct match *);
 
 struct ofpbuf *ofputil_encode_set_protocol(enum ofputil_protocol current,
                                            enum ofputil_protocol want,
@@ -185,19 +186,17 @@ const char *ofputil_nx_flow_format_to_string(enum nx_flow_format);
 
 /* Work with ofp10_match. */
 void ofputil_wildcard_from_ofpfw10(uint32_t ofpfw, struct flow_wildcards *);
-void ofputil_cls_rule_from_ofp10_match(const struct ofp10_match *,
-                                       unsigned int priority,
-                                       struct cls_rule *);
-void ofputil_normalize_rule(struct cls_rule *);
-void ofputil_cls_rule_to_ofp10_match(const struct cls_rule *,
-                                     struct ofp10_match *);
+unsigned int ofputil_match_from_ofp10_match(const struct ofp10_match *,
+                                            unsigned int priority,
+                                            struct match *);
+void ofputil_normalize_match(struct match *);
+void ofputil_match_to_ofp10_match(const struct match *, struct ofp10_match *);
+
 
 /* Work with ofp11_match. */
-enum ofperr ofputil_cls_rule_from_ofp11_match(const struct ofp11_match *,
-                                              unsigned int priority,
-                                              struct cls_rule *);
-void ofputil_cls_rule_to_ofp11_match(const struct cls_rule *,
-                                     struct ofp11_match *);
+enum ofperr ofputil_match_from_ofp11_match(const struct ofp11_match *,
+                                           struct match *);
+void ofputil_match_to_ofp11_match(const struct match *, struct ofp11_match *);
 
 /* dl_type translation between OpenFlow and 'struct flow' format. */
 ovs_be16 ofputil_dl_type_to_openflow(ovs_be16 flow_dl_type);
@@ -231,7 +230,8 @@ struct ofpbuf *ofputil_make_flow_mod_table_id(bool flow_mod_table_id);
  * NXM Delete    <used>     <used>        -
  */
 struct ofputil_flow_mod {
-    struct cls_rule cr;
+    struct match match;
+    unsigned int priority;
     ovs_be64 cookie;         /* Cookie bits to match. */
     ovs_be64 cookie_mask;    /* 1-bit in each 'cookie' bit to match. */
     ovs_be64 new_cookie;     /* New cookie to install or -1. */
@@ -259,7 +259,7 @@ enum ofputil_protocol ofputil_flow_mod_usable_protocols(
 /* Flow stats or aggregate stats request, independent of protocol. */
 struct ofputil_flow_stats_request {
     bool aggregate;             /* Aggregate results? */
-    struct cls_rule match;
+    struct match match;
     ovs_be64 cookie;
     ovs_be64 cookie_mask;
     uint16_t out_port;
@@ -275,11 +275,12 @@ enum ofputil_protocol ofputil_flow_stats_request_usable_protocols(
 
 /* Flow stats reply, independent of protocol. */
 struct ofputil_flow_stats {
-    struct cls_rule rule;
+    struct match match;
     ovs_be64 cookie;
     uint8_t table_id;
     uint32_t duration_sec;
     uint32_t duration_nsec;
+    uint16_t priority;
     uint16_t idle_timeout;
     uint16_t hard_timeout;
     int idle_age;               /* Seconds since last packet, -1 if unknown. */
@@ -310,7 +311,8 @@ struct ofpbuf *ofputil_encode_aggregate_stats_reply(
 
 /* Flow removed message, independent of protocol. */
 struct ofputil_flow_removed {
-    struct cls_rule rule;
+    struct match match;
+    uint16_t priority;
     ovs_be64 cookie;
     uint8_t reason;             /* One of OFPRR_*. */
     uint32_t duration_sec;
@@ -517,7 +519,7 @@ struct ofputil_flow_monitor_request {
     enum nx_flow_monitor_flags flags;
     uint16_t out_port;
     uint8_t table_id;
-    struct cls_rule match;
+    struct match match;
 };
 
 int ofputil_decode_flow_monitor_request(struct ofputil_flow_monitor_request *,
@@ -535,7 +537,8 @@ struct ofputil_flow_update {
     uint16_t hard_timeout;
     uint8_t table_id;
     ovs_be64 cookie;
-    struct cls_rule *match;
+    struct match *match;
+    uint16_t priority;
     struct ofpact *ofpacts;
     size_t ofpacts_len;
 
diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c
index a7826b2..4270256 100644
--- a/ofproto/connmgr.c
+++ b/ofproto/connmgr.c
@@ -1599,14 +1599,14 @@ connmgr_flushed(struct connmgr *mgr)
     if (!connmgr_has_controllers(mgr)
         && mgr->fail_mode == OFPROTO_FAIL_STANDALONE) {
         struct ofpbuf ofpacts;
-        struct cls_rule rule;
+        struct match match;
 
         ofpbuf_init(&ofpacts, OFPACT_OUTPUT_SIZE);
         ofpact_put_OUTPUT(&ofpacts)->port = OFPP_NORMAL;
         ofpact_pad(&ofpacts);
 
-        cls_rule_init_catchall(&rule, 0);
-        ofproto_add_flow(mgr->ofproto, &rule, ofpacts.data, ofpacts.size);
+        match_init_catchall(&match);
+        ofproto_add_flow(mgr->ofproto, &match, 0, ofpacts.data, ofpacts.size);
 
         ofpbuf_uninit(&ofpacts);
     }
@@ -1811,7 +1811,7 @@ ofmonitor_report(struct connmgr *mgr, struct rule *rule,
                 fu.hard_timeout = rule->hard_timeout;
                 fu.table_id = rule->table_id;
                 fu.cookie = rule->flow_cookie;
-                fu.match = &rule->cr;
+                fu.match = &rule->cr.match;
                 if (flags & NXFMF_ACTIONS) {
                     fu.ofpacts = rule->ofpacts;
                     fu.ofpacts_len = rule->ofpacts_len;
diff --git a/ofproto/connmgr.h b/ofproto/connmgr.h
index 24a33fb..00dd371 100644
--- a/ofproto/connmgr.h
+++ b/ofproto/connmgr.h
@@ -20,6 +20,7 @@
 #include "classifier.h"
 #include "hmap.h"
 #include "list.h"
+#include "match.h"
 #include "ofp-errors.h"
 #include "ofproto.h"
 #include "openflow/nicira-ext.h"
@@ -172,7 +173,7 @@ struct ofmonitor {
     /* Matching. */
     uint16_t out_port;
     uint8_t table_id;
-    struct cls_rule match;
+    struct match match;
 };
 
 struct ofputil_flow_monitor_request;
diff --git a/ofproto/fail-open.c b/ofproto/fail-open.c
index 495197e..2c0a8f3 100644
--- a/ofproto/fail-open.c
+++ b/ofproto/fail-open.c
@@ -191,14 +191,14 @@ fail_open_maybe_recover(struct fail_open *fo)
 static void
 fail_open_recover(struct fail_open *fo)
 {
-    struct cls_rule rule;
+    struct match match;
 
     VLOG_WARN("No longer in fail-open mode");
     fo->last_disconn_secs = 0;
     fo->next_bogus_packet_in = LLONG_MAX;
 
-    cls_rule_init_catchall(&rule, FAIL_OPEN_PRIORITY);
-    ofproto_delete_flow(fo->ofproto, &rule);
+    match_init_catchall(&match);
+    ofproto_delete_flow(fo->ofproto, &match, FAIL_OPEN_PRIORITY);
 }
 
 void
@@ -216,7 +216,7 @@ fail_open_flushed(struct fail_open *fo)
     bool open = disconn_secs >= trigger_duration(fo);
     if (open) {
         struct ofpbuf ofpacts;
-        struct cls_rule rule;
+        struct match match;
 
         /* Set up a flow that matches every packet and directs them to
          * OFPP_NORMAL. */
@@ -224,8 +224,9 @@ fail_open_flushed(struct fail_open *fo)
         ofpact_put_OUTPUT(&ofpacts)->port = OFPP_NORMAL;
         ofpact_pad(&ofpacts);
 
-        cls_rule_init_catchall(&rule, FAIL_OPEN_PRIORITY);
-        ofproto_add_flow(fo->ofproto, &rule, ofpacts.data, ofpacts.size);
+        match_init_catchall(&match);
+        ofproto_add_flow(fo->ofproto, &match, FAIL_OPEN_PRIORITY,
+                         ofpacts.data, ofpacts.size);
 
         ofpbuf_uninit(&ofpacts);
     }
diff --git a/ofproto/in-band.c b/ofproto/in-band.c
index 43461ad..0e4754c 100644
--- a/ofproto/in-band.c
+++ b/ofproto/in-band.c
@@ -81,7 +81,9 @@ enum in_band_op {
 
 /* A rule to add to or delete from ofproto's flow table.  */
 struct in_band_rule {
-    struct cls_rule cls_rule;
+    struct hmap_node hmap_node; /* In struct in_band's "rules" hmap. */
+    struct match match;
+    unsigned int priority;
     enum in_band_op op;
 };
 
@@ -279,22 +281,23 @@ in_band_rule_check(const struct flow *flow,
 }
 
 static void
-add_rule(struct in_band *ib, const struct cls_rule *cls_rule)
+add_rule(struct in_band *ib, const struct match *match, unsigned int priority)
 {
-    uint32_t hash = cls_rule_hash(cls_rule, 0);
+    uint32_t hash = match_hash(match, 0);
     struct in_band_rule *rule;
 
-    HMAP_FOR_EACH_WITH_HASH (rule, cls_rule.hmap_node, hash, &ib->rules) {
-        if (cls_rule_equal(&rule->cls_rule, cls_rule)) {
+    HMAP_FOR_EACH_WITH_HASH (rule, hmap_node, hash, &ib->rules) {
+        if (match_equal(&rule->match, match)) {
             rule->op = ADD;
             return;
         }
     }
 
     rule = xmalloc(sizeof *rule);
-    rule->cls_rule = *cls_rule;
+    rule->match = *match;
+    rule->priority = priority;
     rule->op = ADD;
-    hmap_insert(&ib->rules, &rule->cls_rule.hmap_node, hash);
+    hmap_insert(&ib->rules, &rule->hmap_node, hash);
 }
 
 static void
@@ -302,38 +305,38 @@ update_rules(struct in_band *ib)
 {
     struct in_band_rule *ib_rule;
     struct in_band_remote *r;
-    struct cls_rule rule;
+    struct match match;
 
     /* Mark all the existing rules for deletion.  (Afterward we will re-add any
      * rules that are still valid.) */
-    HMAP_FOR_EACH (ib_rule, cls_rule.hmap_node, &ib->rules) {
+    HMAP_FOR_EACH (ib_rule, hmap_node, &ib->rules) {
         ib_rule->op = DELETE;
     }
 
     if (ib->n_remotes && !eth_addr_is_zero(ib->local_mac)) {
         /* (a) Allow DHCP requests sent from the local port. */
-        cls_rule_init_catchall(&rule, IBR_FROM_LOCAL_DHCP);
-        cls_rule_set_in_port(&rule, OFPP_LOCAL);
-        cls_rule_set_dl_type(&rule, htons(ETH_TYPE_IP));
-        cls_rule_set_dl_src(&rule, ib->local_mac);
-        cls_rule_set_nw_proto(&rule, IPPROTO_UDP);
-        cls_rule_set_tp_src(&rule, htons(DHCP_CLIENT_PORT));
-        cls_rule_set_tp_dst(&rule, htons(DHCP_SERVER_PORT));
-        add_rule(ib, &rule);
+        match_init_catchall(&match);
+        match_set_in_port(&match, OFPP_LOCAL);
+        match_set_dl_type(&match, htons(ETH_TYPE_IP));
+        match_set_dl_src(&match, ib->local_mac);
+        match_set_nw_proto(&match, IPPROTO_UDP);
+        match_set_tp_src(&match, htons(DHCP_CLIENT_PORT));
+        match_set_tp_dst(&match, htons(DHCP_SERVER_PORT));
+        add_rule(ib, &match, IBR_FROM_LOCAL_DHCP);
 
         /* (b) Allow ARP replies to the local port's MAC address. */
-        cls_rule_init_catchall(&rule, IBR_TO_LOCAL_ARP);
-        cls_rule_set_dl_type(&rule, htons(ETH_TYPE_ARP));
-        cls_rule_set_dl_dst(&rule, ib->local_mac);
-        cls_rule_set_nw_proto(&rule, ARP_OP_REPLY);
-        add_rule(ib, &rule);
+        match_init_catchall(&match);
+        match_set_dl_type(&match, htons(ETH_TYPE_ARP));
+        match_set_dl_dst(&match, ib->local_mac);
+        match_set_nw_proto(&match, ARP_OP_REPLY);
+        add_rule(ib, &match, IBR_TO_LOCAL_ARP);
 
         /* (c) Allow ARP requests from the local port's MAC address.  */
-        cls_rule_init_catchall(&rule, IBR_FROM_LOCAL_ARP);
-        cls_rule_set_dl_type(&rule, htons(ETH_TYPE_ARP));
-        cls_rule_set_dl_src(&rule, ib->local_mac);
-        cls_rule_set_nw_proto(&rule, ARP_OP_REQUEST);
-        add_rule(ib, &rule);
+        match_init_catchall(&match);
+        match_set_dl_type(&match, htons(ETH_TYPE_ARP));
+        match_set_dl_src(&match, ib->local_mac);
+        match_set_nw_proto(&match, ARP_OP_REQUEST);
+        add_rule(ib, &match, IBR_FROM_LOCAL_ARP);
     }
 
     for (r = ib->remotes; r < &ib->remotes[ib->n_remotes]; r++) {
@@ -344,18 +347,18 @@ update_rules(struct in_band *ib)
         }
 
         /* (d) Allow ARP replies to the next hop's MAC address. */
-        cls_rule_init_catchall(&rule, IBR_TO_NEXT_HOP_ARP);
-        cls_rule_set_dl_type(&rule, htons(ETH_TYPE_ARP));
-        cls_rule_set_dl_dst(&rule, remote_mac);
-        cls_rule_set_nw_proto(&rule, ARP_OP_REPLY);
-        add_rule(ib, &rule);
+        match_init_catchall(&match);
+        match_set_dl_type(&match, htons(ETH_TYPE_ARP));
+        match_set_dl_dst(&match, remote_mac);
+        match_set_nw_proto(&match, ARP_OP_REPLY);
+        add_rule(ib, &match, IBR_TO_NEXT_HOP_ARP);
 
         /* (e) Allow ARP requests from the next hop's MAC address. */
-        cls_rule_init_catchall(&rule, IBR_FROM_NEXT_HOP_ARP);
-        cls_rule_set_dl_type(&rule, htons(ETH_TYPE_ARP));
-        cls_rule_set_dl_src(&rule, remote_mac);
-        cls_rule_set_nw_proto(&rule, ARP_OP_REQUEST);
-        add_rule(ib, &rule);
+        match_init_catchall(&match);
+        match_set_dl_type(&match, htons(ETH_TYPE_ARP));
+        match_set_dl_src(&match, remote_mac);
+        match_set_nw_proto(&match, ARP_OP_REQUEST);
+        add_rule(ib, &match, IBR_FROM_NEXT_HOP_ARP);
     }
 
     for (r = ib->remotes; r < &ib->remotes[ib->n_remotes]; r++) {
@@ -363,35 +366,35 @@ update_rules(struct in_band *ib)
 
         /* (f) Allow ARP replies containing the remote's IP address as a
          * target. */
-        cls_rule_init_catchall(&rule, IBR_TO_REMOTE_ARP);
-        cls_rule_set_dl_type(&rule, htons(ETH_TYPE_ARP));
-        cls_rule_set_nw_proto(&rule, ARP_OP_REPLY);
-        cls_rule_set_nw_dst(&rule, a->sin_addr.s_addr);
-        add_rule(ib, &rule);
+        match_init_catchall(&match);
+        match_set_dl_type(&match, htons(ETH_TYPE_ARP));
+        match_set_nw_proto(&match, ARP_OP_REPLY);
+        match_set_nw_dst(&match, a->sin_addr.s_addr);
+        add_rule(ib, &match, IBR_TO_REMOTE_ARP);
 
         /* (g) Allow ARP requests containing the remote's IP address as a
          * source. */
-        cls_rule_init_catchall(&rule, IBR_FROM_REMOTE_ARP);
-        cls_rule_set_dl_type(&rule, htons(ETH_TYPE_ARP));
-        cls_rule_set_nw_proto(&rule, ARP_OP_REQUEST);
-        cls_rule_set_nw_src(&rule, a->sin_addr.s_addr);
-        add_rule(ib, &rule);
+        match_init_catchall(&match);
+        match_set_dl_type(&match, htons(ETH_TYPE_ARP));
+        match_set_nw_proto(&match, ARP_OP_REQUEST);
+        match_set_nw_src(&match, a->sin_addr.s_addr);
+        add_rule(ib, &match, IBR_FROM_REMOTE_ARP);
 
         /* (h) Allow TCP traffic to the remote's IP and port. */
-        cls_rule_init_catchall(&rule, IBR_TO_REMOTE_TCP);
-        cls_rule_set_dl_type(&rule, htons(ETH_TYPE_IP));
-        cls_rule_set_nw_proto(&rule, IPPROTO_TCP);
-        cls_rule_set_nw_dst(&rule, a->sin_addr.s_addr);
-        cls_rule_set_tp_dst(&rule, a->sin_port);
-        add_rule(ib, &rule);
+        match_init_catchall(&match);
+        match_set_dl_type(&match, htons(ETH_TYPE_IP));
+        match_set_nw_proto(&match, IPPROTO_TCP);
+        match_set_nw_dst(&match, a->sin_addr.s_addr);
+        match_set_tp_dst(&match, a->sin_port);
+        add_rule(ib, &match, IBR_TO_REMOTE_TCP);
 
         /* (i) Allow TCP traffic from the remote's IP and port. */
-        cls_rule_init_catchall(&rule, IBR_FROM_REMOTE_TCP);
-        cls_rule_set_dl_type(&rule, htons(ETH_TYPE_IP));
-        cls_rule_set_nw_proto(&rule, IPPROTO_TCP);
-        cls_rule_set_nw_src(&rule, a->sin_addr.s_addr);
-        cls_rule_set_tp_src(&rule, a->sin_port);
-        add_rule(ib, &rule);
+        match_init_catchall(&match);
+        match_set_dl_type(&match, htons(ETH_TYPE_IP));
+        match_set_nw_proto(&match, IPPROTO_TCP);
+        match_set_nw_src(&match, a->sin_addr.s_addr);
+        match_set_tp_src(&match, a->sin_port);
+        add_rule(ib, &match, IBR_FROM_REMOTE_TCP);
     }
 }
 
@@ -420,18 +423,19 @@ in_band_run(struct in_band *ib)
 
     update_rules(ib);
 
-    HMAP_FOR_EACH_SAFE (rule, next, cls_rule.hmap_node, &ib->rules) {
+    HMAP_FOR_EACH_SAFE (rule, next, hmap_node, &ib->rules) {
         switch (rule->op) {
         case ADD:
-            ofproto_add_flow(ib->ofproto, &rule->cls_rule,
+            ofproto_add_flow(ib->ofproto, &rule->match, rule->priority,
                              ofpacts.data, ofpacts.size);
             break;
 
         case DELETE:
-            if (ofproto_delete_flow(ib->ofproto, &rule->cls_rule)) {
+            if (ofproto_delete_flow(ib->ofproto,
+                                    &rule->match, rule->priority)) {
                 /* ofproto doesn't have the rule anymore so there's no reason
                  * for us to track it any longer. */
-                hmap_remove(&ib->rules, &rule->cls_rule.hmap_node);
+                hmap_remove(&ib->rules, &rule->hmap_node);
                 free(rule);
             }
             break;
@@ -486,8 +490,8 @@ in_band_destroy(struct in_band *ib)
     if (ib) {
         struct in_band_rule *rule, *next;
 
-        HMAP_FOR_EACH_SAFE (rule, next, cls_rule.hmap_node, &ib->rules) {
-            hmap_remove(&ib->rules, &rule->cls_rule.hmap_node);
+        HMAP_FOR_EACH_SAFE (rule, next, hmap_node, &ib->rules) {
+            hmap_remove(&ib->rules, &rule->hmap_node);
             free(rule);
         }
         hmap_destroy(&ib->rules);
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index e21a6a2..297e62e 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -812,8 +812,9 @@ add_internal_flow(struct ofproto_dpif *ofproto, int id,
     struct ofputil_flow_mod fm;
     int error;
 
-    cls_rule_init_catchall(&fm.cr, 0);
-    cls_rule_set_reg(&fm.cr, 0, id);
+    match_init_catchall(&fm.match);
+    fm.priority = 0;
+    match_set_reg(&fm.match, 0, id);
     fm.new_cookie = htonll(0);
     fm.cookie = htonll(0);
     fm.cookie_mask = htonll(0);
@@ -834,7 +835,7 @@ add_internal_flow(struct ofproto_dpif *ofproto, int id,
         return error;
     }
 
-    *rulep = rule_dpif_lookup__(ofproto, &fm.cr.flow, TBL_INTERNAL);
+    *rulep = rule_dpif_lookup__(ofproto, &fm.match.flow, TBL_INTERNAL);
     assert(*rulep != NULL);
 
     return 0;
@@ -4620,7 +4621,8 @@ rule_construct(struct rule *rule_)
     table_id = rule->up.table_id;
     rule->tag = (victim ? victim->tag
                  : table_id == 0 ? 0
-                 : rule_calculate_tag(&rule->up.cr.flow, &rule->up.cr.wc,
+                 : rule_calculate_tag(&rule->up.cr.match.flow,
+                                      &rule->up.cr.match.wc,
                                       ofproto->tables[table_id].basis));
 
     complete_operation(rule);
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 80cd55f..91f5de2 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -29,6 +29,7 @@
 #include "shash.h"
 #include "timeval.h"
 
+struct match;
 struct ofpact;
 struct ofputil_flow_mod;
 struct simap;
@@ -701,24 +702,22 @@ struct ofproto_class {
 /* ## OpenFlow Rule Functions ## */
 /* ## ----------------------- ## */
 
-
-
-    /* Chooses an appropriate table for 'cls_rule' within 'ofproto'.  On
+    /* Chooses an appropriate table for 'match' within 'ofproto'.  On
      * success, stores the table ID into '*table_idp' and returns 0.  On
      * failure, returns an OpenFlow error code.
      *
-     * The choice of table should be a function of 'cls_rule' and 'ofproto''s
+     * The choice of table should be a function of 'match' and 'ofproto''s
      * datapath capabilities.  It should not depend on the flows already in
      * 'ofproto''s flow tables.  Failure implies that an OpenFlow rule with
-     * 'cls_rule' as its matching condition can never be inserted into
-     * 'ofproto', even starting from an empty flow table.
+     * 'match' as its matching condition can never be inserted into 'ofproto',
+     * even starting from an empty flow table.
      *
      * If multiple tables are candidates for inserting the flow, the function
      * should choose one arbitrarily (but deterministically).
      *
      * If this function is NULL then table 0 is always chosen. */
     enum ofperr (*rule_choose_table)(const struct ofproto *ofproto,
-                                     const struct cls_rule *cls_rule,
+                                     const struct match *match,
                                      uint8_t *table_idp);
 
     /* Life-cycle functions for a "struct rule" (see "Life Cycle" above).
@@ -1210,9 +1209,11 @@ enum { OFPROTO_POSTPONE = 1 << 16 };
 BUILD_ASSERT_DECL(OFPROTO_POSTPONE < OFPERR_OFS);
 
 int ofproto_flow_mod(struct ofproto *, const struct ofputil_flow_mod *);
-void ofproto_add_flow(struct ofproto *, const struct cls_rule *,
+void ofproto_add_flow(struct ofproto *, const struct match *,
+                      unsigned int priority,
                       const struct ofpact *ofpacts, size_t ofpacts_len);
-bool ofproto_delete_flow(struct ofproto *, const struct cls_rule *);
+bool ofproto_delete_flow(struct ofproto *,
+                         const struct match *, unsigned int priority);
 void ofproto_flush_flows(struct ofproto *);
 
 #endif /* ofproto/ofproto-provider.h */
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 73b2efb..e55e89f 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -1418,19 +1418,23 @@ ofproto_port_del(struct ofproto *ofproto, uint16_t ofp_port)
  *
  * This is a helper function for in-band control and fail-open. */
 void
-ofproto_add_flow(struct ofproto *ofproto, const struct cls_rule *cls_rule,
+ofproto_add_flow(struct ofproto *ofproto, const struct match *match,
+                 unsigned int priority,
                  const struct ofpact *ofpacts, size_t ofpacts_len)
 {
     const struct rule *rule;
+    struct cls_rule cr;
 
+    cls_rule_init(&cr, match, priority);
     rule = rule_from_cls_rule(classifier_find_rule_exactly(
-                                    &ofproto->tables[0].cls, cls_rule));
+                                    &ofproto->tables[0].cls, &cr));
     if (!rule || !ofpacts_equal(rule->ofpacts, rule->ofpacts_len,
                                 ofpacts, ofpacts_len)) {
         struct ofputil_flow_mod fm;
 
         memset(&fm, 0, sizeof fm);
-        fm.cr = *cls_rule;
+        fm.match = *match;
+        fm.priority = priority;
         fm.buffer_id = UINT32_MAX;
         fm.ofpacts = xmemdup(ofpacts, ofpacts_len);
         fm.ofpacts_len = ofpacts_len;
@@ -1455,12 +1459,15 @@ ofproto_flow_mod(struct ofproto *ofproto, const struct ofputil_flow_mod *fm)
  *
  * This is a helper function for in-band control and fail-open. */
 bool
-ofproto_delete_flow(struct ofproto *ofproto, const struct cls_rule *target)
+ofproto_delete_flow(struct ofproto *ofproto,
+                    const struct match *target, unsigned int priority)
 {
+    struct cls_rule cr;
     struct rule *rule;
 
+    cls_rule_init(&cr, target, priority);
     rule = rule_from_cls_rule(classifier_find_rule_exactly(
-                                  &ofproto->tables[0].cls, target));
+                                  &ofproto->tables[0].cls, &cr));
     if (!rule) {
         /* No such rule -> success. */
         return true;
@@ -2427,11 +2434,12 @@ next_matching_table(const struct ofproto *ofproto,
  * Returns 0 on success, otherwise an OpenFlow error code. */
 static enum ofperr
 collect_rules_loose(struct ofproto *ofproto, uint8_t table_id,
-                    const struct cls_rule *match,
+                    const struct match *match,
                     ovs_be64 cookie, ovs_be64 cookie_mask,
                     uint16_t out_port, struct list *rules)
 {
     struct oftable *table;
+    struct cls_rule cr;
     enum ofperr error;
 
     error = check_table_id(ofproto, table_id);
@@ -2440,11 +2448,12 @@ collect_rules_loose(struct ofproto *ofproto, uint8_t table_id,
     }
 
     list_init(rules);
+    cls_rule_init(&cr, match, 0);
     FOR_EACH_MATCHING_TABLE (table, table_id, ofproto) {
         struct cls_cursor cursor;
         struct rule *rule;
 
-        cls_cursor_init(&cursor, &table->cls, match);
+        cls_cursor_init(&cursor, &table->cls, &cr);
         CLS_CURSOR_FOR_EACH (rule, cr, &cursor) {
             if (rule->pending) {
                 return OFPROTO_POSTPONE;
@@ -2472,11 +2481,12 @@ collect_rules_loose(struct ofproto *ofproto, uint8_t table_id,
  * Returns 0 on success, otherwise an OpenFlow error code. */
 static enum ofperr
 collect_rules_strict(struct ofproto *ofproto, uint8_t table_id,
-                     const struct cls_rule *match,
+                     const struct match *match, unsigned int priority,
                      ovs_be64 cookie, ovs_be64 cookie_mask,
                      uint16_t out_port, struct list *rules)
 {
     struct oftable *table;
+    struct cls_rule cr;
     int error;
 
     error = check_table_id(ofproto, table_id);
@@ -2485,11 +2495,12 @@ collect_rules_strict(struct ofproto *ofproto, uint8_t table_id,
     }
 
     list_init(rules);
+    cls_rule_init(&cr, match, priority);
     FOR_EACH_MATCHING_TABLE (table, table_id, ofproto) {
         struct rule *rule;
 
         rule = rule_from_cls_rule(classifier_find_rule_exactly(&table->cls,
-                                                               match));
+                                                               &cr));
         if (rule) {
             if (rule->pending) {
                 return OFPROTO_POSTPONE;
@@ -2542,7 +2553,8 @@ handle_flow_stats_request(struct ofconn *ofconn,
         long long int now = time_msec();
         struct ofputil_flow_stats fs;
 
-        fs.rule = rule->cr;
+        fs.match = rule->cr.match;
+        fs.priority = rule->cr.priority;
         fs.cookie = rule->flow_cookie;
         fs.table_id = rule->table_id;
         calc_flow_duration__(rule->created, now, &fs.duration_sec,
@@ -2850,6 +2862,7 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
     struct oftable *table;
     struct ofopgroup *group;
     struct rule *victim;
+    struct cls_rule cr;
     struct rule *rule;
     int error;
 
@@ -2862,7 +2875,8 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
     if (fm->table_id == 0xff) {
         uint8_t table_id;
         if (ofproto->ofproto_class->rule_choose_table) {
-            error = ofproto->ofproto_class->rule_choose_table(ofproto, &fm->cr,
+            error = ofproto->ofproto_class->rule_choose_table(ofproto,
+                                                              &fm->match,
                                                               &table_id);
             if (error) {
                 return error;
@@ -2882,26 +2896,29 @@ add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
         return OFPERR_OFPBRC_EPERM;
     }
 
-    /* Check for overlap, if requested. */
-    if (fm->flags & OFPFF_CHECK_OVERLAP
-        && classifier_rule_overlaps(&table->cls, &fm->cr)) {
-        return OFPERR_OFPFMFC_OVERLAP;
+    /* Allocate new rule and initialize classifier rule. */
+    rule = ofproto->ofproto_class->rule_alloc();
+    if (!rule) {
+        VLOG_WARN_RL(&rl, "%s: failed to create rule (%s)",
+                     ofproto->name, strerror(error));
+        return ENOMEM;
     }
+    cls_rule_init(&rule->cr, &fm->match, fm->priority);
 
     /* Serialize against pending deletion. */
-    if (is_flow_deletion_pending(ofproto, &fm->cr, table - ofproto->tables)) {
+    if (is_flow_deletion_pending(ofproto, &cr, table - ofproto->tables)) {
+        ofproto->ofproto_class->rule_dealloc(rule);
         return OFPROTO_POSTPONE;
     }
 
-    /* Allocate new rule. */
-    rule = ofproto->ofproto_class->rule_alloc();
-    if (!rule) {
-        VLOG_WARN_RL(&rl, "%s: failed to create rule (%s)",
-                     ofproto->name, strerror(error));
-        return ENOMEM;
+    /* Check for overlap, if requested. */
+    if (fm->flags & OFPFF_CHECK_OVERLAP
+        && classifier_rule_overlaps(&table->cls, &rule->cr)) {
+        ofproto->ofproto_class->rule_dealloc(rule);
+        return OFPERR_OFPFMFC_OVERLAP;
     }
+
     rule->ofproto = ofproto;
-    rule->cr = fm->cr;
     rule->pending = NULL;
     rule->flow_cookie = fm->new_cookie;
     rule->created = rule->modified = rule->used = time_msec();
@@ -3041,7 +3058,7 @@ modify_flows_loose(struct ofproto *ofproto, struct ofconn *ofconn,
     struct list rules;
     int error;
 
-    error = collect_rules_loose(ofproto, fm->table_id, &fm->cr,
+    error = collect_rules_loose(ofproto, fm->table_id, &fm->match,
                                 fm->cookie, fm->cookie_mask,
                                 OFPP_NONE, &rules);
     if (error) {
@@ -3066,8 +3083,8 @@ modify_flow_strict(struct ofproto *ofproto, struct ofconn *ofconn,
     struct list rules;
     int error;
 
-    error = collect_rules_strict(ofproto, fm->table_id, &fm->cr,
-                                 fm->cookie, fm->cookie_mask,
+    error = collect_rules_strict(ofproto, fm->table_id, &fm->match,
+                                 fm->priority, fm->cookie, fm->cookie_mask,
                                  OFPP_NONE, &rules);
 
     if (error) {
@@ -3123,7 +3140,7 @@ delete_flows_loose(struct ofproto *ofproto, struct ofconn *ofconn,
     struct list rules;
     enum ofperr error;
 
-    error = collect_rules_loose(ofproto, fm->table_id, &fm->cr,
+    error = collect_rules_loose(ofproto, fm->table_id, &fm->match,
                                 fm->cookie, fm->cookie_mask,
                                 fm->out_port, &rules);
     return (error ? error
@@ -3141,8 +3158,8 @@ delete_flow_strict(struct ofproto *ofproto, struct ofconn *ofconn,
     struct list rules;
     enum ofperr error;
 
-    error = collect_rules_strict(ofproto, fm->table_id, &fm->cr,
-                                 fm->cookie, fm->cookie_mask,
+    error = collect_rules_strict(ofproto, fm->table_id, &fm->match,
+                                 fm->priority, fm->cookie, fm->cookie_mask,
                                  fm->out_port, &rules);
     return (error ? error
             : list_is_singleton(&rules) ? delete_flows__(ofproto, ofconn,
@@ -3159,7 +3176,8 @@ ofproto_rule_send_removed(struct rule *rule, uint8_t reason)
         return;
     }
 
-    fr.rule = rule->cr;
+    fr.match = rule->cr.match;
+    fr.priority = rule->cr.priority;
     fr.cookie = rule->flow_cookie;
     fr.reason = reason;
     calc_flow_duration__(rule->created, time_msec(),
@@ -3243,7 +3261,7 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
     }
     if (!error) {
         error = ofpacts_check(fm.ofpacts, fm.ofpacts_len,
-                              &fm.cr.flow, ofproto->max_ports);
+                              &fm.match.flow, ofproto->max_ports);
     }
     if (!error) {
         error = handle_flow_mod__(ofconn_get_ofproto(ofconn), ofconn, &fm, oh);
@@ -3484,7 +3502,7 @@ ofproto_compose_flow_refresh_update(const struct rule *rule,
     fu.hard_timeout = rule->hard_timeout;
     fu.table_id = rule->table_id;
     fu.cookie = rule->flow_cookie;
-    fu.match = CONST_CAST(struct cls_rule *, &rule->cr);
+    fu.match = CONST_CAST(struct match *, &rule->cr.match);
     if (!(flags & NXFMF_ACTIONS)) {
         fu.ofpacts = NULL;
         fu.ofpacts_len = 0;
@@ -3587,12 +3605,14 @@ ofproto_collect_ofmonitor_refresh_rules(const struct ofmonitor *m,
     const struct ofproto *ofproto = ofconn_get_ofproto(m->ofconn);
     const struct ofoperation *op;
     const struct oftable *table;
+    struct cls_rule target;
 
+    cls_rule_init(&target, &m->match, 0);
     FOR_EACH_MATCHING_TABLE (table, m->table_id, ofproto) {
         struct cls_cursor cursor;
         struct rule *rule;
 
-        cls_cursor_init(&cursor, &table->cls, &m->match);
+        cls_cursor_init(&cursor, &table->cls, &target);
         CLS_CURSOR_FOR_EACH (rule, cr, &cursor) {
             assert(!rule->pending); /* XXX */
             ofproto_collect_ofmonitor_refresh_rule(m, rule, seqno, rules);
@@ -3605,7 +3625,7 @@ ofproto_collect_ofmonitor_refresh_rules(const struct ofmonitor *m,
         if (((m->table_id == 0xff
               ? !(ofproto->tables[rule->table_id].flags & OFTABLE_HIDDEN)
               : m->table_id == rule->table_id))
-            && cls_rule_is_loose_match(&rule->cr, &m->match)) {
+            && cls_rule_is_loose_match(&rule->cr, &target.match)) {
             ofproto_collect_ofmonitor_refresh_rule(m, rule, seqno, rules);
         }
     }
@@ -3989,11 +4009,12 @@ ofopgroup_complete(struct ofopgroup *group)
         case OFOPERATION_ADD:
             if (!op->error) {
                 ofproto_rule_destroy__(op->victim);
-                if ((rule->cr.wc.masks.vlan_tci & htons(VLAN_VID_MASK))
+                if ((rule->cr.match.wc.masks.vlan_tci & htons(VLAN_VID_MASK))
                     == htons(VLAN_VID_MASK)) {
                     if (ofproto->vlan_bitmap) {
-                        uint16_t vid = vlan_tci_to_vid(rule->cr.flow.vlan_tci);
+                        uint16_t vid;
 
+                        vid = vlan_tci_to_vid(rule->cr.match.flow.vlan_tci);
                         if (!bitmap_is_set(ofproto->vlan_bitmap, vid)) {
                             bitmap_set1(ofproto->vlan_bitmap, vid);
                             ofproto->vlans_changed = true;
@@ -4334,10 +4355,10 @@ eviction_group_hash_rule(struct rule *rule)
          sf < &table->eviction_fields[table->n_eviction_fields];
          sf++)
     {
-        if (mf_are_prereqs_ok(sf->field, &rule->cr.flow)) {
+        if (mf_are_prereqs_ok(sf->field, &rule->cr.match.flow)) {
             union mf_value value;
 
-            mf_get_value(sf->field, &rule->cr.flow, &value);
+            mf_get_value(sf->field, &rule->cr.match.flow, &value);
             if (sf->ofs) {
                 bitwise_zero(&value, sf->field->n_bytes, 0, sf->ofs);
             }
@@ -4648,7 +4669,7 @@ ofproto_get_vlan_usage(struct ofproto *ofproto, unsigned long int *vlan_bitmap)
                 const struct cls_rule *rule;
 
                 HMAP_FOR_EACH (rule, hmap_node, &table->rules) {
-                    uint16_t vid = vlan_tci_to_vid(rule->flow.vlan_tci);
+                    uint16_t vid = vlan_tci_to_vid(rule->match.flow.vlan_tci);
                     bitmap_set1(vlan_bitmap, vid);
                     bitmap_set1(ofproto->vlan_bitmap, vid);
                 }
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index 3e99205..1492985 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -360,8 +360,8 @@ AT_CHECK([ovs-ofctl '-vPATTERN:console:%c|%p|%m' ofp-print "\
 OFPT_FLOW_MOD (xid=0x0): ADD priority=65535,arp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,arp_op=2 idle:5 buf:0x10e out_port:0 actions=output:3
 ], [dnl
 ofp_util|INFO|normalization changed ofp_match, details:
-ofp_util|INFO| pre: priority=65535,arp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,arp_op=2,nw_tos=0,tp_src=0,tp_dst=0
-ofp_util|INFO|post: priority=65535,arp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,arp_op=2
+ofp_util|INFO| pre: arp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,arp_op=2,nw_tos=0,tp_src=0,tp_dst=0
+ofp_util|INFO|post: arp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,arp_op=2
 ])
 AT_CLEANUP
 
@@ -379,8 +379,8 @@ AT_CHECK([ovs-ofctl '-vPATTERN:console:%c|%p|%m' ofp-print "\
 OFPT_FLOW_MOD (xid=0x0): ADD arp,in_port=1,dl_vlan=65535,dl_vlan_pcp=0,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,arp_op=2,nw_tos=0,tp_src=0,tp_dst=0 idle:5 pri:65535 buf:0x10e out_port:0 actions=output:3
 ], [dnl
 ofp_util|INFO|normalization changed ofp_match, details:
-ofp_util|INFO| pre: priority=65535,arp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,arp_op=2,nw_tos=0,tp_src=0,tp_dst=0
-ofp_util|INFO|post: priority=65535,arp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,arp_op=2
+ofp_util|INFO| pre: arp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,arp_op=2,nw_tos=0,tp_src=0,tp_dst=0
+ofp_util|INFO|post: arp,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:06,dl_dst=50:54:00:00:00:05,nw_src=192.168.0.2,nw_dst=192.168.0.1,arp_op=2
 ])
 AT_CLEANUP
 
diff --git a/tests/test-classifier.c b/tests/test-classifier.c
index 3ee6ddb..f279bda 100644
--- a/tests/test-classifier.c
+++ b/tests/test-classifier.c
@@ -186,44 +186,44 @@ match(const struct cls_rule *wild, const struct flow *fixed)
         bool eq;
 
         if (f_idx == CLS_F_IDX_NW_SRC) {
-            eq = !((fixed->nw_src ^ wild->flow.nw_src)
-                   & wild->wc.masks.nw_src);
+            eq = !((fixed->nw_src ^ wild->match.flow.nw_src)
+                   & wild->match.wc.masks.nw_src);
         } else if (f_idx == CLS_F_IDX_NW_DST) {
-            eq = !((fixed->nw_dst ^ wild->flow.nw_dst)
-                   & wild->wc.masks.nw_dst);
+            eq = !((fixed->nw_dst ^ wild->match.flow.nw_dst)
+                   & wild->match.wc.masks.nw_dst);
         } else if (f_idx == CLS_F_IDX_TP_SRC) {
-            eq = !((fixed->tp_src ^ wild->flow.tp_src)
-                   & wild->wc.masks.tp_src);
+            eq = !((fixed->tp_src ^ wild->match.flow.tp_src)
+                   & wild->match.wc.masks.tp_src);
         } else if (f_idx == CLS_F_IDX_TP_DST) {
-            eq = !((fixed->tp_dst ^ wild->flow.tp_dst)
-                   & wild->wc.masks.tp_dst);
+            eq = !((fixed->tp_dst ^ wild->match.flow.tp_dst)
+                   & wild->match.wc.masks.tp_dst);
         } else if (f_idx == CLS_F_IDX_DL_SRC) {
-            eq = eth_addr_equal_except(fixed->dl_src, wild->flow.dl_src,
-                                       wild->wc.masks.dl_src);
+            eq = eth_addr_equal_except(fixed->dl_src, wild->match.flow.dl_src,
+                                       wild->match.wc.masks.dl_src);
         } else if (f_idx == CLS_F_IDX_DL_DST) {
-            eq = eth_addr_equal_except(fixed->dl_dst, wild->flow.dl_dst,
-                                       wild->wc.masks.dl_dst);
+            eq = eth_addr_equal_except(fixed->dl_dst, wild->match.flow.dl_dst,
+                                       wild->match.wc.masks.dl_dst);
         } else if (f_idx == CLS_F_IDX_VLAN_TCI) {
-            eq = !((fixed->vlan_tci ^ wild->flow.vlan_tci)
-                   & wild->wc.masks.vlan_tci);
+            eq = !((fixed->vlan_tci ^ wild->match.flow.vlan_tci)
+                   & wild->match.wc.masks.vlan_tci);
         } else if (f_idx == CLS_F_IDX_TUN_ID) {
-            eq = !((fixed->tun_id ^ wild->flow.tun_id)
-                   & wild->wc.masks.tun_id);
+            eq = !((fixed->tun_id ^ wild->match.flow.tun_id)
+                   & wild->match.wc.masks.tun_id);
         } else if (f_idx == CLS_F_IDX_METADATA) {
-            eq = !((fixed->metadata ^ wild->flow.metadata)
-                   & wild->wc.masks.metadata);
+            eq = !((fixed->metadata ^ wild->match.flow.metadata)
+                   & wild->match.wc.masks.metadata);
         } else if (f_idx == CLS_F_IDX_NW_DSCP) {
-            eq = !((fixed->nw_tos ^ wild->flow.nw_tos) &
-                   (wild->wc.masks.nw_tos & IP_DSCP_MASK));
+            eq = !((fixed->nw_tos ^ wild->match.flow.nw_tos) &
+                   (wild->match.wc.masks.nw_tos & IP_DSCP_MASK));
         } else if (f_idx == CLS_F_IDX_NW_PROTO) {
-            eq = !((fixed->nw_proto ^ wild->flow.nw_proto)
-                   & wild->wc.masks.nw_proto);
+            eq = !((fixed->nw_proto ^ wild->match.flow.nw_proto)
+                   & wild->match.wc.masks.nw_proto);
         } else if (f_idx == CLS_F_IDX_DL_TYPE) {
-            eq = !((fixed->dl_type ^ wild->flow.dl_type)
-                   & wild->wc.masks.dl_type);
+            eq = !((fixed->dl_type ^ wild->match.flow.dl_type)
+                   & wild->match.wc.masks.dl_type);
         } else if (f_idx == CLS_F_IDX_IN_PORT) {
-            eq = !((fixed->in_port ^ wild->flow.in_port)
-                   & wild->wc.masks.in_port);
+            eq = !((fixed->in_port ^ wild->match.flow.in_port)
+                   & wild->match.wc.masks.in_port);
         } else {
             NOT_REACHED();
         }
@@ -256,8 +256,9 @@ tcls_delete_matches(struct tcls *cls, const struct cls_rule *target)
 
     for (i = 0; i < cls->n_rules; ) {
         struct test_rule *pos = cls->rules[i];
-        if (!flow_wildcards_has_extra(&pos->cls_rule.wc, &target->wc)
-            && match(target, &pos->cls_rule.flow)) {
+        if (!flow_wildcards_has_extra(&pos->cls_rule.match.wc,
+                                      &target->match.wc)
+            && match(target, &pos->cls_rule.match.flow)) {
             tcls_remove(cls, pos);
         } else {
             i++;
@@ -476,45 +477,48 @@ make_rule(int wc_fields, unsigned int priority, int value_pat)
 {
     const struct cls_field *f;
     struct test_rule *rule;
+    struct match match;
 
-    rule = xzalloc(sizeof *rule);
-    cls_rule_init_catchall(&rule->cls_rule, wc_fields ? priority : UINT_MAX);
+    match_init_catchall(&match);
     for (f = &cls_fields[0]; f < &cls_fields[CLS_N_FIELDS]; f++) {
         int f_idx = f - cls_fields;
         int value_idx = (value_pat & (1u << f_idx)) != 0;
-        memcpy((char *) &rule->cls_rule.flow + f->ofs,
+        memcpy((char *) &match.flow + f->ofs,
                values[f_idx][value_idx], f->len);
 
         if (f_idx == CLS_F_IDX_NW_SRC) {
-            rule->cls_rule.wc.masks.nw_src = htonl(UINT32_MAX);
+            match.wc.masks.nw_src = htonl(UINT32_MAX);
         } else if (f_idx == CLS_F_IDX_NW_DST) {
-            rule->cls_rule.wc.masks.nw_dst = htonl(UINT32_MAX);
+            match.wc.masks.nw_dst = htonl(UINT32_MAX);
         } else if (f_idx == CLS_F_IDX_TP_SRC) {
-            rule->cls_rule.wc.masks.tp_src = htons(UINT16_MAX);
+            match.wc.masks.tp_src = htons(UINT16_MAX);
         } else if (f_idx == CLS_F_IDX_TP_DST) {
-            rule->cls_rule.wc.masks.tp_dst = htons(UINT16_MAX);
+            match.wc.masks.tp_dst = htons(UINT16_MAX);
         } else if (f_idx == CLS_F_IDX_DL_SRC) {
-            memset(rule->cls_rule.wc.masks.dl_src, 0xff, ETH_ADDR_LEN);
+            memset(match.wc.masks.dl_src, 0xff, ETH_ADDR_LEN);
         } else if (f_idx == CLS_F_IDX_DL_DST) {
-            memset(rule->cls_rule.wc.masks.dl_dst, 0xff, ETH_ADDR_LEN);
+            memset(match.wc.masks.dl_dst, 0xff, ETH_ADDR_LEN);
         } else if (f_idx == CLS_F_IDX_VLAN_TCI) {
-            rule->cls_rule.wc.masks.vlan_tci = htons(UINT16_MAX);
+            match.wc.masks.vlan_tci = htons(UINT16_MAX);
         } else if (f_idx == CLS_F_IDX_TUN_ID) {
-            rule->cls_rule.wc.masks.tun_id = htonll(UINT64_MAX);
+            match.wc.masks.tun_id = htonll(UINT64_MAX);
         } else if (f_idx == CLS_F_IDX_METADATA) {
-            rule->cls_rule.wc.masks.metadata = htonll(UINT64_MAX);
+            match.wc.masks.metadata = htonll(UINT64_MAX);
         } else if (f_idx == CLS_F_IDX_NW_DSCP) {
-            rule->cls_rule.wc.masks.nw_tos |= IP_DSCP_MASK;
+            match.wc.masks.nw_tos |= IP_DSCP_MASK;
         } else if (f_idx == CLS_F_IDX_NW_PROTO) {
-            rule->cls_rule.wc.masks.nw_proto = UINT8_MAX;
+            match.wc.masks.nw_proto = UINT8_MAX;
         } else if (f_idx == CLS_F_IDX_DL_TYPE) {
-            rule->cls_rule.wc.masks.dl_type = htons(UINT16_MAX);
+            match.wc.masks.dl_type = htons(UINT16_MAX);
         } else if (f_idx == CLS_F_IDX_IN_PORT) {
-            rule->cls_rule.wc.masks.in_port = UINT16_MAX;
+            match.wc.masks.in_port = UINT16_MAX;
         } else {
             NOT_REACHED();
         }
     }
+
+    rule = xzalloc(sizeof *rule);
+    cls_rule_init(&rule->cls_rule, &match, wc_fields ? priority : UINT_MAX);
     return rule;
 }
 
diff --git a/tests/test-flows.c b/tests/test-flows.c
index 33417e0..a4d7c09 100644
--- a/tests/test-flows.c
+++ b/tests/test-flows.c
@@ -56,7 +56,7 @@ main(int argc OVS_UNUSED, char *argv[])
     while (fread(&expected_match, sizeof expected_match, 1, flows)) {
         struct ofpbuf *packet;
         struct ofp10_match extracted_match;
-        struct cls_rule rule;
+        struct match match;
         struct flow flow;
 
         n++;
@@ -69,8 +69,8 @@ main(int argc OVS_UNUSED, char *argv[])
         }
 
         flow_extract(packet, 0, 0, 1, &flow);
-        cls_rule_init_exact(&flow, 0, &rule);
-        ofputil_cls_rule_to_ofp10_match(&rule, &extracted_match);
+        match_init_exact(&match, &flow);
+        ofputil_match_to_ofp10_match(&match, &extracted_match);
 
         if (memcmp(&expected_match, &extracted_match, sizeof expected_match)) {
             char *exp_s = ofp10_match_to_string(&expected_match, 2);
@@ -80,7 +80,7 @@ main(int argc OVS_UNUSED, char *argv[])
             printf("Packet:\n");
             ofp_print_packet(stdout, packet->data, packet->size);
             ovs_hex_dump(stdout, packet->data, packet->size, 0, true);
-            cls_rule_print(&rule);
+            match_print(&match);
             printf("Expected flow:\n%s\n", exp_s);
             printf("Actually extracted flow:\n%s\n", got_s);
             ovs_hex_dump(stdout, &expected_match, sizeof expected_match, 0, false);
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index afaef13..7295cef 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -860,8 +860,8 @@ compare_flows(const void *afs_, const void *bfs_)
 {
     const struct ofputil_flow_stats *afs = afs_;
     const struct ofputil_flow_stats *bfs = bfs_;
-    const struct cls_rule *a = &afs->rule;
-    const struct cls_rule *b = &bfs->rule;
+    const struct match *a = &afs->match;
+    const struct match *b = &bfs->match;
     const struct sort_criterion *sc;
 
     for (sc = criteria; sc < &criteria[n_criteria]; sc++) {
@@ -869,7 +869,9 @@ compare_flows(const void *afs_, const void *bfs_)
         int ret;
 
         if (!f) {
-            ret = a->priority < b->priority ? -1 : a->priority > b->priority;
+            unsigned int a_pri = afs->priority;
+            unsigned int b_pri = bfs->priority;
+            ret = a_pri < b_pri ? -1 : a_pri > b_pri;
         } else {
             bool ina, inb;
 
@@ -1801,13 +1803,13 @@ fte_free_all(struct classifier *cls)
  *
  * Takes ownership of 'version'. */
 static void
-fte_insert(struct classifier *cls, const struct cls_rule *rule,
-           struct fte_version *version, int index)
+fte_insert(struct classifier *cls, const struct match *match,
+           unsigned int priority, struct fte_version *version, int index)
 {
     struct fte *old, *fte;
 
     fte = xzalloc(sizeof *fte);
-    fte->rule = *rule;
+    cls_rule_init(&fte->rule, match, priority);
     fte->versions[index] = version;
 
     old = fte_from_cls_rule(classifier_replace(cls, &fte->rule));
@@ -1849,9 +1851,9 @@ read_flows_from_file(const char *filename, struct classifier *cls, int index)
         version->ofpacts = fm.ofpacts;
         version->ofpacts_len = fm.ofpacts_len;
 
-        usable_protocols &= ofputil_usable_protocols(&fm.cr);
+        usable_protocols &= ofputil_usable_protocols(&fm.match);
 
-        fte_insert(cls, &fm.cr, version, index);
+        fte_insert(cls, &fm.match, fm.priority, version, index);
     }
     ds_destroy(&s);
 
@@ -1933,7 +1935,7 @@ read_flows_from_switch(struct vconn *vconn,
     ovs_be32 send_xid;
 
     fsr.aggregate = false;
-    cls_rule_init_catchall(&fsr.match, 0);
+    match_init_catchall(&fsr.match);
     fsr.out_port = OFPP_NONE;
     fsr.table_id = 0xff;
     fsr.cookie = fsr.cookie_mask = htonll(0);
@@ -1954,7 +1956,7 @@ read_flows_from_switch(struct vconn *vconn,
         version->ofpacts_len = fs.ofpacts_len;
         version->ofpacts = xmemdup(fs.ofpacts, fs.ofpacts_len);
 
-        fte_insert(cls, &fs.rule, version, index);
+        fte_insert(cls, &fs.match, fs.priority, version, index);
     }
     ofpbuf_uninit(&ofpacts);
 }
@@ -1967,7 +1969,8 @@ fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
     struct ofputil_flow_mod fm;
     struct ofpbuf *ofm;
 
-    fm.cr = fte->rule;
+    fm.match = fte->rule.match;
+    fm.priority = fte->rule.priority;
     fm.cookie = htonll(0);
     fm.cookie_mask = htonll(0);
     fm.new_cookie = version->cookie;
@@ -2079,7 +2082,8 @@ ofctl_diff_flows(int argc OVS_UNUSED, char *argv[])
         struct fte_version *b = fte->versions[1];
 
         if (!a || !b || !fte_version_equals(a, b)) {
-            char *rule_s = cls_rule_to_string(&fte->rule);
+            char *rule_s = match_to_string(&fte->rule.match,
+                                           fte->rule.priority);
             if (a) {
                 printf("-%s", rule_s);
                 fte_version_print(a);
@@ -2173,7 +2177,7 @@ ofctl_parse_nxm__(bool oxm)
     ds_init(&in);
     while (!ds_get_test_line(&in, stdin)) {
         struct ofpbuf nx_match;
-        struct cls_rule rule;
+        struct match match;
         ovs_be64 cookie, cookie_mask;
         enum ofperr error;
         int match_len;
@@ -2182,22 +2186,22 @@ ofctl_parse_nxm__(bool oxm)
         ofpbuf_init(&nx_match, 0);
         match_len = nx_match_from_string(ds_cstr(&in), &nx_match);
 
-        /* Convert nx_match to cls_rule. */
+        /* Convert nx_match to match. */
         if (strict) {
-            error = nx_pull_match(&nx_match, match_len, 0, &rule,
+            error = nx_pull_match(&nx_match, match_len, &match,
                                   &cookie, &cookie_mask);
         } else {
-            error = nx_pull_match_loose(&nx_match, match_len, 0, &rule,
+            error = nx_pull_match_loose(&nx_match, match_len, &match,
                                         &cookie, &cookie_mask);
         }
 
         if (!error) {
             char *out;
 
-            /* Convert cls_rule back to nx_match. */
+            /* Convert match back to nx_match. */
             ofpbuf_uninit(&nx_match);
             ofpbuf_init(&nx_match, 0);
-            match_len = nx_put_match(&nx_match, oxm, &rule,
+            match_len = nx_put_match(&nx_match, oxm, &match,
                                      cookie, cookie_mask);
 
             /* Convert nx_match to string. */
@@ -2326,7 +2330,7 @@ ofctl_parse_ofp10_match(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
         struct ofpbuf match_in;
         struct ofp10_match match_out;
         struct ofp10_match match_normal;
-        struct cls_rule rule;
+        struct match match;
 
         /* Parse hex bytes. */
         ofpbuf_init(&match_in, 0);
@@ -2339,18 +2343,18 @@ ofctl_parse_ofp10_match(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
         }
 
         /* Convert to cls_rule and print. */
-        ofputil_cls_rule_from_ofp10_match(match_in.data, OFP_DEFAULT_PRIORITY,
-                                          &rule);
-        cls_rule_print(&rule);
+        ofputil_match_from_ofp10_match(match_in.data, OFP_DEFAULT_PRIORITY,
+                                       &match);
+        match_print(&match);
 
         /* Convert back to ofp10_match and print differences from input. */
-        ofputil_cls_rule_to_ofp10_match(&rule, &match_out);
+        ofputil_match_to_ofp10_match(&match, &match_out);
         print_differences("", match_in.data, match_in.size,
                           &match_out, sizeof match_out);
 
         /* Normalize, then convert and compare again. */
-        ofputil_normalize_rule(&rule);
-        ofputil_cls_rule_to_ofp10_match(&rule, &match_normal);
+        ofputil_normalize_match(&match);
+        ofputil_match_to_ofp10_match(&match, &match_normal);
         print_differences("normal: ", &match_out, sizeof match_out,
                           &match_normal, sizeof match_normal);
         putchar('\n');
@@ -2361,9 +2365,9 @@ ofctl_parse_ofp10_match(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 }
 
 /* "parse-ofp11-match": reads a series of ofp11_match specifications as hex
- * bytes from stdin, converts them to cls_rules, prints them as strings on
- * stdout, and then converts them back to hex bytes and prints any differences
- * from the input. */
+ * bytes from stdin, converts them to "struct match"es, prints them as strings
+ * on stdout, and then converts them back to hex bytes and prints any
+ * differences from the input. */
 static void
 ofctl_parse_ofp11_match(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
@@ -2373,7 +2377,7 @@ ofctl_parse_ofp11_match(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
     while (!ds_get_preprocessed_line(&in, stdin)) {
         struct ofpbuf match_in;
         struct ofp11_match match_out;
-        struct cls_rule rule;
+        struct match match;
         enum ofperr error;
 
         /* Parse hex bytes. */
@@ -2386,20 +2390,19 @@ ofctl_parse_ofp11_match(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
                       match_in.size, sizeof(struct ofp11_match));
         }
 
-        /* Convert to cls_rule. */
-        error = ofputil_cls_rule_from_ofp11_match(match_in.data,
-                                                  OFP_DEFAULT_PRIORITY, &rule);
+        /* Convert to match. */
+        error = ofputil_match_from_ofp11_match(match_in.data, &match);
         if (error) {
             printf("bad ofp11_match: %s\n\n", ofperr_get_name(error));
             ofpbuf_uninit(&match_in);
             continue;
         }
 
-        /* Print cls_rule. */
-        cls_rule_print(&rule);
+        /* Print match. */
+        match_print(&match);
 
         /* Convert back to ofp11_match and print differences from input. */
-        ofputil_cls_rule_to_ofp11_match(&rule, &match_out);
+        ofputil_match_to_ofp11_match(&match, &match_out);
 
         print_differences("", match_in.data, match_in.size,
                           &match_out, sizeof match_out);
@@ -2533,74 +2536,74 @@ ofctl_parse_ofp11_instructions(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 static void
 ofctl_check_vlan(int argc OVS_UNUSED, char *argv[])
 {
-    struct cls_rule rule;
+    struct match match;
 
     char *string_s;
     struct ofputil_flow_mod fm;
 
     struct ofpbuf nxm;
-    struct cls_rule nxm_rule;
+    struct match nxm_match;
     int nxm_match_len;
     char *nxm_s;
 
-    struct ofp10_match of10_match;
-    struct cls_rule of10_rule;
+    struct ofp10_match of10_raw;
+    struct match of10_match;
 
-    struct ofp11_match of11_match;
-    struct cls_rule of11_rule;
+    struct ofp11_match of11_raw;
+    struct match of11_match;
 
     enum ofperr error;
 
-    cls_rule_init_catchall(&rule, OFP_DEFAULT_PRIORITY);
-    rule.flow.vlan_tci = htons(strtoul(argv[1], NULL, 16));
-    rule.wc.masks.vlan_tci = htons(strtoul(argv[2], NULL, 16));
+    match_init_catchall(&match);
+    match.flow.vlan_tci = htons(strtoul(argv[1], NULL, 16));
+    match.wc.masks.vlan_tci = htons(strtoul(argv[2], NULL, 16));
 
     /* Convert to and from string. */
-    string_s = cls_rule_to_string(&rule);
+    string_s = match_to_string(&match, OFP_DEFAULT_PRIORITY);
     printf("%s -> ", string_s);
     fflush(stdout);
     parse_ofp_str(&fm, -1, string_s, false);
     printf("%04"PRIx16"/%04"PRIx16"\n",
-           ntohs(fm.cr.flow.vlan_tci),
-           ntohs(fm.cr.wc.masks.vlan_tci));
+           ntohs(fm.match.flow.vlan_tci),
+           ntohs(fm.match.wc.masks.vlan_tci));
 
     /* Convert to and from NXM. */
     ofpbuf_init(&nxm, 0);
-    nxm_match_len = nx_put_match(&nxm, false, &rule, htonll(0), htonll(0));
+    nxm_match_len = nx_put_match(&nxm, false, &match, htonll(0), htonll(0));
     nxm_s = nx_match_to_string(nxm.data, nxm_match_len);
-    error = nx_pull_match(&nxm, nxm_match_len, 0, &nxm_rule, NULL, NULL);
+    error = nx_pull_match(&nxm, nxm_match_len, &nxm_match, NULL, NULL);
     printf("NXM: %s -> ", nxm_s);
     if (error) {
         printf("%s\n", ofperr_to_string(error));
     } else {
         printf("%04"PRIx16"/%04"PRIx16"\n",
-               ntohs(nxm_rule.flow.vlan_tci),
-               ntohs(nxm_rule.wc.masks.vlan_tci));
+               ntohs(nxm_match.flow.vlan_tci),
+               ntohs(nxm_match.wc.masks.vlan_tci));
     }
     free(nxm_s);
     ofpbuf_uninit(&nxm);
 
     /* Convert to and from OpenFlow 1.0. */
-    ofputil_cls_rule_to_ofp10_match(&rule, &of10_match);
-    ofputil_cls_rule_from_ofp10_match(&of10_match, 0, &of10_rule);
+    ofputil_match_to_ofp10_match(&match, &of10_raw);
+    ofputil_match_from_ofp10_match(&of10_raw, 0, &of10_match);
     printf("OF1.0: %04"PRIx16"/%d,%02"PRIx8"/%d -> %04"PRIx16"/%04"PRIx16"\n",
-           ntohs(of10_match.dl_vlan),
-           (of10_match.wildcards & htonl(OFPFW10_DL_VLAN)) != 0,
-           of10_match.dl_vlan_pcp,
-           (of10_match.wildcards & htonl(OFPFW10_DL_VLAN_PCP)) != 0,
-           ntohs(of10_rule.flow.vlan_tci),
-           ntohs(of10_rule.wc.masks.vlan_tci));
+           ntohs(of10_raw.dl_vlan),
+           (of10_raw.wildcards & htonl(OFPFW10_DL_VLAN)) != 0,
+           of10_raw.dl_vlan_pcp,
+           (of10_raw.wildcards & htonl(OFPFW10_DL_VLAN_PCP)) != 0,
+           ntohs(of10_match.flow.vlan_tci),
+           ntohs(of10_match.wc.masks.vlan_tci));
 
     /* Convert to and from OpenFlow 1.1. */
-    ofputil_cls_rule_to_ofp11_match(&rule, &of11_match);
-    ofputil_cls_rule_from_ofp11_match(&of11_match, 0, &of11_rule);
+    ofputil_match_to_ofp11_match(&match, &of11_raw);
+    ofputil_match_from_ofp11_match(&of11_raw, &of11_match);
     printf("OF1.1: %04"PRIx16"/%d,%02"PRIx8"/%d -> %04"PRIx16"/%04"PRIx16"\n",
-           ntohs(of11_match.dl_vlan),
-           (of11_match.wildcards & htonl(OFPFW11_DL_VLAN)) != 0,
-           of11_match.dl_vlan_pcp,
-           (of11_match.wildcards & htonl(OFPFW11_DL_VLAN_PCP)) != 0,
-           ntohs(of11_rule.flow.vlan_tci),
-           ntohs(of11_rule.wc.masks.vlan_tci));
+           ntohs(of11_raw.dl_vlan),
+           (of11_raw.wildcards & htonl(OFPFW11_DL_VLAN)) != 0,
+           of11_raw.dl_vlan_pcp,
+           (of11_raw.wildcards & htonl(OFPFW11_DL_VLAN_PCP)) != 0,
+           ntohs(of11_match.flow.vlan_tci),
+           ntohs(of11_match.wc.masks.vlan_tci));
 }
 
 /* "print-error ENUM": Prints the type and code of ENUM for every OpenFlow
-- 
1.7.2.5




More information about the dev mailing list