[ovs-dev] [PATCHv2 2/2] flow: Adds support for arbitrary ethernet masking

Joe Stringer joe at wand.net.nz
Mon May 28 12:38:21 UTC 2012


Arbitrary ethernet mask support is one step on the way to support for OpenFlow
1.1+. This patch set seeks to add this capability without breaking current
protocol support.

Signed-off-by: Joe Stringer <joe at wand.net.nz>
---
This is version 2 of this patch, incorporating the comments from the first
round of review.

There is currently two tests that this patch fails:-

[Test 137] "ovs-ofctl dump-flows honors -F option" (ovs-ofctl.at:657)

I don't entirely understand this -- what is "reg0=0x12345" meant to match on?
It seems that the flow which matches this is not added, so we get the
following diff on the test:

 OFPST_FLOW reply:
 - actions=drop

[Test 419] "ofproto - basic flow_mod commands (NXM)" (ofproto.at:109)

Here, the command "del-flows" is sent, but when we then dump-flows, one of
them is still present:

 NXST_FLOW reply:
 + table=1, in_port=3 actions=output:2

Any tips about how to fix these problems would be welcome. All other tests
pass, and I've included a couple of extra tests for arbitrary ethernet source
masks.

I have one other query -- Now that FWW_ETH_* have been removed, should I
shuffle the rest of the FWW_* down? (while retaining OFPFW compatibility)

Currently in this patch, FWW_ALL specifies 12 bits set, despite 3 of them
having no designated use.
---
 NEWS                          |    1 +
 include/openflow/nicira-ext.h |    9 ++-
 lib/classifier.c              |   71 +++++++++++----------
 lib/classifier.h              |    2 +
 lib/flow.c                    |  134 +++++++++++------------------------------
 lib/flow.h                    |   17 ++---
 lib/learn.c                   |    2 +-
 lib/meta-flow.c               |   38 ++++++------
 lib/meta-flow.h               |    3 +-
 lib/nx-match.c                |   40 ++++++-------
 lib/nx-match.h                |    6 +-
 lib/ofp-util.c                |   35 +++++++----
 lib/packets.c                 |    2 +-
 tests/ovs-ofctl.at            |    2 +
 tests/test-classifier.c       |   14 ++++-
 utilities/ovs-ofctl.8.in      |    8 ++-
 16 files changed, 174 insertions(+), 210 deletions(-)

diff --git a/NEWS b/NEWS
index 9e5ad14..99680b2 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,7 @@ post-v1.7.0
 ------------------------
     - ovs-ofctl:
         - "mod-port" command can now control all OpenFlow config flags.
+    - Added support for arbitrary ethernet masks
 
 
 v1.7.0 - xx xxx xxxx
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 62fc103..99c2cae 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -1368,13 +1368,14 @@ OFP_ASSERT(sizeof(struct nx_action_output_reg) == 24);
  *
  * Format: 48-bit Ethernet MAC address.
  *
- * Masking: The nxm_mask patterns 01:00:00:00:00:00 and FE:FF:FF:FF:FF:FF must
- *   be supported for NXM_OF_ETH_DST_W (as well as the trivial patterns that
- *   are all-0-bits or all-1-bits).  Support for other patterns and for masking
- *   of NXM_OF_ETH_SRC is optional. */
+ * Masking: Fully maskable, in versions 1.8 and later. Earlier versions only
+ *   require support for NXM_OF_ETH_DST_W, with the following masks:
+ *   00:00:00:00:00:00, fe:ff:ff:ff:ff:ff,
+ *   01:00:00:00:00:00, ff:ff:ff:ff:ff:ff. */
 #define NXM_OF_ETH_DST    NXM_HEADER  (0x0000,  1, 6)
 #define NXM_OF_ETH_DST_W  NXM_HEADER_W(0x0000,  1, 6)
 #define NXM_OF_ETH_SRC    NXM_HEADER  (0x0000,  2, 6)
