+ return check_expectations(present_attrs, out_of_range_attr, expected_attrs,
+ key, key_len);
+}
+
+/* Parse 802.1Q header then encapsulated L3 attributes. */
+static enum odp_key_fitness
+parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
+ uint64_t present_attrs, int out_of_range_attr,
+ uint64_t expected_attrs, struct flow *flow,
+ const struct nlattr *key, size_t key_len)
+{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+ const struct nlattr *encap
+ = (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ENCAP)
+ ? attrs[OVS_KEY_ATTR_ENCAP] : NULL);
+ enum odp_key_fitness encap_fitness;
+ enum odp_key_fitness fitness;
+ ovs_be16 tci;
+
+ /* Calulate fitness of outer attributes. */
+ expected_attrs |= ((UINT64_C(1) << OVS_KEY_ATTR_VLAN) |
+ (UINT64_C(1) << OVS_KEY_ATTR_ENCAP));
+ fitness = check_expectations(present_attrs, out_of_range_attr,
+ expected_attrs, key, key_len);
+
+ /* Get the VLAN TCI value. */
+ if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_VLAN))) {
+ return ODP_FIT_TOO_LITTLE;
+ }
+ tci = nl_attr_get_be16(attrs[OVS_KEY_ATTR_VLAN]);
+ if (tci == htons(0)) {
+ /* Corner case for a truncated 802.1Q header. */
+ if (fitness == ODP_FIT_PERFECT && nl_attr_get_size(encap)) {
+ return ODP_FIT_TOO_MUCH;
+ }
+ return fitness;
+ } else if (!(tci & htons(VLAN_CFI))) {
+ VLOG_ERR_RL(&rl, "OVS_KEY_ATTR_VLAN 0x%04"PRIx16" is nonzero "
+ "but CFI bit is not set", ntohs(tci));
+ return ODP_FIT_ERROR;
+ }
+
+ /* Set vlan_tci.
+ * Remove the TPID from dl_type since it's not the real Ethertype. */
+ flow->vlan_tci = tci;
+ flow->dl_type = htons(0);
+
+ /* Now parse the encapsulated attributes. */
+ if (!parse_flow_nlattrs(nl_attr_get(encap), nl_attr_get_size(encap),
+ attrs, &present_attrs, &out_of_range_attr)) {
+ return ODP_FIT_ERROR;
+ }
+ expected_attrs = 0;
+
+ if (!parse_ethertype(attrs, present_attrs, &expected_attrs, flow)) {
+ return ODP_FIT_ERROR;
+ }
+ encap_fitness = parse_l3_onward(attrs, present_attrs, out_of_range_attr,
+ expected_attrs, flow, key, key_len);
+
+ /* The overall fitness is the worse of the outer and inner attributes. */
+ return MAX(fitness, encap_fitness);
+}
+
+/* Converts the 'key_len' bytes of OVS_KEY_ATTR_* attributes in 'key' to a flow
+ * structure in 'flow'. Returns an ODP_FIT_* value that indicates how well
+ * 'key' fits our expectations for what a flow key should contain.
+ *
+ * This function doesn't take the packet itself as an argument because none of
+ * the currently understood OVS_KEY_ATTR_* attributes require it. Currently,
+ * it is always possible to infer which additional attribute(s) should appear
+ * by looking at the attributes for lower-level protocols, e.g. if the network
+ * protocol in OVS_KEY_ATTR_IPV4 or OVS_KEY_ATTR_IPV6 is IPPROTO_TCP then we
+ * know that a OVS_KEY_ATTR_TCP attribute must appear and that otherwise it
+ * must be absent. */
+enum odp_key_fitness
+odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
+ struct flow *flow)
+{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1];
+ uint64_t expected_attrs;
+ uint64_t present_attrs;
+ int out_of_range_attr;
+
+ memset(flow, 0, sizeof *flow);
+
+ /* Parse attributes. */
+ if (!parse_flow_nlattrs(key, key_len, attrs, &present_attrs,
+ &out_of_range_attr)) {
+ return ODP_FIT_ERROR;
+ }
+ expected_attrs = 0;
+
+ /* Metadata. */
+ if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_PRIORITY)) {
+ flow->skb_priority = nl_attr_get_u32(attrs[OVS_KEY_ATTR_PRIORITY]);
+ expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_PRIORITY;
+ }
+
+ if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_TUN_ID)) {
+ flow->tun_id = nl_attr_get_be64(attrs[OVS_KEY_ATTR_TUN_ID]);
+ expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_TUN_ID;
+ }
+
+ if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IN_PORT)) {
+ uint32_t in_port = nl_attr_get_u32(attrs[OVS_KEY_ATTR_IN_PORT]);
+ if (in_port >= UINT16_MAX || in_port >= OFPP_MAX) {
+ VLOG_ERR_RL(&rl, "in_port %"PRIu32" out of supported range",
+ in_port);
+ return ODP_FIT_ERROR;
+ }
+ flow->in_port = odp_port_to_ofp_port(in_port);
+ expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_IN_PORT;
+ } else {
+ flow->in_port = OFPP_NONE;
+ }
+
+ /* Ethernet header. */
+ if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ETHERNET)) {
+ const struct ovs_key_ethernet *eth_key;
+
+ eth_key = nl_attr_get(attrs[OVS_KEY_ATTR_ETHERNET]);
+ memcpy(flow->dl_src, eth_key->eth_src, ETH_ADDR_LEN);
+ memcpy(flow->dl_dst, eth_key->eth_dst, ETH_ADDR_LEN);
+ }
+ expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_ETHERNET;
+
+ /* Get Ethertype or 802.1Q TPID or FLOW_DL_TYPE_NONE. */
+ if (!parse_ethertype(attrs, present_attrs, &expected_attrs, flow)) {
+ return ODP_FIT_ERROR;
+ }
+
+ if (flow->dl_type == htons(ETH_TYPE_VLAN)) {
+ return parse_8021q_onward(attrs, present_attrs, out_of_range_attr,
+ expected_attrs, flow, key, key_len);
+ }
+ return parse_l3_onward(attrs, present_attrs, out_of_range_attr,
+ expected_attrs, flow, key, key_len);
+}
+
+/* Appends an OVS_ACTION_ATTR_USERSPACE action to 'odp_actions' that specifies
+ * Netlink PID 'pid'. If 'cookie' is nonnull, adds a userdata attribute whose
+ * contents contains 'cookie' and returns the offset within 'odp_actions' of
+ * the start of the cookie. (If 'cookie' is null, then the return value is not
+ * meaningful.) */
+size_t
+odp_put_userspace_action(uint32_t pid, const struct user_action_cookie *cookie,
+ struct ofpbuf *odp_actions)
+{
+ size_t offset;
+
+ offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_USERSPACE);
+ nl_msg_put_u32(odp_actions, OVS_USERSPACE_ATTR_PID, pid);
+ if (cookie) {
+ nl_msg_put_unspec(odp_actions, OVS_USERSPACE_ATTR_USERDATA,
+ cookie, sizeof *cookie);
+ }
+ nl_msg_end_nested(odp_actions, offset);
+
+ return cookie ? odp_actions->size - NLA_ALIGN(sizeof *cookie) : 0;
+}
+\f
+/* The commit_odp_actions() function and its helpers. */
+
+static void
+commit_set_action(struct ofpbuf *odp_actions, enum ovs_key_attr key_type,
+ const void *key, size_t key_size)
+{
+ size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET);
+ nl_msg_put_unspec(odp_actions, key_type, key, key_size);
+ nl_msg_end_nested(odp_actions, offset);
+}
+
+static void
+commit_set_tun_id_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions)
+{
+ if (base->tun_id == flow->tun_id) {
+ return;
+ }
+ base->tun_id = flow->tun_id;
+
+ commit_set_action(odp_actions, OVS_KEY_ATTR_TUN_ID,
+ &base->tun_id, sizeof(base->tun_id));
+}
+
+static void
+commit_set_ether_addr_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions)
+{
+ struct ovs_key_ethernet eth_key;
+
+ if (eth_addr_equals(base->dl_src, flow->dl_src) &&
+ eth_addr_equals(base->dl_dst, flow->dl_dst)) {
+ return;
+ }
+
+ memcpy(base->dl_src, flow->dl_src, ETH_ADDR_LEN);
+ memcpy(base->dl_dst, flow->dl_dst, ETH_ADDR_LEN);
+
+ memcpy(eth_key.eth_src, base->dl_src, ETH_ADDR_LEN);
+ memcpy(eth_key.eth_dst, base->dl_dst, ETH_ADDR_LEN);
+
+ commit_set_action(odp_actions, OVS_KEY_ATTR_ETHERNET,
+ ð_key, sizeof(eth_key));
+}
+
+static void
+commit_vlan_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions)
+{
+ if (base->vlan_tci == flow->vlan_tci) {
+ return;
+ }
+
+ if (base->vlan_tci & htons(VLAN_CFI)) {
+ nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_VLAN);
+ }
+
+ if (flow->vlan_tci & htons(VLAN_CFI)) {
+ struct ovs_action_push_vlan vlan;
+
+ vlan.vlan_tpid = htons(ETH_TYPE_VLAN);
+ vlan.vlan_tci = flow->vlan_tci;
+ nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_PUSH_VLAN,
+ &vlan, sizeof vlan);
+ }
+ base->vlan_tci = flow->vlan_tci;
+}
+
+static void
+commit_set_nw_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions)
+{
+ struct ovs_key_ipv4 ipv4_key;
+
+ if (base->dl_type != htons(ETH_TYPE_IP) ||
+ !base->nw_src || !base->nw_dst) {
+ return;
+ }
+
+ if (base->nw_src == flow->nw_src &&
+ base->nw_dst == flow->nw_dst &&
+ base->nw_tos == flow->nw_tos &&
+ base->nw_ttl == flow->nw_ttl &&
+ base->nw_frag == flow->nw_frag) {
+ return;
+ }
+
+ ipv4_key.ipv4_src = base->nw_src = flow->nw_src;
+ ipv4_key.ipv4_dst = base->nw_dst = flow->nw_dst;
+ ipv4_key.ipv4_tos = base->nw_tos = flow->nw_tos;
+ ipv4_key.ipv4_ttl = base->nw_ttl = flow->nw_ttl;
+ ipv4_key.ipv4_proto = base->nw_proto;
+ ipv4_key.ipv4_frag = (base->nw_frag == 0 ? OVS_FRAG_TYPE_NONE
+ : base->nw_frag == FLOW_NW_FRAG_ANY
+ ? OVS_FRAG_TYPE_FIRST : OVS_FRAG_TYPE_LATER);
+
+ commit_set_action(odp_actions, OVS_KEY_ATTR_IPV4,
+ &ipv4_key, sizeof(ipv4_key));
+}
+
+static void
+commit_set_port_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions)
+{
+ if (!base->tp_src || !base->tp_dst) {
+ return;
+ }
+
+ if (base->tp_src == flow->tp_src &&
+ base->tp_dst == flow->tp_dst) {
+ return;
+ }
+
+ if (flow->nw_proto == IPPROTO_TCP) {
+ struct ovs_key_tcp port_key;
+
+ port_key.tcp_src = base->tp_src = flow->tp_src;
+ port_key.tcp_dst = base->tp_dst = flow->tp_dst;
+
+ commit_set_action(odp_actions, OVS_KEY_ATTR_TCP,
+ &port_key, sizeof(port_key));
+
+ } else if (flow->nw_proto == IPPROTO_UDP) {
+ struct ovs_key_udp port_key;
+
+ port_key.udp_src = base->tp_src = flow->tp_src;
+ port_key.udp_dst = base->tp_dst = flow->tp_dst;
+
+ commit_set_action(odp_actions, OVS_KEY_ATTR_UDP,
+ &port_key, sizeof(port_key));
+ }
+}
+
+static void
+commit_set_priority_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions)
+{
+ if (base->skb_priority == flow->skb_priority) {
+ return;
+ }
+ base->skb_priority = flow->skb_priority;
+
+ commit_set_action(odp_actions, OVS_KEY_ATTR_PRIORITY,
+ &base->skb_priority, sizeof(base->skb_priority));
+}
+
+/* If any of the flow key data that ODP actions can modify are different in
+ * 'base' and 'flow', appends ODP actions to 'odp_actions' that change the flow
+ * key from 'base' into 'flow', and then changes 'base' the same way. */
+void
+commit_odp_actions(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions)
+{
+ commit_set_tun_id_action(flow, base, odp_actions);
+ commit_set_ether_addr_action(flow, base, odp_actions);
+ commit_vlan_action(flow, base, odp_actions);
+ commit_set_nw_action(flow, base, odp_actions);
+ commit_set_port_action(flow, base, odp_actions);
+ commit_set_priority_action(flow, base, odp_actions);