[align=left]
The journey of a packet through the linux 2.4network stack[/align]
[align=left] [/align]
作者:Harald Welte
[email protected][align=left]1.4, 2000/10/1420:27:43[/align]
翻譯:yunyuaner
[email protected][align=left] [/align]
本文描述網絡數據包在linux內核
2.4.x中的傳遞過程。由於自2.2版本以來,序列化底半部被性能更優越的軟中斷系統所取代,該傳遞過程也相應的有了大幅度的變化。
1.序言
[align=left]我必須為自己的才識淺薄抱歉,本文很大程度上是針對“默認情況”:IP數據包在基於x86體系結構系統中的傳遞。[/align]
[align=left]我不是什麼內核專家,所以本文中的錯誤在所難免。所以,不要奢望太高,我將始終期待著您的批評指正。[/align]
[align=left] [/align]
2.接收數據包
2.1接收中斷
[align=left]如果網卡接收到一個與自己硬件地址相符的或者一個鏈路層廣播的以太網幀,它將會發出一個中斷。網卡設備驅動程序負責處理這個中斷,通過DMA/PIO等方式將數據拷貝到內存中。接著,它申請一個skb的結構並調用一個與設備無關的函數:net/core/dev.c:netif_rx(skb)[/align]
[align=left] [/align]
[align=left]如果驅動程序沒有個skb標記一個時間戳,則該函數負責標記。接下來,skb被加入適當的隊列中以供處理器處理數據包。如果隊列已滿,數據包將在此處被丟棄。當skb被加入接受隊列後,接收軟件中斷會被標記並在將來的某個時間被執行,執行的函數為:include/linux/interrupt.h:__cpu_raise_softiqu()[/align]
[align=left]中斷處理例程結束同時中斷被使能。[/align]
[align=left] [/align]
2.2網絡接收軟件中斷
[align=left]現在我們遇到了2.2和2.4之間的最大變化:整個網絡協議棧不再是一個底半部而是一個軟件中斷。軟件中斷的最大優點是他們能夠同時在多個CPU上執行;而底半部確保在一個時間只運行在一個CPU上。[/align]
[align=left] [/align]
[align=left]我們網絡接收軟件中斷是在net/core/dev.c:net_init()中由kernel/softirq.c:open_softirq()注冊的,後者有軟件中斷子系統提供。[/align]
[align=left] [/align]
[align=left]進一步處理我們的數據包的工作是在網絡接收軟件中斷(NET_RX_SOFTIRQ)中執行的,它由kernel/softirq.c:do_softirq()調用。Do_softirq()自身在內核中的三處被調用。[/align]
從arch/i386/kernel/irq.c:do_IRQ(),它是一個通用IRQ處理例程
從arch/i386/kernel/entry.S中,當內核剛從系統調用中返回的時候
在主進程調度函數kernel/sched.c:schedule()中
[align=left]所以如果執行路徑通過以上三點,do_softirq()就會被調用,它檢測到NET_RX_SOFTIRQ被標記後,就調用net/core/dev.c:net_rx_action()。在net_rx_action()函數中,skb將從cpu的接收隊列中卸載並分發給適當的數據包處理例程。當然,我們這裡討論的是ipv4數據包,它會被分發給ipv4處理例程。 [/align]
[align=left] [/align]
2.3IPV4數據包處理例程
[align=left]IP數據包在net/core/dev.c:dev_add_pack()中完成注冊,後者被net/ipv4/ip_output.c:ip_init()調用從而完成該項任務。[/align]
[align=left] [/align]
[align=left]Linux內核網絡協議棧裡,負責處理ipv4數據包處理函數是net/ipv4/ip_input.c:ip_rcv()。在一些初始化檢驗(如數據包是否針對該主機等)後,ip檢驗和被計算出來。其余一些檢驗如數據包的長度、協議版本號等也已經做好了。[/align]
[align=left] [/align]
[align=left]沒用通過有效性檢驗的數據包都將被丟棄。[/align]
[align=left] [/align]
[align=left]一旦數據包通過了上述檢驗,數據包的大小就被計算出來,一些用於傳輸介質的無效填充字段將被截去。[/align]
[align=left] [/align]
[align=left]接下來是首次netfilter鉤子函數將要被調用的時機了。[/align]
[align=left] [/align]
Netrilter提供了一個泛化的一致性標准編程接口。它當前被用於數據包過濾、mangling、NAT和將數據包拷貝到用戶空間。你可以在我的’Thenetfilter
subsystem in linux 2.4’中獲得詳細的參考,此外還有一篇叫做’thenetfilter-hacking guide’也是很不錯的參考讀物。
[align=left] [/align]
[align=left]成功的穿過netfilter鉤子函數後,net/ipv4/ipv_input.c:in_rcv_finish()函數被調用。[/align]
[align=left]在ip_rcv_finish()內,數據包的目的是根據調用函數net/ipv4/route.c:ip_route_input()來決定的。尤有進者,如果我們的ip數據包含有ip選項,它們會在此處被處理。數據包的傳遞路徑可能會有幾條,依net/ipv4/route.c:ip_route_slow()中做出的裁決而定。[/align]
[align=left] [/align]
[align=left]
net/ipv4/ip_input.c:ip_local_deliver()[/align]
[align=left]數據包的目的地是本地,我們必須處理layer4協議並把數據包傳遞給用戶空間。[/align]
net/ipv4/ip_forward.c:ip_forward()[align=left]數據包的目的地不是本地,我們要把它路由到其它網絡。[/align]
net/ipv4/route.c:ip_error()[align=left]遇到了錯誤,我們無法在路由表中找到何時的項。[/align]
net/ipv4/ipmr.c:ip_mr_input()[align=left]是多播數據包,我們需要做多播路由。[/align]
[align=left] [/align]
4.數據包路由到其它設備
[align=left]如果路由程序決定數據包要被路由到另外一台設備,函數net/ipv4/ip_forward.c:ip_forward()將被調用。[/align]
[align=left]該函數所做的第一件事情是檢查數據包首部的TTL,如果它小於等於1,則直接丟棄之並回復以ICMP超時報文給傳送者。[/align]
[align=left] [/align]
[align=left]我們檢查skb首部的尾空間來判定是否有足夠的尾空間來存放設備的數據鏈路層首部,若沒有,則要把skb做適當的擴充。[/align]
[align=left] [/align]
[align=left]下一步就是吧TTL減1。如果我們的新數據包大於目標設備的MTU並且IP首部的不分片字段被設置,我們將丟棄該數據包並發送一個ICMP錯誤報文給發送者。[/align]
[align=left] [/align]
[align=left]最後,是調用另外一個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()繼續它的工作。[/align]
[align=left] [/align]
[align=left]函數ip_finish_output()做的工作無外乎調用netfilterNF_IP_POST_ROUTING鉤子函數以及把接下來的工作交給ip_finish_output2()。函數ip_finish_output2()把數據鏈路層首部加入我們的skb中,然後調用net/ipv4/ip_output.c:ip_output() 。[/align]