mpls: Allow l3 and l4 actions to prior to a push_mpls action
[sliver-openvswitch.git] / lib / odp-util.c
index de97fd2..f9e9321 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -73,6 +73,8 @@ odp_action_len(uint16_t type)
     case OVS_ACTION_ATTR_USERSPACE: return -2;
     case OVS_ACTION_ATTR_PUSH_VLAN: return sizeof(struct ovs_action_push_vlan);
     case OVS_ACTION_ATTR_POP_VLAN: return 0;
+    case OVS_ACTION_ATTR_PUSH_MPLS: return sizeof(struct ovs_action_push_mpls);
+    case OVS_ACTION_ATTR_POP_MPLS: return sizeof(ovs_be16);
     case OVS_ACTION_ATTR_SET: return -2;
     case OVS_ACTION_ATTR_SAMPLE: return -2;
 
@@ -92,10 +94,9 @@ ovs_key_attr_to_string(enum ovs_key_attr attr)
     switch (attr) {
     case OVS_KEY_ATTR_UNSPEC: return "unspec";
     case OVS_KEY_ATTR_ENCAP: return "encap";
-    case OVS_KEY_ATTR_PRIORITY: return "priority";
+    case OVS_KEY_ATTR_PRIORITY: return "skb_priority";
     case OVS_KEY_ATTR_SKB_MARK: return "skb_mark";
-    case OVS_KEY_ATTR_TUN_ID: return "tun_id";
-    case OVS_KEY_ATTR_IPV4_TUNNEL: return "ipv4_tunnel";
+    case OVS_KEY_ATTR_TUNNEL: return "tunnel";
     case OVS_KEY_ATTR_IN_PORT: return "in_port";
     case OVS_KEY_ATTR_ETHERNET: return "eth";
     case OVS_KEY_ATTR_VLAN: return "vlan";
@@ -108,6 +109,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr)
     case OVS_KEY_ATTR_ICMPV6: return "icmpv6";
     case OVS_KEY_ATTR_ARP: return "arp";
     case OVS_KEY_ATTR_ND: return "nd";
+    case OVS_KEY_ATTR_MPLS: return "mpls";
 
     case __OVS_KEY_ATTR_MAX:
     default:
@@ -245,9 +247,11 @@ format_odp_userspace_action(struct ds *ds, const struct nlattr *attr)
 {
     static const struct nl_policy ovs_userspace_policy[] = {
         [OVS_USERSPACE_ATTR_PID] = { .type = NL_A_U32 },
-        [OVS_USERSPACE_ATTR_USERDATA] = { .type = NL_A_U64, .optional = true },
+        [OVS_USERSPACE_ATTR_USERDATA] = { .type = NL_A_UNSPEC,
+                                          .optional = true },
     };
     struct nlattr *a[ARRAY_SIZE(ovs_userspace_policy)];
+    const struct nlattr *userdata_attr;
 
     if (!nl_parse_nested(attr, ovs_userspace_policy, a, ARRAY_SIZE(a))) {
         ds_put_cstr(ds, "userspace(error)");
@@ -257,7 +261,8 @@ format_odp_userspace_action(struct ds *ds, const struct nlattr *attr)
     ds_put_format(ds, "userspace(pid=%"PRIu32,
                   nl_attr_get_u32(a[OVS_USERSPACE_ATTR_PID]));
 
-    if (a[OVS_USERSPACE_ATTR_USERDATA]) {
+    userdata_attr = a[OVS_USERSPACE_ATTR_USERDATA];
+    if (userdata_attr && nl_attr_get_size(userdata_attr) == sizeof(uint64_t)) {
         uint64_t userdata = nl_attr_get_u64(a[OVS_USERSPACE_ATTR_USERDATA]);
         union user_action_cookie cookie;
 
@@ -284,6 +289,16 @@ format_odp_userspace_action(struct ds *ds, const struct nlattr *attr)
             ds_put_format(ds, ",userdata=0x%"PRIx64, userdata);
             break;
         }
+    } else if (userdata_attr) {
+        const uint8_t *userdata = nl_attr_get(userdata_attr);
+        size_t len = nl_attr_get_size(userdata_attr);
+        size_t i;
+
+        ds_put_format(ds, ",userdata(");
+        for (i = 0; i < len; i++) {
+            ds_put_format(ds, "%02x", userdata[i]);
+        }
+        ds_put_char(ds, ')');
     }
 
     ds_put_char(ds, ')');
@@ -300,6 +315,16 @@ format_vlan_tci(struct ds *ds, ovs_be16 vlan_tci)
     }
 }
 
