我尝试通过 ebpf (tc egress) 在 ipv6 消息后添加扩展标头
我成功添加了扩展头。在初步测试中,我发现扩展头的添加并没有导致设备通信异常。
直到我的ebpf程序在openwrt(23.05.3 ramips/mt7621)路由器上运行并且传出的数据包由于校验和而被对方丢失时,我才发现这个问题。
我第一次在x86虚拟机(Ubuntu 22.04)上测试时没有出现这个问题。我发现虚拟机的ebpf程序打印的校验和与我实际发送的校验和不一致。事实上,发送的校验和是正确的。虚拟机好像自动修正了
而且,即使使用BPF_F_RECOMPUTE_CSUM标记调用bpf_skb_store_bytes,前后的校验和也不会改变。
这是我添加ipv6扩展头的代码的一部分,调用bpf_skb_store_bytes之前和之后的校验和是一致的
protocol = ip6->nexthdr; // store original protocol
ip6->nexthdr = ip6nexthdr; // update protocol
// off is the offset of the last nexthdr field (off = sizeof(struct ipv6hdr) + sizeof(struct ethhdr))
// bytes_len is the length of the new bytes
ip6->payload_len = bpf_htons(skb->len - off + bytes_len); // update payload_len
// Print the checksum before bpf_skb_store_bytes
if (protocol == IPPROTO_TCP) {
struct tcphdr *tcp_hdr = (struct tcphdr *)((char *)ip6 + sizeof(*ip6));
if ((char *)(tcp_hdr + 1) > (char *)(long)skb->data_end)
return TC_ACT_SHOT;
BPF_PRINT("TCP checksum before bpf_skb_store_bytes: %x", tcp_hdr->check);
} else if (protocol == IPPROTO_UDP) {
struct udphdr *udp_hdr = (struct udphdr *)((char *)ip6 + sizeof(*ip6));
if ((char *)(udp_hdr + 1) > (char *)(long)skb->data_end)
return TC_ACT_SHOT;
BPF_PRINT("UDP checksum before bpf_skb_store_bytes: %x", udp_hdr->check);
}
/* Make room for new bytes and insert them.
*/
if (bpf_skb_adjust_room(skb, bytes_len, BPF_ADJ_ROOM_NET, 0)) {
BPF_PRINT("Failed to adjust room");
return TC_ACT_SHOT;
}
/* Store the new bytes.
*/
if (bpf_skb_store_bytes(skb, off, exthdr->bytes, bytes_len, BPF_F_RECOMPUTE_CSUM)) {
BPF_PRINT("Failed to store bytes");
return TC_ACT_SHOT;
}
/* Update last Extension Header's nexthdr field.
*/
if (bpf_skb_store_bytes(skb, off + off_last_nexthdr, &protocol, sizeof(protocol), BPF_F_RECOMPUTE_CSUM)) {
BPF_PRINT("Failed to store last nexthdr");
return TC_ACT_SHOT;
}
// Print the checksum after bpf_skb_store_bytes
if (protocol == IPPROTO_TCP) {
ip6 = ipv6_header(skb, &off);
if (!ip6) {
BPF_PRINT("Failed to check IPv6 header");
return TC_ACT_SHOT;
}
struct tcphdr *tcp_hdr = (struct tcphdr *)((char *)ip6 + sizeof(*ip6) + bytes_len);
if ((char *)(tcp_hdr + 1) > (char *)(long)skb->data_end)
return TC_ACT_SHOT;
BPF_PRINT("TCP checksum after bpf_skb_store_bytes: %x", tcp_hdr->check);
} else if (protocol == IPPROTO_UDP) {
ip6 = ipv6_header(skb, &off);
if (!ip6) {
BPF_PRINT("Failed to check IPv6 header");
return TC_ACT_SHOT;
}
struct udphdr *udp_hdr = (struct udphdr *)((char *)ip6 + sizeof(*ip6) + bytes_len);
if ((char *)(udp_hdr + 1) > (char *)(long)skb->data_end)
return TC_ACT_SHOT;
BPF_PRINT("UDP checksum after bpf_skb_store_bytes: %x", udp_hdr->check);
}
为什么我的 ebpf 程序在调用 bpf_skb_store_bytes 时设置了 BPF_F_RECOMPUTE_CSUM 标志,但不更新校验和?我是不是写错了什么地方?
为什么虚拟机(Ubuntu 22.04 x86_64)发送的数据包的校验和会自动修正?
而且,即使使用BPF_F_RECOMPUTE_CSUM标记调用bpf_skb_store_bytes,前后的校验和也不会改变。
BPF_F_RECOMPUTE_CSUM
告诉 bpf_skb_store_bytes
更新数据包校验和,而不是 TCP/UDP 校验和。因此,鉴于您使用的是 IPv6,此处无需更新。
IPv6 没有校验和,并且 TCP/UDP 校验和不覆盖 IPv6 扩展标头。除非您更改了 IPv6 地址或 IPv6 有效负载,否则您的 TCP/UDP 校验和不应更改。