+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;
+}
+