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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 21:03:16