summaryrefslogtreecommitdiff
path: root/net/openvswitch/actions.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/openvswitch/actions.c')
-rw-r--r--net/openvswitch/actions.c61
1 files changed, 48 insertions, 13 deletions
diff --git a/net/openvswitch/actions.c b/net/openvswitch/actions.c
index 2c77e7b1a913..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;
@@ -134,8 +139,8 @@ static int set_eth_addr(struct sk_buff *skb,
skb_postpull_rcsum(skb, eth_hdr(skb), ETH_ALEN * 2);
- memcpy(eth_hdr(skb)->h_source, eth_key->eth_src, ETH_ALEN);
- memcpy(eth_hdr(skb)->h_dest, eth_key->eth_dst, ETH_ALEN);
+ ether_addr_copy(eth_hdr(skb)->h_source, eth_key->eth_src);
+ ether_addr_copy(eth_hdr(skb)->h_dest, eth_key->eth_dst);
ovs_skb_postpush_rcsum(skb, eth_hdr(skb), ETH_ALEN * 2);
@@ -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
@@ -551,6 +588,8 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
case OVS_ACTION_ATTR_SAMPLE:
err = sample(dp, skb, a);
+ if (unlikely(err)) /* skb already freed. */
+ return err;
break;
}
@@ -560,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;
@@ -577,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);
}