+#define NXM_OF_ETH_SRC_W  NXM_HEADER_W(0x0000,  2, 6)
 
 /* Packet's Ethernet type.
  *
diff --git a/lib/classifier.c b/lib/classifier.c
index e11a585..327a8b2 100644
--- a/lib/classifier.c
+++ b/lib/classifier.c
@@ -149,16 +149,31 @@ cls_rule_set_dl_type(struct cls_rule *rule, ovs_be16 dl_type)
 void
 cls_rule_set_dl_src(struct cls_rule *rule, const uint8_t dl_src[ETH_ADDR_LEN])
 {
-    rule->wc.wildcards &= ~FWW_DL_SRC;
     memcpy(rule->flow.dl_src, dl_src, ETH_ADDR_LEN);
+    memset(rule->wc.dl_src_mask, 0xff, ETH_ADDR_LEN);
+}
+
+/* Modifies 'rule' so that the 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])
+{
+    size_t i;
+
+    for (i = 0; i < ETH_ADDR_LEN; i++) {
+        rule->flow.dl_src[i] = dl_src[i] & mask[i];
+        rule->wc.dl_src_mask[i] = mask[i];
+    }
 }
 
 /* Modifies 'rule' so that the 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])
 {
-    rule->wc.wildcards &= ~(FWW_DL_DST | FWW_ETH_MCAST);
     memcpy(rule->flow.dl_dst, dl_dst, ETH_ADDR_LEN);
+    memset(rule->wc.dl_dst_mask, 0xff, ETH_ADDR_LEN);
 }
 
 /* Modifies 'rule' so that the Ethernet address must match 'dl_dst' after each
@@ -171,12 +186,11 @@ 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])
 {
-    flow_wildcards_t *wc = &rule->wc.wildcards;
     size_t i;
 
-    *wc = flow_wildcards_set_dl_dst_mask(*wc, mask);
     for (i = 0; i < ETH_ADDR_LEN; i++) {
         rule->flow.dl_dst[i] = dl_dst[i] & mask[i];
+        rule->wc.dl_dst_mask[i] = mask[i];
     }
 }
 
@@ -449,6 +463,17 @@ cls_rule_hash(const struct cls_rule *rule, uint32_t 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)
 {
@@ -500,7 +525,7 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
 
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     if (rule->priority != OFP_DEFAULT_PRIORITY) {
         ds_put_format(s, "priority=%d,", rule->priority);
@@ -597,24 +622,8 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
                           ntohs(f->vlan_tci), ntohs(wc->vlan_tci_mask));
         }
     }
-    if (!(w & FWW_DL_SRC)) {
-        ds_put_format(s, "dl_src="ETH_ADDR_FMT",", ETH_ADDR_ARGS(f->dl_src));
-    }
-    switch (w & (FWW_DL_DST | FWW_ETH_MCAST)) {
-    case 0:
-        ds_put_format(s, "dl_dst="ETH_ADDR_FMT",", ETH_ADDR_ARGS(f->dl_dst));
-        break;
-    case FWW_DL_DST:
-        ds_put_format(s, "dl_dst="ETH_ADDR_FMT"/01:00:00:00:00:00,",
-                      ETH_ADDR_ARGS(f->dl_dst));
-        break;
-    case FWW_ETH_MCAST:
-        ds_put_format(s, "dl_dst="ETH_ADDR_FMT"/fe:ff:ff:ff:ff:ff,",
-                      ETH_ADDR_ARGS(f->dl_dst));
-        break;
-    case FWW_DL_DST | FWW_ETH_MCAST:
-        break;
-    }
+    format_eth_masked(s, "dl_src", f->dl_src, wc->dl_src_mask);
+    format_eth_masked(s, "dl_dst", f->dl_dst, wc->dl_dst_mask);
     if (!skip_type && !(w & FWW_DL_TYPE)) {
         ds_put_format(s, "dl_type=0x%04"PRIx16",", ntohs(f->dl_type));
     }
@@ -1179,7 +1188,7 @@ flow_equal_except(const struct flow *a, const struct flow *b,
     const flow_wildcards_t wc = wildcards->wildcards;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     for (i = 0; i < FLOW_N_REGS; i++) {
         if ((a->regs[i] ^ b->regs[i]) & wildcards->reg_masks[i]) {
@@ -1195,16 +1204,10 @@ flow_equal_except(const struct flow *a, const struct flow *b,
             && (wc & FWW_DL_TYPE || a->dl_type == b->dl_type)
             && !((a->tp_src ^ b->tp_src) & wildcards->tp_src_mask)
             && !((a->tp_dst ^ b->tp_dst) & wildcards->tp_dst_mask)
-            && (wc & FWW_DL_SRC || eth_addr_equals(a->dl_src, b->dl_src))
-            && (wc & FWW_DL_DST
-                || (!((a->dl_dst[0] ^ b->dl_dst[0]) & 0xfe)
-                    && a->dl_dst[1] == b->dl_dst[1]
-                    && a->dl_dst[2] == b->dl_dst[2]
-                    && a->dl_dst[3] == b->dl_dst[3]
-                    && a->dl_dst[4] == b->dl_dst[4]
-                    && a->dl_dst[5] == b->dl_dst[5]))
-            && (wc & FWW_ETH_MCAST
-                || !((a->dl_dst[0] ^ b->dl_dst[0]) & 0x01))
+            && !eth_addr_equal_except(a->dl_src, b->dl_src,
+                    wildcards->dl_src_mask)
+            && !eth_addr_equal_except(a->dl_dst, b->dl_dst,
+                    wildcards->dl_dst_mask)
             && (wc & FWW_NW_PROTO || a->nw_proto == b->nw_proto)
             && (wc & FWW_NW_TTL || a->nw_ttl == b->nw_ttl)
             && (wc & FWW_NW_DSCP || !((a->nw_tos ^ b->nw_tos) & IP_DSCP_MASK))
diff --git a/lib/classifier.h b/lib/classifier.h
index 92ccc2f..9e4b33e 100644
--- a/lib/classifier.h
+++ b/lib/classifier.h
@@ -96,6 +96,8 @@ void cls_rule_set_tun_id_masked(struct cls_rule *,
 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]);
diff --git a/lib/flow.c b/lib/flow.c
index fc61610..46e0e2d 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -444,7 +444,7 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
     const flow_wildcards_t wc = wildcards->wildcards;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     for (i = 0; i < FLOW_N_REGS; i++) {
         flow->regs[i] &= wildcards->reg_masks[i];
@@ -461,16 +461,8 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
     }
     flow->tp_src &= wildcards->tp_src_mask;
     flow->tp_dst &= wildcards->tp_dst_mask;
-    if (wc & FWW_DL_SRC) {
-        memset(flow->dl_src, 0, sizeof flow->dl_src);
-    }
-    if (wc & FWW_DL_DST) {
-        flow->dl_dst[0] &= 0x01;
-        memset(&flow->dl_dst[1], 0, 5);
-    }
-    if (wc & FWW_ETH_MCAST) {
-        flow->dl_dst[0] &= 0xfe;
-    }
+    eth_addr_bitand(flow->dl_src, wildcards->dl_src_mask, flow->dl_src);
+    eth_addr_bitand(flow->dl_dst, wildcards->dl_dst_mask, flow->dl_dst);
     if (wc & FWW_NW_PROTO) {
         flow->nw_proto = 0;
     }
@@ -506,7 +498,7 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
 void
 flow_get_metadata(const struct flow *flow, struct flow_metadata *fmd)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     fmd->tun_id = flow->tun_id;
     fmd->tun_id_mask = htonll(UINT64_MAX);
@@ -595,7 +587,7 @@ flow_print(FILE *stream, const struct flow *flow)
 void
 flow_wildcards_init_catchall(struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     wc->wildcards = FWW_ALL;
     wc->tun_id_mask = htonll(0);
@@ -609,6 +601,8 @@ flow_wildcards_init_catchall(struct flow_wildcards *wc)
     wc->nw_frag_mask = 0;
     wc->tp_src_mask = htons(0);
     wc->tp_dst_mask = htons(0);
+    memset(wc->dl_src_mask, 0, ETH_ADDR_LEN);
+    memset(wc->dl_dst_mask, 0, ETH_ADDR_LEN);
     memset(wc->zeros, 0, sizeof wc->zeros);
 }
 
@@ -617,7 +611,7 @@ flow_wildcards_init_catchall(struct flow_wildcards *wc)
 void
 flow_wildcards_init_exact(struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     wc->wildcards = 0;
     wc->tun_id_mask = htonll(UINT64_MAX);
@@ -631,6 +625,8 @@ flow_wildcards_init_exact(struct flow_wildcards *wc)
     wc->nw_frag_mask = UINT8_MAX;
     wc->tp_src_mask = htons(UINT16_MAX);
     wc->tp_dst_mask = htons(UINT16_MAX);
+    memset(wc->dl_src_mask, 0xff, ETH_ADDR_LEN);
+    memset(wc->dl_dst_mask, 0xff, ETH_ADDR_LEN);
     memset(wc->zeros, 0, sizeof wc->zeros);
 }
 
@@ -641,7 +637,7 @@ flow_wildcards_is_exact(const struct flow_wildcards *wc)
 {
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     if (wc->wildcards
         || wc->tun_id_mask != htonll(UINT64_MAX)
@@ -650,6 +646,8 @@ flow_wildcards_is_exact(const struct flow_wildcards *wc)
         || wc->tp_src_mask != htons(UINT16_MAX)
         || wc->tp_dst_mask != htons(UINT16_MAX)
         || wc->vlan_tci_mask != htons(UINT16_MAX)
+        || !eth_mask_is_exact(wc->dl_src_mask)
+        || !eth_mask_is_exact(wc->dl_dst_mask)
         || !ipv6_mask_is_exact(&wc->ipv6_src_mask)
         || !ipv6_mask_is_exact(&wc->ipv6_dst_mask)
         || !ipv6_mask_is_exact(&wc->nd_target_mask)
@@ -673,7 +671,7 @@ flow_wildcards_is_catchall(const struct flow_wildcards *wc)
 {
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     if (wc->wildcards != FWW_ALL
         || wc->tun_id_mask != htonll(0)
@@ -682,6 +680,8 @@ flow_wildcards_is_catchall(const struct flow_wildcards *wc)
         || wc->tp_src_mask != htons(0)
         || wc->tp_dst_mask != htons(0)
         || wc->vlan_tci_mask != htons(0)
+        || !eth_addr_is_zero(wc->dl_src_mask)
+        || !eth_addr_is_zero(wc->dl_dst_mask)
         || !ipv6_mask_is_any(&wc->ipv6_src_mask)
         || !ipv6_mask_is_any(&wc->ipv6_dst_mask)
         || !ipv6_mask_is_any(&wc->nd_target_mask)
@@ -708,7 +708,7 @@ flow_wildcards_combine(struct flow_wildcards *dst,
 {
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     dst->wildcards = src1->wildcards | src2->wildcards;
     dst->tun_id_mask = src1->tun_id_mask & src2->tun_id_mask;
@@ -726,6 +726,8 @@ flow_wildcards_combine(struct flow_wildcards *dst,
     dst->vlan_tci_mask = src1->vlan_tci_mask & src2->vlan_tci_mask;
     dst->tp_src_mask = src1->tp_src_mask & src2->tp_src_mask;
     dst->tp_dst_mask = src1->tp_dst_mask & src2->tp_dst_mask;
+    eth_addr_bitand(src1->dl_src_mask, src2->dl_src_mask, dst->dl_src_mask);
+    eth_addr_bitand(src1->dl_dst_mask, src2->dl_dst_mask, dst->dl_dst_mask);
 }
 
 /* Returns a hash of the wildcards in 'wc'. */
