[ovs-dev] [IPv6 IV: A New Hope 6/6] nicira-ext: Support matching IPv6 Neighbor Discovery messages.

Justin Pettit jpettit at nicira.com
Tue Feb 1 08:53:53 UTC 2011


IPv6 uses Neighbor Discovery messages in a similar manner to how IPv4
uses ARP.  This commit adds support for matching deeper into the
payloads of Neighbor Solicitation (NS) and Neighbor Advertisement (NA)
messages.  Currently, the matching fields include:

    - NS and NA Target (nd_target)
    - NS Source Link Layer Address (nd_sll)
    - NA Target Link Layer Address (nd_tll)

When defining IPv6 Neighbor Discovery rules, the Nicira Extensible Match
(NXM) extension to OVS must be used.

Signed-off-by: Justin Pettit <jpettit at nicira.com>
---
 DESIGN                                  |    3 +-
 datapath/flow.c                         |  123 +++++++++++++++++++++++++++++--
 datapath/flow.h                         |    5 +-
 include/openflow/nicira-ext.h           |   40 ++++++++++
 include/openvswitch/datapath-protocol.h |    7 ++
 lib/classifier.c                        |   33 +++++++-
 lib/classifier.h                        |    1 +
 lib/flow.c                              |   95 +++++++++++++++++++++++-
 lib/flow.h                              |   14 ++--
 lib/nx-match.c                          |   42 +++++++++++
 lib/nx-match.def                        |    3 +
 lib/nx-match.h                          |    8 +-
 lib/odp-util.c                          |   55 ++++++++++++++-
 lib/odp-util.h                          |    4 +-
 lib/ofp-parse.c                         |   20 +++++-
 lib/ofp-util.c                          |    2 +-
 tests/ovs-ofctl.at                      |   36 +++++++++
 utilities/ovs-ofctl.8.in                |   18 +++++
 18 files changed, 477 insertions(+), 32 deletions(-)

diff --git a/DESIGN b/DESIGN
index 56e2605..6e25f01 100644
--- a/DESIGN
+++ b/DESIGN
@@ -15,7 +15,8 @@ IPv6
 
 Open vSwitch supports stateless handling of IPv6 packets.  Flows can be
 written to support matching TCP, UDP, and ICMPv6 headers within an IPv6
-packet.
+packet.  Deeper matching of some Neighbor Discovery messages is also
+supported.
 
 IPv6 was not designed to interact well with middle-boxes.  This,
 combined with Open vSwitch's stateless nature, have affected the
diff --git a/datapath/flow.c b/datapath/flow.c
index a0688ec..2383ce2 100644
--- a/datapath/flow.c
+++ b/datapath/flow.c
@@ -32,6 +32,7 @@
 #include <net/inet_ecn.h>
 #include <net/ip.h>
 #include <net/ipv6.h>
+#include <net/ndisc.h>
 
 static struct kmem_cache *flow_cache;
 static unsigned int hash_seed __read_mostly;
@@ -314,6 +315,84 @@ static __be16 parse_ethertype(struct sk_buff *skb)
 	return llc->ethertype;
 }
 
