[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