【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;
}

你可能感兴趣的:(源码,linux,网络,gso)