+static inline bool eth_addr_is_zero(const uint8_t ea[ETH_ALEN])
+{
+	return !(ea[0] | ea[1] | ea[2] | ea[3] | ea[4] | ea[5]);
+}
+
+static int parse_icmpv6(struct sk_buff *skb, struct sw_flow_key *key,
+		int nh_len)
+{
+	struct ipv6hdr *nh = ipv6_hdr(skb);
+	int icmp_len = ntohs(nh->payload_len) + sizeof(*nh) - nh_len;
+	struct icmp6hdr *icmp = icmp6_hdr(skb);
+
+	/* 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. */
+	key->tp_src = htons(icmp->icmp6_type);
+	key->tp_dst = htons(icmp->icmp6_code);
+
+	if (!icmp->icmp6_code
+			&& ((icmp->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION)
+			  || (icmp->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT))) {
+		struct nd_msg *nd;
+		int offset;
+
+		/* In order to process neighbor discovery options, we need the
+		 * entire packet. */
+		if (icmp_len < sizeof(*nd))
+			goto invalid;
+		if (!pskb_may_pull(skb, skb_transport_offset(skb) + icmp_len))
+			return -ENOMEM;
+
+		nd = (struct nd_msg *)skb_transport_header(skb);
+		memcpy(key->nd_target, &nd->target, sizeof(key->nd_target));
+
+		icmp_len -= sizeof(*nd);
+		offset = 0;
+		while (icmp_len >= 8) {
+			struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd->opt + offset);
+			int opt_len = nd_opt->nd_opt_len * 8;
+
+			if (!opt_len || (opt_len > icmp_len))
+				goto invalid;
+
+			/* 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_LL_ADDR
+					&& opt_len == 8) {
+				if (eth_addr_is_zero(key->arp_sha)) {
+					memcpy(key->arp_sha,
+							&nd->opt[offset+sizeof(*nd_opt)], ETH_ALEN);
+				} else {
+					goto invalid;
+				}
+			} else if (nd_opt->nd_opt_type == ND_OPT_TARGET_LL_ADDR
+					&& opt_len == 8) {
+				if (eth_addr_is_zero(key->arp_tha)) {
+					memcpy(key->arp_tha,
+							&nd->opt[offset+sizeof(*nd_opt)], ETH_ALEN);
+				} else {
+					goto invalid;
+				}
+			}
+
+			icmp_len -= opt_len;
+			offset += opt_len;
+		}
+	}
+
+	return 0;
+
+invalid:
+	memset(&key->nd_target, 0, sizeof(key->nd_target));
+	memset(key->arp_sha, 0, sizeof(key->arp_sha));
+	memset(key->arp_tha, 0, sizeof(key->arp_tha));
+
+	return -EINVAL;
+}
+
 /**
  * flow_extract - extracts a flow key from an Ethernet frame.
  * @skb: sk_buff that contains the frame, with skb->data pointing to the
@@ -482,12 +561,9 @@ int flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key,
 			}
 		} else if (key->nw_proto == NEXTHDR_ICMP) {
 			if (icmp6hdr_ok(skb)) {
-				struct icmp6hdr *icmp = icmp6_hdr(skb);
-				/* 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. */
-				key->tp_src = htons(icmp->icmp6_type);
-				key->tp_dst = htons(icmp->icmp6_code);
+				int error = parse_icmpv6(skb, key, nh_len);
+				if (error < 0 && error != -EINVAL)
+					return error;
 			}
 		}
 	}
@@ -541,6 +617,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 			[ODP_KEY_ATTR_UDP] = sizeof(struct odp_key_udp),
 			[ODP_KEY_ATTR_ICMP] = sizeof(struct odp_key_icmp),
 			[ODP_KEY_ATTR_ARP] = sizeof(struct odp_key_arp),
+			[ODP_KEY_ATTR_ND] = sizeof(struct odp_key_nd),
 		};
 
 		const struct odp_key_ethernet *eth_key;
@@ -551,6 +628,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 		const struct odp_key_udp *udp_key;
 		const struct odp_key_icmp *icmp_key;
 		const struct odp_key_arp *arp_key;
+		const struct odp_key_nd *nd_key;
 
                 int type = nla_type(nla);
 
@@ -660,6 +738,17 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 			memcpy(swkey->arp_tha, arp_key->arp_tha, ETH_ALEN);
 			break;
 
+		case TRANSITION(ODP_KEY_ATTR_ICMP, ODP_KEY_ATTR_ND):
+			if (swkey->tp_src != htons(NDISC_NEIGHBOUR_SOLICITATION)
+					&& swkey->tp_src != htons(NDISC_NEIGHBOUR_ADVERTISEMENT))
+				return -EINVAL;
+			nd_key = nla_data(nla);
+			memcpy(swkey->nd_target, nd_key->nd_target,
+					sizeof(swkey->nd_target));
+			memcpy(swkey->arp_sha, nd_key->nd_sll, ETH_ALEN);
+			memcpy(swkey->arp_tha, nd_key->nd_tll, ETH_ALEN);
+			break;
+
 		default:
 			return -EINVAL;
 		}
@@ -696,10 +785,16 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 			return -EINVAL;
 		return 0;
 
+	case ODP_KEY_ATTR_ICMP:
+		if (swkey->tp_src == htons(NDISC_NEIGHBOUR_SOLICITATION) ||
+		    swkey->tp_src == htons(NDISC_NEIGHBOUR_ADVERTISEMENT))
+			return -EINVAL;
+		return 0;
+
 	case ODP_KEY_ATTR_TCP:
 	case ODP_KEY_ATTR_UDP:
-	case ODP_KEY_ATTR_ICMP:
 	case ODP_KEY_ATTR_ARP:
