datapath: Avoid assigning a NULL pointer to flow actions.
[sliver-openvswitch.git] / datapath / linux / compat / gso.c
index 7379a57..9ded17c 100644 (file)
@@ -16,6 +16,9 @@
  * 02110-1301, USA
  */
 
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0)
+
 #include <linux/module.h>
 #include <linux/if.h>
 #include <linux/if_tunnel.h>
 
 #include "gso.h"
 
-static __be16 skb_network_protocol(struct sk_buff *skb)
+#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
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)
+static bool dev_supports_vlan_tx(struct net_device *dev)
+{
+#if defined(HAVE_VLAN_BUG_WORKAROUND)
+       return dev->features & NETIF_F_HW_VLAN_TX;
+#else
+       /* Assume that the driver is buggy. */
+       return false;
+#endif
+}
+
+int rpl_dev_queue_xmit(struct sk_buff *skb)
+{
+#undef dev_queue_xmit
+       int err = -ENOMEM;
+
+       if (vlan_tx_tag_present(skb) && !dev_supports_vlan_tx(skb->dev)) {
+               int features;
+
+               features = netif_skb_features(skb);
+
+               if (!vlan_tso)
+                       features &= ~(NETIF_F_TSO | NETIF_F_TSO6 |
+                                     NETIF_F_UFO | NETIF_F_FSO);
+
+               skb = __vlan_put_tag(skb, skb->vlan_proto, vlan_tx_tag_get(skb));
+               if (unlikely(!skb))
+                       return err;
+               vlan_set_tci(skb, 0);
+
+               if (netif_needs_gso(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;
+}
+#endif /* kernel version < 2.6.37 */
+
+static __be16 __skb_network_protocol(struct sk_buff *skb)
 {
        __be16 type = skb->protocol;
        int vlan_depth = ETH_HLEN;
@@ -65,10 +148,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,6 +160,10 @@ 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));
+
        segs = __skb_gso_segment(skb, 0, tx_path);
        if (!segs || IS_ERR(segs))
                goto free;
@@ -89,6 +177,7 @@ static struct sk_buff *tnl_skb_gso_segment(struct sk_buff *skb,
                skb->mac_len = 0;
 
                memcpy(ip_hdr(skb), iph, pkt_hlen);
+               memcpy(skb->cb, cb, sizeof(cb));
                if (OVS_GSO_CB(skb)->fix_segment)
                        OVS_GSO_CB(skb)->fix_segment(skb);
 
@@ -103,7 +192,7 @@ free:
 int rpl_ip_local_out(struct sk_buff *skb)
 {
        int ret = NETDEV_TX_OK;
-       int id;
+       int id = -1;
 
        if (skb_is_gso(skb)) {
                struct iphdr *iph;
@@ -119,7 +208,6 @@ int rpl_ip_local_out(struct sk_buff *skb)
                err = skb_checksum_help(skb);
                if (unlikely(err))
                        return 0;
-               id = -1;
        }
 
        while (skb) {
@@ -144,3 +232,4 @@ int rpl_ip_local_out(struct sk_buff *skb)
        }
        return ret;
 }
+#endif /* 3.12 */