原始套接字的特點
原始套接字(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地址。
connect函數:調用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包頭。
轉自:https://www.cnblogs.com/new0801/p/6176964.html