net: Support for multiple checksums with gso

When creating a GSO packet segment we may need to set more than
one checksum in the packet (for instance a TCP checksum and
UDP checksum for VXLAN encapsulation). To be efficient, we want
to do checksum calculation for any part of the packet at most once.

This patch adds csum_start offset to skb_gso_cb. This tracks the
starting offset for skb->csum which is initially set in skb_segment.
When a protocol needs to compute a transport checksum it calls
gso_make_checksum which computes the checksum value from the start
of transport header to csum_start and then adds in skb->csum to get
the full checksum. skb->csum and csum_start are then updated to reflect
the checksum of the resultant packet starting from the transport header.

This patch also adds a flag to skbuff, encap_hdr_csum, which is set
in *gso_segment fucntions to indicate that a tunnel protocol needs
checksum calculation

Signed-off-by: Tom Herbert <therbert@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Tom Herbert 2014-06-04 17:20:02 -07:00 committed by David S. Miller
parent 77157e1973
commit 7e2b10c1e5
3 changed files with 40 additions and 2 deletions

View file

@ -567,7 +567,8 @@ struct sk_buff {
* headers if needed * headers if needed
*/ */
__u8 encapsulation:1; __u8 encapsulation:1;
/* 6/8 bit hole (depending on ndisc_nodetype presence) */ __u8 encap_hdr_csum:1;
/* 5/7 bit hole (depending on ndisc_nodetype presence) */
kmemcheck_bitfield_end(flags2); kmemcheck_bitfield_end(flags2);
#if defined CONFIG_NET_DMA || defined CONFIG_NET_RX_BUSY_POLL #if defined CONFIG_NET_DMA || defined CONFIG_NET_RX_BUSY_POLL
@ -2988,6 +2989,7 @@ static inline struct sec_path *skb_sec_path(struct sk_buff *skb)
struct skb_gso_cb { struct skb_gso_cb {
int mac_offset; int mac_offset;
int encap_level; int encap_level;
__u16 csum_start;
}; };
#define SKB_GSO_CB(skb) ((struct skb_gso_cb *)(skb)->cb) #define SKB_GSO_CB(skb) ((struct skb_gso_cb *)(skb)->cb)
@ -3012,6 +3014,28 @@ static inline int gso_pskb_expand_head(struct sk_buff *skb, int extra)
return 0; return 0;
} }
/* Compute the checksum for a gso segment. First compute the checksum value
* from the start of transport header to SKB_GSO_CB(skb)->csum_start, and
* then add in skb->csum (checksum from csum_start to end of packet).
* skb->csum and csum_start are then updated to reflect the checksum of the
* resultant packet starting from the transport header-- the resultant checksum
* is in the res argument (i.e. normally zero or ~ of checksum of a pseudo
* header.
*/
static inline __sum16 gso_make_checksum(struct sk_buff *skb, __wsum res)
{
int plen = SKB_GSO_CB(skb)->csum_start - skb_headroom(skb) -
skb_transport_offset(skb);
__u16 csum;
csum = csum_fold(csum_partial(skb_transport_header(skb),
plen, skb->csum));
skb->csum = res;
SKB_GSO_CB(skb)->csum_start -= plen;
return csum;
}
static inline bool skb_is_gso(const struct sk_buff *skb) static inline bool skb_is_gso(const struct sk_buff *skb)
{ {
return skb_shinfo(skb)->gso_size; return skb_shinfo(skb)->gso_size;

View file

@ -2885,7 +2885,9 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
if (unlikely(!proto)) if (unlikely(!proto))
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
csum = !!can_checksum_protocol(features, proto); csum = !head_skb->encap_hdr_csum &&
!!can_checksum_protocol(features, proto);
__skb_push(head_skb, doffset); __skb_push(head_skb, doffset);
headroom = skb_headroom(head_skb); headroom = skb_headroom(head_skb);
pos = skb_headlen(head_skb); pos = skb_headlen(head_skb);
@ -2983,6 +2985,8 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
nskb->csum = skb_copy_and_csum_bits(head_skb, offset, nskb->csum = skb_copy_and_csum_bits(head_skb, offset,
skb_put(nskb, len), skb_put(nskb, len),
len, 0); len, 0);
SKB_GSO_CB(nskb)->csum_start =
skb_headroom(nskb) + offset;
continue; continue;
} }
@ -3052,6 +3056,8 @@ perform_csum_check:
nskb->csum = skb_checksum(nskb, doffset, nskb->csum = skb_checksum(nskb, doffset,
nskb->len - doffset, 0); nskb->len - doffset, 0);
nskb->ip_summed = CHECKSUM_NONE; nskb->ip_summed = CHECKSUM_NONE;
SKB_GSO_CB(nskb)->csum_start =
skb_headroom(nskb) + doffset;
} }
} while ((offset += len) < head_skb->len); } while ((offset += len) < head_skb->len);

View file

@ -135,6 +135,14 @@ struct sk_buff *iptunnel_handle_offloads(struct sk_buff *skb,
return skb; return skb;
} }
/* If packet is not gso and we are resolving any partial 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) { if (skb->ip_summed == CHECKSUM_PARTIAL && csum_help) {
err = skb_checksum_help(skb); err = skb_checksum_help(skb);
if (unlikely(err)) if (unlikely(err))