+	case ODP_KEY_ATTR_ND:
 		return 0;
 	}
 
@@ -816,6 +911,20 @@ int flow_to_nlattrs(const struct sw_flow_key *swkey, struct sk_buff *skb)
 			icmp_key = nla_data(nla);
 			icmp_key->icmp_type = ntohs(swkey->tp_src);
 			icmp_key->icmp_code = ntohs(swkey->tp_dst);
+
+			if (icmp_key->icmp_type == NDISC_NEIGHBOUR_SOLICITATION
+					|| icmp_key->icmp_type == NDISC_NEIGHBOUR_ADVERTISEMENT) {
+				struct odp_key_nd *nd_key;
+
+				nla = nla_reserve(skb, ODP_KEY_ATTR_ND, sizeof *nd_key);
+				if (!nla)
+					goto nla_put_failure;
+				nd_key = nla_data(nla);
+				memcpy(nd_key->nd_target, swkey->nd_target,
+							sizeof nd_key->nd_target);
+				memcpy(nd_key->nd_sll, swkey->arp_sha, ETH_ALEN);
+				memcpy(nd_key->nd_tll, swkey->arp_tha, ETH_ALEN);
+			}
 		}
 	}
 
diff --git a/datapath/flow.h b/datapath/flow.h
index ee1c4c9..21df5fb 100644
--- a/datapath/flow.h
+++ b/datapath/flow.h
@@ -41,6 +41,7 @@ struct sw_flow_key {
 			__be32	ipv6_dst[4]; /* IPv6 source address. */
 		};
 	};
+	__be32	nd_target[4]; /* IPv6 ND target address. */
 	u16	in_port;    /* Input switch port. */
 	__be16	dl_tci;	    /* 0 if no VLAN, VLAN_TAG_PRESENT set otherwise. */
 	__be16	dl_type;    /* Ethernet frame type. */
