linux 2.4网络栈中包的处理过程(转)

linux 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/s oftirq.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()函数。

你可能感兴趣的:(linux 2.4网络栈中包的处理过程(转))