欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

IPv4输出之概述

程序员文章站 2024-03-12 22:07:56
...

IP层的数据包发送过程应该分两个层次来看:

  1. IP层提供了哪些接口给高层协议(tcp、udp等)使用;
  2. IP层内部在收到发送数据请求后做了哪些处理,之后如何将数据包发给网卡的;

1. 高层协议发送接口

IP层对外提供了多个发送接口,高层协议会根据需要进行调用,下面对一些常用的接口进行简要的介绍。

1.1 ip_queue_xmit()

int ip_queue_xmit(struct sk_buff *skb, int ipfragok);

该接口主要由tcp使用,tcp的大多数报文的发送也均是由该接口完成。

1.2 ip_build_and_send_pkt()

1.3 ip_send_reply()

1.4 ip_append_data()

该接口主要由udp使用,udp的大多数报文的发送也是由该接口完成。但是tcp的RST报文和ip_send_reply()最终也是调用的该接口。

1.5 ip_push_pending_frames()

实际上,ip_append_data()并不会发送报文,它只是将数据封装到skb中,封装完毕后,需要高层协议自己调用ip_push_pending_frames()将数据包发送给IP层。

2. IP层内部发送过程

上面的发送接口处理完毕后,都会调用ip_local_out()进行报文发送。

2.1 ip_local_out()

int ip_local_out(struct sk_buff *skb)
{
	int err;

	err = __ip_local_out(skb);
	//大多数情况下,这里为何会返回1???
	if (likely(err == 1))
		err = dst_output(skb);
	return err;
}
EXPORT_SYMBOL_GPL(ip_local_out);

int __ip_local_out(struct sk_buff *skb)
{
	struct iphdr *iph = ip_hdr(skb);

	iph->tot_len = htons(skb->len);
	//校验和相关处理
	ip_send_check(iph);
	//过LOCAL_OUT点,通过后调用dst_output()
	return nf_hook(PF_INET, NF_INET_LOCAL_OUT, skb, NULL, skb->dst->dev,
		       dst_output);
}

2.2 dst_output()

dst_output()本身很简单,如下:

/* Output packet to network from transport.  */
static inline int dst_output(struct sk_buff *skb)
{
	return skb->dst->output(skb);
}

这里实际上会调用路由查询结果中的output(),对于单播报文(无论是本机,还是转发),该指针指向的实际上是ip_output()。

2.2.1 ip_output()

int ip_output(struct sk_buff *skb)
{
	struct net_device *dev = skb->dst->dev;

	IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS);
	//设置输出设备,和L2报文类型为以太网
	skb->dev = dev;
	skb->protocol = htons(ETH_P_IP);
	//过POST_ROUTING点,通过后,调用ip_finish_out()
	return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,
			    ip_finish_output,
			    !(IPCB(skb)->flags & IPSKB_REROUTED));
}

2.3 ip_finish_output()

该函数进行ip层的最后一段逻辑处理,即将报文发送给网卡,但是从下面的代码可以看出,这个过程并不是直接调用网络设备层的dev_queue_xmit(),而是通过邻居子系统间接调用。

static int ip_finish_output(struct sk_buff *skb)
{
...
	//如果超过了MTU并且不是GSO场景,那么需要分片,分片后再输出。否则直接输出
	if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))
		return ip_fragment(skb, ip_finish_output2);
	else
		return ip_finish_output2(skb);
}

static inline int ip_finish_output2(struct sk_buff *skb)
{
	struct dst_entry *dst = skb->dst;
	struct rtable *rt = (struct rtable *)dst;
	struct net_device *dev = dst->dev;
	unsigned int hh_len = LL_RESERVED_SPACE(dev);

	if (rt->rt_type == RTN_MULTICAST)
		IP_INC_STATS(IPSTATS_MIB_OUTMCASTPKTS);
	else if (rt->rt_type == RTN_BROADCAST)
		IP_INC_STATS(IPSTATS_MIB_OUTBCASTPKTS);

	//如果skb的头部不足以容纳L2的报头,那么重新调整skb的头部空间,并且释放旧的skb
	if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
		struct sk_buff *skb2;

		skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
		if (skb2 == NULL) {
			kfree_skb(skb);
			return -ENOMEM;
		}
		if (skb->sk)
			skb_set_owner_w(skb2, skb->sk);
		kfree_skb(skb);
		skb = skb2;
	}
	//调用邻居子系统的接口输出
	if (dst->hh)
		return neigh_hh_output(dst->hh, skb);
	else if (dst->neighbour)
		return dst->neighbour->output(skb);
	//路由有问题,没有找到邻居项,发送失败
	if (net_ratelimit())
		printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");
	kfree_skb(skb);
	return -EINVAL;
}

3. 小结

上面介绍的都很简单,并没有将每一步都展开来说,但是从中我们能看到IP层的整个发送过程:

  1. 高层协议调用提供的接口将skb传给IP层;
  2. IP层构造首部,根据需要进行分片处理;
  3. 过LOCAL_OUT点、POST_ROUTING点;
  4. 调用邻居子系统接口输出给网卡。
相关标签: IPv4 发送