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

调用两次bpf_csum_diff导致eBPF TC程序加载失败求助

问题

参考《Cracking K8s Node Proxy》编写TC eBPF出口程序,尝试同时修改数据包的源IP与目的IP。在原有仅修改目的IP的可行代码基础上,新增修改源IP的逻辑(包含第二次bpf_csum_diff调用及L3、L4校验和更新),但程序无法通过BPF验证器加载。报错显示第二次调用bpf_csum_diff时,传入的R1参数类型为scalar,不符合预期的内存类类型。

相关代码

__section("egress")
int tc_egress(struct __sk_buff *skb)
{
    const __be32 cluster_ip = SRC_IP_ADDR;
    const __be32 pod_ip = IBP_IP_ADDR;
    const __be32 loc_ip = LOC_IP_ADDR;
    const int l3_off = ETH_HLEN;    // IP header offset
    const int l4_off = l3_off + 20; // TCP header offset: l3_off + sizeof(struct iphdr)
    __be32 sum;                     // IP checksum
    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;
    if (data_end < data + l4_off) { // not our packet
        return TC_ACT_OK;
    }
    struct iphdr *ip4 = (struct iphdr *)(data + l3_off);
    if (ip4->daddr != cluster_ip || ip4->protocol != IPPROTO_TCP /* || tcp->dport == 80 */) {
        return TC_ACT_OK;
    }
    if (data_end < data + l4_off + sizeof(struct tcphdr)) { // have verifier give a enough range
        return TC_ACT_OK;
    }
    struct tcphdr *tcph = (struct tcphdr *)(data + l4_off);
    if (bpf_ntohs(tcph->dest) != 9080) {
        return TC_ACT_OK;
    }
    // DNAT: cluster_ip -> pod_ip, then update L3 and L4 checksum
    sum = csum_diff((void *)&ip4->daddr, 4, (void *)&pod_ip, 4, 0);
    skb_store_bytes(skb, l3_off + offsetof(struct iphdr, daddr), (void *)&pod_ip, 4, 0);
    l3_csum_replace(skb, l3_off + offsetof(struct iphdr, check), 0, sum, 0);
    l4_csum_replace(skb, l4_off + offsetof(struct tcphdr, check), 0, sum, BPF_F_PSEUDO_HDR);

    // modify source ip then update l3 and l4 checksum
    sum = csum_diff((void *)&ip4->saddr, 4, (void *)&loc_ip, 4, 0);
    skb_store_bytes(skb, l3_off + offsetof(struct iphdr, saddr), (void *)&loc_ip, 4, 0);
    l3_csum_replace(skb, l3_off + offsetof(struct iphdr, check), 0, sum, 0);
    l4_csum_replace(skb, l4_off + offsetof(struct tcphdr, check), 0, sum, BPF_F_PSEUDO_HDR);

    return redirect(19, 0);
}

验证器报错日志

