首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Rust eBPF 终极入门:用 Aya 写 XDP 防火墙!

Rust eBPF 终极入门:用 Aya 写 XDP 防火墙!

作者头像
不吃草的牛德
发布2026-04-23 12:21:48
发布2026-04-23 12:21:48
900
举报
文章被收录于专栏:RustRust

Rust eBPF 终极入门:用 Aya 写 XDP 防火墙!

兄弟们!还在用 iptables 苦哈哈限速?还在为内核模块崩溃头疼?今天带你直奔 eBPF 黑科技巅峰:Rust + Aya 一键写 XDP 程序,直接在网卡驱动层拦截数据包,速度快到离谱!

这个教程从零到一,教你:

  • • 搭建环境
  • • 用 Aya 写 XDP 防火墙(真实解析 IP 头,屏蔽指定源 IP)
  • • Perf Buffer 传结构化事件(源 IP + 动作)
  • • Aya-Log 全链路打日志(内核 info! → 用户空间可见)
  • • 多核异步读取 + 错误排查

写完你就能在简历上加一句“熟练掌握 Rust eBPF & XDP”,面试官直接爱了!

为什么 Rust + Aya 是 2026 年 eBPF 最香选择?
  • eBPF:内核热加载代码,无需重启,XDP 阶段最快(网卡收到包就处理)
  • Rust:内存安全 + 零成本抽象,比 C 写 eBPF 更爽、更稳
  • Aya:纯 Rust 生态,不依赖 libbpf,开发者体验爆炸(0.13.x 版本已非常成熟)
  • Perf Buffer:内核 → 用户空间高效通道
  • Aya-Log:内核直接 info!/warn!,dmesg/journalctl 一看就懂

适用场景:DDoS 防护、入侵检测、流量监控、自定义 NAT……干货走起!

第一步:环境准备(5-10 分钟搞定)

系统:Ubuntu 22.04+ / Debian 12+(内核 ≥ 5.10 最佳,支持 XDP 多驱动)

代码语言:javascript
复制
# 1. Rust(最新 stable 即可)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"

# 2. LLVM/Clang(Aya 需要,推荐 15+ 或 18)
sudo apt update
sudo apt install -y clang llvm libelf-dev zlib1g-dev pkg-config libssl-dev build-essential

# 3. bpf-linker(Aya 构建必须)
cargo install bpf-linker

# 4. 可选:用官方模板起步(强烈推荐!)
cargo install cargo-generate

检查:rustc --versionclang --versionuname -r(内核版本)

第二步:创建项目 & 配置依赖
代码语言:javascript
复制
cargo new xdp-aya-firewall
cd xdp-aya-firewall

Cargo.toml(完整版,基于 2026 年主流):

代码语言:javascript
复制
[package]
name = "xdp-aya-firewall"
version = "0.1.0"
edition = "2021"

[dependencies]
aya = { version = "0.13", features = ["async_tokio"] }
aya-log = "0.2"
tokio = { version = "1", features = ["full", "macros"] }
anyhow = "1.0"
network-types = "0.1"   # 安全解析 Ethernet/IP 头
bytes = "1"

[build-dependencies]
aya-build = "0.13"

项目结构:

代码语言:javascript
复制
xdp-aya-firewall/
├── Cargo.toml
├── build.rs               # 构建 eBPF
├── src/
│   └── main.rs            # 用户空间
└── ebpf/
    └── src/
        └── main.rs        # eBPF 内核代码
第三步:写 eBPF 内核代码(ebpf/src/main.rs)

真实解析包头 + 规则匹配 + Perf 发事件 + 日志

代码语言:javascript
复制
#![no_std]
#![no_main]

use aya_ebpf::{
    bindings::xdp_action,
    macros::{map, xdp},
    maps::PerfEventArray,
    programs::XdpContext,
};
use aya_log_ebpf::{info, warn};

use network_types::{
    eth::{EthHdr, EtherType},
    ip::{Ipv4Hdr},
};
use core::mem;

// Perf Buffer 事件结构体(更实用)
#[repr(C)]
#[derive(Copy, Clone)]
struct PacketEvent {
    src_ip: u32,     // 网络字节序源 IP
    action: u32,     // 0 = PASS, 1 = DROP
}

// Perf Map:8192 条足够大多数场景
#[map(name = "PACKET_EVENTS")]
static mut PACKET_EVENTS: PerfEventArray<PacketEvent> = PerfEventArray::with_max_entries(8192, 0);

#[xdp(name = "firewall_xdp")]
pub fn firewall_xdp(ctx: XdpContext) -> u32 {
    match try_firewall_xdp(ctx) {
        Ok(action) => action as u32,
        Err(err) => {
            warn!(&ctx, "处理失败: {:?}", err);
            xdp_action::XDP_ABORTED as u32
        }
    }
}

