首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

基於原始套接字的網路封包嗅探器實作與混雜模式深度解析

     編輯:關於C++

原始套接字(Raw Socket)繞過傳輸層協定處理,直接收發 IP 層封包,是實作嗅探器、防火牆、IDS 的核心技術。本文以 Windows 平台 SIO_RCVALL IOCTL 為例,完整實作一個可視化嗅探器,深入分析為何無法捕獲回傳封包(ACK/資料回應),並給出 WinPcap/Npcap 移植方案。

核心流程五步驟

  1. socket(AF_INET, SOCK_RAW, IPPROTO_IP) 建立原始套接字。
  2. gethostname + gethostbyname 取得本機 IP。
  3. bind 綁定本機介面。
  4. ioctlsocket(s, SIO_RCVALL, &on) 啟用混雜模式(接收所有經過網卡的封包)。
  5. 迴圈 recvfrom 讀取封包 → 解析 IP/TCP/UDP 標頭 → 顯示/儲存。

關鍵程式碼片段(C++/MFC)

嗅探執行緒


DWORD WINAPI CSniffer::SnifferThread(LPVOID lpData) {
    CSniffer *pThis = (CSniffer*)lpData;
    SOCKET hSniffer = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
    DWORD on = 1;
    char hostname[32];
    gethostname(hostname, 32);
    hostent *he = gethostbyname(hostname);

    SOCKADDR_IN local = {AF_INET, htons(0)};
    memcpy(&local.sin_addr, he->h_addr_list[0], he->h_length);
    bind(hSniffer, (sockaddr*)&local, sizeof(local));

    ioctlsocket(hSniffer, SIO_RCVALL, &on);  // 關鍵:開啟混雜模式

    char buf[65536];
    while (true) {
        int len = recv(hSniffer, buf, sizeof(buf), 0);
        if (len != SOCKET_ERROR)
            pThis->IPHeadAnalyze(buf, len);  // 解析封包
    }
    return 0;
}

IP 標頭解析


#pragma pack(1)
struct ip_hdr {
    unsigned char  ver_ihl;      // 版本(4) + 標頭長度(4)
    unsigned char  tos;
    unsigned short total_len;
    unsigned short id;
    unsigned short frag_off;
    unsigned char  ttl;
    unsigned char  proto;        // 6=TCP, 17=UDP, 1=ICMP
    unsigned short checksum;
    unsigned int   src_ip;
    unsigned int   dst_ip;
};
#pragma pack()

int CSniffer::IPHeadAnalyze(const char *buf, int len) {
    ip_hdr *iph = (ip_hdr*)buf;
    int ihl = (iph->ver_ihl & 0x0F) * 4;
    if (len < ihl) return -1;

    // 檢查校驗和
    if (checksum(iph, ihl) != 0) return -2;

    // 解析 TCP/UDP 埠號
    if (iph->proto == 6) {  // TCP
        tcp_hdr *tcph = (tcp_hdr*)(buf + ihl);
        // 記錄 src_ip:src_port ↔ dst_ip:dst_port
    }
    // 存入虛擬清單控制項顯示
    return 0;
}

為何捕獲不到回傳封包?根因分析

現象原因驗證方式
僅見發出的 SYN/HTTP GETNDIS 迷你埠驅動 僅在「發送路徑」複製封包給原始套接字,接收路徑未複製Wireshark 可見雙向 → 驅動層面無問題
Chrome 下載幾百 KB/s 嗅探器無反應大量資料封包走「接收路徑」,同上原因遺漏對比 netsh trace 完整捕獲
原始碼註解「感覺沒真正進入混雜模式」SIO_RCVALL 在 Vista+ 僅對「本機發送/廣播/多播」有效,非真正混雜模式需 WinPcap/Npcap 驅動層攔截

Windows 原始套接字限制演進

  • XP/2003:SIO_RCVALL 真正混雜模式(需管理員)。
  • Vista/7/8/10/11:強制限制,僅能捕獲本機發送、廣播/多播、本機為目的地的單播。無法捕獲經過本機但非發給本機的封包(真正混雜模式需 NDIS 驅動)。

正確解決方案:WinPcap / Npcap

為什麼必須用驅動級庫

  1. NDIS 6.x LightWeight Filter (LWF) 驅動 掛載於協定邊緣,攔截所有進出封包。
  2. 用戶態呼叫 pcap_open_live(..., 1, ...) 第三參數 promisc=1 真正開啟混雜模式。
  3. 跨平台:同一套 API 支援 Windows/Linux/macOS(libpcap)。

Npcap 移植關鍵點


// 取代原始套接字建立
pcap_t *handle = pcap_open_live("\\Device\\NPF_{GUID}", 65536, 1, 1000, errbuf);
pcap_loop(handle, -1, packet_handler, (u_char*)pThis);

// 回調函式
void packet_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes) {
    CSniffer *pThis = (CSniffer*)user;
    pThis->IPHeadAnalyze((const char*)bytes, h->caplen);
}

虛擬清單控制項優化

大量封包時必須使用 LVS_OWNERDATA(虛擬模式):

  • 資料留在 std::vector,清單只負責繪製可見列。
  • SetItemCountEx(count) 通知列數變更,OnGetDispInfo 按需提供文字。
  • 插入新封包:vec.push_back(...); SetItemCountEx(vec.size()); O(1) 擴展。

總結與建議

  1. 原始套接字 SIO_RCVALL 適合本機流量審計(出站封包、本機服務存取),不適合一般網路嗅探。
  2. 生產級嗅探器必須基於 Npcap/WinPcap/libpcap。
  3. 虛擬清單 + 執行緒池 + 無鎖佇列是高效能 UI 架構標配。
  4. 封包解析建議使用成熟庫(libnet, tins, pcapplusplus)避免手寫標頭結構體出錯。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved