net: local checksum offload for encapsulation
authorEdward Cree <ecree@solarflare.com>
Thu, 11 Feb 2016 20:48:04 +0000 (20:48 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 12 Feb 2016 10:52:15 +0000 (05:52 -0500)
The arithmetic properties of the ones-complement checksum mean that a
 correctly checksummed inner packet, including its checksum, has a ones
 complement sum depending only on whatever value was used to initialise
 the checksum field before checksumming (in the case of TCP and UDP,
 this is the ones complement sum of the pseudo header, complemented).
Consequently, if we are going to offload the inner checksum with
 CHECKSUM_PARTIAL, we can compute the outer checksum based only on the
 packed data not covered by the inner checksum, and the initial value of
 the inner checksum field.

Signed-off-by: Edward Cree <ecree@solarflare.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/skbuff.h
net/ipv4/ip_tunnel_core.c
net/ipv4/udp.c
net/ipv6/ip6_checksum.c

index 6ec86f1a2ed96d809e4a6a4579c0325886e77bb5..cf906d1ce8a7fb29522388e174e975863d797b41 100644 (file)
@@ -3702,5 +3702,29 @@ static inline unsigned int skb_gso_network_seglen(const struct sk_buff *skb)
        return hdr_len + skb_gso_transport_seglen(skb);
 }
 
+/* Local Checksum Offload.
+ * Compute outer checksum based on the assumption that the
+ * inner checksum will be offloaded later.
+ * Fill in outer checksum adjustment (e.g. with sum of outer
+ * pseudo-header) before calling.
+ * Also ensure that inner checksum is in linear data area.
+ */
+static inline __wsum lco_csum(struct sk_buff *skb)
+{
+       char *inner_csum_field;
+       __wsum csum;
+
+       /* Start with complement of inner checksum adjustment */
+       inner_csum_field = skb->data + skb_checksum_start_offset(skb) +
+                               skb->csum_offset;
+       csum = ~csum_unfold(*(__force __sum16 *)inner_csum_field);
+       /* Add in checksum of our headers (incl. outer checksum
+        * adjustment filled in by caller)
+        */
+       csum = skb_checksum(skb, 0, skb_checksum_start_offset(skb), csum);
+       /* The result is the checksum from skb->data to end of packet */
+       return csum;
+}
+
 #endif /* __KERNEL__ */
 #endif /* _LINUX_SKBUFF_H */
index 859d415c0b2dc84512798592eec9f2af26ec2837..d74ce93de1fec37d5f151471f6ffbaad9cb8da23 100644 (file)
@@ -166,20 +166,20 @@ struct sk_buff *iptunnel_handle_offloads(struct sk_buff *skb,
                return skb;
        }
 
-       /* If packet is not gso and we are resolving any partial checksum,
+       /* If packet is not gso and we are not offloading inner checksum,
         * clear encapsulation flag. This allows setting CHECKSUM_PARTIAL
         * on the outer header without confusing devices that implement
         * NETIF_F_IP_CSUM with encapsulation.
         */
-       if (csum_help)
-               skb->encapsulation = 0;
-
        if (skb->ip_summed == CHECKSUM_PARTIAL && csum_help) {
+               skb->encapsulation = 0;
                err = skb_checksum_help(skb);
                if (unlikely(err))
                        goto error;
-       } else if (skb->ip_summed != CHECKSUM_PARTIAL)
+       } else if (skb->ip_summed != CHECKSUM_PARTIAL) {
                skb->ip_summed = CHECKSUM_NONE;
+               skb->encapsulation = 0;
+       }
 
        return skb;
 error:
index ac3cedb25a9f02fd648c808cd24f5f17f3cd81d7..a59341cf483edffe4a3911131bb4ab9498328d51 100644 (file)
@@ -848,16 +848,18 @@ void udp_set_csum(bool nocheck, struct sk_buff *skb,
 {
        struct udphdr *uh = udp_hdr(skb);
 
-       if (nocheck)
+       if (nocheck) {
                uh->check = 0;
-       else if (skb_is_gso(skb))
+       } else if (skb_is_gso(skb)) {
                uh->check = ~udp_v4_check(len, saddr, daddr, 0);
-       else if (skb_dst(skb) && skb_dst(skb)->dev &&
-                (skb_dst(skb)->dev->features &
-                 (NETIF_F_IP_CSUM | NETIF_F_HW_CSUM))) {
-
-               BUG_ON(skb->ip_summed == CHECKSUM_PARTIAL);
-
+       } else if (skb->ip_summed == CHECKSUM_PARTIAL) {
+               uh->check = 0;
+               uh->check = udp_v4_check(len, saddr, daddr, lco_csum(skb));
+               if (uh->check == 0)
+                       uh->check = CSUM_MANGLED_0;
+       } else if (skb_dst(skb) && skb_dst(skb)->dev &&
+                  (skb_dst(skb)->dev->features &
+                   (NETIF_F_IP_CSUM | NETIF_F_HW_CSUM))) {
                skb->ip_summed = CHECKSUM_PARTIAL;
                skb->csum_start = skb_transport_header(skb) - skb->head;
                skb->csum_offset = offsetof(struct udphdr, check);
@@ -865,8 +867,6 @@ void udp_set_csum(bool nocheck, struct sk_buff *skb,
        } else {
                __wsum csum;
 
-               BUG_ON(skb->ip_summed == CHECKSUM_PARTIAL);
-
                uh->check = 0;
                csum = skb_checksum(skb, 0, len, 0);
                uh->check = udp_v4_check(len, saddr, daddr, csum);
index 9a4d7322fb22234c5d697c384a74949beb2e50c5..4924bd704e896bbead932d6ebfbec7c10bb0dc0c 100644 (file)
@@ -98,11 +98,13 @@ void udp6_set_csum(bool nocheck, struct sk_buff *skb,
                uh->check = 0;
        else if (skb_is_gso(skb))
                uh->check = ~udp_v6_check(len, saddr, daddr, 0);
-       else if (skb_dst(skb) && skb_dst(skb)->dev &&
-                (skb_dst(skb)->dev->features & NETIF_F_IPV6_CSUM)) {
-
-               BUG_ON(skb->ip_summed == CHECKSUM_PARTIAL);
-
+       else if (skb->ip_summed == CHECKSUM_PARTIAL) {
+               uh->check = 0;
+               uh->check = udp_v6_check(len, saddr, daddr, lco_csum(skb));
+               if (uh->check == 0)
+                       uh->check = CSUM_MANGLED_0;
+       } else if (skb_dst(skb) && skb_dst(skb)->dev &&
+                  (skb_dst(skb)->dev->features & NETIF_F_IPV6_CSUM)) {
                skb->ip_summed = CHECKSUM_PARTIAL;
                skb->csum_start = skb_transport_header(skb) - skb->head;
                skb->csum_offset = offsetof(struct udphdr, check);
@@ -110,8 +112,6 @@ void udp6_set_csum(bool nocheck, struct sk_buff *skb,
        } else {
                __wsum csum;
 
-               BUG_ON(skb->ip_summed == CHECKSUM_PARTIAL);
-
                uh->check = 0;
                csum = skb_checksum(skb, 0, len, 0);
                uh->check = udp_v6_check(len, saddr, daddr, csum);