linux 2.4网络栈中包的处理过程
时间:2006-01-19 来源:linuxjet
linux 2.4网络栈中包的处理过程
作者:Harald Welte [email protected]
原文地址:http://gnumonks.org/ftp/pub/doc/packet-journey-2.4.html
1.4, 2000/10/14 20:27:43
这篇文档描述了Linux2.4.x内核中包的处理过程。这部分的处理与2.2内核完全不一样,这主要是因为在2.4版本中新的软中断系统彻底替换了下半部的处理机制。
1. 前言
由于我的能力有限,这篇文档主要是基于一个前提,即x86架构和IP包。
我对内核的理解也很有限,文章中的错误之处在算难免,请不要对他期望太高,我会很感激你留下意见和建议。
2. 包的接收
2.1 接收中断
如果网卡收到一个包含本地MAC地址的以太帧或者一个链路层的广播, 他会触发一个中断。这个网卡的驱动会处理这个中断, 会通过DMA/PIO或者其他的方式把包数据读入内存中。他会分配一个skb 结构并调用一个设备无关的协议函数: net/core/dev.c:netif_rx(skb)。
如果驱动没有在skb上打时间戳, 现在就要打上。随后这个skb进入队列,等待处理器处理。如果此时队列已满,这个包此处会被丢掉。 Skb进入队列以后,软中断会通过(include/linux/interrupt.h:__cpu_raise_softirq())设置运行标记。
此时这个中断处理退出,其他的中断恢复汇报状态。
2.2 网络收包软中断
2.2和2.4内核存在很大的不同,整个网络栈不再是一个下半部, 而是一个软中断。软中断区别于下半部的主要的优点是他可以运行在多个CPU上。
我们的网络接受软中断在net/core/dev.c:net_init()函数中通过软中断系统提供的kernel/softirq.c:open_softirq()函数进行注册。
更进一步对包的处理在通过kernel/softirq.c:do_softirq()调用的软中断(NET_RX_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()函数。在这里skb从CPU接收队列中被取出,随后在适当的包处理函数中进行处理。 对于IPv4来说,就是IPv4包处理函数。
2.3 IPv4包处理函数
IP包处理函数在net/ipv4/ip_output.c:ip_init()中通过net/core/dev.c:dev_add_pack()注册。
IPv4包处理函数是net/ipv4/ip_input.c:ip_rcv()。 一些初始的检查以后(是否该包是针对本机的, ...) IP 校验和被计算出来。另外,会对包长和IP协议版本4进行检查。
任何没有通过这些检查的包都将在这里被丢弃。
如果包通过了这些检查, 如果传输介质附加了一些信息,我们需要重新计算IP包的大小并相应的调整skb结构。
在这里Netfilter钩子第一次被调用。
Netfilter对标准的路由代码提供了一个通常而又抽象的接口,主要用来进行包过滤,破坏性处理,地址转换和用户空间的包排队。要获取关于Netfilter更详细的信息可以参考我的会议文档 'Linux2.4 netfilter 子系统' 或者 Rustys的一份不可靠文档中, 那就是netfilter 进阶向导。
包成功到达Netfilter以后, 会调用net/ipv4/ipv_input.c:ip_rcv_finish() 函数.
在ip_rcv_finish()函数里面, 包的目的地通过调用路由函数来决定(net/ipv4/route.c:ip_route_input())来决定。而且, 如果我们的IP包有IP项, 他们会马上被处理,根据net/ipv4/route.c:ip_route_input_slow()函数做出的路由结果, 我们的包会继续进入到下面的一个函数中接受处理:
net/ipv4/ip_input.c:ip_local_deliver()
如果包的目的地是本机地址, 我们对它进行协议处理并将它发送到用户态进程,这个函数就是用来做这个的。
net/ipv4/ip_forward.c:ip_forward()
如果包的目的地不是本地, 我们把它转送到另一个网络,这个函数将完成这个任务。
net/ipv4/route.c:ip_error()
如果错误发生, 我们又不能为这个包找到一条适当的路由表项,包将会进入这个函数接受处理。
net/ipv4/ipmr.c:ip_mr_input()
如果是个多播包并且我们设定了多播路由,包将会进入这个函数接受处理。
3. 向另一个设备转发包
如果路由以后决定将该包发送到另一个设备,那末会调用net/ipv4/ip_forward.c:ip_forward() 函数来完成这个操作。
这个函数的第一个任务是用来检查IP头的TLL值。如果这个值小于等于1,我们会丢弃这个包并给发送者返回ICMP过期信息。
如果空间足够我们会检查包头我们检查目的设备的链路头并展开skb结构。
下一步要对 TTL减一。
如果我们的新包大于目的设备的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()函数。
如果我们需要分片, FIXME:ip_fragment 会被调用, 然后我们继续调用net/ipv4/ip_forward:ip_finish_output()函数。
ip_finish_output() 只是重复调用netfilter postrouting 钩子NF_IP_POST_ROUTING,如果这个钩子返回成功的话ip_finish_output2()函数会被调用。
ip_finish_output2() 会预先处理我们skb中的硬件头(链路层) 部分并调用net/ipv4/ip_output.c:ip_output()函数。