天天看點

【Linux4.1.12源碼分析】VXLAN之remcsum實作分析

根據之前VXLAN之csum和remcsum實作分析(發包)的分析,由csum配置決定發送端是否計算UDP層的csum。 remcsum會在vxlan頭中儲存相關資訊,然後在接收端進行處理,先看發送端如何構造vxlan頭資訊。

vxlan_xmit_skb函數(片段,發送方向)

if (type & SKB_GSO_TUNNEL_REMCSUM) {				//配置remcsum場景
		u16 hdrlen = sizeof(struct vxlanhdr);			//vxlan頭長度
		u32 data = (skb_checksum_start_offset(skb) - hdrlen) >>	//内層mac+ip頭長度再除2, 長度值為偶數,除2資訊不會丢失。 減去vxlan頭
			   VXLAN_RCO_SHIFT;			        //是因為目前的封包已經增加了vxlan頭,是以要減去。

		if (skb->csum_offset == offsetof(struct udphdr, check))	//判斷是否為UDP封包,data與上0x80
			data |= VXLAN_RCO_UDP;

		vxh->vx_vni |= htonl(data);			//data值為低8位值
		vxh->vx_flags |= htonl(VXLAN_HF_RCO);		//vxlan頭的flag增加VXLAN_HF_RCO标記

		if (!skb_is_gso(skb)) {
			skb->ip_summed = CHECKSUM_NONE;		//如果是非gso封包,那麼硬體不需做csum計算
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0)
			skb->encapsulation = 0;
#endif
		}
	}
           

vxlan_gro_receive函數(片段,收包)

if ((flags & VXLAN_HF_RCO) && (vs->flags & VXLAN_F_REMCSUM_RX)) { //即發送方攜帶SKB_GSO_TUNNEL_REMCSUM标記,本地要配置VXLAN_F_REMCSUM_RX
		vh = vxlan_gro_remcsum(skb, off_vx, vh, sizeof(struct vxlanhdr),	//remcsum校驗和重新整理
				       ntohl(vh->vx_vni), &grc,
				       !!(vs->flags &
					  VXLAN_F_REMCSUM_NOPARTIAL));

		if (!vh)		//校驗不通過,送出目前封包到協定棧
			goto out;
	}
           

vxlan_gro_remcsum函數

static struct vxlanhdr *vxlan_gro_remcsum(struct sk_buff *skb,
					  unsigned int off,
					  struct vxlanhdr *vh, size_t hdrlen,
					  u32 data, struct gro_remcsum *grc,
					  bool nopartial)
{
	size_t start, offset, plen;

	if (skb->remcsum_offload)	//如果remcsum_offload為真,則傳回NULL,即送出封包到協定棧
		return NULL;

	if (!NAPI_GRO_CB(skb)->csum_valid)	//csum_valid為0,則傳回NULL,即送出封包到協定棧
		return NULL;

	start = (data & VXLAN_RCO_MASK) << VXLAN_RCO_SHIFT;	//得到内層mac+ip頭的長度,即内層的傳輸層header的offset
	offset = start + ((data & VXLAN_RCO_UDP) ?		//check的相對offset
			  offsetof(struct udphdr, check) :
			  offsetof(struct tcphdr, check));

	plen = hdrlen + offset + sizeof(u16);	//vxlan頭長度+check的offset+2位元組,為什麼要加上vxlan頭?

	/* Pull checksum that will be written */
	if (skb_gro_header_hard(skb, off + plen)) {		//檢測下一層封包頭
		vh = skb_gro_header_slow(skb, off + plen, off);
		if (!vh)
			return NULL;
	}

	skb_gro_remcsum_process(skb, (void *)vh + hdrlen,
				start, offset, grc, nopartial);		//進入内層前,需要重新整理csum值,確定内層的csum校驗能夠通過

	skb->remcsum_offload = 1;	//remcsum_offload,避免第二次進入

	return vh;
}
           

skb_gro_remcsum_process函數

static inline void skb_gro_remcsum_process(struct sk_buff *skb, void *ptr,
					   int start, int offset,
					   struct gro_remcsum *grc,
					   bool nopartial)
{
	__wsum delta;

	BUG_ON(!NAPI_GRO_CB(skb)->csum_valid);

	if (!nopartial) {	//如果沒有設定nopartial,那麼設定重新計算gro_remcsum_start值
		NAPI_GRO_CB(skb)->gro_remcsum_start =
		    ((unsigned char *)ptr + start) - skb->head;
		return;
	}

	delta = remcsum_adjust(ptr, NAPI_GRO_CB(skb)->csum, start, offset);  //計算delta值,通過改制可以計算新的csum值

	/* Adjust skb->csum since we changed the packet */
	NAPI_GRO_CB(skb)->csum = csum_add(NAPI_GRO_CB(skb)->csum, delta); //重新計算csum值,減掉了内層的mac和ip 校驗和

	grc->offset = (ptr + offset) - (void *)skb->head;
	grc->delta = delta;
}
           

remcsum_adjust函數

static inline __wsum remcsum_adjust(void *ptr, __wsum csum,
				    int start, int offset)
{
	__sum16 *psum = (__sum16 *)(ptr + offset);	//psum指向傳輸層的check
	__wsum delta;

	/* Subtract out checksum up to start */
	csum = csum_sub(csum, csum_partial(ptr, start, 0));  //csum值減掉内層的mac和ip頭,等效于skb_checksum+僞首部

	/* Set derived checksum in packet */
	delta = csum_sub(csum_fold(csum), *psum);	//delta為check值的內插補點,差異為與内層封包的csum之差。
	*psum = csum_fold(csum); //確定傳輸csum校驗通過,意味着内層封包僅計算僞首部,隻需做最外層的csum計算即可。

	return delta;
}
           

從效果上看,vxlan設定remcsum後,内層封包的不需要進行checksum計算,vxlan封包切換到内層封包校驗時,可以根據内層封包的check值,重新計算csum值,確定checksum校驗通過,說明内層的check值可以是不正确的值,vxlan_gro_remcsum函數中會矯正。 可以改善發送端的開銷,一般硬體可以計算出整個封包的csum值,如果整封包的csum值正确,内層封包csum值是錯誤的幾率比較低,是以可以無視内層封包的check值。

繼續閱讀