編程要求:捕獲本機網卡的IP包,對捕獲的IP包進行解析。要求必須輸出以下字段:版本號、總長度、標志位、片偏移、協議、源地址和目的地址。
TCP/IP協議定義了一個在因特網上傳輸的包,稱為IP數據報(IP Datagram).這是一個與硬件無關的虛擬包,由首部和數據兩部分組成.首部的前一部分是固定長度,共 20 字節,是所有IP數據報必須具有的.在首部的固定部分的後面是一些可選字段,其長度是可變的。下面我們看一下IP數據包的格式:
具體的說明:
各個字段說明
版本
IP協議版本號,IPv4此字段值為4,IPv6此字段值為6
首部長度
取值范圍5(0101)~15(1111),單位為4字節,包括固定部分和可選部分,因此首部最長為60字節,最短為20字節(不包括選項和填充部分);
服務類型
長度為8位(由於該字段一直棄而不用,因此不用考慮)
PHPbrHc1uWbdmWbkrH0Y0ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-bIi4WUvYETgN-TLwGUv3EPjnkPj0krjRd" class="baidu-highlight" rel="nofollow">服務類型(TO S)(8 bit)字段包括一個3 bit的優先權子字段(取值可以從000-111所有值),4 bit的TO S子字段和1 bit未用位但必須置0
總長度
該字段長度為16位,以字節為單位,總長度包含IP的頭部和數據部分,IP數據報最大長度為65535字節,但是注意最大不要超過MTU的長度
標識
16位長度,唯一標識一個數據報,可以將之當成一個計數器,每發送一個數據包,則該值加1,如果數據報分片,則每個分片的標識都一樣,各個分片共享一個標識號
標志
3位標志中第一位不使用,第二位為DF(Don`tFragment不分片),如果該位為1,並且傳輸的數據報超過最大傳輸單元(MTU),則該數據報會被丟棄,並發送一個ICMP差錯報文;第三位MF(MoreFragment更多分片),表示是否有更多的分片,如果該位為1,則說明後續還有分片,最後一片MF為0
片偏移
用以指出該分段的第一個數據字節在原始數據報中的偏移位置(以8字節為單位),IP分片後每一個分組都具有自己的首部,而且標志位相同,但是片偏移值不同,通過片偏移值接收端可以重新組裝IP包
生存時間(TTL)
表示數據報最多可經過的路由器的數量.取值0~255,每經過一個路由器,TTL值減1,為0時被丟棄,並發送ICMP報文通知源主機,TTL可以避免數據報在路由器之間不斷循環(Tranceroute程序的實現原理)
協議類型
指明IP層上承載的是哪個高級協議,在分用的過程中,協議棧知道該交給上層的哪個協議處理,如1為ICMP,2為IGMP,6為TCP,17為UDP等.
頭部校驗和
保證數據報頭部的數據完整性,但校驗不包括數據部分。這樣做的目的有二:一是所有將數據封裝在IP數據包中的高層協議均含有覆蓋整個數據的校驗和,因此IP數據報沒有必要再對其所承載的數據部分進行校驗。二是每經過一個路由器,IP數據報的頭部要發生改變(如TTL),而數據部分不變,這樣只對發生改變的頭部進行校驗,顯然不會浪費太多的時間。為了減少計算時間,一般不用CRC校驗碼,而是采用更簡單的網際校驗和(InternetChecksum)。
選項與填充
增加首部的可變部分是為了增加IP數據報的功能,如支持排錯,測量以及安全等,選項長度從1到40字節不等,取決於所選擇的項目(選項為4字節整數倍,否則用0填充);但這樣就增加了每一個路由器處理數據的開銷,實際上這些選項很少被使用,很多路由器都並不考慮IP首部的選項字段;
到這裡,搞清楚IP數據包的結構體設置之後,剩下的就是基本的socket編程的模式,只不過需要設置幾個選項罷了。
詳細見代碼,有具體的注釋:
#define _CRT_SECURE_NO_WARNINGS #include#include #include #include #include #pragma comment(lib,"Ws2_32.lib") using namespace std; //IP首部 typedef struct tIPPackHead { BYTE ver_hlen; //IP協議版本和IP首部長度。高4位為版本,低4位為首部的長度(單位為4bytes) BYTE byTOS; //服務類型 WORD wPacketLen; //IP包總長度。包括首部,單位為byte。[Big endian] WORD wSequence; //標識,一般每個IP包的序號遞增。[Big endian] union { WORD Flags; //標志 WORD FragOf;//分段偏移 }; BYTE byTTL; //生存時間 BYTE byProtocolType; //協議類型,見PROTOCOL_TYPE定義 WORD wHeadCheckSum; //IP首部校驗和[Big endian] DWORD dwIPSrc; //源地址 DWORD dwIPDes; //目的地址 BYTE Options; //選項 } IP_HEAD; int cnt; int DecodeIP(char *buf, int len) { int n = len; if (n >= sizeof(IP_HEAD)) { IP_HEAD iphead; iphead = *(IP_HEAD*)buf; cout << "第 "< > 4) << endl; cout << "首部長度:" << ((iphead.ver_hlen & 0x0F) << 2) << endl;//單位為4字節 cout << "服務類型:Priority: " << (iphead.byTOS >> 5) << ",Service: " << ((iphead.byTOS >> 1) & 0x0f) << endl; cout << "IP包總長度:" << ntohs(iphead.wPacketLen) << endl; //網絡字節序轉為主機字節序 cout << "標識:" << ntohs(iphead.wSequence) << endl; cout << "標志位:" << "DF=" << ((iphead.Flags >> 14) & 0x01) << ",MF=" << ((iphead.Flags >> 13) & 0x01) << endl; cout << "片偏移:" << (iphead.FragOf & 0x1fff) << endl; cout << "生存周期:" << (int)iphead.byTTL << endl; cout << "協議類型:" << int(iphead.byProtocolType) << endl; cout << "首部校驗和:" << ntohs(iphead.wHeadCheckSum) << endl; cout << "源地址:" << inet_ntoa(*(in_addr*)&iphead.dwIPSrc) << endl; cout << "目的地址:" << inet_ntoa(*(in_addr*)&iphead.dwIPDes) << endl; cout << "==============================================================" << endl << endl; } return 0; } void AutoWSACleanup() { ::WSACleanup(); } int main() { int n; WSADATA wd; n = WSAStartup(MAKEWORD(2, 2), &wd); if (n) { cout << "WSAStartup函數錯誤!" << endl; return -1; } atexit(AutoWSACleanup); //創建SOCKET SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_IP); if (sock == INVALID_SOCKET) { cout << WSAGetLastError(); return 0; } //獲取本機地址 char name[128]; if (-1 == gethostname(name, sizeof(name))) { closesocket(sock); cout << WSAGetLastError(); return 0; } struct hostent * pHostent; pHostent = gethostbyname(name); //綁定本地地址到SOCKET句柄 sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr = *(in_addr*)pHostent->h_addr_list[0]; //IP addr.sin_port = 8888; //端口,IP層端口可隨意填 if (SOCKET_ERROR == bind(sock, (sockaddr *)&addr, sizeof(addr))) { closesocket(sock); cout << WSAGetLastError(); return 0; } //設置該SOCKET為接收所有流經綁定的IP的網卡的所有數據,包括接收和發送的數據包 u_long sioarg = 1; DWORD wt = 0; if (SOCKET_ERROR == WSAIoctl(sock, SIO_RCVALL, &sioarg, sizeof(sioarg), NULL, 0, &wt, NULL, NULL)) { closesocket(sock); cout << WSAGetLastError(); return 0; } //我們只需要接收數據,因此設置為阻塞IO,使用最簡單的IO模型 u_long bioarg = 0; if (SOCKET_ERROR == ioctlsocket(sock, FIONBIO, &bioarg)) { closesocket(sock); cout << WSAGetLastError(); return 0; } //開始接收數據 //因為前面已經設置為阻塞IO,recv在接收到數據前不會返回。 cnt = 1; char buf[65535]; int len = 0; do { len = recv(sock, buf, sizeof(buf), 0); if (len > 0) { DecodeIP(buf, len); } } while (len > 0); closesocket(sock); return 0; }
最後,由於本程序是需要特權用戶權限的,所以我們找到debug下的exe程序,右鍵以管理員權限運行即可。