【免费下载链接】littlesnitch-linux
Open Source components of Little Snitch for Linux
Little Snitch for Linux 是一款开源(GPL-2)的 Linux 防火墙,其内核侧完全用 eBPF 编写。本文带你拆解它的eBPF 可移植编程方案:如何依靠CO-RE(Compile Once – Run Everywhere)字段重定位与bpf-linker链接器,让同一份 eBPF 程序在内核 6.12 到 7.2.0 之间免修改运行——这正是跨内核版本开发 eBPF 最关键的难题。🛡️
一、为什么 eBPF 程序必须"可移植"?
写 eBPF 程序绕不开两个现实:
- 内核结构体布局因版本而异。要追踪"哪个进程发起了连接",程序必须读取
task_struct、dentry、cred等内核结构体。这些结构体在不同内核版本、不同编译选项下,字段偏移可能完全不同,写死偏移量等于埋雷。 - verifier 非常挑剔且会变化。内核验证器会审查每一段字节码;验证器自身、Rust 编译器或链接器一旦升级,原本能加载的程序可能被拒绝。项目 README.md 直言:"eBPF is a bit fragile",因此整条工具链必须精确固定。
Little Snitch for Linux 的解法可以概括为一句话:用"字段名"代替"偏移量",用"固定版本工具链"代替"随手升级"。
二、项目结构速览:四个组件如何分工
| 组件 | 作用 | 关键入口 |
|---|---|---|
| ebpf/ | 挂载到 Linux 内核的全部 eBPF 程序(基于 aya-ebpf 框架) | ebpf/src/main.rs |
| common/ | 内核态与用户态共享的类型与函数(流程标识、过滤引擎等) | common/src/network_filter/filter_engine.rs |
| demo-runner/ | 用户态示例程序:把 eBPF 程序加载进内核、加载封禁列表、消费事件队列 | demo-runner/src/main.rs |
| webroot/ | 纯 JS 单页 Web UI(连接、封禁列表、规则三大视图) | webroot/README.md |
其中与"可移植性"直接相关的胶水层是 ebpf/build.rs,它在构建期完成 CO-RE 头文件的编译与绑定生成,下文细讲。
三、CO-RE 详解:用"字段名"替代硬编码偏移
3.1 声明"瘦身版"内核结构体
传统做法是 include 完整的内核头文件,但这要求编译机上的内核头与目标内核完全一致。CO-RE 的做法相反:只声明你真正用到的字段。在 ebpf/co-re/co-re.h 中,task_struct只保留了mm、pid、tgid、real_parent、cred等少数字段:
"The offset from the struct's base pointer does not matter as it will be fixed by CO-RE relocation. Just the field name is important."(偏移不重要,加载时由 CO-RE 重定位修正,字段名才是关键。)
配合 clang 的preserve_access_index属性(co-re.h#L4),编译器会为每个字段访问生成重定位指令,由加载器在运行内核上按字段名查询 BTF(类型信息)解析出真实偏移。
3.2 连"偏移计算"也要 CO-RE 化
一个容易踩的坑:即便在指针运算前算好偏移,__builtin_offsetof()的结果也可能不被 CO-RE 重定位。项目专门用__builtin_preserve_field_info()生成能穿透算术运算的BPF_CORE_FIELD_BYTE_OFFSET重定位,见 co-re.h#L144-L153:
// __builtin_offsetof() 在参与指针运算时不可靠地被重定位, // 而 __builtin_preserve_field_info() 生成的重定位可以存活 return __builtin_preserve_field_info(((struct dentry *)0)->d_name, 0);3.3 Rust 侧如何复用这些 C 定义
ebpf/build.rs 用 bindgen 从co-re.h生成 Rust 绑定写入OUT_DIR/co-re.rs,再由 ebpf/src/main.rs#L8-L15include!进来。于是 Rust eBPF 程序可以直接像调用普通函数一样使用task_struct_uid()、linux_binprm_path()这些 CO-RE 安全访问器(见 ebpf/src/current_executable.rs#L108-L150),而实际偏移全部留到加载时解析。
四、bpf-linker 构建流程:从 Rust 到内核字节码
eBPF crate 用 Rust 编写,但内核只认一种对象格式。ebpf/build.rs 的构建流程非常值得一看:
- 定位 bpf-linker:通过
which("bpf-linker")找到链接器,并声明为构建依赖,保证链接器升级时强制重编; - 编译 C 模块:用
clang --target=bpf -emit-llvm把 co-re.c 编译成 LLVM 位码(携带全部 CO-RE 重定位信息); - 链接:把位码与 Rust 编译产物一起交给bpf-linker 0.10.3链接成单个 BPF 目标文件;
- 嵌入用户态:demo-runner 在编译期把该对象文件以原始字节嵌入二进制,运行时用 aya 框架加载。
为什么要"钉死"工具链版本?
由于 verifier 的敏感性,项目在 ebpf/rust-toolchain.toml 中固定了 eBPF 构建专用的 nightly(nightly-2026-07-07),并在 Cargo.toml#L14-L22 中把 aya 框架钉到"已验证可用"的 commit。构建脚本解析该 pin 并通过rustup run显式传递。官方口径是:这套组合在内核 6.12 到 7.2.0 上通过验证器,其他版本"may or may not work"。
五、钩子点选择:程序挂在内核的哪些位置
可移植不仅是"偏移可重定位",还包含"钩子点有 fallback"。ebpf/src/main.rs 展示了完整策略:
- cgroup_sock / cgroup_sock_addr(Linux 4.10 / 4.17 起):在
sock_create、connect4/6、sendmsg4/6拦截连接,返回SK_PASS/SK_DROP直接放行或阻断; - cgroup_skb(收发方向):对数据报文做流量裁决;
- fentry/fexit
bprm_execve(5.5 起)追踪进程 exec,若内核不允许 tracing,则回退到kprobe/kretprobe备选实现(main.rs#L144-L165); - tracepoint
sched_process_exec/fork/exit:维护 PID 到"可执行文件节点"的映射,构建父子进程树。
裁决事件经 RingBuf 事件队列上报用户态;demo-runner 则负责加载封禁列表(blocked_hosts.txt、blocked_domains.txt)并轮询事件,详见 demo-runner/src/main.rs#L81-L120。过滤引擎的用户态/内核态共享设计另见 common/src/network_filter/Concepts.md。
六、构建与运行:最快上手步骤
前置依赖(见 README.md#L24-L32):
rustup toolchain install stable rustup toolchain install nightly-2026-07-07 --component rust-src # 与 ebpf/rust-toolchain.toml 一致 cargo install bpf-linker --locked # 0.10.3 已知可用 # 另外需要 clang C/C++ 编译器然后:
cargo build --release sudo cargo run --release # 以 root 运行以便挂载 cgroup 钩子运行后 demo-runner 会打印封禁列表读取/排序/上传耗时,进入事件队列循环;按 Ctrl+C 会触发流程表、DNS 缓存、PID 缓存的 dump,方便观察 eBPF 程序内部状态。📦
七、跨内核版本避坑清单
- 偏移永远不要写死——一切字段访问走 CO-RE 重定位;
- 偏移计算函数也换掉——用
__builtin_preserve_field_info()而非__builtin_offsetof()参与指针运算; - 钩子点准备 fallback——fentry 不可用时退到 kprobe,tracepoint 作为最兼容的兜底;
- 工具链全链路钉版本——nightly Rust、bpf-linker、aya 框架 commit 三处 pin,任何一处变动都可能被 verifier 拒绝;
- 旧内核记得放宽 memlock——demo-runner 启动时会
setrlimit(RLIMIT_MEMLOCK)(demo-runner/src/main.rs#L48-L57),老内核未启用 memcg 计账时此步不可省。
总结
Little Snitch for Linux 提供了一个教科书级的 eBPC 可移植工程范例:CO-RE 解决"结构体偏移随内核版本漂移",钉死的 nightly + bpf-linker 解决"工具链漂移导致 verifier 拒绝",钩子点 fallback 解决"不同内核配置下追踪点缺失"。三层防线叠加,才能让一份 Rust 编写的 eBPF 防火墙程序在内核 6.12 至 7.2.0 的范围内真正"编译一次、到处运行"。🚀
【免费下载链接】littlesnitch-linux
Open Source components of Little Snitch for Linux
相关推荐
Duix.Avatar 免费部署指南:一条命令拉起 AI 数字人本地克隆,10 秒自拍到离线口播视频
Duix.Avatar 免费部署指南:一条命令拉起 AI 数字人本地克隆,10 秒自拍到离线口播视频 需要一条产品讲解视频,但没空拍、没空剪。Duix.Avat
AI 应用人工智能数字人语音音频BCC libbpf-tools 完全指南:基于 CO-RE 的 BPF 工具构建、移植与 BTF 内核部署
BCC libbpf tools 完全指南:基于 CO RE 的 BPF 工具构建、移植与 BTF 内核部署 libbpf tools 是 BCC 仓库中以 l
可观测性性能剖析eBPF从内核到浏览器:Little Snitch for Linux eBPF防火墙全栈技术栈终极总结
从内核到浏览器:Little Snitch for Linux eBPF防火墙全栈技术栈终极总结 Little Snitch for Linux 是著名 mac
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考