分析數據在協議棧底層的流程
分析數據在協議棧底層的流程:當網卡收到數據后,產生硬件中斷,由中斷處理程序(一般為網卡驅動程序所注冊)從網卡內讀取數據,并封裝稱sk_buff{}結構,然后把這些數據傳遞給函數netif_rx()進行進一步的處理。
函數netif_rx()根據當前接收隊列的擁擠情況,選擇丟棄還是接收,如果是接收,則將接收到的sk_buff{}掛到接收隊列softnet_data[CPU]->input_pkt_queue上,并調用函數__cpu_raise_softirq()激活軟中斷NET_RX_SOFTIRQ,相應的處理函數是net_rx_action()。
在函數net_rx_action()中根據數據包的協議類型,調用相應的處理函數。對于IP包,處理函數是ip_rcv()。
函數ip_rcv()對IP包進行了一系列必要的檢查(包括檢查校驗和),最終調用函數ip_rcv_finish()對數據包進行向上傳輸。
函數ip_rcv_finish()首先調用函數ip_route_input()獲取路由,檢測該包是發給本機的還是要進行轉發的,如果要進行轉發,則調用調用函數ip_forward()進行轉發,否則調用函數ip_local_deliver()進一步向上傳遞數據包。
函數ip_local_deliver()首先進行了防火墻的過濾工作,最終調用函數ip_local_deliver_finish()向上傳遞數據。
在函數ip_local_deliver_finish()中,會檢查是否有匹配協議(如根據IP頭判斷我們的數據包是TCP包,則要判斷是否有接收TCP包的原始套接口。當然,如果有接收所有IP包的原始套接口存在也是可以的)的原始套接口。如果有,則調用函數raw_v4_input()進行處理。
在函數raw_v4_input()中,要進一步進行匹配,這次匹配的依據有四個,依次是:協議、源地址、目的地址和接收接口。分別對每一個匹配成功的原始套接口調用函數raw_rcv()傳遞一個克隆的以sk_buff{}為結構的數據包。
接下來的幾個函數都很簡單,調用順序依次是raw_rcv()、raw_rcv_skb()和sock_queue_rcv_skb()。這幾個函數基本上都是簡單的依次調用關系。最后調用函數sock_queue_rcv_skb(),該函數經過skb_queue_tail()函數將數據包sk_buff{}放入了接收隊列sk->receive_queue的末尾。
原始套接口的協議棧實現――原始套接口的綁定
這里我們簡略分析,對原始套接口綁定調用的是函數sk->prot->bind,在原始套接口的創建中我們給出了套接口的sk->prot即structproto結構變量raw_prot,從中可以看出和sk->prot->bind指針實質指向函數raw_bind()。
在這個函數中首先判斷套接口狀態,如果是TCP_CLOSE的話,就退出。然后有對參數進行了一些常規檢查。同時,如果發現要綁定的地址是廣播或多播的話,也會退出。如果通過了這些檢查,就進行一些賦值操作,將用戶要綁定的地址賦值到sk->rcv_saddr和sk->saddr中,即:
sk->rcv_saddr=sk->saddr=addr->sin_addr.s_addr
然后會正常退出。
注意,這里沒有對端口做任何操作,即使用戶指定了要綁定的端口,內核也不予理睬。
原始套接口的協議棧實現――原始套接口的連接
從原始套接口的創建一節中給出的structproto結構可以看出,原始套接口的連接其實調用的是函數udp_connect(),好興奮,終于見到了不那么"原始"的東西了。
在這個函數中,首先對用戶的參數進行了一些檢查。當然,它也檢查了用戶指定的網域是否是"AF_INET",如果不是,會返回一個EAFNOSUPPORT錯誤。
然后,該函數調用了函數ip_route_connect()來獲取一個到目的地址的路由,如果失敗,也會返回錯誤。
接下來的工作看起來就有點令人難以理解。
它檢查了套接口是否指定了源地址,如果沒有指定,則將尋找到的路由的源地址賦值給這個套接口的源地址,即:
if(!sk->saddr)
sk->saddr=rt->rt_src;/*Updatesourceaddress*/
if(!sk->rcv_saddr)
sk->rcv_saddr=rt->rt_src;
其中sk代表我們套接口的sock{}結構,rt代表我們找到的路由,是一個structrtable{}結構。
最后,就是將目的地址和目的端口賦值到我們的套接口的指定字段中,同時更新套接口狀態,即:
sk->daddr=rt->rt_dst;
sk->dport=usin->sin_port;
sk->state=TCP_ESTABLISHED;
原始套接口的協議棧實現――原始套接口的關閉
根據上面的經驗,原始套接口的關閉應該調用函數raw_close(),這個函數只是簡單的調用了函數ip_ra_control(),在函數ip_ra_control()中,將該套接口從鏈表ip_ra_chain中刪除,然后釋放到該套接口占用的所有空間。
原始套接口的應用
根據前面的分析,針對原始套接口的應用,我們可以得出以下結論。
綁定的問題
可以對原始套接口調用bind函數,但并不常用。該函數僅用來設置本地地址。對于一個原始套接口而言,端口號是沒有意義的。當進行輸出的時候,bind設置在原始套接口上所發送的數據報中將要用到的源IP地址(僅當IP_HDRINCL套接口選項未設置時);若不調用bind,則由內核將源IP地址設成外出接口的主IP地址。
連接的問題
在原始套接口上可調用connect函數,但也不常用。connect函數僅設置目的地址。再重申一遍:端口號對原始套接口而言沒有意義。對于輸出而言,調用connect之后,由于目的地址已經指定,我們可以調用write或send,而不是sendto了。
輸出的問題
1)普通輸出通常通過sendto或sendmsg并指定目的IP地址來完成,如果套接口已經連接,也可以調用write、writev或send。
2)如果IP_HDRINCL選項未設置,則內核寫的數據起始地址是IP頭部之后的第一個字節。因為這種情況下,內核將構造IP頭部,并將它安在來自進程數據之前。內核將IPv4頭部的協議字段設置成用戶在調用socket函數時所給的第三個參數。
3)如果IP_HDRINCL選項已設置,則內核寫的數據其實地址是IP頭部的第一個字節。用戶所提供的數據必須包括IP頭部。此時進程構造除了以下兩項以外的整個IP頭部:
(1)IPv4標示字段可以設為0,要求內核設置該值。而且僅當該字段為0時,內核才為其設置。
(2)IPv4頭部校驗和由內核來計算和存儲。
4)如果創建原始套接口時指定了協議類型,即第三個參數protocol,那也并不是說只能發該類型的數據包。如,即使將protocol指定為IPPROTO_TCP,也可以發送用戶自己組裝的UDP報文,不過此時如果IP_HDRINCL選項未設置,那么內核將會在IP頭的協議字段指明后面的報文為TCP報文(不過此時卻為UDP報文)。等數據包發送到對方TCP層,一般說來會因為找不到合適的TCP套接口接收該數據包而被丟棄。不過該包可以在目標主機的原始套接口上接收到。
5)正如前面所述,任何時候,IP頭的校驗和都是由內核來設置的。
6)內核任何時候那會都不會對IP包以后的字段進行校驗和驗證。如,即使我們指定第三個參數protocol為IPPROTO_TCP,在數據發送時內核也不會對進行TCP校驗和計算和驗證。
7)如果IP_HDRINCL選項已設置,按照常規,我們應該組建自己的IP頭,但是即使我們沒有組建IP頭,用sendto或sendmsg并指定目的IP地址來發送數據是照樣可以完成的。但是這樣的數據包在目標機上用原始套接口是接收不到的,因為在ip_rcv()中要對IP頭進行驗證,并且要分析校驗和,所以該包會被丟棄,不過在鏈路層應該能夠接收到該數據包。
8)如果設置了IP_HDRINCL選項,并且數據包超長,那么數據會被丟棄,并會返回出錯碼EMSGSIZE。如果未設置IP_HDRINCL選項,并且數據包超長,那么數據包會被分片。
輸入的問題
1)原始套接口可以接收到任何TCP或UDP報文。
2)要想接收到原始套接口,首先要接收的數據包必須有一個完整的、正確的IP頭,否則不能通過ip_rcv()中的包頭檢查和檢驗和驗證。
3)在原始套接口接收的數據包過程中,內核會對接收的IP包進行校驗和驗證,但不會對IP包以后的任何字段進行檢測和驗證。如,我們創建原始套接口時,所指定的protocol參數為IPPROTO_TCP,內核也不會進行TCP校驗和驗證,而是直接把IP頭中協議字段為TCP的所有數據包都復制一份,提交給該原始套接口。
4)用原始套接口接收到的TCP包都是進行了IP重組以后,TCP排序以前的報文。
5)如果在創建原始套接口時,所指定的protocol參數不為零,(socket的第三個參數),則接收到的數據報的協議字段應該與之匹配。否則該數據報不傳遞給該套接口。
6)如果此原始套接口上綁定了一個本地IP地址,那么接收到的數據報的目的IP地址應該與該綁定的IP地址相匹配,否則該數據包將不傳遞到該套接口。
7)如果此原始套接口通過connect指定了一個對方IP地址,那么接收到的數據包的源IP地址應與該以連接地址相匹配,否則該數據包不傳遞給該套接口。
8)如果一個原始套接口以protocol參數為0的方式創建,并且未調用connect或bind,那么對于內核傳遞給原始套接口的每一個原始數據報,該套接口都會收到一份拷貝。
9)原始套接口接收不到任何的ARP或RARP協議類型的套接口,因為net_rx_action()
會把ARP或RARP協議類型的數據包傳遞給ARP的接收函數類處理,不會傳遞給IP層的接收函數ip_rcv()。
10)原始套接口并不是可以接收到任何的ICMP類型的數據包,因為有些ICMP類型的數據包在傳遞給原始套接口之前已經被系統所響應,并不再向上層傳遞。
11)如果對方的數據包分片了,由于原始套接口的接收是在IP上層,所以會接收到重組以后的原始IP包。
【編輯推薦】

















