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

eBPF修改IP和TCP端口后校验值偏大1的问题排查

eBPF修改数据包IP与TCP端口后校验和始终偏大1的问题

我编写了一个可修改数据包IP地址和TCP端口的简易eBPF程序,但使用辅助函数更新校验和时,计算出的结果始终比正确值大1。tcpdump输出如下:

00:28:00.743620 IP (tos 0x0, ttl 64, id 20245, offset 0, flags [DF], proto TCP (6), length 60, bad cksum 67f0 (->67ef)!)
    192.168.1.1.60758 > 192.168.1.102.5002: Flags [S], cksum 0xb5de (incorrect -> 0xb5dd), seq 1988705508, win 64240, options [mss 1460,sackOK,TS val 4170540693 ecr 0,nop,wscale 7], length 0
00:28:01.776693 IP (tos 0x0, ttl 64, id 20246, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.1.1.60758 > 192.168.1.102.5002: Flags [S], cksum 0xb1d5 (incorrect -> 0xb1d4), seq 1988705508, win 64240, options [mss 1460,sackOK,TS val 4170541726 ecr 0,nop,wscale 7], length 0
00:28:01.776900 IP (tos 0xc0, ttl 64, id 27418, offset 0, flags [none], proto ICMP (1), length 88)
    192.168.1.101 > 192.168.1.1: ICMP redirect 192.168.1.102 to host 192.168.1.102, length 68
    IP (tos 0x0, ttl 63, id 20246, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.1.1.60758 > 192.168.1.102.5002: Flags [S], cksum 0xb1d5 (incorrect -> 0xb1d4), seq 1988705508, win 64240, options [mss 1460,sackOK,TS val 4170541726 ecr 0,nop,wscale 7], length 0
00:28:02.800554 IP (tos 0x0, ttl 64, id 20247, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.1.1.60758 > 192.168.1.102.5002: Flags [S], cksum 0xadd5 (incorrect -> 0xadd4), seq 1988705508, win 64240, options [mss 1460,sackOK,TS val 4170542750 ecr 0,nop,wscale 7], length 0

我的eBPF代码如下:

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/in.h>          // For IPPROTO_TCP and IPPROTO_UDP
#include <linux/pkt_cls.h>     // For TC_ACT_OK and other TC actions
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

#define OLD_IP 0xC0A80165 /* 192.168.1.101 in hex */
#define NEW_IP 0xC0A80166 /* 192.168.1.102 in hex */
#define NEW_PORT 5002     /* Destination port in decimal */

SEC("tc")
int modify_packet(struct __sk_buff *skb) {
    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

    // Check Ethernet header
    struct ethhdr *eth = data;

    if ((void *)(eth + 1) > data_end)
        return TC_ACT_OK; // Pass the packet if bounds are exceeded

    // Only handle IPv4 packets
    if (eth->h_proto != bpf_htons(ETH_P_IP))
        return TC_ACT_OK;

    // Check IP header
    struct iphdr *ip = (struct iphdr *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return TC_ACT_OK;

    // Only handle TCP packets
    if (ip->protocol != IPPROTO_TCP)
        return TC_ACT_OK;

    // Check if destination IP is 192.168.1.101
    if (ip->daddr != bpf_htonl(OLD_IP))
        return TC_ACT_OK;

    // Save old destination IP for checksum calculation
    __u32 old_ip = ip->daddr;

    __u32 new_dst_ip = bpf_htonl(NEW_IP);
    ip->daddr = new_dst_ip; // Update to the new IP

    //!!!!!!!!!!!!!! IP CHECKSUM - IDK IF VALID BUT WORKS
    __u32 csum_diff = bpf_csum_diff(&old_ip, sizeof(old_ip), &new_dst_ip, sizeof(new_dst_ip),0);
    __u32 new = (~((~ip->check) + csum_diff));
    __u32 second = (new>>8) & 0xF;
    if(second > 0)
    {
        second -= 1;
    }
    new &= ~(0xF << 8);
    new |= (second << 8);
    ip->check = new;
    ////////////////////////////////////////////////////////////

    // Handle TCP header
    struct tcphdr *tcp = (void *)ip + (ip->ihl * 4);
    if ((void *)(tcp + 1) > data_end)
        return TC_ACT_OK;

    // Save old port for checksum calculation
    __u16 old_port = tcp->dest;
    __u16 new_port = bpf_htons(NEW_PORT);
    
    tcp->dest = new_port;

    // Calculate TCP pseudo-header checksum
    bpf_l4_csum_replace(skb, offsetof(struct tcphdr, check), old_ip, new, BPF_F_PSEUDO_HDR);
    bpf_l4_csum_replace(skb, offsetof(struct tcphdr, check), old_port, new_port, 0);

    // AGAIN -1 TO DO
    __u16 hej = tcp->check;
    ////////////////////////////////////
    return TC_ACT_OK; // Pass the modified packet
}

char LICENSE[] SEC("license") = "GPL";

核心疑问:为何使用eBPF辅助函数计算出的校验和仍与正确值不匹配?


问题原因与修复方案

1. IP校验和计算错误

你手动调整IP校验和的逻辑完全不必要且易出错。bpf_csum_diff本身可以正确计算校验和的增量更新,正确的IP校验和更新方式是直接利用返回值替换原校验和:

// 替换原有的手动IP校验和计算代码
ip->check = bpf_csum_diff(&old_ip, sizeof(old_ip), &new_dst_ip, sizeof(new_dst_ip), ~ip->check);

bpf_csum_diff的最后一个参数是初始校验和的补码,传入~ip->check即可让辅助函数直接计算出正确的新校验和,无需手动处理进位或减1操作。

2. TCP校验和更新参数错误

你的TCP校验和更新代码存在致命错误:调用bpf_l4_csum_replace更新伪头部IP时,第四个参数传入了IP校验和的临时变量new,而非实际的新目的IPnew_dst_ip。这个错误直接导致TCP校验和计算偏差。

修正后的TCP校验和更新代码:

// 修正后的TCP校验和更新
bpf_l4_csum_replace(skb, offsetof(struct tcphdr, check), old_ip, new_dst_ip, BPF_F_PSEUDO_HDR);
bpf_l4_csum_replace(skb, offsetof(struct tcphdr, check), old_port, new_port, 0);

BPF_F_PSEUDO_HDR标志告诉辅助函数同时考虑TCP伪头部的变化(此处为目的IP修改),第二个调用则仅更新TCP头部内的目的端口变化。

完整修正后的代码

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/pkt_cls.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

#define OLD_IP 0xC0A80165 /* 192.168.1.101 in hex */
#define NEW_IP 0xC0A80166 /* 192.168.1.102 in hex */
#define NEW_PORT 5002     /* Destination port in decimal */

SEC("tc")
int modify_packet(struct __sk_buff *skb) {
    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return TC_ACT_OK;

    if (eth->h_proto != bpf_htons(ETH_P_IP))
        return TC_ACT_OK;

    struct iphdr *ip = (struct iphdr *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return TC_ACT_OK;

    if (ip->protocol != IPPROTO_TCP)
        return TC_ACT_OK;

    if (ip->daddr != bpf_htonl(OLD_IP))
        return TC_ACT_OK;

    __u32 old_ip = ip->daddr;
    __u32 new_dst_ip = bpf_htonl(NEW_IP);
    ip->daddr = new_dst_ip;

    // 正确更新IP校验和
    ip->check = bpf_csum_diff(&old_ip, sizeof(old_ip), &new_dst_ip, sizeof(new_dst_ip), ~ip->check);

    struct tcphdr *tcp = (void *)ip + (ip->ihl * 4);
    if ((void *)(tcp + 1) > data_end)
        return TC_ACT_OK;

    __u16 old_port = tcp->dest;
    __u16 new_port = bpf_htons(NEW_PORT);
    tcp->dest = new_port;

    // 正确更新TCP校验和
    bpf_l4_csum_replace(skb, offsetof(struct tcphdr, check), old_ip, new_dst_ip, BPF_F_PSEUDO_HDR);
    bpf_l4_csum_replace(skb, offsetof(struct tcphdr, check), old_port, new_port, 0);

    return TC_ACT_OK;
}

char LICENSE[] SEC("license") = "GPL";

原理说明

  • IP校验和:IPv4校验和是基于头部的16位补码和,bpf_csum_diff会计算新旧字段的校验和差值,自动处理进位和补码转换,直接返回正确的新校验和。
  • TCP校验和:TCP校验和覆盖TCP头部、数据以及伪头部(包含源/目的IP、协议号、TCP长度)。修改目的IP和端口后,必须通过bpf_l4_csum_replace分别更新这两部分的校验和,且参数必须准确对应新旧值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:09:53