datapath: define compat __skb_gso_segment()
[cascardo/ovs.git] / datapath / linux / compat / gso.c
index 3cadde9..2c19b58 100644 (file)
  * 02110-1301, USA
  */
 
+#include <linux/version.h>
+
 #include <linux/module.h>
 #include <linux/if.h>
 #include <linux/if_tunnel.h>
+#include <linux/if_vlan.h>
 #include <linux/icmp.h>
 #include <linux/in.h>
 #include <linux/ip.h>
 
 #include <net/gre.h>
 #include <net/icmp.h>
+#include <net/mpls.h>
 #include <net/protocol.h>
 #include <net/route.h>
 #include <net/xfrm.h>
 
 #include "gso.h"
+#include "vlan.h"
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) && \
+       !defined(HAVE_VLAN_BUG_WORKAROUND)
+#include <linux/module.h>
+
+static int vlan_tso __read_mostly;
+module_param(vlan_tso, int, 0644);
+MODULE_PARM_DESC(vlan_tso, "Enable TSO for VLAN packets");
+#else
+#define vlan_tso true
+#endif
 
-static __be16 skb_network_protocol(struct sk_buff *skb)
+#ifdef OVS_USE_COMPAT_GSO_SEGMENTATION
+static bool dev_supports_vlan_tx(struct net_device *dev)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,37)
+       return true;
+#elif defined(HAVE_VLAN_BUG_WORKAROUND)
+       return dev->features & NETIF_F_HW_VLAN_TX;
+#else
+       /* Assume that the driver is buggy. */
+       return false;
+#endif
+}
+
+/* Strictly this is not needed and will be optimised out
+ * as this code is guarded by if LINUX_VERSION_CODE < KERNEL_VERSION(3,19,0).
+ * It is here to make things explicit should the compatibility
+ * code be extended in some way prior extending its life-span
+ * beyond v3.19.
+ */
+static bool supports_mpls_gso(void)
+{
+/* MPLS GSO was introduced in v3.11, however it was not correctly
+ * activated using mpls_features until v3.19. */
+#ifdef OVS_USE_COMPAT_GSO_SEGMENTATION
+       return true;
+#else
+       return false;
+#endif
+}
+
+int rpl_dev_queue_xmit(struct sk_buff *skb)
+{
+#undef dev_queue_xmit
+       int err = -ENOMEM;
+       bool vlan, mpls;
+
+       vlan = mpls = false;
+
+       /* Avoid traversing any VLAN tags that are present to determine if
+        * the ethtype is MPLS. Instead compare the mac_len (end of L2) and
+        * skb_network_offset() (beginning of L3) whose inequality will
+        * indicate the presence of an MPLS label stack. */
+       if (skb->mac_len != skb_network_offset(skb) && !supports_mpls_gso())
+               mpls = true;
+
+       if (skb_vlan_tag_present(skb) && !dev_supports_vlan_tx(skb->dev))
+               vlan = true;
+
+       if (vlan || mpls) {
+               int features;
+
+               features = netif_skb_features(skb);
+
+               if (vlan) {
+                       if (!vlan_tso)
+                               features &= ~(NETIF_F_TSO | NETIF_F_TSO6 |
+                                             NETIF_F_UFO | NETIF_F_FSO);
+
+                       skb = vlan_insert_tag_set_proto(skb, skb->vlan_proto,
+                                                       skb_vlan_tag_get(skb));
+                       if (unlikely(!skb))
+                               return err;
+                       vlan_set_tci(skb, 0);
+               }
+
+               /* As of v3.11 the kernel provides an mpls_features field in
+                * struct net_device which allows devices to advertise which
+                * features its supports for MPLS. This value defaults to
+                * NETIF_F_SG and as of v3.19.
+                *
+                * This compatibility code is intended for kernels older
+                * than v3.19 that do not support MPLS GSO and do not
+                * use mpls_features. Thus this code uses NETIF_F_SG
+                * directly in place of mpls_features.
+                */
+               if (mpls)
+                       features &= NETIF_F_SG;
+
+               if (netif_needs_gso(skb->dev, skb, features)) {
+                       struct sk_buff *nskb;
+
+                       nskb = skb_gso_segment(skb, features);
+                       if (!nskb) {
+                               if (unlikely(skb_cloned(skb) &&
+                                   pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
+                                       goto drop;
+
+                               skb_shinfo(skb)->gso_type &= ~SKB_GSO_DODGY;
+                               goto xmit;
+                       }
+
+                       if (IS_ERR(nskb)) {
+                               err = PTR_ERR(nskb);
+                               goto drop;
+                       }
+                       consume_skb(skb);
+                       skb = nskb;
+
+                       do {
+                               nskb = skb->next;
+                               skb->next = NULL;
+                               err = dev_queue_xmit(skb);
+                               skb = nskb;
+                       } while (skb);
+
+                       return err;
+               }
+       }
+xmit:
+       return dev_queue_xmit(skb);
+
+drop:
+       kfree_skb(skb);
+       return err;
+}
+EXPORT_SYMBOL_GPL(rpl_dev_queue_xmit);
+#endif /* OVS_USE_COMPAT_GSO_SEGMENTATION */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,18,0)
+static __be16 __skb_network_protocol(struct sk_buff *skb)
 {
        __be16 type = skb->protocol;
        int vlan_depth = ETH_HLEN;
@@ -52,6 +187,9 @@ static __be16 skb_network_protocol(struct sk_buff *skb)
                vlan_depth += VLAN_HLEN;
        }
 
+       if (eth_p_mpls(type))
+               type = ovs_skb_get_inner_protocol(skb);
+
        return type;
 }
 
@@ -65,10 +203,11 @@ static struct sk_buff *tnl_skb_gso_segment(struct sk_buff *skb,
        struct sk_buff *skb1 = skb;
        struct sk_buff *segs;
        __be16 proto = skb->protocol;
+       char cb[sizeof(skb->cb)];
 
        /* setup whole inner packet to get protocol. */
        __skb_pull(skb, mac_offset);
-       skb->protocol = skb_network_protocol(skb);
+       skb->protocol = __skb_network_protocol(skb);
 
        /* setup l3 packet to gso, to get around segmentation bug on older kernel.*/
        __skb_pull(skb, (pkt_hlen - mac_offset));
@@ -76,7 +215,21 @@ static struct sk_buff *tnl_skb_gso_segment(struct sk_buff *skb,
        skb_reset_network_header(skb);
        skb_reset_transport_header(skb);
 
+       /* From 3.9 kernel skb->cb is used by skb gso. Therefore
+        * make copy of it to restore it back. */
+       memcpy(cb, skb->cb, sizeof(cb));
+
+       /* We are handling offloads by segmenting l3 packet, so
+        * no need to call OVS compat segmentation function. */
+
+#ifdef HAVE___SKB_GSO_SEGMENT
+#undef __skb_gso_segment
        segs = __skb_gso_segment(skb, 0, tx_path);
+#else
+#undef skb_gso_segment
+       segs = skb_gso_segment(skb, 0);
+#endif
+
        if (!segs || IS_ERR(segs))
                goto free;
 
@@ -89,8 +242,8 @@ static struct sk_buff *tnl_skb_gso_segment(struct sk_buff *skb,
                skb->mac_len = 0;
 
                memcpy(ip_hdr(skb), iph, pkt_hlen);
-               if (OVS_GSO_CB(skb)->fix_segment)
-                       OVS_GSO_CB(skb)->fix_segment(skb);
+               memcpy(skb->cb, cb, sizeof(cb));
+               OVS_GSO_CB(skb)->fix_segment(skb);
 
                skb->protocol = proto;
                skb = skb->next;
@@ -100,11 +253,29 @@ free:
        return segs;
 }
 
+static int output_ip(struct sk_buff *skb)
+{
+       int ret = NETDEV_TX_OK;
+       int err;
+
+       memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
+
+#undef ip_local_out
+       err = ip_local_out(skb);
+       if (unlikely(net_xmit_eval(err)))
+               ret = err;
+
+       return ret;
+}
+
 int rpl_ip_local_out(struct sk_buff *skb)
 {
        int ret = NETDEV_TX_OK;
        int id = -1;
 
+       if (!OVS_GSO_CB(skb)->fix_segment)
+               return output_ip(skb);
+
        if (skb_is_gso(skb)) {
                struct iphdr *iph;
 
@@ -124,7 +295,6 @@ int rpl_ip_local_out(struct sk_buff *skb)
        while (skb) {
                struct sk_buff *next_skb = skb->next;
                struct iphdr *iph;
-               int err;
 
                skb->next = NULL;
 
@@ -132,14 +302,11 @@ int rpl_ip_local_out(struct sk_buff *skb)
                if (id >= 0)
                        iph->id = htons(id++);
 
-               memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
-
-#undef ip_local_out
-               err = ip_local_out(skb);
-               if (unlikely(net_xmit_eval(err)))
-                       ret = err;
-
+               ret = output_ip(skb);
                skb = next_skb;
        }
        return ret;
 }
+EXPORT_SYMBOL_GPL(rpl_ip_local_out);
+
+#endif /* 3.18 */