+ return userdata_ofs;
+}
+
+void
+odp_put_tunnel_action(const struct flow_tnl *tunnel,
+ struct ofpbuf *odp_actions)
+{
+ size_t offset = nl_msg_start_nested(odp_actions, OVS_ACTION_ATTR_SET);
+ tun_key_to_attr(odp_actions, tunnel);
+ nl_msg_end_nested(odp_actions, offset);
+}
+\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);
+}
+
+void
+odp_put_skb_mark_action(const uint32_t skb_mark,
+ struct ofpbuf *odp_actions)
+{
+ commit_set_action(odp_actions, OVS_KEY_ATTR_SKB_MARK, &skb_mark,
+ sizeof(skb_mark));
+}
+
+/* If any of the flow key data that ODP actions can modify are different in
+ * 'base->tunnel' and 'flow->tunnel', appends a set_tunnel ODP action to
+ * 'odp_actions' that change the flow tunneling information in key from
+ * 'base->tunnel' into 'flow->tunnel', and then changes 'base->tunnel' in the
+ * same way. In other words, operates the same as commit_odp_actions(), but
+ * only on tunneling information. */
+void
+commit_odp_tunnel_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions)
+{
+ /* A valid IPV4_TUNNEL must have non-zero ip_dst. */
+ if (flow->tunnel.ip_dst) {
+ if (!memcmp(&base->tunnel, &flow->tunnel, sizeof base->tunnel)) {
+ return;
+ }
+ memcpy(&base->tunnel, &flow->tunnel, sizeof base->tunnel);
+ odp_put_tunnel_action(&base->tunnel, odp_actions);
+ }
+}
+
+static void
+commit_set_ether_addr_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions,
+ struct flow_wildcards *wc)
+{
+ 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;
+ }
+
+ memset(&wc->masks.dl_src, 0xff, sizeof wc->masks.dl_src);
+ memset(&wc->masks.dl_dst, 0xff, sizeof wc->masks.dl_dst);
+
+ 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, struct flow_wildcards *wc)
+{
+ if (base->vlan_tci == flow->vlan_tci) {
+ return;
+ }
+
+ memset(&wc->masks.vlan_tci, 0xff, sizeof wc->masks.vlan_tci);
+
+ 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_mpls_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions, struct flow_wildcards *wc)
+{
+ if (flow->mpls_lse == base->mpls_lse &&
+ flow->mpls_depth == base->mpls_depth) {
+ return;
+ }
+
+ memset(&wc->masks.mpls_lse, 0xff, sizeof wc->masks.mpls_lse);
+
+ if (flow->mpls_depth < base->mpls_depth) {
+ if (base->mpls_depth - flow->mpls_depth > 1) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 10);
+ VLOG_WARN_RL(&rl, "Multiple mpls_pop actions reduced to "
+ " a single mpls_pop action");
+ }
+
+ nl_msg_put_be16(odp_actions, OVS_ACTION_ATTR_POP_MPLS, flow->dl_type);
+ } else if (flow->mpls_depth > base->mpls_depth) {
+ struct ovs_action_push_mpls *mpls;
+
+ if (flow->mpls_depth - base->mpls_depth > 1) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 10);
+ VLOG_WARN_RL(&rl, "Multiple mpls_push actions reduced to "
+ " a single mpls_push action");
+ }
+
+ mpls = nl_msg_put_unspec_uninit(odp_actions, OVS_ACTION_ATTR_PUSH_MPLS,
+ sizeof *mpls);
+ memset(mpls, 0, sizeof *mpls);
+ mpls->mpls_ethertype = flow->dl_type;
+ mpls->mpls_lse = flow->mpls_lse;
+ } else {
+ struct ovs_key_mpls mpls_key;
+
+ mpls_key.mpls_lse = flow->mpls_lse;
+ commit_set_action(odp_actions, OVS_KEY_ATTR_MPLS,
+ &mpls_key, sizeof(mpls_key));
+ }
+
+ base->dl_type = flow->dl_type;
+ base->mpls_lse = flow->mpls_lse;
+ base->mpls_depth = flow->mpls_depth;
+}
+
+static void
+commit_set_ipv4_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions, struct flow_wildcards *wc)
+{
+ struct ovs_key_ipv4 ipv4_key;
+
+ 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;
+ }
+
+ memset(&wc->masks.nw_src, 0xff, sizeof wc->masks.nw_src);
+ memset(&wc->masks.nw_dst, 0xff, sizeof wc->masks.nw_dst);
+ memset(&wc->masks.nw_tos, 0xff, sizeof wc->masks.nw_tos);
+ memset(&wc->masks.nw_ttl, 0xff, sizeof wc->masks.nw_ttl);
+ memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
+ memset(&wc->masks.nw_frag, 0xff, sizeof wc->masks.nw_frag);
+
+ 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 = ovs_to_odp_frag(base->nw_frag);
+
+ commit_set_action(odp_actions, OVS_KEY_ATTR_IPV4,
+ &ipv4_key, sizeof(ipv4_key));
+}
+
+static void
+commit_set_ipv6_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions, struct flow_wildcards *wc)
+{
+ struct ovs_key_ipv6 ipv6_key;
+
+ if (ipv6_addr_equals(&base->ipv6_src, &flow->ipv6_src) &&
+ ipv6_addr_equals(&base->ipv6_dst, &flow->ipv6_dst) &&
+ base->ipv6_label == flow->ipv6_label &&
+ base->nw_tos == flow->nw_tos &&
+ base->nw_ttl == flow->nw_ttl &&
+ base->nw_frag == flow->nw_frag) {
+ return;
+ }
+
+ memset(&wc->masks.ipv6_src, 0xff, sizeof wc->masks.ipv6_src);
+ memset(&wc->masks.ipv6_dst, 0xff, sizeof wc->masks.ipv6_dst);
+ memset(&wc->masks.ipv6_label, 0xff, sizeof wc->masks.ipv6_label);
+ memset(&wc->masks.nw_tos, 0xff, sizeof wc->masks.nw_tos);
+ memset(&wc->masks.nw_ttl, 0xff, sizeof wc->masks.nw_ttl);
+ memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
+ memset(&wc->masks.nw_frag, 0xff, sizeof wc->masks.nw_frag);
+
+ base->ipv6_src = flow->ipv6_src;
+ memcpy(&ipv6_key.ipv6_src, &base->ipv6_src, sizeof(ipv6_key.ipv6_src));
+ base->ipv6_dst = flow->ipv6_dst;
+ memcpy(&ipv6_key.ipv6_dst, &base->ipv6_dst, sizeof(ipv6_key.ipv6_dst));
+
+ ipv6_key.ipv6_label = base->ipv6_label = flow->ipv6_label;
+ ipv6_key.ipv6_tclass = base->nw_tos = flow->nw_tos;
+ ipv6_key.ipv6_hlimit = base->nw_ttl = flow->nw_ttl;
+ ipv6_key.ipv6_proto = base->nw_proto;
+ ipv6_key.ipv6_frag = ovs_to_odp_frag(base->nw_frag);
+
+ commit_set_action(odp_actions, OVS_KEY_ATTR_IPV6,
+ &ipv6_key, sizeof(ipv6_key));
+}
+
+static void
+commit_set_nw_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions, struct flow_wildcards *wc)
+{
+ /* Check if flow really have an IP header. */
+ if (!flow->nw_proto) {
+ return;
+ }
+
+ if (base->dl_type == htons(ETH_TYPE_IP)) {
+ commit_set_ipv4_action(flow, base, odp_actions, wc);
+ } else if (base->dl_type == htons(ETH_TYPE_IPV6)) {
+ commit_set_ipv6_action(flow, base, odp_actions, wc);
+ }
+}
+
+static void
+commit_set_port_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions, struct flow_wildcards *wc)
+{
+ if (!is_ip_any(base) || (!base->tp_src && !base->tp_dst)) {
+ return;
+ }
+
+ if (base->tp_src == flow->tp_src &&
+ base->tp_dst == flow->tp_dst) {
+ return;
+ }
+
+ memset(&wc->masks.tp_src, 0xff, sizeof wc->masks.tp_src);
+ memset(&wc->masks.tp_dst, 0xff, sizeof wc->masks.tp_dst);
+
+ 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,
+ struct flow_wildcards *wc)
+{
+ if (base->skb_priority == flow->skb_priority) {
+ return;
+ }
+
+ memset(&wc->masks.skb_priority, 0xff, sizeof wc->masks.skb_priority);
+ base->skb_priority = flow->skb_priority;
+
+ commit_set_action(odp_actions, OVS_KEY_ATTR_PRIORITY,
+ &base->skb_priority, sizeof(base->skb_priority));
+}
+
+static void
+commit_set_skb_mark_action(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions,
+ struct flow_wildcards *wc)
+{
+ if (base->skb_mark == flow->skb_mark) {
+ return;
+ }
+
+ memset(&wc->masks.skb_mark, 0xff, sizeof wc->masks.skb_mark);
+ base->skb_mark = flow->skb_mark;
+
+ odp_put_skb_mark_action(base->skb_mark, odp_actions);
+}
+/* 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. Does not
+ * commit set_tunnel actions. Users should call commit_odp_tunnel_action()
+ * in addition to this function if needed. Sets fields in 'wc' that are
+ * used as part of the action. */
+void
+commit_odp_actions(const struct flow *flow, struct flow *base,
+ struct ofpbuf *odp_actions, struct flow_wildcards *wc)
+{
+ commit_set_ether_addr_action(flow, base, odp_actions, wc);
+ commit_vlan_action(flow, base, odp_actions, wc);
+ commit_set_nw_action(flow, base, odp_actions, wc);
+ commit_set_port_action(flow, base, odp_actions, wc);
+ /* Committing MPLS actions should occur after committing nw and port
+ * actions. This is because committing MPLS actions may alter a packet so
+ * that it is no longer IP and thus nw and port actions are no longer valid.
+ */
+ commit_mpls_action(flow, base, odp_actions, wc);
+ commit_set_priority_action(flow, base, odp_actions, wc);
+ commit_set_skb_mark_action(flow, base, odp_actions, wc);