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:
		
					parent
					
						
							
								77157e1973
							
						
					
				
			
			
				commit
				
					
						7e2b10c1e5
					
				
			
		
					 3 changed files with 40 additions and 2 deletions
				
			
		|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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)) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tom Herbert
				Tom Herbert