資料在底層的傳播套接字

2021-07-22 09:40:13 字數 4729 閱讀 7346

分析資料在協議棧底層的流程:當網絡卡收到資料後,產生硬體中斷,由中斷處理程式(一般為網絡卡驅動程式所註冊)從網絡卡內讀取資料,並封裝稱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包。

套接字通訊底層原理

套接字通訊底層原理 由應用程式記憶體拷貝到作業系統,作業系統遵循tcp協議向對方去發,對方接收到並傳送訊號 from socket import client socket af inet,sock stream client.connect 127.0.0.1 8081 通訊迴圈 while tr...

資料報套接字

又稱udp套接字,它以資料報的方式傳輸資料。特點 面向無連線的 不可靠的 資料報套接字 注意 udp沒有3次握手,4次揮手。服務端流程 客戶端流程 socket af inet,soct dgram 建立udp套接字 socket af inet,soct dgram 建立udp套接字 bind 繫...

UDP套接字的資料傳輸 套接字的關閉

傳送資料 include include ssize t sendto int s,const void msg,size t len int flags,const struct sockaddr to,socklen t tolen 函式sendto的功能與引數send類似,但函式sendto不...