The journey of a packet through the linux 2.4 network stack
作者:Harald Welte laforge@gnumonks.org
1.4, 2000/10/14 20:27:43
翻译:yunyuaner yunyuaner@gmail.com
本文描述网络数据包在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/ipv4/ip_output.c:ip_init()调用
net/core/dev.c:dev_add_pack()注册的。
Linux内核网络协议栈里,负责处理ipv4数据包处理函数是net/ipv4/ip_input.c:ip_rcv()。在一些初始化检验(如数据包是否针对该主机等)后,ip检验和被计算出来。其余一些检验如数据包的长度、协议版本号等也已经做好了。
没用通过有效性检验的数据包都将被丢弃。
一旦数据包通过了上述检验,数据包的大小就被计算出来,一些用于传输介质的无效填充字段将被截去。
接下来是首次netfilter钩子函数将要被调用的时机了。
Netfilter为标准路由代码提供了一个通用和抽象的接口。它当前常被用于数据包过滤、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(生存时间).如果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() 。
The journey of a packet through the linux 2.4 network stack
一个数据包通过Linux 2.4网络协议栈之旅
-----------------------------------------------------------------------------
一个数据包通过Linux 2.4网络协议栈之旅
-----------------------------------------------------------------------------
---
This document describes the journey of a network packet inside the
This document describes the journey of a network packet inside the
linux kernel 2.4.x. 本文档介绍了一个网络数据包在linux 2.4.x.内核中的传输过
程.This has changed drastically since 2.2 because the globally serialized
bottom half was abandoned in favor of the new softirq system.相对于2.2版的
Linux内核,这个过程已经有了彻底的改变,因为2.2内核的下半部分的全局序列已经弃用
了,替换成使用一各新的软中断系统。
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
---
1. Preface前言
I have to excuse for my ignorance, but this document has a strong
focus on the "default case": x86 architecture and ip packets which get
forwarded.本文强烈观注于“默认情况下”:x86架构和IP数据包被转发。
I am definitely no kernel guru and the information provided by this
document may be wrong. So don't expect too much, I'll always appreciate Your
comments and bugfixes.
2. Receiving the packet接收数据包
2.1 The receive interrupt接收中断
If the network card receives an ethernet frame which matches the local
MAC address or is a linklayer broadcast, it issues an interrupt. 如果网卡收到
一个与本地MAC地址相匹配的以太网帧或者是一个linklayer的广播,它会发出一个中断。
The network driver for this particular card handles the interrupt, fetches the
packet data via DMA / PIO / whatever into RAM. 此特定网卡的网络驱动程序会处理
中断,通过DMA(直接存储器存取)或PIO(过程输入输出)来获取分组数据到RAM(随机
存取储存器)。It then allocates a skb and calls a function of the protocol
independent device support routines: net/core/dev.c:netif_rx(skb).然后,它分配
一个skb缓冲区并调用该协议的功能独立的设备支持例程:net/core/dev.c:netif_rx
(skb)。
If the driver didn't already timestamp the skb, it is timestamped now. 如果驱
动没有对skb取时间戳,现再就算时间戳。Afterwards the skb gets enqueued in the
apropriate queue for the processor handling this packet.然后,将skb放入相应的
队列中排队等待处理器处理这个数据包。 If the queue backlog is full the packet
is dropped at this place.如果队列已装满,则数据包在此处被丢弃。 After
enqueuing the skb the receive softinterrupt is marked for execution via
include/linux/interrupt.h:__cpu_raise_softirq().经过排队后skb的接收软中断就标
记为可执行,通过执行函数include/linux/interrupt.h:__cpu_raise_softirq()。
The interrupt handler exits and all interrupts are reenabled.
中断处理程序退出并重新启用所有中断监控。
中断处理程序退出并重新启用所有中断监控。
2.2 The network RX softirq 网络接收软中断
Now we encounter one of the big changes between 2.2 and 2.4: The whole
network stack is no longer a bottom half, but a softirq. Softirqs have the
major advantage, that they may run on more than one CPU simultaneously. bh's
were guaranteed to run only on one CPU at a time.
现在,我们遇到2.2和2.4之间的一个很大的变化:整个网络协议栈不再是下半部
现在,我们遇到2.2和2.4之间的一个很大的变化:整个网络协议栈不再是下半部
,而是软中断. Softirqs的最大优势,是他们能在多个CPU上同时运行.bh's只能保证一次
运行在单CPU上。
Our network receive softirq is registered in net/core/dev.c:net_init()
using the function kernel/softirq.c:open_softirq() provided by the softirq
subsystem.
我们的网络接收的软中断是由net/core/dev.c:net_init()函数调用软中断子系
我们的网络接收的软中断是由net/core/dev.c:net_init()函数调用软中断子系
统提供的函数kernel/softirq.c:open_softirq()注册的。
Further handling of our packet is done in the network receive softirq
(NET_RX_SOFTIRQ) which is called from kernel/softirq.c:do_softirq().
do_softirq() itself is called from three places within the kernel:
进一步处理我们的数据包是在网络接收软中断(NET_RX_SOFTIRQ)中,它是被一
进一步处理我们的数据包是在网络接收软中断(NET_RX_SOFTIRQ)中,它是被一
个叫kernel/softirq.c:do_softirq()函数调用。而 do_softirq() 本身是被内核中的三
个地方调用:
1.from arch/i386/kernel/irq.c:do_IRQ(), which is the generic IRQ
handler。这是常用的IRQ处理函数。
2.from arch/i386/kernel/entry.S in case the kernel just returned from
a syscall inside the main process scheduler in kernel/sched.c:schedule()。
如果内核刚从在主进程调度程序kernel/sched.c:schedule()的系统调用返回。
如果内核刚从在主进程调度程序kernel/sched.c:schedule()的系统调用返回。
So if execution passes one of these points, do_softirq() is called, it
detects the NET_RX_SOFTIRQ marked an calls net/core/dev.c:net_rx_action().
Here the sbk is dequeued from this cpu's receive queue and afterwards handled
to the apropriate packet handler. In case of IPv4 this is the IPv4 packet
handler.
因此,如果执行通过这些位置中的其中一个,do_softirq()就会被调用,它检
因此,如果执行通过这些位置中的其中一个,do_softirq()就会被调用,它检
测NET_RX_SOFTIRQ标志是否标记调用一个函数net/core/dev.c:net_rx_action().这时sbk
出这个CPU接收队列,然后以相应的数据包处理实例处理。如果是IPV4数据包,下面就是
IPV4数据包的处理函数。
2.3 The IPv4 packet handler IPv4数据包处理函数
The IP packet handler is registered via net/core/dev.c:dev_add_pack()
called from net/ipv4/ip_output.c:ip_init().
IP数据包处理程序是通过函数net/ipv4/ip_output.c:ip_init()调用
IP数据包处理程序是通过函数net/ipv4/ip_output.c:ip_init()调用
net/core/dev.c:dev_add_pack()注册的。
The IPv4 packet handling function is net/ipv4/ip_input.c:ip_rcv().
After some initial checks (if the packet is for this host, ...) the ip
checksum is calculated. Additional checks are done on the length and IP
protocol version 4.
IPv4的数据包处理程序是net/ipv4/ip_input.c:ip_rcv()。经过一些初步的检
IPv4的数据包处理程序是net/ipv4/ip_input.c:ip_rcv()。经过一些初步的检
查(如果此数据包接收者不是这个主机,...),计算IP校验和.在长度和IPv4上进行额外
的检查。
Every packet failing one of the sanity checks is dropped at this
point.
若数据包在其中一个完整性检查上失败,则此时直接被丢弃。
若数据包在其中一个完整性检查上失败,则此时直接被丢弃。
If the packet passes the tests, we determine the size of the ip packet
and trim the skb in case the transport medium has appended some padding.
如果数据包通过测试,我们确定IP数据包大小和以防在传输中附加了一些信息,
如果数据包通过测试,我们确定IP数据包大小和以防在传输中附加了一些信息,
调整skb(删除字符串首部和尾部的空格)。
Now it is the first time one of the netfilter hooks is called.
现在是netfilter钩子被第一次调用。
现在是netfilter钩子被第一次调用。
Netfilter provides an generict and abstract interface to the standard
routing code. This is currently used for packet filtering, mangling, NAT and
queuing packets to userspace. For further reference see my conference paper
'The netfilter subsystem in Linux 2.4' or one of Rustys unreliable guides, i.e
the netfilter-hacking-guide.
Netfilter为标准路由代码提供了一个通用和抽象的接口。它常用于包过滤,
Netfilter为标准路由代码提供了一个通用和抽象的接口。它常用于包过滤,
mangling, NAT和排队数据包到用户空间。进一步交流请看我的论文'The netfilter
subsystem in Linux 2.4'和一份Rustys的不可靠的指南the netfilter-hacking-guide。
After successful traversal the netfilter hook,
net/ipv4/ipv_input.c:ip_rcv_finish() is called.
在成功穿越了netfilter钩子, net/ipv4/ipv_input.c:ip_rcv_finish()被调用
在成功穿越了netfilter钩子, net/ipv4/ipv_input.c:ip_rcv_finish()被调用
。
Inside ip_rcv_finish(), the packet's destination is determined by
calling the routing function net/ipv4/route.c:ip_route_input(). Furthermore,
if our IP packet has IP options, they are processed now. Depending on the
routing decision made by net/ipv4/route.c:ip_route_input_slow(), the journey
of our packet continues in one of the following functions:
在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()
The packet's destination is local, we have to process the layer 4
The packet's destination is local, we have to process the layer 4
protocol and pass it to an userspace process.
数据包的目的地是本地的,我们必须处理的第4层协议,并将它传递到用户空间的进程。
数据包的目的地是本地的,我们必须处理的第4层协议,并将它传递到用户空间的进程。
net/ipv4/ip_forward.c:ip_forward()
The packet's destination is not local, we have to forward it to
The packet's destination is not local, we have to forward it to
another network。 数据包的目的地不在本地,我们要转发到另一个网络。
net/ipv4/route.c:ip_error()
An error occurred, we are unable to find an apropriate routing table
An error occurred, we are unable to find an apropriate routing table
entry for this packet. 发生错误,我们无法为这个数据包找到合适的路由表项。
net/ipv4/ipmr.c:ip_mr_input()
It is a Multicast packet and we have to do some multicast routing.
这是一个多播数据包,我们必须做一些多播路由。
It is a Multicast packet and we have to do some multicast routing.
这是一个多播数据包,我们必须做一些多播路由。
3. Packet forwarding to another device 转发包到另一个设备
If the routing decided that this packet has to be forwarded to another
device, the function net/ipv4/ip_forward.c:ip_forward() is called.
如果路由决定该数据包应该转发到另一个设备,调用函数
如果路由决定该数据包应该转发到另一个设备,调用函数
net/ipv4/ip_forward.c:ip_forward()。
The first task of this function is to check the ip header's TTL. If it
is <= 1 we drop the packet and return an ICMP time exceeded message to the
sender.
这个函数的第一个任务是检查IP头的TTL(生存时间).如果TTL<= 1,我们丢弃
这个函数的第一个任务是检查IP头的TTL(生存时间).如果TTL<= 1,我们丢弃
数据包并返回一个ICMP超时消息给发送方。
We check the header's tailroom if we have enough tailroom for the
destination device's link layer header and expand the skb if neccessary.
我们检查数据包头部的tailroom(尾部空间),如果我们有足够tailroom以容纳
我们检查数据包头部的tailroom(尾部空间),如果我们有足够tailroom以容纳
目地设备的链路层报头。否则有扩大skb的必要。
Next the TTL is decremented by one. 接下来将TTL减一。
If our new packet is bigger than the MTU of the destination device and
the don't fragment bit in the IP header is set, we drop the packet and send a
ICMP frag needed message to the sender.
如果我们的新的数据包大小超过了目地设备的MTU(最大传输单元)而且IP头位
如果我们的新的数据包大小超过了目地设备的MTU(最大传输单元)而且IP头位
设置了不分片标志,我们丢弃数据包并发送一个ICMP报片段,提供相应的错误信息给发送
者。
Finally it is time to call another one of the netfilter hooks - this
time it is the NF_IP_FORWARD hook.
最后,是时间调用另一个Netfilter的钩子函数,NF_IP_FORWARD。
最后,是时间调用另一个Netfilter的钩子函数,NF_IP_FORWARD。
Assuming that the netfilter hooks is returning a NF_ACCEPT verdict,
the function net/ipv4/ip_forward.c:ip_forward_finish() is the next step in our
packet's journey.
假设Netfilter钩子函数返回一个NF_ACCEPT判决,函数
假设Netfilter钩子函数返回一个NF_ACCEPT判决,函数
net/ipv4/ip_forward.c:ip_forward_finish()是我们的数据包旅程的下一步。
ip_forward_finish() itself checks if we need to set any additional
options in the IP header, and has ip_optFIXME doing this. Afterwards it calls
include/net/ip.h:ip_send().
ip_forward_finish()本身的检查,我们是否需要在IP头部设置任何其他选项
ip_forward_finish()本身的检查,我们是否需要在IP头部设置任何其他选项
,并且ip_optFIXME做这个行为.最后它调用include/net/ip.h:ip_send()。
If we need some fragmentation, FIXME:ip_fragment gets called,
otherwise we continue in net/ipv4/ip_forward:ip_finish_output().
如果我们需要一些分片,FIXME:ip_fragment被调用,否则我们继续
如果我们需要一些分片,FIXME:ip_fragment被调用,否则我们继续
net/ipv4/ip_forward:ip_finish_output()。
ip_finish_output() again does nothing else than calling the netfilter
postrouting hook NF_IP_POST_ROUTING and calling ip_finish_output2() on
successful traversal of this hook.
ip_finish_output()只是调用Netfilter的postrouting钩子函数
ip_finish_output()只是调用Netfilter的postrouting钩子函数
NF_IP_POST_ROUTING,并成功穿越这个钩子函数后调用函数ip_finish_output2()。
ip_finish_output2() calls prepends the hardware (link layer) header to
our skb and calls net/ipv4/ip_output.c:ip_output().
ip_finish_output2()调用将硬件(链路层)头部加在skb缓冲区开始处,并调
ip_finish_output2()调用将硬件(链路层)头部加在skb缓冲区开始处,并调
用net/ipv4/ip_output.c:ip_output() 。