You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mac OS X下如何为两个TCP结构体生成有效TCP校验和?

解决Mac OS X下混合TCP结构体的校验和计算问题

我之前也碰到过类似的情况——Mac自带的tcphdr确实会因为字段不全(比如缺少自定义扩展选项或特定业务字段),导致构造数据包时必须搭配额外结构体补充。你的IP校验和能正常工作,说明已经掌握了基础的校验和累加逻辑,问题核心在于没让校验和函数正确遍历所有TCP相关的字节数据(包括系统结构体和自定义结构体的内容)。

核心思路

TCP校验和的计算需要覆盖四个部分,缺一不可:

  • 伪IP头部(用于校验源/目的IP、协议类型、TCP总长度)
  • 标准TCP头部(tcphdr中的内容)
  • 自定义TCP补充字段(你额外定义的结构体内容)
  • TCP负载数据

只要让校验和函数能把这些部分的字节数据全部纳入累加计算,就能解决问题。下面是两种可行的实现方案:

具体解决方案

方法一:合并结构体到连续缓冲区计算

最直接的方式是把所有TCP相关数据(标准头、自定义头、负载)拷贝到一块连续内存,再结合伪头部计算校验和,避免分段处理的麻烦:

#include <stdint.h>
#include <string.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>

// 示例自定义TCP结构体,根据你的实际需求调整
typedef struct {
    uint32_t custom_option1;  // 自定义选项1,需转网络字节序
    uint16_t custom_option2;  // 自定义选项2,需转网络字节序
    // 其他自定义字段...
} tcp_custom_hdr_t;

// 复用你已有的IP校验和计算函数(假设它能处理任意长度的字节缓冲区)
extern uint16_t calculate_ip_checksum(const uint8_t* buf, size_t len);

uint16_t calculate_tcp_checksum(const struct ip* ip_hdr, 
                                const struct tcphdr* tcp_hdr, 
                                const tcp_custom_hdr_t* custom_hdr, 
                                const uint8_t* payload, 
                                size_t payload_len) {
    // 计算各部分长度
    size_t std_tcp_len = sizeof(struct tcphdr);
    size_t custom_tcp_len = sizeof(tcp_custom_hdr_t);
    size_t tcp_total_len = std_tcp_len + custom_tcp_len + payload_len;
    // 伪IP头部固定为12字节(源IP+目的IP+保留位+协议+TCP总长度)
    const size_t pseudo_hdr_len = 12;
    size_t total_buf_len = pseudo_hdr_len + tcp_total_len;

    // 分配临时缓冲区(可根据实际场景改用栈数组优化)
    uint8_t* buf = malloc(total_buf_len);
    if (!buf) return 0;

    // 填充伪IP头部
    uint8_t* ptr = buf;
    memcpy(ptr, &ip_hdr->ip_src.s_addr, 4); ptr +=4;
    memcpy(ptr, &ip_hdr->ip_dst.s_addr, 4); ptr +=4;
    *ptr++ = 0;  // 保留位
    *ptr++ = IPPROTO_TCP;  // 协议号:TCP=6
    memcpy(ptr, &tcp_total_len, 2); ptr +=2;
    // 注意:这里tcp_total_len需要转成网络字节序

    // 拷贝标准TCP头部
    memcpy(ptr, tcp_hdr, std_tcp_len); ptr += std_tcp_len;
    // 拷贝自定义TCP头部
    memcpy(ptr, custom_hdr, custom_tcp_len); ptr += custom_tcp_len;
    // 拷贝负载数据
    memcpy(ptr, payload, payload_len);

    // 计算校验和前,必须把TCP头部的校验和字段置0
    ((struct tcphdr*)(buf + pseudo_hdr_len))->th_sum = 0;

    // 调用已有校验和函数计算
    uint16_t checksum = calculate_ip_checksum(buf, total_buf_len);

    free(buf);
    return checksum;
}

方法二:修改校验和函数,支持多段数据累加

如果不想拷贝内存(比如数据包体积较大时),可以改造校验和函数,让它能逐个累加多段数据:

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

// 内部累加函数,处理单段数据
static uint32_t checksum_accumulate(uint32_t sum, const uint8_t* data, size_t len) {
    const uint16_t* data_16 = (const uint16_t*)data;
    while (len >= 2) {
        sum += *data_16++;
        len -= 2;
    }
    // 处理剩余的单个字节
    if (len > 0) {
        sum += *(const uint8_t*)data_16;
    }
    return sum;
}

uint16_t calculate_tcp_checksum(const struct ip* ip_hdr, 
                                const struct tcphdr* tcp_hdr, 
                                const void* custom_hdr, 
                                size_t custom_hdr_len,
                                const uint8_t* payload, 
                                size_t payload_len) {
    uint32_t sum = 0;
    size_t tcp_total_len = sizeof(struct tcphdr) + custom_hdr_len + payload_len;

    // 累加伪IP头部
    sum += ip_hdr->ip_src.s_addr;
    sum += ip_hdr->ip_dst.s_addr;
    sum += htons(0x0006);  // 协议号TCP=6,转网络字节序
    sum += htons(tcp_total_len);  // TCP总长度转网络字节序

    // 累加标准TCP头部(注意先把th_sum置0)
    struct tcphdr tcp_hdr_copy = *tcp_hdr;
    tcp_hdr_copy.th_sum = 0;
    sum = checksum_accumulate(sum, (const uint8_t*)&tcp_hdr_copy, sizeof(struct tcphdr));

    // 累加自定义TCP头部
    sum = checksum_accumulate(sum, (const uint8_t*)custom_hdr, custom_hdr_len);

    // 累加负载数据
    sum = checksum_accumulate(sum, payload, payload_len);

    // 折叠32位和为16位,取反得到最终校验和
    while (sum >> 16) {
        sum = (sum & 0xFFFF) + (sum >> 16);
    }
    return (uint16_t)~sum;
}

关键注意事项

  • 字节序必须正确:所有TCP头部字段(包括自定义结构体中的字段)都要转换为网络字节序(大端),否则校验和计算会完全错误。
  • 伪头部的TCP长度要准确:必须是标准TCP头+自定义头+负载的总长度,不能只计算标准头的长度。
  • 校验和字段先清零:计算前一定要把tcphdr的th_sum字段置0,否则会把旧值纳入计算,导致结果不正确。
  • 奇数长度处理:如果总数据长度是奇数,最后一个字节要单独累加,不能忽略。

内容的提问来源于stack exchange,提问作者John John

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:34:10