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

網絡協議校驗和計算:IP、UDP、TCP 偽首部算法與 C 實現

     編輯:關於C

IPv4 協議族中,IP、UDP、TCP 均使用16 位反碼求和校驗和。IP 僅覆蓋首部;UDP/TCP 除覆蓋自身首部與負載外,還必須包含偽首部以驗證源/目的地址與協議類型。本文詳述算法規則、偽首部結構、邊界處理及高性能 C 實現。

校驗和算法核心

  1. 將數據視為16 位字(大端/網絡序)序列,長度為奇數時最後一字節補 0 擴展為 16 位。
  2. 所有 16 位字進行反碼求和:相加產生的進位(carry)回滾加到低 16 位(sum = (sum >> 16) + (sum & 0xFFFF)),直到無進位。
  3. 最終結果取反碼(~sum),作為校驗和字段值。
  4. 發送前校驗和字段置 0;接收端計算包含校驗和字段的總和,結果應為 0xFFFF(全 1)。

偽首部結構


struct pseudo_header {
    uint32_t saddr;      // 源 IP (網絡序)
    uint32_t daddr;      // 目的 IP (網絡序)
    uint8_t  zero;       // 必須為 0
    uint8_t  protocol;   // IPPROTO_TCP(6) / IPPROTO_UDP(17)
    uint16_t length;     // TCP/UDP 段長度 (含首部+數據,網絡序)
};
// 總計 12 字節

長度計算規則

  • IP 校驗和:固定 20 字節(基本首部),選項另算。
  • UDP 長度:sizeof(udphdr) + 數據長度 = 8 + 數據長度。
  • TCP 長度:IP 總長度 - IP 首部長度(含選項)。TCP 首部 20~60 字節。

高性能 C 實現


#include <stdint.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <net/ethernet.h>

// 通用反碼求和:輸入初始 sum 可累加多段
static inline uint16_t in_cksum(const uint16_t* buf, int len, uint32_t sum) {
    int n = len;
    while (n > 1) {
        sum += *buf++;
        n -= 2;
    }
    if (n == 1)                    // 奇數字節:最後一字節作高位,低位補 0
        sum += *(const uint8_t*)buf << 8;

    // 折疊進位
    while (sum >> 16)
        sum = (sum & 0xFFFF) + (sum >> 16);

    return (uint16_t)~sum;
}

// 計算緩衝區 16 位字和(不折疊進位,供累加用)
static inline uint32_t calc_sum(const uint16_t* buf, int len) {
    uint32_t sum = 0;
    while (len > 1) {
        sum += *buf++;
        len -= 2;
    }
    if (len == 1) sum += *(const uint8_t*)buf << 8;
    return sum;
}

// IP 校驗和
static inline void calc_ip_cksum(struct iphdr* ip) {
    ip->check = 0;
    ip->check = in_cksum((const uint16_t*)ip, 20, 0);
}

// UDP/TCP 校驗和(含偽首部)
static inline uint16_t calc_udp_tcp_cksum(const uint16_t* seg, struct iphdr* ip) {
    int seg_len = ntohs(ip->tot_len) - (ip->ihl * 4); // IP 負載長度 = TCP/UDP 段長

    struct pseudo_header ph = {0};
    ph.saddr = ip->saddr;
    ph.daddr = ip->daddr;
    ph.protocol = ip->protocol;
    ph.length = htons(seg_len);

    uint32_t sum = calc_sum((const uint16_t*)&ph, sizeof(ph)); // 偽首部求和
    return in_cksum(seg, seg_len, sum);                        // 累加段數據並折疊取反
}

// 完整處理流程示例
int process_packet(char* pkt) {
    struct ethhdr* eth = (struct ethhdr*)pkt;
    struct iphdr* ip = (struct iphdr*)(pkt + sizeof(struct ethhdr));

    calc_ip_cksum(ip);

    const uint16_t* l4 = (const uint16_t*)((char*)ip + ip->ihl * 4);
    switch (ip->protocol) {
    case IPPROTO_TCP:
        ((struct tcphdr*)l4)->check = 0;
        ((struct tcphdr*)l4)->check = calc_udp_tcp_cksum(l4, ip);
        break;
    case IPPROTO_UDP:
        ((struct udphdr*)l4)->check = 0;
        ((struct udphdr*)l4)->check = calc_udp_tcp_cksum(l4, ip);
        break;
    }
    return 0;
}

關鍵工程細節

  1. 網絡序:偽首部字段、IP 首部字段、TCP/UDP 首部字段均為大端序。主機序機器需 htons/htonl 轉換。
  2. 對齊訪問:uint16_t* 要求 2 字節對齊。若數據包未對齊,需用 memcpy 到對齊緩衝區或逐字節組裝 16 位字。
  3. 零填充:奇數長度數據最後一字節視為高 8 位,低 8 位為 0(即 sum += last_byte << 8)。
  4. 硬件加速:現代 NIC 支持校驗和卸載(TX/RX Checksum Offload),驅動程序需設置 skb->ip_summed = CHECKSUM_PARTIAL 等標誌,內核/硬件自動計算/驗證。
  5. IPv6 差異:IPv6 無首部校驗和;UDP/TCP 偽首部擴展為 40 字節(128 位地址 + 32 位長度 + 8 位下一頭部 + 24 位保留)。

驗證測試


// 已知測試向量 (RFC 1071, RFC 1624)
// 數據: 0x0001 0x0203 0x0405 0x0607 0x0809 0x0A0B 0x0C0D 0x0E0F
// 16位字和: 0x0001+0x0203+...+0x0E0F = 0x03F0 (無進位)
// 校驗和: ~0x03F0 = 0xFC0F

總結

校驗和計算雖算法簡單,但偽首部構造、長度計算、字節序、對齊、奇數長度處理等工程細節極易出錯。建議封裝為庫函數並編寫充分單元測試(含邊界長度、跨 MTU 分片、IPv6 擴展)。高性能場景下利用 SIMD(SSE/AVX/NEON)並行求和、硬件卸載,可顯著降低 CPU 佔用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved