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

BPF生成SYN-ACK包时TCP校验和无效问题求助

问题描述

我尝试在客户端发送SYN包后回复SYN-ACK包,编写了如下BPF代码:

static inline void swap_mac_addresses(struct ethhdr *eth)
{
    unsigned char tmp[ETH_ALEN];
    memcpy(tmp, eth->h_source, ETH_ALEN);
    memcpy(eth->h_source, eth->h_dest, ETH_ALEN);
    memcpy(eth->h_dest, tmp, ETH_ALEN);
}

static inline void swap_ipv4_addresses(struct __sk_buff *skb, struct iphdr *iph)
{
    uint32_t tmps = iph->saddr;
    iph->saddr = iph->daddr;
    iph->daddr = tmps;
    iph->check = 0;

    update_iph_checksum(iph);
}

unsigned short calculateChecksum(unsigned short *ptr, int nbytes) {
    unsigned long sum;
    unsigned short oddbyte;
    unsigned short checksum;

    sum = 0;
    while (nbytes > 1) {
        sum += *ptr++;
        nbytes -= 2;
    }

    if (nbytes == 1) {
        oddbyte = 0;
        *((unsigned char*)&oddbyte) = *(unsigned char*)ptr;
        sum += oddbyte;
    }

    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    checksum = ~sum;

    return checksum;
}

static inline void create_syn_ack_packet(struct __sk_buff *skb, struct ethhdr *eth, struct iphdr *iph, struct tcphdr *tcph)
{
    swap_mac_addresses(eth);

    swap_ipv4_addresses(skb, iph);

    uint16_t old_sport = tcph->source;
    uint16_t old_dport = tcph->dest;

    tcph->syn = 1;
    tcph->ack = 1;
    tcph->ack_seq = htonl(ntohl(tcph->seq) + 1);
    tcph->seq = 0;
    tcph->dest = old_sport;
    tcph->source = old_dport;
    tcph->check = 0;
    tcph->check = csum_diff4(old_sport, tcph->source, tcph->check);
    tcph->check = csum_diff4(old_dport, tcph->dest, tcph->check);
}

校验和计算使用的函数来自开源XDP转发项目的csum.h文件。

发送数据包时出现校验和无效的情况,抓包信息如下:

16:57:33.407099 eth0  In  IP (tos 0x0, ttl 53, id 43174, offset 0, flags [DF], proto TCP (6), length 60)
    x.x.x.x.51896 > x.x.x.x.24: Flags [S], cksum 0xa061 (correct), seq 2634206384, win 64860, options [mss 1380,sackOK,TS val 1124381365 ecr 0,nop,wscale 7], length 0
16:57:33.407190 eth0  Out IP (tos 0x0, ttl 52, id 43174, offset 0, flags [DF], proto TCP (6), length 60)
    x.x.x.x.24 > x.x.x.x.51896: Flags [S.], cksum 0x0000 (incorrect -> 0xa050), seq 0, ack 2634206385, win 64860, options [mss 1380,sackOK,TS val 1124381365 ecr 0,nop,wscale 7], length 0

请问为何我的校验和会无效?


问题分析与解决

你的TCP校验和无效是因为两个核心错误:

  1. 忽略了TCP伪头部的校验和更新
    TCP校验和计算必须包含伪头部(源IP、目的IP、协议类型、TCP总长度),你交换了IP地址,但完全没处理伪头部对应的校验和部分,仅更新端口的校验和远远不够。

  2. 误用了csum_diff4函数
    csum_diff4是用于32位值变更的增量校验和更新,你用它处理16位的端口值属于用法错误,即使要处理端口也应该用csum_diff16,但更关键的是,仅更新端口无法覆盖IP地址、TCP标志位、seq/ack_seq等字段变更带来的校验和变化。

修正后的代码

正确做法是重新计算完整的TCP校验和(包含伪头部):

static inline void create_syn_ack_packet(struct __sk_buff *skb, struct ethhdr *eth, struct iphdr *iph, struct tcphdr *tcph)
{
    swap_mac_addresses(eth);

    swap_ipv4_addresses(skb, iph);

    uint16_t old_sport = tcph->source;
    uint16_t old_dport = tcph->dest;

    tcph->syn = 1;
    tcph->ack = 1;
    tcph->ack_seq = htonl(ntohl(tcph->seq) + 1);
    tcph->seq = 0;
    tcph->dest = old_sport;
    tcph->source = old_dport;
    
    // 重置校验和后重新计算
    tcph->check = 0;
    
    // 计算TCP伪头部的校验和
    __wsum csum = csum_tcpudp_nofold(iph->saddr, iph->daddr, 
                                     ntohs(iph->tot_len) - iph->ihl * 4, 
                                     IPPROTO_TCP, 0);
    
    // 合并TCP头部及数据的校验和,最终折叠为16位校验和
    tcph->check = csum_fold(csum_add(csum, 
        skb_checksum(skb, sizeof(*eth) + iph->ihl * 4, 
                     ntohs(iph->tot_len) - iph->ihl * 4, 0)));
}

额外注意事项

  • skb_checksum的偏移要准确:从以太网头部结束、IP头部结束的位置开始,长度为TCP总长度(IP总长度减去IP头部长度)。
  • 如果你想尝试增量更新,需要同时处理IP地址和端口的变更,先用csum_diff4处理源/目的IP的交换,再用csum_diff16处理端口交换,但完整计算的方式更可靠,尤其是在你修改了seq、ack_seq、标志位等多个字段的情况下。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 14:14:58