Aya Rust eBPF转发异常:接收数据量大于发送量
问题分析与解决方案
问题现象
使用Aya eBPF结合Tokio实现内核态TCP转发时,采用stream_verdict程序+SockHash的转发方案:
- 小数据量转发正常
- 大数据量场景(如发送50MB)下,端点接收的数据量大于发送量(示例中接收约50001245字节)
- 内核中接收的数据包大小显示正确,但接收端出现数据冗余
核心原因排查与修复
1. eBPF程序返回值错误导致数据包重复处理
当前eBPF程序直接将redirect_skb的返回值转为u32返回,但stream_verdict程序的返回值需要遵循内核的verdict规则:
- 若
redirect_skb成功(返回0),需返回SK_REDIRECT告知内核该skb已被转发,无需继续处理 - 若直接返回0,内核会认为该skb未被处理,会继续走常规协议栈流程,导致原数据包与转发数据包重复到达接收端
修复后的eBPF程序:
#![no_std] #![no_main] use aya_ebpf::{ macros::{map, stream_verdict}, maps::SockHash, programs::{SkBuffContext, verdict::SK_REDIRECT, verdict::SK_PASS}, }; use aya_forwarder_common::SocketKey; use aya_log_ebpf::info; #[map(name = "INTERCEPT_EGRESS")] static mut INTERCEPT_EGRESS: SockHash<SocketKey> = SockHash::with_max_entries(8, 0); #[stream_verdict] pub fn aya_forwarder(ctx: SkBuffContext) -> u32 { let mut key = SocketKey { ip: u32::from_be(ctx.skb.remote_ipv4()), port: u32::from_be(ctx.skb.remote_port()), }; unsafe { match INTERCEPT_EGRESS.redirect_skb(&ctx, &mut key, 0) { 0 => SK_REDIRECT, // 转发成功,告知内核无需后续处理 _ => SK_PASS // 转发失败,交由内核常规处理 } } } #[cfg(not(test))] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }
2. SocketKey字节序不匹配导致转发失效
eBPF程序中使用u32::from_be将网络字节序的IP/Port转为主机字节序存储到SocketKey,若用户态create_key函数未采用相同的字节序转换,会导致哈希表key不匹配,转发失败后内核继续处理原数据包,最终出现数据重复。
用户态create_key正确实现示例:
use std::net::{TcpStream, SocketAddr, Ipv4Addr}; use aya_forwarder_common::SocketKey; impl YourForwarderStruct { fn create_key(stream: &TcpStream) -> SocketKey { let peer_addr = stream.peer_addr().unwrap(); if let SocketAddr::V4(v4) = peer_addr { SocketKey { // 将IPv4地址转为网络字节序的u32 ip: u32::from_be_bytes(v4.ip().octets()), // 将端口转为网络字节序的u32 port: u32::from_be(v4.port() as u16), } } else { panic!("仅支持IPv4连接"); } } }
3. 哈希表插入的映射关系错误
需确保两个方向的流量都能映射到正确的目标socket:
- 客户端→代理的流量(ingress),需映射到代理→服务端的socket fd
- 代理→服务端的流量(egress),需映射到客户端→代理的socket fd
验证用户态插入逻辑:确认create_key(&egress_stream)获取的是服务端的IP/Port,对应插入的是客户端连接的fd;create_key(&ingress_stream)获取的是客户端的IP/Port,对应插入的是服务端连接的fd。
4. TCP分段合并/拆分导致的计数异常
大数据量下TCP会启用GRO(Generic Receive Offload)或GSO(Generic Segmentation Offload),可能导致skb长度计算与实际转发的分段数不匹配。可临时关闭测试环境的GRO/GSO验证:
# 关闭网卡GRO sudo ethtool -K eth0 gro off # 关闭网卡GSO sudo ethtool -K eth0 gso off
额外验证步骤
- 用
bpftool map dump查看INTERCEPT_EGRESS哈希表的条目,确认key和fd的映射关系正确 - 在内核中开启eBPF日志,查看
redirect_skb的调用结果是否全部成功 - 用tcpdump抓包,确认是否存在重复的数据包或额外的分段
内容的提问来源于stack exchange,提问作者Alexander Enneking
相关产品推荐
相关产品推荐

