編寫 C++ 程序捕獲本機網卡上的 IP 數據包,並解析輸出關鍵字段:版本號、總長度、標志位、片偏移、協議類型、源地址、目的地址。
IPv4 數據報頭部固定 20 字節,包含以下關鍵字段(位域結構):
| 字段 | 位數 | 說明 |
|---|---|---|
| 版本 | 4 | IPv4 為 4,IPv6 為 6 |
| 首部長度 | 4 | 單位 4 字節,最小 5 (20 字節),最大 15 (60 字節) |
| 服務類型 | 8 | 含優先權、TOS 子字段,現多忽略 |
| 總長度 | 16 | 首部+數據總字節數,最大 65535 |
| 標識 | 16 | 唯一標識數據報,分片共享同一標識 |
| 標志位 | 3 | DF(不分片)、MF(更多片段) |
| 片偏移 | 13 | 分片在原數據報中的偏移 (單位 8 字節) |
| 生存時間 TTL | 8 | 最大路由躍點數,防止循環 |
| 協議類型 | 8 | 上層協議:1=ICMP, 6=TCP, 17=UDP 等 |
| 首部校驗和 | 16 | 僅校驗首部,網際校驗和算法 |
| 源/目的地址 | 32+32 | IPv4 地址 |
socket(AF_INET, SOCK_RAW, IPPROTO_IP) 接收 IP 層數據。WSAIoctl(sock, SIO_RCVALL, ...) 開啟接收所有經過綁定網卡的數據包。ntohs(), ntohl(), inet_ntoa() 處理大端序。#pragma pack(1) 或位域確保與協議頭部一致。#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#include <mstcpip.h>
#pragma comment(lib, "Ws2_32.lib")
using namespace std;
// IP 首部結構體 (對應 IPv4 固定 20 字節)
typedef struct _IP_HEAD {
BYTE ver_hlen; // 版本(高4位) + 首部長度(低4位)
BYTE tos; // 服務類型
WORD total_len; // 總長度 (網絡序)
WORD id; // 標識 (網絡序)
WORD flags_frag; // 標志位(高3位) + 片偏移(低13位)
BYTE ttl; // 生存時間
BYTE protocol; // 協議類型
WORD checksum; // 首部校驗和 (網絡序)
DWORD src_addr; // 源地址 (網絡序)
DWORD dst_addr; // 目的地址 (網絡序)
} IP_HEAD;
int packet_count = 0;
void DecodeIP(char* buffer, int len) {
if (len < sizeof(IP_HEAD)) return;
IP_HEAD* ip = (IP_HEAD*)buffer;
cout << "第 " << ++packet_count << " 個 IP 數據包信息:" << endl;
cout << " 版本號: " << (ip->ver_hlen >> 4) << endl;
cout << " 首部長度: " << ((ip->ver_hlen & 0x0F) * 4) << " 字節" << endl;
cout << " 總長度: " << ntohs(ip->total_len) << " 字節" << endl;
cout << " 標識: 0x" << hex << ntohs(ip->id) << dec << endl;
cout << " 標志位: DF=" << ((ip->flags_frag >> 14) & 1)
<< " MF=" << ((ip->flags_frag >> 13) & 1) << endl;
cout << " 片偏移: " << (ip->flags_frag & 0x1FFF) * 8 << " 字節" << endl;
cout << " TTL: " << (int)ip->ttl << endl;
cout << " 協議: " << (int)ip->protocol
<< (ip->protocol==6?" (TCP)":ip->protocol==17?" (UDP)":ip->protocol==1?" (ICMP)":"") << endl;
cout << " 源地址: " << inet_ntoa(*(in_addr*)&ip->src_addr) << endl;
cout << " 目的地址: " << inet_ntoa(*(in_addr*)&ip->dst_addr) << endl;
cout << "------------------------------------------------------------" << endl;
}
int main() {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2), &wsaData)) {
cerr << "WSAStartup 失敗: " << WSAGetLastError() << endl;
return -1;
}
atexit(WSACleanup);
// 創建原始套接字
SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
if (sock == INVALID_SOCKET) {
cerr << "Socket 創建失敗: " << WSAGetLastError() << endl;
return -1;
}
// 獲取本機 IP 並綁定
char hostname[128];
gethostname(hostname, sizeof(hostname));
hostent* host = gethostbyname(hostname);
sockaddr_in addr = {AF_INET, 0, *(in_addr*)host->h_addr_list[0]};
if (bind(sock, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
cerr << "綁定失敗: " << WSAGetLastError() << endl;
closesocket(sock);
return -1;
}
// 開啟混亂模式
DWORD dwValue = 1, dwBytesReturned = 0;
if (WSAIoctl(sock, SIO_RCVALL, &dwValue, sizeof(dwValue),
NULL, 0, &dwBytesReturned, NULL, NULL) == SOCKET_ERROR) {
cerr << "SIO_RCVALL 失敗: " << WSAGetLastError() << endl;
closesocket(sock);
return -1;
}
// 設置阻塞模式
u_long mode = 0;
ioctlsocket(sock, FIONBIO, &mode);
cout << "開始捕獲數據包... (Ctrl+C 停止)" << endl;
char buffer[65535];
int len;
while ((len = recv(sock, buffer, sizeof(buffer), 0)) > 0) {
DecodeIP(buffer, len);
}
closesocket(sock);
return 0;
}
SIO_RCVALL 會失敗 (錯誤碼 10013/10022)。sizeof(IP_HEAD) == 20。SIO_RCVALL_IGMPMCAST 或使用 NDIS/Libpcap/WinPcap 替代方案。此實現展示了 Windows 平台下底層網絡編程的核心流程,是理解網絡協議、開發嗅探器/防火牆的基礎。生產環境建議使用成熟庫如 WinPcap/Npcap 或 Linux 上的 libpcap。