From: Justin Pettit Date: Thu, 30 Dec 2010 03:03:46 +0000 (-0800) Subject: nicira-ext: Support matching IPv6 traffic. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d31f1109f10e5ffb9bf266306b913ebf23781666;p=openvswitch nicira-ext: Support matching IPv6 traffic. 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 Acked-by: Ben Pfaff --- diff --git a/DESIGN b/DESIGN new file mode 100644 index 00000000..56e26053 --- /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@openvswitch.org. diff --git a/Makefile.am b/Makefile.am index deae5127..71a0652c 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 6fc1c7a1..0cd14272 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 d83c17d9..4365e22c 100644 --- a/datapath/flow.c +++ b/datapath/flow.c @@ -24,11 +24,14 @@ #include #include #include +#include #include #include #include +#include #include #include +#include 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 - nh_ofs; + + /* 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; } @@ -420,7 +516,8 @@ int flow_cmp(const struct tbl_node *node, void *key2_) * This state machine accepts the following forms, with [] for optional * elements and | for alternatives: * - * [tun_id] in_port ethernet [8021q] [ethertype [IP [TCP|UDP|ICMP] | ARP] + * [tun_id] in_port ethernet [8021q] [ethertype \ + * [IPv4 [TCP|UDP|ICMP] | IPv6 [TCP|UDP|ICMPv6] | ARP]] */ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr) { @@ -440,18 +537,22 @@ 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), + [ODP_KEY_ATTR_ICMPV6] = sizeof(struct odp_key_icmpv6), [ODP_KEY_ATTR_ARP] = sizeof(struct odp_key_arp), }; 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; + const struct odp_key_icmpv6 *icmpv6_key; const struct odp_key_arp *arp_key; int type = nla_type(nla); @@ -499,15 +600,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 +632,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); @@ -531,12 +648,20 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr) swkey->tp_dst = htons(icmp_key->icmp_code); break; + case TRANSITION(ODP_KEY_ATTR_IPV6, ODP_KEY_ATTR_ICMPV6): + if (swkey->nw_proto != IPPROTO_ICMPV6) + return -EINVAL; + icmpv6_key = nla_data(nla); + swkey->tp_src = htons(icmpv6_key->icmpv6_type); + swkey->tp_dst = htons(icmpv6_key->icmpv6_code); + break; + case TRANSITION(ODP_KEY_ATTR_ETHERTYPE, ODP_KEY_ATTR_ARP): 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); @@ -578,9 +703,17 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr) return -EINVAL; return 0; + case ODP_KEY_ATTR_IPV6: + if (swkey->nw_proto == IPPROTO_TCP || + swkey->nw_proto == IPPROTO_UDP || + swkey->nw_proto == IPPROTO_ICMPV6) + return -EINVAL; + return 0; + case ODP_KEY_ATTR_TCP: case ODP_KEY_ATTR_UDP: case ODP_KEY_ATTR_ICMP: + case ODP_KEY_ATTR_ICMPV6: case ODP_KEY_ATTR_ARP: return 0; } @@ -626,10 +759,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 +811,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 +821,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_icmpv6 *icmpv6_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_ICMPV6, sizeof(*icmpv6_key)); + if (!nla) + goto nla_put_failure; + icmpv6_key = nla_data(nla); + icmpv6_key->icmpv6_type = ntohs(swkey->tp_src); + icmpv6_key->icmpv6_code = ntohs(swkey->tp_dst); + } } return 0; diff --git a/datapath/flow.h b/datapath/flow.h index b9af2722..ee1c4c92 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 b7b95243..88c5769d 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 00000000..f005a48e --- /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 + +#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 c5b0a7ca..f6786263 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 fdd225db..13b7d9d1 100644 --- a/include/openvswitch/datapath-protocol.h +++ b/include/openvswitch/datapath-protocol.h @@ -311,9 +311,11 @@ 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 */ + ODP_KEY_ATTR_ICMPV6, /* struct odp_key_icmpv6 */ ODP_KEY_ATTR_ARP, /* struct odp_key_arp */ __ODP_KEY_ATTR_MAX }; @@ -337,6 +339,13 @@ struct odp_key_ipv4 { uint8_t ipv4_tos; }; +struct odp_key_ipv6 { + ovs_be32 ipv6_src[4]; + ovs_be32 ipv6_dst[4]; + uint8_t ipv6_proto; + uint8_t ipv6_tos; +}; + struct odp_key_tcp { ovs_be16 tcp_src; ovs_be16 tcp_dst; @@ -352,6 +361,11 @@ struct odp_key_icmp { uint8_t icmp_code; }; +struct odp_key_icmpv6 { + uint8_t icmpv6_type; + uint8_t icmpv6_code; +}; + struct odp_key_arp { ovs_be32 arp_sip; ovs_be32 arp_tip; diff --git a/lib/classifier.c b/lib/classifier.c index a4532a61..658be808 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)); @@ -964,6 +1049,30 @@ next_rule_in_list(struct cls_rule *rule) return next->priority < rule->priority ? next : NULL; } +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) @@ -971,7 +1080,7 @@ flow_equal_except(const struct flow *a, const struct flow *b, const flow_wildcards_t wc = wildcards->wildcards; int i; - BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 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 40149727..c82a4844 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 ff06318c..41f13b88 100644 --- a/lib/flow.c +++ b/lib/flow.c @@ -16,8 +16,11 @@ #include #include #include "flow.h" +#include #include #include +#include +#include #include #include #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 @@ -475,7 +683,10 @@ uint32_t flow_hash_symmetric_l4(const struct flow *flow, uint32_t basis) { struct { - ovs_be32 ip_addr; + union { + ovs_be32 ipv4_addr; + struct in6_addr ipv6_addr; + }; ovs_be16 eth_type; ovs_be16 vlan_tci; ovs_be16 tp_addr; @@ -492,17 +703,23 @@ flow_hash_symmetric_l4(const struct flow *flow, uint32_t basis) fields.vlan_tci = flow->vlan_tci & htons(VLAN_VID_MASK); fields.eth_type = flow->dl_type; if (fields.eth_type == htons(ETH_TYPE_IP)) { - fields.ip_addr = flow->nw_src ^ flow->nw_dst; + fields.ipv4_addr = flow->nw_src ^ flow->nw_dst; + fields.ip_proto = flow->nw_proto; + if (fields.ip_proto == IPPROTO_TCP || fields.ip_proto == IPPROTO_UDP) { + fields.tp_addr = flow->tp_src ^ flow->tp_dst; + } + } else if (fields.eth_type == htons(ETH_TYPE_IPV6)) { + const uint8_t *a = &flow->ipv6_src.s6_addr[0]; + const uint8_t *b = &flow->ipv6_dst.s6_addr[0]; + uint8_t *ipv6_addr = &fields.ipv6_addr.s6_addr[0]; + + for (i=0; i<16; i++) { + ipv6_addr[i] = a[i] ^ b[i]; + } fields.ip_proto = flow->nw_proto; if (fields.ip_proto == IPPROTO_TCP || fields.ip_proto == IPPROTO_UDP) { fields.tp_addr = flow->tp_src ^ flow->tp_dst; - } else { - fields.tp_addr = htons(0); } - } else { - fields.ip_addr = htonl(0); - fields.ip_proto = 0; - fields.tp_addr = htons(0); } return hash_bytes(&fields, sizeof fields, basis); } diff --git a/lib/flow.h b/lib/flow.h index a5b271ee..d331aa36 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 d43372b8..5fc6aa24 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 966143dc..41e76d65 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, IPPROTO_TCP, false) -DEFINE_FIELD (OF_TCP_DST, FWW_TP_DST, ETH_TYPE_IP, IPPROTO_TCP, false) -DEFINE_FIELD (OF_UDP_SRC, FWW_TP_SRC, ETH_TYPE_IP, IPPROTO_UDP, false) -DEFINE_FIELD (OF_UDP_DST, FWW_TP_DST, ETH_TYPE_IP, IPPROTO_UDP, false) -DEFINE_FIELD (OF_ICMP_TYPE, FWW_TP_SRC, ETH_TYPE_IP, IPPROTO_ICMP, false) -DEFINE_FIELD (OF_ICMP_CODE, FWW_TP_DST, ETH_TYPE_IP, IPPROTO_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, IPPROTO_TCP, false) +DEFINE_FIELD (OF_TCP_DST, FWW_TP_DST, NXM_DL_IP_ANY, IPPROTO_TCP, false) +DEFINE_FIELD (OF_UDP_SRC, FWW_TP_SRC, NXM_DL_IP_ANY, IPPROTO_UDP, false) +DEFINE_FIELD (OF_UDP_DST, FWW_TP_DST, NXM_DL_IP_ANY, IPPROTO_UDP, false) +DEFINE_FIELD (OF_ICMP_TYPE, FWW_TP_SRC, NXM_DL_IP, IPPROTO_ICMP, false) +DEFINE_FIELD (OF_ICMP_CODE, FWW_TP_DST, NXM_DL_IP, IPPROTO_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 bd4fea27..aefcb653 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 bacb1c04..e7acaad8 100644 --- a/lib/odp-util.c +++ b/lib/odp-util.c @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include "odp-util.h" #include @@ -194,9 +195,11 @@ 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); + case ODP_KEY_ATTR_ICMPV6: return sizeof(struct odp_key_icmpv6); case ODP_KEY_ATTR_ARP: return sizeof(struct odp_key_arp); case ODP_KEY_ATTR_UNSPEC: @@ -233,9 +236,11 @@ 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; + const struct odp_key_icmpv6 *icmpv6_key; const struct odp_key_arp *arp_key; if (nl_attr_get_size(a) != odp_flow_key_attr_len(nl_attr_type(a))) { @@ -287,6 +292,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")", @@ -305,6 +324,12 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds) icmp_key->icmp_type, icmp_key->icmp_code); break; + case ODP_KEY_ATTR_ICMPV6: + icmpv6_key = nl_attr_get(a); + ds_put_format(ds, "icmpv6(type=%"PRIu8",code=%"PRIu8")", + icmpv6_key->icmpv6_type, icmpv6_key->icmpv6_code); + break; + case ODP_KEY_ATTR_ARP: arp_key = nl_attr_get(a); ds_put_format(ds, "arp(sip="IP_FMT",tip="IP_FMT",op=%"PRIu16"," @@ -387,6 +412,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 == IPPROTO_TCP) { struct odp_key_tcp *tcp_key; @@ -402,24 +450,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 == IPPROTO_ICMP) { + } else if (flow->dl_type == htons(ETH_TYPE_IP) + && flow->nw_proto == IPPROTO_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_IPV6) + && flow->nw_proto == IPPROTO_ICMPV6) { + struct odp_key_icmpv6 *icmpv6_key; + + icmpv6_key = nl_msg_put_unspec_uninit(buf, ODP_KEY_ATTR_ICMPV6, + sizeof *icmpv6_key); + icmpv6_key->icmpv6_type = ntohs(flow->tp_src); + icmpv6_key->icmpv6_code = ntohs(flow->tp_dst); } - } 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); } } @@ -441,9 +488,11 @@ 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; + const struct odp_key_icmpv6 *icmpv6_key; const struct odp_key_arp *arp_key; uint16_t type = nl_attr_type(nla); @@ -507,7 +556,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 != IPPROTO_TCP) { return EINVAL; } @@ -517,6 +581,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 != IPPROTO_UDP) { return EINVAL; } @@ -534,6 +599,15 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len, flow->tp_dst = htons(icmp_key->icmp_code); break; + case TRANSITION(ODP_KEY_ATTR_IPV6, ODP_KEY_ATTR_ICMPV6): + if (flow->nw_proto != IPPROTO_ICMPV6) { + return EINVAL; + } + icmpv6_key = nl_attr_get(nla); + flow->tp_src = htons(icmpv6_key->icmpv6_type); + flow->tp_dst = htons(icmpv6_key->icmpv6_code); + break; + case TRANSITION(ODP_KEY_ATTR_ETHERTYPE, ODP_KEY_ATTR_ARP): if (flow->dl_type != htons(ETH_TYPE_ARP)) { return EINVAL; @@ -577,6 +651,7 @@ 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; } @@ -590,9 +665,18 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len, } return 0; + case ODP_KEY_ATTR_IPV6: + if (flow->nw_proto == IPPROTO_TCP + || flow->nw_proto == IPPROTO_UDP + || flow->nw_proto == IPPROTO_ICMPV6) { + return EINVAL; + } + return 0; + case ODP_KEY_ATTR_TCP: case ODP_KEY_ATTR_UDP: case ODP_KEY_ATTR_ICMP: + case ODP_KEY_ATTR_ICMPV6: case ODP_KEY_ATTR_ARP: return 0; diff --git a/lib/odp-util.h b/lib/odp-util.h index 1a0d58df..8ec09f3c 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 366664e3..e77453e0 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, IPPROTO_ICMP }, { "tcp", ETH_TYPE_IP, IPPROTO_TCP }, { "udp", ETH_TYPE_IP, IPPROTO_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 59a5fc43..4d89e0ae 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 2dc82fe1..05148fe0 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -16,10 +16,16 @@ #include #include "packets.h" +#include +#include #include #include +#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 c0480db7..51121108 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 @@ -371,4 +373,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 e97e7c90..469131d4 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 40a96144..f4e617a6 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 7eecf287..a86588b9 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 464a8eb7..dda4797f 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 135e705c..37425529 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