+static void
+format_mpls_lse(struct ds *ds, ovs_be32 mpls_lse)
+{
+    ds_put_format(ds, "label=%"PRIu32",tc=%d,ttl=%d,bos=%d",
+                  mpls_lse_to_label(mpls_lse),
+                  mpls_lse_to_tc(mpls_lse),
+                  mpls_lse_to_ttl(mpls_lse),
+                  mpls_lse_to_bos(mpls_lse));
+}
+
 static void
 format_odp_action(struct ds *ds, const struct nlattr *a)
 {
@@ -339,6 +364,18 @@ format_odp_action(struct ds *ds, const struct nlattr *a)
     case OVS_ACTION_ATTR_POP_VLAN:
         ds_put_cstr(ds, "pop_vlan");
         break;
+    case OVS_ACTION_ATTR_PUSH_MPLS: {
+        const struct ovs_action_push_mpls *mpls = nl_attr_get(a);
+        ds_put_cstr(ds, "push_mpls(");
+        format_mpls_lse(ds, mpls->mpls_lse);
+        ds_put_format(ds, ",eth_type=0x%"PRIx16")", ntohs(mpls->mpls_ethertype));
+        break;
+    }
+    case OVS_ACTION_ATTR_POP_MPLS: {
+        ovs_be16 ethertype = nl_attr_get_be16(a);
+        ds_put_format(ds, "pop_mpls(eth_type=0x%"PRIx16")", ntohs(ethertype));
+        break;
+    }
     case OVS_ACTION_ATTR_SAMPLE:
         format_odp_sample_action(ds, a);
         break;
@@ -424,7 +461,7 @@ parse_odp_action(const char *s, const struct simap *port_names,
         int n = -1;
 
         if (sscanf(s, "userspace(pid=%lli)%n", &pid, &n) > 0 && n > 0) {
-            odp_put_userspace_action(pid, NULL, actions);
+            odp_put_userspace_action(pid, NULL, 0, actions);
             return n;
         } else if (sscanf(s, "userspace(pid=%lli,sFlow(vid=%i,"
                           "pcp=%i,output=%lli))%n",
@@ -440,7 +477,7 @@ parse_odp_action(const char *s, const struct simap *port_names,
             cookie.type = USER_ACTION_COOKIE_SFLOW;
             cookie.sflow.vlan_tci = htons(tci);
             cookie.sflow.output = output;
-            odp_put_userspace_action(pid, &cookie, actions);
+            odp_put_userspace_action(pid, &cookie, sizeof cookie, actions);
             return n;
         } else if (sscanf(s, "userspace(pid=%lli,slow_path%n", &pid, &n) > 0
                    && n > 0) {
@@ -462,18 +499,29 @@ parse_odp_action(const char *s, const struct simap *port_names,
             }
             n++;
 
-            odp_put_userspace_action(pid, &cookie, actions);
+            odp_put_userspace_action(pid, &cookie, sizeof cookie, actions);
             return n;
         } else if (sscanf(s, "userspace(pid=%lli,userdata="
                           "%31[x0123456789abcdefABCDEF])%n", &pid, userdata_s,
                           &n) > 0 && n > 0) {
-            union user_action_cookie cookie;
             uint64_t userdata;
 
             userdata = strtoull(userdata_s, NULL, 0);
-            memcpy(&cookie, &userdata, sizeof cookie);
-            odp_put_userspace_action(pid, &cookie, actions);
+            odp_put_userspace_action(pid, &userdata, sizeof(userdata),
+                                     actions);
             return n;
+        } else if (sscanf(s, "userspace(pid=%lli,userdata(%n", &pid, &n) > 0
+                   && n > 0) {
+            struct ofpbuf buf;
+            char *end;
+
+            ofpbuf_init(&buf, 16);
+            end = ofpbuf_put_hex(&buf, &s[n], NULL);
+            if (end[0] == ')' && end[1] == ')') {
+                odp_put_userspace_action(pid, buf.data, buf.size, actions);
+                ofpbuf_uninit(&buf);
+                return (end + 2) - s;
+            }
         }
     }
 
@@ -616,12 +664,12 @@ odp_flow_key_attr_len(uint16_t type)
     case OVS_KEY_ATTR_ENCAP: return -2;
     case OVS_KEY_ATTR_PRIORITY: return 4;
     case OVS_KEY_ATTR_SKB_MARK: return 4;
-    case OVS_KEY_ATTR_TUN_ID: return 8;
-    case OVS_KEY_ATTR_IPV4_TUNNEL: return sizeof(struct ovs_key_ipv4_tunnel);
+    case OVS_KEY_ATTR_TUNNEL: return -2;
     case OVS_KEY_ATTR_IN_PORT: return 4;
     case OVS_KEY_ATTR_ETHERNET: return sizeof(struct ovs_key_ethernet);
     case OVS_KEY_ATTR_VLAN: return sizeof(ovs_be16);
     case OVS_KEY_ATTR_ETHERTYPE: return 2;
+    case OVS_KEY_ATTR_MPLS: return sizeof(struct ovs_key_mpls);
     case OVS_KEY_ATTR_IPV4: return sizeof(struct ovs_key_ipv4);
     case OVS_KEY_ATTR_IPV6: return sizeof(struct ovs_key_ipv6);
     case OVS_KEY_ATTR_TCP: return sizeof(struct ovs_key_tcp);
@@ -672,19 +720,109 @@ ovs_frag_type_to_string(enum ovs_frag_type type)
     }
 }
 
-static const char *
-odp_tun_flag_to_string(uint32_t flags)
+static int
+tunnel_key_attr_len(int type)
 {
-    switch (flags) {
-    case OVS_TNL_F_DONT_FRAGMENT:
-        return "df";
-    case OVS_TNL_F_CSUM:
-        return "csum";
-    case OVS_TNL_F_KEY:
-        return "key";
-    default:
-        return NULL;
+    switch (type) {
+    case OVS_TUNNEL_KEY_ATTR_ID: return 8;
+    case OVS_TUNNEL_KEY_ATTR_IPV4_SRC: return 4;
+    case OVS_TUNNEL_KEY_ATTR_IPV4_DST: return 4;
+    case OVS_TUNNEL_KEY_ATTR_TOS: return 1;
+    case OVS_TUNNEL_KEY_ATTR_TTL: return 1;
+    case OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT: return 0;
+    case OVS_TUNNEL_KEY_ATTR_CSUM: return 0;
+    case __OVS_TUNNEL_KEY_ATTR_MAX:
+        return -1;
+    }
+    return -1;
+}
+
+static enum odp_key_fitness
+tun_key_from_attr(const struct nlattr *attr, struct flow_tnl *tun)
+{
+    unsigned int left;
+    const struct nlattr *a;
+    bool ttl = false;
+    bool unknown = false;
+
+    NL_NESTED_FOR_EACH(a, left, attr) {
+        uint16_t type = nl_attr_type(a);
+        size_t len = nl_attr_get_size(a);
+        int expected_len = tunnel_key_attr_len(type);
+
+        if (len != expected_len && expected_len >= 0) {
+            return ODP_FIT_ERROR;
+        }
+
+        switch (type) {
+        case OVS_TUNNEL_KEY_ATTR_ID:
+            tun->tun_id = nl_attr_get_be64(a);
+            tun->flags |= FLOW_TNL_F_KEY;
+            break;
+        case OVS_TUNNEL_KEY_ATTR_IPV4_SRC:
+            tun->ip_src = nl_attr_get_be32(a);
+            break;
+        case OVS_TUNNEL_KEY_ATTR_IPV4_DST:
+            tun->ip_dst = nl_attr_get_be32(a);
+            break;
+        case OVS_TUNNEL_KEY_ATTR_TOS:
+            tun->ip_tos = nl_attr_get_u8(a);
+            break;
+        case OVS_TUNNEL_KEY_ATTR_TTL:
+            tun->ip_ttl = nl_attr_get_u8(a);
+            ttl = true;
+            break;
+        case OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT:
+            tun->flags |= FLOW_TNL_F_DONT_FRAGMENT;
+            break;
+        case OVS_TUNNEL_KEY_ATTR_CSUM:
+            tun->flags |= FLOW_TNL_F_CSUM;
+            break;
+        default:
+            /* Allow this to show up as unexpected, if there are unknown
+             * tunnel attribute, eventually resulting in ODP_FIT_TOO_MUCH. */
+            unknown = true;
+            break;
+        }
     }
+
+    if (!ttl) {
+        return ODP_FIT_ERROR;
+    }
+    if (unknown) {
+            return ODP_FIT_TOO_MUCH;
+    }
+    return ODP_FIT_PERFECT;
+}
+
+static void
+tun_key_to_attr(struct ofpbuf *a, const struct flow_tnl *tun_key)
+{
+    size_t tun_key_ofs;
+
+    tun_key_ofs = nl_msg_start_nested(a, OVS_KEY_ATTR_TUNNEL);
+
+    if (tun_key->flags & FLOW_TNL_F_KEY) {
+        nl_msg_put_be64(a, OVS_TUNNEL_KEY_ATTR_ID, tun_key->tun_id);
+    }
+    if (tun_key->ip_src) {
+        nl_msg_put_be32(a, OVS_TUNNEL_KEY_ATTR_IPV4_SRC, tun_key->ip_src);
+    }
+    if (tun_key->ip_dst) {
+        nl_msg_put_be32(a, OVS_TUNNEL_KEY_ATTR_IPV4_DST, tun_key->ip_dst);
+    }
+    if (tun_key->ip_tos) {
+        nl_msg_put_u8(a, OVS_TUNNEL_KEY_ATTR_TOS, tun_key->ip_tos);
+    }
+    nl_msg_put_u8(a, OVS_TUNNEL_KEY_ATTR_TTL, tun_key->ip_ttl);
+    if (tun_key->flags & FLOW_TNL_F_DONT_FRAGMENT) {
+        nl_msg_put_flag(a, OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT);
+    }
+    if (tun_key->flags & FLOW_TNL_F_CSUM) {
+        nl_msg_put_flag(a, OVS_TUNNEL_KEY_ATTR_CSUM);
+    }
+
+    nl_msg_end_nested(a, tun_key_ofs);
 }
 
 static void
@@ -699,7 +837,7 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
     const struct ovs_key_icmpv6 *icmpv6_key;
     const struct ovs_key_arp *arp_key;
     const struct ovs_key_nd *nd_key;
-    const struct ovs_key_ipv4_tunnel *ipv4_tun_key;
+    struct flow_tnl tun_key;
     enum ovs_key_attr attr = nl_attr_type(a);
     int expected_len;
 
@@ -723,29 +861,29 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
         break;
 
     case OVS_KEY_ATTR_PRIORITY:
-        ds_put_format(ds, "(%"PRIu32")", nl_attr_get_u32(a));
+        ds_put_format(ds, "(%#"PRIx32")", nl_attr_get_u32(a));
         break;
 
     case OVS_KEY_ATTR_SKB_MARK:
-        ds_put_format(ds, "(%"PRIu32")", nl_attr_get_u32(a));
-        break;
-
-    case OVS_KEY_ATTR_TUN_ID:
-        ds_put_format(ds, "(%#"PRIx64")", ntohll(nl_attr_get_be64(a)));
+        ds_put_format(ds, "(%#"PRIx32")", nl_attr_get_u32(a));
         break;
 
-    case OVS_KEY_ATTR_IPV4_TUNNEL:
-        ipv4_tun_key = nl_attr_get(a);
-        ds_put_format(ds, "(tun_id=0x%"PRIx64",src="IP_FMT",dst="IP_FMT","
-                      "tos=0x%"PRIx8",ttl=%"PRIu8",flags(",
-                      ntohll(ipv4_tun_key->tun_id),
-                      IP_ARGS(&ipv4_tun_key->ipv4_src),
-                      IP_ARGS(&ipv4_tun_key->ipv4_dst),
-                      ipv4_tun_key->ipv4_tos, ipv4_tun_key->ipv4_ttl);
-
-        format_flags(ds, odp_tun_flag_to_string,
-                     ipv4_tun_key->tun_flags, ',');
-        ds_put_format(ds, "))");
+    case OVS_KEY_ATTR_TUNNEL:
+        memset(&tun_key, 0, sizeof tun_key);
+        if (tun_key_from_attr(a, &tun_key) == ODP_FIT_ERROR) {
+            ds_put_format(ds, "(error)");
+        } else {
+            ds_put_format(ds, "(tun_id=0x%"PRIx64",src="IP_FMT",dst="IP_FMT","
+                          "tos=0x%"PRIx8",ttl=%"PRIu8",flags(",
+                          ntohll(tun_key.tun_id),
+                          IP_ARGS(tun_key.ip_src),
+                          IP_ARGS(tun_key.ip_dst),
+                          tun_key.ip_tos, tun_key.ip_ttl);
+
+            format_flags(ds, flow_tun_flag_to_string,
+                         (uint32_t) tun_key.flags, ',');
+            ds_put_format(ds, "))");
+        }
         break;
 
     case OVS_KEY_ATTR_IN_PORT:
@@ -765,6 +903,14 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
         ds_put_char(ds, ')');
         break;
 
+    case OVS_KEY_ATTR_MPLS: {
+        const struct ovs_key_mpls *mpls_key = nl_attr_get(a);
+        ds_put_char(ds, '(');
+        format_mpls_lse(ds, mpls_key->mpls_top_lse);
+        ds_put_char(ds, ')');
+        break;
+    }
+
     case OVS_KEY_ATTR_ETHERTYPE:
         ds_put_format(ds, "(0x%04"PRIx16")",
                       ntohs(nl_attr_get_be16(a)));
@@ -774,8 +920,8 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
         ipv4_key = nl_attr_get(a);
         ds_put_format(ds, "(src="IP_FMT",dst="IP_FMT",proto=%"PRIu8
                       ",tos=%#"PRIx8",ttl=%"PRIu8",frag=%s)",
-                      IP_ARGS(&ipv4_key->ipv4_src),
-                      IP_ARGS(&ipv4_key->ipv4_dst),
+                      IP_ARGS(ipv4_key->ipv4_src),
+                      IP_ARGS(ipv4_key->ipv4_dst),
                       ipv4_key->ipv4_proto, ipv4_key->ipv4_tos,
                       ipv4_key->ipv4_ttl,
                       ovs_frag_type_to_string(ipv4_key->ipv4_frag));
@@ -826,7 +972,7 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
         arp_key = nl_attr_get(a);
         ds_put_format(ds, "(sip="IP_FMT",tip="IP_FMT",op=%"PRIu16","
                       "sha="ETH_ADDR_FMT",tha="ETH_ADDR_FMT")",
-                      IP_ARGS(&arp_key->arp_sip), IP_ARGS(&arp_key->arp_tip),
+                      IP_ARGS(arp_key->arp_sip), IP_ARGS(arp_key->arp_tip),
                       ntohs(arp_key->arp_op), ETH_ADDR_ARGS(arp_key->arp_sha),
                       ETH_ADDR_ARGS(arp_key->arp_tha));
         break;
@@ -925,6 +1071,15 @@ ovs_frag_type_from_string(const char *s, enum ovs_frag_type *type)
     return true;
 }
 
+static ovs_be32
+mpls_lse_from_components(int mpls_label, int mpls_tc, int mpls_ttl, int mpls_bos)
+{
+    return (htonl((mpls_label << MPLS_LABEL_SHIFT) |
+                  (mpls_tc << MPLS_TC_SHIFT)       |
+                  (mpls_ttl << MPLS_TTL_SHIFT)     |
+                  (mpls_bos << MPLS_BOS_SHIFT)));
+}
+
 static int
 parse_odp_key_attr(const char *s, const struct simap *port_names,
                    struct ofpbuf *key)
@@ -943,7 +1098,7 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
         unsigned long long int priority;
         int n = -1;
 
-        if (sscanf(s, "priority(%lli)%n", &priority, &n) > 0 && n > 0) {
+        if (sscanf(s, "skb_priority(%llx)%n", &priority, &n) > 0 && n > 0) {
             nl_msg_put_u32(key, OVS_KEY_ATTR_PRIORITY, priority);
             return n;
         }
@@ -953,44 +1108,33 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
         unsigned long long int mark;
         int n = -1;
 
-        if (sscanf(s, "skb_mark(%lli)%n", &mark, &n) > 0 && n > 0) {
+        if (sscanf(s, "skb_mark(%llx)%n", &mark, &n) > 0 && n > 0) {
             nl_msg_put_u32(key, OVS_KEY_ATTR_SKB_MARK, mark);
             return n;
         }
     }
 
-    {
-        char tun_id_s[32];
-        int n = -1;
-
-        if (sscanf(s, "tun_id(%31[x0123456789abcdefABCDEF])%n",
-                   tun_id_s, &n) > 0 && n > 0) {
-            uint64_t tun_id = strtoull(tun_id_s, NULL, 0);
-            nl_msg_put_be64(key, OVS_KEY_ATTR_TUN_ID, htonll(tun_id));
-            return n;
-        }
-    }
-
     {
         char tun_id_s[32];
         int tos, ttl;
-        struct ovs_key_ipv4_tunnel tun_key;
+        struct flow_tnl tun_key;
         int n = -1;
 
-        if (sscanf(s, "ipv4_tunnel(tun_id=%31[x0123456789abcdefABCDEF],"
+        if (sscanf(s, "tunnel(tun_id=%31[x0123456789abcdefABCDEF],"
                    "src="IP_SCAN_FMT",dst="IP_SCAN_FMT
                    ",tos=%i,ttl=%i,flags%n", tun_id_s,
-                    IP_SCAN_ARGS(&tun_key.ipv4_src),
-                    IP_SCAN_ARGS(&tun_key.ipv4_dst), &tos, &ttl,
+                    IP_SCAN_ARGS(&tun_key.ip_src),
+                    IP_SCAN_ARGS(&tun_key.ip_dst), &tos, &ttl,
                     &n) > 0 && n > 0) {
             int res;
+            uint32_t flags;
 
             tun_key.tun_id = htonll(strtoull(tun_id_s, NULL, 0));
-            tun_key.ipv4_tos = tos;
-            tun_key.ipv4_ttl = ttl;
+            tun_key.ip_tos = tos;
+            tun_key.ip_ttl = ttl;
+            res = parse_flags(&s[n], flow_tun_flag_to_string, &flags);
+            tun_key.flags = (uint16_t) flags;
 
-            res = parse_flags(&s[n], odp_tun_flag_to_string,
-                              &tun_key.tun_flags);
             if (res < 0) {
                 return res;
             }
@@ -999,10 +1143,7 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
                 return -EINVAL;
             }
             n++;
-
-            memset(&tun_key.pad, 0, sizeof tun_key.pad);
-            nl_msg_put_unspec(key, OVS_KEY_ATTR_IPV4_TUNNEL, &tun_key,
-                              sizeof tun_key);
+            tun_key_to_attr(key, &tun_key);
             return n;
         }
     }
@@ -1079,6 +1220,22 @@ parse_odp_key_attr(const char *s, const struct simap *port_names,
         }
     }
 
+    {
+        int label, tc, ttl, bos;
+        int n = -1;
+
+        if (sscanf(s, "mpls(label=%"SCNi32",tc=%i,ttl=%i,bos=%i)%n",
+                    &label, &tc, &ttl, &bos, &n) > 0 &&
+                    n > 0) {
+            struct ovs_key_mpls *mpls;
+
+            mpls = nl_msg_put_unspec_uninit(key, OVS_KEY_ATTR_MPLS,
+                                            sizeof *mpls);
+            mpls->mpls_top_lse = mpls_lse_from_components(label, tc, ttl, bos);
+            return n;
+        }
+    }
+
     {
         ovs_be32 ipv4_src;
         ovs_be32 ipv4_dst;
@@ -1361,8 +1518,8 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow,
         nl_msg_put_u32(buf, OVS_KEY_ATTR_PRIORITY, flow->skb_priority);
     }
 
-    if (flow->tunnel.tun_id != htonll(0)) {
-        nl_msg_put_be64(buf, OVS_KEY_ATTR_TUN_ID, flow->tunnel.tun_id);
+    if (flow->tunnel.ip_dst) {
+        tun_key_to_attr(buf, &flow->tunnel);
     }
 
     if (flow->skb_mark) {
@@ -1432,10 +1589,15 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow,
         memcpy(arp_key->arp_tha, flow->arp_tha, ETH_ADDR_LEN);
     }
 
-    if ((flow->dl_type == htons(ETH_TYPE_IP)
-         || flow->dl_type == htons(ETH_TYPE_IPV6))
-        && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
+    if (flow->mpls_depth) {
+        struct ovs_key_mpls *mpls_key;
+
+        mpls_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_MPLS,
+                                            sizeof *mpls_key);
+        mpls_key->mpls_top_lse = flow->mpls_lse;
+    }
 
+    if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
         if (flow->nw_proto == IPPROTO_TCP) {
             struct ovs_key_tcp *tcp_key;
 
@@ -1637,14 +1799,22 @@ parse_ethertype(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
 }
 
 static enum odp_key_fitness
-parse_l3_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)
+parse_l2_5_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);
 
-    if (flow->dl_type == htons(ETH_TYPE_IP)) {
+    if (eth_type_mpls(flow->dl_type)) {
+        expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_MPLS);
+
+        if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_MPLS))) {
+            return ODP_FIT_TOO_LITTLE;
+        }
+        flow->mpls_lse = nl_attr_get_be32(attrs[OVS_KEY_ATTR_MPLS]);
+        flow->mpls_depth++;
+    } else if (flow->dl_type == htons(ETH_TYPE_IP)) {
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_IPV4;
         if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IPV4)) {
             const struct ovs_key_ipv4 *ipv4_key;
@@ -1815,8 +1985,8 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
     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);