@@ -735,7 +737,7 @@ flow_wildcards_hash(const struct flow_wildcards *wc, uint32_t basis)
     /* If you change struct flow_wildcards and thereby trigger this
      * assertion, please check that the new struct flow_wildcards has no holes
      * in it before you update the assertion. */
-    BUILD_ASSERT_DECL(sizeof *wc == 80 + FLOW_N_REGS * 4);
+    BUILD_ASSERT_DECL(sizeof *wc == 88 + FLOW_N_REGS * 4);
     return hash_bytes(wc, sizeof *wc, basis);
 }
 
@@ -747,7 +749,7 @@ flow_wildcards_equal(const struct flow_wildcards *a,
 {
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     if (a->wildcards != b->wildcards
         || a->tun_id_mask != b->tun_id_mask
@@ -758,7 +760,9 @@ flow_wildcards_equal(const struct flow_wildcards *a,
         || !ipv6_addr_equals(&a->ipv6_dst_mask, &b->ipv6_dst_mask)
         || !ipv6_addr_equals(&a->nd_target_mask, &b->nd_target_mask)
         || a->tp_src_mask != b->tp_src_mask
-        || a->tp_dst_mask != b->tp_dst_mask) {
+        || a->tp_dst_mask != b->tp_dst_mask
+        || !eth_addr_equals(a->dl_src_mask, b->dl_src_mask)
+        || !eth_addr_equals(a->dl_dst_mask, b->dl_dst_mask)) {
         return false;
     }
 
@@ -778,9 +782,10 @@ flow_wildcards_has_extra(const struct flow_wildcards *a,
                          const struct flow_wildcards *b)
 {
     int i;
+    uint8_t eth_masked[ETH_ADDR_LEN];
     struct in6_addr ipv6_masked;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     for (i = 0; i < FLOW_N_REGS; i++) {
         if ((a->reg_masks[i] & b->reg_masks[i]) != b->reg_masks[i]) {
@@ -788,6 +793,16 @@ flow_wildcards_has_extra(const struct flow_wildcards *a,
         }
     }
 
+    eth_addr_bitand(a->dl_src_mask, b->dl_src_mask, eth_masked);
+    if (!eth_addr_equals(eth_masked, b->dl_src_mask)) {
+        return true;
+    }
+
+    eth_addr_bitand(a->dl_dst_mask, b->dl_dst_mask, eth_masked);
+    if (!eth_addr_equals(eth_masked, b->dl_dst_mask)) {
+        return true;
+    }
+
     ipv6_masked = ipv6_addr_bitand(&a->ipv6_src_mask, &b->ipv6_src_mask);
     if (!ipv6_addr_equals(&ipv6_masked, &b->ipv6_src_mask)) {
         return true;
@@ -820,83 +835,6 @@ flow_wildcards_set_reg_mask(struct flow_wildcards *wc, int idx, uint32_t mask)
     wc->reg_masks[idx] = mask;
 }
 
-/* Returns the wildcard bitmask for the Ethernet destination address
- * that 'wc' specifies.  The bitmask has a 0 in each bit that is wildcarded
- * and a 1 in each bit that must match.  */
-const uint8_t *
-flow_wildcards_to_dl_dst_mask(flow_wildcards_t wc)
-{
-    static const uint8_t    no_wild[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
-    static const uint8_t  addr_wild[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
-    static const uint8_t mcast_wild[] = {0xfe, 0xff, 0xff, 0xff, 0xff, 0xff};
-    static const uint8_t   all_wild[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-
-    switch (wc & (FWW_DL_DST | FWW_ETH_MCAST)) {
-    case 0:                             return no_wild;
-    case FWW_DL_DST:                    return addr_wild;
-    case FWW_ETH_MCAST:                 return mcast_wild;
-    case FWW_DL_DST | FWW_ETH_MCAST:    return all_wild;
-    }
-    NOT_REACHED();
-}
-
-/* Returns true if 'mask' is a valid wildcard bitmask for the Ethernet
- * destination address.  Valid bitmasks are either all-bits-0 or all-bits-1,
- * except that the multicast bit may differ from the rest of the bits.  So,
- * there are four possible valid bitmasks:
- *
- *  - 00:00:00:00:00:00
- *  - 01:00:00:00:00:00
- *  - fe:ff:ff:ff:ff:ff
- *  - ff:ff:ff:ff:ff:ff
- *
- * All other bitmasks are invalid. */
-bool
-flow_wildcards_is_dl_dst_mask_valid(const uint8_t mask[ETH_ADDR_LEN])
-{
-    switch (mask[0]) {
-    case 0x00:
-    case 0x01:
-        return (mask[1] | mask[2] | mask[3] | mask[4] | mask[5]) == 0x00;
-
-    case 0xfe:
-    case 0xff:
-        return (mask[1] & mask[2] & mask[3] & mask[4] & mask[5]) == 0xff;
-
-    default:
-        return false;
-    }
-}
-
-/* Returns 'wc' with the FWW_DL_DST and FWW_ETH_MCAST bits modified
- * appropriately to match 'mask'.
- *
- * This function will assert-fail if 'mask' is invalid.  Only 'mask' values
- * accepted by flow_wildcards_is_dl_dst_mask_valid() are allowed. */
-flow_wildcards_t
-flow_wildcards_set_dl_dst_mask(flow_wildcards_t wc,
-                               const uint8_t mask[ETH_ADDR_LEN])
-{
-    assert(flow_wildcards_is_dl_dst_mask_valid(mask));
-
-    switch (mask[0]) {
-    case 0x00:
-        return wc | FWW_DL_DST | FWW_ETH_MCAST;
-
-    case 0x01:
-        return (wc | FWW_DL_DST) & ~FWW_ETH_MCAST;
-
-    case 0xfe:
-        return (wc & ~FWW_DL_DST) | FWW_ETH_MCAST;
-
-    case 0xff:
-        return wc & ~(FWW_DL_DST | FWW_ETH_MCAST);
-
-    default:
-        NOT_REACHED();
-    }
-}
-
 /* Hashes 'flow' based on its L2 through L4 protocol information. */
 uint32_t
 flow_hash_symmetric_l4(const struct flow *flow, uint32_t basis)
diff --git a/lib/flow.h b/lib/flow.h
index 7ee9a26..0595aff 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -35,7 +35,7 @@ struct ofpbuf;
 /* This sequence number should be incremented whenever anything involving flows
  * or the wildcarding of flows changes.  This will cause build assertion
  * failures in places which likely need to be updated. */
-#define FLOW_WC_SEQ 10
+#define FLOW_WC_SEQ 11
 
 #define FLOW_N_REGS 8
 BUILD_ASSERT_DECL(FLOW_N_REGS <= NXM_NX_MAX_REGS);
@@ -100,7 +100,7 @@ BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->nw_frag) == 1);
 BUILD_ASSERT_DECL(sizeof(struct flow) == FLOW_SIG_SIZE + FLOW_PAD_SIZE);
 
 /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
-BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 142 && FLOW_WC_SEQ == 10);
+BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 142 && FLOW_WC_SEQ == 11);
 
 void flow_extract(struct ofpbuf *, uint32_t priority, ovs_be64 tun_id,
                   uint16_t in_port, struct flow *);
@@ -147,14 +147,9 @@ typedef unsigned int OVS_BITWISE flow_wildcards_t;
 
 /* Same values and meanings as corresponding OFPFW_* bits. */
 #define FWW_IN_PORT     ((OVS_FORCE flow_wildcards_t) (1 << 0))
-#define FWW_DL_SRC      ((OVS_FORCE flow_wildcards_t) (1 << 2))
-#define FWW_DL_DST      ((OVS_FORCE flow_wildcards_t) (1 << 3))
-                                              /* excluding the multicast bit */
 #define FWW_DL_TYPE     ((OVS_FORCE flow_wildcards_t) (1 << 4))
 #define FWW_NW_PROTO    ((OVS_FORCE flow_wildcards_t) (1 << 5))
 /* No corresponding OFPFW_* bits. */
-#define FWW_ETH_MCAST   ((OVS_FORCE flow_wildcards_t) (1 << 1))
-                                                       /* multicast bit only */
 #define FWW_NW_DSCP     ((OVS_FORCE flow_wildcards_t) (1 << 6))
 #define FWW_NW_ECN      ((OVS_FORCE flow_wildcards_t) (1 << 7))
 #define FWW_ARP_SHA     ((OVS_FORCE flow_wildcards_t) (1 << 8))
@@ -164,7 +159,7 @@ typedef unsigned int OVS_BITWISE flow_wildcards_t;
 #define FWW_ALL         ((OVS_FORCE flow_wildcards_t) (((1 << 12)) - 1))
 
 /* Remember to update FLOW_WC_SEQ when adding or removing FWW_*. */
-BUILD_ASSERT_DECL(FWW_ALL == ((1 << 12) - 1) && FLOW_WC_SEQ == 10);
+BUILD_ASSERT_DECL(FWW_ALL == ((1 << 12) - 1) && FLOW_WC_SEQ == 11);
 
 /* Information on wildcards for a flow, as a supplement to "struct flow".
  *
@@ -184,11 +179,13 @@ struct flow_wildcards {
     ovs_be16 tp_src_mask;       /* 1-bit in each significant tp_src bit. */
     ovs_be16 tp_dst_mask;       /* 1-bit in each significant tp_dst bit. */
     uint8_t nw_frag_mask;       /* 1-bit in each significant nw_frag bit. */
-    uint8_t zeros[5];           /* Padding field set to zero. */
+    uint8_t zeros[1];           /* Padding field set to zero. */
+    uint8_t dl_src_mask[6];     /* 1-bit in each significant dl_src bit. */
+    uint8_t dl_dst_mask[6];     /* 1-bit in each significant dl_dst bit. */
 };
 
 /* Remember to update FLOW_WC_SEQ when updating struct flow_wildcards. */
-BUILD_ASSERT_DECL(sizeof(struct flow_wildcards) == 112 && FLOW_WC_SEQ == 10);
+BUILD_ASSERT_DECL(sizeof(struct flow_wildcards) == 120 && FLOW_WC_SEQ == 11);
 
 void flow_wildcards_init_catchall(struct flow_wildcards *);
 void flow_wildcards_init_exact(struct flow_wildcards *);
diff --git a/lib/learn.c b/lib/learn.c
index e995c29..a89ed70 100644
--- a/lib/learn.c
+++ b/lib/learn.c
@@ -184,7 +184,7 @@ learn_check(const struct nx_action_learn *learn, const struct flow *flow)
                      * prerequisites.  No prerequisite depends on the value of
                      * a field that is wider than 64 bits.  So just skip
                      * setting it entirely. */
-                    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+                    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
                 }
             }
         }
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index 9387d3a..f18d1a0 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -113,7 +113,7 @@ static const struct mf_field mf_fields[MFF_N_IDS] = {
     {
         MFF_ETH_SRC, "eth_src", "dl_src",
         MF_FIELD_SIZES(mac),
-        MFM_NONE, FWW_DL_SRC,
+        MFM_FULLY, 0,
         MFS_ETHERNET,
         MFP_NONE,
         true,
@@ -122,7 +122,7 @@ static const struct mf_field mf_fields[MFF_N_IDS] = {
     }, {
         MFF_ETH_DST, "eth_dst", "dl_dst",
         MF_FIELD_SIZES(mac),
-        MFM_MCAST, 0,
+        MFM_FULLY, 0,
         MFS_ETHERNET,
         MFP_NONE,
         true,
@@ -543,7 +543,6 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
 {
     switch (mf->id) {
     case MFF_IN_PORT:
-    case MFF_ETH_SRC:
     case MFF_ETH_TYPE:
     case MFF_IP_PROTO:
     case MFF_IP_DSCP:
@@ -590,9 +589,10 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
 #endif
         return !wc->reg_masks[mf->id - MFF_REG0];
 
+    case MFF_ETH_SRC:
+        return eth_addr_is_zero(wc->dl_src_mask);
     case MFF_ETH_DST:
-        return ((wc->wildcards & (FWW_ETH_MCAST | FWW_DL_DST))
-                == (FWW_ETH_MCAST | FWW_DL_DST));
+        return eth_addr_is_zero(wc->dl_dst_mask);
 
     case MFF_VLAN_TCI:
         return !wc->vlan_tci_mask;
@@ -651,7 +651,6 @@ mf_get_mask(const struct mf_field *mf, const struct flow_wildcards *wc,
 {
     switch (mf->id) {
     case MFF_IN_PORT:
-    case MFF_ETH_SRC:
     case MFF_ETH_TYPE:
     case MFF_IP_PROTO:
     case MFF_IP_DSCP:
@@ -702,8 +701,11 @@ mf_get_mask(const struct mf_field *mf, const struct flow_wildcards *wc,
         break;
 
     case MFF_ETH_DST:
-        memcpy(mask->mac, flow_wildcards_to_dl_dst_mask(wc->wildcards),
-               ETH_ADDR_LEN);
+        memcpy(mask->mac, wc->dl_dst_mask, ETH_ADDR_LEN);
+        break;
+
+    case MFF_ETH_SRC:
+        memcpy(mask->mac, wc->dl_src_mask, ETH_ADDR_LEN);
         break;
 
     case MFF_VLAN_TCI:
@@ -786,9 +788,6 @@ mf_is_mask_valid(const struct mf_field *mf, const union mf_value *mask)
         return (mf->n_bytes == 4
                 ? ip_is_cidr(mask->be32)
                 : ipv6_is_cidr(&mask->ipv6));
-
-    case MFM_MCAST:
-        return flow_wildcards_is_dl_dst_mask_valid(mask->mac);
     }
 
     NOT_REACHED();
@@ -1532,13 +1531,13 @@ mf_set_wild(const struct mf_field *mf, struct cls_rule *rule)
 #endif
 
     case MFF_ETH_SRC:
-        rule->wc.wildcards |= FWW_DL_SRC;
-        memset(rule->flow.dl_src, 0, sizeof rule->flow.dl_src);
+        memset(rule->flow.dl_src, 0, ETH_ADDR_LEN);
+        memset(rule->wc.dl_src_mask, 0, ETH_ADDR_LEN);
         break;
 
     case MFF_ETH_DST:
-        rule->wc.wildcards |= FWW_DL_DST | FWW_ETH_MCAST;
-        memset(rule->flow.dl_dst, 0, sizeof rule->flow.dl_dst);
+        memset(rule->flow.dl_dst, 0, ETH_ADDR_LEN);
+        memset(rule->wc.dl_dst_mask, 0, ETH_ADDR_LEN);
         break;
 
     case MFF_ETH_TYPE:
@@ -1678,7 +1677,6 @@ mf_set(const struct mf_field *mf,
 
     switch (mf->id) {
     case MFF_IN_PORT:
-    case MFF_ETH_SRC:
     case MFF_ETH_TYPE:
     case MFF_VLAN_VID:
     case MFF_VLAN_PCP:
@@ -1734,9 +1732,11 @@ mf_set(const struct mf_field *mf,
         break;
 
     case MFF_ETH_DST:
-        if (flow_wildcards_is_dl_dst_mask_valid(mask->mac)) {
-            cls_rule_set_dl_dst_masked(rule, value->mac, mask->mac);
-        }
+        cls_rule_set_dl_dst_masked(rule, value->mac, mask->mac);
+        break;
+
+    case MFF_ETH_SRC:
+        cls_rule_set_dl_src_masked(rule, value->mac, mask->mac);
         break;
 
     case MFF_VLAN_TCI:
diff --git a/lib/meta-flow.h b/lib/meta-flow.h
index 632cb46..29e3fa7 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -144,8 +144,7 @@ enum mf_prereqs {
 enum mf_maskable {
     MFM_NONE,                   /* No sub-field masking. */
     MFM_FULLY,                  /* Every bit is individually maskable. */
-    MFM_CIDR,                   /* Contiguous low-order bits may be masked. */
-    MFM_MCAST                   /* Byte 0, bit 0 is separately maskable. */
+    MFM_CIDR                    /* Contiguous low-order bits may be masked. */
 };
 
 /* How to format or parse a field's value. */
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 34c8354..75d4358 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -354,24 +354,6 @@ nxm_put_eth(struct ofpbuf *b, uint32_t header,
 }
 
 static void
-nxm_put_eth_dst(struct ofpbuf *b,
-                flow_wildcards_t wc, const uint8_t value[ETH_ADDR_LEN])
-{
-    switch (wc & (FWW_DL_DST | FWW_ETH_MCAST)) {
-    case FWW_DL_DST | FWW_ETH_MCAST:
-        break;
-    default:
-        nxm_put_header(b, NXM_OF_ETH_DST_W);
-        ofpbuf_put(b, value, ETH_ADDR_LEN);
-        ofpbuf_put(b, flow_wildcards_to_dl_dst_mask(wc), ETH_ADDR_LEN);
-        break;
-    case 0:
-        nxm_put_eth(b, NXM_OF_ETH_DST, value);
-        break;
-    }
-}
-
-static void
 nxm_put_ipv6(struct ofpbuf *b, uint32_t header,
              const struct in6_addr *value, const struct in6_addr *mask)
 {
@@ -471,7 +453,7 @@ nx_put_match(struct ofpbuf *b, const struct cls_rule *cr,
     int match_len;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     /* Metadata. */
     if (!(wc & FWW_IN_PORT)) {
@@ -480,10 +462,24 @@ nx_put_match(struct ofpbuf *b, const struct cls_rule *cr,
     }
 
     /* Ethernet. */
-    nxm_put_eth_dst(b, wc, flow->dl_dst);
-    if (!(wc & FWW_DL_SRC)) {
-        nxm_put_eth(b, NXM_OF_ETH_SRC, flow->dl_src);
+    if (!eth_addr_is_zero(cr->wc.dl_src_mask)) {
+        if (eth_mask_is_exact(cr->wc.dl_src_mask)) {
+            nxm_put_eth(b, NXM_OF_ETH_SRC, flow->dl_src);
+        } else {
+            nxm_put_eth(b, NXM_OF_ETH_SRC_W, flow->dl_src);
+            ofpbuf_put(b, cr->wc.dl_src_mask, ETH_ADDR_LEN);
+        }
+    }
+
+    if (!eth_addr_is_zero(cr->wc.dl_dst_mask)) {
+        if (eth_mask_is_exact(cr->wc.dl_dst_mask)) {
+            nxm_put_eth(b, NXM_OF_ETH_DST, flow->dl_dst);
+        } else {
+            nxm_put_eth(b, NXM_OF_ETH_DST_W, flow->dl_dst);
+            ofpbuf_put(b, cr->wc.dl_dst_mask, ETH_ADDR_LEN);
+        }
     }
+
     if (!(wc & FWW_DL_TYPE)) {
         nxm_put_16(b, NXM_OF_ETH_TYPE,
                    ofputil_dl_type_to_openflow(flow->dl_type));
diff --git a/lib/nx-match.h b/lib/nx-match.h
index a039225..fd101b6 100644
--- a/lib/nx-match.h
+++ b/lib/nx-match.h
@@ -90,7 +90,7 @@ void nxm_decode(struct mf_subfield *, ovs_be32 header, ovs_be16 ofs_nbits);
 void nxm_decode_discrete(struct mf_subfield *, ovs_be32 header,
                          ovs_be16 ofs, ovs_be16 n_bits);
 
-BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 /* Upper bound on the length of an nx_match.  The longest nx_match (an
  * IPV6 neighbor discovery message using 5 registers) would be:
  *
@@ -98,7 +98,7 @@ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
  *                   ------  -----  ----  -----
  *  NXM_OF_IN_PORT      4       2    --      6
  *  NXM_OF_ETH_DST_W    4       6     6     16
- *  NXM_OF_ETH_SRC      4       6    --     10
+ *  NXM_OF_ETH_SRC_W    4       6     6     16
  *  NXM_OF_ETH_TYPE     4       2    --      6
  *  NXM_OF_VLAN_TCI     4       2     2      8
  *  NXM_OF_IP_TOS       4       1    --      5
@@ -123,7 +123,7 @@ BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
  *  NXM_NX_REG_W(7)     4       4     4     12
  *  NXM_NX_TUN_ID_W     4       8     8     20
  *  -------------------------------------------
- *  total                                  327
+ *  total                                  333
  *
  * So this value is conservative.
  */
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 90124ec..3f93d9e 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -76,8 +76,6 @@ ofputil_netmask_to_wcbits(ovs_be32 netmask)
  * name. */
 #define WC_INVARIANT_LIST \
     WC_INVARIANT_BIT(IN_PORT) \
-    WC_INVARIANT_BIT(DL_SRC) \
-    WC_INVARIANT_BIT(DL_DST) \
     WC_INVARIANT_BIT(DL_TYPE) \
     WC_INVARIANT_BIT(NW_PROTO)
 
@@ -101,7 +99,7 @@ static const flow_wildcards_t WC_INVARIANTS = 0
 void
 ofputil_wildcard_from_openflow(uint32_t ofpfw, struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
     /* Initialize most of rule->wc. */
     flow_wildcards_init_catchall(wc);
@@ -127,11 +125,11 @@ ofputil_wildcard_from_openflow(uint32_t ofpfw, struct flow_wildcards *wc)
         wc->tp_dst_mask = htons(UINT16_MAX);
     }
 
-    if (ofpfw & OFPFW_DL_DST) {
-        /* OpenFlow 1.0 OFPFW_DL_DST covers the whole Ethernet destination, but
-         * Open vSwitch breaks the Ethernet destination into bits as FWW_DL_DST
-         * and FWW_ETH_MCAST. */
-        wc->wildcards |= FWW_ETH_MCAST;
+    if (!(ofpfw & OFPFW_DL_SRC)) {
+        memset(wc->dl_src_mask, 0xff, ETH_ADDR_LEN);
+    }
+    if (!(ofpfw & OFPFW_DL_DST)) {
+        memset(wc->dl_dst_mask, 0xff, ETH_ADDR_LEN);
     }
 
     /* VLAN TCI mask. */
@@ -212,6 +210,12 @@ ofputil_cls_rule_to_match(const struct cls_rule *rule, struct ofp_match *match)
     if (!wc->tp_dst_mask) {
         ofpfw |= OFPFW_TP_DST;
     }
+    if (eth_addr_is_zero(wc->dl_src_mask)) {
+        ofpfw |= OFPFW_DL_SRC;
+    }
+    if (eth_addr_is_zero(wc->dl_dst_mask)) {
+        ofpfw |= OFPFW_DL_DST;
+    }
 
     /* Translate VLANs. */
     match->dl_vlan = htons(0);
@@ -1174,10 +1178,15 @@ ofputil_usable_protocols(const struct cls_rule *rule)
 {
     const struct flow_wildcards *wc = &rule->wc;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 10);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 11);
 
-    /* Only NXM supports separately wildcards the Ethernet multicast bit. */
-    if (!(wc->wildcards & FWW_DL_DST) != !(wc->wildcards & FWW_ETH_MCAST)) {
+    /* NXM and OF1.1+ supports bitwise matching on ethernet addresses. */
+    if (wc->dl_src_mask && !eth_mask_is_exact(wc->dl_src_mask)
+            && !eth_addr_is_zero(wc->dl_src_mask)) {
+        return OFPUTIL_P_NXM_ANY;
+    }
+    if (wc->dl_dst_mask && !eth_mask_is_exact(wc->dl_dst_mask)
+            && !eth_addr_is_zero(wc->dl_dst_mask)) {
         return OFPUTIL_P_NXM_ANY;
     }
 
@@ -3858,6 +3867,10 @@ ofputil_normalize_rule(struct cls_rule *rule)
         wc.nd_target_mask = in6addr_any;
     }
 
+    /* Ethernet can always be matched. */
+    memcpy(wc.dl_src_mask, rule->wc.dl_src_mask, ETH_ADDR_LEN);
+    memcpy(wc.dl_dst_mask, rule->wc.dl_dst_mask, ETH_ADDR_LEN);
+
     /* Log any changes. */
     if (!flow_wildcards_equal(&wc, &rule->wc)) {
         bool log = !VLOG_DROP_INFO(&bad_ofmsg_rl);
diff --git a/lib/packets.c b/lib/packets.c
index be36f29..0986d32 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -153,7 +153,7 @@ eth_format_masked(const uint8_t eth[ETH_ADDR_LEN],
                   const uint8_t mask[ETH_ADDR_LEN], struct ds *s)
 {
     ds_put_format(s, ETH_ADDR_FMT, ETH_ADDR_ARGS(eth));
-    if (mask) {
+    if (mask && !eth_mask_is_exact(mask)) {
         ds_put_format(s, "/"ETH_ADDR_FMT, ETH_ADDR_ARGS(mask));
     }
 }
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index 37498a7..8fea809 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -233,6 +233,8 @@ NXM_OF_ETH_DST_W(0002e30f80a4/feffffffffff)
 
 # eth src
 NXM_OF_ETH_SRC(020898456ddb)
+NXM_OF_ETH_SRC_W(012345abcdef/ffffff555555)
+NXM_OF_ETH_SRC_W(020898456ddb/ffffffffffff)
 
 # eth type
 NXM_OF_ETH_TYPE(0800)
diff --git a/tests/test-classifier.c b/tests/test-classifier.c
index fcafdb2..7403159 100644
--- a/tests/test-classifier.c
+++ b/tests/test-classifier.c
@@ -52,8 +52,8 @@
     CLS_FIELD(FWW_DL_TYPE,                dl_type,     DL_TYPE)     \
     CLS_FIELD(0,                          tp_src,      TP_SRC)      \
     CLS_FIELD(0,                          tp_dst,      TP_DST)      \
-    CLS_FIELD(FWW_DL_SRC,                 dl_src,      DL_SRC)      \
-    CLS_FIELD(FWW_DL_DST | FWW_ETH_MCAST, dl_dst,      DL_DST)      \
+    CLS_FIELD(0,                          dl_src,      DL_SRC)      \
+    CLS_FIELD(0,                          dl_dst,      DL_DST)      \
     CLS_FIELD(FWW_NW_PROTO,               nw_proto,    NW_PROTO)    \
     CLS_FIELD(FWW_NW_DSCP,                nw_tos,      NW_DSCP)
 
@@ -202,6 +202,12 @@ match(const struct cls_rule *wild, const struct flow *fixed)
             eq = !((fixed->tp_src ^ wild->flow.tp_src) & wild->wc.tp_src_mask);
         } else if (f_idx == CLS_F_IDX_TP_DST) {
             eq = !((fixed->tp_dst ^ wild->flow.tp_dst) & wild->wc.tp_dst_mask);
+        } else if (f_idx == CLS_F_IDX_DL_SRC) {
+            eq = !eth_addr_equal_except(fixed->dl_src, wild->flow.dl_src,
+                                        wild->wc.dl_src_mask);
+        } else if (f_idx == CLS_F_IDX_DL_DST) {
+            eq = !eth_addr_equal_except(fixed->dl_dst, wild->flow.dl_dst,
+                                        wild->wc.dl_dst_mask);
         } else if (f_idx == CLS_F_IDX_VLAN_TCI) {
             eq = !((fixed->vlan_tci ^ wild->flow.vlan_tci)
                    & wild->wc.vlan_tci_mask);
@@ -471,6 +477,10 @@ make_rule(int wc_fields, unsigned int priority, int value_pat)
             rule->cls_rule.wc.tp_src_mask = htons(UINT16_MAX);
         } else if (f_idx == CLS_F_IDX_TP_DST) {
             rule->cls_rule.wc.tp_dst_mask = htons(UINT16_MAX);
+        } else if (f_idx == CLS_F_IDX_DL_SRC) {
+            memset(rule->cls_rule.wc.dl_src_mask, 0xff, ETH_ADDR_LEN);
+        } else if (f_idx == CLS_F_IDX_DL_DST) {
+            memset(rule->cls_rule.wc.dl_dst_mask, 0xff, ETH_ADDR_LEN);
         } else if (f_idx == CLS_F_IDX_VLAN_TCI) {
             rule->cls_rule.wc.vlan_tci_mask = htons(UINT16_MAX);
         } else if (f_idx == CLS_F_IDX_TUN_ID) {
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index a9398e3..29e8d73 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -381,11 +381,13 @@ Matches an Ethernet source (or destination) address specified as 6
 pairs of hexadecimal digits delimited by colons
 (e.g. \fB00:0A:E4:25:6B:B0\fR).
 .
-.IP \fBdl_dst=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB/\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR
+.IP \fBdl_src=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB/\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR
+.IQ \fBdl_dst=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB/\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR
 Matches an Ethernet destination address specified as 6 pairs of
 hexadecimal digits delimited by colons (e.g. \fB00:0A:E4:25:6B:B0\fR),
-with a wildcard mask following the slash.  Only
-the following masks are allowed:
+with a wildcard mask following the slash. Open vSwitch 1.8 and later
+support arbitrary masks for source and/or destination. Earlier
+versions only support masking the destination with the following masks:
 .RS
 .IP \fB01:00:00:00:00:00\fR
 Match only the multicast bit.  Thus,
-- 
1.7.2.5




More information about the dev mailing list