ip4ip6: Support for GSO/GRO
[cascardo/linux.git] / net / ipv4 / af_inet.c
index 8217cd2..377424e 100644 (file)
@@ -1192,35 +1192,19 @@ int inet_sk_rebuild_header(struct sock *sk)
 }
 EXPORT_SYMBOL(inet_sk_rebuild_header);
 
-static struct sk_buff *inet_gso_segment(struct sk_buff *skb,
-                                       netdev_features_t features)
+struct sk_buff *inet_gso_segment(struct sk_buff *skb,
+                                netdev_features_t features)
 {
+       bool udpfrag = false, fixedid = false, encap;
        struct sk_buff *segs = ERR_PTR(-EINVAL);
        const struct net_offload *ops;
        unsigned int offset = 0;
-       bool udpfrag, encap;
        struct iphdr *iph;
-       int proto;
+       int proto, tot_len;
        int nhoff;
        int ihl;
        int id;
 
-       if (unlikely(skb_shinfo(skb)->gso_type &
-                    ~(SKB_GSO_TCPV4 |
-                      SKB_GSO_UDP |
-                      SKB_GSO_DODGY |
-                      SKB_GSO_TCP_ECN |
-                      SKB_GSO_GRE |
-                      SKB_GSO_GRE_CSUM |
-                      SKB_GSO_IPIP |
-                      SKB_GSO_SIT |
-                      SKB_GSO_TCPV6 |
-                      SKB_GSO_UDP_TUNNEL |
-                      SKB_GSO_UDP_TUNNEL_CSUM |
-                      SKB_GSO_TUNNEL_REMCSUM |
-                      0)))
-               goto out;
-
        skb_reset_network_header(skb);
        nhoff = skb_network_header(skb) - skb_mac_header(skb);
        if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))