+    encap_fitness = parse_l2_5_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);
@@ -1866,9 +2036,15 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
         expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_SKB_MARK;
     }
 
-    if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_TUN_ID)) {
-        flow->tunnel.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_TUNNEL)) {
+        enum odp_key_fitness res;
+
+        res = tun_key_from_attr(attrs[OVS_KEY_ATTR_TUNNEL], &flow->tunnel);
+        if (res == ODP_FIT_ERROR) {
+            return ODP_FIT_ERROR;
+        } else if (res == ODP_FIT_PERFECT) {
+            expected_attrs |= UINT64_C(1) << OVS_KEY_ATTR_TUNNEL;
+        }
     }
 
     if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_IN_PORT)) {
@@ -1897,8 +2073,8 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
         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);
+    return parse_l2_5_onward(attrs, present_attrs, out_of_range_attr,
+                             expected_attrs, flow, key, key_len);
 }
 
 /* Returns 'fitness' as a string, for use in debug messages. */
@@ -1920,25 +2096,39 @@ odp_key_fitness_to_string(enum odp_key_fitness fitness)
 }
 
 /* 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.) */
+ * Netlink PID 'pid'.  If 'userdata' is nonnull, adds a userdata attribute
+ * whose contents are the 'userdata_size' bytes at 'userdata' and returns the
+ * offset within 'odp_actions' of the start of the cookie.  (If 'userdata' is
+ * null, then the return value is not meaningful.) */
 size_t