@@ -50,8 +51,8 @@ struct sw_flow_key {
 	u8	dl_dst[ETH_ALEN]; /* Ethernet destination address. */
 	u8	nw_proto;   /* IP protocol or lower 8 bits of ARP opcode. */
 	u8	nw_tos;	    /* IP ToS (DSCP field, 6 bits). */
-	u8	arp_sha[ETH_ALEN]; /* ARP source hardware address. */
-	u8	arp_tha[ETH_ALEN]; /* ARP target hardware address. */
+	u8	arp_sha[ETH_ALEN]; /* ARP/ND source hardware address. */
+	u8	arp_tha[ETH_ALEN]; /* ARP/ND target hardware address. */
 };
 
 struct sw_flow {
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index f136ba5..f7cdb7d 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -390,6 +390,8 @@ OFP_ASSERT(sizeof(struct nx_action_pop_queue) == 16);
  *   - NXM_NX_ARP_THA
  *   - NXM_NX_ICMPV6_TYPE
  *   - NXM_NX_ICMPV6_CODE
+ *   - NXM_NX_ND_SLL
+ *   - NXM_NX_ND_TLL
  *   - NXM_NX_REG(idx) for idx in the switch's accepted range.
  *
  * The following nxm_header values are potentially acceptable as 'dst':
@@ -1078,6 +1080,44 @@ enum nx_mp_algorithm {
 #define NXM_NX_ICMPV6_TYPE NXM_HEADER  (0x0001, 21, 1)
 #define NXM_NX_ICMPV6_CODE NXM_HEADER  (0x0001, 22, 1)
 
+/* The target address in an IPv6 Neighbor Discovery message.
+ *
+ * Prereqs:
+ *   NXM_OF_ETH_TYPE must match 0x86dd exactly.
+ *   NXM_OF_IP_PROTO must match 58 exactly.
+ *   NXM_OF_ICMPV6_TYPE must be either 135 or 136.
+ *
+ * Format: 128-bit IPv6 address.
+ *
+ * Masking: Not maskable. */
+#define NXM_NX_ND_TARGET   NXM_HEADER  (0x0001, 23, 16)
+
+/* The source link-layer address option in an IPv6 Neighbor Discovery
+ * message.
+ *
+ * Prereqs:
+ *   NXM_OF_ETH_TYPE must match 0x86dd exactly.
+ *   NXM_OF_IP_PROTO must match 58 exactly.
+ *   NXM_OF_ICMPV6_TYPE must be exactly 135.
+ *
+ * Format: 48-bit Ethernet MAC address.
+ *
+ * Masking: Not maskable. */
+#define NXM_NX_ND_SLL      NXM_HEADER  (0x0001, 24, 6)
+
+/* The target link-layer address option in an IPv6 Neighbor Discovery
+ * message.
+ *
+ * Prereqs:
+ *   NXM_OF_ETH_TYPE must match 0x86dd exactly.
+ *   NXM_OF_IP_PROTO must match 58 exactly.
+ *   NXM_OF_ICMPV6_TYPE must be exactly 136.
+ *
+ * Format: 48-bit Ethernet MAC address.
+ *
+ * Masking: Not maskable. */
+#define NXM_NX_ND_TLL      NXM_HEADER  (0x0001, 25, 6)
+
 
 /* ## --------------------- ## */
 /* ## Requests and replies. ## */
diff --git a/include/openvswitch/datapath-protocol.h b/include/openvswitch/datapath-protocol.h
index 921d6a2..700795d 100644
--- a/include/openvswitch/datapath-protocol.h
+++ b/include/openvswitch/datapath-protocol.h
@@ -316,6 +316,7 @@ enum odp_key_type {
 	ODP_KEY_ATTR_UDP,       /* struct odp_key_udp */
 	ODP_KEY_ATTR_ICMP,      /* struct odp_key_icmp */
 	ODP_KEY_ATTR_ARP,       /* struct odp_key_arp */
+	ODP_KEY_ATTR_ND,        /* struct odp_key_nd */
 	__ODP_KEY_ATTR_MAX
 };
 
@@ -368,6 +369,12 @@ struct odp_key_arp {
     uint8_t  arp_tha[6];
 };
 
+struct odp_key_nd {
+    uint32_t nd_target[4];
+    uint8_t  nd_sll[6];
+    uint8_t  nd_tll[6];
+};
+
 /**
  * enum odp_flow_attr - attributes for %ODP_FLOW_* commands.
  * @ODP_FLOW_ATTR_KEY: Nested %ODP_KEY_ATTR_* attributes specifying the flow
diff --git a/lib/classifier.c b/lib/classifier.c
index b606f6f..56c577c 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 c82a484..d3121bf 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 75efbdd..58ddf6a 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -230,6 +230,95 @@ 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 int
+parse_icmpv6(struct ofpbuf *b, struct flow *flow, int icmp_len)
+{
+    const struct icmp6_hdr *icmp = pull_icmpv6(b);
+
+    if (!icmp) {
+        return -EINVAL;
+    }
+
+    /* 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 -EINVAL;
+        }
+        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_try_pull(b, 8);
+            if (!nd_opt || !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 0;
+
+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 -EINVAL;
+
+}
+
 /* Initializes 'flow' members from 'packet', 'tun_id', and 'in_port.
  * Initializes 'packet' header pointers as follows:
  *
@@ -344,10 +433,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 83830e6..b7ecebd 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 f962e5c..fae7377 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 e4ed43f..7926898 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 aefcb65..a76ad1f 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 6270f04..ea60d34 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"
@@ -200,6 +201,7 @@ odp_flow_key_attr_len(uint16_t type)
     case ODP_KEY_ATTR_UDP: return sizeof(struct odp_key_udp);
     case ODP_KEY_ATTR_ICMP: return sizeof(struct odp_key_icmp);
     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:
@@ -240,6 +242,7 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
     const struct odp_key_udp *udp_key;
     const struct odp_key_icmp *icmp_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: ",
@@ -338,6 +341,25 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
         ds_put_char(ds, ')');
         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;
@@ -465,6 +487,18 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
                                                 sizeof *icmp_key);
             icmp_key->icmp_type = ntohs(flow->tp_src);
             icmp_key->icmp_code = ntohs(flow->tp_dst);
+
+            if (icmp_key->icmp_type == ND_NEIGHBOR_SOLICIT
+                    || icmp_key->icmp_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);
+            }
         }
     }
 }
@@ -492,6 +526,7 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
         const struct odp_key_udp *udp_key;
         const struct odp_key_icmp *icmp_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);
@@ -614,6 +649,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_ICMP, 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) {
@@ -658,10 +704,17 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
         }
         return 0;
 
+    case ODP_KEY_ATTR_ICMP:
+        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_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 8ec09f3..074df87 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 7349c40..38902fe 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 4d89e0a..1125b83 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;
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index a86588b..111ed88 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -71,6 +71,9 @@ in_port=3 icmp6,ipv6_src=2001:db8:3c4d:1::1,icmp_type=134 actions=drop
 udp dl_vlan_pcp=7 idle_timeout=5 actions=strip_vlan output:0
 tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1
 udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,output:1
+icmp6,icmp_type=135,nd_target=FEC0::1234:F045:8FFF:1111:FE4E:0571 actions=drop
+icmp6,icmp_type=135,nd_sll=00:0A:E4:25:6B:B0 actions=drop
+icmp6,icmp_type=136,nd_target=FEC0::1234:F045:8FFF:1111:FE4E:0571,nd_tll=00:0A:E4:25:6B:B1 actions=drop
 cookie=0x123456789abcdef hard_timeout=10 priority=60000 actions=controller
 actions=note:41.42.43,note:00.01.02.03.04.05.06.07,note
 tun_id=0x1234,cookie=0x5678,actions=flood
@@ -92,6 +95,9 @@ NXT_FLOW_MOD: ADD icmp6,in_port=3,ipv6_src=2001:db8:3c4d:1::1,icmp_type=134 acti
 NXT_FLOW_MOD: ADD udp,dl_vlan_pcp=7 idle:5 actions=strip_vlan,output:0
 NXT_FLOW_MOD: ADD tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1
 NXT_FLOW_MOD: ADD udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,output:1
+NXT_FLOW_MOD: ADD icmp6,icmp_type=135,nd_target=fec0:0:1234:f045:8fff:1111:fe4e:571 actions=drop
+NXT_FLOW_MOD: ADD icmp6,icmp_type=135,nd_sll=00:0a:e4:25:6b:b0 actions=drop
+NXT_FLOW_MOD: ADD icmp6,icmp_type=136,nd_target=fec0:0:1234:f045:8fff:1111:fe4e:571,nd_tll=00:0a:e4:25:6b:b1 actions=drop
 NXT_FLOW_MOD: ADD priority=60000 cookie:0x123456789abcdef hard:10 actions=CONTROLLER:65535
 NXT_FLOW_MOD: ADD actions=note:41.42.43.00.00.00,note:00.01.02.03.04.05.06.07.00.00.00.00.00.00,note:00.00.00.00.00.00
 NXT_FLOW_MOD: ADD tun_id=0x1234 cookie:0x5678 actions=FLOOD
@@ -116,6 +122,9 @@ in_port=3 icmp6,ipv6_src=2001:db8:3c4d:1::1,icmp_type=134 actions=drop
 udp dl_vlan_pcp=7 idle_timeout=5 actions=strip_vlan output:0
 tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1
 udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,output:1
+icmp6,icmp_type=135,nd_target=FEC0::1234:F045:8FFF:1111:FE4E:0571 actions=drop
+icmp6,icmp_type=135,nd_sll=00:0A:E4:25:6B:B0 actions=drop
+icmp6,icmp_type=136,nd_target=FEC0::1234:F045:8FFF:1111:FE4E:0571,nd_tll=00:0A:E4:25:6B:B1 actions=drop
 cookie=0x123456789abcdef hard_timeout=10 priority=60000 actions=controller
 actions=note:41.42.43,note:00.01.02.03.04.05.06.07,note
 tun_id=0x1234,cookie=0x5678,actions=flood
@@ -137,6 +146,9 @@ NXT_FLOW_MOD: ADD NXM_OF_IN_PORT(0003), NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_SRC(2
 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800), NXM_OF_VLAN_TCI_W(f000/f000), NXM_OF_IP_PROTO(11) idle:5 actions=strip_vlan,output:0
 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800), NXM_OF_IP_SRC(c0a80003), NXM_OF_IP_PROTO(06), NXM_OF_TCP_DST(0050) actions=set_queue:37,output:1
 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800), NXM_OF_IP_SRC(c0a80003), NXM_OF_IP_PROTO(11), NXM_OF_UDP_DST(0035) actions=pop_queue,output:1
+NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(87), NXM_NX_ND_TARGET(fec000001234f0458fff1111fe4e0571) actions=drop
+NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(87), NXM_NX_ND_SLL(000ae4256bb0) actions=drop
+NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(88), NXM_NX_ND_TARGET(fec000001234f0458fff1111fe4e0571), NXM_NX_ND_TLL(000ae4256bb1) actions=drop
 NXT_FLOW_MOD: ADD <any> cookie:0x123456789abcdef hard:10 pri:60000 actions=CONTROLLER:65535
 NXT_FLOW_MOD: ADD <any> actions=note:41.42.43.00.00.00,note:00.01.02.03.04.05.06.07.00.00.00.00.00.00,note:00.00.00.00.00.00
 NXT_FLOW_MOD: ADD NXM_NX_TUN_ID(0000000000001234) cookie:0x5678 actions=FLOOD
@@ -267,6 +279,18 @@ NXM_OF_ETH_TYPE(0800) NXM_NX_IPV6_DST(20010db83c4d00010002000300040005)
 NXM_OF_ETH_TYPE(86dd) NXM_NX_IPV6_DST_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000)
 NXM_OF_ETH_TYPE(0800) NXM_NX_IPV6_DST_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000)
 
+# ND source hardware address
+NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(87) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_SLL(0002e30f80a4)
+NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(88) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_SLL(0002e30f80a4)
+NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3b) NXM_NX_ICMPV6_TYPE(87) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_SLL(0002e30f80a4)
+NXM_OF_ETH_TYPE(0800) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(87) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_SLL(0002e30f80a4)
+
+# ND destination hardware address
+NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(88) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_TLL(0002e30f80a4)
+NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(87) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_TLL(0002e30f80a4)
+NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3b) NXM_NX_ICMPV6_TYPE(87) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_TLL(0002e30f80a4)
+NXM_OF_ETH_TYPE(0800) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(88) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_TLL(0002e30f80a4)
+
 # Tunnel ID.
 NXM_NX_TUN_ID(00000000abcdef01)
 NXM_NX_TUN_ID_W(84200000abcdef01/84200000FFFFFFFF)
@@ -407,6 +431,18 @@ nx_pull_match() returned error 44010104
 NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_DST_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000)
 nx_pull_match() returned error 44010104
 
+# ND source hardware address
+NXM_OF_ETH_TYPE(86dd), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(87), NXM_NX_ND_TARGET(20010db83c4d00010002000300040005), NXM_NX_ND_SLL(0002e30f80a4)
+nx_pull_match() returned error 44010104
+nx_pull_match() returned error 44010104
+nx_pull_match() returned error 44010104
+
+# ND destination hardware address
+NXM_OF_ETH_TYPE(86dd), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(88), NXM_NX_ND_TARGET(20010db83c4d00010002000300040005), NXM_NX_ND_TLL(0002e30f80a4)
+nx_pull_match() returned error 44010104
+nx_pull_match() returned error 44010104
+nx_pull_match() returned error 44010104
+
 # Tunnel ID.
 NXM_NX_TUN_ID(00000000abcdef01)
 NXM_NX_TUN_ID_W(84200000abcdef01/84200000ffffffff)
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 3742552..95b0884 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -388,6 +388,24 @@ groups of 16-bits of zeros.  The optional \fInetmask\fR allows
 restricting a match to an IPv6 address prefix.  A netmask is specified
 as a CIDR block (e.g. \fB2001:db8:3c4d:1::/64\fR).
 .
+.IP \fBnd_target=\fIipv6\fR
+When \fBdl_type\fR, \fBnw_proto\fR, and \fBicmp_type\fR specify
+IPv6 Neighbor Discovery (ICMPv6 type 135 or 136), matches the target address
+\fIipv6\fR.  \fIipv6\fR is in the same format described earlier for the
+\fBipv6_src\fR and \fBipv6_dst\fR fields.
+.
+.IP \fBnd_sll=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR
+When \fBdl_type\fR, \fBnw_proto\fR, and \fBicmp_type\fR specify IPv6
+Neighbor Solicitation (ICMPv6 type 135), matches the source link\-layer
+address option.  An address is specified as 6 pairs of hexadecimal
+digits delimited by colons.
+.
+.IP \fBnd_tll=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR
+When \fBdl_type\fR, \fBnw_proto\fR, and \fBicmp_type\fR specify IPv6
+Neighbor Advertisement (ICMPv6 type 136), matches the target link\-layer
+address option.  An address is specified as 6 pairs of hexadecimal
+digits delimited by colons.
+.
 .IP \fBtun_id=\fItunnel-id\fR[\fB/\fImask\fR]
 Matches tunnel identifier \fItunnel-id\fR.  Only packets that arrive
 over a tunnel that carries a key (e.g. GRE with the RFC 2890 key
-- 
1.7.1





More information about the dev mailing list