[ovs-dev] [IPv6 IV: A New Hope 5/6] nicira-ext: Support matching IPv6 traffic.

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


Provides ability to match over IPv6 traffic in the same manner as IPv4.
Currently, the matching fields include:

    - IPv6 source and destination addresses (ipv6_src and ipv6_dst)
    - Traffic Class (nw_tos)
    - Next Header (nw_proto)
    - ICMPv6 Type and Code (icmp_type and icmp_code)
    - TCP and UDP Ports over IPv6 (tp_src and tp_dst)

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

Signed-off-by: Justin Pettit <jpettit at nicira.com>
---
 DESIGN                                             |   76 +++++++
 Makefile.am                                        |    1 +
 acinclude.m4                                       |    2 +
 datapath/flow.c                                    |  206 +++++++++++++++---
 datapath/flow.h                                    |   12 +-
 datapath/linux-2.6/Modules.mk                      |    1 +
 .../linux-2.6/compat-2.6/include/linux/icmpv6.h    |   13 +
 include/openflow/nicira-ext.h                      |   36 +++-
 include/openvswitch/datapath-protocol.h            |    8 +
 lib/classifier.c                                   |  127 ++++++++++-
 lib/classifier.h                                   |    6 +
 lib/flow.c                                         |  232 +++++++++++++++++++-
 lib/flow.h                                         |   18 +-
 lib/nx-match.c                                     |  163 +++++++++++++-
 lib/nx-match.def                                   |   72 ++++---
 lib/nx-match.h                                     |    8 +-
 lib/odp-util.c                                     |   87 +++++++-
 lib/odp-util.h                                     |    4 +-
 lib/ofp-parse.c                                    |   66 ++++++-
 lib/ofp-util.c                                     |    9 +
 lib/packets.c                                      |  120 ++++++++++
 lib/packets.h                                      |   32 +++
 lib/socket-util.c                                  |   14 ++
 lib/socket-util.h                                  |    1 +
 tests/ovs-ofctl.at                                 |   48 ++++
 tests/test-packets.c                               |  116 ++++++++++
 utilities/ovs-ofctl.8.in                           |   84 +++++--
 27 files changed, 1417 insertions(+), 145 deletions(-)
 create mode 100644 DESIGN
 create mode 100644 datapath/linux-2.6/compat-2.6/include/linux/icmpv6.h

diff --git a/DESIGN b/DESIGN
new file mode 100644
index 0000000..56e2605
--- /dev/null
+++ b/DESIGN
@@ -0,0 +1,76 @@
+                     Design Decisions In Open vSwitch
+                     ================================
+
+This document describes design decisions that went into implementing
+Open vSwitch.  While we believe these to be reasonable decisions, it is
+impossible to predict how Open vSwitch will be used in all environments.
+Understanding assumptions made by Open vSwitch is critical to a
+successful deployment.  The end of this document contains contact
+information that can be used to let us know how we can make Open vSwitch
+more generally useful.
+
+
+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.
+
+IPv6 was not designed to interact well with middle-boxes.  This,
+combined with Open vSwitch's stateless nature, have affected the
+processing of IPv6 traffic, which is detailed below.
+
+Extension Headers
+-----------------
+
+The base IPv6 header is incredibly simple with the intention of only
+containing information relevant for routing packets between two
+endpoints.  IPv6 relies heavily on the use of extension headers to
+provide any other functionality.  Unfortunately, the extension headers
+were designed in such a way that it is impossible to move to the next
+header (including the layer-4 payload) unless the current header is
+understood.
+
+Open vSwitch will process the following extension headers and continue
+to the next header:
+
+    * Fragment (see the next section)
+    * AH (Authentication Header)
+    * Hop-by-Hop Options
+    * Routing
+    * Destination Options
+
+When a header is encountered that is not in that list, it is considered
+"terminal".  A terminal header's IPv6 protocol value is stored in
+"nw_proto" for matching purposes.  If a terminal header is TCP, UDP, or
+ICMPv6, the packet will be further processed in an attempt to extract
+layer-4 information.
+
+Fragments
+---------
+
+IPv6 requires that every link in the internet have an MTU of 1280 octets
+or greater (RFC 2460).  As such, a terminal header (as described above in
+"Extension Headers") in the first fragment should generally be
+reachable.  In this case, the terminal header's IPv6 protocol type is
+stored in the "nw_proto" field for matching purposes.  If a terminal
+header cannot be found in the first fragment (one with a fragment offset
+of zero), the "nw_proto" field is set to 0.  Subsequent fragments (those
+with a non-zero fragment offset) have the "nw_proto" field set to the
+IPv6 protocol type for fragments (44).
+
+Jumbograms
+----------
+
+An IPv6 jumbogram (RFC 2675) is a packet containing a payload longer
+than 65,535 octets.  A jumbogram is only relevant in subnets with a link
+MTU greater than 65,575 octets, and are not required to be supported on
+nodes that do not connect to link with such large MTUs.  Currently, Open
+vSwitch doesn't process jumbograms.
+
+
+Suggestions
+===========
+
+Suggestions to improve Open vSwitch are welcome at discuss at openvswitch.org.
diff --git a/Makefile.am b/Makefile.am
index deae512..71a0652 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -31,6 +31,7 @@ CLEAN_LOCAL =
 DISTCLEANFILES =
 EXTRA_DIST = \
 	CodingStyle \
+	DESIGN \
 	INSTALL.KVM \
 	INSTALL.Linux \
 	INSTALL.OpenFlow \
diff --git a/acinclude.m4 b/acinclude.m4
index 6fc1c7a..0cd1427 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -184,6 +184,8 @@ AC_DEFUN([OVS_CHECK_LINUX26_COMPAT], [
   OVS_GREP_IFELSE([$KSRC26/include/linux/skbuff.h], [skb_cow_head])
   OVS_GREP_IFELSE([$KSRC26/include/linux/skbuff.h], [skb_transport_header],
                   [OVS_DEFINE([HAVE_SKBUFF_HEADER_HELPERS])])
+  OVS_GREP_IFELSE([$KSRC26/include/linux/icmpv6.h], [icmp6_hdr],
+                  [OVS_DEFINE([HAVE_ICMP6_HDR])])
   OVS_GREP_IFELSE([$KSRC26/include/linux/skbuff.h], [skb_warn_if_lro],
                   [OVS_DEFINE([HAVE_SKB_WARN_LRO])])
 
diff --git a/datapath/flow.c b/datapath/flow.c
index d83c17d..a0688ec 100644
--- a/datapath/flow.c
+++ b/datapath/flow.c
@@ -24,11 +24,14 @@
 #include <linux/if_arp.h>
 #include <linux/if_ether.h>
 #include <linux/ip.h>
+#include <linux/ipv6.h>
 #include <linux/tcp.h>
 #include <linux/udp.h>
 #include <linux/icmp.h>
+#include <linux/icmpv6.h>
 #include <net/inet_ecn.h>
 #include <net/ip.h>
+#include <net/ipv6.h>
 
 static struct kmem_cache *flow_cache;
 static unsigned int hash_seed __read_mostly;
@@ -95,6 +98,63 @@ u64 flow_used_time(unsigned long flow_jiffies)
 	return cur_ms - idle_ms;
 }
 
+static int parse_ipv6hdr(struct sk_buff *skb, struct sw_flow_key *key)
+{
+	unsigned int nh_ofs = skb_network_offset(skb);
+	unsigned int nh_len;
+	int payload_ofs;
+	int payload_len;
+	struct ipv6hdr *nh;
+	uint8_t nexthdr;
+
+	if (unlikely(skb->len < nh_ofs + sizeof(*nh)))
+		return -EINVAL;
+
+	nh = ipv6_hdr(skb);
+	nexthdr = nh->nexthdr;
+	payload_ofs = (u8 *)(nh + 1) - skb->data;
+	payload_len = ntohs(nh->payload_len);
+
+	memcpy(key->ipv6_src, nh->saddr.in6_u.u6_addr8, sizeof(key->ipv6_src));
+	memcpy(key->ipv6_dst, nh->daddr.in6_u.u6_addr8, sizeof(key->ipv6_dst));
+	key->nw_tos = ipv6_get_dsfield(nh) & ~INET_ECN_MASK;
+	key->nw_proto = NEXTHDR_NONE;
+
+	/* We don't process jumbograms. */
+	if (!payload_len)
+		return -EINVAL;
+
+	if (unlikely(skb->len < nh_ofs + sizeof(*nh) + payload_len))
+		return -EINVAL;
+
+	payload_ofs = ipv6_skip_exthdr(skb, payload_ofs, &nexthdr);
+	if (payload_ofs < 0) {
+		return -EINVAL;
+	}
+	nh_len = payload_ofs - skb_network_offset(skb);
+
+	/* Ensure that the payload length claimed is at least large enough
+	 * for the headers we've already processed. */
+	if (payload_len < nh_len - sizeof(*nh))
+		return -EINVAL;
+
+	/* Pull enough header bytes to account for the IP header plus the
+	 * longest transport header that we parse, currently 20 bytes for TCP.
+	 * To dig deeper than the transport header, transport parsers may need
+	 * to pull more header bytes.
+	 */
+	if (unlikely(!pskb_may_pull(skb, min(nh_ofs + nh_len + 20, skb->len))))
+		return -ENOMEM;
+
+	skb_set_transport_header(skb, nh_ofs + nh_len);
+	key->nw_proto = nexthdr;
+	return nh_len;
+}
+
+static bool icmp6hdr_ok(struct sk_buff *skb)
+{
+	return skb->len >= skb_transport_offset(skb) + sizeof(struct icmp6hdr);
+}
 
 #define TCP_FLAGS_OFFSET 13
 #define TCP_FLAG_MASK 0x3f
@@ -274,10 +334,10 @@ static __be16 parse_ethertype(struct sk_buff *skb)
  *    - skb->network_header: just past the Ethernet header, or just past the
  *      VLAN header, to the first byte of the Ethernet payload.
  *
- *    - skb->transport_header: If key->dl_type is ETH_P_IP on output, then just
- *      past the IPv4 header, if one is present and of a correct length,
- *      otherwise the same as skb->network_header.  For other key->dl_type
- *      values it is left untouched.
+ *    - skb->transport_header: If key->dl_type is ETH_P_IP or ETH_P_IPV6
+ *      on output, then just past the IP header, if one is present and
+ *      of a correct length, otherwise the same as skb->network_header.
+ *      For other key->dl_type values it is left untouched.
  */
 int flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key,
 		 bool *is_frag)
@@ -291,7 +351,8 @@ int flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key,
 
 	/*
 	 * We would really like to pull as many bytes as we could possibly
-	 * want to parse into the linear data area.  Currently that is:
+	 * want to parse into the linear data area.  Currently, for IPv4,
+	 * that is:
 	 *
 	 *    14     Ethernet header
 	 *     4     VLAN header
@@ -339,8 +400,8 @@ int flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key,
 		}
 
 		nh = ip_hdr(skb);
-		key->nw_src = nh->saddr;
-		key->nw_dst = nh->daddr;
+		key->ipv4_src = nh->saddr;
+		key->ipv4_dst = nh->daddr;
 		key->nw_tos = nh->tos & ~INET_ECN_MASK;
 		key->nw_proto = nh->protocol;
 
@@ -388,12 +449,47 @@ int flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key,
 
 			if (key->nw_proto == ARPOP_REQUEST
 					|| key->nw_proto == ARPOP_REPLY) {
-				memcpy(&key->nw_src, arp->ar_sip, sizeof(key->nw_src));
-				memcpy(&key->nw_dst, arp->ar_tip, sizeof(key->nw_dst));
+				memcpy(&key->ipv4_src, arp->ar_sip, sizeof(key->ipv4_src));
+				memcpy(&key->ipv4_dst, arp->ar_tip, sizeof(key->ipv4_dst));
 				memcpy(key->arp_sha, arp->ar_sha, ETH_ALEN);
 				memcpy(key->arp_tha, arp->ar_tha, ETH_ALEN);
 			}
 		}
+	} else if (key->dl_type == htons(ETH_P_IPV6)) {
+		int nh_len;             /* IPv6 Header + Extensions */
+
+		nh_len = parse_ipv6hdr(skb, key);
+		if (unlikely(nh_len < 0)) {
+			if (nh_len == -EINVAL) {
+				skb->transport_header = skb->network_header;
+				return 0;
+			}
+			return nh_len;
+		}
+
+		/* Transport layer. */
+		if (key->nw_proto == NEXTHDR_TCP) {
+			if (tcphdr_ok(skb)) {
+				struct tcphdr *tcp = tcp_hdr(skb);
+				key->tp_src = tcp->source;
+				key->tp_dst = tcp->dest;
+			}
+		} else if (key->nw_proto == NEXTHDR_UDP) {
+			if (udphdr_ok(skb)) {
+				struct udphdr *udp = udp_hdr(skb);
+				key->tp_src = udp->source;
+				key->tp_dst = udp->dest;
+			}
+		} 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);
+			}
+		}
 	}
 	return 0;
 }
@@ -440,6 +536,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 			[ODP_KEY_ATTR_8021Q] = sizeof(struct odp_key_8021q),
 			[ODP_KEY_ATTR_ETHERTYPE] = 2,
 			[ODP_KEY_ATTR_IPV4] = sizeof(struct odp_key_ipv4),
+			[ODP_KEY_ATTR_IPV6] = sizeof(struct odp_key_ipv6),
 			[ODP_KEY_ATTR_TCP] = sizeof(struct odp_key_tcp),
 			[ODP_KEY_ATTR_UDP] = sizeof(struct odp_key_udp),
 			[ODP_KEY_ATTR_ICMP] = sizeof(struct odp_key_icmp),
@@ -449,6 +546,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 		const struct odp_key_ethernet *eth_key;
 		const struct odp_key_8021q *q_key;
 		const struct odp_key_ipv4 *ipv4_key;
+		const struct odp_key_ipv6 *ipv6_key;
 		const struct odp_key_tcp *tcp_key;
 		const struct odp_key_udp *udp_key;
 		const struct odp_key_icmp *icmp_key;
@@ -499,15 +597,30 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 			if (swkey->dl_type != htons(ETH_P_IP))
 				return -EINVAL;
 			ipv4_key = nla_data(nla);
-			swkey->nw_src = ipv4_key->ipv4_src;
-			swkey->nw_dst = ipv4_key->ipv4_dst;
+			swkey->ipv4_src = ipv4_key->ipv4_src;
+			swkey->ipv4_dst = ipv4_key->ipv4_dst;
 			swkey->nw_proto = ipv4_key->ipv4_proto;
 			swkey->nw_tos = ipv4_key->ipv4_tos;
 			if (swkey->nw_tos & INET_ECN_MASK)
 				return -EINVAL;
 			break;
 
