调用两次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类型标记,无法被识别为合法内存指针。
解决方法
- 重新获取数据包指针并做边界检查:在第二次修改IP前,重新通过
skb->data获取数据包指针,再次验证内存边界,让验证器确认指针合法性。 - 避免依赖丢失类型标记的寄存器:直接从
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
相关产品推荐
相关产品推荐

