aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/classifier.c33
-rw-r--r--lib/classifier.h1
-rw-r--r--lib/flow.c94
-rw-r--r--lib/flow.h14
-rw-r--r--lib/nx-match.c42
-rw-r--r--lib/nx-match.def3
-rw-r--r--lib/nx-match.h8
-rw-r--r--lib/odp-util.c55
-rw-r--r--lib/odp-util.h4
-rw-r--r--lib/ofp-parse.c20
-rw-r--r--lib/ofp-util.c2
11 files changed, 254 insertions, 22 deletions
diff --git a/lib/classifier.c b/lib/classifier.c
index 658be808..fcc965c8 100644
--- a/lib/classifier.c
+++ b/lib/classifier.c
@@ -369,6 +369,13 @@ cls_rule_set_ipv6_dst_masked(struct cls_rule *rule, const struct in6_addr *dst,
}
}
+void
+cls_rule_set_nd_target(struct cls_rule *rule, const struct in6_addr target)
+{
+ rule->wc.wildcards &= ~FWW_ND_TARGET;
+ rule->flow.nd_target = target;
+}
+
/* Returns true if 'a' and 'b' have the same priority, wildcard the same
* fields, and have the same values for fixed fields, otherwise false. */
bool
@@ -586,7 +593,20 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
if (!(w & FWW_TP_DST)) {
ds_put_format(s, "icmp_code=%"PRIu16",", ntohs(f->tp_dst));
}
- } else {
+ if (!(w & FWW_ND_TARGET)) {
+ ds_put_cstr(s, "nd_target=");
+ print_ipv6_addr(s, &f->nd_target);
+ ds_put_char(s, ',');
+ }
+ if (!(w & FWW_ARP_SHA)) {
+ ds_put_format(s, "nd_sll="ETH_ADDR_FMT",",
+ ETH_ADDR_ARGS(f->arp_sha));
+ }
+ if (!(w & FWW_ARP_THA)) {
+ ds_put_format(s, "nd_tll="ETH_ADDR_FMT",",
+ ETH_ADDR_ARGS(f->arp_tha));
+ }
+ } else {
if (!(w & FWW_TP_SRC)) {
ds_put_format(s, "tp_src=%"PRIu16",", ntohs(f->tp_src));
}
@@ -1080,7 +1100,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_SIG_SIZE == 84 + FLOW_N_REGS * 4);
+ BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 100 + FLOW_N_REGS * 4);
for (i = 0; i < FLOW_N_REGS; i++) {
if ((a->regs[i] ^ b->regs[i]) & wildcards->reg_masks[i]) {
@@ -1113,7 +1133,9 @@ flow_equal_except(const struct flow *a, const struct flow *b,
&& ipv6_equal_except(&a->ipv6_src, &b->ipv6_src,
&wildcards->ipv6_src_mask)
&& ipv6_equal_except(&a->ipv6_dst, &b->ipv6_dst,
- &wildcards->ipv6_dst_mask));
+ &wildcards->ipv6_dst_mask)
+ && (wc & FWW_ND_TARGET
+ || ipv6_addr_equals(&a->nd_target, &b->nd_target)));
}
static void
@@ -1122,7 +1144,7 @@ zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
const flow_wildcards_t wc = wildcards->wildcards;
int i;
- BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 84 + 4 * FLOW_N_REGS);
+ BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 100 + 4 * FLOW_N_REGS);
for (i = 0; i < FLOW_N_REGS; i++) {
flow->regs[i] &= wildcards->reg_masks[i];
@@ -1169,4 +1191,7 @@ zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
&wildcards->ipv6_src_mask);
flow->ipv6_dst = ipv6_addr_bitand(&flow->ipv6_dst,
&wildcards->ipv6_dst_mask);
+ if (wc & FWW_ND_TARGET) {
+ memset(&flow->nd_target, 0, sizeof flow->nd_target);
+ }
}
diff --git a/lib/classifier.h b/lib/classifier.h
index c82a4844..d3121bfb 100644
--- a/lib/classifier.h
+++ b/lib/classifier.h
@@ -109,6 +109,7 @@ bool cls_rule_set_ipv6_src_masked(struct cls_rule *, const struct in6_addr *,
void cls_rule_set_ipv6_dst(struct cls_rule *, const struct in6_addr *);
bool cls_rule_set_ipv6_dst_masked(struct cls_rule *, const struct in6_addr *,
const struct in6_addr *);
+void cls_rule_set_nd_target(struct cls_rule *, const struct in6_addr);
bool cls_rule_equal(const struct cls_rule *, const struct cls_rule *);
diff --git a/lib/flow.c b/lib/flow.c
index 41f13b88..879e462f 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -230,6 +230,94 @@ parse_ipv6(struct ofpbuf *packet, struct flow *flow)
return nh_len;
}
+/* Neighbor Discovery Solicitation and Advertisement messages are
+ * identical in structure, so we'll just use one of them. To be safe,
+ * we'll assert that they're still identical. */
+BUILD_ASSERT_DECL(sizeof(struct nd_neighbor_solicit)
+ == sizeof(struct nd_neighbor_advert));
+
+static bool
+parse_icmpv6(struct ofpbuf *b, struct flow *flow, int icmp_len)
+{
+ const struct icmp6_hdr *icmp = pull_icmpv6(b);
+
+ if (!icmp) {
+ return false;
+ }
+
+ /* The ICMPv6 type and code fields use the 16-bit transport port
+ * fields, so we need to store them in 16-bit network byte order. */
+ flow->icmp_type = htons(icmp->icmp6_type);
+ flow->icmp_code = htons(icmp->icmp6_code);
+
+ if (!icmp->icmp6_code
+ && ((icmp->icmp6_type == ND_NEIGHBOR_SOLICIT)
+ || (icmp->icmp6_type == ND_NEIGHBOR_ADVERT))) {
+ struct nd_neighbor_solicit *nd_ns; /* Identical to ND advert */
+
+ /* In order to process neighbor discovery options, we need the
+ * entire packet. */
+ if ((icmp_len < sizeof *nd_ns)
+ || (!ofpbuf_try_pull(b, sizeof *nd_ns - sizeof *icmp))) {
+ return false;
+ }
+ nd_ns = (struct nd_neighbor_solicit *)icmp;
+ flow->nd_target = nd_ns->nd_ns_target;
+
+ icmp_len -= sizeof(*nd_ns);
+ while (icmp_len >= 8) {
+ struct nd_opt_hdr *nd_opt;
+ int opt_len;
+ const uint8_t *data;
+
+ /* The minimum size of an option is 8 bytes, which also is
+ * the size of Ethernet link-layer options. */
+ nd_opt = ofpbuf_pull(b, 8);
+ if (!nd_opt->nd_opt_len || nd_opt->nd_opt_len * 8 > icmp_len) {
+ goto invalid;
+ }
+ opt_len = nd_opt->nd_opt_len * 8;
+ data = (const uint8_t *)(nd_opt + 1);
+
+ /* Store the link layer address if the appropriate option is
+ * provided. It is considered an error if the same link
+ * layer option is specified twice. */
+ if (nd_opt->nd_opt_type == ND_OPT_SOURCE_LINKADDR
+ && opt_len == 8) {
+ if (eth_addr_is_zero(flow->arp_sha)) {
+ memcpy(flow->arp_sha, data, ETH_ADDR_LEN);
+ } else {
+ goto invalid;
+ }
+ } else if (nd_opt->nd_opt_type == ND_OPT_TARGET_LINKADDR
+ && opt_len == 8) {
+ if (eth_addr_is_zero(flow->arp_tha)) {
+ memcpy(flow->arp_tha, data, ETH_ADDR_LEN);
+ } else {
+ goto invalid;
+ }
+ }
+
+ /* Pull the rest of this option. */
+ if (!ofpbuf_try_pull(b, opt_len - 8)) {
+ goto invalid;
+ }
+
+ icmp_len -= opt_len;
+ }
+ }
+
+ return true;
+
+invalid:
+ memset(&flow->nd_target, '\0', sizeof(flow->nd_target));
+ memset(flow->arp_sha, '\0', sizeof(flow->arp_sha));
+ memset(flow->arp_tha, '\0', sizeof(flow->arp_tha));
+
+ return false;
+
+}
+
/* Initializes 'flow' members from 'packet', 'tun_id', and 'in_port.
* Initializes 'packet' header pointers as follows:
*
@@ -344,10 +432,8 @@ flow_extract(struct ofpbuf *packet, ovs_be64 tun_id, uint16_t in_port,
packet->l7 = b.data;
}
} else if (flow->nw_proto == IPPROTO_ICMPV6) {
- const struct icmp6_hdr *icmp = pull_icmpv6(&b);
- if (icmp) {
- flow->icmp_type = htons(icmp->icmp6_type);
- flow->icmp_code = htons(icmp->icmp6_code);
+ int icmp_len = ntohs(nh->ip6_plen) + sizeof *nh - nh_len;
+ if (parse_icmpv6(&b, flow, icmp_len)) {
packet->l7 = b.data;
}
}
diff --git a/lib/flow.h b/lib/flow.h
index d331aa36..60229f58 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -54,19 +54,20 @@ struct flow {
uint8_t dl_dst[6]; /* Ethernet destination address. */
uint8_t nw_proto; /* IP protocol or low 8 bits of ARP opcode. */
uint8_t nw_tos; /* IP ToS (DSCP field, 6 bits). */
- uint8_t arp_sha[6]; /* ARP source hardware address. */
- uint8_t arp_tha[6]; /* ARP target hardware address. */
+ uint8_t arp_sha[6]; /* ARP/ND source hardware address. */
+ uint8_t arp_tha[6]; /* ARP/ND target hardware address. */
struct in6_addr ipv6_src; /* IPv6 source address. */
struct in6_addr ipv6_dst; /* IPv6 destination address. */
+ struct in6_addr nd_target; /* IPv6 neighbor discovery (ND) target. */
uint32_t reserved; /* Reserved for 64-bit packing. */
};
/* Assert that there are FLOW_SIG_SIZE bytes of significant data in "struct
* flow", followed by FLOW_PAD_SIZE bytes of padding. */
-#define FLOW_SIG_SIZE (84 + FLOW_N_REGS * 4)
+#define FLOW_SIG_SIZE (100 + FLOW_N_REGS * 4)
#define FLOW_PAD_SIZE 4
-BUILD_ASSERT_DECL(offsetof(struct flow, ipv6_dst) == FLOW_SIG_SIZE - 16);
-BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->ipv6_dst) == 16);
+BUILD_ASSERT_DECL(offsetof(struct flow, nd_target) == FLOW_SIG_SIZE - 16);
+BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->nd_target) == 16);
BUILD_ASSERT_DECL(sizeof(struct flow) == FLOW_SIG_SIZE + FLOW_PAD_SIZE);
int flow_extract(struct ofpbuf *, uint64_t tun_id, uint16_t in_port,
@@ -122,7 +123,8 @@ typedef unsigned int OVS_BITWISE flow_wildcards_t;
/* multicast bit only */
#define FWW_ARP_SHA ((OVS_FORCE flow_wildcards_t) (1 << 9))
#define FWW_ARP_THA ((OVS_FORCE flow_wildcards_t) (1 << 10))
-#define FWW_ALL ((OVS_FORCE flow_wildcards_t) (((1 << 11)) - 1))
+#define FWW_ND_TARGET ((OVS_FORCE flow_wildcards_t) (1 << 11))
+#define FWW_ALL ((OVS_FORCE flow_wildcards_t) (((1 << 12)) - 1))
/* Information on wildcards for a flow, as a supplement to "struct flow".
*
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 5fc6aa24..abc3b210 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -18,6 +18,8 @@
#include "nx-match.h"
+#include <netinet/icmp6.h>
+
#include "classifier.h"
#include "dynamic-string.h"
#include "ofp-util.h"
@@ -363,6 +365,30 @@ parse_nxm_entry(struct cls_rule *rule, const struct nxm_field *f,
flow->tp_dst = htons(*(uint8_t *) value);
return 0;
+ /* IPv6 Neighbor Discovery. */
+ case NFI_NXM_NX_ND_TARGET:
+ /* We've already verified that it's an ICMPv6 message. */
+ if ((flow->tp_src != htons(ND_NEIGHBOR_SOLICIT))
+ && (flow->tp_src != htons(ND_NEIGHBOR_ADVERT))) {
+ return NXM_BAD_PREREQ;
+ }
+ memcpy(&flow->nd_target, value, sizeof flow->nd_target);
+ return 0;
+ case NFI_NXM_NX_ND_SLL:
+ /* We've already verified that it's an ICMPv6 message. */
+ if (flow->tp_src != htons(ND_NEIGHBOR_SOLICIT)) {
+ return NXM_BAD_PREREQ;
+ }
+ memcpy(flow->arp_sha, value, ETH_ADDR_LEN);
+ return 0;
+ case NFI_NXM_NX_ND_TLL:
+ /* We've already verified that it's an ICMPv6 message. */
+ if (flow->tp_src != htons(ND_NEIGHBOR_ADVERT)) {
+ return NXM_BAD_PREREQ;
+ }
+ memcpy(flow->arp_tha, value, ETH_ADDR_LEN);
+ return 0;
+
/* ARP header. */
case NFI_NXM_OF_ARP_OP:
if (ntohs(get_unaligned_be16(value)) > 255) {
@@ -815,6 +841,16 @@ nx_put_match(struct ofpbuf *b, const struct cls_rule *cr)
if (!(wc & FWW_TP_DST)) {
nxm_put_8(b, NXM_NX_ICMPV6_CODE, ntohs(flow->tp_dst));
}
+ if (!(wc & FWW_ND_TARGET)) {
+ nxm_put_ipv6(b, NXM_NX_ND_TARGET, &flow->nd_target,
+ &in6addr_exact);
+ }
+ if (!(wc & FWW_ARP_SHA)) {
+ nxm_put_eth(b, NXM_NX_ND_SLL, flow->arp_sha);
+ }
+ if (!(wc & FWW_ARP_THA)) {
+ nxm_put_eth(b, NXM_NX_ND_TLL, flow->arp_tha);
+ }
break;
}
}
@@ -1303,9 +1339,11 @@ nxm_read_field(const struct nxm_field *src, const struct flow *flow)
#endif
case NFI_NXM_NX_ARP_SHA:
+ case NFI_NXM_NX_ND_SLL:
return eth_addr_to_uint64(flow->arp_sha);
case NFI_NXM_NX_ARP_THA:
+ case NFI_NXM_NX_ND_TLL:
return eth_addr_to_uint64(flow->arp_tha);
case NFI_NXM_NX_TUN_ID_W:
@@ -1319,6 +1357,7 @@ nxm_read_field(const struct nxm_field *src, const struct flow *flow)
case NFI_NXM_NX_IPV6_SRC_W:
case NFI_NXM_NX_IPV6_DST:
case NFI_NXM_NX_IPV6_DST_W:
+ case NFI_NXM_NX_ND_TARGET:
case N_NXM_FIELDS:
NOT_REACHED();
}
@@ -1392,6 +1431,9 @@ nxm_write_field(const struct nxm_field *dst, struct flow *flow,
case NFI_NXM_NX_IPV6_DST_W:
case NFI_NXM_NX_ICMPV6_TYPE:
case NFI_NXM_NX_ICMPV6_CODE:
+ case NFI_NXM_NX_ND_TARGET:
+ case NFI_NXM_NX_ND_SLL:
+ case NFI_NXM_NX_ND_TLL:
case N_NXM_FIELDS:
NOT_REACHED();
}
diff --git a/lib/nx-match.def b/lib/nx-match.def
index 41e76d65..4a42aaa4 100644
--- a/lib/nx-match.def
+++ b/lib/nx-match.def
@@ -53,6 +53,9 @@ DEFINE_FIELD_M(NX_IPV6_SRC, 0, NXM_DL_IPV6, 0, false)
DEFINE_FIELD_M(NX_IPV6_DST, 0, NXM_DL_IPV6, 0, false)
DEFINE_FIELD (NX_ICMPV6_TYPE, FWW_TP_SRC, NXM_DL_IPV6, IPPROTO_ICMPV6, false)
DEFINE_FIELD (NX_ICMPV6_CODE, FWW_TP_DST, NXM_DL_IPV6, IPPROTO_ICMPV6, false)
+DEFINE_FIELD (NX_ND_TARGET, FWW_ND_TARGET,NXM_DL_IPV6, IPPROTO_ICMPV6, false)
+DEFINE_FIELD (NX_ND_SLL, FWW_ARP_SHA, NXM_DL_IPV6, IPPROTO_ICMPV6, false)
+DEFINE_FIELD (NX_ND_TLL, FWW_ARP_THA, NXM_DL_IPV6, IPPROTO_ICMPV6, false)
DEFINE_FIELD_M(NX_REG0, 0, NXM_DL_NONE, 0, true)
#if FLOW_N_REGS >= 2
diff --git a/lib/nx-match.h b/lib/nx-match.h
index aefcb653..a76ad1f2 100644
--- a/lib/nx-match.h
+++ b/lib/nx-match.h
@@ -95,15 +95,17 @@ nxm_decode_n_bits(ovs_be16 ofs_nbits)
* NXM_OF_IP_PROTO 4 2 -- 6
* NXM_OF_IPV6_SRC_W 4 16 16 36
* NXM_OF_IPV6_DST_W 4 16 16 36
- * NXM_OF_TCP_SRC 4 2 -- 6
- * NXM_OF_TCP_DST 4 2 -- 6
+ * NXM_OF_ICMP_TYPE 4 1 -- 5
+ * NXM_OF_ICMP_CODE 4 1 -- 5
+ * NXM_NX_ND_TARGET 4 16 -- 20
+ * NXM_NX_ND_SLL 4 6 -- 10
* NXM_NX_REG_W(0) 4 4 4 12
* NXM_NX_REG_W(1) 4 4 4 12
* NXM_NX_REG_W(2) 4 4 4 12
* NXM_NX_REG_W(3) 4 4 4 12
* NXM_NX_TUN_ID_W 4 8 8 20
* -------------------------------------------
- * total 209
+ * total 237
*
* So this value is conservative.
*/
diff --git a/lib/odp-util.c b/lib/odp-util.c
index e7acaad8..c90ff7d2 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -19,6 +19,7 @@
#include "odp-util.h"
#include <errno.h>
#include <inttypes.h>
+#include <netinet/icmp6.h>
#include <stdlib.h>
#include <string.h>
#include "byte-order.h"
@@ -201,6 +202,7 @@ odp_flow_key_attr_len(uint16_t type)
case ODP_KEY_ATTR_ICMP: return sizeof(struct odp_key_icmp);
case ODP_KEY_ATTR_ICMPV6: return sizeof(struct odp_key_icmpv6);
case ODP_KEY_ATTR_ARP: return sizeof(struct odp_key_arp);
+ case ODP_KEY_ATTR_ND: return sizeof(struct odp_key_nd);
case ODP_KEY_ATTR_UNSPEC:
case __ODP_KEY_ATTR_MAX:
@@ -242,6 +244,7 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
const struct odp_key_icmp *icmp_key;
const struct odp_key_icmpv6 *icmpv6_key;
const struct odp_key_arp *arp_key;
+ const struct odp_key_nd *nd_key;
if (nl_attr_get_size(a) != odp_flow_key_attr_len(nl_attr_type(a))) {
ds_put_format(ds, "bad length %zu, expected %d for: ",
@@ -339,6 +342,25 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
ETH_ADDR_ARGS(arp_key->arp_tha));
break;
+ case ODP_KEY_ATTR_ND: {
+ char target[INET6_ADDRSTRLEN];
+
+ nd_key = nl_attr_get(a);
+ inet_ntop(AF_INET6, nd_key->nd_target, target, sizeof target);
+
+ ds_put_format(ds, "nd(target=%s", target);
+ if (!eth_addr_is_zero(nd_key->nd_sll)) {
+ ds_put_format(ds, ",sll="ETH_ADDR_FMT,
+ ETH_ADDR_ARGS(nd_key->nd_sll));
+ }
+ if (!eth_addr_is_zero(nd_key->nd_tll)) {
+ ds_put_format(ds, ",tll="ETH_ADDR_FMT,
+ ETH_ADDR_ARGS(nd_key->nd_tll));
+ }
+ ds_put_char(ds, ')');
+ break;
+ }
+
default:
format_generic_odp_key(a, ds);
break;
@@ -466,6 +488,18 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
sizeof *icmpv6_key);
icmpv6_key->icmpv6_type = ntohs(flow->tp_src);
icmpv6_key->icmpv6_code = ntohs(flow->tp_dst);
+
+ if (icmpv6_key->icmpv6_type == ND_NEIGHBOR_SOLICIT
+ || icmpv6_key->icmpv6_type == ND_NEIGHBOR_ADVERT) {
+ struct odp_key_nd *nd_key;
+
+ nd_key = nl_msg_put_unspec_uninit(buf, ODP_KEY_ATTR_ND,
+ sizeof *nd_key);
+ memcpy(nd_key->nd_target, &flow->nd_target,
+ sizeof nd_key->nd_target);
+ memcpy(nd_key->nd_sll, flow->arp_sha, ETH_ADDR_LEN);
+ memcpy(nd_key->nd_tll, flow->arp_tha, ETH_ADDR_LEN);
+ }
}
}
}
@@ -494,6 +528,7 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
const struct odp_key_icmp *icmp_key;
const struct odp_key_icmpv6 *icmpv6_key;
const struct odp_key_arp *arp_key;
+ const struct odp_key_nd *nd_key;
uint16_t type = nl_attr_type(nla);
int len = odp_flow_key_attr_len(type);
@@ -623,6 +658,17 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
memcpy(flow->arp_tha, arp_key->arp_tha, ETH_ADDR_LEN);
break;
+ case TRANSITION(ODP_KEY_ATTR_ICMPV6, ODP_KEY_ATTR_ND):
+ if (flow->tp_src != htons(ND_NEIGHBOR_SOLICIT)
+ && flow->tp_src != htons(ND_NEIGHBOR_ADVERT)) {
+ return EINVAL;
+ }
+ nd_key = nl_attr_get(nla);
+ memcpy(&flow->nd_target, nd_key->nd_target, sizeof flow->nd_target);
+ memcpy(flow->arp_sha, nd_key->nd_sll, ETH_ADDR_LEN);
+ memcpy(flow->arp_tha, nd_key->nd_tll, ETH_ADDR_LEN);
+ break;
+
default:
if (type == ODP_KEY_ATTR_UNSPEC
|| prev_type == ODP_KEY_ATTR_UNSPEC) {
@@ -673,11 +719,18 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
}
return 0;
+ case ODP_KEY_ATTR_ICMPV6:
+ if (flow->icmp_type == htons(ND_NEIGHBOR_SOLICIT)
+ || flow->icmp_type == htons(ND_NEIGHBOR_ADVERT)) {
+ return EINVAL;
+ }
+ return 0;
+
case ODP_KEY_ATTR_TCP:
case ODP_KEY_ATTR_UDP:
case ODP_KEY_ATTR_ICMP:
- case ODP_KEY_ATTR_ICMPV6:
case ODP_KEY_ATTR_ARP:
+ case ODP_KEY_ATTR_ND:
return 0;
case __ODP_KEY_ATTR_MAX:
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 8ec09f3c..074df87f 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -64,13 +64,13 @@ void format_odp_actions(struct ds *, const struct nlattr *odp_actions,
size_t actions_len);
/* By my calculations currently the longest valid nlattr-formatted flow key is
- * 92 bytes long, so this leaves some safety margin.
+ * 124 bytes long, so this leaves some safety margin.
*
* We allocate temporary on-stack buffers for flow keys as arrays of uint32_t
* to ensure proper 32-bit alignment for Netlink attributes. (An array of
* "struct nlattr" might not, in theory, be sufficiently aligned because it
* only contains 16-bit types.) */
-#define ODPUTIL_FLOW_KEY_BYTES 112
+#define ODPUTIL_FLOW_KEY_BYTES 144
#define ODPUTIL_FLOW_KEY_U32S DIV_ROUND_UP(ODPUTIL_FLOW_KEY_BYTES, 4)
void odp_flow_key_format(const struct nlattr *, size_t, struct ds *);
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index e77453e0..3fac4749 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -540,7 +540,10 @@ parse_protocol(const char *name, const struct protocol **p_out)
FIELD(F_ARP_SHA, "arp_sha", FWW_ARP_SHA) \
FIELD(F_ARP_THA, "arp_tha", FWW_ARP_THA) \
FIELD(F_IPV6_SRC, "ipv6_src", 0) \
- FIELD(F_IPV6_DST, "ipv6_dst", 0)
+ FIELD(F_IPV6_DST, "ipv6_dst", 0) \
+ FIELD(F_ND_TARGET, "nd_target", FWW_ND_TARGET) \
+ FIELD(F_ND_SLL, "nd_sll", FWW_ARP_SHA) \
+ FIELD(F_ND_TLL, "nd_tll", FWW_ARP_THA)
enum field_index {
#define FIELD(ENUM, NAME, WILDCARD) ENUM,
@@ -677,6 +680,21 @@ parse_field_value(struct cls_rule *rule, enum field_index index,
cls_rule_set_ipv6_dst_masked(rule, &ipv6, &ipv6_mask);
break;
+ case F_ND_TARGET:
+ str_to_ipv6(value, &ipv6, NULL);
+ cls_rule_set_nd_target(rule, ipv6);
+ break;
+
+ case F_ND_SLL:
+ str_to_mac(value, mac);
+ cls_rule_set_arp_sha(rule, mac);
+ break;
+
+ case F_ND_TLL:
+ str_to_mac(value, mac);
+ cls_rule_set_arp_tha(rule, mac);
+ break;
+
case N_FIELDS:
NOT_REACHED();
}
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 4d89e0ae..1125b83f 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -126,7 +126,7 @@ ofputil_cls_rule_from_match(const struct ofp_match *match,
wc->wildcards = ofpfw & WC_INVARIANTS;
/* Wildcard fields that aren't defined by ofp_match or tun_id. */
- wc->wildcards |= (FWW_ARP_SHA | FWW_ARP_THA);
+ wc->wildcards |= (FWW_ARP_SHA | FWW_ARP_THA | FWW_ND_TARGET);
if (ofpfw & OFPFW_NW_TOS) {
wc->wildcards |= FWW_NW_TOS;