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

IP/UDP/TCP 校驗和計算演算法與 Linux 核心實現解析

     編輯:關於C語言

網路協定校驗和計算原理與高效實現

校驗和是網路協定確保資料完整性的核心機制。IPv4 首部、UDP、TCP 均採用 16 位元補碼和演算法,但計算範圍與偽首部處理有所不同。

協定首部長度與校驗範圍

協定首部長度校驗範圍特殊處理
IPv420 位元組 (基本)僅 IPv4 首部計算前將 checksum 欄位歸零
UDP8 位元組偽首部 + UDP 首部 + 資料偽首部包含 IP 位址、協定號、長度
TCP20~60 位元組偽首部 + TCP 首部 + 資料偽首部結構同 UDP,協定號為 6

偽首部結構定義


  0       7 8      15 16      23 24      31
 +--------+--------+--------+--------+
 |        source address (32 bits)    |
 +--------+--------+--------+--------+
 |      destination address (32 bits) |
 +--------+--------+--------+--------+
 |  zero  |protocol| UDP/TCP length   |
 +--------+--------+--------+--------+

// 所有欄位均為網路位元組序 (大端序)
struct pseudo_head {
    uint32_t saddr;      // 來源 IP
    uint32_t daddr;      // 目的 IP
    uint8_t  zero;       // 必須為 0
    uint8_t  proto;      // IPPROTO_UDP (17) 或 IPPROTO_TCP (6)
    uint16_t len;        // UDP/TCP 總長度 (首部+資料),網路序
};

核心演算法:16 位元補碼和


// 標準校驗和計算:輸入 16 位元字陣列、長度、初始累加值
uint16_t in_cksum(uint16_t *buf, int len, int sum) {
    int n = len;
    
    // 1. 逐個 16 位元字累加
    while (n > 1) {
        sum += *buf++;
        n -= 2;
    }
    
    // 2. 處理奇數長度:最後一個位元組單獨加入
    if (n == 1)
        sum += *(uint8_t *)buf;
    
    // 3. 將進位摺疊回低 16 位 (可能需重複直到無進位)
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);  // 第二次摺疊確保無進位
    
    // 4. 取反碼得到最終校驗和
    return (uint16_t)~sum;
}

// 純累加函數 (不摺疊進位、不取反)
int cal_sum(uint16_t *buf, int len) {
    int sum = 0;
    while (len > 0) {
        sum += *buf++;
        len -= 2;
    }
    return sum;
}

完整計算流程實現 (Linux 風格)


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

struct pseudo_head {
    uint32_t saddr, daddr;
    uint8_t zero, proto;
    uint16_t len;
};

// IPv4 首部校驗和
static inline void cal_ip_cksum(struct iphdr *ip) {
    ip->check = 0;
    ip->check = in_cksum((uint16_t *)ip, 20, 0);
}

// UDP/TCP 校驗和 (含偽首部)
static inline uint16_t cal_udptcp_cksum(uint16_t *payload, struct iphdr *ip) {
    int len = ntohs(ip->tot_len) - 20;  // IP 總長 - IP 首部長 = UDP/TCP 長度
    
    struct pseudo_head ph = {
        .saddr = ip->saddr,
        .daddr = ip->daddr,
        .proto = ip->protocol,
        .zero = 0,
        .len = htons(len)
    };
    
    // 1. 先累加偽首部
    int sum = cal_sum((uint16_t *)&ph, sizeof(ph));
    
    // 2. 再累加 UDP/TCP 首部與資料,並處理進位/取反
    return in_cksum(payload, len, sum);
}

// 封包處理入口
int cal_cksum(char *packet) {
    struct ethhdr *eth = (struct ethhdr *)packet;
    struct iphdr *ip = (struct iphdr *)(packet + sizeof(struct ethhdr));
    
    cal_ip_cksum(ip);
    
    uint16_t *l4 = (uint16_t *)((char *)ip + 20);  // 跳過 IP 首部
    
    switch (ip->protocol) {
        case IPPROTO_TCP: {
            struct tcphdr *tcp = (struct tcphdr *)l4;
            tcp->check = 0;
            tcp->check = cal_udptcp_cksum(l4, ip);
            break;
        }
        case IPPROTO_UDP: {
            struct udphdr *udp = (struct udphdr *)l4;
            udp->check = 0;
            udp->check = cal_udptcp_cksum(l4, ip);
            break;
        }
        default:
            return 1;  // 不支援協定
    }
    return 0;
}

關鍵實現細節

  1. 網路位元組序:所有多位元組欄位 (IP 位址、埠號、長度、協定號) 必須為大端序。`htons`/`htonl` 轉換主機序→網路序,`ntohs`/`ntohl` 反向。
  2. 校驗和欄位歸零:計算前必須將首部中的 checksum 欄位設為 0,否則結果錯誤。
  3. 奇數長度處理:最後一個位元組視為高 8 位,低 8 位補 0 (即 `*(uint8_t *)buf`)。
  4. 進位摺疊:補碼加法要求將超出 16 位的進位加回低 16 位,直到無進位。
  5. 零校驗和特例:UDP 允許 checksum 為 0 表示不檢查 (IPv4),IPv6 強制啟用。

效能優化方向

  • 使用 SIMD 指令 (SSE/AVX/NEON) 並行累加 128/256/512 位
  • 硬體卸載:現代 NIC 支援 TCP/UDP 校驗和計算 (TX/RX checksum offload)
  • 增量更新:修改封包部分欄位 (如 TTL、IP 位址) 時,可按 RFC 1624 快速重算而非全算

理解校驗和演算法不僅有助於協定實現與除錯,也是實作高效能網路堆疊、封包處理框架 (如 DPDK、XDP) 的基礎技能。

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