-odp_put_userspace_action(uint32_t pid, const union user_action_cookie *cookie,
+odp_put_userspace_action(uint32_t pid,
+                         const void *userdata, size_t userdata_size,
                          struct ofpbuf *odp_actions)
 {
+    size_t userdata_ofs;
     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) {
+    if (userdata) {
+        userdata_ofs = odp_actions->size + NLA_HDRLEN;
         nl_msg_put_unspec(odp_actions, OVS_USERSPACE_ATTR_USERDATA,
-                          cookie, sizeof *cookie);
+                          userdata, userdata_size);
+    } else {
+        userdata_ofs = 0;
     }
     nl_msg_end_nested(odp_actions, offset);
 
-    return cookie ? odp_actions->size - NLA_ALIGN(sizeof *cookie) : 0;
+    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. */
@@ -1952,17 +2142,33 @@ commit_set_action(struct ofpbuf *odp_actions, enum ovs_key_attr key_type,
     nl_msg_end_nested(odp_actions, offset);
 }
 
-static void
-commit_set_tun_id_action(const struct flow *flow, struct flow *base,
+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)
 {
-    if (base->tunnel.tun_id == flow->tunnel.tun_id) {
+    if (!memcmp(&base->tunnel, &flow->tunnel, sizeof base->tunnel)) {
         return;
     }
-    base->tunnel.tun_id = flow->tunnel.tun_id;
+    memcpy(&base->tunnel, &flow->tunnel, sizeof base->tunnel);
 
-    commit_set_action(odp_actions, OVS_KEY_ATTR_TUN_ID,
-                      &base->tunnel.tun_id, sizeof(base->tunnel.tun_id));
+    /* A valid IPV4_TUNNEL must have non-zero ip_dst. */
+    if (flow->tunnel.ip_dst) {
+        odp_put_tunnel_action(&base->tunnel, odp_actions);
+    }
 }
 
 static void
@@ -2009,6 +2215,50 @@ commit_vlan_action(const struct flow *flow, struct flow *base,
     base->vlan_tci = flow->vlan_tci;
 }
 
+static void
+commit_mpls_action(const struct flow *flow, struct flow *base,
+                   struct ofpbuf *odp_actions)
+{
+    if (flow->mpls_lse == base->mpls_lse &&
+        flow->mpls_depth == base->mpls_depth) {
+        return;
+    }
+
+    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_top_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)
@@ -2084,7 +2334,7 @@ static void
 commit_set_port_action(const struct flow *flow, struct flow *base,
                        struct ofpbuf *odp_actions)
 {
-    if (!base->tp_src && !base->tp_dst) {
+    if (!is_ip_any(base) || (!base->tp_src && !base->tp_dst)) {
         return;
     }
 
@@ -2135,21 +2385,26 @@ commit_set_skb_mark_action(const struct flow *flow, struct flow *base,
     }
     base->skb_mark = flow->skb_mark;
 
-    commit_set_action(odp_actions, OVS_KEY_ATTR_SKB_MARK,
-                      &base->skb_mark, sizeof(base->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. */
+ * 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. */
 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);
+    /* Commiting 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);
     commit_set_priority_action(flow, base, odp_actions);
     commit_set_skb_mark_action(flow, base, odp_actions);
 }