libbpf: -- BEGIN DUMP LOG ---
libbpf: 
0: R1=ctx(off=0,imm=0) R10=fp0 
0: (bf) r6 = r1                       ; R1=ctx(off=0,imm=0) R6_w=ctx(off=0,imm=0)
1: (b7) r1 = 139192586                ; R1_w=139192586 
2: (63) *(u32 *)(r10 -4) = r1         ; R1_w=139192586 R10=fp0 fp-8=mmmm????
3: (b7) r1 = 335587520                ; R1_w=335587520 
4: (63) *(u32 *)(r10 -8) = r1         ; R1_w=335587520 R10=fp0 fp-8=335587520 
5: (b7) r0 = 0                        ; R0_w=0 
6: (61) r1 = *(u32 *)(r6 +80)         ; R1_w=pkt_end(off=0,imm=0) R6_w=ctx(off=0,imm=0)
7: (61) r7 = *(u32 *)(r6 +76)         ; R6_w=ctx(off=0,imm=0) R7_w=pkt(off=0,r=0,imm=0)
8: (bf) r2 = r7                       ; R2_w=pkt(off=0,r=0,imm=0) R7_w=pkt(off=0,r=0,imm=0)
9: (07) r2 += 34                      ; R2_w=pkt(off=34,r=0,imm=0) 
10: (2d) if r2 > r1 goto pc+68        ; R1_w=pkt_end(off=0,imm=0) R2_w=pkt(off=34,r=34,imm=0)
11: (61) r2 = *(u32 *)(r7 +30)        ; R2_w=scalar(umax=4294967295,var_off=(0x0; 0xffffffff)) R7_w=pkt(off=0,r=34,imm=0) 
12: (55) if r2 != 0x100007f goto pc+66        ; R2_w=16777343 
13: (71) r2 = *(u8 *)(r7 +23)         ; R2=scalar(umax=255,var_off=(0x0; 0xff)) R7=pkt(off=0,r=34,imm=0) 
14: (55) if r2 != 0x6 goto pc+64      ; R2=6 
15: (bf) r2 = r7                      ; R2_w=pkt(off=0,r=34,imm=0) R7=pkt(off=0,r=34,imm=0)
16: (07) r2 += 54                     ; R2_w=pkt(off=54,r=34,imm=0) 
17: (2d) if r2 > r1 goto pc+61        ; R1=pkt_end(off=0,imm=0) R2_w=pkt(off=54,r=54,imm=0)
18: (69) r1 = *(u16 *)(r7 +36)        ; R1_w=scalar(umax=65535,var_off=(0x0; 0xffff)) R7=pkt(off=0,r=54,imm= 0) 
19: (55) if r1 != 0x7823 goto pc+59   ; R1_w=30755 
20: (bf) r1 = r7                      ; R1_w=pkt(off=0,r=54,imm=0) R7=pkt(off=0,r=54,imm=0)
21: (07) r1 += 30                     ; R1_w=pkt(off=30,r=54,imm=0) 
22: (07) r7 += 26                     ; R7_w=pkt(off=26,r=54,imm=0) 
23: (bf) r9 = r10                     ; R9_w=fp0 R10=fp0 
24: (07) r9 += -4                     ; R9_w=fp-4 
25: (b7) r2 = 4                       ; R2_w=4 
26: (bf) r3 = r9                      ; R3_w=fp-4 R9_w=fp-4 
27: (b7) r4 = 4                       ; R4_w=4 
28: (b7) r5 = 0                       ; R5_w=0 
29: (85) call bpf_csum_diff#28        ; R0=scalar() 
30: (bf) r8 = r0                      ; R0=scalar(id=1) R8_w=scalar(id=1) 
31: (bf) r1 = r6                      ; R1_w=ctx(off=0,imm=0) R6=ctx(off=0,imm=0) 
32: (b7) r2 = 30                      ; R2_w=30 
33: (bf) r3 = r9                      ; R3_w=fp-4 R9=fp-4 
34: (b7) r4 = 4                       ; R4_w=4 
35: (b7) r5 = 0                       ; R5_w=0 
36: (85) call bpf_skb_store_bytes#9   ; R0_w=scalar() 
37: (bf) r1 = r6                      ; R1_w=ctx(off=0,imm=0) R6=ctx(off=0,imm=0) 
38: (b7) r2 = 24                      ; R2_w=24 
39: (b7) r3 = 0                       ; R3_w=0 
40: (bf) r4 = r8                      ; R4_w=scalar(id=1) R8_w=scalar(id=1) 
41: (b7) r5 = 0                       ; R5_w=0 
42: (85) call bpf_l3_csum_replace#10          ; R0=scalar()
43: (bf) r1 = r6                      ; R1_w=ctx(off=0,imm=0) R6=ctx(off=0,imm=0)
44: (b7) r2 = 50                      ; R2_w=50 
45: (b7) r3 = 0                       ; R3_w=0 
46: (bf) r4 = r8                      ; R4_w=scalar(id=1) R8=scalar(id=1) 
47: (b7) r5 = 16                      ; R5_w=16 
48: (85) call bpf_l4_csum_replace#11          ; R0_w=scalar() 
49: (bf) r8 = r10                     ; R8_w=fp0 R10=fp0 
50: (07) r8 += -8                     ; R8_w=fp-8 
51: (bf) r1 = r7                      ; R1_w=scalar(id=2) R7=scalar(id=2) 
52: (b7) r2 = 4                       ; R2_w=4 
53: (bf) r3 = r8                      ; R3_w=fp-8 R8_w=fp-8 
54: (b7) r4 = 4                       ; R4_w=4 
55: (b7) r5 = 0                       ; R5_w=0 
56: (85) call bpf_csum_diff#28 
R1 type=scalar expected=fp, pkt, pkt_meta, map_key, map_value, mem, ringbuf_mem, buf processed 57 insns (limit 1000000) max_states_per_insn 0 total_states 3 peak_states 3 mark_read 2

