Linux系統程式設計(37)—— socket程式設計之原始套接字

尹成發表於2014-09-04

 

原始套接字的特點

 

原始套接字(SOCK_RAW)可以用來自行組裝IP資料包,然後將資料包傳送到其他終端。也就是說原始套接字是基於IP資料包的程式設計(SOCK_PACKET是基於資料鏈路層的程式設計)。另外,必須在管理員許可權下才能使用原始套接字。

原始套介面提供了普通TCP和UDP socket不能提供的3個能力:

1、程式使用raw socket 可以讀寫ICMP、IGMP等分組。這個能力還使得使用ICMP或IGMP構造的應用程式能夠完全作為使用者程式處理,而不必往核心中新增額外程式碼。

2、大多數核心只處理IPv4資料包中一個名為協議的8位欄位的值為1(ICMP)、2(IGMP)、6(TCP)、17(UDP)四種情況。然而該欄位的值還有許多其他值。程式使用raw socket 就可以讀寫那些核心不處理的IPv4資料包了。因此,可以使用原始套接字定義使用者自己的協議格式。

3、通過使用raw socket ,程式可以使用IP_HDRINCL套介面選項自行構造IP頭部。這個能力可用於構造特定型別的TCP或UDP分組等。

 

原始套接字的建立

   int sockfd = socket (AF_INET, SOCK_RAW, protocol);


       protocol常用引數值如下(定義在netinet/in.h):

       IPPROTO_IP = 0,   /* Dummyprotocol for TCP.  *///這個協議的Dummy的意思是系統什麼也不做。
       IPPROTO_ICMP = 1,   /* InternetControl Message Protocol.  */
       IPPROTO_IGMP = 2,   /* InternetGroup Management Protocol. */
       IPPROTO_TCP = 6,   /* TransmissionControl Protocol.  */
       IPPROTO_UDP = 17,   /* UserDatagram Protocol.  */
       IPPROTO_RAW = 255,   /* Raw IPpackets.  */

       如果指定protocol為0(IPPROTO_IP)時,原始套接字可以接收核心傳遞給原始套接字的任何IP資料包

 

        

bind和 connect 函式說明

原始套接字直接使用IP協議的套接字,所以是非面向連線的,使用sendto和recvfrom函式。在這個套接字上能夠呼叫connect和bind函式(一般不這麼用),分別執行繫結對方和本地地址。

bind函式:呼叫bind函式後,傳送資料包的源IP地址將是bind函式指定的地址。該函式僅僅設定本地地址,因為原始套介面不存在埠的概念。如是不呼叫bind,則核心將以發介面的主IP地址填充。假如配置了IP_HDRINCL,那麼必須手工填充每個傳送資料包的源IP地址。

connetc函式:呼叫connect函式後,能夠用write和send傳送資料包。呼叫該函式僅僅設定遠地地址,同樣因為原始套介面不存在埠號的概念。核心將用這個繫結的地址填充IP資料包的目的IP地址。

 

原始套接字的輸出

如果IP_HDRINCL套接字選項未開啟,那麼由程式讓核心傳送的資料的起始位置指的是IP首部之後的第一個位元組,因為核心將構造IP首部並把它置於來自程式的資料之前。核心把所有構造IPv4首部的協議欄位設定成來之sock呼叫的第三個引數。

 

如果IP_HDRINCL套接字選項已開啟,那麼由程式讓核心傳送的資料的起始位置指的是IP首部的第一個位元組。程式呼叫輸出函式寫出的資料量必須包括IP首部的大小。整個IP首部都是由程式構造,不過IP的標識欄位可以設定為0,從而告知核心設定該值;IP首部校驗和欄位總是由核心計算並儲存;IPv4的選項欄位也是可選的。

另外,核心會對超出外出介面MTU的原始分組進行分片。

開啟IP_HDRINCL的程式碼是:

       const int on =1;
       if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on))< 0)
       {
        printf("setsockopt error!\n");
       }

 

原始套接字的輸入

首先要考慮核心將哪些接收到的IP資料包傳遞到原始套接字?這要遵循下面的規則:

 

1、接收到的UDP或者TCP分組絕不傳遞到任何原始套接字,如果一個程式想要讀取含有UDP分組或TCP分組的IP資料包,它就必須在資料鏈路層讀取這些分組(即使用IPPROTO_IP選項讀取整個IP包)。

2、大多數ICMP分組在核心處理完其中的ICMP訊息後傳遞到原始套接字。IGMP亦是如此。

3、核心把不認識其協議欄位的所有IP資料包傳遞給原始套接字。核心把這些分組執行的唯一處理是針對某些IP首部欄位的最小驗證:IP版本,IPv4校驗和,首部長度,以及目的地址。

4、如果某個資料包以片段的形式到達,那麼在它的所有片段均到達且重組出該資料包之前,不傳遞任何片段分組給原始套接字。

當核心有一個需要傳遞到原始套接字的資料包時,它將檢查所有程式上的所有原始套接字,以尋找所有匹配的套接字。每個匹配的套接字將被傳遞送以該IP資料包的一個副本。核心對每個原始套接字均執行以下3個測試,只有這三個測試均為真,核心才把接收到的資料包傳送給這個套接字。

1、如果建立這個原始套接字時指定了非0的協議引數(socket的第三個引數),那麼接收到的資料包協議欄位必須匹配該值。

2、如果這個套接字已由bind呼叫繫結了某個IP地址,那麼接收到的資料包的目的地址必須匹配這個繫結地址。

3、若該套接字呼叫了connect,那麼接收到的資料包的源地址必須匹配這個已連線地址。

注意:如果一個原始套接字是以0值協議引數傳遞的,並且沒有呼叫bind,connect,那麼該套接字將接收可由核心傳遞到原始套接字的每個原始資料包的一個副本。

無論何時往一個原始IPv4套接字上遞送一個IP資料包,傳遞到該套接字所在程式的都是包括IP首部在內的完整資料包。

 

需要注意的地方

 

1、使用 IPPROTO_TCP 和 IPPROTO_UDP選項的原始套接字時,只能發TCP或者UDP資料包(是否需要對IP頭部的操作由 IP_HDRINCL 決定),而不能接收TCP或者UDP協議的資料包,因為TCP和UDP資料包由核心進行協議的判斷,並查詢IP地址和埠號相匹配的socket連線來遞交資料包,而原始套接字沒有埠的概念,因此不能接收TCP或者UDP的資料包。原始套接字只能通過IPPROTO_IP來獲得整個IP資料包,然後從中提取TCP和UDP的資料。

2、使用 IPPROTO_IP 選項時,必須要設定IP_HDRINCL,因為核心自動合成IP資料包頭部時,並不知道協議欄位是什麼,所以必須要使用者自己來構建IP包頭。

 

 

 

相關文章