1. 程式人生 > >UDP之資料報校驗和

UDP之資料報校驗和

由於目前很多網絡卡裝置是支援對L4層資料包進行校驗和的計算和驗證的,所以在L4協議軟體的實現中,會根據網絡卡的支援情況作不同的處理,為此核心在struct sk_buff結構和struct net_device中增加了校驗和相關的引數,如下:

struct sk_buff

上面的結構中,和校驗和有關的幾個欄位如下:

#define CHECKSUM_NONE 0
#define CHECKSUM_UNNECESSARY 1
#define CHECKSUM_COMPLETE 2
#define CHECKSUM_PARTIAL 3

struct sk_buff
{
	union {
		__wsum		csum;
struct { __u16 csum_start; __u16 csum_offset; }; }; __u8 ip_summed:2, }

聯合體中哪個成員有效取決於ip_summed的值,ip_summed共兩個bit,可取四個標誌,而且在傳送和接收時的含義還有所不同。

在接收過程中,ip_summed欄位包含了裝置驅動告訴L4軟體當前校驗和的狀態,各取值含義如下:

  • CHECKSUM_NONE:硬體沒有提供校驗和,可能是硬體不支援,也可能是硬體校驗出錯但是並未丟棄資料包,而是讓L4軟體重新校驗;
  • CHECKSUM_UNNECESSARY:硬體已經進行了完整的校驗,無需軟體再進行檢查,L4收到資料包後如果檢查ip_summed是這種情況,就可以跳過校驗過程;
  • CHECKSUM_COMPLETE:硬體已經校驗了L4報頭和其payload部分,並且校驗和儲存在了csum中,L4軟體只需要再計算偽報頭然後檢查校驗結果即可。

在傳送過程中,ip_summed欄位包含了L4軟體告訴裝置驅動程式當前校驗和的狀態,各取值含義如下:

  • CHECKSUM_NONE:L4軟體已經進行了校驗,硬體無需做任何事情;
  • CHECKSUM_PARTIAL:L4軟體計算了偽報頭,並且將值儲存在了首部的check欄位中,硬體需要計算其餘部分的校驗和。

struct net_device

net_device結構中的feature欄位中定義瞭如下和校驗和相關的欄位,這些欄位表明了硬體計算校驗和的能力。

feature 含義
NETIF_F_NO_CSUM 該裝置非常可靠,無需L4執行任何校驗,環回裝置一般設定該標記
NETIF_F_IP_CSUM 裝置可以對基於IPv4的TCP和UDP資料包進行校驗
NETIF_F_IPV6_CSUM 裝置可以對基於IPv6的TCP和UDP資料包進行校驗
NETIF_F_HW_CSUM 裝置可以對任何L4協議的資料包進行校驗

注:這些概念和欄位的含義同樣適用於TCP校驗和處理過程

1. 輸入資料報的校驗和計算

1.1 udp4_csum_init()

@skb: 待校驗的資料報
@uh:該資料報的UDP首部
@proto:L4協議號,為IPPROTO_UDP或者IPPROTO_UDPLITE
static inline int udp4_csum_init(struct sk_buff *skb, struct udphdr *uh,
				 int proto)
{
	const struct iphdr *iph;
	int err;

	//這兩個欄位用於指示對報文的哪些部分進行校驗,cov指coverage,
	//只有UDPLite使用,對於UDP,會對整個報文進行校驗
	UDP_SKB_CB(skb)->partial_cov = 0;
	UDP_SKB_CB(skb)->cscov = skb->len;

	//UDPLITE,忽略
	if (proto == IPPROTO_UDPLITE) {
		err = udplite_checksum_init(skb, uh);
		if (err)
			return err;
	}

	iph = ip_hdr(skb);
	//UDP首部校驗和欄位為0,這種情況說明已經處理過了,設定為CHECKSUM_UNNECESSARY,後續無需再進行處理
	if (uh->check == 0) {
		skb->ip_summed = CHECKSUM_UNNECESSARY;
	} else if (skb->ip_summed == CHECKSUM_COMPLETE) {
		//還有偽首部需要校驗,所以新增偽首部校驗,如果校驗成功,設定為CHECKSUM_UNNECESSARY
		//csum_tcpudp_magic()計算偽首部校驗和後進行驗證,如果驗證ok,返回0,該函式體系結構相關,
		//為了高效,用匯編語言實現
		if (!csum_tcpudp_magic(iph->saddr, iph->daddr, skb->len, proto, skb->csum))
			skb->ip_summed = CHECKSUM_UNNECESSARY;
	}
	//如果經過上面處理後仍然需要校驗,則計算校驗和,並將結果放入到skb->csum中
	if (!skb_csum_unnecessary(skb))
		skb->csum = csum_tcpudp_nofold(iph->saddr, iph->daddr,
					       skb->len, proto, 0);

	return 0;
}

//在接收方向上,CHECKSUM_UNNECESSARY表示校驗ok,無需再進行校驗和計算
static inline int skb_csum_unnecessary(const struct sk_buff *skb)
{
	return skb->ip_summed & CHECKSUM_UNNECESSARY;
}

1.2 udp_lib_checksum_complete()

//返回0表示校驗成功
static inline int udp_lib_checksum_complete(struct sk_buff *skb)
{
	//如果需要校驗則呼叫__udp_lib_checksum_complete()進行校驗
	return !skb_csum_unnecessary(skb) &&
		__udp_lib_checksum_complete(skb);
}

/*
 *	Generic checksumming routines for UDP(-Lite) v4 and v6
 */
static inline __sum16 __udp_lib_checksum_complete(struct sk_buff *skb)
{
	//增加一個需要校驗的長度欄位,對於UDP,該欄位就是整個報文長度
	return __skb_checksum_complete_head(skb, UDP_SKB_CB(skb)->cscov);
}

__sum16 __skb_checksum_complete_head(struct sk_buff *skb, int len)
{
	__sum16 sum;

	//計算校驗和,如果成功,那麼最終結果應該是0
	sum = csum_fold(skb_checksum(skb, 0, len, skb->csum));
	if (likely(!sum)) {
		//為什麼CHECKSUM_COMPLETE時是校驗失敗???
		if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE))
			netdev_rx_csum_fault(skb->dev);
		//設定校驗和狀態為CHECKSUM_UNNECESSARY
		skb->ip_summed = CHECKSUM_UNNECESSARY;
	}
	return sum;
}

2. 輸出資料包的校驗和計算

待補充…