/*
* Distributed under the terms of the GNU GPL version 2.
- * Copyright (c) 2007, 2008, 2009 Nicira Networks.
+ * Copyright (c) 2007, 2008, 2009, 2010 Nicira Networks.
*
* Significant portions of this file may be copied from parts of the Linux
* kernel, by Linus Torvalds and others.
#include "actions.h"
#include "openvswitch/datapath-protocol.h"
-struct sk_buff *
-make_writable(struct sk_buff *skb, gfp_t gfp)
+static struct sk_buff *
+make_writable(struct sk_buff *skb, unsigned min_headroom, gfp_t gfp)
{
if (skb_shared(skb) || skb_cloned(skb)) {
- struct sk_buff *nskb = skb_copy(skb, gfp);
+ struct sk_buff *nskb;
+ unsigned headroom = max(min_headroom, skb_headroom(skb));
+
+ nskb = skb_copy_expand(skb, headroom, skb_tailroom(skb), gfp);
if (nskb) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+ /* Before 2.6.24 these fields were not copied when
+ * doing an skb_copy_expand. */
+ nskb->ip_summed = skb->ip_summed;
+ nskb->csum = skb->csum;
+#endif
+#if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID)
+ /* These fields are copied in skb_clone but not in
+ * skb_copy or related functions. We need to manually
+ * copy them over here. */
+ nskb->proto_data_valid = skb->proto_data_valid;
+ nskb->proto_csum_blank = skb->proto_csum_blank;
+#endif
kfree_skb(skb);
return nskb;
}
mask = VLAN_VID_MASK;
key->dl_vlan = htons(tci & mask);
} else {
- tci = a->vlan_pcp.vlan_pcp << 13;
+ tci = a->vlan_pcp.vlan_pcp << VLAN_PCP_SHIFT;
mask = VLAN_PCP_MASK;
}
- skb = make_writable(skb, gfp);
+ skb = make_writable(skb, VLAN_HLEN, gfp);
if (!skb)
return ERR_PTR(-ENOMEM);
static struct sk_buff *strip_vlan(struct sk_buff *skb,
struct odp_flow_key *key, gfp_t gfp)
{
- skb = make_writable(skb, gfp);
+ skb = make_writable(skb, 0, gfp);
if (skb) {
vlan_pull_tag(skb);
key->dl_vlan = htons(ODP_VLAN_NONE);
const struct odp_action_dl_addr *a,
gfp_t gfp)
{
- skb = make_writable(skb, gfp);
+ skb = make_writable(skb, 0, gfp);
if (skb) {
struct ethhdr *eh = eth_hdr(skb);
memcpy(a->type == ODPAT_SET_DL_SRC ? eh->h_source : eh->h_dest,
if (key->dl_type != htons(ETH_P_IP))
return skb;
- skb = make_writable(skb, gfp);
+ skb = make_writable(skb, 0, gfp);
if (skb) {
struct iphdr *nh = ip_hdr(skb);
u32 *f = a->type == ODPAT_SET_NW_SRC ? &nh->saddr : &nh->daddr;
else
return skb;
- skb = make_writable(skb, gfp);
+ skb = make_writable(skb, 0, gfp);
if (skb) {
struct udphdr *th = udp_hdr(skb);
u16 *f = a->type == ODPAT_SET_TP_SRC ? &th->source : &th->dest;
u16 old = *f;
u16 new = a->tp_port;
- update_csum((u16*)((u8*)skb->data + check_ofs),
- skb, old, new, 1);
+ update_csum((u16*)(skb_transport_header(skb) + check_ofs),
+ skb, old, new, 1);
*f = new;
}
return skb;
dev = skb->dev = p->dev;
if (is_dp_dev(dev))
dp_dev_recv(dev, skb);
- else
+ else
dp_xmit_skb(skb);
return;
return dp_output_control(dp, skb, _ODPL_ACTION_NR, arg);
}
+/* Send a copy of this packet up to the sFlow agent, along with extra
+ * information about what happened to it. */
+static void sflow_sample(struct datapath *dp, struct sk_buff *skb,
+ const union odp_action *a, int n_actions,
+ gfp_t gfp, struct net_bridge_port *nbp)
+{
+ struct odp_sflow_sample_header *hdr;
+ unsigned int actlen = n_actions * sizeof(union odp_action);
+ unsigned int hdrlen = sizeof(struct odp_sflow_sample_header);
+ struct sk_buff *nskb;
+
+ nskb = skb_copy_expand(skb, actlen + hdrlen, 0, gfp);
+ if (!nskb)
+ return;
+
+ memcpy(__skb_push(nskb, actlen), a, actlen);
+ hdr = (struct odp_sflow_sample_header*)__skb_push(nskb, hdrlen);
+ hdr->n_actions = n_actions;
+ hdr->sample_pool = atomic_read(&nbp->sflow_pool);
+ dp_output_control(dp, nskb, _ODPL_SFLOW_NR, 0);
+}
+
/* Execute a list of actions against 'skb'. */
int execute_actions(struct datapath *dp, struct sk_buff *skb,
struct odp_flow_key *key,
* is slightly obscure just to avoid that. */
int prev_port = -1;
int err;
+
+ if (dp->sflow_probability) {
+ struct net_bridge_port *p = skb->dev->br_port;
+ if (p) {
+ atomic_inc(&p->sflow_pool);
+ if (dp->sflow_probability == UINT_MAX ||
+ net_random() < dp->sflow_probability)
+ sflow_sample(dp, skb, a, n_actions, gfp, p);
+ }
+ }
+
for (; n_actions > 0; a++, n_actions--) {
WARN_ON_ONCE(skb_shared(skb));
if (prev_port != -1) {