@@ -1248,11 +1232,14 @@ static struct sk_buff *inet_gso_segment(struct sk_buff *skb,
 
        segs = ERR_PTR(-EPROTONOSUPPORT);
 
-       if (skb->encapsulation &&
-           skb_shinfo(skb)->gso_type & (SKB_GSO_SIT|SKB_GSO_IPIP))
-               udpfrag = proto == IPPROTO_UDP && encap;
-       else
-               udpfrag = proto == IPPROTO_UDP && !skb->encapsulation;
+       if (!skb->encapsulation || encap) {
+               udpfrag = !!(skb_shinfo(skb)->gso_type & SKB_GSO_UDP);
+               fixedid = !!(skb_shinfo(skb)->gso_type & SKB_GSO_TCP_FIXEDID);
+
+               /* fixed ID is invalid if DF bit is not set */
+               if (fixedid && !(iph->frag_off & htons(IP_DF)))
+                       goto out;
+       }
 
        ops = rcu_dereference(inet_offloads[proto]);
        if (likely(ops && ops->callbacks.gso_segment))
@@ -1265,15 +1252,25 @@ static struct sk_buff *inet_gso_segment(struct sk_buff *skb,
        do {
                iph = (struct iphdr *)(skb_mac_header(skb) + nhoff);
                if (udpfrag) {
-                       iph->id = htons(id);
                        iph->frag_off = htons(offset >> 3);
                        if (skb->next)
                                iph->frag_off |= htons(IP_MF);
                        offset += skb->len - nhoff - ihl;
+                       tot_len = skb->len - nhoff;
+               } else if (skb_is_gso(skb)) {
+                       if (!fixedid) {
+                               iph->id = htons(id);
+                               id += skb_shinfo(skb)->gso_segs;
+                       }
+                       tot_len = skb_shinfo(skb)->gso_size +
+                                 SKB_GSO_CB(skb)->data_offset +
+                                 skb->head - (unsigned char *)iph;
                } else {
-                       iph->id = htons(id++);
+                       if (!fixedid)
+                               iph->id = htons(id++);
+                       tot_len = skb->len - nhoff;
                }
-               iph->tot_len = htons(skb->len - nhoff);
+               iph->tot_len = htons(tot_len);
                ip_send_check(iph);
                if (encap)
                        skb_reset_inner_headers(skb);
@@ -1283,9 +1280,9 @@ static struct sk_buff *inet_gso_segment(struct sk_buff *skb,
 out:
        return segs;
 }
+EXPORT_SYMBOL(inet_gso_segment);
 
-static struct sk_buff **inet_gro_receive(struct sk_buff **head,
-                                        struct sk_buff *skb)
+struct sk_buff **inet_gro_receive(struct sk_buff **head, struct sk_buff *skb)
 {
        const struct net_offload *ops;
        struct sk_buff **pp = NULL;
@@ -1325,6 +1322,7 @@ static struct sk_buff **inet_gro_receive(struct sk_buff **head,
 
        for (p = *head; p; p = p->next) {
                struct iphdr *iph2;
+               u16 flush_id;
 
                if (!NAPI_GRO_CB(p)->same_flow)
                        continue;
@@ -1348,16 +1346,36 @@ static struct sk_buff **inet_gro_receive(struct sk_buff **head,
                        (iph->tos ^ iph2->tos) |
                        ((iph->frag_off ^ iph2->frag_off) & htons(IP_DF));
 
-               /* Save the IP ID check to be included later when we get to
-                * the transport layer so only the inner most IP ID is checked.
-                * This is because some GSO/TSO implementations do not
-                * correctly increment the IP ID for the outer hdrs.
-                */
-               NAPI_GRO_CB(p)->flush_id =
-                           ((u16)(ntohs(iph2->id) + NAPI_GRO_CB(p)->count) ^ id);
                NAPI_GRO_CB(p)->flush |= flush;
+
+               /* We need to store of the IP ID check to be included later
+                * when we can verify that this packet does in fact belong
+                * to a given flow.
+                */
+               flush_id = (u16)(id - ntohs(iph2->id));
+
+               /* This bit of code makes it much easier for us to identify
+                * the cases where we are doing atomic vs non-atomic IP ID
+                * checks.  Specifically an atomic check can return IP ID
+                * values 0 - 0xFFFF, while a non-atomic check can only
+                * return 0 or 0xFFFF.
+                */
+               if (!NAPI_GRO_CB(p)->is_atomic ||
+                   !(iph->frag_off & htons(IP_DF))) {
+                       flush_id ^= NAPI_GRO_CB(p)->count;
+                       flush_id = flush_id ? 0xFFFF : 0;
+               }
+
+               /* If the previous IP ID value was based on an atomic
+                * datagram we can overwrite the value and ignore it.
+                */
+               if (NAPI_GRO_CB(skb)->is_atomic)
+                       NAPI_GRO_CB(p)->flush_id = flush_id;
+               else
+                       NAPI_GRO_CB(p)->flush_id |= flush_id;
        }
 
+       NAPI_GRO_CB(skb)->is_atomic = !!(iph->frag_off & htons(IP_DF));
        NAPI_GRO_CB(skb)->flush |= flush;
        skb_set_network_header(skb, off);
        /* The above will be needed by the transport layer if there is one
@@ -1380,6 +1398,7 @@ out:
 
        return pp;
 }
+EXPORT_SYMBOL(inet_gro_receive);
 
 static struct sk_buff **ipip_gro_receive(struct sk_buff **head,
                                         struct sk_buff *skb)
@@ -1431,7 +1450,7 @@ int inet_recv_error(struct sock *sk, struct msghdr *msg, int len, int *addr_len)
        return -EINVAL;
 }
 
-static int inet_gro_complete(struct sk_buff *skb, int nhoff)
+int inet_gro_complete(struct sk_buff *skb, int nhoff)
 {
        __be16 newlen = htons(skb->len - nhoff);
        struct iphdr *iph = (struct iphdr *)(skb->data + nhoff);
@@ -1461,11 +1480,12 @@ out_unlock:
 
        return err;
 }
+EXPORT_SYMBOL(inet_gro_complete);
 
 static int ipip_gro_complete(struct sk_buff *skb, int nhoff)
 {
        skb->encapsulation = 1;
-       skb_shinfo(skb)->gso_type |= SKB_GSO_IPIP;
+       skb_shinfo(skb)->gso_type |= SKB_GSO_IPXIP4;
        return inet_gro_complete(skb, nhoff);
 }