1. 原始套接字能力:
(1) 進程可以讀寫ICMP,IGMP等分組,如ping程序;
(2) 進程可以讀寫內核不處理協議字段的ipv4數據報;如OSPF等;
(3) 進程可以使用IP_HDRINCL套接字選項自行構造ipv4首部;
2. 原始套接字的創建:
int sockfd; sockfd = socket(AF_INET, SOCK_RAW, protocol);
開啟ip頭構造選項:
const int on = 1; if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0){ error... }
當選項開啟時,我們需要構造完整的IP首部,下列情況除外:
(1) IP總是計算並存儲IP首部校驗和;
(2) 如果我們將IP標識字段設置為0,內核將設置該字段;
(3) 如果源ip地址是INADDR_ANY,IP將把它設置為外出接口的IP地址;
(4) 如何設置IP選項取決於實現。有些實現取出我們預先使用IP_OPTIONS套接字選項設置的任何IP選項,把它們添加到我們的構造首部中,而其他實現則要求我們親自在首部指定任何期望的IP選項;
(5) IP首部中的有些字段必須以主機字節序填寫,有些字段必須以網絡字節序填寫,具體取決於實現。linux上所有字節都采用網絡字節序;
3. 原始套接字的輸出:
(1) 普通輸出通過調用sendto或者sendmsg並指定目的IP地址完成,如果套接字已經連接,那么也可以使用write,writev,send;
(2) 如果IP_HDRINCL套接字選項未開啟,那么由進程讓內核發送的數據的起始地址是IP首部之后的第一個字節,因為內核將構造IP首部並把它置於來自進程的數據之前。內把所構造的ipv4首部的協議字段設置成來自socket調用的第三個參數;
(3) 如果IP_HDRINCL套接字選項已開啟,那么由進程讓內核發送的數據的起始地址是IP首部的第一個字節。進程調用輸出函數寫出的數據量必須包括IP首部大小。整個IP首部由進程構造。不過(a)ipv4標識字段可以設置為0,從而告知內核設置該值;(b) ipv4首部校驗和字段總是由內核計算並存儲 (c) ipv4選項字段是可選的;
(4) 內核會對超出外出接口MTU的原始分組執行分片;
另:ip首部之后所含有的其他任何首部的校驗和需要用戶進程計算;
4. 原始套接字的輸入:
(1) 接收到的udp/tcp分組決不傳遞到任何原始套接字;進程只能通過數據鏈路層讀取這些分組;但是可以發哦;
(2) 大多數icmp分組在內核處理完其中的icmp消息后傳遞到原始套接字;源自Berkeley的實現把不是回射請求,時間戳請求或者地址掩碼請求(這三類均有內核處理)的所有接收到的icmp分組傳遞給原始套接字;
(3) 所有igmp分組在內核完成處理其中的igmp消息后傳遞給原始套接字;
(4) 內核不認識協議字段的所有ip數據報傳遞到原始套接字;內核對這些分組進行ip頭最小驗證:ip版本,ip校驗和,首部長度,目的ip地址;
(5) 如果某個數據報以片段的形式到達,那么在它的所有片段均到達並重組出該數據報之前,不會傳遞任何片段分組到原始套接字;
(6) 如果創建原始套接字制定了非0協議參數,那么接收到的數據報協議字段必須匹配該值,否則該數據報不會傳遞到該套接字;
(7) 如果原始套接字已由bind調用綁定了某個本地ip地址,那么接收到的數據報目的IP地址必須匹配這個綁定地址,否則該數據報不會傳遞到這個套接字;
(8) 如果這個原始套接字由connect調用制定了某個外地ip地址,那么接收到的數據報的源ip地址必須匹配該連接,否則數據報不會傳遞到該套接字;
注意:如果一個原始套接字是以0值協議參數創建,並且既未調用bind,也未調用connect,那么該套接字將接收可由內核傳遞到原始套接字的每個原始數據報的一個副本;
無論何時往一個原始IPv4套接字傳遞一個接收的數據報,傳到該套接字所在進程都包括ip首部在內的完整數據報;ipv6不包含首部;
5. 數據鏈路層訪問:
接收所有幀:
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
接收ipv4幀:
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
注意:如果設置接口為混雜模式,則可接收所有經過數據流,而不論其目的地址是否是它;