fn try_firewall_xdp(ctx: XdpContext) -> Result<xdp_action, ()> {
    // 1. 读取以太网头部(从数据包开头)
    let eth_hdr: EthHdr = ctx.load(0)?;

    // 只处理 IPv4,其他放行
    if eth_hdr.ether_type != EtherType::Ipv4 {
        info!(&ctx, "非 IPv4 包,直接 PASS");
        return Ok(xdp_action::XDP_PASS);
    }

    // 2. 读取 IPv4 头部(以太网头后)
    let ip_offset = EthHdr::LEN;
    let ip_hdr: Ipv4Hdr = ctx.load(ip_offset)?;

    // 源 IP(网络字节序 → u32)
    let src_ip = u32::from_be_bytes(ip_hdr.src);

    // 示例黑名单 IP:192.168.1.100
    let blocked_ip: u32 = 0xC0A80164;  // 192.168.1.100

    let action = if src_ip == blocked_ip {
        info!(&ctx, "拦截可疑 IP: {:i} → DROP", src_ip);
        xdp_action::XDP_DROP
    } else {
        // info!(&ctx, "正常流量: {:i} → PASS", src_ip);  // 调试时打开
        xdp_action::XDP_PASS
    };

    // 3. 发送事件到用户空间
    let event = PacketEvent {
        src_ip: ip_hdr.src,   // 保持网络序,用户空间再转换
        action: action as u32,
    };
    unsafe { PACKET_EVENTS.output(&ctx, &event, 0); }

    Ok(action)
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    unsafe { core::hint::unreachable_unchecked() }
}

小彩蛋{:i} 格式化直接把 u32 打印成 192.168.x.x,Aya-Log 真香!

第四步:构建脚本(build.rs)
代码语言:javascript
复制
fn main() -> anyhow::Result<()> {
    aya_build::build_ebpf("ebpf")?;
    Ok(())
}
第五步:用户空间加载 & 处理(src/main.rs)
代码语言:javascript
复制
use aya::{include_bytes_aligned, Bpf};
use aya::maps::perf::AsyncPerfEventArray;
use aya::programs::{Xdp, XdpFlags};
use aya_log::EbpfLogger;
use anyhow::Context;
use std::net::Ipv4Addr;
use tokio::signal;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. 加载 eBPF 对象(路径根据 target 调整)
    let mut bpf = Bpf::load(include_bytes_aligned!(
        "../../target/bpf-el.elf/x86_64-unknown-none/debug/ebpf"
    ))?;

    // 2. 初始化 Aya-Log(内核日志 → dmesg)
    if let Err(e) = EbpfLogger::init(&mut bpf) {
        eprintln!("日志初始化失败(非致命):{e}");
    }

    // 3. 加载并挂载 XDP 程序(改成你的网卡,如 enp1s0)
    let prog: &mut Xdp = bpf.program_mut("firewall_xdp").context("找不到程序")?.try_into()?;
    prog.load()?;
    prog.attach("eth0", XdpFlags::default())?;  // 可加 XDP_FLAGS_SKB_MODE 等

    // 4. Perf Buffer 读取(多 CPU 异步)
    let mut perf_array = AsyncPerfEventArray::try_from(bpf.map_mut("PACKET_EVENTS")?)?;

    for cpu in aya::util::online_cpus()? {
        let mut reader = perf_array.open(cpu, None)?;

        tokio::spawn(async move {
            let mut bufs = vec![vec![0u8; 4096]; 10];

            loop {
                let cnt = reader.read_events(&mut bufs).await.unwrap_or(0);
                for i in 0..cnt.read as usize {
                    let buf = &bufs[i];
                    if buf.len() < core::mem::size_of::<super::ebpf::PacketEvent>() {
                        continue;
                    }

                    let event = unsafe { *(buf.as_ptr() as *const super::ebpf::PacketEvent) };
                    let ip = Ipv4Addr::from(event.src_ip.to_be());

                    let act = if event.action == xdp_action::XDP_DROP as u32 { "DROP" } else { "PASS" };
                    println!("[CPU {cpu}] 检测到包!源 IP: {ip} 动作: {act}");
                }
            }
        });
    }

    println!("XDP 防火墙已启动!Ctrl+C 退出");
    signal::ctrl_c().await?;
    println!("卸载中...");

    Ok(())
}
第六步:构建 & 运行
代码语言:javascript
复制
cargo build --release
sudo target/release/xdp-aya-firewall

测试

  • • 从黑名单 IP ping 本机 → 应该 ping 不通 + 终端打印 DROP + dmesg 看到日志
  • • 查看日志:sudo dmesg -w | grep firewall
常见坑 & 解决方案(保命锦囊)
  • • 网卡名错 → ip link 查看
  • • 权限 → 必须 sudo
  • • Perf 读不到 → map 名一致?entries 够?
  • • 日志无输出 → EbpfLogger init 成功?dmesg -w 看
  • • 编译失败 → LLVM/Clang 版本太旧,升级到 18+
结语:你现在就是 eBPF Rust 大神了!

恭喜!从零掌握了 XDP + Perf Buffer + Aya-Log 全套流程。想进阶?加 HashMap 动态黑名单、用 Ring Buffer 替换 Perf、追踪 TC 层、甚至改写包内容做负载均衡……

觉得干货满满?点个在看、转发、收藏三连!咱们继续冲!🚀

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-02-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust火箭工坊 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Rust eBPF 终极入门:用 Aya 写 XDP 防火墙!
    • 为什么 Rust + Aya 是 2026 年 eBPF 最香选择?
    • 第一步:环境准备(5-10 分钟搞定)
    • 第二步:创建项目 & 配置依赖
    • 第三步:写 eBPF 内核代码(ebpf/src/main.rs)
    • 第四步:构建脚本(build.rs)
    • 第五步:用户空间加载 & 处理(src/main.rs)
    • 第六步:构建 & 运行
    • 常见坑 & 解决方案(保命锦囊)
    • 结语:你现在就是 eBPF Rust 大神了!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档