Sniffnet:Rust 跨平台网络流量监控工具架构解析
posts posts 2026-04-27T19:40:00+08:00Sniffnet 是一款使用 Rust 和 iced 框架构建的跨平台网络流量监控工具,支持 PCAP 导入导出、实时流量图表、地理定位、协议识别等功能。本文从架构设计角度深入解析其主要模块、技术选型和性能优化策略。技术笔记Rust, 跨平台快速信息卡
| 属性 | 值 |
|---|---|
| GitHub Stars | 37,000+ |
| GitHub Forks | 1,400+ |
| 主要语言 | Rust(edition 2024) |
| 开源协议 | MIT OR Apache-2.0 |
| GUI 框架 | iced 0.14(wgpu 加速) |
| 当前版本 | v1.5.1 |
| 项目定位 | 跨平台网络流量监控工具 |
Sniffnet:Rust 跨平台网络流量监控工具架构解析
Sniffnet 真正解决的不是"怎么抓到数据包"——抓包这件事 libpcap 早就做完了。它解决的是两个更难的问题:把一瞬间的原始字节流,整理成普通人能一眼看懂的连接视图;以及让这一过程在 Windows、macOS、Linux 三套权限模型下都跑得通。理解它的架构,关键在看清它如何在"抓包"和"展示"之间切出一层清晰的分隔,并把跨平台的差异化悄悄塞进这一层的边界里。
学习目标
读完本文后,你应该能够:
- 理解 Sniffnet 的技术选型理由(为什么选择 Rust + iced)
- 区分 Sniffnet 的模块职责(networking、gui、chart、notifications)
- 成功从源码构建 Sniffnet,并理解 pcap 抓包流程
- 针对你的场景判断 Sniffnet 是否合适(替代 Wireshark?补充监控?)
- 理解跨平台差异处理(Windows 权限、macOS 授权、Linux capabilities、程序识别来源差异)
目录
- 快速信息卡
- 学习目标
- 项目背景与定位
- 整体架构:两块主线和一条边界
- 技术选型决策分析
- 主要模块深度解析
- 一次数据包的完整旅程
- 性能优化策略
- 跨平台差异处理
- 开发与扩展
- 常见问题与故障排查
- 自测题
- 进阶路径
- 与 Wireshark 如何取舍
项目背景与定位
Sniffnet 是意大利开发者 Giuliano Bellini 用 Rust 写的开源网络流量监控应用,GitHub 标星超过 37,000,Fork 超过 1,400,支持 Windows、macOS、Linux 三大平台,界面翻译超过 20 种语言。
它想服务的既不是资深安全研究员的取证需求,也不是运维手里的全流量分析平台,而是"我想知道我电脑现在到底在和谁说话"的普通用户。这个定位决定了它靠直觉完成的事,多数监控工具要靠命令行参数完成。
能力一览:
| 功能 | 说明 |
|---|---|
| 适配器选择 | 选择本机任意网络适配器进行监控 |
| 流量过滤 | BPF 过滤器精准筛选流量 |
| PCAP 导入导出 | 兼容 Wireshark 生态 |
| 实时统计 | 流量速率、连接数、协议分布 |
| 地理定位 | 基于 MaxMind GeoIP 定位 IP 归属地 |
| 协议识别 | 内置 6000+ 上层服务、协议、木马、蠕虫识别 |
| 程序级监控 | 识别产生网络流量的应用(Windows/macOS/Linux/BSD) |
| 告警通知 | 自定义规则触发系统通知 |
整体架构:两块主线和一条边界
读代码前,先抓住 Sniffnet 内部其实是两条在时间尺度上完全不同的主线:
- 抓包链路(高频、铁定顺序):网卡 → pcap → 协议解析 → 结构化数据包。这条线只做一件事,不关心界面。
- 展示链路(低频、响应交互):连接状态聚合 → 图表 → 页面渲染。这条线关心的是"当前有哪些连接、速率多少",而不是单个数据包。
两条线之间用一条异步通道隔开,通道传送的是已经解析好的 ParsedPacket。这条边界的意义在于:抓包线程永远不被界面卡住拖慢,而界面线程也永远不需要面对原始字节。
[网络适配器]
↓ pcap 捕获(内核 BPF 先过滤一层)
[networking::parse_packets] 抓包链路
↓ 解析为 ParsedPacket
[async-channel] ←── 边界(解耦点)
↓
[gui::manage_packets] 展示链路
↓ 更新统计与环形缓冲
[iced GUI rendering]模块职责矩阵:
| 模块 | 职责 | 关键类型 |
|---|---|---|
| networking | 抓包设备管理、数据包读取 | Capture, Packet |
| networking::parse_packets | 协议解析、流量分类 | IpHeader, TransportHeader |
| gui::manage_packets | 数据包汇总、状态更新 | State, Message |
| chart | 实时图表渲染 | Chart, Series |
| notifications | 规则匹配、告警触发 | Rule, Notification |
| report | PCAP 文件读写 | pcap::Writer, pcap::Reader |
技术选型决策分析
为什么选择 Rust
Rust 给 Sniffnet 带来三个实际好处。
内存安全与零成本抽象的平衡。 网络监控应用长时间运行,抓到的每个数据包都绑定到原始缓冲区的生命周期。Rust 的所有权和借用检查在编译期保证这些缓冲区在解析期间始终有效,杜绝悬垂引用和数据竞争,又不引入运行时开销。对一个从网卡持续灌数据的程序来说,这直接决定了它能跑多久不出事。
异步并发模型。 tokio 让抓包、渲染、GeoIP 反查三件耗时不同的事各自跑在独立的异步任务里,之间用 async-channel 传递数据。开发者不需要手写锁和条件变量,也能保证不会因为抓包阻塞了界面。
二进制分发友好。 Rust 编译产物静态链接,部署时不需要在目标机器装运行时。Windows 用户下载 MSI、macOS 用户加载 DMG、Linux 用户跑 AppImage,开箱即用。
GUI 框架选型:iced
Rust 生态里的 GUI 框架有 egui、iced、relm4、dioxus 等。Sniffnet 选 iced,理由有三点。
声明式 UI 与单向数据流。 iced 移植了 Elm 的 “State → View → Message” 模型:应用状态是一个纯数据结构,通过纯函数渲染成界面;用户交互产生消息,消息驱动状态更新,状态再触发重绘。这个模型天然避开了 GTK/Qt 那套信号槽回调,也让状态可以整体被序列化、测试和复现。
// iced 的编程模型:view 是纯函数,update 应答消息
fn view(&self) -> Element<Message> {
Column::new()
.push(Text::new(&self.status))
.push(Button::new("Start").on_press(Message::StartCapturing))
.into()
}
fn update(&mut self, message: Message) {
match message {
Message::StartCapturing => self.status = "Capturing...".to_string(),
_ => {}
}
}硬件加速渲染。 iced 底层基于 wgpu,能吃到 GPU 渲染红利。对流量图表这种高频重绘的界面,CPU 软件渲染很容易成为瓶颈,wgpu 通过 Vulkan、Metal、DirectX 12 把三套平台统一到同一份代码。
跨平台控件抽象。 iced 为按钮、输入框、下拉菜单提供了统一的实现,写一套界面代码,三端渲染成各自的原生外观,Sniffnet 不用为每个平台单独写 UI。
关键依赖分析
pcap crate(网络抓包)
Rust 的 pcap crate 封装了 libpcap。Sniffnet 在初始化时打开指定适配器、设置 BPF 过滤器,然后进入循环读包。pcap crate 用的是同步 API,Sniffnet 把它放进一个专门的异步任务里跑,避免阻塞其他部分。
use pcap::Capture<pcap::Active>;
// 打开网络适配器并启用混杂模式
let mut cap = Capture::from_device("eth0")?
.promisc(true)
.snaplen(65535)
.open()?;
// 设置 BPF 过滤器,例如只捕获 HTTP 流量
cap.filter("tcp port 80")?;etherparse(网络包解析)
原始 pcap 数据是 Ethernet II 帧,要一层层剥开才能拿到 TCP/UDP 负载。etherparse 提供从链路层到传输层的解析,支持 IPv4/IPv6、TCP/UDP/ICMP,也能识 GRE、VLAN 等隧道协议。
use etherparse::{IpHeader, SlicedPacket};
fn parse_packet(data: &[u8]) -> Result<(), ()> {
match SlicedPacket::from_ethernet(data) {
Ok(value) => {
match &value.ip {
Some(IpHeader::Version4(header, _)) => {
println!("IPv4: {} -> {}", header.source, header.destination);
}
_ => {}
}
}
Err(_) => return Err(())
}
Ok(())
}maxminddb(GeoIP 定位)
MaxMind 的 GeoLite2 数据库包含全球 IP 段到国家/城市/ASN 的映射。maxminddb crate 提供内存映射读取,单次查询是微秒级。Sniffnet 把查询结果缓存起来,同一 IP 只查一次,避免反复命中磁盘映射。
plotters(数据可视化)
plotters 是 Rust 生态里最成熟的 2D 图表库,折线、柱状、散点、热力图都有。plotters-iced2 把它接到 iced 里,让 Sniffnet 能在界面内嵌动态刷新的速率曲线和协议分布图。
主要模块深度解析
网络抓包流程
networking 模块是数据引擎,核心逻辑在 networking/mod.rs。它有五步:
- 设备枚举:
pcap::Device::list()拉出本机所有适配器,带名称和描述。 - 设备打开:选定适配器后,
Capture::from_device()建会话。Linux 需要CAP_NET_RAW权限。 - 过滤器编译:
filter()把 BPF 表达式编成内核过滤程序,不匹配的包在内核就被丢掉,减少进用户空间的拷贝。 - 抓包循环:
while let Ok(packet) = cap.next_packet()同步读包,解析后经通道发出去。 - 收尾:程序退出自动关 pcap 句柄,释放适配器。
常用 BPF 过滤器:
# 只捕获目标端口为 443 的 HTTPS 流量
tcp dst port 443
# 捕获特定网段的入站流量
src net 192.168.1.0/24
# 排除 DNS 流量
not port 53协议识别与端口映射
parse_packets.rs 把 SlicedPacket 转成应用需要的结构化数据:
pub struct ParsedPacket {
pub timestamp: DateTime<Utc>,
pub src_ip: IpAddress,
pub dst_ip: IpAddress,
pub src_port: u16,
pub dst_port: u16,
pub protocol: TransportProtocol,
pub payload_size: usize,
pub process_id: Option<u32>, // v1.5 起三端可用
}应用层识别依赖项目根目录的 services.txt。这份文件有看点:它不在运行时被反复遍历。build.rs 在编译期把它读进来,烘焙进一张由 phf 生成的静态完美哈希表,覆盖 6000+ 上层服务(含协议、木马、蠕虫)。也就是说,运行时根据"端口 + 传输协议"定位服务名是 O(1) 的精确查找,代价是改一次表就要重编译。这个取舍对静态文件合理,但对"想热更新协议库"的开发者就要注意。
服务名查找键是 (端口, TCP/UDP) 二元组,这就是为什么同样的端口号 TCP 和 UDP 可能对应不同协议。
GUI 页面架构
iced 应用通常把状态收敛进一个大 Struct。Sniffnet 的页面状态包括:
Overview(总览页面):当前适配器的实时上下行速率、连接数、协议分布饼图、各协议字节占比。
Inspect(检查页面):表格列出所有检测到的连接,可按源/目的 IP、端口、协议过滤,点开单条看详情。
Notifications(通知页面):管理告警规则,例如"192.168.1.100 的 22 端口出现流量时通知我"。规则匹配在每次数据包处理时同步进行。
Settings(设置页面):主题切换、语言、过滤器、通知音效等。
一次数据包的完整旅程
用一个具体场景把上面拆散的机制串起来:你启动 Sniffnet,选了 Wi-Fi 适配器,浏览器打开 https://example.com。
- 你先设置了过滤条件,界面把
tcp dst port 443交给Capture::filter(),编译成内核 BPF 程序。 - 内核在网卡驱动层就拦下目标端口不是 443 的包,白费的用户空间拷贝为零。你的 HTTPS 流量通过。
networking线程从 pcap 读到这个 1500 字节左右的以太网帧,交给parse_packets。etherparse剥掉以太网头拿到 IPv4 头,再剥 IP 层拿到 TCP 头,得到源/目的 IP、端口、协议类型、负载长度。parse_packets用(目的端口 443, TCP)键去 phf 哈希表里查一次,命中https,把这包标成 HTTPS。- 一个
ParsedPacket被塞进async-channel。抓包线程立刻回去读下一个包,不等界面。 - GUI 线程从通道取出,更新 Overview 页的速率曲线和连接表;同一条连接累加字节数,窗口计数加一。
- iced 收到
Message,触发一次重绘。你看到速率曲线向上跳了一下。 - 如果 GeoIP 数据库里有目标 IP,
maxminddb查一次国家并缓存;Inspect页那行连接旁边就多了一面国旗。
这一步一步里,真正"抓"的部分在第 2、3 步,剩下的全是在把抓到的数据变成"人看得懂的连接"。这也是理解 Sniffnet 架构的关键——大部分代码不是抓包,而是围绕抓包结果做的整理与呈现。
性能优化策略
Release 编译优化
Cargo.toml 的 release 配置直接反映了对性能的认真程度:
[profile.release]
opt-level = 3 # 最高优化级别
lto = true # 跨 crate 链接期优化
strip = true # 剥离调试符号
codegen-units = 1 # 单 codegen 单元,换取更多优化空间lto = true 让编译器能跨 crate 边界做内联和死代码消除。以 pcap 为例,未做 LTO 时调用 pcap 函数有一层函数跳转开销;开启后这些调用能被内联进调用点,减少单包处理路径上的间接跳转。codegen-units = 1 会拉长编译时间,但对性能敏感的桌面应用是划算的交换。
异步并发:channel 而非锁
抓包和渲染之间用 async-channel 而不是标准库同步 mpsc。它的价值在于抓包线程永远不阻塞:缓冲区满时它可以选择丢弃或给界面信号,而不是让抓包停下等界面消费。对实时监控,丢几帧老数据远好过界面拖慢抓包。
内存守恒
Snaplen 设成 65535,是 pcap 允许的最大单包捕获长度,保证任何帧(包括巨型帧)都有充足缓冲。对绝大多数不到 1500 字节的以太网帧,这不算浪费——缓冲是复用的一块固定内存,不是每包重新申请。
流量统计用滑动窗口,只保留最近 N 分钟的数据点,超窗数据直接丢弃。连接表也按生命周期裁剪,已结束的连接移出活跃表。这样长时间运行,内存曲线保持平坦,不会线性上涨。
跨平台差异处理
跨平台是 Sniffnet 复杂度最高的地方,也是最容易看得出设计功力的部分。抓包读包逻辑三端共用,差异被压在"获取权限"“程序识别"这两个点上。
在 v1.5 之前,“这个连接是哪个程序发的"只在 Windows 上可查(因为它能从系统连接表拿到进程归属),另两端无从判断。v1.5 把程序识别扩展到了 Windows、Linux、macOS 甚至 BSD——各端通过不同的内部机制拿到进程信息:Windows 走系统连接表 API(netstat -ano 的数据源),Linux 借助 netlink 套接字读取连接对应的进程,macOS 则遍历系统打开的 socket 到进程的映射。三端用不同的底层来源,对外暴露的是同一套能力:在 Inspect 页看"这条连接是 Chrome 发的”。
Windows
Windows 抓包底层用 npcap(libpcap 的 Windows 移植),需要管理员权限。安装包为 MSI,支持静默安装和组策略批量部署。
进程归属的信息来源是系统连接表 API:据"本地端口"反查"拥有该连接的进程 PID”,再把这个 PID 填进 ParsedPacket.process_id。
macOS
macOS 同样依赖 libpcap,抓包需要以授权后的权限运行(首次会提示授权)。安装包为 DMG。程序的进程识别通过遍历系统 socket 到进程的映射获得。
Linux
Linux 抓包也依赖 libpcap,但提供了最灵活的权限方案。RPM/DEB 安装后可用 setcap 给二进制授予 CAP_NET_RAW,让普通用户也能抓包:
setcap cap_net_raw,cap_net_admin=eip /usr/bin/sniffnet进程识别走 netlink 套接字读取连接与进程的对应关系。Linux 还支持 AppImage——把运行时、库、资源打成一个可执行文件,不污染系统。
三端权限与能力差异总结:
| 平台 | 底层库 | 权限方式 | 程序识别来源 |
|---|---|---|---|
| Windows | npcap | 管理员权限 | 系统连接表 API |
| macOS | libpcap | 授权提示 | socket → 进程遍历 |
| Linux | libpcap | setcap capabilities | netlink 套接字 |
开发与扩展
运行调试版本
git clone https://github.com/GyulyVGC/sniffnet.git
cd sniffnet
cargo run # 或 cargo run --release首次编译需装系统依赖:Linux 要 libpcap-dev,macOS 要 Xcode 命令行工具,Windows 要 Visual Studio Build Tools。详见仓库 Wiki 的 Required Dependencies。
新增协议识别
想在运行时新增服务映射,改 services.txt 加一行:
# 格式:服务名 <Tab> 端口/传输协议
myapp 8101/tcp注意:改动后必须重新编译,因为这张表在编译期被烘焙成哈希表,并受条目数断言约束。若你只是加条目,把 build.rs 里的断言一并更新,不然编译会失败。
自定义主题
内置 Deep Cosmos、Monokai、Dracula 等主题。新建主题修改 gui/styles/ 下的定义文件,遵循 iced 的颜色系统即可。
常见问题与故障排查
Q: 运行 sniffnet 时提示 “Permission denied” 怎么办?
Linux:设置 capabilities:
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/sniffnetmacOS:以授权方式运行,首次启动配合系统授权提示。
Windows:以管理员身份运行。
Q: 为什么看不到任何流量?
按顺序排查:
- 适配器选择:是否在 UI 里选了正确的适配器(通常是 Wi-Fi 或 Ethernet)。
- 过滤器设置:BPF 过滤是否过严,先清空过滤器再试。
- 权限问题:确认已正确配置权限。
- 网络活动:所选适配器上是否有真实的出流量。
Q: 为什么看不到"是哪个程序发的"?
程序识别在 v1.5 起覆盖 Windows、macOS、Linux(乃至 BSD),但各端条件略有差别:
- Windows:以管理员身份运行,稍等片刻让系统连接表建立出进程映射。
- macOS:授予抓包相关权限后可用。
- Linux:授予
CAP_NET_RAW后走 netlink 读取连接对应的进程。
自测题
- Sniffnet 为什么选 Rust + iced?Rust 带来了哪些实际好处?
- Sniffnet 的两条主线和它们之间的边界各是什么?
- BPF 过滤器在哪一层起作用?如何设置只捕获 HTTPS 流量?
services.txt为什么是编译期的?运行时改成无效吗?- 在 Linux 上让非 root 用户运行 Sniffnet 需要做什么?程序识别在 v1.5 前/后有何不同?
参考答案
题 1:Rust 提供内存安全、异步并发(tokio)、静态分发。iced 提供单向数据流、GPU 加速、跨平台控件。
题 2:抓包链路(网卡 → pcap → 解析)和展示链路(聚合 → 图表 → 渲染),中间用 async-channel 边界解耦,抓包线程不被界面阻塞。
题 3:双在内核层。BPF 过滤程序由内核执行,不匹配的包不进用户空间。tcp dst port 443 只留 HTTPS 出流量。
题 4:因为 6000+ 条映射在 build.rs 里编译成 phf 静态哈希表,运行时是 O(1) 查找。运行时改 services.txt 不会生效,需要重新编译(且要同步更新条目数断言)。
题 5:Linux 用 setcap cap_net_raw,cap_net_admin=eip 授予能力;程序识别在 v1.5 前仅 Windows 能做(查系统连接表),v1.5 起扩展到 Windows、macOS、Linux 与 BSD,各端用不同来源(系统连接表 API / socket 遍历 / netlink)拿到进程归属。
进阶路径
按下面顺序读,每环都搭在前一环的问题上:
- Sniffnet GitHub 仓库:先通读 README 和 Wiki 的 Required Dependencies,建立整体认知。
- pcap crate 文档:想搞懂"如何抓包"“BPF 怎么工作”,这是最直接的参考。
- iced 官方教程:想理解 State-View-Message 模型、如何在界面里嵌实时图表时读。当前依赖是 iced 0.14。
- etherparse crate 文档:需要自定义协议解析时读。
- plotters-iced2 文档(可选):想基于 Sniffnet 做二次开发或复现实时图表时读。
与 Wireshark 如何取舍
把 Sniffnet 定位清楚,才知道什么时候派得上用场。
Wireshark 胜在深度:完整解码器、协议分析、流重组、复杂过滤表达式,是安全分析和协议调试的标配。它的问题是学习曲线陡,界面信息密度高,普通用户被劝退。
Sniffnet 赢在直觉:三端图形界面 + 进程归属 + 地理定位 + 一键通知,是"我想知道电脑在和谁通信"这类问题的答案,装了就能用。
一个可行的采用顺序:
- 日常查流量、看谁在联网、简单告警 → 直接上 Sniffnet,几分钟上手。
- 排查协议问题、深挖会话、取证 → 该用 Wireshark,Sniffnet 导出 PCAP 正好喂给 Wireshark 继续分析。
- 两者不冲突,Sniffnet 抓包、Wireshark 深挖,是目前被验证过的高效组合。
项目信息:
- GitHub:https://github.com/GyulyVGC/sniffnet
- 官网:https://sniffnet.app
- 当前版本:v1.5.1
- License:MIT OR Apache-2.0
🦞 钳岳星君撰写 | 2026-04-27
参与讨论
使用 GitHub 登录。欢迎补充事实、异议与实践。
讨论暂时无法加载。