mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-10-29 03:34:44 +08:00
Document TCP quarantine namespace scope
This commit is contained in:
@@ -4,7 +4,7 @@ kernel_min=6.5
|
||||
kernel_min_basis=documented
|
||||
architectures=x86_64
|
||||
btf=required
|
||||
kernel_config=CONFIG_BPF=y,CONFIG_BPF_SYSCALL=y,CONFIG_DEBUG_INFO_BTF=y,CONFIG_INET=y
|
||||
kernel_config=CONFIG_BPF=y,CONFIG_BPF_SYSCALL=y,CONFIG_BPF_JIT=y,CONFIG_DEBUG_INFO_BTF=y,CONFIG_INET=y,CONFIG_PROC_FS=y
|
||||
hardware=none
|
||||
root=required
|
||||
test_status=ci-runtime
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Your threat-intel feed just flagged `203.0.113.99` as a command-and-control server. Firewall rules and iptables block new connections immediately, but `netstat` shows three production workers already holding established TCP sessions to that address. Those sessions will keep exfiltrating data until the process closes them or the machine reboots. You need to sever the active connections right now, without restarting services, without killing processes, and without dropping unrelated traffic.
|
||||
|
||||
This tutorial builds a small eBPF tool that scans every TCP socket on the host, finds established IPv4 connections to an exact remote address and port, and destroys them with `bpf_sock_destroy`. A dry-run mode lets you verify what would be killed before you pull the trigger.
|
||||
This tutorial builds a small eBPF tool that scans the TCP sockets in the current network namespace, finds established IPv4 connections to an exact remote address and port, and destroys them with `bpf_sock_destroy`. The TCP BPF iterator sees only the sockets belonging to the network namespace of the process that loads it, not every socket on the host. A dry-run mode lets you verify what would be killed before you pull the trigger.
|
||||
|
||||
> Complete source: <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/51-tcp-quarantine>
|
||||
|
||||
@@ -38,13 +38,24 @@ PASS: dry-run preserved both connections; apply destroyed only the target
|
||||
|
||||
Substitute your own destination IP and port. The tool exits after one scan; it does not run continuously.
|
||||
|
||||
### Targeting a different network namespace
|
||||
|
||||
The TCP BPF iterator scans the network namespace of the process that loads the BPF program. To quarantine connections inside a container or network namespace other than your own, enter that namespace first:
|
||||
|
||||
```bash
|
||||
sudo nsenter --net=/proc/<target-pid>/ns/net -- ./tcp_quarantine \
|
||||
--destination 203.0.113.99 --port 443 --apply
|
||||
```
|
||||
|
||||
The `<target-pid>` identifies any process in the target network namespace. Only the network namespace is entered; the binary and current working directory remain available from the caller's mount namespace. The tool does not scan other network namespaces automatically.
|
||||
|
||||
## How it works: end-to-end flow
|
||||
|
||||
The tool uses a BPF TCP iterator to walk the kernel's socket table. Here is the flow:
|
||||
|
||||
1. **User space parses the target.** The loader validates the IPv4 address with `inet_pton`, converts the port, and stores both into the BPF program's read-only data section before loading.
|
||||
|
||||
2. **BPF program loads and attaches as a TCP iterator.** The kernel calls the iterator callback once per socket in the TCP hash table.
|
||||
2. **BPF program loads and attaches as a TCP iterator.** The kernel calls the iterator callback once per TCP socket in the loading process's network namespace.
|
||||
|
||||
3. **The iterator filters.** For each socket it increments `scanned`, skips anything that isn't `AF_INET` + `TCP_ESTABLISHED`, then compares `skc_daddr` and `skc_dport` against the target.
|
||||
|
||||
@@ -75,7 +86,7 @@ const volatile bool apply;
|
||||
|
||||
struct quarantine_stats stats;
|
||||
|
||||
extern int bpf_sock_destroy(struct sock_common *sock) __weak __ksym;
|
||||
extern int bpf_sock_destroy(struct sock_common *sock) __ksym;
|
||||
|
||||
SEC("iter/tcp")
|
||||
int quarantine_tcp(struct bpf_iter__tcp *ctx)
|
||||
@@ -120,7 +131,7 @@ Key points:
|
||||
|
||||
- **`const volatile` for parameters.** These variables live in the `.rodata` section. User space sets them after `open()` but before `load()`, so the verifier sees them as constants. This avoids maps for simple configuration.
|
||||
|
||||
- **`bpf_sock_destroy` as a kfunc.** Declared with `__weak __ksym`, the kernel resolves this at load time. If the kfunc is unavailable (kernel too old), loading fails with a clear error rather than silently doing nothing.
|
||||
- **`bpf_sock_destroy` as a kfunc.** Declared with `__ksym`, this is a required (strong) kfunc symbol. The kernel resolves it at load time. If the symbol is missing (kernel too old or kfunc not available), BPF loading fails at relocation.
|
||||
|
||||
- **`BPF_CORE_READ` for portability.** CO-RE relocations let the same compiled object run on kernels with different `sock_common` layouts, as long as BTF is available.
|
||||
|
||||
@@ -146,7 +157,7 @@ if (err) {
|
||||
fprintf(stderr,
|
||||
"failed to load TCP quarantine program: %s\n"
|
||||
"This tool requires Linux 6.5+ with BTF, TCP BPF iterators, "
|
||||
"and the bpf_sock_destroy kfunc.\n",
|
||||
"BPF JIT, and the bpf_sock_destroy kfunc.\n",
|
||||
strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -198,11 +209,12 @@ This runs `python3 tests/test_tcp_quarantine.py ./tcp_quarantine` which creates
|
||||
|---|---|
|
||||
| Kernel | Linux 6.5+ (where `bpf_sock_destroy` first appeared) |
|
||||
| BTF | Required (`CONFIG_DEBUG_INFO_BTF=y`) |
|
||||
| Kernel configs | `CONFIG_BPF=y`, `CONFIG_BPF_SYSCALL=y`, `CONFIG_DEBUG_INFO_BTF=y`, `CONFIG_INET=y` |
|
||||
| Kernel configs | `CONFIG_BPF=y`, `CONFIG_BPF_SYSCALL=y`, `CONFIG_BPF_JIT=y`, `CONFIG_DEBUG_INFO_BTF=y`, `CONFIG_INET=y`, `CONFIG_PROC_FS=y` |
|
||||
| BPF JIT runtime | Must be enabled at runtime (e.g. `net.core.bpf_jit_enable=1`). Programs that call kfuncs cannot fall back to the BPF interpreter. On kernels built with `CONFIG_BPF_JIT_ALWAYS_ON`, the sysctl may not exist because JIT is permanently enabled. |
|
||||
| Architecture | Tested on x86_64 |
|
||||
| Privileges | Tested as root. A least-privilege deployment must determine the required capabilities and LSM policy for its kernel version and environment. |
|
||||
|
||||
If loading fails, the loader prints the kernel errno followed by the kernel/BTF/iterator/kfunc requirement so you can identify which prerequisite is missing.
|
||||
If loading fails, the loader prints the kernel errno followed by the kernel/BTF/iterator/JIT/kfunc requirements so you can identify which prerequisite is missing.
|
||||
|
||||
## Limitations
|
||||
|
||||
@@ -211,7 +223,7 @@ This is a teaching tool, not a production agent. Specifically:
|
||||
- **IPv4 only.** There is no IPv6 path.
|
||||
- **One-shot scan.** The tool exits after a single pass. It does not monitor for new connections.
|
||||
- **No process attribution.** You cannot see which process owns the matched sockets.
|
||||
- **No cgroup scoping.** The scan covers all sockets on the host, not a specific container or service.
|
||||
- **Network-namespace scope, no cgroup selector.** The scan covers all TCP sockets in the loading process's network namespace, but there is no cgroup-level filter inside that namespace. A production controller that manages several namespaces must enter and run in each intended namespace or orchestrate them explicitly.
|
||||
- **No authorization or audit log.** A production version would gate destruction behind policy approval and record an audit trail.
|
||||
- **No UDP or packet inspection.** Only TCP established sockets are considered.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
你的威胁情报源刚刚把 `203.0.113.99` 标记为 C2 服务器。防火墙规则可以立即阻止新连接,但 `netstat` 显示三台生产节点已经和这个地址保持着活跃的 TCP 会话,数据仍在外泄。你需要立即切断这些连接,不重启服务,不杀死进程,也不影响无关流量。
|
||||
|
||||
本教程构建一个小型 eBPF 工具:扫描主机上所有 TCP 套接字,找到目标远端 IPv4 地址和端口完全匹配的 `ESTABLISHED` 连接,用 `bpf_sock_destroy` 将其销毁。默认模式是 dry-run,确认影响范围后再执行销毁。
|
||||
本教程构建一个小型 eBPF 工具:扫描当前网络命名空间中的 TCP 套接字,找到目标远端 IPv4 地址和端口完全匹配的 `ESTABLISHED` 连接,用 `bpf_sock_destroy` 将其销毁。TCP BPF 迭代器只能看到加载它的进程所在网络命名空间中的套接字,而非主机上的全部套接字。默认模式是 dry-run,确认影响范围后再执行销毁。
|
||||
|
||||
> 完整源码: <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/51-tcp-quarantine>
|
||||
|
||||
@@ -38,13 +38,24 @@ PASS: dry-run preserved both connections; apply destroyed only the target
|
||||
|
||||
请将目标 IP 和端口替换为你要隔离的实际地址。该工具执行一次扫描后退出,不会持续运行。
|
||||
|
||||
### 在其他网络命名空间中使用
|
||||
|
||||
TCP BPF 迭代器扫描的是加载 BPF 程序的进程所在的网络命名空间。要隔离容器或其他网络命名空间中的连接,先进入该命名空间:
|
||||
|
||||
```bash
|
||||
sudo nsenter --net=/proc/<target-pid>/ns/net -- ./tcp_quarantine \
|
||||
--destination 203.0.113.99 --port 443 --apply
|
||||
```
|
||||
|
||||
`<target-pid>` 是目标网络命名空间中任意进程的 PID。这里只进入网络命名空间,二进制文件和当前工作目录仍来自调用者的 mount 命名空间。该工具不会自动扫描其他网络命名空间。
|
||||
|
||||
## 工作原理:端到端流程
|
||||
|
||||
工具使用 BPF TCP iterator 遍历内核套接字表,流程如下:
|
||||
|
||||
1. **用户态解析目标参数。** 加载器用 `inet_pton` 验证 IPv4 地址,转换端口号,在加载 BPF 程序之前写入只读数据段。
|
||||
|
||||
2. **BPF 程序作为 TCP iterator 加载并挂载。** 内核对 TCP 哈希表中的每个套接字调用一次迭代器回调。
|
||||
2. **BPF 程序作为 TCP iterator 加载并挂载。** 内核对加载进程所在网络命名空间中的每个 TCP 套接字调用一次迭代器回调。
|
||||
|
||||
3. **迭代器过滤。** 对每个套接字递增 `scanned` 计数器,跳过非 `AF_INET` 或非 `TCP_ESTABLISHED` 的条目,然后比较 `skc_daddr` 和 `skc_dport` 是否与目标匹配。
|
||||
|
||||
@@ -75,7 +86,7 @@ const volatile bool apply;
|
||||
|
||||
struct quarantine_stats stats;
|
||||
|
||||
extern int bpf_sock_destroy(struct sock_common *sock) __weak __ksym;
|
||||
extern int bpf_sock_destroy(struct sock_common *sock) __ksym;
|
||||
|
||||
SEC("iter/tcp")
|
||||
int quarantine_tcp(struct bpf_iter__tcp *ctx)
|
||||
@@ -120,7 +131,7 @@ int quarantine_tcp(struct bpf_iter__tcp *ctx)
|
||||
|
||||
- **`const volatile` 用于参数传递。** 这些变量位于 `.rodata` 段,用户态在 `open()` 之后、`load()` 之前设置它们,验证器将其视为常量。这种方式比使用 map 更简洁。
|
||||
|
||||
- **`bpf_sock_destroy` 作为 kfunc 声明。** 通过 `__weak __ksym` 标注,内核在加载时解析。如果当前内核不提供该 kfunc(版本过低),加载将明确失败。
|
||||
- **`bpf_sock_destroy` 作为 kfunc 声明。** 通过 `__ksym` 标注为必需(强)符号,内核在加载时解析。如果内核中缺少该符号(版本过低或 kfunc 不可用),BPF 加载会在重定位阶段失败。
|
||||
|
||||
- **`BPF_CORE_READ` 保证可移植性。** CO-RE 重定位使同一编译产物能在不同 `sock_common` 布局的内核上运行,前提是有 BTF。
|
||||
|
||||
@@ -146,7 +157,7 @@ if (err) {
|
||||
fprintf(stderr,
|
||||
"failed to load TCP quarantine program: %s\n"
|
||||
"This tool requires Linux 6.5+ with BTF, TCP BPF iterators, "
|
||||
"and the bpf_sock_destroy kfunc.\n",
|
||||
"BPF JIT, and the bpf_sock_destroy kfunc.\n",
|
||||
strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -198,11 +209,12 @@ sudo make test
|
||||
|---|---|
|
||||
| 内核版本 | Linux 6.5+(`bpf_sock_destroy` 首次出现的版本) |
|
||||
| BTF | 必须启用 (`CONFIG_DEBUG_INFO_BTF=y`) |
|
||||
| 内核配置 | `CONFIG_BPF=y`, `CONFIG_BPF_SYSCALL=y`, `CONFIG_DEBUG_INFO_BTF=y`, `CONFIG_INET=y` |
|
||||
| 内核配置 | `CONFIG_BPF=y`, `CONFIG_BPF_SYSCALL=y`, `CONFIG_BPF_JIT=y`, `CONFIG_DEBUG_INFO_BTF=y`, `CONFIG_INET=y`, `CONFIG_PROC_FS=y` |
|
||||
| BPF JIT 运行时 | 必须在运行时启用(例如 `net.core.bpf_jit_enable=1`)。调用 kfunc 的程序无法回退到 BPF 解释器。如果内核编译时启用了 `CONFIG_BPF_JIT_ALWAYS_ON`,该 sysctl 可能不存在,因为 JIT 已永久启用。 |
|
||||
| 架构 | 已在 x86_64 上测试 |
|
||||
| 权限 | 已在 root 下测试。最小权限部署需要根据具体内核版本和环境确定所需的 capabilities 和 LSM 策略。 |
|
||||
|
||||
加载失败时,加载器会打印内核 errno 以及内核/BTF/迭代器/kfunc 的要求信息,帮助定位缺失的前置条件。
|
||||
加载失败时,加载器会打印内核 errno 以及内核/BTF/迭代器/JIT/kfunc 的要求信息,帮助定位缺失的前置条件。
|
||||
|
||||
## 局限性
|
||||
|
||||
@@ -211,7 +223,7 @@ sudo make test
|
||||
- **仅支持 IPv4。** 没有 IPv6 路径。
|
||||
- **单次扫描。** 工具执行一次后退出,不会监控新建连接。
|
||||
- **无进程归属信息。** 无法看到匹配套接字属于哪个进程。
|
||||
- **无 cgroup 范围限定。** 扫描覆盖主机上所有套接字,不限定容器或服务。
|
||||
- **网络命名空间范围,无 cgroup 筛选。** 扫描覆盖加载进程所在网络命名空间中的所有 TCP 套接字,但命名空间内部没有 cgroup 级别的过滤。管理多个命名空间的生产控制器需要分别进入每个目标命名空间运行,或显式编排。
|
||||
- **无授权和审计日志。** 生产版本应通过策略审批控制销毁操作并记录审计轨迹。
|
||||
- **不支持 UDP 或数据包检测。** 仅处理 TCP ESTABLISHED 套接字。
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const volatile bool apply;
|
||||
|
||||
struct quarantine_stats stats;
|
||||
|
||||
extern int bpf_sock_destroy(struct sock_common *sock) __weak __ksym;
|
||||
extern int bpf_sock_destroy(struct sock_common *sock) __ksym;
|
||||
|
||||
SEC("iter/tcp")
|
||||
int quarantine_tcp(struct bpf_iter__tcp *ctx)
|
||||
|
||||
@@ -164,7 +164,7 @@ int main(int argc, char **argv)
|
||||
fprintf(stderr,
|
||||
"failed to load TCP quarantine program: %s\n"
|
||||
"This tool requires Linux 6.5+ with BTF, TCP BPF iterators, "
|
||||
"and the bpf_sock_destroy kfunc.\n",
|
||||
"BPF JIT, and the bpf_sock_destroy kfunc.\n",
|
||||
strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ This table is generated from each tutorial's `.config` metadata. `Minimum kernel
|
||||
| [eBPF Tutorial: Energy Monitoring for Process-Level Power Analysis](48-energy/README.md) | 5.8 | Required feature | x86_64, arm64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_EVENTS=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_TRACEPOINTS=y | None | Required | CI build |
|
||||
| [eBPF Tutorial: Fixing Broken HID Devices Without Kernel Patches](49-hid/README.md) | 6.3 | Tutorial docs | x86_64, arm64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_HID_BPF=y, CONFIG_UHID=y\|m | None | Required | CI build |
|
||||
| [eBPF Tutorial by Example 50: Composable Traffic Control with TCX Links](50-tcx/README.md) | 6.6 | Tutorial docs | x86_64, arm64 | Optional | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_NET_XGRESS=y | Network interface | Required | CI build |
|
||||
| [eBPF Tutorial by Example: Quarantine an Established TCP Connection](51-tcp-quarantine/README.md) | 6.5 | Tutorial docs | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_INET=y | None | Required | CI runtime |
|
||||
| [eBPF Tutorial by Example: Quarantine an Established TCP Connection](51-tcp-quarantine/README.md) | 6.5 | Tutorial docs | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_INET=y, CONFIG_PROC_FS=y | None | Required | CI runtime |
|
||||
| [eBPF Tutorial: cgroup-based Policy Control](cgroup/README.md) | 5.8 | Required feature | x86_64, arm64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_CGROUPS=y, CONFIG_CGROUP_BPF=y, CONFIG_CGROUP_DEVICE=y, CONFIG_DEBUG_INFO_BTF=y | None | Required | CI build |
|
||||
| [eBPF Tutorial by Example: BPF Arena for Zero-Copy Shared Memory](features/bpf_arena/README.md) | 6.9 | Tutorial docs | x86_64, arm64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y | None | Required | CI build |
|
||||
| [eBPF Tutorial: BPF Iterators for Kernel Data Export](features/bpf_iters/README.md) | 5.8 | Tutorial docs | x86_64, arm64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_DEBUG_INFO_BTF=y | None | Required | CI build |
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
| [eBPF 教程:进程级能源监控与功耗分析](48-energy/README.zh.md) | 5.8 | 必需特性 | x86_64, arm64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_EVENTS=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_TRACEPOINTS=y | 无 | 需要 | CI 构建测试 |
|
||||
| [eBPF 教程:无需内核补丁修复故障的 HID 设备](49-hid/README.zh.md) | 6.3 | 教程文档 | x86_64, arm64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_HID_BPF=y, CONFIG_UHID=y\|m | 无 | 需要 | CI 构建测试 |
|
||||
| [eBPF 入门实践教程第五十篇:使用 TCX Link 实现可组合的流量控制](50-tcx/README.zh.md) | 6.6 | 教程文档 | x86_64, arm64 | 可选 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_NET_XGRESS=y | 网络接口 | 需要 | CI 构建测试 |
|
||||
| [eBPF 实践教程: 隔离已建立的 TCP 连接](51-tcp-quarantine/README.zh.md) | 6.5 | 教程文档 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_INET=y | 无 | 需要 | CI 运行测试 |
|
||||
| [eBPF 实践教程: 隔离已建立的 TCP 连接](51-tcp-quarantine/README.zh.md) | 6.5 | 教程文档 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_INET=y, CONFIG_PROC_FS=y | 无 | 需要 | CI 运行测试 |
|
||||
| [eBPF 实例教程:基于 cgroup 的策略控制](cgroup/README.zh.md) | 5.8 | 必需特性 | x86_64, arm64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_CGROUPS=y, CONFIG_CGROUP_BPF=y, CONFIG_CGROUP_DEVICE=y, CONFIG_DEBUG_INFO_BTF=y | 无 | 需要 | CI 构建测试 |
|
||||
| [eBPF 实例教程:BPF Arena 零拷贝共享内存](features/bpf_arena/README.zh.md) | 6.9 | 教程文档 | x86_64, arm64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y | 无 | 需要 | CI 构建测试 |
|
||||
| [eBPF 教程:BPF 迭代器用于内核数据导出](features/bpf_iters/README.zh.md) | 5.8 | 教程文档 | x86_64, arm64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_DEBUG_INFO_BTF=y | 无 | 需要 | CI 构建测试 |
|
||||
|
||||
Reference in New Issue
Block a user