+		case TRANSITION(ODP_KEY_ATTR_ETHERTYPE, ODP_KEY_ATTR_IPV6):
+			if (swkey->dl_type != htons(ETH_P_IPV6))
+				return -EINVAL;
+			ipv6_key = nla_data(nla);
+			memcpy(swkey->ipv6_src, ipv6_key->ipv6_src,
+					sizeof(swkey->ipv6_src));
+			memcpy(swkey->ipv6_dst, ipv6_key->ipv6_dst,
+					sizeof(swkey->ipv6_dst));
+			swkey->nw_proto = ipv6_key->ipv6_proto;
+			swkey->nw_tos = ipv6_key->ipv6_tos;
+			if (swkey->nw_tos & INET_ECN_MASK)
+				return -EINVAL;
+			break;
+
 		case TRANSITION(ODP_KEY_ATTR_IPV4, ODP_KEY_ATTR_TCP):
+		case TRANSITION(ODP_KEY_ATTR_IPV6, ODP_KEY_ATTR_TCP):
 			if (swkey->nw_proto != IPPROTO_TCP)
 				return -EINVAL;
 			tcp_key = nla_data(nla);
@@ -516,6 +629,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 			break;
 
 		case TRANSITION(ODP_KEY_ATTR_IPV4, ODP_KEY_ATTR_UDP):
+		case TRANSITION(ODP_KEY_ATTR_IPV6, ODP_KEY_ATTR_UDP):
 			if (swkey->nw_proto != IPPROTO_UDP)
 				return -EINVAL;
 			udp_key = nla_data(nla);
@@ -524,7 +638,9 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 			break;
 
 		case TRANSITION(ODP_KEY_ATTR_IPV4, ODP_KEY_ATTR_ICMP):
-			if (swkey->nw_proto != IPPROTO_ICMP)
+		case TRANSITION(ODP_KEY_ATTR_IPV6, ODP_KEY_ATTR_ICMP):
+			if (swkey->nw_proto != IPPROTO_ICMP
+					&& swkey->nw_proto != IPPROTO_ICMPV6)
 				return -EINVAL;
 			icmp_key = nla_data(nla);
 			swkey->tp_src = htons(icmp_key->icmp_type);
@@ -535,8 +651,8 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 			if (swkey->dl_type != htons(ETH_P_ARP))
 				return -EINVAL;
 			arp_key = nla_data(nla);
-			swkey->nw_src = arp_key->arp_sip;
-			swkey->nw_dst = arp_key->arp_tip;
+			swkey->ipv4_src = arp_key->arp_sip;
+			swkey->ipv4_dst = arp_key->arp_tip;
 			if (arp_key->arp_op & htons(0xff00))
 				return -EINVAL;
 			swkey->nw_proto = ntohs(arp_key->arp_op);
@@ -572,9 +688,11 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr)
 		return 0;
 
 	case ODP_KEY_ATTR_IPV4:
+	case ODP_KEY_ATTR_IPV6:
 		if (swkey->nw_proto == IPPROTO_TCP ||
 		    swkey->nw_proto == IPPROTO_UDP ||
-		    swkey->nw_proto == IPPROTO_ICMP)
+		    swkey->nw_proto == IPPROTO_ICMP ||
+		    swkey->nw_proto == IPPROTO_ICMPV6)
 			return -EINVAL;
 		return 0;
 
@@ -626,10 +744,39 @@ int flow_to_nlattrs(const struct sw_flow_key *swkey, struct sk_buff *skb)
 		if (!nla)
 			goto nla_put_failure;
 		ipv4_key = nla_data(nla);
-		ipv4_key->ipv4_src = swkey->nw_src;
-		ipv4_key->ipv4_dst = swkey->nw_dst;
+		ipv4_key->ipv4_src = swkey->ipv4_src;
+		ipv4_key->ipv4_dst = swkey->ipv4_dst;
 		ipv4_key->ipv4_proto = swkey->nw_proto;
 		ipv4_key->ipv4_tos = swkey->nw_tos;
+	} else if (swkey->dl_type == htons(ETH_P_IPV6)) {
+		struct odp_key_ipv6 *ipv6_key;
+
+		nla = nla_reserve(skb, ODP_KEY_ATTR_IPV6, sizeof(*ipv6_key));
+		if (!nla)
+			goto nla_put_failure;
+		ipv6_key = nla_data(nla);
+		memcpy(ipv6_key->ipv6_src, swkey->ipv6_src,
+				sizeof(ipv6_key->ipv6_src));
+		memcpy(ipv6_key->ipv6_dst, swkey->ipv6_dst,
+				sizeof(ipv6_key->ipv6_dst));
+		ipv6_key->ipv6_proto = swkey->nw_proto;
+		ipv6_key->ipv6_tos = swkey->nw_tos;
+	} else if (swkey->dl_type == htons(ETH_P_ARP)) {
+		struct odp_key_arp *arp_key;
+
+		nla = nla_reserve(skb, ODP_KEY_ATTR_ARP, sizeof(*arp_key));
+		if (!nla)
+			goto nla_put_failure;
+		arp_key = nla_data(nla);
+		arp_key->arp_sip = swkey->ipv4_src;
+		arp_key->arp_tip = swkey->ipv4_dst;
+		arp_key->arp_op = htons(swkey->nw_proto);
+		memcpy(arp_key->arp_sha, swkey->arp_sha, ETH_ALEN);
+		memcpy(arp_key->arp_tha, swkey->arp_tha, ETH_ALEN);
+	}
+
+	if (swkey->dl_type == htons(ETH_P_IP)
+			|| swkey->dl_type == htons(ETH_P_IPV6)) {
 
 		if (swkey->nw_proto == IPPROTO_TCP) {
 			struct odp_key_tcp *tcp_key;
@@ -649,7 +796,8 @@ int flow_to_nlattrs(const struct sw_flow_key *swkey, struct sk_buff *skb)
 			udp_key = nla_data(nla);
 			udp_key->udp_src = swkey->tp_src;
 			udp_key->udp_dst = swkey->tp_dst;
-		} else if (swkey->nw_proto == IPPROTO_ICMP) {
+		} else if (swkey->dl_type == htons(ETH_P_IP)
+				&& swkey->nw_proto == IPPROTO_ICMP) {
 			struct odp_key_icmp *icmp_key;
 
 			nla = nla_reserve(skb, ODP_KEY_ATTR_ICMP, sizeof(*icmp_key));
@@ -658,19 +806,17 @@ 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);
-		}
-	} else if (swkey->dl_type == htons(ETH_P_ARP)) {
-		struct odp_key_arp *arp_key;
+		} else if (swkey->dl_type == htons(ETH_P_IPV6)
+				&& swkey->nw_proto == IPPROTO_ICMPV6) {
+			struct odp_key_icmp *icmp_key;
 
-		nla = nla_reserve(skb, ODP_KEY_ATTR_ARP, sizeof(*arp_key));
-		if (!nla)
-			goto nla_put_failure;
-		arp_key = nla_data(nla);
-		arp_key->arp_sip = swkey->nw_src;
-		arp_key->arp_tip = swkey->nw_dst;
-		arp_key->arp_op = htons(swkey->nw_proto);
-		memcpy(arp_key->arp_sha, swkey->arp_sha, ETH_ALEN);
-		memcpy(arp_key->arp_tha, swkey->arp_tha, ETH_ALEN);
+			nla = nla_reserve(skb, ODP_KEY_ATTR_ICMP, sizeof(*icmp_key));
+			if (!nla)
+				goto nla_put_failure;
+			icmp_key = nla_data(nla);
+			icmp_key->icmp_type = ntohs(swkey->tp_src);
+			icmp_key->icmp_code = ntohs(swkey->tp_dst);
+		}
 	}
 
 	return 0;
