openvswitch: New MPLS actions for layer 2 tunnelling
[sfrench/cifs-2.6.git] / net / openvswitch / flow_netlink.c
index 65c2e3458ff586bac669b8d478ce252f58b1c018..7da4230627f51489707fc199c4333702c344282e 100644 (file)
@@ -79,6 +79,7 @@ static bool actions_may_change_flow(const struct nlattr *actions)
                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;
                }
@@ -3005,6 +3006,7 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
                        [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);
@@ -3072,6 +3074,33 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
                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);
 
@@ -3109,6 +3138,11 @@ static int __ovs_nla_copy_actions(struct net *net, const struct nlattr *attr,
                         * 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)