The journey of a packet through the linux 2.4 network stack

 
作者:Harald Welte [email protected]
1.4, 2000/10/14 20:27:43

翻译:yunyuaner [email protected]

 
本文描述网络数据包在 linux 内核 2.4.x 中的传递过程。由于自 2.2 版本以来,序列化底半部被性能更优越的软中断系统所取代,该传递过程也相应的有了大幅度的变化。

 

1.序言

 

我必须为自己的才识浅薄抱歉,本文很大程度上是针对 默认情况 IP 数据包在基于 x86 体系结构系统中的传递。

我不是什么内核专家,所以本文中的错误在所难免。所以,不要奢望太高,我将始终期待着您的批评指正。

 

2.接收数据包

 

2.1接收中断

 
如果网卡接收到一个与自己硬件地址相符的或者一个链路层广播的以太网帧,它将会发出一个中断。网卡设备驱动程序负责处理这个中断,通过 DMA/PIO 等方式将数据拷贝到内存中。接着,它申请一个 skb 的结构并调用一个与设备无关的函数: net/core/dev.c:netif_rx(skb)

 
如果驱动程序没有个 skb 标记一个时间戳,则该函数负责标记。接下来, skb 被加入适当的队列中以供处理器处理数据包。如果队列已满,数据包将在此处被丢弃。当 skb 被加入接受队列后,接收软件中断会被标记并在将来的某个时间被执行,执行的函数为: include/linux/interrupt.h:__cpu_raise_softiqu()

中断处理例程结束同时中断被使能。
 

2.2网络接收软件中断

 
现在我们遇到了 2.2 2.4 之间的最大变化:整个网络协议栈不再是一个底半部而是一个软件中断。软件中断的最大优点是他们能够同时在多个 CPU 上执行;而底半部确保在一个时间只运行在一个 CPU 上。

 
我们网络接收软件中断是在 net/core/dev.c:net_init() 中由 kernel/softirq.c:open_softirq() 注册的,后者有软件中断子系统提供。

 
进一步处理我们的数据包的工作是在网络接收软件中断 (NET_RX_SOFTIRQ) 中执行的,它由 kernel/softirq.c:do_softirq() 调用。 Do_softirq() 自身在内核中的三处被调用。

  1. arch/i386/kernel/irq.c:do_IRQ(),它是一个通用IRQ处理例程

  2. arch/i386/kernel/entry.S中,当内核刚从系统调用中返回的时候

  3. 在主进程调度函数kernel/sched.c:schedule()

所以如果执行路径通过以上三点, do_softirq() 就会被调用,它检测到 NET_RX_SOFTIRQ 被标记后,就调用 net/core/dev.c:net_rx_action() 。在 net_rx_action() 函数中, skb 将从 cpu 的接收队列中卸载并分发给适当的数据包处理例程。当然,我们这里讨论的是 ipv4 数据包,它会被分发给 ipv4 处理例程。  

 

2.3 IPV4数据包处理例程

 
IP 数据包在 net/core/dev.c:dev_add_pack() 中完成注册,后者被 net/ipv4/ip_output.c:ip_init() 调用从而完成该项任务。

 
Linux 内核网络协议栈里,负责处理 ipv4 数据包处理函数是 net/ipv4/ip_input.c:ip_rcv() 。在一些初始化检验 ( 如数据包是否针对该主机等 ) 后, ip 检验和被计算出来。其余一些检验如数据包的长度、协议版本号等也已经做好了。

 
没用通过有效性检验的数据包都将被丢弃。

 
一旦数据包通过了上述检验,数据包的大小就被计算出来,一些用于传输介质的无效填充字段将被截去。

 
接下来是首次 netfilter 钩子函数将要被调用的时机了。

 
Netrilter 提供了一个泛化的一致性标准编程接口。它当前被用于数据包过滤、 mangling NAT 和将数据包拷贝到用户空间。你可以在我的 ’The netfilter subsystem in linux 2.4’ 中获得详细的参考,此外还有一篇叫做 ’the netfilter-hacking guide’ 也是很不错的参考读物。

 
成功的穿过 netfilter 钩子函数后, net/ipv4/ipv_input.c:in_rcv_finish() 函数被调用。

ip_rcv_finish() 内,数据包的目的是根据调用函数 net/ipv4/route.c:ip_route_input() 来决定的。尤有进者,如果我们的 ip 数据包含有 ip 选项,它们会在此处被处理。数据包的传递路径可能会有几条,依 net/ipv4/route.c:ip_route_slow() 中做出的裁决而定。

 
net/ipv4/ip_input.c:ip_local_deliver()

数据包的目的地是本地,我们必须处理 layer 4 协议并把数据包传递给用户空间。

net/ipv4/ip_forward.c:ip_forward()

数据包的目的地不是本地,我们要把它路由到其它网络。

net/ipv4/route.c:ip_error()

遇到了错误,我们无法在路由表中找到何时的项。

net/ipv4/ipmr.c:ip_mr_input()

是多播数据包,我们需要做多播路由。

 

4.数据包路由到其它设备

 

如果路由程序决定数据包要被路由到另外一台设备,函数 net/ipv4/ip_forward.c:ip_forward() 将被调用。
该函数所做的第一件事情是检查数据包首部的 TTL ,如果它小于等于 1 ,则直接丢弃之并回复以 ICMP 超时报文给传送者。
 
我们检查 skb 首部的尾空间来判定是否有足够的尾空间来存放设备的数据链路层首部,若没有,则要把 skb 做适当的扩充。
 
下一步就是吧 TTL 1 如果我们的新数据包大于目标设备的 MTU 并且 IP 首部的不分片字段被设置,我们将丢弃该数据包并发送一个 ICMP 错误报文给发送者。
 
最后,是调用另外一个 netfilter 钩子函数的时间了,这一回是 NF_IP_FORWARD 钩子。 假设该 netfilter 钩子函数返回 NF_ACCEPT ,函数 net/ipv4/ip_forward.c:ip_forward_finish() 将是下一步我们要调用的。 函数 ip_forward_finish() 本身将检测我们是否设置了 ip 选项,并且用专门的 ip_optFIXME 来处理之。接着,它调用 include/net/ip.h:ip_send()。 如果我们需要分片, ip_fragment() 将被调用;反之 net/ipv4/ip_forward:ip_finish_output() 继续它的工作。
 
函数 ip_finish_output() 做的工作无外乎调用 netfilter NF_IP_POST_ROUTING 钩子函数以及把接下来的工作交给 ip_finish_output2()。 函数 ip_finish_output2() 把数据链路层首部加入我们的 skb 中,然后调用 net/ipv4/ip_output.c:ip_output() 。