v4 GRE: Add TCP segmentation offload for GRE
[firefly-linux-kernel-4.4.55.git] / net / ipv4 / ip_gre.c
index 00a14b9864ea91b3d87955efa06c45d1ffe64d10..a56f1182c176dd87cff194914bd379f9674f6b00 100644 (file)
@@ -735,8 +735,33 @@ drop:
        return 0;
 }
 
+static struct sk_buff *handle_offloads(struct sk_buff *skb)
+{
+       int err;
+
+       if (skb_is_gso(skb)) {
+               err = skb_unclone(skb, GFP_ATOMIC);
+               if (unlikely(err))
+                       goto error;
+               skb_shinfo(skb)->gso_type |= SKB_GSO_GRE;
+               return skb;
+       } else if (skb->ip_summed == CHECKSUM_PARTIAL) {
+               err = skb_checksum_help(skb);
+               if (unlikely(err))
+                       goto error;
+       }
+       skb->ip_summed = CHECKSUM_NONE;
+
+       return skb;
+
+error:
+       kfree_skb(skb);
+       return ERR_PTR(err);
+}
+
 static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
 {
+       struct pcpu_tstats *tstats = this_cpu_ptr(dev->tstats);
        struct ip_tunnel *tunnel = netdev_priv(dev);
        const struct iphdr  *old_iph;
        const struct iphdr  *tiph;
@@ -751,10 +776,19 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
        __be32 dst;
        int    mtu;
        u8     ttl;
+       int    err;
+       int    pkt_len;
 
-       if (skb->ip_summed == CHECKSUM_PARTIAL &&
-           skb_checksum_help(skb))
-               goto tx_error;
+       skb = handle_offloads(skb);
+       if (IS_ERR(skb)) {
+               dev->stats.tx_dropped++;
+               return NETDEV_TX_OK;
+       }
+
+       if (!skb->encapsulation) {
+               skb_reset_inner_headers(skb);
+               skb->encapsulation = 1;
+       }
 
        old_iph = ip_hdr(skb);
 
@@ -855,7 +889,8 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
        if (skb->protocol == htons(ETH_P_IP)) {
                df |= (old_iph->frag_off&htons(IP_DF));
 
-               if ((old_iph->frag_off&htons(IP_DF)) &&
+               if (!skb_is_gso(skb) &&
+                   (old_iph->frag_off&htons(IP_DF)) &&
                    mtu < ntohs(old_iph->tot_len)) {
                        icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
                        ip_rt_put(rt);
@@ -875,7 +910,9 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
                        }
                }
 
-               if (mtu >= IPV6_MIN_MTU && mtu < skb->len - tunnel->hlen + gre_hlen) {
+               if (!skb_is_gso(skb) &&
+                   mtu >= IPV6_MIN_MTU &&
+                   mtu < skb->len - tunnel->hlen + gre_hlen) {
                        icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
                        ip_rt_put(rt);
                        goto tx_error;
@@ -936,6 +973,7 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
        iph->daddr              =       fl4.daddr;
        iph->saddr              =       fl4.saddr;
        iph->ttl                =       ttl;
+       iph->id                 =       0;
 
        if (ttl == 0) {
                if (skb->protocol == htons(ETH_P_IP))
@@ -964,9 +1002,19 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
                        *ptr = tunnel->parms.o_key;
                        ptr--;
                }
-               if (tunnel->parms.o_flags&GRE_CSUM) {
+               /* Skip GRE checksum if skb is getting offloaded. */
+               if (!(skb_shinfo(skb)->gso_type & SKB_GSO_GRE) &&
+                   (tunnel->parms.o_flags&GRE_CSUM)) {
                        int offset = skb_transport_offset(skb);
 
+                       if (skb_has_shared_frag(skb)) {
+                               err = __skb_linearize(skb);
+                               if (err) {
+                                       ip_rt_put(rt);
+                                       goto tx_error;
+                               }
+                       }
+
                        *ptr = 0;
                        *(__sum16 *)ptr = csum_fold(skb_checksum(skb, offset,
                                                                 skb->len - offset,
@@ -974,7 +1022,19 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev
                }
        }
 
-       iptunnel_xmit(skb, dev);
+       nf_reset(skb);
+
+       pkt_len = skb->len - skb_transport_offset(skb);
+       err = ip_local_out(skb);
+       if (likely(net_xmit_eval(err) == 0)) {
+               u64_stats_update_begin(&tstats->syncp);
+               tstats->tx_bytes += pkt_len;
+               tstats->tx_packets++;
+               u64_stats_update_end(&tstats->syncp);
+       } else {
+               dev->stats.tx_errors++;
+               dev->stats.tx_aborted_errors++;
+       }
        return NETDEV_TX_OK;
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -1044,6 +1104,11 @@ static int ipgre_tunnel_bind_dev(struct net_device *dev)
                mtu = 68;
 
        tunnel->hlen = addend;
+       /* TCP offload with GRE SEQ is not supported. */
+       if (!(tunnel->parms.o_flags & GRE_SEQ)) {
+               dev->features           |= NETIF_F_GSO_SOFTWARE;
+               dev->hw_features        |= NETIF_F_GSO_SOFTWARE;
+       }
 
        return mtu;
 }
@@ -1593,6 +1658,9 @@ static void ipgre_tap_setup(struct net_device *dev)
 
        dev->iflink             = 0;
        dev->features           |= NETIF_F_NETNS_LOCAL;
+
+       dev->features           |= GRE_FEATURES;
+       dev->hw_features        |= GRE_FEATURES;
 }
 
 static int ipgre_newlink(struct net *src_net, struct net_device *dev, struct nlattr *tb[],