diff --git a/datapath/flow.h b/datapath/flow.h
index b9af272..ee1c4c9 100644
--- a/datapath/flow.h
+++ b/datapath/flow.h
@@ -31,8 +31,16 @@ struct sw_flow_actions {
 
 struct sw_flow_key {
 	__be64	tun_id;     /* Encapsulating tunnel ID. */
-	__be32	nw_src;	    /* IP source address. */
-	__be32	nw_dst;	    /* IP destination address. */
+	union {
+		struct {
+			__be32	ipv4_src;	 /* IPv4 source address. */
+			__be32	ipv4_dst;	 /* IPv4 destination address. */
+		};
+		struct {
+			__be32	ipv6_src[4]; /* IPv6 source address. */
+			__be32	ipv6_dst[4]; /* IPv6 source 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. */
diff --git a/datapath/linux-2.6/Modules.mk b/datapath/linux-2.6/Modules.mk
index b7b9524..88c5769 100644
--- a/datapath/linux-2.6/Modules.mk
+++ b/datapath/linux-2.6/Modules.mk
@@ -17,6 +17,7 @@ openvswitch_headers += \
 	linux-2.6/compat-2.6/include/linux/err.h \
 	linux-2.6/compat-2.6/include/linux/genetlink.h \
 	linux-2.6/compat-2.6/include/linux/icmp.h \
+	linux-2.6/compat-2.6/include/linux/icmpv6.h \
 	linux-2.6/compat-2.6/include/linux/if.h \
 	linux-2.6/compat-2.6/include/linux/if_arp.h \
 	linux-2.6/compat-2.6/include/linux/if_ether.h \
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/icmpv6.h b/datapath/linux-2.6/compat-2.6/include/linux/icmpv6.h
new file mode 100644
index 0000000..f005a48
--- /dev/null
+++ b/datapath/linux-2.6/compat-2.6/include/linux/icmpv6.h
@@ -0,0 +1,13 @@
+#ifndef __LINUX_ICMPV6_WRAPPER_H
+#define __LINUX_ICMPV6_WRAPPER_H 1
+
+#include_next <linux/icmpv6.h>
+
+#ifndef HAVE_ICMP6_HDR
+static inline struct icmp6hdr *icmp6_hdr(const struct sk_buff *skb)
+{
+        return (struct icmp6hdr *)skb_transport_header(skb);
+}
+#endif
+
+#endif
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index 01e735d..f136ba5 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -388,6 +388,8 @@ OFP_ASSERT(sizeof(struct nx_action_pop_queue) == 16);
  *   - NXM_NX_TUN_ID
  *   - NXM_NX_ARP_SHA
  *   - NXM_NX_ARP_THA
+ *   - NXM_NX_ICMPV6_TYPE
+ *   - NXM_NX_ICMPV6_CODE
  *   - NXM_NX_REG(idx) for idx in the switch's accepted range.
  *
  * The following nxm_header values are potentially acceptable as 'dst':
@@ -904,7 +906,7 @@ enum nx_mp_algorithm {
 
 /* The "type of service" byte of the IP header, with the ECN bits forced to 0.
  *
- * Prereqs: NXM_OF_ETH_TYPE must match 0x0800 exactly.
+ * Prereqs: NXM_OF_ETH_TYPE must be either 0x0800 or 0x86dd.
  *
  * Format: 8-bit integer with 2 least-significant bits forced to 0.
  *
@@ -913,7 +915,7 @@ enum nx_mp_algorithm {
 
 /* The "protocol" byte in the IP header.
  *
- * Prereqs: NXM_OF_ETH_TYPE must match 0x0800 exactly.
+ * Prereqs: NXM_OF_ETH_TYPE must be either 0x0800 or 0x86dd.
  *
  * Format: 8-bit integer.
  *
@@ -936,7 +938,7 @@ enum nx_mp_algorithm {
 /* The source or destination port in the TCP header.
  *
  * Prereqs:
- *   NXM_OF_ETH_TYPE must match 0x0800 exactly.
+ *   NXM_OF_ETH_TYPE must be either 0x0800 or 0x86dd.
  *   NXM_OF_IP_PROTO must match 6 exactly.
  *
  * Format: 16-bit integer in network byte order.
@@ -948,7 +950,7 @@ enum nx_mp_algorithm {
 /* The source or destination port in the UDP header.
  *
  * Prereqs:
- *   NXM_OF_ETH_TYPE must match 0x0800 exactly.
+ *   NXM_OF_ETH_TYPE must match either 0x0800 or 0x86dd.
  *   NXM_OF_IP_PROTO must match 17 exactly.
  *
  * Format: 16-bit integer in network byte order.
@@ -1051,6 +1053,32 @@ enum nx_mp_algorithm {
 #define NXM_NX_ARP_SHA    NXM_HEADER  (0x0001, 17, 6)
 #define NXM_NX_ARP_THA    NXM_HEADER  (0x0001, 18, 6)
 
+/* The source or destination address in the IPv6 header.
+ *
+ * Prereqs: NXM_OF_ETH_TYPE must match 0x86dd exactly.
+ *
+ * Format: 128-bit IPv6 address.
+ *
+ * Masking: Only CIDR masks are allowed, that is, masks that consist of N
+ *   high-order bits set to 1 and the other 128-N bits set to 0. */
+#define NXM_NX_IPV6_SRC    NXM_HEADER  (0x0001, 19, 16)
+#define NXM_NX_IPV6_SRC_W  NXM_HEADER_W(0x0001, 19, 16)
+#define NXM_NX_IPV6_DST    NXM_HEADER  (0x0001, 20, 16)
+#define NXM_NX_IPV6_DST_W  NXM_HEADER_W(0x0001, 20, 16)
+
+/* The type or code in the ICMPv6 header.
+ *
+ * Prereqs:
+ *   NXM_OF_ETH_TYPE must match 0x86dd exactly.
+ *   NXM_OF_IP_PROTO must match 58 exactly.
+ *
+ * Format: 8-bit integer.
+ *
+ * Masking: Not maskable. */
+#define NXM_NX_ICMPV6_TYPE NXM_HEADER  (0x0001, 21, 1)
+#define NXM_NX_ICMPV6_CODE NXM_HEADER  (0x0001, 22, 1)
+
+
 /* ## --------------------- ## */
 /* ## Requests and replies. ## */
 /* ## --------------------- ## */
diff --git a/include/openvswitch/datapath-protocol.h b/include/openvswitch/datapath-protocol.h
index 4d1b17c..921d6a2 100644
--- a/include/openvswitch/datapath-protocol.h
+++ b/include/openvswitch/datapath-protocol.h
@@ -311,6 +311,7 @@ enum odp_key_type {
 	ODP_KEY_ATTR_8021Q,     /* struct odp_key_8021q */
 	ODP_KEY_ATTR_ETHERTYPE,	/* 16-bit Ethernet type */
 	ODP_KEY_ATTR_IPV4,      /* struct odp_key_ipv4 */
+	ODP_KEY_ATTR_IPV6,      /* struct odp_key_ipv6 */
 	ODP_KEY_ATTR_TCP,       /* struct odp_key_tcp */
 	ODP_KEY_ATTR_UDP,       /* struct odp_key_udp */
 	ODP_KEY_ATTR_ICMP,      /* struct odp_key_icmp */
@@ -337,6 +338,13 @@ struct odp_key_ipv4 {
 	uint8_t  ipv4_tos;
 };
 
+struct odp_key_ipv6 {
+    uint32_t ipv6_src[4];
+    uint32_t ipv6_dst[4];
+	uint8_t  ipv6_proto;
+	uint8_t  ipv6_tos;
+};
+
 struct odp_key_tcp {
 	ovs_be16 tcp_src;
 	ovs_be16 tcp_dst;
diff --git a/lib/classifier.c b/lib/classifier.c
index a4d53c6..b606f6f 100644
--- a/lib/classifier.c
+++ b/lib/classifier.c
@@ -333,6 +333,42 @@ cls_rule_set_arp_tha(struct cls_rule *rule, const uint8_t tha[ETH_ADDR_LEN])
     memcpy(rule->flow.arp_tha, tha, ETH_ADDR_LEN);
 }
 
+void
+cls_rule_set_ipv6_src(struct cls_rule *rule, const struct in6_addr *src)
+{
+    cls_rule_set_ipv6_src_masked(rule, src, &in6addr_exact);
+}
+
+bool
+cls_rule_set_ipv6_src_masked(struct cls_rule *rule, const struct in6_addr *src,
+                             const struct in6_addr *mask)
+{
+    if (flow_wildcards_set_ipv6_src_mask(&rule->wc, mask)) {
+        rule->flow.ipv6_src = ipv6_addr_bitand(src, mask);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void
+cls_rule_set_ipv6_dst(struct cls_rule *rule, const struct in6_addr *dst)
+{
+    cls_rule_set_ipv6_dst_masked(rule, dst, &in6addr_exact);
+}
+
+bool
+cls_rule_set_ipv6_dst_masked(struct cls_rule *rule, const struct in6_addr *dst,
+                             const struct in6_addr *mask)
+{
+    if (flow_wildcards_set_ipv6_dst_mask(&rule->wc, mask)) {
+        rule->flow.ipv6_dst = ipv6_addr_bitand(dst, mask);
+        return true;
+    } else {
+        return false;
+    }
+}
+
 /* 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
@@ -361,6 +397,27 @@ format_ip_netmask(struct ds *s, const char *name, ovs_be32 ip,
     }
 }
 
+static void
+format_ipv6_netmask(struct ds *s, const char *name,
+                    const struct in6_addr *addr,
+                    const struct in6_addr *netmask)
+{
+    if (!ipv6_mask_is_any(netmask)) {
+        ds_put_format(s, "%s=", name);
+        print_ipv6_addr(s, addr);
+        if (!ipv6_mask_is_exact(netmask)) {
+            if (ipv6_is_cidr(netmask)) {
+                int cidr_bits = ipv6_count_cidr_bits(netmask);
+                ds_put_format(s, "/%d", cidr_bits);
+            } else {
+                ds_put_char(s, '/');
+                print_ipv6_addr(s, netmask);
+            }
+        }
+        ds_put_char(s, ',');
+    }
+}
+
 void
 cls_rule_format(const struct cls_rule *rule, struct ds *s)
 {
@@ -395,6 +452,22 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
             } else {
                 ds_put_cstr(s, "ip,");
             }
+        } else if (f->dl_type == htons(ETH_TYPE_IPV6)) {
+            if (!(w & FWW_NW_PROTO)) {
+                skip_proto = true;
+                if (f->nw_proto == IPPROTO_ICMPV6) {
+                    ds_put_cstr(s, "icmp6,");
+                } else if (f->nw_proto == IPPROTO_TCP) {
+                    ds_put_cstr(s, "tcp6,");
+                } else if (f->nw_proto == IPPROTO_UDP) {
+                    ds_put_cstr(s, "udp6,");
+                } else {
+                    ds_put_cstr(s, "ipv6,");
+                    skip_proto = false;
+                }
+            } else {
+                ds_put_cstr(s, "ipv6,");
+            }
         } else if (f->dl_type == htons(ETH_TYPE_ARP)) {
             ds_put_cstr(s, "arp,");
         } else {
@@ -472,8 +545,13 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
     if (!skip_type && !(w & FWW_DL_TYPE)) {
         ds_put_format(s, "dl_type=0x%04"PRIx16",", ntohs(f->dl_type));
     }
-    format_ip_netmask(s, "nw_src", f->nw_src, wc->nw_src_mask);
-    format_ip_netmask(s, "nw_dst", f->nw_dst, wc->nw_dst_mask);
+    if (f->dl_type == htons(ETH_TYPE_IPV6)) {
+        format_ipv6_netmask(s, "ipv6_src", &f->ipv6_src, &wc->ipv6_src_mask);
+        format_ipv6_netmask(s, "ipv6_dst", &f->ipv6_dst, &wc->ipv6_dst_mask);
+    } else {
+        format_ip_netmask(s, "nw_src", f->nw_src, wc->nw_src_mask);
+        format_ip_netmask(s, "nw_dst", f->nw_dst, wc->nw_dst_mask);
+    }
     if (!skip_proto && !(w & FWW_NW_PROTO)) {
         if (f->dl_type == htons(ETH_TYPE_ARP)) {
             ds_put_format(s, "opcode=%"PRIu8",", f->nw_proto);
@@ -501,6 +579,13 @@ 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 (f->nw_proto == IPPROTO_ICMPV6) {
+        if (!(w & FWW_TP_SRC)) {
+            ds_put_format(s, "icmp_type=%"PRIu16",", ntohs(f->tp_src));
+        }
+        if (!(w & FWW_TP_DST)) {
+            ds_put_format(s, "icmp_code=%"PRIu16",", ntohs(f->tp_dst));
+        }
     } else {
         if (!(w & FWW_TP_SRC)) {
             ds_put_format(s, "tp_src=%"PRIu16",", ntohs(f->tp_src));
@@ -965,13 +1050,37 @@ next_rule_in_list(struct cls_rule *rule)
 }
 
 static bool
+ipv6_equal_except(const struct in6_addr *a, const struct in6_addr *b,
+                  const struct in6_addr *mask)
+{
+    int i;
+
+#ifdef s6_addr32
+    for (i=0; i<4; i++) {
+        if ((a->s6_addr32[i] ^ b->s6_addr32[i]) & mask->s6_addr32[i]) {
+            return false;
+        }
+    }
+#else
+    for (i=0; i<16; i++) {
+        if ((a->s6_addr[i] ^ b->s6_addr[i]) & mask->s6_addr[i]) {
+            return false;
+        }
+    }
+#endif
+
+    return true;
+}
+
+
+static bool
 flow_equal_except(const struct flow *a, const struct flow *b,
                   const struct flow_wildcards *wildcards)
 {
     const flow_wildcards_t wc = wildcards->wildcards;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 52 + FLOW_N_REGS * 4);
+    BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 84 + FLOW_N_REGS * 4);
 
     for (i = 0; i < FLOW_N_REGS; i++) {
         if ((a->regs[i] ^ b->regs[i]) & wildcards->reg_masks[i]) {
@@ -1000,7 +1109,11 @@ flow_equal_except(const struct flow *a, const struct flow *b,
             && (wc & FWW_NW_PROTO || a->nw_proto == b->nw_proto)
             && (wc & FWW_NW_TOS || a->nw_tos == b->nw_tos)
             && (wc & FWW_ARP_SHA || eth_addr_equals(a->arp_sha, b->arp_sha))
-            && (wc & FWW_ARP_THA || eth_addr_equals(a->arp_tha, b->arp_tha)));
+            && (wc & FWW_ARP_THA || eth_addr_equals(a->arp_tha, b->arp_tha))
+            && 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));
 }
 
 static void
@@ -1009,7 +1122,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 == 52 + 4 * FLOW_N_REGS);
+    BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 84 + 4 * FLOW_N_REGS);
 
     for (i = 0; i < FLOW_N_REGS; i++) {
         flow->regs[i] &= wildcards->reg_masks[i];
@@ -1052,4 +1165,8 @@ zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
     if (wc & FWW_ARP_THA) {
         memset(flow->arp_tha, 0, sizeof flow->arp_tha);
     }
+    flow->ipv6_src = ipv6_addr_bitand(&flow->ipv6_src,
+            &wildcards->ipv6_src_mask);
+    flow->ipv6_dst = ipv6_addr_bitand(&flow->ipv6_dst,
+            &wildcards->ipv6_dst_mask);
 }
diff --git a/lib/classifier.h b/lib/classifier.h
index 4014972..c82a484 100644
--- a/lib/classifier.h
+++ b/lib/classifier.h
@@ -103,6 +103,12 @@ void cls_rule_set_icmp_type(struct cls_rule *, uint8_t);
 void cls_rule_set_icmp_code(struct cls_rule *, uint8_t);
 void cls_rule_set_arp_sha(struct cls_rule *, const uint8_t[6]);
 void cls_rule_set_arp_tha(struct cls_rule *, const uint8_t[6]);
+void cls_rule_set_ipv6_src(struct cls_rule *, const struct in6_addr *);
+bool cls_rule_set_ipv6_src_masked(struct cls_rule *, const struct in6_addr *,
+                                  const struct in6_addr *);
+void cls_rule_set_ipv6_dst(struct cls_rule *, const struct in6_addr *);
+bool cls_rule_set_ipv6_dst_masked(struct cls_rule *, const struct in6_addr *,
+                                  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 684ec0f..75efbdd 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -16,8 +16,11 @@
 #include <config.h>
 #include <sys/types.h>
 #include "flow.h"
+#include <errno.h>
 #include <inttypes.h>
 #include <netinet/in.h>
+#include <netinet/icmp6.h>
+#include <netinet/ip6.h>
 #include <stdlib.h>
 #include <string.h>
 #include "byte-order.h"
@@ -80,6 +83,12 @@ pull_icmp(struct ofpbuf *packet)
     return ofpbuf_try_pull(packet, ICMP_HEADER_LEN);
 }
 
+static struct icmp6_hdr *
+pull_icmpv6(struct ofpbuf *packet)
+{
+    return ofpbuf_try_pull(packet, sizeof(struct icmp6_hdr));
+}
+
 static void
 parse_vlan(struct ofpbuf *b, struct flow *flow)
 {
@@ -122,6 +131,105 @@ parse_ethertype(struct ofpbuf *b)
     return llc->snap.snap_type;
 }
 
+static int
+parse_ipv6(struct ofpbuf *packet, struct flow *flow)
+{
+    struct ip6_hdr *nh;
+    int nh_len = sizeof(struct ip6_hdr);
+    int payload_len;
+    ovs_be32 tc_flow;
+    int nexthdr;
+
+    if (packet->size < sizeof *nh) {
+        return -EINVAL;
+    }
+
+    nh = packet->data;
+    nexthdr = nh->ip6_nxt;
+    payload_len = ntohs(nh->ip6_plen);
+
+    flow->ipv6_src = nh->ip6_src;
+    flow->ipv6_dst = nh->ip6_dst;
+
+    tc_flow = get_unaligned_be32(&nh->ip6_flow);
+    flow->nw_tos = (ntohl(tc_flow) >> 4) & IP_DSCP_MASK;
+    flow->nw_proto = IPPROTO_NONE;
+
+    /* We don't process jumbograms. */
+    if (!payload_len) {
+        return -EINVAL;
+    }
+
+    if (packet->size < sizeof *nh + payload_len) {
+        return -EINVAL;
+    }
+
+    while (1) {
+        if ((nexthdr != IPPROTO_HOPOPTS)
+                && (nexthdr != IPPROTO_ROUTING)
+                && (nexthdr != IPPROTO_DSTOPTS)
+                && (nexthdr != IPPROTO_AH)
+                && (nexthdr != IPPROTO_FRAGMENT)) {
+            /* It's either a terminal header (e.g., TCP, UDP) or one we
+             * don't understand.  In either case, we're done with the
+             * packet, so use it to fill in 'nw_proto'. */
+            break;
+        }
+
+        /* We only verify that at least 8 bytes of the next header are
+         * available, but many of these headers are longer.  Ensure that
+         * accesses within the extension header are within those first 8
+         * bytes. */
+        if (packet->size < nh_len + 8) {
+            return -EINVAL;
+        }
+
+        if ((nexthdr == IPPROTO_HOPOPTS)
+                || (nexthdr == IPPROTO_ROUTING)
+                || (nexthdr == IPPROTO_DSTOPTS)) {
+            /* These headers, while different, have the fields we care about
+             * in the same location and with the same interpretation. */
+            struct ip6_ext *ext_hdr;
+
+            ext_hdr = (struct ip6_ext *)((char *)packet->data + nh_len);
+            nexthdr = ext_hdr->ip6e_nxt;
+            nh_len += (ext_hdr->ip6e_len + 1) * 8;
+        } else if (nexthdr == IPPROTO_AH) {
+            /* A standard AH definition isn't available, but the fields
+             * we care about are in the same location as the generic
+             * option header--only the header length is calculated
+             * differently. */
+            struct ip6_ext *ext_hdr;
+
+            ext_hdr = (struct ip6_ext *)((char *)packet->data + nh_len);
+            nexthdr = ext_hdr->ip6e_nxt;
+            nh_len += (ext_hdr->ip6e_len + 2) * 4;
+        } else if (nexthdr == IPPROTO_FRAGMENT) {
+            struct ip6_frag *frag_hdr;
+
+            frag_hdr = (struct ip6_frag *)((char *)packet->data + nh_len);
+
+            nexthdr = frag_hdr->ip6f_nxt;
+            nh_len += sizeof *frag_hdr;
+
+            /* We only process the first fragment. */
+            if ((frag_hdr->ip6f_offlg & IP6F_OFF_MASK) != htons(0)) {
+                nexthdr = IPPROTO_FRAGMENT;
+                break;
+            }
+        }
+    }
+
+    /* The payload length claims to be smaller than the size of the
+     * headers we've already processed. */
+    if (payload_len < nh_len - sizeof *nh) {
+        return -EINVAL;
+    }
+
+    flow->nw_proto = nexthdr;
+    return nh_len;
+}
+
 /* Initializes 'flow' members from 'packet', 'tun_id', and 'in_port.
  * Initializes 'packet' header pointers as follows:
  *
@@ -209,6 +317,41 @@ flow_extract(struct ofpbuf *packet, ovs_be64 tun_id, uint16_t in_port,
                 retval = 1;
             }
         }
+    } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+        int nh_len;
+        const struct ip6_hdr *nh;
+
+        nh_len = parse_ipv6(&b, flow);
+        if (nh_len < 0) {
+            return 0;
+        }
+
+        nh = ofpbuf_pull(&b, nh_len);
+        if (nh) {
+            packet->l4 = b.data;
+            if (flow->nw_proto == IPPROTO_TCP) {
+                const struct tcp_header *tcp = pull_tcp(&b);
+                if (tcp) {
+                    flow->tp_src = tcp->tcp_src;
+                    flow->tp_dst = tcp->tcp_dst;
+                    packet->l7 = b.data;
+                }
+            } else if (flow->nw_proto == IPPROTO_UDP) {
+                const struct udp_header *udp = pull_udp(&b);
+                if (udp) {
+                    flow->tp_src = udp->udp_src;
+                    flow->tp_dst = udp->udp_dst;
+                    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);
+                    packet->l7 = b.data;
+                }
+            }
+        }
     } else if (flow->dl_type == htons(ETH_TYPE_ARP)) {
         const struct arp_eth_header *arp = pull_arp(&b);
         if (arp && arp->ar_hrd == htons(1)
@@ -229,6 +372,7 @@ flow_extract(struct ofpbuf *packet, ovs_be64 tun_id, uint16_t in_port,
             }
         }
     }
+
     return retval;
 }
 
@@ -273,17 +417,27 @@ flow_format(struct ds *ds, const struct flow *flow)
         ds_put_char(ds, '0');
     }
     ds_put_format(ds, ") mac"ETH_ADDR_FMT"->"ETH_ADDR_FMT
-                      " type%04"PRIx16
-                      " proto%"PRIu8
-                      " tos%"PRIu8
-                      " ip"IP_FMT"->"IP_FMT,
+                      " type%04"PRIx16,
                   ETH_ADDR_ARGS(flow->dl_src),
                   ETH_ADDR_ARGS(flow->dl_dst),
-                  ntohs(flow->dl_type),
-                  flow->nw_proto,
-                  flow->nw_tos,
-                  IP_ARGS(&flow->nw_src),
-                  IP_ARGS(&flow->nw_dst));
+                  ntohs(flow->dl_type));
+
+    if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+        ds_put_format(ds, " proto%"PRIu8" tos%"PRIu8" ipv6",
+                      flow->nw_proto, flow->nw_tos);
+        print_ipv6_addr(ds, &flow->ipv6_src);
+        ds_put_cstr(ds, "->");
+        print_ipv6_addr(ds, &flow->ipv6_dst);
+       
+    } else {
+        ds_put_format(ds, " proto%"PRIu8
+                          " tos%"PRIu8
+                          " ip"IP_FMT"->"IP_FMT,
+                      flow->nw_proto,
+                      flow->nw_tos,
+                      IP_ARGS(&flow->nw_src),
+                      IP_ARGS(&flow->nw_dst));
+    }
     if (flow->tp_src || flow->tp_dst) {
         ds_put_format(ds, " port%"PRIu16"->%"PRIu16,
                 ntohs(flow->tp_src), ntohs(flow->tp_dst));
@@ -313,6 +467,8 @@ flow_wildcards_init_catchall(struct flow_wildcards *wc)
     wc->tun_id_mask = htonll(0);
     wc->nw_src_mask = htonl(0);
     wc->nw_dst_mask = htonl(0);
+    wc->ipv6_src_mask = in6addr_any;
+    wc->ipv6_dst_mask = in6addr_any;
     memset(wc->reg_masks, 0, sizeof wc->reg_masks);
     wc->vlan_tci_mask = htons(0);
     wc->zero = 0;
@@ -327,6 +483,8 @@ flow_wildcards_init_exact(struct flow_wildcards *wc)
     wc->tun_id_mask = htonll(UINT64_MAX);
     wc->nw_src_mask = htonl(UINT32_MAX);
     wc->nw_dst_mask = htonl(UINT32_MAX);
+    wc->ipv6_src_mask = in6addr_exact;
+    wc->ipv6_dst_mask = in6addr_exact;
     memset(wc->reg_masks, 0xff, sizeof wc->reg_masks);
     wc->vlan_tci_mask = htons(UINT16_MAX);
     wc->zero = 0;
@@ -343,7 +501,9 @@ flow_wildcards_is_exact(const struct flow_wildcards *wc)
         || wc->tun_id_mask != htonll(UINT64_MAX)
         || wc->nw_src_mask != htonl(UINT32_MAX)
         || wc->nw_dst_mask != htonl(UINT32_MAX)
-        || wc->vlan_tci_mask != htons(UINT16_MAX)) {
+        || wc->vlan_tci_mask != htons(UINT16_MAX)
+        || !ipv6_mask_is_exact(&wc->ipv6_src_mask)
+        || !ipv6_mask_is_exact(&wc->ipv6_dst_mask)) {
         return false;
     }
 
@@ -370,6 +530,10 @@ flow_wildcards_combine(struct flow_wildcards *dst,
     dst->tun_id_mask = src1->tun_id_mask & src2->tun_id_mask;
     dst->nw_src_mask = src1->nw_src_mask & src2->nw_src_mask;
     dst->nw_dst_mask = src1->nw_dst_mask & src2->nw_dst_mask;
+    dst->ipv6_src_mask = ipv6_addr_bitand(&src1->ipv6_src_mask,
+                                        &src2->ipv6_src_mask);
+    dst->ipv6_dst_mask = ipv6_addr_bitand(&src1->ipv6_dst_mask,
+                                        &src2->ipv6_dst_mask);
     for (i = 0; i < FLOW_N_REGS; i++) {
         dst->reg_masks[i] = src1->reg_masks[i] & src2->reg_masks[i];
     }
@@ -383,7 +547,7 @@ flow_wildcards_hash(const struct flow_wildcards *wc)
     /* 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 == 24 + FLOW_N_REGS * 4);
+    BUILD_ASSERT_DECL(sizeof *wc == 56 + FLOW_N_REGS * 4);
     return hash_bytes(wc, sizeof *wc, 0);
 }
 
@@ -399,7 +563,9 @@ flow_wildcards_equal(const struct flow_wildcards *a,
         || a->tun_id_mask != b->tun_id_mask
         || a->nw_src_mask != b->nw_src_mask
         || a->nw_dst_mask != b->nw_dst_mask
-        || a->vlan_tci_mask != b->vlan_tci_mask) {
+        || a->vlan_tci_mask != b->vlan_tci_mask 
+        || !ipv6_addr_equals(&a->ipv6_src_mask, &b->ipv6_src_mask)
+        || !ipv6_addr_equals(&a->ipv6_dst_mask, &b->ipv6_dst_mask)) {
         return false;
     }
 
@@ -419,6 +585,7 @@ flow_wildcards_has_extra(const struct flow_wildcards *a,
                          const struct flow_wildcards *b)
 {
     int i;
+    struct in6_addr ipv6_masked;
 
     for (i = 0; i < FLOW_N_REGS; i++) {
         if ((a->reg_masks[i] & b->reg_masks[i]) != b->reg_masks[i]) {
@@ -426,6 +593,16 @@ flow_wildcards_has_extra(const struct flow_wildcards *a,
         }
     }
 
+    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;
+    }
+
+    ipv6_masked = ipv6_addr_bitand(&a->ipv6_dst_mask, &b->ipv6_dst_mask);
+    if (!ipv6_addr_equals(&ipv6_masked, &b->ipv6_dst_mask)) {
+        return true;
+    }
+
     return (a->wildcards & ~b->wildcards
             || (a->tun_id_mask & b->tun_id_mask) != b->tun_id_mask
             || (a->nw_src_mask & b->nw_src_mask) != b->nw_src_mask
@@ -462,6 +639,37 @@ flow_wildcards_set_nw_dst_mask(struct flow_wildcards *wc, ovs_be32 mask)
     return set_nw_mask(&wc->nw_dst_mask, mask);
 }
 
+static bool
+set_ipv6_mask(struct in6_addr *maskp, const struct in6_addr *mask)
+{
+    if (ipv6_is_cidr(mask)) {
+        *maskp = *mask;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/* Sets the IPv6 source wildcard mask to CIDR 'mask' (consisting of N
+ * high-order 1-bit and 128-N low-order 0-bits).  Returns true if successful,
+ * false if 'mask' is not a CIDR mask.  */
+bool
+flow_wildcards_set_ipv6_src_mask(struct flow_wildcards *wc,
+                                 const struct in6_addr *mask)
+{
+    return set_ipv6_mask(&wc->ipv6_src_mask, mask);
+}
+
+/* Sets the IPv6 destination wildcard mask to CIDR 'mask' (consisting of
+ * N high-order 1-bit and 128-N low-order 0-bits).  Returns true if
+ * successful, false if 'mask' is not a CIDR mask.  */
+bool
+flow_wildcards_set_ipv6_dst_mask(struct flow_wildcards *wc,
+                                 const struct in6_addr *mask)
+{
+    return set_ipv6_mask(&wc->ipv6_dst_mask, mask);
+}
+
 /* Sets the wildcard mask for register 'idx' in 'wc' to 'mask'.
  * (A 0-bit indicates a wildcard bit.) */
 void
diff --git a/lib/flow.h b/lib/flow.h
index a4cf60e..83830e6 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -43,8 +43,8 @@ BUILD_ASSERT_DECL(FLOW_N_REGS <= NXM_NX_MAX_REGS);
 struct flow {
     ovs_be64 tun_id;            /* Encapsulating tunnel ID. */
     uint32_t regs[FLOW_N_REGS]; /* Registers. */
-    ovs_be32 nw_src;            /* IP source address. */
-    ovs_be32 nw_dst;            /* IP destination address. */
+    ovs_be32 nw_src;            /* IPv4 source address. */
+    ovs_be32 nw_dst;            /* IPv4 destination address. */
     uint16_t in_port;           /* Input switch port. */
     ovs_be16 vlan_tci;          /* If 802.1Q, TCI | VLAN_CFI; otherwise 0. */
     ovs_be16 dl_type;           /* Ethernet frame type. */
@@ -56,15 +56,17 @@ struct flow {
     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. */
+    struct in6_addr ipv6_src;   /* IPv6 source address. */
+    struct in6_addr ipv6_dst;   /* IPv6 destination address. */
     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 (52 + FLOW_N_REGS * 4)
+#define FLOW_SIG_SIZE (84 + FLOW_N_REGS * 4)
 #define FLOW_PAD_SIZE 4
-BUILD_ASSERT_DECL(offsetof(struct flow, arp_tha) == FLOW_SIG_SIZE - 6);
-BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->arp_tha) == 6);
+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(sizeof(struct flow) == FLOW_SIG_SIZE + FLOW_PAD_SIZE);
 
 int flow_extract(struct ofpbuf *, uint64_t tun_id, uint16_t in_port,
@@ -132,6 +134,8 @@ struct flow_wildcards {
     uint32_t reg_masks[FLOW_N_REGS]; /* 1-bit in each significant regs bit. */
     ovs_be32 nw_src_mask;       /* 1-bit in each significant nw_src bit. */
     ovs_be32 nw_dst_mask;       /* 1-bit in each significant nw_dst bit. */
+    struct in6_addr ipv6_src_mask; /* 1-bit in each signficant ipv6_src bit. */
+    struct in6_addr ipv6_dst_mask; /* 1-bit in each signficant ipv6_dst bit. */
     ovs_be16 vlan_tci_mask;     /* 1-bit in each significant vlan_tci bit. */
     uint16_t zero;              /* Padding field set to zero. */
 };
@@ -143,6 +147,10 @@ bool flow_wildcards_is_exact(const struct flow_wildcards *);
 
 bool flow_wildcards_set_nw_src_mask(struct flow_wildcards *, ovs_be32);
 bool flow_wildcards_set_nw_dst_mask(struct flow_wildcards *, ovs_be32);
+bool flow_wildcards_set_ipv6_src_mask(struct flow_wildcards *,
+                                      const struct in6_addr *);
+bool flow_wildcards_set_ipv6_dst_mask(struct flow_wildcards *,
+                                      const struct in6_addr *);
 void flow_wildcards_set_reg_mask(struct flow_wildcards *,
                                  int idx, uint32_t mask);
 
diff --git a/lib/nx-match.c b/lib/nx-match.c
index afa88e2..f962e5c 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -46,7 +46,7 @@ enum {
 /* For each NXM_* field, define NFI_NXM_* as consecutive integers starting from
  * zero. */
 enum nxm_field_index {
-#define DEFINE_FIELD(HEADER, WILDCARD, DL_TYPE, NW_PROTO, WRITABLE) \
+#define DEFINE_FIELD(HEADER, WILDCARD, DL_TYPES, NW_PROTO, WRITABLE) \
         NFI_NXM_##HEADER,
 #include "nx-match.def"
     N_NXM_FIELDS
@@ -54,20 +54,22 @@ enum nxm_field_index {
 
 struct nxm_field {
     struct hmap_node hmap_node;
-    enum nxm_field_index index; /* NFI_* value. */
-    uint32_t header;            /* NXM_* value. */
-    flow_wildcards_t wildcard;  /* FWW_* bit, if exactly one. */
-    ovs_be16 dl_type;           /* dl_type prerequisite, if nonzero. */
-    uint8_t nw_proto;           /* nw_proto prerequisite, if nonzero. */
-    const char *name;           /* "NXM_*" string. */
-    bool writable;              /* Writable with NXAST_REG_{MOVE,LOAD}? */
+    enum nxm_field_index index;       /* NFI_* value. */
+    uint32_t header;                  /* NXM_* value. */
+    flow_wildcards_t wildcard;        /* FWW_* bit, if exactly one. */
+    ovs_be16 dl_type[N_NXM_DL_TYPES]; /* dl_type prerequisites. */
+    uint8_t nw_proto;                 /* nw_proto prerequisite, if nonzero. */
+    const char *name;                 /* "NXM_*" string. */
+    bool writable;                    /* Writable with NXAST_REG_{MOVE,LOAD}? */
 };
 
+
 /* All the known fields. */
 static struct nxm_field nxm_fields[N_NXM_FIELDS] = {
-#define DEFINE_FIELD(HEADER, WILDCARD, DL_TYPE, NW_PROTO, WRITABLE)     \
+#define DEFINE_FIELD(HEADER, WILDCARD, DL_TYPES, NW_PROTO, WRITABLE)     \
     { HMAP_NODE_NULL_INITIALIZER, NFI_NXM_##HEADER, NXM_##HEADER, WILDCARD, \
-      CONSTANT_HTONS(DL_TYPE), NW_PROTO, "NXM_" #HEADER, WRITABLE },
+        DL_CONVERT DL_TYPES, NW_PROTO, "NXM_" #HEADER, WRITABLE },
+#define DL_CONVERT(T1, T2) { CONSTANT_HTONS(T1), CONSTANT_HTONS(T2) }
 #include "nx-match.def"
 };
 
@@ -285,6 +287,50 @@ parse_nxm_entry(struct cls_rule *rule, const struct nxm_field *f,
             return 0;
         }
 
+        /* IPv6 addresses. */
+    case NFI_NXM_NX_IPV6_SRC:
+        if (!ipv6_mask_is_any(&wc->ipv6_src_mask)) {
+            return NXM_DUP_TYPE;
+        } else {
+            struct in6_addr ipv6;
+            memcpy(&ipv6, value, sizeof ipv6);
+            cls_rule_set_ipv6_src(rule, &ipv6);
+            return 0;
+        }
+    case NFI_NXM_NX_IPV6_SRC_W:
+        if (!ipv6_mask_is_any(&wc->ipv6_src_mask)) {
+            return NXM_DUP_TYPE;
+        } else {
+            struct in6_addr ipv6, netmask;
+            memcpy(&ipv6, value, sizeof ipv6);
+            memcpy(&netmask, mask, sizeof netmask);
+            if (!cls_rule_set_ipv6_src_masked(rule, &ipv6, &netmask)) {
+                return NXM_BAD_MASK;
+            }
+            return 0;
+        }
+    case NFI_NXM_NX_IPV6_DST:
+        if (!ipv6_mask_is_any(&wc->ipv6_dst_mask)) {
+            return NXM_DUP_TYPE;
+        } else {
+            struct in6_addr ipv6;
+            memcpy(&ipv6, value, sizeof ipv6);
+            cls_rule_set_ipv6_dst(rule, &ipv6);
+            return 0;
+        }
+    case NFI_NXM_NX_IPV6_DST_W:
+        if (!ipv6_mask_is_any(&wc->ipv6_dst_mask)) {
+            return NXM_DUP_TYPE;
+        } else {
+            struct in6_addr ipv6, netmask;
+            memcpy(&ipv6, value, sizeof ipv6);
+            memcpy(&netmask, mask, sizeof netmask);
+            if (!cls_rule_set_ipv6_dst_masked(rule, &ipv6, &netmask)) {
+                return NXM_BAD_MASK;
+            }
+            return 0;
+        }
+
         /* TCP header. */
     case NFI_NXM_OF_TCP_SRC:
         flow->tp_src = get_unaligned_be16(value);
@@ -309,6 +355,14 @@ parse_nxm_entry(struct cls_rule *rule, const struct nxm_field *f,
         flow->tp_dst = htons(*(uint8_t *) value);
         return 0;
 
+        /* ICMPv6 header. */
+    case NFI_NXM_NX_ICMPV6_TYPE:
+        flow->tp_src = htons(*(uint8_t *) value);
+        return 0;
+    case NFI_NXM_NX_ICMPV6_CODE:
+        flow->tp_dst = htons(*(uint8_t *) value);
+        return 0;
+
         /* ARP header. */
     case NFI_NXM_OF_ARP_OP:
         if (ntohs(get_unaligned_be16(value)) > 255) {
@@ -372,9 +426,19 @@ parse_nxm_entry(struct cls_rule *rule, const struct nxm_field *f,
 static bool
 nxm_prereqs_ok(const struct nxm_field *field, const struct flow *flow)
 {
-    return (!field->dl_type
-            || (field->dl_type == flow->dl_type
-                && (!field->nw_proto || field->nw_proto == flow->nw_proto)));
+    if (field->nw_proto && field->nw_proto != flow->nw_proto) {
+        return false;
+    }
+
+    if (!field->dl_type[0]) {
+        return true;
+    } else if (field->dl_type[0] == flow->dl_type) {
+        return true;
+    } else if (field->dl_type[1] && field->dl_type[1] == flow->dl_type) {
+        return true;
+    }
+
+    return false;
 }
 
 static uint32_t
@@ -609,6 +673,22 @@ nxm_put_eth_dst(struct ofpbuf *b,
     }
 }
 
+static void
+nxm_put_ipv6(struct ofpbuf *b, uint32_t header,
+             const struct in6_addr *value, const struct in6_addr *mask)
+{
+    if (ipv6_mask_is_any(mask)) {
+        return;
+    } else if (ipv6_mask_is_exact(mask)) {
+        nxm_put_header(b, header);
+        ofpbuf_put(b, value, sizeof *value);
+    } else {
+        nxm_put_header(b, NXM_MAKE_WILD_HEADER(header));
+        ofpbuf_put(b, value, sizeof *value);
+        ofpbuf_put(b, mask, sizeof *mask);
+    }
+}
+
 /* Appends to 'b' the nx_match format that expresses 'cr' (except for
  * 'cr->priority', because priority is not part of nx_match), plus enough
  * zero bytes to pad the nx_match out to a multiple of 8.
@@ -693,6 +773,51 @@ nx_put_match(struct ofpbuf *b, const struct cls_rule *cr)
                 break;
             }
         }
+    } else if (!(wc & FWW_DL_TYPE) && flow->dl_type == htons(ETH_TYPE_IPV6)) {
+        /* IPv6. */
+
+        if (!(wc & FWW_NW_TOS)) {
+            nxm_put_8(b, NXM_OF_IP_TOS, flow->nw_tos & 0xfc);
+        }
+        nxm_put_ipv6(b, NXM_NX_IPV6_SRC, &flow->ipv6_src,
+                &cr->wc.ipv6_src_mask);
+        nxm_put_ipv6(b, NXM_NX_IPV6_DST, &flow->ipv6_dst,
+                &cr->wc.ipv6_dst_mask);
+
+        if (!(wc & FWW_NW_PROTO)) {
+            nxm_put_8(b, NXM_OF_IP_PROTO, flow->nw_proto);
+            switch (flow->nw_proto) {
+                /* TCP. */
+            case IPPROTO_TCP:
+                if (!(wc & FWW_TP_SRC)) {
+                    nxm_put_16(b, NXM_OF_TCP_SRC, flow->tp_src);
+                }
+                if (!(wc & FWW_TP_DST)) {
+                    nxm_put_16(b, NXM_OF_TCP_DST, flow->tp_dst);
+                }
+                break;
+
+                /* UDP. */
+            case IPPROTO_UDP:
+                if (!(wc & FWW_TP_SRC)) {
+                    nxm_put_16(b, NXM_OF_UDP_SRC, flow->tp_src);
+                }
+                if (!(wc & FWW_TP_DST)) {
+                    nxm_put_16(b, NXM_OF_UDP_DST, flow->tp_dst);
+                }
+                break;
+
+                /* ICMPv6. */
+            case IPPROTO_ICMPV6:
+                if (!(wc & FWW_TP_SRC)) {
+                    nxm_put_8(b, NXM_NX_ICMPV6_TYPE, ntohs(flow->tp_src));
+                }
+                if (!(wc & FWW_TP_DST)) {
+                    nxm_put_8(b, NXM_NX_ICMPV6_CODE, ntohs(flow->tp_dst));
+                }
+                break;
+            }
+        }
     } else if (!(wc & FWW_DL_TYPE) && flow->dl_type == htons(ETH_TYPE_ARP)) {
         /* ARP. */
         if (!(wc & FWW_NW_PROTO)) {
@@ -1147,9 +1272,11 @@ nxm_read_field(const struct nxm_field *src, const struct flow *flow)
         return ntohs(flow->tp_dst);
 
     case NFI_NXM_OF_ICMP_TYPE:
+    case NFI_NXM_NX_ICMPV6_TYPE:
         return ntohs(flow->tp_src) & 0xff;
 
     case NFI_NXM_OF_ICMP_CODE:
+    case NFI_NXM_NX_ICMPV6_CODE:
         return ntohs(flow->tp_dst) & 0xff;
 
     case NFI_NXM_NX_TUN_ID:
@@ -1188,6 +1315,10 @@ nxm_read_field(const struct nxm_field *src, const struct flow *flow)
     case NFI_NXM_OF_IP_DST_W:
     case NFI_NXM_OF_ARP_SPA_W:
     case NFI_NXM_OF_ARP_TPA_W:
+    case NFI_NXM_NX_IPV6_SRC:
+    case NFI_NXM_NX_IPV6_SRC_W:
+    case NFI_NXM_NX_IPV6_DST:
+    case NFI_NXM_NX_IPV6_DST_W:
     case N_NXM_FIELDS:
         NOT_REACHED();
     }
@@ -1255,6 +1386,12 @@ nxm_write_field(const struct nxm_field *dst, struct flow *flow,
     case NFI_NXM_OF_ARP_TPA_W:
     case NFI_NXM_NX_ARP_SHA:
     case NFI_NXM_NX_ARP_THA:
+    case NFI_NXM_NX_IPV6_SRC:
+    case NFI_NXM_NX_IPV6_SRC_W:
+    case NFI_NXM_NX_IPV6_DST:
+    case NFI_NXM_NX_IPV6_DST_W:
+    case NFI_NXM_NX_ICMPV6_TYPE:
+    case NFI_NXM_NX_ICMPV6_CODE:
     case N_NXM_FIELDS:
         NOT_REACHED();
     }
diff --git a/lib/nx-match.def b/lib/nx-match.def
index d32b94e..e4ed43f 100644
--- a/lib/nx-match.def
+++ b/lib/nx-match.def
@@ -14,43 +14,55 @@
  * limitations under the License.
  */
 
-#define DEFINE_FIELD_M(HEADER, WILDCARD, DL_TYPE, NW_PROTO, WRITABLE)   \
-    DEFINE_FIELD(HEADER, WILDCARD, DL_TYPE, NW_PROTO, WRITABLE)         \
-    DEFINE_FIELD(HEADER##_W, WILDCARD, DL_TYPE, NW_PROTO, false)
+#define N_NXM_DL_TYPES 2
 
-/*             NXM_ suffix   FWW_* bit     dl_type       nw_proto      rw? */
-/*             ------------  ------------  -----------   ------------- --- */
-DEFINE_FIELD  (OF_IN_PORT,   FWW_IN_PORT,  0,            0,            false)
-DEFINE_FIELD_M(OF_ETH_DST,   0,            0,            0,            false)
-DEFINE_FIELD  (OF_ETH_SRC,   FWW_DL_SRC,   0,            0,            false)
-DEFINE_FIELD  (OF_ETH_TYPE,  FWW_DL_TYPE,  0,            0,            false)
-DEFINE_FIELD_M(OF_VLAN_TCI,  0,            0,            0,            true)
-DEFINE_FIELD  (OF_IP_TOS,    FWW_NW_TOS,   ETH_TYPE_IP,  0,            false)
-DEFINE_FIELD  (OF_IP_PROTO,  FWW_NW_PROTO, ETH_TYPE_IP,  0,            false)
-DEFINE_FIELD_M(OF_IP_SRC,    0,            ETH_TYPE_IP,  0,            false)
-DEFINE_FIELD_M(OF_IP_DST,    0,            ETH_TYPE_IP,  0,            false)
-DEFINE_FIELD  (OF_TCP_SRC,   FWW_TP_SRC,   ETH_TYPE_IP,  IP_TYPE_TCP,  false)
-DEFINE_FIELD  (OF_TCP_DST,   FWW_TP_DST,   ETH_TYPE_IP,  IP_TYPE_TCP,  false)
-DEFINE_FIELD  (OF_UDP_SRC,   FWW_TP_SRC,   ETH_TYPE_IP,  IP_TYPE_UDP,  false)
-DEFINE_FIELD  (OF_UDP_DST,   FWW_TP_DST,   ETH_TYPE_IP,  IP_TYPE_UDP,  false)
-DEFINE_FIELD  (OF_ICMP_TYPE, FWW_TP_SRC,   ETH_TYPE_IP,  IP_TYPE_ICMP, false)
-DEFINE_FIELD  (OF_ICMP_CODE, FWW_TP_DST,   ETH_TYPE_IP,  IP_TYPE_ICMP, false)
-DEFINE_FIELD  (OF_ARP_OP,    FWW_NW_PROTO, ETH_TYPE_ARP, 0,            false)
-DEFINE_FIELD_M(OF_ARP_SPA,   0,            ETH_TYPE_ARP, 0,            false)
-DEFINE_FIELD_M(OF_ARP_TPA,   0,            ETH_TYPE_ARP, 0,            false)
-DEFINE_FIELD_M(NX_TUN_ID,    0,            0,            0,            true)
-DEFINE_FIELD  (NX_ARP_SHA,   FWW_ARP_SHA,  ETH_TYPE_ARP, 0,            false)
-DEFINE_FIELD  (NX_ARP_THA,   FWW_ARP_THA,  ETH_TYPE_ARP, 0,            false)
+#define NXM_DL_NONE   (0, 0)
+#define NXM_DL_ARP    (ETH_TYPE_ARP, 0)
+#define NXM_DL_IP     (ETH_TYPE_IP, 0)
+#define NXM_DL_IPV6   (ETH_TYPE_IPV6, 0)
+#define NXM_DL_IP_ANY (ETH_TYPE_IP, ETH_TYPE_IPV6)
 
-DEFINE_FIELD_M(NX_REG0,      0,            0,            0,            true)
+#define DEFINE_FIELD_M(HEADER, WILDCARD, DL_TYPES, NW_PROTO, WRITABLE)  \
+    DEFINE_FIELD(HEADER, WILDCARD, DL_TYPES, NW_PROTO, WRITABLE)        \
+    DEFINE_FIELD(HEADER##_W, WILDCARD, DL_TYPES, NW_PROTO, false)
+
+/*             NXM_ suffix     FWW_* bit     dl_types       nw_proto      rw? */
+/*             ------------    ------------  -----------    ------------- --- */
+DEFINE_FIELD_M(NX_TUN_ID,      0,            NXM_DL_NONE,   0,            true)
+DEFINE_FIELD  (OF_IN_PORT,     FWW_IN_PORT,  NXM_DL_NONE,   0,            false)
+DEFINE_FIELD_M(OF_ETH_DST,     0,            NXM_DL_NONE,   0,            false)
+DEFINE_FIELD  (OF_ETH_SRC,     FWW_DL_SRC,   NXM_DL_NONE,   0,            false)
+DEFINE_FIELD  (OF_ETH_TYPE,    FWW_DL_TYPE,  NXM_DL_NONE,   0,            false)
+DEFINE_FIELD_M(OF_VLAN_TCI,    0,            NXM_DL_NONE,   0,            true)
+DEFINE_FIELD  (OF_IP_TOS,      FWW_NW_TOS,   NXM_DL_IP_ANY, 0,            false)
+DEFINE_FIELD  (OF_IP_PROTO,    FWW_NW_PROTO, NXM_DL_IP_ANY, 0,            false)
+DEFINE_FIELD_M(OF_IP_SRC,      0,            NXM_DL_IP,     0,            false)
+DEFINE_FIELD_M(OF_IP_DST,      0,            NXM_DL_IP,     0,            false)
+DEFINE_FIELD  (OF_TCP_SRC,     FWW_TP_SRC,   NXM_DL_IP_ANY, IP_TYPE_TCP,  false)
+DEFINE_FIELD  (OF_TCP_DST,     FWW_TP_DST,   NXM_DL_IP_ANY, IP_TYPE_TCP,  false)
+DEFINE_FIELD  (OF_UDP_SRC,     FWW_TP_SRC,   NXM_DL_IP_ANY, IP_TYPE_UDP,  false)
+DEFINE_FIELD  (OF_UDP_DST,     FWW_TP_DST,   NXM_DL_IP_ANY, IP_TYPE_UDP,  false)
+DEFINE_FIELD  (OF_ICMP_TYPE,   FWW_TP_SRC,   NXM_DL_IP,     IP_TYPE_ICMP, false)
+DEFINE_FIELD  (OF_ICMP_CODE,   FWW_TP_DST,   NXM_DL_IP,     IP_TYPE_ICMP, false)
+DEFINE_FIELD  (OF_ARP_OP,      FWW_NW_PROTO, NXM_DL_ARP,    0,            false)
+DEFINE_FIELD_M(OF_ARP_SPA,     0,            NXM_DL_ARP,    0,            false)
+DEFINE_FIELD_M(OF_ARP_TPA,     0,            NXM_DL_ARP,    0,            false)
+DEFINE_FIELD  (NX_ARP_SHA,     FWW_ARP_SHA,  NXM_DL_ARP,    0,            false)
+DEFINE_FIELD  (NX_ARP_THA,     FWW_ARP_THA,  NXM_DL_ARP,    0,            false)
+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_M(NX_REG0,        0,            NXM_DL_NONE,   0,            true)
 #if FLOW_N_REGS >= 2
-DEFINE_FIELD_M(NX_REG1,      0,            0,            0,            true)
+DEFINE_FIELD_M(NX_REG1,        0,            NXM_DL_NONE,   0,            true)
 #endif
 #if FLOW_N_REGS >= 3
-DEFINE_FIELD_M(NX_REG2,      0,            0,            0,            true)
+DEFINE_FIELD_M(NX_REG2,        0,            NXM_DL_NONE,   0,            true)
 #endif
 #if FLOW_N_REGS >= 4
-DEFINE_FIELD_M(NX_REG3,      0,            0,            0,            true)
+DEFINE_FIELD_M(NX_REG3,        0,            NXM_DL_NONE,   0,            true)
 #endif
 #if FLOW_N_REGS > 4
 #error
diff --git a/lib/nx-match.h b/lib/nx-match.h
index bd4fea2..aefcb65 100644
--- a/lib/nx-match.h
+++ b/lib/nx-match.h
@@ -93,8 +93,8 @@ nxm_decode_n_bits(ovs_be16 ofs_nbits)
  *  NXM_OF_VLAN_TCI     4       2     2      8
  *  NXM_OF_IP_TOS       4       1    --      5
  *  NXM_OF_IP_PROTO     4       2    --      6
- *  NXM_OF_IP_SRC_W     4       4     4     12
- *  NXM_OF_IP_DST_W     4       4     4     12
+ *  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_NX_REG_W(0)     4       4     4     12
@@ -103,11 +103,11 @@ nxm_decode_n_bits(ovs_be16 ofs_nbits)
  *  NXM_NX_REG_W(3)     4       4     4     12
  *  NXM_NX_TUN_ID_W     4       8     8     20
  *  -------------------------------------------
- *  total                                  161
+ *  total                                  209
  *
  * So this value is conservative.
  */
-#define NXM_MAX_LEN 192
+#define NXM_MAX_LEN 256
 
 /* This is my guess at the length of a "typical" nx_match, for use in
  * predicting space requirements. */
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 89f67b0..6270f04 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <arpa/inet.h>
 #include <config.h>
 #include "odp-util.h"
 #include <errno.h>
@@ -194,6 +195,7 @@ odp_flow_key_attr_len(uint16_t type)
     case ODP_KEY_ATTR_8021Q: return sizeof(struct odp_key_8021q);
     case ODP_KEY_ATTR_ETHERTYPE: return 2;
     case ODP_KEY_ATTR_IPV4: return sizeof(struct odp_key_ipv4);
+    case ODP_KEY_ATTR_IPV6: return sizeof(struct odp_key_ipv6);
     case ODP_KEY_ATTR_TCP: return sizeof(struct odp_key_tcp);
     case ODP_KEY_ATTR_UDP: return sizeof(struct odp_key_udp);
     case ODP_KEY_ATTR_ICMP: return sizeof(struct odp_key_icmp);
@@ -233,6 +235,7 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
     const struct odp_key_ethernet *eth_key;
     const struct odp_key_8021q *q_key;
     const struct odp_key_ipv4 *ipv4_key;
+    const struct odp_key_ipv6 *ipv6_key;
     const struct odp_key_tcp *tcp_key;
     const struct odp_key_udp *udp_key;
     const struct odp_key_icmp *icmp_key;
@@ -287,6 +290,20 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
                       ipv4_key->ipv4_proto, ipv4_key->ipv4_tos);
         break;
 
+    case ODP_KEY_ATTR_IPV6: {
+        char src_str[INET6_ADDRSTRLEN];
+        char dst_str[INET6_ADDRSTRLEN];
+
+        ipv6_key = nl_attr_get(a);
+        inet_ntop(AF_INET6, ipv6_key->ipv6_src, src_str, sizeof src_str);
+        inet_ntop(AF_INET6, ipv6_key->ipv6_dst, dst_str, sizeof dst_str);
+
+        ds_put_format(ds, "ipv6(src=%s,dst=%s,proto=%"PRId8",tos=%"PRIu8")",
+                      src_str, dst_str, ipv6_key->ipv6_proto,
+                      ipv6_key->ipv6_tos);
+        break;
+    }
+
     case ODP_KEY_ATTR_TCP:
         tcp_key = nl_attr_get(a);
         ds_put_format(ds, "tcp(src=%"PRIu16",dst=%"PRIu16")",
@@ -394,6 +411,29 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
         ipv4_key->ipv4_dst = flow->nw_dst;
         ipv4_key->ipv4_proto = flow->nw_proto;
         ipv4_key->ipv4_tos = flow->nw_tos;
+    } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
+        struct odp_key_ipv6 *ipv6_key;
+
+        ipv6_key = nl_msg_put_unspec_uninit(buf, ODP_KEY_ATTR_IPV6,
+                                            sizeof *ipv6_key);
+        memcpy(ipv6_key->ipv6_src, &flow->ipv6_src, sizeof ipv6_key->ipv6_src);
+        memcpy(ipv6_key->ipv6_dst, &flow->ipv6_dst, sizeof ipv6_key->ipv6_dst);
+        ipv6_key->ipv6_proto = flow->nw_proto;
+        ipv6_key->ipv6_tos = flow->nw_tos;
+    } else if (flow->dl_type == htons(ETH_TYPE_ARP)) {
+        struct odp_key_arp *arp_key;
+
+        arp_key = nl_msg_put_unspec_uninit(buf, ODP_KEY_ATTR_ARP,
+                                           sizeof *arp_key);
+        arp_key->arp_sip = flow->nw_src;
+        arp_key->arp_tip = flow->nw_dst;
+        arp_key->arp_op = htons(flow->nw_proto);
+        memcpy(arp_key->arp_sha, flow->arp_sha, ETH_ADDR_LEN);
+        memcpy(arp_key->arp_tha, flow->arp_tha, ETH_ADDR_LEN);
+    }
+    
+    if (flow->dl_type == htons(ETH_TYPE_IP)
+            || flow->dl_type == htons(ETH_TYPE_IPV6)) {
 
         if (flow->nw_proto == IP_TYPE_TCP) {
             struct odp_key_tcp *tcp_key;
@@ -409,24 +449,23 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
                                                sizeof *udp_key);
             udp_key->udp_src = flow->tp_src;
             udp_key->udp_dst = flow->tp_dst;
-        } else if (flow->nw_proto == IP_TYPE_ICMP) {
+        } else if (flow->dl_type == htons(ETH_TYPE_IP)
+                && flow->nw_proto == IP_TYPE_ICMP) {
             struct odp_key_icmp *icmp_key;
 
             icmp_key = nl_msg_put_unspec_uninit(buf, ODP_KEY_ATTR_ICMP,
                                                 sizeof *icmp_key);
             icmp_key->icmp_type = ntohs(flow->tp_src);
             icmp_key->icmp_code = ntohs(flow->tp_dst);
-        }
-    } else if (flow->dl_type == htons(ETH_TYPE_ARP)) {
-        struct odp_key_arp *arp_key;
+        } else if (flow->dl_type == htons(ETH_TYPE_IPV6)
+                && flow->nw_proto == IPPROTO_ICMPV6) {
+            struct odp_key_icmp *icmp_key;
 
-        arp_key = nl_msg_put_unspec_uninit(buf, ODP_KEY_ATTR_ARP,
-                                           sizeof *arp_key);
-        arp_key->arp_sip = flow->nw_src;
-        arp_key->arp_tip = flow->nw_dst;
-        arp_key->arp_op = htons(flow->nw_proto);
-        memcpy(arp_key->arp_sha, flow->arp_sha, ETH_ADDR_LEN);
-        memcpy(arp_key->arp_tha, flow->arp_tha, ETH_ADDR_LEN);
+            icmp_key = nl_msg_put_unspec_uninit(buf, ODP_KEY_ATTR_ICMP,
+                                                sizeof *icmp_key);
+            icmp_key->icmp_type = ntohs(flow->tp_src);
+            icmp_key->icmp_code = ntohs(flow->tp_dst);
+        }
     }
 }
 
@@ -448,6 +487,7 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
         const struct odp_key_ethernet *eth_key;
         const struct odp_key_8021q *q_key;
         const struct odp_key_ipv4 *ipv4_key;
+        const struct odp_key_ipv6 *ipv6_key;
         const struct odp_key_tcp *tcp_key;
         const struct odp_key_udp *udp_key;
         const struct odp_key_icmp *icmp_key;
@@ -514,7 +554,22 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
             }
             break;
 
+        case TRANSITION(ODP_KEY_ATTR_ETHERTYPE, ODP_KEY_ATTR_IPV6):
+            if (flow->dl_type != htons(ETH_TYPE_IPV6)) {
+                return EINVAL;
+            }
+            ipv6_key = nl_attr_get(nla);
+            memcpy(&flow->ipv6_src, ipv6_key->ipv6_src, sizeof flow->ipv6_src);
+            memcpy(&flow->ipv6_dst, ipv6_key->ipv6_dst, sizeof flow->ipv6_dst);
+            flow->nw_proto = ipv6_key->ipv6_proto;
+            flow->nw_tos = ipv6_key->ipv6_tos;
+            if (flow->nw_tos & IP_ECN_MASK) {
+                return EINVAL;
+            }
+            break;
+
         case TRANSITION(ODP_KEY_ATTR_IPV4, ODP_KEY_ATTR_TCP):
+        case TRANSITION(ODP_KEY_ATTR_IPV6, ODP_KEY_ATTR_TCP):
             if (flow->nw_proto != IP_TYPE_TCP) {
                 return EINVAL;
             }
@@ -524,6 +579,7 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
             break;
 
         case TRANSITION(ODP_KEY_ATTR_IPV4, ODP_KEY_ATTR_UDP):
+        case TRANSITION(ODP_KEY_ATTR_IPV6, ODP_KEY_ATTR_UDP):
             if (flow->nw_proto != IP_TYPE_UDP) {
                 return EINVAL;
             }
@@ -533,7 +589,9 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
             break;
 
         case TRANSITION(ODP_KEY_ATTR_IPV4, ODP_KEY_ATTR_ICMP):
-            if (flow->nw_proto != IP_TYPE_ICMP) {
+        case TRANSITION(ODP_KEY_ATTR_IPV6, ODP_KEY_ATTR_ICMP):
+            if (flow->nw_proto != IP_TYPE_ICMP
+                    && flow->nw_proto != IPPROTO_ICMPV6) {
                 return EINVAL;
             }
             icmp_key = nl_attr_get(nla);
@@ -584,15 +642,18 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
 
     case ODP_KEY_ATTR_ETHERTYPE:
         if (flow->dl_type == htons(ETH_TYPE_IP)
+            || flow->dl_type == htons(ETH_TYPE_IPV6)
             || flow->dl_type == htons(ETH_TYPE_ARP)) {
             return EINVAL;
         }
         return 0;
 
     case ODP_KEY_ATTR_IPV4:
+    case ODP_KEY_ATTR_IPV6:
         if (flow->nw_proto == IP_TYPE_TCP
             || flow->nw_proto == IP_TYPE_UDP
-            || flow->nw_proto == IP_TYPE_ICMP) {
+            || flow->nw_proto == IP_TYPE_ICMP
+            || flow->nw_proto == IPPROTO_ICMPV6) {
             return EINVAL;
         }
         return 0;
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 1a0d58d..8ec09f3 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
- * 80 bytes long, so this leaves some safety margin.
+ * 92 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 96
+#define ODPUTIL_FLOW_KEY_BYTES 112
 #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 2ab684c..7349c40 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -157,6 +157,46 @@ error:
     ovs_fatal(0, "%s: bad syntax for tunnel id", str);
 }
 
+static void
+str_to_ipv6(const char *str_, struct in6_addr *addrp, struct in6_addr *maskp)
+{
+    char *str = xstrdup(str_);
+    char *save_ptr = NULL;
+    const char *name, *netmask;
+    struct in6_addr addr, mask;
+    int retval;
+
+    name = strtok_r(str, "/", &save_ptr);
+    retval = name ? lookup_ipv6(name, &addr) : EINVAL;
+    if (retval) {
+        ovs_fatal(0, "%s: could not convert to IPv6 address", str);
+    }
+
+    netmask = strtok_r(NULL, "/", &save_ptr);
+    if (netmask) {
+        int prefix = atoi(netmask);
+        if (prefix <= 0 || prefix > 128) {
+            ovs_fatal(0, "%s: network prefix bits not between 1 and 128",
+                      str);
+        } else {
+            mask = ipv6_create_mask(prefix);
+        }
+    } else {
+        mask = in6addr_exact;
+    }
+    *addrp = ipv6_addr_bitand(&addr, &mask);
+
+    if (maskp) {
+        *maskp = mask;
+    } else {
+        if (!ipv6_mask_is_exact(&mask)) {
+            ovs_fatal(0, "%s: netmask not allowed here", str_);
+        }
+    }
+
+    free(str);
+}
+
 static void *
 put_action(struct ofpbuf *b, size_t size, uint16_t type)
 {
@@ -463,6 +503,11 @@ parse_protocol(const char *name, const struct protocol **p_out)
         { "icmp", ETH_TYPE_IP, IP_TYPE_ICMP },
         { "tcp", ETH_TYPE_IP, IP_TYPE_TCP },
         { "udp", ETH_TYPE_IP, IP_TYPE_UDP },
+        { "ipv6", ETH_TYPE_IPV6, 0 },
+        { "ip6", ETH_TYPE_IPV6, 0 },
+        { "icmp6", ETH_TYPE_IPV6, IPPROTO_ICMPV6 },
+        { "tcp6", ETH_TYPE_IPV6, IPPROTO_TCP },
+        { "udp6", ETH_TYPE_IPV6, IPPROTO_UDP },
     };
     const struct protocol *p;
 
@@ -493,7 +538,9 @@ parse_protocol(const char *name, const struct protocol **p_out)
     FIELD(F_ICMP_TYPE,   "icmp_type",   FWW_TP_SRC)         \
     FIELD(F_ICMP_CODE,   "icmp_code",   FWW_TP_DST)         \
     FIELD(F_ARP_SHA,     "arp_sha",     FWW_ARP_SHA)        \
-    FIELD(F_ARP_THA,     "arp_tha",     FWW_ARP_THA)
+    FIELD(F_ARP_THA,     "arp_tha",     FWW_ARP_THA)        \
+    FIELD(F_IPV6_SRC,    "ipv6_src",    0)                  \
+    FIELD(F_IPV6_DST,    "ipv6_dst",    0)
 
 enum field_index {
 #define FIELD(ENUM, NAME, WILDCARD) ENUM,
@@ -535,6 +582,7 @@ parse_field_value(struct cls_rule *rule, enum field_index index,
     uint8_t mac[ETH_ADDR_LEN];
     ovs_be64 tun_id, tun_mask;
     ovs_be32 ip, mask;
+    struct in6_addr ipv6, ipv6_mask;
     uint16_t port_no;
 
     switch (index) {
@@ -619,6 +667,16 @@ parse_field_value(struct cls_rule *rule, enum field_index index,
         cls_rule_set_arp_tha(rule, mac);
         break;
 
+    case F_IPV6_SRC:
+        str_to_ipv6(value, &ipv6, &ipv6_mask);
+        cls_rule_set_ipv6_src_masked(rule, &ipv6, &ipv6_mask);
+        break;
+
+    case F_IPV6_DST:
+        str_to_ipv6(value, &ipv6, &ipv6_mask);
+        cls_rule_set_ipv6_dst_masked(rule, &ipv6, &ipv6_mask);
+        break;
+
     case N_FIELDS:
         NOT_REACHED();
     }
@@ -723,6 +781,12 @@ parse_ofp_str(struct flow_mod *fm, uint8_t *table_idx,
                         cls_rule_set_nw_src_masked(&fm->cr, 0, 0);
                     } else if (f->index == F_NW_DST) {
                         cls_rule_set_nw_dst_masked(&fm->cr, 0, 0);
+                    } else if (f->index == F_IPV6_SRC) {
+                        cls_rule_set_ipv6_src_masked(&fm->cr,
+                                &in6addr_any, &in6addr_any);
+                    } else if (f->index == F_IPV6_DST) {
+                        cls_rule_set_ipv6_dst_masked(&fm->cr,
+                                &in6addr_any, &in6addr_any);
                     } else if (f->index == F_DL_VLAN) {
                         cls_rule_set_any_vid(&fm->cr);
                     } else if (f->index == F_DL_VLAN_PCP) {
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 59a5fc4..4d89e0a 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -868,6 +868,12 @@ is_nxm_required(const struct cls_rule *rule, bool cookie_support,
         return true;
     }
 
+    /* Only NXM supports matching IPv6 traffic. */
+    if (!(wc->wildcards & FWW_DL_TYPE)
+            && (rule->flow.dl_type == htons(ETH_TYPE_IPV6))) {
+        return true;
+    }
+
     /* Only NXM supports matching registers. */
     if (!regs_fully_wildcarded(wc)) {
         return true;
@@ -2041,6 +2047,9 @@ normalize_match(struct ofp_match *m)
             m->nw_dst &= ofputil_wcbits_to_netmask(wc >> OFPFW_NW_DST_SHIFT);
         }
         m->tp_src = m->tp_dst = m->nw_tos = 0;
+    } else if (m->dl_type == htons(ETH_TYPE_IPV6)) {
+        /* Don't normalize IPv6 traffic, since OpenFlow doesn't have a
+         * way to express it. */
     } else {
         /* Network and transport layer fields will always be extracted as
          * zeros, so we can do an exact-match on those values. */
diff --git a/lib/packets.c b/lib/packets.c
index 2dc82fe..05148fe 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -16,10 +16,16 @@
 
 #include <config.h>
 #include "packets.h"
+#include <assert.h>
+#include <arpa/inet.h>
 #include <netinet/in.h>
 #include <stdlib.h>
+#include "byte-order.h"
+#include "dynamic-string.h"
 #include "ofpbuf.h"
 
+const struct in6_addr in6addr_exact = IN6ADDR_EXACT_INIT;
+
 /* Parses 's' as a 16-digit hexadecimal number representing a datapath ID.  On
  * success stores the dpid into '*dpidp' and returns true, on failure stores 0
  * into '*dpidp' and returns false.
@@ -83,3 +89,117 @@ compose_benign_packet(struct ofpbuf *b, const char *tag, uint16_t snap_type,
     memcpy(llc_snap->snap.snap_org, "\x00\x23\x20", 3);
     llc_snap->snap.snap_type = htons(snap_type);
 }
+
+/* Stores the string representation of the IPv6 address 'addr' into the
+ * character array 'addr_str', which must be at least INET6_ADDRSTRLEN
+ * bytes long. */
+void
+format_ipv6_addr(char *addr_str, const struct in6_addr *addr)
+{
+    inet_ntop(AF_INET6, addr, addr_str, INET6_ADDRSTRLEN);
+}
+
+void
+print_ipv6_addr(struct ds *string, const struct in6_addr *addr)
+{
+    char addr_str[INET6_ADDRSTRLEN];
+
+    format_ipv6_addr(addr_str, addr);
+    ds_put_format(string, "%s", addr_str);
+}
+
+struct in6_addr ipv6_addr_bitand(const struct in6_addr *a,
+                                 const struct in6_addr *b)
+{
+    int i;
+    struct in6_addr dst;
+
+#ifdef s6_addr32
+    for (i=0; i<4; i++) {
+        dst.s6_addr32[i] = a->s6_addr32[i] & b->s6_addr32[i];
+    }
+#else
+    for (i=0; i<16; i++) {
+        dst.s6_addr[i] = a->s6_addr[i] & b->s6_addr[i];
+    }
+#endif
+
+    return dst;
+}
+
+/* Returns an in6_addr consisting of 'mask' high-order 1-bits and 128-N
+ * low-order 0-bits. */
+struct in6_addr
+ipv6_create_mask(int mask)
+{
+    struct in6_addr netmask;
+    uint8_t *netmaskp = &netmask.s6_addr[0];
+
+    memset(&netmask, 0, sizeof netmask);
+    while (mask > 8) {
+        *netmaskp = 0xff;
+        netmaskp++;
+        mask -= 8;
+    }
+
+    if (mask) {
+        *netmaskp = 0xff << (8 - mask);
+    }
+
+    return netmask;
+}
+
+/* Given the IPv6 netmask 'netmask', returns the number of bits of the
+ * IPv6 address that it wildcards.  'netmask' must be a CIDR netmask (see
+ * ipv6_is_cidr()). */
+int
+ipv6_count_cidr_bits(const struct in6_addr *netmask)
+{
+    int i;
+    int count = 0;
+    const uint8_t *netmaskp = &netmask->s6_addr[0];
+
+    assert(ipv6_is_cidr(netmask));
+
+    for (i=0; i<16; i++) {
+        if (netmaskp[i] == 0xff) {
+            count += 8;
+        } else {
+            uint8_t nm;
+
+            for(nm = netmaskp[i]; nm; nm <<= 1) {
+                count++;
+            }
+            break;
+        }
+
+    }
+
+    return count;
+}
+
+
+/* Returns true if 'netmask' is a CIDR netmask, that is, if it consists of N
+ * high-order 1-bits and 128-N low-order 0-bits. */
+bool
+ipv6_is_cidr(const struct in6_addr *netmask)
+{
+    const uint8_t *netmaskp = &netmask->s6_addr[0];
+    int i;
+
+    for (i=0; i<16; i++) {
+        if (netmaskp[i] != 0xff) {
+            uint8_t x = ~netmaskp[i];
+            if (x & (x + 1)) {
+                return false;
+            }
+            while (++i < 16) {
+                if (netmaskp[i]) {
+                    return false;
+                }
+            }
+        }
+    }
+
+    return true;
+}
diff --git a/lib/packets.h b/lib/packets.h
index 96e23e1..4d4a967 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -28,6 +28,7 @@
 #include "util.h"
 
 struct ofpbuf;
+struct ds;
 
 bool dpid_from_string(const char *s, uint64_t *dpidp);
 
@@ -153,6 +154,7 @@ void compose_benign_packet(struct ofpbuf *, const char *tag,
 #define ETH_TYPE_IP            0x0800
 #define ETH_TYPE_ARP           0x0806
 #define ETH_TYPE_VLAN          0x8100
+#define ETH_TYPE_IPV6          0x86dd
 #define ETH_TYPE_CFM           0x8902
 
 /* Minimum value for an Ethernet type.  Values below this are IEEE 802.2 frame
@@ -375,4 +377,34 @@ struct arp_eth_header {
 } __attribute__((packed));
 BUILD_ASSERT_DECL(ARP_ETH_HEADER_LEN == sizeof(struct arp_eth_header));
 
+extern const struct in6_addr in6addr_exact;
+#define IN6ADDR_EXACT_INIT { { { 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, \
+                                 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff } } }
+
+static inline bool ipv6_addr_equals(const struct in6_addr *a,
+                                    const struct in6_addr *b)
+{
+#ifdef IN6_ARE_ADDR_EQUAL
+    return IN6_ARE_ADDR_EQUAL(a, b);
+#else
+    return !memcmp(a, b, sizeof(*a));
+#endif
+}
+
+static inline bool ipv6_mask_is_any(const struct in6_addr *mask) {
+    return ipv6_addr_equals(mask, &in6addr_any);
+}
+
+static inline bool ipv6_mask_is_exact(const struct in6_addr *mask) {
+    return ipv6_addr_equals(mask, &in6addr_exact);
+}
+
+void format_ipv6_addr(char *addr_str, const struct in6_addr *addr);
+void print_ipv6_addr(struct ds *string, const struct in6_addr *addr);
+struct in6_addr ipv6_addr_bitand(const struct in6_addr *src,
+                                 const struct in6_addr *mask);
+struct in6_addr ipv6_create_mask(int mask);
+int ipv6_count_cidr_bits(const struct in6_addr *netmask);
+bool ipv6_is_cidr(const struct in6_addr *netmask);
+
 #endif /* packets.h */
diff --git a/lib/socket-util.c b/lib/socket-util.c
index e97e7c9..469131d 100644
--- a/lib/socket-util.c
+++ b/lib/socket-util.c
@@ -121,6 +121,20 @@ lookup_ip(const char *host_name, struct in_addr *addr)
     return 0;
 }
 
+/* Translates 'host_name', which must be a string representation of an IPv6
+ * address, into a numeric IPv6 address in '*addr'.  Returns 0 if successful,
+ * otherwise a positive errno value. */
+int
+lookup_ipv6(const char *host_name, struct in6_addr *addr)
+{
+    if (inet_pton(AF_INET6, host_name, addr) != 1) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_ERR_RL(&rl, "\"%s\" is not a valid IPv6 address", host_name);
+        return ENOENT;
+    }
+    return 0;
+}
+
 /* Returns the error condition associated with socket 'fd' and resets the
  * socket's error status. */
 int
diff --git a/lib/socket-util.h b/lib/socket-util.h
index 40a9614..f4e617a 100644
--- a/lib/socket-util.h
+++ b/lib/socket-util.h
@@ -24,6 +24,7 @@
 int set_nonblocking(int fd);
 int get_max_fds(void);
 int lookup_ip(const char *host_name, struct in_addr *address);
+int lookup_ipv6(const char *host_name, struct in6_addr *address);
 int get_socket_error(int sock);
 int check_connection_completion(int fd);
 int drain_rcvbuf(int fd);
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index 7eecf28..a86588b 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -62,6 +62,12 @@ tcp,tp_src=123,actions=flood
 in_port=LOCAL dl_vlan=9 dl_src=00:0A:E4:25:6B:B0 actions=drop
 arp,nw_src=192.168.0.1 actions=drop_spoofed_arp,NORMAL
 arp,dl_src=00:0A:E4:25:6B:B0,arp_sha=00:0A:E4:25:6B:B0 actions=drop
+ipv6,ipv6_src=2001:db8:3c4d:1:2:3:4:5 actions=3
+ipv6,ipv6_src=2001:db8:3c4d:1:2:3:4:5/64 actions=4
+ipv6,ipv6_dst=2001:db8:3c4d:1:2:3:4:5/127 actions=5
+tcp6,ipv6_src=2001:db8:3c4d:1::1,tp_dst=80 actions=drop 
+udp6,ipv6_src=2001:db8:3c4d:1::3,tp_dst=53 actions=drop 
+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
@@ -77,6 +83,12 @@ NXT_FLOW_MOD: ADD tcp,tp_src=123 actions=FLOOD
 NXT_FLOW_MOD: ADD in_port=65534,dl_vlan=9,dl_src=00:0a:e4:25:6b:b0 actions=drop
 NXT_FLOW_MOD: ADD arp,nw_src=192.168.0.1 actions=drop_spoofed_arp,NORMAL
 NXT_FLOW_MOD: ADD arp,dl_src=00:0a:e4:25:6b:b0,arp_sha=00:0a:e4:25:6b:b0 actions=drop
+NXT_FLOW_MOD: ADD ipv6,ipv6_src=2001:db8:3c4d:1:2:3:4:5 actions=output:3
+NXT_FLOW_MOD: ADD ipv6,ipv6_src=2001:db8:3c4d:1::/64 actions=output:4
+NXT_FLOW_MOD: ADD ipv6,ipv6_dst=2001:db8:3c4d:1:2:3:4:4/127 actions=output:5
+NXT_FLOW_MOD: ADD tcp6,ipv6_src=2001:db8:3c4d:1::1,tp_dst=80 actions=drop
+NXT_FLOW_MOD: ADD udp6,ipv6_src=2001:db8:3c4d:1::3,tp_dst=53 actions=drop
+NXT_FLOW_MOD: ADD icmp6,in_port=3,ipv6_src=2001:db8:3c4d:1::1,icmp_type=134 actions=drop
 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
@@ -95,6 +107,12 @@ tcp,tp_src=123,actions=flood
 in_port=LOCAL dl_vlan=9 dl_src=00:0A:E4:25:6B:B0 actions=drop
 arp,nw_src=192.168.0.1 actions=drop_spoofed_arp,NORMAL
 arp,dl_src=00:0A:E4:25:6B:B0,arp_sha=00:0A:E4:25:6B:B0 actions=drop
+ipv6,ipv6_src=2001:db8:3c4d:1:2:3:4:5 actions=3
+ipv6,ipv6_src=2001:db8:3c4d:1:2:3:4:5/64 actions=4
+ipv6,ipv6_dst=2001:db8:3c4d:1:2:3:4:5/127 actions=5
+tcp6,ipv6_src=2001:db8:3c4d:1::1,tp_dst=80 actions=drop 
+udp6,ipv6_src=2001:db8:3c4d:1::3,tp_dst=53 actions=drop 
+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
@@ -110,6 +128,12 @@ AT_CHECK([[sed 's/ (xid=0x[0-9a-fA-F]*)//' stdout]], [0],
 NXT_FLOW_MOD: ADD NXM_OF_IN_PORT(fffe), NXM_OF_ETH_SRC(000ae4256bb0), NXM_OF_VLAN_TCI_W(1009/1fff) actions=drop
 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0806), NXM_OF_ARP_SPA(c0a80001) actions=drop_spoofed_arp,NORMAL
 NXT_FLOW_MOD: ADD NXM_OF_ETH_SRC(000ae4256bb0), NXM_OF_ETH_TYPE(0806), NXM_NX_ARP_SHA(000ae4256bb0) actions=drop
+NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_SRC(20010db83c4d00010002000300040005) actions=output:3
+NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_SRC_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000) actions=output:4
+NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_DST_W(20010db83c4d00010002000300040004/fffffffffffffffffffffffffffffffe) actions=output:5
+NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_SRC(20010db83c4d00010000000000000001), NXM_OF_IP_PROTO(06), NXM_OF_TCP_DST(0050) actions=drop
+NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_SRC(20010db83c4d00010000000000000003), NXM_OF_IP_PROTO(11), NXM_OF_UDP_DST(0035) actions=drop
+NXT_FLOW_MOD: ADD NXM_OF_IN_PORT(0003), NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_SRC(20010db83c4d00010000000000000001), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(86) actions=drop
 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
@@ -231,6 +255,18 @@ NXM_OF_ETH_TYPE(0806) NXM_NX_ARP_THA(0002e30f80a4)
 NXM_OF_ETH_TYPE(0800) NXM_NX_ARP_THA(0002e30f80a4)
 NXM_NX_ARP_THA(0002e30f80a4)
 
+# IPv6 source
+NXM_OF_ETH_TYPE(86dd) NXM_NX_IPV6_SRC(20010db83c4d00010002000300040005)
+NXM_OF_ETH_TYPE(0800) NXM_NX_IPV6_SRC(20010db83c4d00010002000300040005)
+NXM_OF_ETH_TYPE(86dd) NXM_NX_IPV6_SRC_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000)
+NXM_OF_ETH_TYPE(0800) NXM_NX_IPV6_SRC_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000)
+
+# IPv6 destination
+NXM_OF_ETH_TYPE(86dd) NXM_NX_IPV6_DST(20010db83c4d00010002000300040005)
+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)
+
 # Tunnel ID.
 NXM_NX_TUN_ID(00000000abcdef01)
 NXM_NX_TUN_ID_W(84200000abcdef01/84200000FFFFFFFF)
@@ -359,6 +395,18 @@ NXM_OF_ETH_TYPE(0806), NXM_NX_ARP_THA(0002e30f80a4)
 nx_pull_match() returned error 44010104
 nx_pull_match() returned error 44010104
 
+# IPv6 source
+NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_SRC(20010db83c4d00010002000300040005)
+nx_pull_match() returned error 44010104
+NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_SRC_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000)
+nx_pull_match() returned error 44010104
+
+# IPv6 destination
+NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_DST(20010db83c4d00010002000300040005)
+nx_pull_match() returned error 44010104
+NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_DST_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000)
+nx_pull_match() returned error 44010104
+
 # Tunnel ID.
 NXM_NX_TUN_ID(00000000abcdef01)
 NXM_NX_TUN_ID_W(84200000abcdef01/84200000ffffffff)
diff --git a/tests/test-packets.c b/tests/test-packets.c
index 464a8eb..dda4797 100644
--- a/tests/test-packets.c
+++ b/tests/test-packets.c
@@ -39,10 +39,126 @@ test_ipv4_cidr(void)
     assert(!ip_is_cidr(htonl(0xffffffd0)));
 }
 
+static void
+test_ipv6_static_masks(void)
+{
+    /* The 'exact' and 'any' addresses should be identical to
+     * 'in6addr_exact' and  'in6addr_any' definitions, but we redefine
+     * them here since the pre-defined ones are used in the functions
+     * we're testing. */
+    struct in6_addr exact   = {{{ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, \
+                                  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff }}};
+
+    struct in6_addr any     = {{{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, \
+                                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }}};
+
+    struct in6_addr neither = {{{ 0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef, \
+                                  0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef }}};
+
+    assert(ipv6_mask_is_exact(&exact));
+    assert(!ipv6_mask_is_exact(&any));
+    assert(!ipv6_mask_is_exact(&neither));
+
+    assert(!ipv6_mask_is_any(&exact));
+    assert(ipv6_mask_is_any(&any));
+    assert(!ipv6_mask_is_any(&neither));
+
+}
+
+static void
+test_ipv6_cidr(void)
+{
+    struct in6_addr dest;
+
+    struct in6_addr src   = {{{ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, \
+                                0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }}};
+
+    dest = ipv6_create_mask(0);
+    assert(ipv6_mask_is_any(&dest));
+    assert(ipv6_count_cidr_bits(&dest) == 0);
+    assert(ipv6_is_cidr(&dest));
+
+    dest = ipv6_create_mask(128);
+    assert(ipv6_mask_is_exact(&dest));
+    assert(ipv6_count_cidr_bits(&dest) == 128);
+    assert(ipv6_is_cidr(&dest));
+
+    dest = ipv6_create_mask(1);
+    assert(ipv6_count_cidr_bits(&dest) == 1);
+    assert(ipv6_is_cidr(&dest));
+
+    dest = ipv6_create_mask(13);
+    assert(ipv6_count_cidr_bits(&dest) == 13);
+    assert(ipv6_is_cidr(&dest));
+
+    dest = ipv6_create_mask(64);
+    assert(ipv6_count_cidr_bits(&dest) == 64);
+    assert(ipv6_is_cidr(&dest));
+
+    dest = ipv6_create_mask(95);
+    assert(ipv6_count_cidr_bits(&dest) == 95);
+    assert(ipv6_is_cidr(&dest));
+
+    dest = ipv6_create_mask(96);
+    assert(ipv6_count_cidr_bits(&dest) == 96);
+    assert(ipv6_is_cidr(&dest));
+
+    dest = ipv6_create_mask(97);
+    assert(ipv6_count_cidr_bits(&dest) == 97);
+    assert(ipv6_is_cidr(&dest));
+
+    dest = ipv6_create_mask(127);
+    assert(ipv6_count_cidr_bits(&dest) == 127);
+    assert(ipv6_is_cidr(&dest));
+
+    src.s6_addr[8] = 0xf0;
+    assert(ipv6_is_cidr(&src));
+    assert(ipv6_count_cidr_bits(&src) == 68);
+
+    src.s6_addr[15] = 0x01;
+    assert(!ipv6_is_cidr(&src));
+    src.s6_addr[15] = 0x00;
+    assert(ipv6_is_cidr(&src));
+
+    src.s6_addr[8] = 0x0f;
+    assert(!ipv6_is_cidr(&src));
+}
+
+
+static void
+test_ipv6_masking(void)
+{
+    struct in6_addr dest;
+    struct in6_addr mask;
+
+    mask = ipv6_create_mask(0);
+    dest = ipv6_addr_bitand(&in6addr_exact, &mask);
+    assert(ipv6_count_cidr_bits(&dest) == 0);
+
+    mask = ipv6_create_mask(1);
+    dest = ipv6_addr_bitand(&in6addr_exact, &mask);
+    assert(ipv6_count_cidr_bits(&dest) == 1);
+
+    mask = ipv6_create_mask(13);
+    dest = ipv6_addr_bitand(&in6addr_exact, &mask);
+    assert(ipv6_count_cidr_bits(&dest) == 13);
+
+    mask = ipv6_create_mask(127);
+    dest = ipv6_addr_bitand(&in6addr_exact, &mask);
+    assert(ipv6_count_cidr_bits(&dest) == 127);
+
+    mask = ipv6_create_mask(128);
+    dest = ipv6_addr_bitand(&in6addr_exact, &mask);
+    assert(ipv6_count_cidr_bits(&dest) == 128);
+}
+
 int
 main(void)
 {
     test_ipv4_cidr();
+    test_ipv6_static_masks();
+    test_ipv6_cidr();
+    test_ipv6_masking();
 
     return 0;
 }
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 135e705..3742552 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -299,24 +299,31 @@ or 0x0806, the values of \fBnw_src\fR and \fBnw_dst\fR are ignored
 .IP \fBnw_proto=\fIproto\fR
 When \fBip\fR or \fBdl_type=0x0800\fR is specified, matches IP
 protocol type \fIproto\fR, which is specified as a decimal number
-between 0 and 255, inclusive (e.g. 6 to match TCP packets).
+between 0 and 255, inclusive (e.g. 1 to match ICMP packets or 6 to match
+TCP packets).
+.IP
+When \fBipv6\fR or \fBdl_type=0x86dd\fR is specified, matches IPv6
+header type \fIproto\fR, which is specified as a decimal number between
+0 and 255, inclusive (e.g. 58 to match ICMPv6 packets or 6 to match
+TCP).  The header type is the terminal header as described in the
+\fBDESIGN\fR document.
 .IP
 When \fBarp\fR or \fBdl_type=0x0806\fR is specified, matches the lower
 8 bits of the ARP opcode.  ARP opcodes greater than 255 are treated as
 0.
 .IP
-When \fBdl_type\fR is wildcarded or set to a value other than 0x0800
-or 0x0806, the value of \fBnw_proto\fR is ignored (see \fBFlow
+When \fBdl_type\fR is wildcarded or set to a value other than 0x0800,
+0x0806, or 0x86dd, the value of \fBnw_proto\fR is ignored (see \fBFlow
 Syntax\fR above).
 .
 .IP \fBnw_tos=\fItos\fR
-Matches IP ToS/DSCP field \fItos\fR, which is specified as a decimal 
-number between 0 and 255, inclusive.  Note that the two lower reserved
-bits are ignored for matching purposes.
+Matches IP ToS/DSCP or IPv6 traffic class field \fItos\fR, which is
+specified as a decimal number between 0 and 255, inclusive.  Note that
+the two lower reserved bits are ignored for matching purposes.
 .IP
-The value of \fBnw_tos\fR is ignored unless \fBdl_type=0x0800\fR,
-\fBip\fR, \fBicmp\fR, \fBtcp\fR, or \fBudp\fR is also specified (see
-\fBFlow Syntax\fR above).
+When \fBdl_type\fR is wildcarded or set to a value other than 0x0800,
+0x0806, or 0x86dd, the value of \fBnw_tos\fR is ignored (see \fBFlow
+Syntax\fR above).
 .
 .IP \fBtp_src=\fIport\fR
 .IQ \fBtp_dst=\fIport\fR
@@ -331,14 +338,32 @@ these settings are ignored (see \fBFlow Syntax\fR above).
 .
 .IP \fBicmp_type=\fItype\fR
 .IQ \fBicmp_code=\fIcode\fR
-When \fBdl_type\fR and \fBnw_proto\fR specify ICMP, \fItype\fR matches
-the ICMP type and \fIcode\fR matches the ICMP code.  Each is specified
-as a decimal number between 0 and 255, inclusive.
+When \fBdl_type\fR and \fBnw_proto\fR specify ICMP or ICMPv6, \fItype\fR
+matches the ICMP type and \fIcode\fR matches the ICMP code.  Each is
+specified as a decimal number between 0 and 255, inclusive.
 .IP
 When \fBdl_type\fR and \fBnw_proto\fR take other values, the values of
 these settings are ignored (see \fBFlow Syntax\fR above).
 .
 .PP
+The following shorthand notations are also available:
+.
+.IP \fBip\fR
+Same as \fBdl_type=0x0800\fR.
+.
+.IP \fBicmp\fR
+Same as \fBdl_type=0x0800,nw_proto=1\fR.
+.
+.IP \fBtcp\fR
+Same as \fBdl_type=0x0800,nw_proto=6\fR.
+.
+.IP \fBudp\fR
+Same as \fBdl_type=0x0800,nw_proto=17\fR.
+.
+.IP \fBarp\fR
+Same as \fBdl_type=0x0806\fR.
+.
+.PP
 The following field assignments require support for the NXM (Nicira
 Extended Match) extension to OpenFlow.  When one of these is specified,
 \fBovs\-ofctl\fR will automatically attempt to negotiate use of this
@@ -351,6 +376,18 @@ When \fBdl_type\fR specifies ARP, \fBarp_sha\fR and \fBarp_tha\fR match
 the source and target hardware address, respectively.  An address is
 specified as 6 pairs of hexadecimal digits delimited by colons.
 .
+.IP \fBipv6_src=\fIipv6\fR[\fB/\fInetmask\fR]
+.IQ \fBipv6_dst=\fIipv6\fR[\fB/\fInetmask\fR]
+When \fBdl_type\fR is 0x86dd (possibly via shorthand, e.g., \fBipv6\fR
+or \fBtcp6\fR), matches IPv6 source (or destination) address \fIipv6\fR,
+which may be specified as defined in RFC 2373.  The preferred format is 
+\fIx\fB:\fIx\fB:\fIx\fB:\fIx\fB:\fIx\fB:\fIx\fB:\fIx\fB:\fIx\fR, where
+\fIx\fR are the hexadecimal values of the eight 16-bit pieces of the
+address.  A single instance of \fB::\fR may be used to indicate multiple
+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 \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
@@ -381,22 +418,21 @@ When a packet enters an OpenFlow switch, all of the registers are set
 to 0.  Only explicit Nicira extension actions change register values.
 .
 .PP
-The following shorthand notations are also available:
+Defining IPv6 flows (those with \fBdl_type\fR equal to 0x86dd) requires
+support for NXM.  The following shorthand notations are available for
+IPv6-related flows:
 .
-.IP \fBip\fR
-Same as \fBdl_type=0x0800\fR.
+.IP \fBipv6\fR
+Same as \fBdl_type=0x86dd\fR.
 .
-.IP \fBicmp\fR
-Same as \fBdl_type=0x0800,nw_proto=1\fR.
-.
-.IP \fBtcp\fR
-Same as \fBdl_type=0x0800,nw_proto=6\fR.
+.IP \fBtcp6\fR
+Same as \fBdl_type=0x86dd,nw_proto=6\fR.
 .
-.IP \fBudp\fR
-Same as \fBdl_type=0x0800,nw_proto=17\fR.
+.IP \fBudp6\fR
+Same as \fBdl_type=0x86dd,nw_proto=17\fR.
 .
-.IP \fBarp\fR
-Same as \fBdl_type=0x0806\fR.
+.IP \fBicmp6\fR
+Same as \fBdl_type=0x86dd,nw_proto=58\fR.
 .
 .PP
 The \fBadd\-flow\fR and \fBadd\-flows\fR commands require an additional
-- 
1.7.1





More information about the dev mailing list