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校验和无效是因为两个核心错误:
忽略了TCP伪头部的校验和更新
TCP校验和计算必须包含伪头部(源IP、目的IP、协议类型、TCP总长度),你交换了IP地址,但完全没处理伪头部对应的校验和部分,仅更新端口的校验和远远不够。误用了
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
相关产品推荐
相关产品推荐

