【Linux4.1.12源碼分析】二層報文發送之報文GSO分段(IP層)

IP層的GSO/GRO定義在ip_packet_offload結構體中。

static struct packet_offload ip_packet_offload __read_mostly = {
	.type = cpu_to_be16(ETH_P_IP),
	.callbacks = {
		.gso_segment = inet_gso_segment,     //gso分段函數
		.gro_receive = inet_gro_receive,     //gro收包函數
		.gro_complete = inet_gro_complete,
	},
};

inet_gso_segment函數

static struct sk_buff *inet_gso_segment(struct sk_buff *skb,
					netdev_features_t features)
{
	struct sk_buff *segs = ERR_PTR(-EINVAL);
	const struct net_offload *ops;
	unsigned int offset = 0;
	bool udpfrag, encap;
	struct iphdr *iph;
	int proto;
	int nhoff;
	int ihl;
	int id;

	if (unlikely(skb_shinfo(skb)->gso_type &
		     ~(SKB_GSO_TCPV4 |
		       SKB_GSO_UDP |
		       SKB_GSO_DODGY |
		       SKB_GSO_TCP_ECN |
		       SKB_GSO_GRE |
		       SKB_GSO_GRE_CSUM |
		       SKB_GSO_IPIP |
		       SKB_GSO_SIT |
		       SKB_GSO_TCPV6 |
		       SKB_GSO_UDP_TUNNEL |
		       SKB_GSO_UDP_TUNNEL_CSUM |
		       SKB_GSO_TUNNEL_REMCSUM |
		       0)))
		goto out;

	skb_reset_network_header(skb);
	nhoff = skb_network_header(skb) - skb_mac_header(skb);	//根據network header和mac header得到IP頭相對MAC的偏移
	if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))	//檢測skb是否可以移動到L4頭?
		goto out;

	iph = ip_hdr(skb);
	ihl = iph->ihl * 4;		//得到IP包頭的實際長度,基於此可以得到L4的首地址
	if (ihl < sizeof(*iph))
		goto out;

	id = ntohs(iph->id);
	proto = iph->protocol;		//L4層協議類型

	/* Warning: after this point, iph might be no longer valid */
	if (unlikely(!pskb_may_pull(skb, ihl)))	//檢測skb是否可以移動到L4頭?
		goto out;
	__skb_pull(skb, ihl);		//報文data指針移動到傳輸層

	encap = SKB_GSO_CB(skb)->encap_level > 0;
	if (encap)
		features &= skb->dev->hw_enc_features;		//如果encap,那麼feature與hw_enc_features取交集
	SKB_GSO_CB(skb)->encap_level += ihl;	//用來標示是否爲內層報文

	skb_reset_transport_header(skb);	//設置transport header值

	segs = ERR_PTR(-EPROTONOSUPPORT);

	if (skb->encapsulation &&
	    skb_shinfo(skb)->gso_type & (SKB_GSO_SIT|SKB_GSO_IPIP))
		udpfrag = proto == IPPROTO_UDP && encap;
	else
		udpfrag = proto == IPPROTO_UDP && !skb->encapsulation;		//vxlan封裝報文走此分支,此時udpfrag爲false

	ops = rcu_dereference(inet_offloads[proto]);
	if (likely(ops && ops->callbacks.gso_segment))
		segs = ops->callbacks.gso_segment(skb, features);	//UDP或TCP的分段函數

	if (IS_ERR_OR_NULL(segs))
		goto out;

	skb = segs;
	do {
		iph = (struct iphdr *)(skb_mac_header(skb) + nhoff);	//根據分段報文的mac header 和 IP偏移
		if (udpfrag) {				//ip分片報文
			iph->id = htons(id);
			iph->frag_off = htons(offset >> 3);	//設置ip頭的frag_off值
			if (skb->next)
				iph->frag_off |= htons(IP_MF);	//後面還有報文,需要設置more frag標記
			offset += skb->len - nhoff - ihl;	//計算offset值,下一個報文需要使用
		} else {
			iph->id = htons(id++);		//每個報文爲完整的IP報文
		}
		iph->tot_len = htons(skb->len - nhoff);
		ip_send_check(iph);				//計算ip頭 csum值
		if (encap)		//如果encap值非空,說明當前處於內層報文中,所以需要設置inner heaer值
			skb_reset_inner_headers(skb);
		skb->network_header = (u8 *)iph - skb->head;	//設置network header
	} while ((skb = skb->next));

out:
	return segs;
}

發佈了92 篇原創文章 · 獲贊 7 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章