+ const struct nlattr *nla;
+ enum odp_key_type prev_type;
+ size_t left;
+
+ memset(flow, 0, sizeof *flow);
+ flow->dl_type = htons(FLOW_DL_TYPE_NONE);
+
+ prev_type = ODP_KEY_ATTR_UNSPEC;
+ NL_ATTR_FOR_EACH (nla, left, key, 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;
+ const struct odp_key_nd *nd_key;
+
+ uint16_t type = nl_attr_type(nla);
+ int len = odp_flow_key_attr_len(type);
+
+ if (nl_attr_get_size(nla) != len && len != -1) {
+ return EINVAL;
+ }
+
+#define TRANSITION(PREV_TYPE, TYPE) (((PREV_TYPE) << 16) | (TYPE))
+ switch (TRANSITION(prev_type, type)) {
+ case TRANSITION(ODP_KEY_ATTR_UNSPEC, ODP_KEY_ATTR_TUN_ID):
+ flow->tun_id = nl_attr_get_be64(nla);
+ break;
+
+ case TRANSITION(ODP_KEY_ATTR_UNSPEC, ODP_KEY_ATTR_IN_PORT):
+ case TRANSITION(ODP_KEY_ATTR_TUN_ID, ODP_KEY_ATTR_IN_PORT):
+ if (nl_attr_get_u32(nla) >= UINT16_MAX) {
+ return EINVAL;
+ }
+ flow->in_port = nl_attr_get_u32(nla);
+ break;
+
+ case TRANSITION(ODP_KEY_ATTR_IN_PORT, ODP_KEY_ATTR_ETHERNET):
+ eth_key = nl_attr_get(nla);
+ memcpy(flow->dl_src, eth_key->eth_src, ETH_ADDR_LEN);
+ memcpy(flow->dl_dst, eth_key->eth_dst, ETH_ADDR_LEN);
+ break;
+
+ case TRANSITION(ODP_KEY_ATTR_ETHERNET, ODP_KEY_ATTR_8021Q):
+ q_key = nl_attr_get(nla);
+ if (q_key->q_tpid != htons(ETH_TYPE_VLAN)) {
+ /* Only standard 0x8100 VLANs currently supported. */
+ return EINVAL;
+ }
+ if (q_key->q_tci & htons(VLAN_CFI)) {
+ return EINVAL;
+ }
+ flow->vlan_tci = q_key->q_tci | htons(VLAN_CFI);
+ break;
+
+ case TRANSITION(ODP_KEY_ATTR_8021Q, ODP_KEY_ATTR_ETHERTYPE):
+ case TRANSITION(ODP_KEY_ATTR_ETHERNET, ODP_KEY_ATTR_ETHERTYPE):
+ flow->dl_type = nl_attr_get_be16(nla);
+ if (ntohs(flow->dl_type) < 1536) {
+ return EINVAL;
+ }
+ break;
+
+ case TRANSITION(ODP_KEY_ATTR_ETHERTYPE, ODP_KEY_ATTR_IPV4):
+ if (flow->dl_type != htons(ETH_TYPE_IP)) {
+ return EINVAL;
+ }
+ ipv4_key = nl_attr_get(nla);
+ flow->nw_src = ipv4_key->ipv4_src;
+ flow->nw_dst = ipv4_key->ipv4_dst;
+ flow->nw_proto = ipv4_key->ipv4_proto;
+ flow->nw_tos = ipv4_key->ipv4_tos;
+ if (flow->nw_tos & IP_ECN_MASK) {
+ return EINVAL;
+ }
+ 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;
+ }
+ tcp_key = nl_attr_get(nla);
+ flow->tp_src = tcp_key->tcp_src;
+ flow->tp_dst = tcp_key->tcp_dst;
+ 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;
+ }
+ udp_key = nl_attr_get(nla);
+ flow->tp_src = udp_key->udp_src;
+ flow->tp_dst = udp_key->udp_dst;
+ break;
+
+ case TRANSITION(ODP_KEY_ATTR_IPV4, ODP_KEY_ATTR_ICMP):
+ if (flow->nw_proto != IPPROTO_ICMP) {
+ return EINVAL;
+ }
+ icmp_key = nl_attr_get(nla);
+ flow->tp_src = htons(icmp_key->icmp_type);
+ 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;
+ }
+ arp_key = nl_attr_get(nla);
+ flow->nw_src = arp_key->arp_sip;
+ flow->nw_dst = arp_key->arp_tip;
+ if (arp_key->arp_op & htons(0xff00)) {
+ return EINVAL;
+ }
+ flow->nw_proto = ntohs(arp_key->arp_op);
+ memcpy(flow->arp_sha, arp_key->arp_sha, ETH_ADDR_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) {
+ return EINVAL;
+ }
+ return EINVAL;
+ }
+
+ prev_type = type;
+ }
+ if (left) {
+ return EINVAL;
+ }
+
+ switch (prev_type) {
+ case ODP_KEY_ATTR_UNSPEC:
+ return EINVAL;
+
+ case ODP_KEY_ATTR_TUN_ID:
+ case ODP_KEY_ATTR_IN_PORT:
+ return EINVAL;
+
+ case ODP_KEY_ATTR_ETHERNET:
+ case ODP_KEY_ATTR_8021Q:
+ return 0;
+
+ case ODP_KEY_ATTR_ETHERTYPE:
+ if (flow->dl_type == htons(ETH_TYPE_IP)
+ || flow->dl_type == htons(ETH_TYPE_IPV6)
+ || flow->dl_type == htons(ETH_TYPE_ARP)) {
+ return EINVAL;
+ }
+ return 0;
+
+ case ODP_KEY_ATTR_IPV4:
+ if (flow->nw_proto == IPPROTO_TCP
+ || flow->nw_proto == IPPROTO_UDP
+ || flow->nw_proto == IPPROTO_ICMP) {
+ return EINVAL;
+ }
+ 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_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_ARP:
+ case ODP_KEY_ATTR_ND:
+ return 0;
+
+ case __ODP_KEY_ATTR_MAX:
+ default:
+ NOT_REACHED();
+ }