The journey of a packet through the linux 2.4 network stack
作者:Harald Welte
1.4, 2000/10/14 20:27:43
翻译:yunyuaner
本文描述网络数据包在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()自身在内核中的三处被调用。
- 从arch/i386/kernel/irq.c:do_IRQ(),它是一个通用IRQ处理例程
- 从arch/i386/kernel/entry.S中,当内核刚从系统调用中返回的时候
- 在主进程调度函数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
成功的穿过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() 。