解决方案

问题原因

从验证器日志可见,寄存器R7在指令22被执行r7 +=26操作后,类型从pkt(数据包内存指针)变为普通scalar值。第二次调用bpf_csum_diff时,传入的&ip4->saddr最终关联到R7这个scalar,导致验证器判定参数类型非法。

核心问题:

  • 第一次修改IP后,BPF验证器无法跟踪ip4指针在skb_store_bytes操作后仍指向有效数据包内存的关系。
  • 寄存器的算术操作导致R7丢失了pkt类型标记,无法被识别为合法内存指针。

解决方法

  1. 重新获取数据包指针并做边界检查:在第二次修改IP前,重新通过skb->data获取数据包指针,再次验证内存边界,让验证器确认指针合法性。
  2. 避免依赖丢失类型标记的寄存器:直接从skb重新计算偏移,不使用之前可能被修改过类型的寄存器值。

修改后的代码

__section("egress")
int tc_egress(struct __sk_buff *skb)
{
    const __be32 cluster_ip = SRC_IP_ADDR;
    const __be32 pod_ip = IBP_IP_ADDR;
    const __be32 loc_ip = LOC_IP_ADDR;
    const int l3_off = ETH_HLEN;    // IP header offset
    const int l4_off = l3_off + 20; // TCP header offset: l3_off + sizeof(struct iphdr)
    __be32 sum;                     // IP checksum
    void *data = (void *)(long)skb->data;
    void *data_end = (void *)(long)skb->data_end;
    
    // 初始边界检查,合并为一次更严格的检查
    if (data_end < data + l4_off + sizeof(struct tcphdr)) {
        return TC_ACT_OK;
    }
    
    struct iphdr *ip4 = (struct iphdr *)(data + l3_off);
    struct tcphdr *tcph = (struct tcphdr *)(data + l4_off);
    
    // 过滤目标数据包,合并条件判断
    if (ip4->daddr != cluster_ip || ip4->protocol != IPPROTO_TCP || bpf_ntohs(tcph->dest) != 9080) {
        return TC_ACT_OK;
    }

    // DNAT: 修改目的IP并更新校验和
    sum = csum_diff((void *)&ip4->daddr, 4, (void *)&pod_ip, 4, 0);
    skb_store_bytes(skb, l3_off + offsetof(struct iphdr, daddr), (void *)&pod_ip, 4, 0);
    l3_csum_replace(skb, l3_off + offsetof(struct iphdr, check), 0, sum, 0);
    l4_csum_replace(skb, l4_off + offsetof(struct tcphdr, check), 0, sum, BPF_F_PSEUDO_HDR);

    // 重新获取数据包指针并做边界检查,确保验证器识别合法内存
    data = (void *)(long)skb->data;
    data_end = (void *)(long)skb->data_end;
    if (data_end < data + l3_off + sizeof(struct iphdr)) {
        return TC_ACT_OK;
    }
    ip4 = (struct iphdr *)(data + l3_off);

    // SNAT: 修改源IP并更新校验和
    sum = csum_diff((void *)&ip4->saddr, 4, (void *)&loc_ip, 4, 0);
    skb_store_bytes(skb, l3_off + offsetof(struct iphdr, saddr), (void *)&loc_ip, 4, 0);
    l3_csum_replace(skb, l3_off + offsetof(struct iphdr, check), 0, sum, 0);
    l4_csum_replace(skb, l4_off + offsetof(struct tcphdr, check), 0, sum, BPF_F_PSEUDO_HDR);

    return redirect(19, 0);
}

关键修改点

  • 在第二次修改IP前,重新从skb->data获取数据包指针,并再次执行内存边界检查,让BPF验证器确认指针的合法性。
  • 重新赋值ip4指针,确保验证器能跟踪到它指向合法的数据包内存区域,而非之前丢失类型标记的寄存器值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 17:15:53