diff options
Diffstat (limited to 'net/openvswitch/actions.c')
| -rw-r--r-- | net/openvswitch/actions.c | 55 | 
1 files changed, 44 insertions, 11 deletions
diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c index e70d8b18e962..5231652a95d9 100644 --- a/net/openvswitch/actions.c +++ b/net/openvswitch/actions.c @@ -38,10 +38,13 @@  #include "vport.h"  static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, -			const struct nlattr *attr, int len, bool keep_skb); +			      const struct nlattr *attr, int len);  static int make_writable(struct sk_buff *skb, int write_len)  { +	if (!pskb_may_pull(skb, write_len)) +		return -ENOMEM; +  	if (!skb_cloned(skb) || skb_clone_writable(skb, write_len))  		return 0; @@ -70,6 +73,8 @@ static int __pop_vlan_tci(struct sk_buff *skb, __be16 *current_tci)  	vlan_set_encap_proto(skb, vhdr);  	skb->mac_header += VLAN_HLEN; +	if (skb_network_offset(skb) < ETH_HLEN) +		skb_set_network_header(skb, ETH_HLEN);  	skb_reset_mac_len(skb);  	return 0; @@ -434,11 +439,17 @@ static int output_userspace(struct datapath *dp, struct sk_buff *skb,  	return ovs_dp_upcall(dp, skb, &upcall);  } +static bool last_action(const struct nlattr *a, int rem) +{ +	return a->nla_len == rem; +} +  static int sample(struct datapath *dp, struct sk_buff *skb,  		  const struct nlattr *attr)  {  	const struct nlattr *acts_list = NULL;  	const struct nlattr *a; +	struct sk_buff *sample_skb;  	int rem;  	for (a = nla_data(attr), rem = nla_len(attr); rem > 0; @@ -455,8 +466,34 @@ static int sample(struct datapath *dp, struct sk_buff *skb,  		}  	} -	return do_execute_actions(dp, skb, nla_data(acts_list), -						 nla_len(acts_list), true); +	rem = nla_len(acts_list); +	a = nla_data(acts_list); + +	/* Actions list is either empty or only contains a single user-space +	 * action, the latter being a special case as it is the only known +	 * usage of the sample action. +	 * In these special cases don't clone the skb as there are no +	 * side-effects in the nested actions. +	 * Otherwise, clone in case the nested actions have side effects. +	 */ +	if (likely(rem == 0 || (nla_type(a) == OVS_ACTION_ATTR_USERSPACE && +				last_action(a, rem)))) { +		sample_skb = skb; +		skb_get(skb); +	} else { +		sample_skb = skb_clone(skb, GFP_ATOMIC); +		if (!sample_skb) /* Skip sample action when out of memory. */ +			return 0; +	} + +	/* Note that do_execute_actions() never consumes skb. +	 * In the case where skb has been cloned above it is the clone that +	 * is consumed.  Otherwise the skb_get(skb) call prevents +	 * consumption by do_execute_actions(). Thus, it is safe to simply +	 * return the error code and let the caller (also +	 * do_execute_actions()) free skb on error. +	 */ +	return do_execute_actions(dp, sample_skb, a, rem);  }  static int execute_set_action(struct sk_buff *skb, @@ -507,7 +544,7 @@ static int execute_set_action(struct sk_buff *skb,  /* Execute a list of actions against 'skb'. */  static int do_execute_actions(struct datapath *dp, struct sk_buff *skb, -			const struct nlattr *attr, int len, bool keep_skb) +			      const struct nlattr *attr, int len)  {  	/* Every output action needs a separate clone of 'skb', but the common  	 * case is just a single output action, so that doing a clone and @@ -562,12 +599,9 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,  		}  	} -	if (prev_port != -1) { -		if (keep_skb) -			skb = skb_clone(skb, GFP_ATOMIC); - +	if (prev_port != -1)  		do_output(dp, skb, prev_port); -	} else if (!keep_skb) +	else  		consume_skb(skb);  	return 0; @@ -579,6 +613,5 @@ int ovs_execute_actions(struct datapath *dp, struct sk_buff *skb)  	struct sw_flow_actions *acts = rcu_dereference(OVS_CB(skb)->flow->sf_acts);  	OVS_CB(skb)->tun_key = NULL; -	return do_execute_actions(dp, skb, acts->actions, -					 acts->actions_len, false); +	return do_execute_actions(dp, skb, acts->actions, acts->actions_len);  }  |