diff options
| author | Pravin Shelar <pshelar@nicira.com> | 2011-09-09 18:13:26 -0700 |
|---|---|---|
| committer | Pravin Shelar <pshelar@nicira.com> | 2011-09-09 18:13:26 -0700 |
| commit | d9065a90b6b955aa38586c952e8804ca7a22547e (patch) | |
| tree | 76f07c99c491ccbfc247fc52961e05b56ec53b04 /datapath | |
| parent | f37c91c76785e335e9d89aa31a7f0ab290223193 (diff) | |
| download | openvswitch-d9065a90b6b955aa38586c952e8804ca7a22547e.tar.gz | |
datapath: VLAN actions should use push/pop semantics
Currently the kernel vlan actions mirror those used by OpenFlow 1.0.
i.e. MODIFY and STRIP. More flexible approach is to have an action to
push a tag and pop a tag off, so that it can handle multiple levels of vlan
tags. Plus it aligns with newer version of OpenFlow.
As this patch replaces MODIFY with PUSH semantic, action
mapping done in userpace is fixed accordingly.
GSO handling for multiple levels of vlan tags is also added as
Jesse suggested before.
Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
Acked-by: Jesse Gross <jesse@nicira.com>
Diffstat (limited to 'datapath')
| -rw-r--r-- | datapath/actions.c | 71 | ||||
| -rw-r--r-- | datapath/datapath.c | 8 | ||||
| -rw-r--r-- | datapath/linux/compat/include/linux/if_ether.h | 6 | ||||
| -rw-r--r-- | datapath/linux/compat/include/linux/netdevice.h | 26 | ||||
| -rw-r--r-- | datapath/linux/compat/netdevice.c | 99 | ||||
| -rw-r--r-- | datapath/vport-netdev.c | 10 |
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); |
