C++ 捕獲本機網絡卡的IP包並對其解析的實現
程式設計要求:捕獲本機網絡卡的IP包,對捕獲的IP包進行解析。要求必須輸出以下欄位:版本號、總長度、標誌位、片偏移、協議、源地址和目的地址。
TCP/IP協議定義了一個在因特網上傳輸的包,稱為IP資料報(IP Datagram).這是一個與硬體無關的虛擬包,由首部和資料兩部分組成.首部的前一部分是固定長度,共 20 位元組,是所有IP資料報必須具有的.在首部的固定部分的後面是一些可選欄位,其長度是可變的。下面我們看一下IP資料包的格式:
具體的說明:
各個欄位說明 |
|
版本 |
IP協議版本號, IPv4此欄位值為4, IPv6此欄位值為6 |
首部長度 |
取值範圍5(0101)~15(1111), 單位為4位元組 |
服務型別 |
長度為8位(由於該欄位一直棄而不用, 因此不用考慮) 服務型別(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`t Fragment不分片), 如果該位為1, 並且傳輸的資料報超過最大傳輸單元(MTU), 則該資料報會被丟棄, 併發送一個ICMP差錯報文; 第三位MF(More Fragment更多分片),表示是否有更多的分片, 如果該位為1, 則說明後續還有分片, 最後一片MF為0 |
片偏移 |
用以指出該分段的第一個資料位元組在原始資料報中的偏移位置(以8位元組為單位),IP分片後每一個分組都具有自己的首部, 而且標誌位相同, 但是片偏移值不同, 通過片偏移值接收端可以重新組裝IP包 |
生存時間(TTL) |
表示資料報最多可經過的路由器的數量 |
協議型別 |
指明IP層上承載的是哪個高階協議, 在分用的過程中, 協議棧知道該交給上層的哪個協議處理, 如1為ICMP, 2為IGMP, 6為TCP, 17為UDP等. |
頭部校驗和 |
保證資料報頭部的資料完整性,但校驗不包括資料部分。這樣做的目的有二:一是所有將資料封裝在IP資料包中的高層協議均含有覆蓋整個資料的校驗和,因此IP資料報沒有必要再對其所承載的資料部分進行校驗。二是每經過一個路由器,IP資料報的頭部要發生改變(如TTL),而資料部分不變,這樣只對發生改變的頭部進行校驗,顯然不會浪費太多的時間。為了減少計算時間,一般不用CRC校驗碼,而是採用更簡單的網際校驗和(Internet Checksum)。 |
選項與填充 |
增加首部的可變部分是為了增加IP資料報的功能, 如支援排錯, 測量以及安全等, 選項長度從1到40位元組不等, 取決於所選擇的專案(選項為4位元組整數倍,否則用0填充); 但這樣就增加了每一個路由器處理資料的開銷, 實際上這些選項很少被使用, 很多路由器都並不考慮IP首部的選項欄位; |
到這裡,搞清楚IP資料包的結構體設定之後,剩下的就是基本的socket程式設計的模式,只不過需要設定幾個選項罷了。
詳細見程式碼,有具體的註釋:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#include <string.h>
#include <mstcpip.h>
#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 << "第 "<<cnt++<<" 個IP資料包資訊:" << endl;
cout << "協議版本:" <<(iphead.ver_hlen >> 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程式,右鍵以管理員許可權執行即可。
結果截圖: