From 685a51a5b89750cead1b2934c2079d2bb9c52a4a Mon Sep 17 00:00:00 2001 From: Justin Pettit Date: Tue, 1 Feb 2011 22:54:11 -0800 Subject: [PATCH] nicira-ext: Support matching IPv6 Neighbor Discovery messages. IPv6 uses Neighbor Discovery messages in a similar manner to how IPv4 uses ARP. This commit adds support for matching deeper into the payloads of Neighbor Solicitation (NS) and Neighbor Advertisement (NA) messages. Currently, the matching fields include: - NS and NA Target (nd_target) - NS Source Link Layer Address (nd_sll) - NA Target Link Layer Address (nd_tll) When defining IPv6 Neighbor Discovery rules, the Nicira Extensible Match (NXM) extension to OVS must be used. Signed-off-by: Justin Pettit Acked-by: Ben Pfaff --- DESIGN | 3 +- datapath/flow.c | 116 ++++++++++++++++++++++-- datapath/flow.h | 5 +- include/openflow/nicira-ext.h | 40 ++++++++ include/openvswitch/datapath-protocol.h | 7 ++ lib/classifier.c | 33 ++++++- lib/classifier.h | 1 + lib/flow.c | 94 ++++++++++++++++++- lib/flow.h | 14 +-- lib/nx-match.c | 42 +++++++++ lib/nx-match.def | 3 + lib/nx-match.h | 8 +- lib/odp-util.c | 55 ++++++++++- lib/odp-util.h | 4 +- lib/ofp-parse.c | 20 +++- lib/ofp-util.c | 2 +- tests/ovs-ofctl.at | 36 ++++++++ utilities/ovs-ofctl.8.in | 18 ++++ 18 files changed, 468 insertions(+), 33 deletions(-) diff --git a/DESIGN b/DESIGN index 56e26053..6e25f01b 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 4365e22c..9823b9fe 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 ee1c4c92..21df5fbe 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 f6786263..88efdf65 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 13b7d9d1..8645096a 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 658be808..fcc965c8 100644 --- a/lib/classifier.c +++ b/lib/classifier.c @@ -369,6 +369,13 @@ cls_rule_set_ipv6_dst_masked(struct cls_rule *rule, const struct in6_addr *dst, } } +void +cls_rule_set_nd_target(struct cls_rule *rule, const struct in6_addr target) +{ + rule->wc.wildcards &= ~FWW_ND_TARGET; + rule->flow.nd_target = target; +} + /* Returns true if 'a' and 'b' have the same priority, wildcard the same * fields, and have the same values for fixed fields, otherwise false. */ bool @@ -586,7 +593,20 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s) if (!(w & FWW_TP_DST)) { ds_put_format(s, "icmp_code=%"PRIu16",", ntohs(f->tp_dst)); } - } else { + if (!(w & FWW_ND_TARGET)) { + ds_put_cstr(s, "nd_target="); + print_ipv6_addr(s, &f->nd_target); + ds_put_char(s, ','); + } + if (!(w & FWW_ARP_SHA)) { + ds_put_format(s, "nd_sll="ETH_ADDR_FMT",", + ETH_ADDR_ARGS(f->arp_sha)); + } + if (!(w & FWW_ARP_THA)) { + ds_put_format(s, "nd_tll="ETH_ADDR_FMT",", + ETH_ADDR_ARGS(f->arp_tha)); + } + } else { if (!(w & FWW_TP_SRC)) { ds_put_format(s, "tp_src=%"PRIu16",", ntohs(f->tp_src)); } @@ -1080,7 +1100,7 @@ flow_equal_except(const struct flow *a, const struct flow *b, const flow_wildcards_t wc = wildcards->wildcards; int i; - BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 84 + FLOW_N_REGS * 4); + BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 100 + FLOW_N_REGS * 4); for (i = 0; i < FLOW_N_REGS; i++) { if ((a->regs[i] ^ b->regs[i]) & wildcards->reg_masks[i]) { @@ -1113,7 +1133,9 @@ flow_equal_except(const struct flow *a, const struct flow *b, && ipv6_equal_except(&a->ipv6_src, &b->ipv6_src, &wildcards->ipv6_src_mask) && ipv6_equal_except(&a->ipv6_dst, &b->ipv6_dst, - &wildcards->ipv6_dst_mask)); + &wildcards->ipv6_dst_mask) + && (wc & FWW_ND_TARGET + || ipv6_addr_equals(&a->nd_target, &b->nd_target))); } static void @@ -1122,7 +1144,7 @@ zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards) const flow_wildcards_t wc = wildcards->wildcards; int i; - BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 84 + 4 * FLOW_N_REGS); + BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 100 + 4 * FLOW_N_REGS); for (i = 0; i < FLOW_N_REGS; i++) { flow->regs[i] &= wildcards->reg_masks[i]; @@ -1169,4 +1191,7 @@ zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards) &wildcards->ipv6_src_mask); flow->ipv6_dst = ipv6_addr_bitand(&flow->ipv6_dst, &wildcards->ipv6_dst_mask); + if (wc & FWW_ND_TARGET) { + memset(&flow->nd_target, 0, sizeof flow->nd_target); + } } diff --git a/lib/classifier.h b/lib/classifier.h index c82a4844..d3121bfb 100644 --- a/lib/classifier.h +++ b/lib/classifier.h @@ -109,6 +109,7 @@ bool cls_rule_set_ipv6_src_masked(struct cls_rule *, const struct in6_addr *, void cls_rule_set_ipv6_dst(struct cls_rule *, const struct in6_addr *); bool cls_rule_set_ipv6_dst_masked(struct cls_rule *, const struct in6_addr *, const struct in6_addr *); +void cls_rule_set_nd_target(struct cls_rule *, const struct in6_addr); bool cls_rule_equal(const struct cls_rule *, const struct cls_rule *); diff --git a/lib/flow.c b/lib/flow.c index 41f13b88..879e462f 100644 --- a/lib/flow.c +++ b/lib/flow.c @@ -230,6 +230,94 @@ parse_ipv6(struct ofpbuf *packet, struct flow *flow) return nh_len; } +/* Neighbor Discovery Solicitation and Advertisement messages are + * identical in structure, so we'll just use one of them. To be safe, + * we'll assert that they're still identical. */ +BUILD_ASSERT_DECL(sizeof(struct nd_neighbor_solicit) + == sizeof(struct nd_neighbor_advert)); + +static bool +parse_icmpv6(struct ofpbuf *b, struct flow *flow, int icmp_len) +{ + const struct icmp6_hdr *icmp = pull_icmpv6(b); + + if (!icmp) { + return false; + } + + /* The ICMPv6 type and code fields use the 16-bit transport port + * fields, so we need to store them in 16-bit network byte order. */ + flow->icmp_type = htons(icmp->icmp6_type); + flow->icmp_code = htons(icmp->icmp6_code); + + if (!icmp->icmp6_code + && ((icmp->icmp6_type == ND_NEIGHBOR_SOLICIT) + || (icmp->icmp6_type == ND_NEIGHBOR_ADVERT))) { + struct nd_neighbor_solicit *nd_ns; /* Identical to ND advert */ + + /* In order to process neighbor discovery options, we need the + * entire packet. */ + if ((icmp_len < sizeof *nd_ns) + || (!ofpbuf_try_pull(b, sizeof *nd_ns - sizeof *icmp))) { + return false; + } + nd_ns = (struct nd_neighbor_solicit *)icmp; + flow->nd_target = nd_ns->nd_ns_target; + + icmp_len -= sizeof(*nd_ns); + while (icmp_len >= 8) { + struct nd_opt_hdr *nd_opt; + int opt_len; + const uint8_t *data; + + /* The minimum size of an option is 8 bytes, which also is + * the size of Ethernet link-layer options. */ + nd_opt = ofpbuf_pull(b, 8); + if (!nd_opt->nd_opt_len || nd_opt->nd_opt_len * 8 > icmp_len) { + goto invalid; + } + opt_len = nd_opt->nd_opt_len * 8; + data = (const uint8_t *)(nd_opt + 1); + + /* Store the link layer address if the appropriate option is + * provided. It is considered an error if the same link + * layer option is specified twice. */ + if (nd_opt->nd_opt_type == ND_OPT_SOURCE_LINKADDR + && opt_len == 8) { + if (eth_addr_is_zero(flow->arp_sha)) { + memcpy(flow->arp_sha, data, ETH_ADDR_LEN); + } else { + goto invalid; + } + } else if (nd_opt->nd_opt_type == ND_OPT_TARGET_LINKADDR + && opt_len == 8) { + if (eth_addr_is_zero(flow->arp_tha)) { + memcpy(flow->arp_tha, data, ETH_ADDR_LEN); + } else { + goto invalid; + } + } + + /* Pull the rest of this option. */ + if (!ofpbuf_try_pull(b, opt_len - 8)) { + goto invalid; + } + + icmp_len -= opt_len; + } + } + + return true; + +invalid: + memset(&flow->nd_target, '\0', sizeof(flow->nd_target)); + memset(flow->arp_sha, '\0', sizeof(flow->arp_sha)); + memset(flow->arp_tha, '\0', sizeof(flow->arp_tha)); + + return false; + +} + /* Initializes 'flow' members from 'packet', 'tun_id', and 'in_port. * Initializes 'packet' header pointers as follows: * @@ -344,10 +432,8 @@ flow_extract(struct ofpbuf *packet, ovs_be64 tun_id, uint16_t in_port, packet->l7 = b.data; } } else if (flow->nw_proto == IPPROTO_ICMPV6) { - const struct icmp6_hdr *icmp = pull_icmpv6(&b); - if (icmp) { - flow->icmp_type = htons(icmp->icmp6_type); - flow->icmp_code = htons(icmp->icmp6_code); + int icmp_len = ntohs(nh->ip6_plen) + sizeof *nh - nh_len; + if (parse_icmpv6(&b, flow, icmp_len)) { packet->l7 = b.data; } } diff --git a/lib/flow.h b/lib/flow.h index d331aa36..60229f58 100644 --- a/lib/flow.h +++ b/lib/flow.h @@ -54,19 +54,20 @@ struct flow { uint8_t dl_dst[6]; /* Ethernet destination address. */ uint8_t nw_proto; /* IP protocol or low 8 bits of ARP opcode. */ uint8_t nw_tos; /* IP ToS (DSCP field, 6 bits). */ - uint8_t arp_sha[6]; /* ARP source hardware address. */ - uint8_t arp_tha[6]; /* ARP target hardware address. */ + uint8_t arp_sha[6]; /* ARP/ND source hardware address. */ + uint8_t arp_tha[6]; /* ARP/ND target hardware address. */ struct in6_addr ipv6_src; /* IPv6 source address. */ struct in6_addr ipv6_dst; /* IPv6 destination address. */ + struct in6_addr nd_target; /* IPv6 neighbor discovery (ND) target. */ uint32_t reserved; /* Reserved for 64-bit packing. */ }; /* Assert that there are FLOW_SIG_SIZE bytes of significant data in "struct * flow", followed by FLOW_PAD_SIZE bytes of padding. */ -#define FLOW_SIG_SIZE (84 + FLOW_N_REGS * 4) +#define FLOW_SIG_SIZE (100 + FLOW_N_REGS * 4) #define FLOW_PAD_SIZE 4 -BUILD_ASSERT_DECL(offsetof(struct flow, ipv6_dst) == FLOW_SIG_SIZE - 16); -BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->ipv6_dst) == 16); +BUILD_ASSERT_DECL(offsetof(struct flow, nd_target) == FLOW_SIG_SIZE - 16); +BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->nd_target) == 16); BUILD_ASSERT_DECL(sizeof(struct flow) == FLOW_SIG_SIZE + FLOW_PAD_SIZE); int flow_extract(struct ofpbuf *, uint64_t tun_id, uint16_t in_port, @@ -122,7 +123,8 @@ typedef unsigned int OVS_BITWISE flow_wildcards_t; /* multicast bit only */ #define FWW_ARP_SHA ((OVS_FORCE flow_wildcards_t) (1 << 9)) #define FWW_ARP_THA ((OVS_FORCE flow_wildcards_t) (1 << 10)) -#define FWW_ALL ((OVS_FORCE flow_wildcards_t) (((1 << 11)) - 1)) +#define FWW_ND_TARGET ((OVS_FORCE flow_wildcards_t) (1 << 11)) +#define FWW_ALL ((OVS_FORCE flow_wildcards_t) (((1 << 12)) - 1)) /* Information on wildcards for a flow, as a supplement to "struct flow". * diff --git a/lib/nx-match.c b/lib/nx-match.c index 5fc6aa24..abc3b210 100644 --- a/lib/nx-match.c +++ b/lib/nx-match.c @@ -18,6 +18,8 @@ #include "nx-match.h" +#include + #include "classifier.h" #include "dynamic-string.h" #include "ofp-util.h" @@ -363,6 +365,30 @@ parse_nxm_entry(struct cls_rule *rule, const struct nxm_field *f, flow->tp_dst = htons(*(uint8_t *) value); return 0; + /* IPv6 Neighbor Discovery. */ + case NFI_NXM_NX_ND_TARGET: + /* We've already verified that it's an ICMPv6 message. */ + if ((flow->tp_src != htons(ND_NEIGHBOR_SOLICIT)) + && (flow->tp_src != htons(ND_NEIGHBOR_ADVERT))) { + return NXM_BAD_PREREQ; + } + memcpy(&flow->nd_target, value, sizeof flow->nd_target); + return 0; + case NFI_NXM_NX_ND_SLL: + /* We've already verified that it's an ICMPv6 message. */ + if (flow->tp_src != htons(ND_NEIGHBOR_SOLICIT)) { + return NXM_BAD_PREREQ; + } + memcpy(flow->arp_sha, value, ETH_ADDR_LEN); + return 0; + case NFI_NXM_NX_ND_TLL: + /* We've already verified that it's an ICMPv6 message. */ + if (flow->tp_src != htons(ND_NEIGHBOR_ADVERT)) { + return NXM_BAD_PREREQ; + } + memcpy(flow->arp_tha, value, ETH_ADDR_LEN); + return 0; + /* ARP header. */ case NFI_NXM_OF_ARP_OP: if (ntohs(get_unaligned_be16(value)) > 255) { @@ -815,6 +841,16 @@ nx_put_match(struct ofpbuf *b, const struct cls_rule *cr) if (!(wc & FWW_TP_DST)) { nxm_put_8(b, NXM_NX_ICMPV6_CODE, ntohs(flow->tp_dst)); } + if (!(wc & FWW_ND_TARGET)) { + nxm_put_ipv6(b, NXM_NX_ND_TARGET, &flow->nd_target, + &in6addr_exact); + } + if (!(wc & FWW_ARP_SHA)) { + nxm_put_eth(b, NXM_NX_ND_SLL, flow->arp_sha); + } + if (!(wc & FWW_ARP_THA)) { + nxm_put_eth(b, NXM_NX_ND_TLL, flow->arp_tha); + } break; } } @@ -1303,9 +1339,11 @@ nxm_read_field(const struct nxm_field *src, const struct flow *flow) #endif case NFI_NXM_NX_ARP_SHA: + case NFI_NXM_NX_ND_SLL: return eth_addr_to_uint64(flow->arp_sha); case NFI_NXM_NX_ARP_THA: + case NFI_NXM_NX_ND_TLL: return eth_addr_to_uint64(flow->arp_tha); case NFI_NXM_NX_TUN_ID_W: @@ -1319,6 +1357,7 @@ nxm_read_field(const struct nxm_field *src, const struct flow *flow) case NFI_NXM_NX_IPV6_SRC_W: case NFI_NXM_NX_IPV6_DST: case NFI_NXM_NX_IPV6_DST_W: + case NFI_NXM_NX_ND_TARGET: case N_NXM_FIELDS: NOT_REACHED(); } @@ -1392,6 +1431,9 @@ nxm_write_field(const struct nxm_field *dst, struct flow *flow, case NFI_NXM_NX_IPV6_DST_W: case NFI_NXM_NX_ICMPV6_TYPE: case NFI_NXM_NX_ICMPV6_CODE: + case NFI_NXM_NX_ND_TARGET: + case NFI_NXM_NX_ND_SLL: + case NFI_NXM_NX_ND_TLL: case N_NXM_FIELDS: NOT_REACHED(); } diff --git a/lib/nx-match.def b/lib/nx-match.def index 41e76d65..4a42aaa4 100644 --- a/lib/nx-match.def +++ b/lib/nx-match.def @@ -53,6 +53,9 @@ DEFINE_FIELD_M(NX_IPV6_SRC, 0, NXM_DL_IPV6, 0, false) DEFINE_FIELD_M(NX_IPV6_DST, 0, NXM_DL_IPV6, 0, false) DEFINE_FIELD (NX_ICMPV6_TYPE, FWW_TP_SRC, NXM_DL_IPV6, IPPROTO_ICMPV6, false) DEFINE_FIELD (NX_ICMPV6_CODE, FWW_TP_DST, NXM_DL_IPV6, IPPROTO_ICMPV6, false) +DEFINE_FIELD (NX_ND_TARGET, FWW_ND_TARGET,NXM_DL_IPV6, IPPROTO_ICMPV6, false) +DEFINE_FIELD (NX_ND_SLL, FWW_ARP_SHA, NXM_DL_IPV6, IPPROTO_ICMPV6, false) +DEFINE_FIELD (NX_ND_TLL, FWW_ARP_THA, NXM_DL_IPV6, IPPROTO_ICMPV6, false) DEFINE_FIELD_M(NX_REG0, 0, NXM_DL_NONE, 0, true) #if FLOW_N_REGS >= 2 diff --git a/lib/nx-match.h b/lib/nx-match.h index aefcb653..a76ad1f2 100644 --- a/lib/nx-match.h +++ b/lib/nx-match.h @@ -95,15 +95,17 @@ nxm_decode_n_bits(ovs_be16 ofs_nbits) * NXM_OF_IP_PROTO 4 2 -- 6 * NXM_OF_IPV6_SRC_W 4 16 16 36 * NXM_OF_IPV6_DST_W 4 16 16 36 - * NXM_OF_TCP_SRC 4 2 -- 6 - * NXM_OF_TCP_DST 4 2 -- 6 + * NXM_OF_ICMP_TYPE 4 1 -- 5 + * NXM_OF_ICMP_CODE 4 1 -- 5 + * NXM_NX_ND_TARGET 4 16 -- 20 + * NXM_NX_ND_SLL 4 6 -- 10 * NXM_NX_REG_W(0) 4 4 4 12 * NXM_NX_REG_W(1) 4 4 4 12 * NXM_NX_REG_W(2) 4 4 4 12 * NXM_NX_REG_W(3) 4 4 4 12 * NXM_NX_TUN_ID_W 4 8 8 20 * ------------------------------------------- - * total 209 + * total 237 * * So this value is conservative. */ diff --git a/lib/odp-util.c b/lib/odp-util.c index e7acaad8..c90ff7d2 100644 --- a/lib/odp-util.c +++ b/lib/odp-util.c @@ -19,6 +19,7 @@ #include "odp-util.h" #include #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 8ec09f3c..074df87f 100644 --- a/lib/odp-util.h +++ b/lib/odp-util.h @@ -64,13 +64,13 @@ void format_odp_actions(struct ds *, const struct nlattr *odp_actions, size_t actions_len); /* By my calculations currently the longest valid nlattr-formatted flow key is - * 92 bytes long, so this leaves some safety margin. + * 124 bytes long, so this leaves some safety margin. * * We allocate temporary on-stack buffers for flow keys as arrays of uint32_t * to ensure proper 32-bit alignment for Netlink attributes. (An array of * "struct nlattr" might not, in theory, be sufficiently aligned because it * only contains 16-bit types.) */ -#define ODPUTIL_FLOW_KEY_BYTES 112 +#define ODPUTIL_FLOW_KEY_BYTES 144 #define ODPUTIL_FLOW_KEY_U32S DIV_ROUND_UP(ODPUTIL_FLOW_KEY_BYTES, 4) void odp_flow_key_format(const struct nlattr *, size_t, struct ds *); diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c index e77453e0..3fac4749 100644 --- a/lib/ofp-parse.c +++ b/lib/ofp-parse.c @@ -540,7 +540,10 @@ parse_protocol(const char *name, const struct protocol **p_out) FIELD(F_ARP_SHA, "arp_sha", FWW_ARP_SHA) \ FIELD(F_ARP_THA, "arp_tha", FWW_ARP_THA) \ FIELD(F_IPV6_SRC, "ipv6_src", 0) \ - FIELD(F_IPV6_DST, "ipv6_dst", 0) + FIELD(F_IPV6_DST, "ipv6_dst", 0) \ + FIELD(F_ND_TARGET, "nd_target", FWW_ND_TARGET) \ + FIELD(F_ND_SLL, "nd_sll", FWW_ARP_SHA) \ + FIELD(F_ND_TLL, "nd_tll", FWW_ARP_THA) enum field_index { #define FIELD(ENUM, NAME, WILDCARD) ENUM, @@ -677,6 +680,21 @@ parse_field_value(struct cls_rule *rule, enum field_index index, cls_rule_set_ipv6_dst_masked(rule, &ipv6, &ipv6_mask); break; + case F_ND_TARGET: + str_to_ipv6(value, &ipv6, NULL); + cls_rule_set_nd_target(rule, ipv6); + break; + + case F_ND_SLL: + str_to_mac(value, mac); + cls_rule_set_arp_sha(rule, mac); + break; + + case F_ND_TLL: + str_to_mac(value, mac); + cls_rule_set_arp_tha(rule, mac); + break; + case N_FIELDS: NOT_REACHED(); } diff --git a/lib/ofp-util.c b/lib/ofp-util.c index 4d89e0ae..1125b83f 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -126,7 +126,7 @@ ofputil_cls_rule_from_match(const struct ofp_match *match, wc->wildcards = ofpfw & WC_INVARIANTS; /* Wildcard fields that aren't defined by ofp_match or tun_id. */ - wc->wildcards |= (FWW_ARP_SHA | FWW_ARP_THA); + wc->wildcards |= (FWW_ARP_SHA | FWW_ARP_THA | FWW_ND_TARGET); if (ofpfw & OFPFW_NW_TOS) { wc->wildcards |= FWW_NW_TOS; diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at index a86588b9..111ed881 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 37425529..95b08842 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 -- 2.30.2