IPv4 協議族中,IP、UDP、TCP 均使用16 位反碼求和校驗和。IP 僅覆蓋首部;UDP/TCP 除覆蓋自身首部與負載外,還必須包含偽首部以驗證源/目的地址與協議類型。本文詳述算法規則、偽首部結構、邊界處理及高性能 C 實現。
sum = (sum >> 16) + (sum & 0xFFFF)),直到無進位。~sum),作為校驗和字段值。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 字節
sizeof(udphdr) + 數據長度 = 8 + 數據長度。IP 總長度 - IP 首部長度(含選項)。TCP 首部 20~60 字節。
#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;
}
htons/htonl 轉換。uint16_t* 要求 2 字節對齊。若數據包未對齊,需用 memcpy 到對齊緩衝區或逐字節組裝 16 位字。sum += last_byte << 8)。skb->ip_summed = CHECKSUM_PARTIAL 等標誌,內核/硬件自動計算/驗證。
// 已知測試向量 (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 佔用。