__be16 mpls_ethertype; /* Either %ETH_P_MPLS_UC or %ETH_P_MPLS_MC */
 };
 
+/**
+ * struct ovs_action_add_mpls - %OVS_ACTION_ATTR_ADD_MPLS action
+ * argument.
+ * @mpls_lse: MPLS label stack entry to push.
+ * @mpls_ethertype: Ethertype to set in the encapsulating ethernet frame.
+ * @tun_flags: MPLS tunnel attributes.
+ *
+ * The only values @mpls_ethertype should ever be given are %ETH_P_MPLS_UC and
+ * %ETH_P_MPLS_MC, indicating MPLS unicast or multicast. Other are rejected.
+ */
+struct ovs_action_add_mpls {
+       __be32 mpls_lse;
+       __be16 mpls_ethertype; /* Either %ETH_P_MPLS_UC or %ETH_P_MPLS_MC */
+       __u16 tun_flags;
+};
+
+#define OVS_MPLS_L3_TUNNEL_FLAG_MASK  (1 << 0) /* Flag to specify the place of
+                                               * insertion of MPLS header.
+                                               * When false, the MPLS header
+                                               * will be inserted at the start
+                                               * of the packet.
+                                               * When true, the MPLS header
+                                               * will be inserted at the start
+                                               * of the l3 header.
+                                               */
+
 /**
  * struct ovs_action_push_vlan - %OVS_ACTION_ATTR_PUSH_VLAN action argument.
  * @vlan_tpid: Tag protocol identifier (TPID) to push.
  * @OVS_ACTION_ATTR_CHECK_PKT_LEN: Check the packet length and execute a set
  * of actions if greater than the specified packet length, else execute
  * another set of actions.
+ * @OVS_ACTION_ATTR_ADD_MPLS: Push a new MPLS label stack entry at the
+ * start of the packet or at the start of the l3 header depending on the value
+ * of l3 tunnel flag in the tun_flags field of OVS_ACTION_ATTR_ADD_MPLS
+ * argument.
  *
  * Only a single header can be set with a single %OVS_ACTION_ATTR_SET.  Not all
  * fields within a header are modifiable, e.g. the IPv4 protocol and fragment
        OVS_ACTION_ATTR_METER,        /* u32 meter ID. */
        OVS_ACTION_ATTR_CLONE,        /* Nested OVS_CLONE_ATTR_*.  */
        OVS_ACTION_ATTR_CHECK_PKT_LEN, /* Nested OVS_CHECK_PKT_LEN_ATTR_*. */
+       OVS_ACTION_ATTR_ADD_MPLS,     /* struct ovs_action_add_mpls. */
 
        __OVS_ACTION_ATTR_MAX,        /* Nothing past this will be accepted
                                       * from userspace. */
 
                              const struct nlattr *attr, int len);
 
 static int push_mpls(struct sk_buff *skb, struct sw_flow_key *key,
-                    const struct ovs_action_push_mpls *mpls)
+                    __be32 mpls_lse, __be16 mpls_ethertype, __u16 mac_len)
 {
        int err;
 
-       err = skb_mpls_push(skb, mpls->mpls_lse, mpls->mpls_ethertype,
-                           skb->mac_len,
-                           ovs_key_mac_proto(key) == MAC_PROTO_ETHERNET);
+       err = skb_mpls_push(skb, mpls_lse, mpls_ethertype, mac_len, !!mac_len);
        if (err)
                return err;
 
+       if (!mac_len)
+               key->mac_proto = MAC_PROTO_NONE;
+
        invalidate_flow_key(key);
        return 0;
 }
        if (err)
                return err;
 
+       if (ethertype == htons(ETH_P_TEB))
+               key->mac_proto = MAC_PROTO_ETHERNET;
+
        invalidate_flow_key(key);
        return 0;
 }
                        execute_hash(skb, key, a);
                        break;
 
-               case OVS_ACTION_ATTR_PUSH_MPLS:
-                       err = push_mpls(skb, key, nla_data(a));
+               case OVS_ACTION_ATTR_PUSH_MPLS: {
+                       struct ovs_action_push_mpls *mpls = nla_data(a);
+
+                       err = push_mpls(skb, key, mpls->mpls_lse,
+                                       mpls->mpls_ethertype, skb->mac_len);
                        break;
+               }
+               case OVS_ACTION_ATTR_ADD_MPLS: {
+                       struct ovs_action_add_mpls *mpls = nla_data(a);
+                       __u16 mac_len = 0;
+
+                       if (mpls->tun_flags & OVS_MPLS_L3_TUNNEL_FLAG_MASK)
+                               mac_len = skb->mac_len;
 
+                       err = push_mpls(skb, key, mpls->mpls_lse,
+                                       mpls->mpls_ethertype, mac_len);
+                       break;
+               }
                case OVS_ACTION_ATTR_POP_MPLS:
                        err = pop_mpls(skb, key, nla_get_be16(a));
                        break;
 
                case OVS_ACTION_ATTR_SET_MASKED:
                case OVS_ACTION_ATTR_METER:
                case OVS_ACTION_ATTR_CHECK_PKT_LEN:
+               case OVS_ACTION_ATTR_ADD_MPLS:
                default:
                        return true;
                }
                        [OVS_ACTION_ATTR_METER] = sizeof(u32),
                        [OVS_ACTION_ATTR_CLONE] = (u32)-1,
                        [OVS_ACTION_ATTR_CHECK_PKT_LEN] = (u32)-1,
+                       [OVS_ACTION_ATTR_ADD_MPLS] = sizeof(struct ovs_action_add_mpls),
                };
                const struct ovs_action_push_vlan *vlan;
                int type = nla_type(a);
                case OVS_ACTION_ATTR_RECIRC:
                        break;
 
+               case OVS_ACTION_ATTR_ADD_MPLS: {
+                       const struct ovs_action_add_mpls *mpls = nla_data(a);
+
+                       if (!eth_p_mpls(mpls->mpls_ethertype))
+                               return -EINVAL;
+
+                       if (mpls->tun_flags & OVS_MPLS_L3_TUNNEL_FLAG_MASK) {
+                               if (vlan_tci & htons(VLAN_CFI_MASK) ||
+                                   (eth_type != htons(ETH_P_IP) &&
+                                    eth_type != htons(ETH_P_IPV6) &&
+                                    eth_type != htons(ETH_P_ARP) &&
+                                    eth_type != htons(ETH_P_RARP) &&
+                                    !eth_p_mpls(eth_type)))
+                                       return -EINVAL;
+                               mpls_label_count++;
+                       } else {
+                               if (mac_proto == MAC_PROTO_ETHERNET) {
+                                       mpls_label_count = 1;
+                                       mac_proto = MAC_PROTO_NONE;
+                               } else {
+                                       mpls_label_count++;
+                               }
+                       }
+                       eth_type = mpls->mpls_ethertype;
+                       break;
+               }
+
                case OVS_ACTION_ATTR_PUSH_MPLS: {
                        const struct ovs_action_push_mpls *mpls = nla_data(a);
 
                         * recirculation.
                         */
                        proto = nla_get_be16(a);
+
+                       if (proto == htons(ETH_P_TEB) &&
+                           mac_proto != MAC_PROTO_NONE)
+                               return -EINVAL;
+
                        mpls_label_count--;
 
                        if (!eth_p_mpls(proto) || !mpls_label_count)