diff --git a/DESIGN b/DESIGN index 56e260532..6e25f01bf 100644 --- a/DESIGN +++ b/DESIGN @@ -15,7 +15,8 @@ IPv6 Open vSwitch supports stateless handling of IPv6 packets. Flows can be written to support matching TCP, UDP, and ICMPv6 headers within an IPv6 -packet. +packet. Deeper matching of some Neighbor Discovery messages is also +supported. IPv6 was not designed to interact well with middle-boxes. This, combined with Open vSwitch's stateless nature, have affected the diff --git a/datapath/flow.c b/datapath/flow.c index 4365e22c3..9823b9feb 100644 --- a/datapath/flow.c +++ b/datapath/flow.c @@ -32,6 +32,7 @@ #include #include #include +#include static struct kmem_cache *flow_cache; static unsigned int hash_seed __read_mostly; @@ -314,6 +315,75 @@ static __be16 parse_ethertype(struct sk_buff *skb) return llc->ethertype; } +static int parse_icmpv6(struct sk_buff *skb, struct sw_flow_key *key, + int nh_len) +{ + struct ipv6hdr *nh = ipv6_hdr(skb); + int icmp_len = ntohs(nh->payload_len) + sizeof(*nh) - nh_len; + struct icmp6hdr *icmp = icmp6_hdr(skb); + + /* The ICMPv6 type and code fields use the 16-bit transport port + * fields, so we need to store them in 16-bit network byte order. */ + key->tp_src = htons(icmp->icmp6_type); + key->tp_dst = htons(icmp->icmp6_code); + + if (!icmp->icmp6_code + && ((icmp->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION) + || (icmp->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT))) { + struct nd_msg *nd; + int offset; + + /* In order to process neighbor discovery options, we need the + * entire packet. */ + if (icmp_len < sizeof(*nd)) + goto invalid; + if (!pskb_may_pull(skb, skb_transport_offset(skb) + icmp_len)) + return -ENOMEM; + + nd = (struct nd_msg *)skb_transport_header(skb); + memcpy(key->nd_target, &nd->target, sizeof(key->nd_target)); + + icmp_len -= sizeof(*nd); + offset = 0; + while (icmp_len >= 8) { + struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd->opt + offset); + int opt_len = nd_opt->nd_opt_len * 8; + + if (!opt_len || (opt_len > icmp_len)) + goto invalid; + + /* Store the link layer address if the appropriate option is + * provided. It is considered an error if the same link + * layer option is specified twice. */ + if (nd_opt->nd_opt_type == ND_OPT_SOURCE_LL_ADDR + && opt_len == 8) { + if (!is_zero_ether_addr(key->arp_sha)) + goto invalid; + memcpy(key->arp_sha, + &nd->opt[offset+sizeof(*nd_opt)], ETH_ALEN); + } else if (nd_opt->nd_opt_type == ND_OPT_TARGET_LL_ADDR + && opt_len == 8) { + if (!is_zero_ether_addr(key->arp_tha)) + goto invalid; + memcpy(key->arp_tha, + &nd->opt[offset+sizeof(*nd_opt)], ETH_ALEN); + } + + icmp_len -= opt_len; + offset += opt_len; + } + } + + return 0; + +invalid: + memset(key->nd_target, 0, sizeof(key->nd_target)); + memset(key->arp_sha, 0, sizeof(key->arp_sha)); + memset(key->arp_tha, 0, sizeof(key->arp_tha)); + + return 0; +} + /** * flow_extract - extracts a flow key from an Ethernet frame. * @skb: sk_buff that contains the frame, with skb->data pointing to the @@ -482,12 +552,9 @@ int flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key, } } else if (key->nw_proto == NEXTHDR_ICMP) { if (icmp6hdr_ok(skb)) { - struct icmp6hdr *icmp = icmp6_hdr(skb); - /* The ICMPv6 type and code fields use the 16-bit - * transport port fields, so we need to store them - * in 16-bit network byte order. */ - key->tp_src = htons(icmp->icmp6_type); - key->tp_dst = htons(icmp->icmp6_code); + int error = parse_icmpv6(skb, key, nh_len); + if (error < 0) + return error; } } } @@ -517,7 +584,7 @@ int flow_cmp(const struct tbl_node *node, void *key2_) * elements and | for alternatives: * * [tun_id] in_port ethernet [8021q] [ethertype \ - * [IPv4 [TCP|UDP|ICMP] | IPv6 [TCP|UDP|ICMPv6] | ARP]] + * [IPv4 [TCP|UDP|ICMP] | IPv6 [TCP|UDP|ICMPv6 [ND]] | ARP]] */ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr) { @@ -543,6 +610,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr) [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), + [ODP_KEY_ATTR_ND] = sizeof(struct odp_key_nd), }; const struct odp_key_ethernet *eth_key; @@ -554,6 +622,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr) const struct odp_key_icmp *icmp_key; const struct odp_key_icmpv6 *icmpv6_key; const struct odp_key_arp *arp_key; + const struct odp_key_nd *nd_key; int type = nla_type(nla); @@ -669,6 +738,17 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr) memcpy(swkey->arp_tha, arp_key->arp_tha, ETH_ALEN); break; + case TRANSITION(ODP_KEY_ATTR_ICMPV6, ODP_KEY_ATTR_ND): + if (swkey->tp_src != htons(NDISC_NEIGHBOUR_SOLICITATION) + && swkey->tp_src != htons(NDISC_NEIGHBOUR_ADVERTISEMENT)) + return -EINVAL; + nd_key = nla_data(nla); + memcpy(swkey->nd_target, nd_key->nd_target, + sizeof(swkey->nd_target)); + memcpy(swkey->arp_sha, nd_key->nd_sll, ETH_ALEN); + memcpy(swkey->arp_tha, nd_key->nd_tll, ETH_ALEN); + break; + default: return -EINVAL; } @@ -710,11 +790,17 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, const struct nlattr *attr) return -EINVAL; return 0; + case ODP_KEY_ATTR_ICMPV6: + if (swkey->tp_src == htons(NDISC_NEIGHBOUR_SOLICITATION) || + swkey->tp_src == htons(NDISC_NEIGHBOUR_ADVERTISEMENT)) + return -EINVAL; + return 0; + case ODP_KEY_ATTR_TCP: case ODP_KEY_ATTR_UDP: case ODP_KEY_ATTR_ICMP: - case ODP_KEY_ATTR_ICMPV6: case ODP_KEY_ATTR_ARP: + case ODP_KEY_ATTR_ND: return 0; } @@ -831,6 +917,20 @@ int flow_to_nlattrs(const struct sw_flow_key *swkey, struct sk_buff *skb) icmpv6_key = nla_data(nla); icmpv6_key->icmpv6_type = ntohs(swkey->tp_src); icmpv6_key->icmpv6_code = ntohs(swkey->tp_dst); + + if (icmpv6_key->icmpv6_type == NDISC_NEIGHBOUR_SOLICITATION + || icmpv6_key->icmpv6_type == NDISC_NEIGHBOUR_ADVERTISEMENT) { + struct odp_key_nd *nd_key; + + nla = nla_reserve(skb, ODP_KEY_ATTR_ND, sizeof(*nd_key)); + if (!nla) + goto nla_put_failure; + nd_key = nla_data(nla); + memcpy(nd_key->nd_target, swkey->nd_target, + sizeof(nd_key->nd_target)); + memcpy(nd_key->nd_sll, swkey->arp_sha, ETH_ALEN); + memcpy(nd_key->nd_tll, swkey->arp_tha, ETH_ALEN); + } } } diff --git a/datapath/flow.h b/datapath/flow.h index ee1c4c92a..21df5fbee 100644 --- a/datapath/flow.h +++ b/datapath/flow.h @@ -41,6 +41,7 @@ struct sw_flow_key { __be32 ipv6_dst[4]; /* IPv6 source address. */ }; }; + __be32 nd_target[4]; /* IPv6 ND target address. */ u16 in_port; /* Input switch port. */ __be16 dl_tci; /* 0 if no VLAN, VLAN_TAG_PRESENT set otherwise. */ __be16 dl_type; /* Ethernet frame type. */ @@ -50,8 +51,8 @@ struct sw_flow_key { u8 dl_dst[ETH_ALEN]; /* Ethernet destination address. */ u8 nw_proto; /* IP protocol or lower 8 bits of ARP opcode. */ u8 nw_tos; /* IP ToS (DSCP field, 6 bits). */ - u8 arp_sha[ETH_ALEN]; /* ARP source hardware address. */ - u8 arp_tha[ETH_ALEN]; /* ARP target hardware address. */ + u8 arp_sha[ETH_ALEN]; /* ARP/ND source hardware address. */ + u8 arp_tha[ETH_ALEN]; /* ARP/ND target hardware address. */ }; struct sw_flow { diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h index f67862639..88efdf653 100644 --- a/include/openflow/nicira-ext.h +++ b/include/openflow/nicira-ext.h @@ -390,6 +390,8 @@ OFP_ASSERT(sizeof(struct nx_action_pop_queue) == 16); * - NXM_NX_ARP_THA * - NXM_NX_ICMPV6_TYPE * - NXM_NX_ICMPV6_CODE + * - NXM_NX_ND_SLL + * - NXM_NX_ND_TLL * - NXM_NX_REG(idx) for idx in the switch's accepted range. * * The following nxm_header values are potentially acceptable as 'dst': @@ -1078,6 +1080,44 @@ enum nx_mp_algorithm { #define NXM_NX_ICMPV6_TYPE NXM_HEADER (0x0001, 21, 1) #define NXM_NX_ICMPV6_CODE NXM_HEADER (0x0001, 22, 1) +/* The target address in an IPv6 Neighbor Discovery message. + * + * Prereqs: + * NXM_OF_ETH_TYPE must match 0x86dd exactly. + * NXM_OF_IP_PROTO must match 58 exactly. + * NXM_OF_ICMPV6_TYPE must be either 135 or 136. + * + * Format: 128-bit IPv6 address. + * + * Masking: Not maskable. */ +#define NXM_NX_ND_TARGET NXM_HEADER (0x0001, 23, 16) + +/* The source link-layer address option in an IPv6 Neighbor Discovery + * message. + * + * Prereqs: + * NXM_OF_ETH_TYPE must match 0x86dd exactly. + * NXM_OF_IP_PROTO must match 58 exactly. + * NXM_OF_ICMPV6_TYPE must be exactly 135. + * + * Format: 48-bit Ethernet MAC address. + * + * Masking: Not maskable. */ +#define NXM_NX_ND_SLL NXM_HEADER (0x0001, 24, 6) + +/* The target link-layer address option in an IPv6 Neighbor Discovery + * message. + * + * Prereqs: + * NXM_OF_ETH_TYPE must match 0x86dd exactly. + * NXM_OF_IP_PROTO must match 58 exactly. + * NXM_OF_ICMPV6_TYPE must be exactly 136. + * + * Format: 48-bit Ethernet MAC address. + * + * Masking: Not maskable. */ +#define NXM_NX_ND_TLL NXM_HEADER (0x0001, 25, 6) + /* ## --------------------- ## */ /* ## Requests and replies. ## */ diff --git a/include/openvswitch/datapath-protocol.h b/include/openvswitch/datapath-protocol.h index 13b7d9d1a..8645096ab 100644 --- a/include/openvswitch/datapath-protocol.h +++ b/include/openvswitch/datapath-protocol.h @@ -317,6 +317,7 @@ enum odp_key_type { 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_ND, /* struct odp_key_nd */ __ODP_KEY_ATTR_MAX }; @@ -374,6 +375,12 @@ struct odp_key_arp { uint8_t arp_tha[6]; }; +struct odp_key_nd { + uint32_t nd_target[4]; + uint8_t nd_sll[6]; + uint8_t nd_tll[6]; +}; + /** * enum odp_flow_attr - attributes for %ODP_FLOW_* commands. * @ODP_FLOW_ATTR_KEY: Nested %ODP_KEY_ATTR_* attributes specifying the flow diff --git a/lib/classifier.c b/lib/classifier.c index 658be8086..fcc965c87 100644 --- a/lib/classifier.c +++ b/lib/classifier.c @@ -369,6 +369,13 @@ cls_rule_set_ipv6_dst_masked(struct cls_rule *rule, const struct in6_addr *dst, } } +void +cls_rule_set_nd_target(struct cls_rule *rule, const struct in6_addr target) +{ + rule->wc.wildcards &= ~FWW_ND_TARGET; + rule->flow.nd_target = target; +} + /* Returns true if 'a' and 'b' have the same priority, wildcard the same * fields, and have the same values for fixed fields, otherwise false. */ bool @@ -586,7 +593,20 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s) if (!(w & FWW_TP_DST)) { ds_put_format(s, "icmp_code=%"PRIu16",", ntohs(f->tp_dst)); } - } else { + if (!(w & FWW_ND_TARGET)) { + ds_put_cstr(s, "nd_target="); + print_ipv6_addr(s, &f->nd_target); + ds_put_char(s, ','); + } + if (!(w & FWW_ARP_SHA)) { + ds_put_format(s, "nd_sll="ETH_ADDR_FMT",", + ETH_ADDR_ARGS(f->arp_sha)); + } + if (!(w & FWW_ARP_THA)) { + ds_put_format(s, "nd_tll="ETH_ADDR_FMT",", + ETH_ADDR_ARGS(f->arp_tha)); + } + } else { if (!(w & FWW_TP_SRC)) { ds_put_format(s, "tp_src=%"PRIu16",", ntohs(f->tp_src)); } @@ -1080,7 +1100,7 @@ flow_equal_except(const struct flow *a, const struct flow *b, const flow_wildcards_t wc = wildcards->wildcards; int i; - BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 84 + FLOW_N_REGS * 4); + BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 100 + FLOW_N_REGS * 4); for (i = 0; i < FLOW_N_REGS; i++) { if ((a->regs[i] ^ b->regs[i]) & wildcards->reg_masks[i]) { @@ -1113,7 +1133,9 @@ flow_equal_except(const struct flow *a, const struct flow *b, && ipv6_equal_except(&a->ipv6_src, &b->ipv6_src, &wildcards->ipv6_src_mask) && ipv6_equal_except(&a->ipv6_dst, &b->ipv6_dst, - &wildcards->ipv6_dst_mask)); + &wildcards->ipv6_dst_mask) + && (wc & FWW_ND_TARGET + || ipv6_addr_equals(&a->nd_target, &b->nd_target))); } static void @@ -1122,7 +1144,7 @@ zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards) const flow_wildcards_t wc = wildcards->wildcards; int i; - BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 84 + 4 * FLOW_N_REGS); + BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 100 + 4 * FLOW_N_REGS); for (i = 0; i < FLOW_N_REGS; i++) { flow->regs[i] &= wildcards->reg_masks[i]; @@ -1169,4 +1191,7 @@ zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards) &wildcards->ipv6_src_mask); flow->ipv6_dst = ipv6_addr_bitand(&flow->ipv6_dst, &wildcards->ipv6_dst_mask); + if (wc & FWW_ND_TARGET) { + memset(&flow->nd_target, 0, sizeof flow->nd_target); + } } diff --git a/lib/classifier.h b/lib/classifier.h index c82a48449..d3121bfb8 100644 --- a/lib/classifier.h +++ b/lib/classifier.h @@ -109,6 +109,7 @@ bool cls_rule_set_ipv6_src_masked(struct cls_rule *, const struct in6_addr *, void cls_rule_set_ipv6_dst(struct cls_rule *, const struct in6_addr *); bool cls_rule_set_ipv6_dst_masked(struct cls_rule *, const struct in6_addr *, const struct in6_addr *); +void cls_rule_set_nd_target(struct cls_rule *, const struct in6_addr); bool cls_rule_equal(const struct cls_rule *, const struct cls_rule *); diff --git a/lib/flow.c b/lib/flow.c index 41f13b88c..879e462f0 100644 --- a/lib/flow.c +++ b/lib/flow.c @@ -230,6 +230,94 @@ parse_ipv6(struct ofpbuf *packet, struct flow *flow) return nh_len; } +/* Neighbor Discovery Solicitation and Advertisement messages are + * identical in structure, so we'll just use one of them. To be safe, + * we'll assert that they're still identical. */ +BUILD_ASSERT_DECL(sizeof(struct nd_neighbor_solicit) + == sizeof(struct nd_neighbor_advert)); + +static bool +parse_icmpv6(struct ofpbuf *b, struct flow *flow, int icmp_len) +{ + const struct icmp6_hdr *icmp = pull_icmpv6(b); + + if (!icmp) { + return false; + } + + /* The ICMPv6 type and code fields use the 16-bit transport port + * fields, so we need to store them in 16-bit network byte order. */ + flow->icmp_type = htons(icmp->icmp6_type); + flow->icmp_code = htons(icmp->icmp6_code); + + if (!icmp->icmp6_code + && ((icmp->icmp6_type == ND_NEIGHBOR_SOLICIT) + || (icmp->icmp6_type == ND_NEIGHBOR_ADVERT))) { + struct nd_neighbor_solicit *nd_ns; /* Identical to ND advert */ + + /* In order to process neighbor discovery options, we need the + * entire packet. */ + if ((icmp_len < sizeof *nd_ns) + || (!ofpbuf_try_pull(b, sizeof *nd_ns - sizeof *icmp))) { + return false; + } + nd_ns = (struct nd_neighbor_solicit *)icmp; + flow->nd_target = nd_ns->nd_ns_target; + + icmp_len -= sizeof(*nd_ns); + while (icmp_len >= 8) { + struct nd_opt_hdr *nd_opt; + int opt_len; + const uint8_t *data; + + /* The minimum size of an option is 8 bytes, which also is + * the size of Ethernet link-layer options. */ + nd_opt = ofpbuf_pull(b, 8); + if (!nd_opt->nd_opt_len || nd_opt->nd_opt_len * 8 > icmp_len) { + goto invalid; + } + opt_len = nd_opt->nd_opt_len * 8; + data = (const uint8_t *)(nd_opt + 1); + + /* Store the link layer address if the appropriate option is + * provided. It is considered an error if the same link + * layer option is specified twice. */ + if (nd_opt->nd_opt_type == ND_OPT_SOURCE_LINKADDR + && opt_len == 8) { + if (eth_addr_is_zero(flow->arp_sha)) { + memcpy(flow->arp_sha, data, ETH_ADDR_LEN); + } else { + goto invalid; + } + } else if (nd_opt->nd_opt_type == ND_OPT_TARGET_LINKADDR + && opt_len == 8) { + if (eth_addr_is_zero(flow->arp_tha)) { + memcpy(flow->arp_tha, data, ETH_ADDR_LEN); + } else { + goto invalid; + } + } + + /* Pull the rest of this option. */ + if (!ofpbuf_try_pull(b, opt_len - 8)) { + goto invalid; + } + + icmp_len -= opt_len; + } + } + + return true; + +invalid: + memset(&flow->nd_target, '\0', sizeof(flow->nd_target)); + memset(flow->arp_sha, '\0', sizeof(flow->arp_sha)); + memset(flow->arp_tha, '\0', sizeof(flow->arp_tha)); + + return false; + +} + /* Initializes 'flow' members from 'packet', 'tun_id', and 'in_port. * Initializes 'packet' header pointers as follows: * @@ -344,10 +432,8 @@ flow_extract(struct ofpbuf *packet, ovs_be64 tun_id, uint16_t in_port, packet->l7 = b.data; } } else if (flow->nw_proto == IPPROTO_ICMPV6) { - const struct icmp6_hdr *icmp = pull_icmpv6(&b); - if (icmp) { - flow->icmp_type = htons(icmp->icmp6_type); - flow->icmp_code = htons(icmp->icmp6_code); + int icmp_len = ntohs(nh->ip6_plen) + sizeof *nh - nh_len; + if (parse_icmpv6(&b, flow, icmp_len)) { packet->l7 = b.data; } } diff --git a/lib/flow.h b/lib/flow.h index d331aa361..60229f58d 100644 --- a/lib/flow.h +++ b/lib/flow.h @@ -54,19 +54,20 @@ struct flow { uint8_t dl_dst[6]; /* Ethernet destination address. */ uint8_t nw_proto; /* IP protocol or low 8 bits of ARP opcode. */ uint8_t nw_tos; /* IP ToS (DSCP field, 6 bits). */ - uint8_t arp_sha[6]; /* ARP source hardware address. */ - uint8_t arp_tha[6]; /* ARP target hardware address. */ + uint8_t arp_sha[6]; /* ARP/ND source hardware address. */ + uint8_t arp_tha[6]; /* ARP/ND target hardware address. */ struct in6_addr ipv6_src; /* IPv6 source address. */ struct in6_addr ipv6_dst; /* IPv6 destination address. */ + struct in6_addr nd_target; /* IPv6 neighbor discovery (ND) target. */ uint32_t reserved; /* Reserved for 64-bit packing. */ }; /* Assert that there are FLOW_SIG_SIZE bytes of significant data in "struct * flow", followed by FLOW_PAD_SIZE bytes of padding. */ -#define FLOW_SIG_SIZE (84 + FLOW_N_REGS * 4) +#define FLOW_SIG_SIZE (100 + FLOW_N_REGS * 4) #define FLOW_PAD_SIZE 4 -BUILD_ASSERT_DECL(offsetof(struct flow, ipv6_dst) == FLOW_SIG_SIZE - 16); -BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->ipv6_dst) == 16); +BUILD_ASSERT_DECL(offsetof(struct flow, nd_target) == FLOW_SIG_SIZE - 16); +BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->nd_target) == 16); BUILD_ASSERT_DECL(sizeof(struct flow) == FLOW_SIG_SIZE + FLOW_PAD_SIZE); int flow_extract(struct ofpbuf *, uint64_t tun_id, uint16_t in_port, @@ -122,7 +123,8 @@ typedef unsigned int OVS_BITWISE flow_wildcards_t; /* multicast bit only */ #define FWW_ARP_SHA ((OVS_FORCE flow_wildcards_t) (1 << 9)) #define FWW_ARP_THA ((OVS_FORCE flow_wildcards_t) (1 << 10)) -#define FWW_ALL ((OVS_FORCE flow_wildcards_t) (((1 << 11)) - 1)) +#define FWW_ND_TARGET ((OVS_FORCE flow_wildcards_t) (1 << 11)) +#define FWW_ALL ((OVS_FORCE flow_wildcards_t) (((1 << 12)) - 1)) /* Information on wildcards for a flow, as a supplement to "struct flow". * diff --git a/lib/nx-match.c b/lib/nx-match.c index 5fc6aa249..abc3b210a 100644 --- a/lib/nx-match.c +++ b/lib/nx-match.c @@ -18,6 +18,8 @@ #include "nx-match.h" +#include + #include "classifier.h" #include "dynamic-string.h" #include "ofp-util.h" @@ -363,6 +365,30 @@ parse_nxm_entry(struct cls_rule *rule, const struct nxm_field *f, flow->tp_dst = htons(*(uint8_t *) value); return 0; + /* IPv6 Neighbor Discovery. */ + case NFI_NXM_NX_ND_TARGET: + /* We've already verified that it's an ICMPv6 message. */ + if ((flow->tp_src != htons(ND_NEIGHBOR_SOLICIT)) + && (flow->tp_src != htons(ND_NEIGHBOR_ADVERT))) { + return NXM_BAD_PREREQ; + } + memcpy(&flow->nd_target, value, sizeof flow->nd_target); + return 0; + case NFI_NXM_NX_ND_SLL: + /* We've already verified that it's an ICMPv6 message. */ + if (flow->tp_src != htons(ND_NEIGHBOR_SOLICIT)) { + return NXM_BAD_PREREQ; + } + memcpy(flow->arp_sha, value, ETH_ADDR_LEN); + return 0; + case NFI_NXM_NX_ND_TLL: + /* We've already verified that it's an ICMPv6 message. */ + if (flow->tp_src != htons(ND_NEIGHBOR_ADVERT)) { + return NXM_BAD_PREREQ; + } + memcpy(flow->arp_tha, value, ETH_ADDR_LEN); + return 0; + /* ARP header. */ case NFI_NXM_OF_ARP_OP: if (ntohs(get_unaligned_be16(value)) > 255) { @@ -815,6 +841,16 @@ nx_put_match(struct ofpbuf *b, const struct cls_rule *cr) if (!(wc & FWW_TP_DST)) { nxm_put_8(b, NXM_NX_ICMPV6_CODE, ntohs(flow->tp_dst)); } + if (!(wc & FWW_ND_TARGET)) { + nxm_put_ipv6(b, NXM_NX_ND_TARGET, &flow->nd_target, + &in6addr_exact); + } + if (!(wc & FWW_ARP_SHA)) { + nxm_put_eth(b, NXM_NX_ND_SLL, flow->arp_sha); + } + if (!(wc & FWW_ARP_THA)) { + nxm_put_eth(b, NXM_NX_ND_TLL, flow->arp_tha); + } break; } } @@ -1303,9 +1339,11 @@ nxm_read_field(const struct nxm_field *src, const struct flow *flow) #endif case NFI_NXM_NX_ARP_SHA: + case NFI_NXM_NX_ND_SLL: return eth_addr_to_uint64(flow->arp_sha); case NFI_NXM_NX_ARP_THA: + case NFI_NXM_NX_ND_TLL: return eth_addr_to_uint64(flow->arp_tha); case NFI_NXM_NX_TUN_ID_W: @@ -1319,6 +1357,7 @@ nxm_read_field(const struct nxm_field *src, const struct flow *flow) case NFI_NXM_NX_IPV6_SRC_W: case NFI_NXM_NX_IPV6_DST: case NFI_NXM_NX_IPV6_DST_W: + case NFI_NXM_NX_ND_TARGET: case N_NXM_FIELDS: NOT_REACHED(); } @@ -1392,6 +1431,9 @@ nxm_write_field(const struct nxm_field *dst, struct flow *flow, case NFI_NXM_NX_IPV6_DST_W: case NFI_NXM_NX_ICMPV6_TYPE: case NFI_NXM_NX_ICMPV6_CODE: + case NFI_NXM_NX_ND_TARGET: + case NFI_NXM_NX_ND_SLL: + case NFI_NXM_NX_ND_TLL: case N_NXM_FIELDS: NOT_REACHED(); } diff --git a/lib/nx-match.def b/lib/nx-match.def index 41e76d656..4a42aaa47 100644 --- a/lib/nx-match.def +++ b/lib/nx-match.def @@ -53,6 +53,9 @@ DEFINE_FIELD_M(NX_IPV6_SRC, 0, NXM_DL_IPV6, 0, false) DEFINE_FIELD_M(NX_IPV6_DST, 0, NXM_DL_IPV6, 0, false) DEFINE_FIELD (NX_ICMPV6_TYPE, FWW_TP_SRC, NXM_DL_IPV6, IPPROTO_ICMPV6, false) DEFINE_FIELD (NX_ICMPV6_CODE, FWW_TP_DST, NXM_DL_IPV6, IPPROTO_ICMPV6, false) +DEFINE_FIELD (NX_ND_TARGET, FWW_ND_TARGET,NXM_DL_IPV6, IPPROTO_ICMPV6, false) +DEFINE_FIELD (NX_ND_SLL, FWW_ARP_SHA, NXM_DL_IPV6, IPPROTO_ICMPV6, false) +DEFINE_FIELD (NX_ND_TLL, FWW_ARP_THA, NXM_DL_IPV6, IPPROTO_ICMPV6, false) DEFINE_FIELD_M(NX_REG0, 0, NXM_DL_NONE, 0, true) #if FLOW_N_REGS >= 2 diff --git a/lib/nx-match.h b/lib/nx-match.h index aefcb653b..a76ad1f2e 100644 --- a/lib/nx-match.h +++ b/lib/nx-match.h @@ -95,15 +95,17 @@ nxm_decode_n_bits(ovs_be16 ofs_nbits) * NXM_OF_IP_PROTO 4 2 -- 6 * NXM_OF_IPV6_SRC_W 4 16 16 36 * NXM_OF_IPV6_DST_W 4 16 16 36 - * NXM_OF_TCP_SRC 4 2 -- 6 - * NXM_OF_TCP_DST 4 2 -- 6 + * NXM_OF_ICMP_TYPE 4 1 -- 5 + * NXM_OF_ICMP_CODE 4 1 -- 5 + * NXM_NX_ND_TARGET 4 16 -- 20 + * NXM_NX_ND_SLL 4 6 -- 10 * NXM_NX_REG_W(0) 4 4 4 12 * NXM_NX_REG_W(1) 4 4 4 12 * NXM_NX_REG_W(2) 4 4 4 12 * NXM_NX_REG_W(3) 4 4 4 12 * NXM_NX_TUN_ID_W 4 8 8 20 * ------------------------------------------- - * total 209 + * total 237 * * So this value is conservative. */ diff --git a/lib/odp-util.c b/lib/odp-util.c index e7acaad85..c90ff7d24 100644 --- a/lib/odp-util.c +++ b/lib/odp-util.c @@ -19,6 +19,7 @@ #include "odp-util.h" #include #include +#include #include #include #include "byte-order.h" @@ -201,6 +202,7 @@ odp_flow_key_attr_len(uint16_t type) case ODP_KEY_ATTR_ICMP: return sizeof(struct odp_key_icmp); case ODP_KEY_ATTR_ICMPV6: return sizeof(struct odp_key_icmpv6); case ODP_KEY_ATTR_ARP: return sizeof(struct odp_key_arp); + case ODP_KEY_ATTR_ND: return sizeof(struct odp_key_nd); case ODP_KEY_ATTR_UNSPEC: case __ODP_KEY_ATTR_MAX: @@ -242,6 +244,7 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds) const struct odp_key_icmp *icmp_key; const struct odp_key_icmpv6 *icmpv6_key; const struct odp_key_arp *arp_key; + const struct odp_key_nd *nd_key; if (nl_attr_get_size(a) != odp_flow_key_attr_len(nl_attr_type(a))) { ds_put_format(ds, "bad length %zu, expected %d for: ", @@ -339,6 +342,25 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds) ETH_ADDR_ARGS(arp_key->arp_tha)); break; + case ODP_KEY_ATTR_ND: { + char target[INET6_ADDRSTRLEN]; + + nd_key = nl_attr_get(a); + inet_ntop(AF_INET6, nd_key->nd_target, target, sizeof target); + + ds_put_format(ds, "nd(target=%s", target); + if (!eth_addr_is_zero(nd_key->nd_sll)) { + ds_put_format(ds, ",sll="ETH_ADDR_FMT, + ETH_ADDR_ARGS(nd_key->nd_sll)); + } + if (!eth_addr_is_zero(nd_key->nd_tll)) { + ds_put_format(ds, ",tll="ETH_ADDR_FMT, + ETH_ADDR_ARGS(nd_key->nd_tll)); + } + ds_put_char(ds, ')'); + break; + } + default: format_generic_odp_key(a, ds); break; @@ -466,6 +488,18 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow) sizeof *icmpv6_key); icmpv6_key->icmpv6_type = ntohs(flow->tp_src); icmpv6_key->icmpv6_code = ntohs(flow->tp_dst); + + if (icmpv6_key->icmpv6_type == ND_NEIGHBOR_SOLICIT + || icmpv6_key->icmpv6_type == ND_NEIGHBOR_ADVERT) { + struct odp_key_nd *nd_key; + + nd_key = nl_msg_put_unspec_uninit(buf, ODP_KEY_ATTR_ND, + sizeof *nd_key); + memcpy(nd_key->nd_target, &flow->nd_target, + sizeof nd_key->nd_target); + memcpy(nd_key->nd_sll, flow->arp_sha, ETH_ADDR_LEN); + memcpy(nd_key->nd_tll, flow->arp_tha, ETH_ADDR_LEN); + } } } } @@ -494,6 +528,7 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len, const struct odp_key_icmp *icmp_key; const struct odp_key_icmpv6 *icmpv6_key; const struct odp_key_arp *arp_key; + const struct odp_key_nd *nd_key; uint16_t type = nl_attr_type(nla); int len = odp_flow_key_attr_len(type); @@ -623,6 +658,17 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len, memcpy(flow->arp_tha, arp_key->arp_tha, ETH_ADDR_LEN); break; + case TRANSITION(ODP_KEY_ATTR_ICMPV6, ODP_KEY_ATTR_ND): + if (flow->tp_src != htons(ND_NEIGHBOR_SOLICIT) + && flow->tp_src != htons(ND_NEIGHBOR_ADVERT)) { + return EINVAL; + } + nd_key = nl_attr_get(nla); + memcpy(&flow->nd_target, nd_key->nd_target, sizeof flow->nd_target); + memcpy(flow->arp_sha, nd_key->nd_sll, ETH_ADDR_LEN); + memcpy(flow->arp_tha, nd_key->nd_tll, ETH_ADDR_LEN); + break; + default: if (type == ODP_KEY_ATTR_UNSPEC || prev_type == ODP_KEY_ATTR_UNSPEC) { @@ -673,11 +719,18 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len, } return 0; + case ODP_KEY_ATTR_ICMPV6: + if (flow->icmp_type == htons(ND_NEIGHBOR_SOLICIT) + || flow->icmp_type == htons(ND_NEIGHBOR_ADVERT)) { + return EINVAL; + } + return 0; + case ODP_KEY_ATTR_TCP: case ODP_KEY_ATTR_UDP: case ODP_KEY_ATTR_ICMP: - case ODP_KEY_ATTR_ICMPV6: case ODP_KEY_ATTR_ARP: + case ODP_KEY_ATTR_ND: return 0; case __ODP_KEY_ATTR_MAX: diff --git a/lib/odp-util.h b/lib/odp-util.h index 8ec09f3cf..074df87fb 100644 --- a/lib/odp-util.h +++ b/lib/odp-util.h @@ -64,13 +64,13 @@ void format_odp_actions(struct ds *, const struct nlattr *odp_actions, size_t actions_len); /* By my calculations currently the longest valid nlattr-formatted flow key is - * 92 bytes long, so this leaves some safety margin. + * 124 bytes long, so this leaves some safety margin. * * We allocate temporary on-stack buffers for flow keys as arrays of uint32_t * to ensure proper 32-bit alignment for Netlink attributes. (An array of * "struct nlattr" might not, in theory, be sufficiently aligned because it * only contains 16-bit types.) */ -#define ODPUTIL_FLOW_KEY_BYTES 112 +#define ODPUTIL_FLOW_KEY_BYTES 144 #define ODPUTIL_FLOW_KEY_U32S DIV_ROUND_UP(ODPUTIL_FLOW_KEY_BYTES, 4) void odp_flow_key_format(const struct nlattr *, size_t, struct ds *); diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c index e77453e06..3fac4749c 100644 --- a/lib/ofp-parse.c +++ b/lib/ofp-parse.c @@ -540,7 +540,10 @@ parse_protocol(const char *name, const struct protocol **p_out) FIELD(F_ARP_SHA, "arp_sha", FWW_ARP_SHA) \ FIELD(F_ARP_THA, "arp_tha", FWW_ARP_THA) \ FIELD(F_IPV6_SRC, "ipv6_src", 0) \ - FIELD(F_IPV6_DST, "ipv6_dst", 0) + FIELD(F_IPV6_DST, "ipv6_dst", 0) \ + FIELD(F_ND_TARGET, "nd_target", FWW_ND_TARGET) \ + FIELD(F_ND_SLL, "nd_sll", FWW_ARP_SHA) \ + FIELD(F_ND_TLL, "nd_tll", FWW_ARP_THA) enum field_index { #define FIELD(ENUM, NAME, WILDCARD) ENUM, @@ -677,6 +680,21 @@ parse_field_value(struct cls_rule *rule, enum field_index index, cls_rule_set_ipv6_dst_masked(rule, &ipv6, &ipv6_mask); break; + case F_ND_TARGET: + str_to_ipv6(value, &ipv6, NULL); + cls_rule_set_nd_target(rule, ipv6); + break; + + case F_ND_SLL: + str_to_mac(value, mac); + cls_rule_set_arp_sha(rule, mac); + break; + + case F_ND_TLL: + str_to_mac(value, mac); + cls_rule_set_arp_tha(rule, mac); + break; + case N_FIELDS: NOT_REACHED(); } diff --git a/lib/ofp-util.c b/lib/ofp-util.c index 4d89e0ae4..1125b83fc 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -126,7 +126,7 @@ ofputil_cls_rule_from_match(const struct ofp_match *match, wc->wildcards = ofpfw & WC_INVARIANTS; /* Wildcard fields that aren't defined by ofp_match or tun_id. */ - wc->wildcards |= (FWW_ARP_SHA | FWW_ARP_THA); + wc->wildcards |= (FWW_ARP_SHA | FWW_ARP_THA | FWW_ND_TARGET); if (ofpfw & OFPFW_NW_TOS) { wc->wildcards |= FWW_NW_TOS; diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at index a86588b9f..111ed881c 100644 --- a/tests/ovs-ofctl.at +++ b/tests/ovs-ofctl.at @@ -71,6 +71,9 @@ in_port=3 icmp6,ipv6_src=2001:db8:3c4d:1::1,icmp_type=134 actions=drop udp dl_vlan_pcp=7 idle_timeout=5 actions=strip_vlan output:0 tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1 udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,output:1 +icmp6,icmp_type=135,nd_target=FEC0::1234:F045:8FFF:1111:FE4E:0571 actions=drop +icmp6,icmp_type=135,nd_sll=00:0A:E4:25:6B:B0 actions=drop +icmp6,icmp_type=136,nd_target=FEC0::1234:F045:8FFF:1111:FE4E:0571,nd_tll=00:0A:E4:25:6B:B1 actions=drop cookie=0x123456789abcdef hard_timeout=10 priority=60000 actions=controller actions=note:41.42.43,note:00.01.02.03.04.05.06.07,note tun_id=0x1234,cookie=0x5678,actions=flood @@ -92,6 +95,9 @@ NXT_FLOW_MOD: ADD icmp6,in_port=3,ipv6_src=2001:db8:3c4d:1::1,icmp_type=134 acti NXT_FLOW_MOD: ADD udp,dl_vlan_pcp=7 idle:5 actions=strip_vlan,output:0 NXT_FLOW_MOD: ADD tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1 NXT_FLOW_MOD: ADD udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,output:1 +NXT_FLOW_MOD: ADD icmp6,icmp_type=135,nd_target=fec0:0:1234:f045:8fff:1111:fe4e:571 actions=drop +NXT_FLOW_MOD: ADD icmp6,icmp_type=135,nd_sll=00:0a:e4:25:6b:b0 actions=drop +NXT_FLOW_MOD: ADD icmp6,icmp_type=136,nd_target=fec0:0:1234:f045:8fff:1111:fe4e:571,nd_tll=00:0a:e4:25:6b:b1 actions=drop NXT_FLOW_MOD: ADD priority=60000 cookie:0x123456789abcdef hard:10 actions=CONTROLLER:65535 NXT_FLOW_MOD: ADD actions=note:41.42.43.00.00.00,note:00.01.02.03.04.05.06.07.00.00.00.00.00.00,note:00.00.00.00.00.00 NXT_FLOW_MOD: ADD tun_id=0x1234 cookie:0x5678 actions=FLOOD @@ -116,6 +122,9 @@ in_port=3 icmp6,ipv6_src=2001:db8:3c4d:1::1,icmp_type=134 actions=drop udp dl_vlan_pcp=7 idle_timeout=5 actions=strip_vlan output:0 tcp,nw_src=192.168.0.3,tp_dst=80 actions=set_queue:37,output:1 udp,nw_src=192.168.0.3,tp_dst=53 actions=pop_queue,output:1 +icmp6,icmp_type=135,nd_target=FEC0::1234:F045:8FFF:1111:FE4E:0571 actions=drop +icmp6,icmp_type=135,nd_sll=00:0A:E4:25:6B:B0 actions=drop +icmp6,icmp_type=136,nd_target=FEC0::1234:F045:8FFF:1111:FE4E:0571,nd_tll=00:0A:E4:25:6B:B1 actions=drop cookie=0x123456789abcdef hard_timeout=10 priority=60000 actions=controller actions=note:41.42.43,note:00.01.02.03.04.05.06.07,note tun_id=0x1234,cookie=0x5678,actions=flood @@ -137,6 +146,9 @@ NXT_FLOW_MOD: ADD NXM_OF_IN_PORT(0003), NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_SRC(2 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800), NXM_OF_VLAN_TCI_W(f000/f000), NXM_OF_IP_PROTO(11) idle:5 actions=strip_vlan,output:0 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800), NXM_OF_IP_SRC(c0a80003), NXM_OF_IP_PROTO(06), NXM_OF_TCP_DST(0050) actions=set_queue:37,output:1 NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(0800), NXM_OF_IP_SRC(c0a80003), NXM_OF_IP_PROTO(11), NXM_OF_UDP_DST(0035) actions=pop_queue,output:1 +NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(87), NXM_NX_ND_TARGET(fec000001234f0458fff1111fe4e0571) actions=drop +NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(87), NXM_NX_ND_SLL(000ae4256bb0) actions=drop +NXT_FLOW_MOD: ADD NXM_OF_ETH_TYPE(86dd), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(88), NXM_NX_ND_TARGET(fec000001234f0458fff1111fe4e0571), NXM_NX_ND_TLL(000ae4256bb1) actions=drop NXT_FLOW_MOD: ADD cookie:0x123456789abcdef hard:10 pri:60000 actions=CONTROLLER:65535 NXT_FLOW_MOD: ADD actions=note:41.42.43.00.00.00,note:00.01.02.03.04.05.06.07.00.00.00.00.00.00,note:00.00.00.00.00.00 NXT_FLOW_MOD: ADD NXM_NX_TUN_ID(0000000000001234) cookie:0x5678 actions=FLOOD @@ -267,6 +279,18 @@ NXM_OF_ETH_TYPE(0800) NXM_NX_IPV6_DST(20010db83c4d00010002000300040005) NXM_OF_ETH_TYPE(86dd) NXM_NX_IPV6_DST_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000) NXM_OF_ETH_TYPE(0800) NXM_NX_IPV6_DST_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000) +# ND source hardware address +NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(87) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_SLL(0002e30f80a4) +NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(88) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_SLL(0002e30f80a4) +NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3b) NXM_NX_ICMPV6_TYPE(87) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_SLL(0002e30f80a4) +NXM_OF_ETH_TYPE(0800) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(87) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_SLL(0002e30f80a4) + +# ND destination hardware address +NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(88) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_TLL(0002e30f80a4) +NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(87) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_TLL(0002e30f80a4) +NXM_OF_ETH_TYPE(86dd) NXM_OF_IP_PROTO(3b) NXM_NX_ICMPV6_TYPE(87) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_TLL(0002e30f80a4) +NXM_OF_ETH_TYPE(0800) NXM_OF_IP_PROTO(3a) NXM_NX_ICMPV6_TYPE(88) NXM_NX_ND_TARGET(20010db83c4d00010002000300040005) NXM_NX_ND_TLL(0002e30f80a4) + # Tunnel ID. NXM_NX_TUN_ID(00000000abcdef01) NXM_NX_TUN_ID_W(84200000abcdef01/84200000FFFFFFFF) @@ -407,6 +431,18 @@ nx_pull_match() returned error 44010104 NXM_OF_ETH_TYPE(86dd), NXM_NX_IPV6_DST_W(20010db83c4d00010000000000000000/ffffffffffffffff0000000000000000) nx_pull_match() returned error 44010104 +# ND source hardware address +NXM_OF_ETH_TYPE(86dd), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(87), NXM_NX_ND_TARGET(20010db83c4d00010002000300040005), NXM_NX_ND_SLL(0002e30f80a4) +nx_pull_match() returned error 44010104 +nx_pull_match() returned error 44010104 +nx_pull_match() returned error 44010104 + +# ND destination hardware address +NXM_OF_ETH_TYPE(86dd), NXM_OF_IP_PROTO(3a), NXM_NX_ICMPV6_TYPE(88), NXM_NX_ND_TARGET(20010db83c4d00010002000300040005), NXM_NX_ND_TLL(0002e30f80a4) +nx_pull_match() returned error 44010104 +nx_pull_match() returned error 44010104 +nx_pull_match() returned error 44010104 + # Tunnel ID. NXM_NX_TUN_ID(00000000abcdef01) NXM_NX_TUN_ID_W(84200000abcdef01/84200000ffffffff) diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index 37425529e..95b088429 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -388,6 +388,24 @@ groups of 16-bits of zeros. The optional \fInetmask\fR allows restricting a match to an IPv6 address prefix. A netmask is specified as a CIDR block (e.g. \fB2001:db8:3c4d:1::/64\fR). . +.IP \fBnd_target=\fIipv6\fR +When \fBdl_type\fR, \fBnw_proto\fR, and \fBicmp_type\fR specify +IPv6 Neighbor Discovery (ICMPv6 type 135 or 136), matches the target address +\fIipv6\fR. \fIipv6\fR is in the same format described earlier for the +\fBipv6_src\fR and \fBipv6_dst\fR fields. +. +.IP \fBnd_sll=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR +When \fBdl_type\fR, \fBnw_proto\fR, and \fBicmp_type\fR specify IPv6 +Neighbor Solicitation (ICMPv6 type 135), matches the source link\-layer +address option. An address is specified as 6 pairs of hexadecimal +digits delimited by colons. +. +.IP \fBnd_tll=\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR +When \fBdl_type\fR, \fBnw_proto\fR, and \fBicmp_type\fR specify IPv6 +Neighbor Advertisement (ICMPv6 type 136), matches the target link\-layer +address option. An address is specified as 6 pairs of hexadecimal +digits delimited by colons. +. .IP \fBtun_id=\fItunnel-id\fR[\fB/\fImask\fR] Matches tunnel identifier \fItunnel-id\fR. Only packets that arrive over a tunnel that carries a key (e.g. GRE with the RFC 2890 key