文章目录
网络层的数据包发送过程应该分两个层次来看:
- 网络层提供了哪些接口给高层协议(tcp、udp等)使用;
- 网络层内部在收到要发送的数据包后做了哪些处理,然后是如何将数据包发给设备接口层的;
高层协议先通过网络层提供的发送接口将要发送的数据准备好,添加自己的首部后,将数据包交给网络层继续发送出去。
高层协议发送接口
网络层对外提供了较多的发送接口,高层协议会根据场景使用,下面对常用的接口进行简要的介绍。
ip_queue_xmit()
int ip_queue_xmit(struct sk_buff *skb, int ipfragok);
该接口主要由tcp使用。
ip_build_and_send_pkt()
待补充…
ip_send_reply()
待补充…
ip_append_data()
该接口主要由udp使用。它并非真正的发送接口,高层协议可以连续多次调用该函数将要发送的数据封装成一个IP报文(可能包含多个片段),然后通过ip_push_pending_frames()发送给网络层。
该接口在封装skb的时候,充分考虑了IP层的分段,其实现非常的复杂,但是它对高效的组织skb数据相当有参考意义。该接口的实现参考笔记IPv4之数据包发送接口ip_append_data(一)和IPv4之数据包发送接口ip_append_data(二)。
ip_push_pending_frames()
配合ip_append_data(),将前者封装好的skb添加IP包头后交给网络层继续处理。为了完整性,该函数的分析见IPv4之数据包发送接口ip_append_data(二)。
IP内部数据包发送流程
上面的发送接口处理完毕后,最终都会调用ip_local_out()继续发送报文。
ip_local_out()
int ip_local_out(struct sk_buff *skb)
{
int err;
err = __ip_local_out(skb);
// 如果Netfilter不处理该数据包就会返回1,交给路由结果处理,
// 对于本地生成的数据包,对应的函数是ip_output()
if (likely(err == 1))
err = dst_output(skb);
return err;
}
int __ip_local_out(struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
// 对skb的长度和IP首部的校验和进行了设置
iph->tot_len = htons(skb->len);
ip_send_check(iph); // 计算并设置校验和
// 让数据包过LOCAL_OUT点,通过后调用dst_output(),这和上面Netfilter不处理该数据包的逻辑一致
return nf_hook(PF_INET, NF_INET_LOCAL_OUT, skb, NULL, skb->dst->dev,
dst_output);
}
// 实际上调用路由查询结果中的output(),对于单播报文(无论是本机,还是转发),
// 该指针指向的实际上是ip_output()。
static inline int dst_output(struct sk_buff *skb)
{
return skb->dst->output(skb);
}
ip_output()
通过Netfilter的LOCAL_OUT点后,单播报文交给ip_output()继续处理。
int ip_output(struct sk_buff *skb)
{
struct net_device *dev = skb->dst->dev;
IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS);
// 设置输出设备,和L3报文类型为IP报文,对应L2帧首部的协议字段
skb->dev = dev;
skb->protocol = htons(ETH_P_IP);
// 让数据包过Netfilter的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));
}
ip_finish_output()
通过Netfilter的POST_ROUTING点后,数据包交给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);
}
ip_finish_output2()
如上,无论是分片后的输出,还是直接输出,最终调用的函数都是ip_finish_output2()。
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层的整个发送流程如下:
- 高层协议调用提供的接口将skb封装好后传给IP层;
- IP层构造首部,根据需要进行分片处理;
- IP层让数据包分别通过Netfilter的LOCAL_OUT点、POST_ROUTING点;
- 调用邻居子系统接口将数据包输出给设备接口层。