summaryrefslogtreecommitdiff
path: root/datapath
diff options
context:
space:
mode:
Diffstat (limited to 'datapath')
-rw-r--r--datapath/actions.c71
-rw-r--r--datapath/datapath.c8
-rw-r--r--datapath/linux/compat/include/linux/if_ether.h6
-rw-r--r--datapath/linux/compat/include/linux/netdevice.h26
-rw-r--r--datapath/linux/compat/netdevice.c99
-rw-r--r--datapath/vport-netdev.c10
6 files changed, 188 insertions, 32 deletions
diff --git a/datapath/actions.c b/datapath/actions.c
index 8aec438ba..83b531adf 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -39,20 +39,13 @@ static int make_writable(struct sk_buff *skb, int write_len)
return pskb_expand_head(skb, 0, 0, GFP_ATOMIC);
}
-static int strip_vlan(struct sk_buff *skb)
+/* remove VLAN header from packet and update csum accrodingly. */
+static int __pop_vlan_tci(struct sk_buff *skb, __be16 *current_tci)
{
struct ethhdr *eh;
+ struct vlan_ethhdr *veth;
int err;
- if (vlan_tx_tag_present(skb)) {
- vlan_set_tci(skb, 0);
- return 0;
- }
-
- if (unlikely(skb->protocol != htons(ETH_P_8021Q) ||
- skb->len < VLAN_ETH_HLEN))
- return 0;
-
err = make_writable(skb, VLAN_ETH_HLEN);
if (unlikely(err))
return err;
@@ -61,9 +54,12 @@ static int strip_vlan(struct sk_buff *skb)
skb->csum = csum_sub(skb->csum, csum_partial(skb->data
+ ETH_HLEN, VLAN_HLEN, 0));
+ veth = (struct vlan_ethhdr *) skb->data;
+ *current_tci = veth->h_vlan_TCI;
+
memmove(skb->data + VLAN_HLEN, skb->data, 2 * ETH_ALEN);
- eh = (struct ethhdr *)skb_pull(skb, VLAN_HLEN);
+ eh = (struct ethhdr *)__skb_pull(skb, VLAN_HLEN);
skb->protocol = eh->h_proto;
skb->mac_header += VLAN_HLEN;
@@ -71,21 +67,52 @@ static int strip_vlan(struct sk_buff *skb)
return 0;
}
-static int modify_vlan_tci(struct sk_buff *skb, __be16 tci)
+static int pop_vlan(struct sk_buff *skb)
{
- if (!vlan_tx_tag_present(skb) && skb->protocol == htons(ETH_P_8021Q)) {
- int err;
+ __be16 tci;
+ int err;
- if (unlikely(skb->len < VLAN_ETH_HLEN))
+ if (likely(vlan_tx_tag_present(skb))) {
+ vlan_set_tci(skb, 0);
+ } else {
+ if (unlikely(skb->protocol != htons(ETH_P_8021Q) ||
+ skb->len < VLAN_ETH_HLEN))
return 0;
- err = strip_vlan(skb);
- if (unlikely(err))
+ err = __pop_vlan_tci(skb, &tci);
+ if (err)
return err;
}
+ /* move next vlan tag to hw accel tag */
+ if (likely(skb->protocol != htons(ETH_P_8021Q) ||
+ skb->len < VLAN_ETH_HLEN))
+ return 0;
+
+ err = __pop_vlan_tci(skb, &tci);
+ if (unlikely(err))
+ return err;
__vlan_hwaccel_put_tag(skb, ntohs(tci));
+ return 0;
+}
+
+static int push_vlan(struct sk_buff *skb, __be16 new_tci)
+{
+ if (unlikely(vlan_tx_tag_present(skb))) {
+ u16 current_tag;
+
+ /* push down current VLAN tag */
+ current_tag = vlan_tx_tag_get(skb);
+
+ if (!__vlan_put_tag(skb, current_tag))
+ return -ENOMEM;
+ if (get_ip_summed(skb) == OVS_CSUM_COMPLETE)
+ skb->csum = csum_add(skb->csum, csum_partial(skb->data
+ + ETH_HLEN, VLAN_HLEN, 0));
+
+ }
+ __vlan_hwaccel_put_tag(skb, ntohs(new_tci));
return 0;
}
@@ -270,12 +297,14 @@ static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
OVS_CB(skb)->tun_id = nla_get_be64(a);
break;
- case OVS_ACTION_ATTR_SET_DL_TCI:
- err = modify_vlan_tci(skb, nla_get_be16(a));
+ case OVS_ACTION_ATTR_PUSH_VLAN:
+ err = push_vlan(skb, nla_get_be16(a));
+ if (unlikely(err)) /* skb already freed */
+ return err;
break;
- case OVS_ACTION_ATTR_STRIP_VLAN:
- err = strip_vlan(skb);
+ case OVS_ACTION_ATTR_POP_VLAN:
+ err = pop_vlan(skb);
break;
case OVS_ACTION_ATTR_SET_DL_SRC:
diff --git a/datapath/datapath.c b/datapath/datapath.c
index c1c843da5..454f96cc2 100644
--- a/datapath/datapath.c
+++ b/datapath/datapath.c
@@ -545,8 +545,8 @@ static int validate_actions(const struct nlattr *attr)
static const u32 action_lens[OVS_ACTION_ATTR_MAX + 1] = {
[OVS_ACTION_ATTR_OUTPUT] = 4,
[OVS_ACTION_ATTR_USERSPACE] = 8,
- [OVS_ACTION_ATTR_SET_DL_TCI] = 2,
- [OVS_ACTION_ATTR_STRIP_VLAN] = 0,
+ [OVS_ACTION_ATTR_PUSH_VLAN] = 2,
+ [OVS_ACTION_ATTR_POP_VLAN] = 0,
[OVS_ACTION_ATTR_SET_DL_SRC] = ETH_ALEN,
[OVS_ACTION_ATTR_SET_DL_DST] = ETH_ALEN,
[OVS_ACTION_ATTR_SET_NW_SRC] = 4,
@@ -568,7 +568,7 @@ static int validate_actions(const struct nlattr *attr)
return -EINVAL;
case OVS_ACTION_ATTR_USERSPACE:
- case OVS_ACTION_ATTR_STRIP_VLAN:
+ case OVS_ACTION_ATTR_POP_VLAN:
case OVS_ACTION_ATTR_SET_DL_SRC:
case OVS_ACTION_ATTR_SET_DL_DST:
case OVS_ACTION_ATTR_SET_NW_SRC:
@@ -586,7 +586,7 @@ static int validate_actions(const struct nlattr *attr)
return -EINVAL;
break;
- case OVS_ACTION_ATTR_SET_DL_TCI:
+ case OVS_ACTION_ATTR_PUSH_VLAN:
if (nla_get_be16(a) & htons(VLAN_CFI_MASK))
return -EINVAL;
break;
diff --git a/datapath/linux/compat/include/linux/if_ether.h b/datapath/linux/compat/include/linux/if_ether.h
index b8390e230..41cc05375 100644
--- a/datapath/linux/compat/include/linux/if_ether.h
+++ b/datapath/linux/compat/include/linux/if_ether.h
@@ -10,4 +10,10 @@
#endif /* linux kernel < 2.6.28 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)
+
+#define ETH_P_FCOE 0x8906 /* Fibre Channel over Ethernet */
+
+#endif /* linux kernel < 2.6.30 */
+
#endif
diff --git a/datapath/linux/compat/include/linux/netdevice.h b/datapath/linux/compat/include/linux/netdevice.h
index 04ebd89a7..1ac383128 100644
--- a/datapath/linux/compat/include/linux/netdevice.h
+++ b/datapath/linux/compat/include/linux/netdevice.h
@@ -141,4 +141,30 @@ static inline struct net_device *dev_get_by_index_rcu(struct net *net, int ifind
#define NETIF_F_FSO 0
#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)
+#define NETIF_F_FCOE_CRC (1 << 24) /* FCoE CRC32 */
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
+#define NETIF_F_IPV6_CSUM 16 /* Can checksum TCP/UDP over IPV6 */
+
+#define NETIF_F_V4_CSUM (NETIF_F_GEN_CSUM | NETIF_F_IP_CSUM)
+#define NETIF_F_V6_CSUM (NETIF_F_GEN_CSUM | NETIF_F_IPV6_CSUM)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)
+#define skb_gso_segment rpl_skb_gso_segment
+struct sk_buff *rpl_skb_gso_segment(struct sk_buff *skb, u32 features);
+
+#define netif_skb_features rpl_netif_skb_features
+u32 rpl_netif_skb_features(struct sk_buff *skb);
+
+#define netif_needs_gso rpl_netif_needs_gso
+static inline int rpl_netif_needs_gso(struct sk_buff *skb, int features)
+{
+ return skb_is_gso(skb) && (!skb_gso_ok(skb, features) ||
+ unlikely(skb->ip_summed != CHECKSUM_PARTIAL));
+}
+#endif
+
#endif
diff --git a/datapath/linux/compat/netdevice.c b/datapath/linux/compat/netdevice.c
index 1a6f327ad..91855c47a 100644
--- a/datapath/linux/compat/netdevice.c
+++ b/datapath/linux/compat/netdevice.c
@@ -1,5 +1,6 @@
#include <linux/if_link.h>
#include <linux/netdevice.h>
+#include <linux/if_vlan.h>
/* Linux 2.6.28 introduced dev_get_stats():
* const struct net_device_stats *dev_get_stats(struct net_device *dev);
@@ -47,3 +48,101 @@ struct rtnl_link_stats64 *dev_get_stats(struct net_device *dev,
return storage;
}
#endif /* kernel version < 2.6.36 */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)
+static bool can_checksum_protocol(unsigned long features, __be16 protocol)
+{
+ return ((features & NETIF_F_GEN_CSUM) ||
+ ((features & NETIF_F_V4_CSUM) &&
+ protocol == htons(ETH_P_IP)) ||
+ ((features & NETIF_F_V6_CSUM) &&
+ protocol == htons(ETH_P_IPV6)) ||
+ ((features & NETIF_F_FCOE_CRC) &&
+ protocol == htons(ETH_P_FCOE)));
+}
+
+static inline int illegal_highdma(struct net_device *dev, struct sk_buff *skb)
+{
+#ifdef CONFIG_HIGHMEM
+ int i;
+
+ if (dev->features & NETIF_F_HIGHDMA)
+ return 0;
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
+ if (PageHighMem(skb_shinfo(skb)->frags[i].page))
+ return 1;
+
+#endif
+ return 0;
+}
+
+static u32 harmonize_features(struct sk_buff *skb, __be16 protocol, u32 features)
+{
+ if (!can_checksum_protocol(features, protocol)) {
+ features &= ~NETIF_F_ALL_CSUM;
+ features &= ~NETIF_F_SG;
+ } else if (illegal_highdma(skb->dev, skb)) {
+ features &= ~NETIF_F_SG;
+ }
+
+ return features;
+}
+
+u32 rpl_netif_skb_features(struct sk_buff *skb)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+ unsigned long vlan_features = 0;
+#else
+ unsigned long vlan_features = skb->dev->vlan_features;
+#endif /* kernel version < 2.6.26 */
+
+ __be16 protocol = skb->protocol;
+ u32 features = skb->dev->features;
+
+ if (protocol == htons(ETH_P_8021Q)) {
+ struct vlan_ethhdr *veh = (struct vlan_ethhdr *)skb->data;
+ protocol = veh->h_vlan_encapsulated_proto;
+ } else if (!vlan_tx_tag_present(skb)) {
+ return harmonize_features(skb, protocol, features);
+ }
+
+ features &= (vlan_features | NETIF_F_HW_VLAN_TX);
+
+ if (protocol != htons(ETH_P_8021Q)) {
+ return harmonize_features(skb, protocol, features);
+ } else {
+ features &= NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_FRAGLIST |
+ NETIF_F_GEN_CSUM | NETIF_F_HW_VLAN_TX;
+ return harmonize_features(skb, protocol, features);
+ }
+}
+
+struct sk_buff *rpl_skb_gso_segment(struct sk_buff *skb, u32 features)
+{
+ int vlan_depth = ETH_HLEN;
+ __be16 type = skb->protocol;
+ __be16 skb_proto;
+ struct sk_buff *skb_gso;
+
+ while (type == htons(ETH_P_8021Q)) {
+ struct vlan_hdr *vh;
+
+ if (unlikely(!pskb_may_pull(skb, vlan_depth + VLAN_HLEN)))
+ return ERR_PTR(-EINVAL);
+
+ vh = (struct vlan_hdr *)(skb->data + vlan_depth);
+ type = vh->h_vlan_encapsulated_proto;
+ vlan_depth += VLAN_HLEN;
+ }
+
+ /* this hack needed to get regular skb_gso_segment() */
+#undef skb_gso_segment
+ skb_proto = skb->protocol;
+ skb->protocol = type;
+
+ skb_gso = skb_gso_segment(skb, features);
+ skb->protocol = skb_proto;
+ return skb_gso;
+}
+#endif /* kernel version < 2.6.38 */
diff --git a/datapath/vport-netdev.c b/datapath/vport-netdev.c
index dbc8db2ca..ee39ee372 100644
--- a/datapath/vport-netdev.c
+++ b/datapath/vport-netdev.c
@@ -333,19 +333,15 @@ static int netdev_send(struct vport *vport, struct sk_buff *skb)
forward_ip_summed(skb, true);
if (vlan_tx_tag_present(skb) && !dev_supports_vlan_tx(skb->dev)) {
- int features = 0;
+ int features;
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
- features = skb->dev->features & skb->dev->vlan_features;
-#endif
+ features = netif_skb_features(skb);
if (!vlan_tso)
features &= ~(NETIF_F_TSO | NETIF_F_TSO6 |
NETIF_F_UFO | NETIF_F_FSO);
- if (skb_is_gso(skb) &&
- (!skb_gso_ok(skb, features) ||
- unlikely(skb->ip_summed != CHECKSUM_PARTIAL))) {
+ if (netif_needs_gso(skb, features)) {
struct sk_buff *nskb;
nskb = skb_gso_segment(skb, features);