PawseySC/ebpf-traffic-meter
GitHub: PawseySC/ebpf-traffic-meter
基于eBPF的按用户网络流量计量工具,支持IPv4/IPv6双栈,按UID记录流量方向、字节量和通信IP。
Stars: 11 | Forks: 1
# eBPF 流量计量器
- [eBPF 流量计量器](#ebpf-traffic-meter)
- [eBPF](#ebpf)
- [环境要求](#requirements)
- [Fedora/RHEL](#fedorarhel)
- [Ubuntu/Debian](#ubuntudebian)
- [SLES](#sles)
- [构建](#building)
- [用法](#usage)
- [参数](#arguments)
- [示例](#examples)
- [输出](#output)
- [字段](#fields)
- [输出示例](#example-output)
- [非跟踪 IP 过滤](#untracked-ip-filtering)
- [架构](#architecture)
- [许可证](#license)
- [参考](#references)
一款使用 eBPF 的按用户网络流量监控工具。捕获所有 IPv4 和 IPv6 网络流量,并将其记录到按用户区分的文件中,包含方向、字节数和 IP 地址。
可以指定要忽略的网络地址,例如仅捕获外部网络流量。
## eBPF
eBPF 是一项源于 Linux 内核的技术,可以在特权上下文(如操作系统内核)中运行沙盒程序。
它用于安全高效地扩展内核的功能,而无需更改内核源代码或加载内核模块。
它可以跟踪事件并在任何级别与操作系统交互,包括文件系统、GPU、网络和任何系统调用,参见[参考](#references)。
## 环境要求
- Linux 内核 5.8+(用于 ring buffer 支持)
- clang(用于 BPF 编译)
- libbpf、libelf、libz 开发头文件
- **Root 权限**(用于加载 eBPF 程序)
### Fedora/RHEL
```
sudo dnf install clang libbpf-devel elfutils-libelf-devel zlib-devel
```
### Ubuntu/Debian
```
sudo apt install clang libbpf-dev libelf-dev zlib1g-dev
```
### SLES
**警告**:待确认
```
sudo zypper install clang libbpf-devel libelf-devel zlib-devel
```
## 构建
```
make clean && make
```
这将生成:
- `traffic_meter.bpf.o` - eBPF 程序对象
- `traffic_meter_user` - 用户空间加载器
- `ipmask_tool` - 生成网络字节序的非跟踪网络的位掩码,参见[下方](#untracked-ip-filtering)
## 用法
```
sudo ./traffic_meter_user [cgroup_path] [nic_id]
```
### 参数
| 参数 | 默认值 | 描述 |
|----------|---------|-------------|
| `cgroup_path` | `/sys/fs/cgroup/user.slice` | 要附加的 Cgroup(回退到 `/sys/fs/cgroup`) |
| `nic_id` | `unknown` | 日志文件名的标识符 |
### 示例
```
# 默认 cgroup,指定文件名的 NIC
sudo ./traffic_meter_user "" eno1
# 指定 cgroup 路径
sudo ./traffic_meter_user /sys/fs/cgroup/user.slice/user-1000.slice eth0
# 监控所有流量 (root cgroup)
sudo ./traffic_meter_user /sys/fs/cgroup wlan0
```
按 `Ctrl+C` 停止。
## 输出
日志文件以 CSV 格式写入 `/tmp/traffic_user__.log`:
```
direction,bytes,src_ip,dst_ip,timestamp,total_in,total_out
```
### 字段
| 字段 | 描述 |
|-------|-------------|
| `direction` | `in`(接收)或 `out`(发送) |
| `bytes` | 数据包大小(字节) |
| `src_ip` | 源 IP 地址(IPv4 或 IPv6) |
| `dst_ip` | 目的 IP 地址(IPv4 或 IPv6) |
| `timestamp` | 纳秒精度的 Unix 时间戳(秒.纳秒) |
| `total_in` | 此 UID 目前累计接收的字节数 |
| `total_out` | 此 UID 目前累计发送的字节数 |
### 输出示例
```
out,52,192.168.1.100,8.8.8.8,1732985432.123456789,0,52
in,84,8.8.8.8,192.168.1.100,1732985432.234567890,84,52
out,1500,192.168.1.100,142.250.185.78,1732985433.345678901,84,1552
in,1500,142.250.185.78,192.168.1.100,1732985433.456789012,1584,1552
out,80,2001:db8::1,2607:f8b0:4004:800::200e,1732985434.567890123,1584,1632
in,1280,2607:f8b0:4004:800::200e,2001:db8::1,1732985434.678901234,2864,1632
```
## 非跟踪 IP 过滤
eBPF 程序包含一个静态的 IPv4 和 IPv6 网络掩码列表,在监控期间这些掩码会被**忽略**。
这些掩码定义在 `untracked_masks.h` 文件中,由 `traffic_meter.bpf.c` 作为 `untracked_ipv4` 和 `untracked_ipv6` 数组包含。
对于每个数据包,程序会检查源地址**和**目的地址是否匹配任何已配置的网络。
如果找到匹配项,则使用 `bpf_ringbuf_discard()` 丢弃该事件,并允许数据包继续传输而不被记录。
* **IPv4** – 每个条目以网络字节序存储网络地址和子网掩码。示例条目包括 `10.0.0.0/8` 和 `192.168.1.0/24`。
* **IPv6** – 每个条目存储一个 16 字节的网络前缀和前缀长度。辅助函数 `ipv6_is_untracked()` 根据前缀长度执行逐字节比较。
要修改被忽略的网络,请编辑 `untracked_masks.h` 中的静态数组并重新构建项目(`make clean && make`)。
这允许您定制监控以排除内部或其他无关流量。
文件 `untracked_masks.h` 的内容
```
static const struct ipv4_mask untracked_ipv4[] = {
{ __builtin_bswap32(0x0a000000), __builtin_bswap32(0xff000000) }, // 10.0.0.0/8
{ __builtin_bswap32(0xc0a80100), __builtin_bswap32(0xffffff00) }, // 192.168.1.0/24
};
static const struct ipv6_mask untracked_ipv6[] = {
{ { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 32 },
{ { 0x20, 0x01, 0x0d, 0xb8, 0xab, 0xcd, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x00, 0x00 }, 112 },
};
```
使用随附的 `ipmask_tool` 从存储在文件中的 IP 地址列表生成 `untracked_masks.h` 文件,使用 `*` 作为通配符;例如:
`ip_list.txt`:
```
10.0.*.*
192.168.1.*
2001:db8::*
2001:db8:abcd:1234:5678:9abc:def0:*
```
**用法**
```
./ipmask_tool ip_list.txt > untracked_masks.h
```
该工具对于维护非跟踪网络非常有用,无需手动计算位掩码。
请注意,由于 eBPF 内核中禁止循环,因此使用 `#pragma unroll` 指令展开循环,并且数组长度是一个静态常量。完全展开所需考虑的最大迭代次数因编译器和编译器版本而异,在某些情况下,可能基于编译器对代码的分析而动态变化。
## 架构
1. **eBPF 程序** (`traffic_meter.bpf.c`):
- 附加到 IPv4 和 IPv6 的 `cgroup_skb/egress` 和 `cgroup_skb/ingress` 钩子
- 使用 `bpf_get_socket_uid()` 识别套接字所有者 (UID)
- 从 IP 头提取源/目的 IP
- 通过 IPv4 和 IPv6 各自独立的 ring buffer 将事件发送到用户空间
2. **用户空间加载器** (`traffic_meter_user.c`):
- 加载并附加 eBPF 程序(共 4 个:IPv4 和 IPv6 的 ingress/egress)
- 轮询两个 ring buffer 以获取事件
- 跟踪每个 UID 在入/出方向上的累计字节数
- 将事件传递给可插拔的输出后端
- 默认后端将 CSV 写入按用户区分的日志文件
输出系统使用可插拔的后端接口:
```
struct output_backend {
int (*init)(void *config);
int (*write)(const struct traffic_record *rec);
void (*close)(void);
};
```
要添加新的输出目标(syslog、网络套接字、数据库等),请实现此接口并将 `g_backend` 设置为您的实现。
## 许可证
GPL(对于使用仅限 GPL 辅助函数的 eBPF 程序是必需的)
## 参考
- [官方网站](https://ebpf.io/)
- [eBPF 文档](https://docs.ebpf.io/)
- [教程](https://eunomia.dev/tutorials/)
标签:C/C++, certspotter, Docker镜像, IPv4/IPv6, Linux内核, 事务性I/O, 二进制发布, 内核编程, 安全渗透, 客户端加密, 开源工具, 用户行为审计, 系统性能监控, 网络可见性, 网络安全, 进程隐藏, 防御绕过, 隐私保护