校驗和是網路協定確保資料完整性的核心機制。IPv4 首部、UDP、TCP 均採用 16 位元補碼和演算法,但計算範圍與偽首部處理有所不同。
| 協定 | 首部長度 | 校驗範圍 | 特殊處理 |
|---|---|---|---|
| IPv4 | 20 位元組 (基本) | 僅 IPv4 首部 | 計算前將 checksum 欄位歸零 |
| UDP | 8 位元組 | 偽首部 + UDP 首部 + 資料 | 偽首部包含 IP 位址、協定號、長度 |
| TCP | 20~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 位元字陣列、長度、初始累加值
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;
}
#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;
}
理解校驗和演算法不僅有助於協定實現與除錯,也是實作高效能網路堆疊、封包處理框架 (如 DPDK、XDP) 的基礎技能。