IP數據包的輸入與輸出

IP層主要函數之間的調用關係如下圖所示:

上面的圖主要是拷貝的《Linux內核源碼剖析----TCP/IP實現上冊》中的圖11.3,原圖中有部分錯誤,所以這裏重新繪製了一下,並且去掉了一些冗餘的部分。
下面簡述一下數據包傳遞的大致過程:
一、IP數據包的輸入
ip_rcv()是網絡層(IPv4,以下同)接收數據包的入口函數,鏈路層在接收到數據包後調用netif_receive_skb()將數據包傳遞到網絡層。網絡層的packet_type實例爲ip_packet_type,在Internet協議族的初始化函數inet_init()中調用dev_add_pack()來註冊到ptype_base散列表中。
ip_rcv()中接收到數據包後會檢查是否是一個完整的、沒有錯誤的數據包。如果是合法的數據包,會傳遞到netfilter的NF_INET_PRE_ROUTING鉤子點進行處理,如果鉤子處理函數中沒有截獲數據包,則傳遞到ip_rcv_finish()進行下一階段的處理。
ip_rcv_finish()會檢查是否已設置路由緩存項,如果沒有,則調用ip_route_input()來查找路由,如果查找失敗,則丟棄數據包。如果找到路由項,在路由緩存項描述結構dst_entry的input和outpu接口中設置下一個階段的處理函數。
如果是要轉發的數據包,input接口設置的是ip_forward()函數,output接口設置的是ip_output()。ip_forward()中會檢查數據包的IP選項,並做相應處理。如果沒有問題,將數據包的TTl值減1,然後將數據包傳遞到NF_INET_FORWARD鉤子點進行處理。如果鉤子處理函數沒有截獲數據包,則調用路由緩存項的output接口輸出數據包,即ip_output()函數。
如果是要交給本地的數據包,input接口設置的ip_local_deliver()函數,output接口設置的ip_rt_bug()。ip_local_deliver()中會檢查接收到的數據包是否是IP報文的分片,如果是,則調用ip_defrag()組裝分片的各個部分。如果分片沒有到齊或出錯,則直接返回,當前報文不再向傳輸層傳遞。如果不是IP報文的分片或IP報文的所有分片組裝成功,則將報文傳遞到netfilter的NF_INET_LOCAL_IN鉤子點進行處理。如果鉤子處理函數沒有截獲報文,則調用ip_local_deliver_finish()將報文傳遞到傳輸層。在傳遞到傳輸層之前,ip_local_deliver_finish()中會將sk_buff中的成員指向傳輸層報文的位置。
二、IP數據包的輸出
傳輸層向IP層輸出數據包主要是調用ip_queue_xmit()和ip_push_pending_frames()。TCP協議主要使用ip_queue_xmit()來發送數據,UDP協議主要使用ip_push_pending_frames(),不過這兩者都是由本地發送的數據包,需要轉發的數據包也需要IP層來處理。本地發送的數據包需要傳遞到netfilter的NF_INET_LOCAL_OUT鉤子點處理,轉發的數據包則不需要。如果沒有鉤子處理函數截獲數據包,則繼續進行處理。
如果是組播數據包,則output接口設置的是ip_mc_output()接口;如果是單播數據包,則output接口設置的ip_output。這裏只討論單播數據包。ip_output()中只是簡單地將路由緩存項中存儲的網絡設備設置到數據包的dev成員中,並且設置三層的協議類型(protocol成員),然後將數據包傳遞到netfilter的NF_INET_POST_ROUTING鉤子點。如果沒有鉤子處理函數截獲數據包,則將數據包傳遞到ip_finish_output()中處理。
ip_finish_output()中會檢查報文的長度是否大於MTU。如果大於MTU,則調用ip_fragment()對數據包進行分片,然後再調用ip_finish_output2()將數據包通過鄰居子系統傳遞到網絡設備。如果不需要分片,則直接調用ip_finish_output2()處理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章