openvswitch: avoid deferred execution of recirc actions
[cascardo/linux.git] / net / openvswitch / actions.c
index 1ecbd77..863e992 100644 (file)
@@ -71,6 +71,8 @@ struct ovs_frag_data {
 static DEFINE_PER_CPU(struct ovs_frag_data, ovs_frag_data_storage);
 
 #define DEFERRED_ACTION_FIFO_SIZE 10
+#define OVS_RECURSION_LIMIT 5
+#define OVS_DEFERRED_ACTION_THRESHOLD (OVS_RECURSION_LIMIT - 2)
 struct action_fifo {
        int head;
        int tail;
@@ -78,7 +80,12 @@ struct action_fifo {
        struct deferred_action fifo[DEFERRED_ACTION_FIFO_SIZE];
 };
 
+struct recirc_keys {
+       struct sw_flow_key key[OVS_DEFERRED_ACTION_THRESHOLD];
+};
+
 static struct action_fifo __percpu *action_fifos;
+static struct recirc_keys __percpu *recirc_keys;
 static DEFINE_PER_CPU(int, exec_actions_level);
 
 static void action_fifo_init(struct action_fifo *fifo)
@@ -162,10 +169,16 @@ static int push_mpls(struct sk_buff *skb, struct sw_flow_key *key,
        if (skb_cow_head(skb, MPLS_HLEN) < 0)
                return -ENOMEM;
 
+       if (!skb->inner_protocol) {
+               skb_set_inner_network_header(skb, skb->mac_len);
+               skb_set_inner_protocol(skb, skb->protocol);
+       }
+
        skb_push(skb, MPLS_HLEN);
        memmove(skb_mac_header(skb) - MPLS_HLEN, skb_mac_header(skb),
                skb->mac_len);
        skb_reset_mac_header(skb);
+       skb_set_network_header(skb, skb->mac_len);
 
        new_mpls_lse = (__be32 *)skb_mpls_header(skb);
        *new_mpls_lse = mpls->mpls_lse;
@@ -173,8 +186,6 @@ static int push_mpls(struct sk_buff *skb, struct sw_flow_key *key,
        skb_postpush_rcsum(skb, new_mpls_lse, MPLS_HLEN);
 
        update_ethertype(skb, eth_hdr(skb), mpls->mpls_ethertype);
-       if (!skb->inner_protocol)
-               skb_set_inner_protocol(skb, skb->protocol);
        skb->protocol = mpls->mpls_ethertype;
 
        invalidate_flow_key(key);
@@ -198,6 +209,7 @@ static int pop_mpls(struct sk_buff *skb, struct sw_flow_key *key,
 
        __skb_pull(skb, MPLS_HLEN);
        skb_reset_mac_header(skb);
+       skb_set_network_header(skb, skb->mac_len);
 
        /* skb_mpls_header() is used to locate the ethertype
         * field correctly in the presence of VLAN tags.
@@ -241,20 +253,24 @@ static int pop_vlan(struct sk_buff *skb, struct sw_flow_key *key)
        int err;
 
        err = skb_vlan_pop(skb);
-       if (skb_vlan_tag_present(skb))
+       if (skb_vlan_tag_present(skb)) {
                invalidate_flow_key(key);
-       else
-               key->eth.tci = 0;
+       } else {
+               key->eth.vlan.tci = 0;
+               key->eth.vlan.tpid = 0;
+       }
        return err;
 }
 
 static int push_vlan(struct sk_buff *skb, struct sw_flow_key *key,
                     const struct ovs_action_push_vlan *vlan)
 {
-       if (skb_vlan_tag_present(skb))
+       if (skb_vlan_tag_present(skb)) {
                invalidate_flow_key(key);
-       else
-               key->eth.tci = vlan->vlan_tci;
+       } else {
+               key->eth.vlan.tci = vlan->vlan_tci;
+               key->eth.vlan.tpid = vlan->vlan_tpid;
+       }
        return skb_vlan_push(skb, vlan->vlan_tpid,
                             ntohs(vlan->vlan_tci) & ~VLAN_TAG_PRESENT);
 }
@@ -1011,6 +1027,7 @@ static int execute_recirc(struct datapath *dp, struct sk_buff *skb,
                          const struct nlattr *a, int rem)
 {
        struct deferred_action *da;
+       int level;
 
        if (!is_flow_key_valid(key)) {
                int err;
@@ -1034,6 +1051,18 @@ static int execute_recirc(struct datapath *dp, struct sk_buff *skb,
                        return 0;
        }
 
+       level = this_cpu_read(exec_actions_level);
+       if (level <= OVS_DEFERRED_ACTION_THRESHOLD) {
+               struct recirc_keys *rks = this_cpu_ptr(recirc_keys);
+               struct sw_flow_key *recirc_key = &rks->key[level - 1];
+
+               *recirc_key = *key;
+               recirc_key->recirc_id = nla_get_u32(a);
+               ovs_dp_process_packet(skb, recirc_key);
+
+               return 0;
+       }
+
        da = add_deferred_actions(skb, key, NULL);
        if (da) {
                da->pkt_key.recirc_id = nla_get_u32(a);
@@ -1200,11 +1229,10 @@ int ovs_execute_actions(struct datapath *dp, struct sk_buff *skb,
                        const struct sw_flow_actions *acts,
                        struct sw_flow_key *key)
 {
-       static const int ovs_recursion_limit = 5;
        int err, level;
 
        level = __this_cpu_inc_return(exec_actions_level);
-       if (unlikely(level > ovs_recursion_limit)) {
+       if (unlikely(level > OVS_RECURSION_LIMIT)) {
                net_crit_ratelimited("ovs: recursion limit reached on datapath %s, probable configuration error\n",
                                     ovs_dp_name(dp));
                kfree_skb(skb);
@@ -1229,10 +1257,17 @@ int action_fifos_init(void)
        if (!action_fifos)
                return -ENOMEM;
 
+       recirc_keys = alloc_percpu(struct recirc_keys);
+       if (!recirc_keys) {
+               free_percpu(action_fifos);
+               return -ENOMEM;
+       }
+
        return 0;
 }
 
 void action_fifos_exit(void)
 {
        free_percpu(action_fifos);
+       free_percpu(recirc_keys);
 }