mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-10-29 03:34:44 +08:00
Document TCP quarantine workflow
This commit is contained in:
@@ -98,6 +98,7 @@ Security:
|
||||
- [lesson 27-replace](src/27-replace/README.md) Transparent Text Replacement in File Reads
|
||||
- [lesson 28-detach](src/28-detach/README.md) Running eBPF After Application Exits: The Lifecycle of eBPF Programs
|
||||
- [lesson 34-syscall](src/34-syscall/README.md) Modifying System Call Arguments with eBPF
|
||||
- [lesson 51-tcp-quarantine](src/51-tcp-quarantine/README.md) Quarantine an Established TCP Connection
|
||||
|
||||
|
||||
Features:
|
||||
|
||||
@@ -97,6 +97,7 @@ GPU:
|
||||
- [lesson 27-replace](src/27-replace/README.zh.md) 替换任意程序读取或者写入的文本
|
||||
- [lesson 28-detach](src/28-detach/README.zh.md) 在应用程序退出后运行 eBPF 程序:eBPF 程序的生命周期
|
||||
- [lesson 34-syscall](src/34-syscall/README.zh.md) eBPF 开发实践:使用 eBPF 修改系统调用参数
|
||||
- [lesson 51-tcp-quarantine](src/51-tcp-quarantine/README.zh.md) 隔离已建立的 TCP 连接
|
||||
|
||||
|
||||
特性:
|
||||
|
||||
247
src/51-tcp-quarantine/README.md
Normal file
247
src/51-tcp-quarantine/README.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# eBPF Tutorial by Example: Quarantine an Established TCP Connection
|
||||
|
||||
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.
|
||||
|
||||
> Complete source: <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/51-tcp-quarantine>
|
||||
|
||||
## Quick demo
|
||||
|
||||
Build the tool and run it against a target destination:
|
||||
|
||||
```bash
|
||||
cd src/51-tcp-quarantine
|
||||
make clean
|
||||
make -j2
|
||||
```
|
||||
|
||||
Dry-run (safe, nothing is destroyed):
|
||||
|
||||
```bash
|
||||
sudo ./tcp_quarantine --destination 127.0.0.1 --port 42063
|
||||
```
|
||||
|
||||
Apply (destroys matching sockets):
|
||||
|
||||
```bash
|
||||
sudo ./tcp_quarantine --destination 127.0.0.1 --port 42063 --apply
|
||||
```
|
||||
|
||||
Real output from the test environment:
|
||||
|
||||
```console
|
||||
mode=dry-run destination=127.0.0.1:42063 scanned=6 established=4 matched=1 destroyed=0 failed=0
|
||||
mode=apply destination=127.0.0.1:42063 scanned=6 established=4 matched=1 destroyed=1 failed=0
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
4. **Dry-run vs. apply.** If `apply` is false (the default), matched sockets are counted but left alone. If true, the program calls `bpf_sock_destroy` on each match.
|
||||
|
||||
5. **User space reads statistics.** After consuming the iterator to completion, the loader reads counters from BSS and prints the single summary line.
|
||||
|
||||
## The BPF program
|
||||
|
||||
The complete BPF side fits in under 60 lines:
|
||||
|
||||
```c
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "tcp_quarantine.h"
|
||||
|
||||
#define AF_INET 2
|
||||
#define TCP_ESTABLISHED 1
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
|
||||
const volatile __u32 target_addr;
|
||||
const volatile __u16 target_port;
|
||||
const volatile bool apply;
|
||||
|
||||
struct quarantine_stats stats;
|
||||
|
||||
extern int bpf_sock_destroy(struct sock_common *sock) __weak __ksym;
|
||||
|
||||
SEC("iter/tcp")
|
||||
int quarantine_tcp(struct bpf_iter__tcp *ctx)
|
||||
{
|
||||
struct sock_common *sk = ctx->sk_common;
|
||||
__u32 dst_addr;
|
||||
__u16 dst_port;
|
||||
__u16 family;
|
||||
__u8 state;
|
||||
int err;
|
||||
|
||||
if (!sk)
|
||||
return 0;
|
||||
|
||||
stats.scanned++;
|
||||
family = BPF_CORE_READ(sk, skc_family);
|
||||
state = BPF_CORE_READ(sk, skc_state);
|
||||
if (family != AF_INET || state != TCP_ESTABLISHED)
|
||||
return 0;
|
||||
|
||||
stats.established++;
|
||||
dst_addr = BPF_CORE_READ(sk, skc_daddr);
|
||||
dst_port = BPF_CORE_READ(sk, skc_dport);
|
||||
if (dst_addr != target_addr || dst_port != bpf_htons(target_port))
|
||||
return 0;
|
||||
|
||||
stats.matched++;
|
||||
if (!apply)
|
||||
return 0;
|
||||
|
||||
err = bpf_sock_destroy(sk);
|
||||
if (err)
|
||||
stats.failed++;
|
||||
else
|
||||
stats.destroyed++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
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_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.
|
||||
|
||||
- **Network byte order for the port.** `skc_dport` is stored in network order, so the comparison uses `bpf_htons(target_port)`.
|
||||
|
||||
## The user-space loader
|
||||
|
||||
The loader handles argument parsing, BPF lifecycle, and iterator execution. The critical section:
|
||||
|
||||
```c
|
||||
skel = tcp_quarantine_bpf__open();
|
||||
if (!skel) {
|
||||
fprintf(stderr, "failed to open BPF skeleton\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
skel->rodata->target_addr = destination.s_addr;
|
||||
skel->rodata->target_port = env.port;
|
||||
skel->rodata->apply = env.apply;
|
||||
|
||||
err = tcp_quarantine_bpf__load(skel);
|
||||
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",
|
||||
strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
```
|
||||
|
||||
After loading, the iterator is attached and consumed:
|
||||
|
||||
```c
|
||||
link = bpf_program__attach_iter(program, NULL);
|
||||
iter_fd = bpf_iter_create(bpf_link__fd(link));
|
||||
while ((length = read(iter_fd, buffer, sizeof(buffer))) > 0)
|
||||
;
|
||||
```
|
||||
|
||||
Reading the iterator fd to EOF drives the kernel to call the BPF callback for every TCP socket. The read buffer content is unused here because the program communicates results through BSS counters rather than seq_file output.
|
||||
|
||||
## The shared header
|
||||
|
||||
```c
|
||||
struct quarantine_stats {
|
||||
unsigned long long scanned;
|
||||
unsigned long long established;
|
||||
unsigned long long matched;
|
||||
unsigned long long destroyed;
|
||||
unsigned long long failed;
|
||||
};
|
||||
```
|
||||
|
||||
Both the BPF program and user-space loader include this header so the BSS layout is consistent.
|
||||
|
||||
## Running the automated test
|
||||
|
||||
The Python test (`tests/test_tcp_quarantine.py`) exercises four scenarios:
|
||||
|
||||
1. Invalid IPv4 address is rejected.
|
||||
2. Dry-run finds and reports the target but leaves it alive.
|
||||
3. Apply mode destroys the target socket (verified by `send()` failing with `ECONNABORTED`, `ECONNRESET`, or `EPIPE`, matching `BROKEN_ERRORS` in the test).
|
||||
4. A control connection to a different port survives both runs.
|
||||
|
||||
```bash
|
||||
sudo make test
|
||||
```
|
||||
|
||||
This runs `python3 tests/test_tcp_quarantine.py ./tcp_quarantine` which creates ephemeral loopback connections, exercises the tool, and asserts the expected behavior.
|
||||
|
||||
## Requirements
|
||||
|
||||
| Requirement | Detail |
|
||||
|---|---|
|
||||
| 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` |
|
||||
| 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.
|
||||
|
||||
## Limitations
|
||||
|
||||
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.
|
||||
- **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.
|
||||
|
||||
A real deployment would source targets from threat intelligence or policy, add RBAC controls, and integrate with an incident management system.
|
||||
|
||||
## How bpf_sock_destroy works
|
||||
|
||||
The `bpf_sock_destroy` kfunc was introduced in Linux 6.5 to give BPF programs the ability to forcibly close a socket from within an iterator context. The original motivation was allowing network policy enforcement to terminate connections that violate updated rules without requiring cooperation from user space.
|
||||
|
||||
When called on a TCP socket, the kernel invokes the protocol-specific destroy path. The exact error the owning application sees depends on which endpoint is destroyed and what operation it attempts next. The upstream TCP selftest expects `ECONNABORTED` on the client side and `ECONNRESET` on the server side; this lesson's test also accepts `EPIPE` as a send-side result. Do not assume a specific TCP state transition or packet-level behavior from these observations alone.
|
||||
|
||||
The kernel commit introducing this functionality: <https://github.com/torvalds/linux/commit/4ddbcb886268af8d12a23e6640b39d1d9c652b1b>
|
||||
|
||||
Upstream BPF selftests provide additional reference:
|
||||
- BPF program: <https://github.com/torvalds/linux/blob/v7.1/tools/testing/selftests/bpf/progs/sock_destroy_prog.c>
|
||||
- Userspace test: <https://github.com/torvalds/linux/blob/v7.1/tools/testing/selftests/bpf/prog_tests/sock_destroy.c>
|
||||
|
||||
## What you can do now
|
||||
|
||||
After completing this tutorial, you can:
|
||||
|
||||
- Identify and terminate specific TCP connections by destination without killing processes.
|
||||
- Safely preview the blast radius with dry-run before taking action.
|
||||
- Understand how BPF iterators walk kernel data structures and how kfuncs extend BPF capabilities.
|
||||
- Build on this pattern to create policy-driven connection enforcement tools.
|
||||
|
||||
## References
|
||||
|
||||
- Tutorial repository: <https://github.com/eunomia-bpf/bpf-developer-tutorial>
|
||||
- Tutorial website: <https://eunomia.dev/tutorials/>
|
||||
- `bpf_sock_destroy` kernel commit: <https://github.com/torvalds/linux/commit/4ddbcb886268af8d12a23e6640b39d1d9c652b1b>
|
||||
- Upstream sock_destroy selftest (BPF): <https://github.com/torvalds/linux/blob/v7.1/tools/testing/selftests/bpf/progs/sock_destroy_prog.c>
|
||||
- Upstream sock_destroy selftest (userspace): <https://github.com/torvalds/linux/blob/v7.1/tools/testing/selftests/bpf/prog_tests/sock_destroy.c>
|
||||
247
src/51-tcp-quarantine/README.zh.md
Normal file
247
src/51-tcp-quarantine/README.zh.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# eBPF 实践教程: 隔离已建立的 TCP 连接
|
||||
|
||||
你的威胁情报源刚刚把 `203.0.113.99` 标记为 C2 服务器。防火墙规则可以立即阻止新连接,但 `netstat` 显示三台生产节点已经和这个地址保持着活跃的 TCP 会话,数据仍在外泄。你需要立即切断这些连接,不重启服务,不杀死进程,也不影响无关流量。
|
||||
|
||||
本教程构建一个小型 eBPF 工具:扫描主机上所有 TCP 套接字,找到目标远端 IPv4 地址和端口完全匹配的 `ESTABLISHED` 连接,用 `bpf_sock_destroy` 将其销毁。默认模式是 dry-run,确认影响范围后再执行销毁。
|
||||
|
||||
> 完整源码: <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/51-tcp-quarantine>
|
||||
|
||||
## 快速演示
|
||||
|
||||
编译工具:
|
||||
|
||||
```bash
|
||||
cd src/51-tcp-quarantine
|
||||
make clean
|
||||
make -j2
|
||||
```
|
||||
|
||||
Dry-run 模式(安全,不会销毁任何连接):
|
||||
|
||||
```bash
|
||||
sudo ./tcp_quarantine --destination 127.0.0.1 --port 42063
|
||||
```
|
||||
|
||||
Apply 模式(销毁匹配的套接字):
|
||||
|
||||
```bash
|
||||
sudo ./tcp_quarantine --destination 127.0.0.1 --port 42063 --apply
|
||||
```
|
||||
|
||||
测试环境的真实输出:
|
||||
|
||||
```console
|
||||
mode=dry-run destination=127.0.0.1:42063 scanned=6 established=4 matched=1 destroyed=0 failed=0
|
||||
mode=apply destination=127.0.0.1:42063 scanned=6 established=4 matched=1 destroyed=1 failed=0
|
||||
PASS: dry-run preserved both connections; apply destroyed only the target
|
||||
```
|
||||
|
||||
请将目标 IP 和端口替换为你要隔离的实际地址。该工具执行一次扫描后退出,不会持续运行。
|
||||
|
||||
## 工作原理:端到端流程
|
||||
|
||||
工具使用 BPF TCP iterator 遍历内核套接字表,流程如下:
|
||||
|
||||
1. **用户态解析目标参数。** 加载器用 `inet_pton` 验证 IPv4 地址,转换端口号,在加载 BPF 程序之前写入只读数据段。
|
||||
|
||||
2. **BPF 程序作为 TCP iterator 加载并挂载。** 内核对 TCP 哈希表中的每个套接字调用一次迭代器回调。
|
||||
|
||||
3. **迭代器过滤。** 对每个套接字递增 `scanned` 计数器,跳过非 `AF_INET` 或非 `TCP_ESTABLISHED` 的条目,然后比较 `skc_daddr` 和 `skc_dport` 是否与目标匹配。
|
||||
|
||||
4. **Dry-run 与 apply。** 如果 `apply` 为 false(默认),匹配的套接字只被计数。如果为 true,程序调用 `bpf_sock_destroy` 销毁它。
|
||||
|
||||
5. **用户态读取统计。** 迭代器消费完毕后,加载器从 BSS 段读取计数器并打印汇总行。
|
||||
|
||||
## BPF 程序
|
||||
|
||||
完整的 BPF 侧代码不到 60 行:
|
||||
|
||||
```c
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "tcp_quarantine.h"
|
||||
|
||||
#define AF_INET 2
|
||||
#define TCP_ESTABLISHED 1
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
|
||||
const volatile __u32 target_addr;
|
||||
const volatile __u16 target_port;
|
||||
const volatile bool apply;
|
||||
|
||||
struct quarantine_stats stats;
|
||||
|
||||
extern int bpf_sock_destroy(struct sock_common *sock) __weak __ksym;
|
||||
|
||||
SEC("iter/tcp")
|
||||
int quarantine_tcp(struct bpf_iter__tcp *ctx)
|
||||
{
|
||||
struct sock_common *sk = ctx->sk_common;
|
||||
__u32 dst_addr;
|
||||
__u16 dst_port;
|
||||
__u16 family;
|
||||
__u8 state;
|
||||
int err;
|
||||
|
||||
if (!sk)
|
||||
return 0;
|
||||
|
||||
stats.scanned++;
|
||||
family = BPF_CORE_READ(sk, skc_family);
|
||||
state = BPF_CORE_READ(sk, skc_state);
|
||||
if (family != AF_INET || state != TCP_ESTABLISHED)
|
||||
return 0;
|
||||
|
||||
stats.established++;
|
||||
dst_addr = BPF_CORE_READ(sk, skc_daddr);
|
||||
dst_port = BPF_CORE_READ(sk, skc_dport);
|
||||
if (dst_addr != target_addr || dst_port != bpf_htons(target_port))
|
||||
return 0;
|
||||
|
||||
stats.matched++;
|
||||
if (!apply)
|
||||
return 0;
|
||||
|
||||
err = bpf_sock_destroy(sk);
|
||||
if (err)
|
||||
stats.failed++;
|
||||
else
|
||||
stats.destroyed++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
- **`const volatile` 用于参数传递。** 这些变量位于 `.rodata` 段,用户态在 `open()` 之后、`load()` 之前设置它们,验证器将其视为常量。这种方式比使用 map 更简洁。
|
||||
|
||||
- **`bpf_sock_destroy` 作为 kfunc 声明。** 通过 `__weak __ksym` 标注,内核在加载时解析。如果当前内核不提供该 kfunc(版本过低),加载将明确失败。
|
||||
|
||||
- **`BPF_CORE_READ` 保证可移植性。** CO-RE 重定位使同一编译产物能在不同 `sock_common` 布局的内核上运行,前提是有 BTF。
|
||||
|
||||
- **端口比较使用网络字节序。** `skc_dport` 以网络字节序存储,因此比较时使用 `bpf_htons(target_port)`。
|
||||
|
||||
## 用户态加载器
|
||||
|
||||
加载器负责参数解析、BPF 生命周期管理和迭代器执行。核心代码:
|
||||
|
||||
```c
|
||||
skel = tcp_quarantine_bpf__open();
|
||||
if (!skel) {
|
||||
fprintf(stderr, "failed to open BPF skeleton\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
skel->rodata->target_addr = destination.s_addr;
|
||||
skel->rodata->target_port = env.port;
|
||||
skel->rodata->apply = env.apply;
|
||||
|
||||
err = tcp_quarantine_bpf__load(skel);
|
||||
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",
|
||||
strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
```
|
||||
|
||||
加载完成后,挂载并消费迭代器:
|
||||
|
||||
```c
|
||||
link = bpf_program__attach_iter(program, NULL);
|
||||
iter_fd = bpf_iter_create(bpf_link__fd(link));
|
||||
while ((length = read(iter_fd, buffer, sizeof(buffer))) > 0)
|
||||
;
|
||||
```
|
||||
|
||||
对迭代器 fd 读到 EOF 会驱动内核为每个 TCP 套接字调用 BPF 回调。读取缓冲区的内容在这里不使用,因为程序通过 BSS 计数器传递结果,而非 seq_file 输出。
|
||||
|
||||
## 共享头文件
|
||||
|
||||
```c
|
||||
struct quarantine_stats {
|
||||
unsigned long long scanned;
|
||||
unsigned long long established;
|
||||
unsigned long long matched;
|
||||
unsigned long long destroyed;
|
||||
unsigned long long failed;
|
||||
};
|
||||
```
|
||||
|
||||
BPF 程序和用户态加载器都包含这个头文件,保证 BSS 布局一致。
|
||||
|
||||
## 运行自动化测试
|
||||
|
||||
Python 测试 (`tests/test_tcp_quarantine.py`) 验证四个场景:
|
||||
|
||||
1. 无效 IPv4 地址被拒绝。
|
||||
2. Dry-run 发现并报告目标但不销毁。
|
||||
3. Apply 模式销毁目标套接字(通过 `send()` 返回 `ECONNABORTED`、`ECONNRESET` 或 `EPIPE` 验证,与测试中的 `BROKEN_ERRORS` 一致)。
|
||||
4. 不同端口的控制连接在两次执行中均存活。
|
||||
|
||||
```bash
|
||||
sudo make test
|
||||
```
|
||||
|
||||
该命令执行 `python3 tests/test_tcp_quarantine.py ./tcp_quarantine`,创建临时的回环连接,运行工具,并断言预期行为。
|
||||
|
||||
## 环境要求
|
||||
|
||||
| 要求 | 详情 |
|
||||
|---|---|
|
||||
| 内核版本 | 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` |
|
||||
| 架构 | 已在 x86_64 上测试 |
|
||||
| 权限 | 已在 root 下测试。最小权限部署需要根据具体内核版本和环境确定所需的 capabilities 和 LSM 策略。 |
|
||||
|
||||
加载失败时,加载器会打印内核 errno 以及内核/BTF/迭代器/kfunc 的要求信息,帮助定位缺失的前置条件。
|
||||
|
||||
## 局限性
|
||||
|
||||
这是一个教学工具,不是生产级 agent。具体限制:
|
||||
|
||||
- **仅支持 IPv4。** 没有 IPv6 路径。
|
||||
- **单次扫描。** 工具执行一次后退出,不会监控新建连接。
|
||||
- **无进程归属信息。** 无法看到匹配套接字属于哪个进程。
|
||||
- **无 cgroup 范围限定。** 扫描覆盖主机上所有套接字,不限定容器或服务。
|
||||
- **无授权和审计日志。** 生产版本应通过策略审批控制销毁操作并记录审计轨迹。
|
||||
- **不支持 UDP 或数据包检测。** 仅处理 TCP ESTABLISHED 套接字。
|
||||
|
||||
实际部署中应从威胁情报或策略系统获取目标,增加 RBAC 控制,并集成事件管理系统。
|
||||
|
||||
## bpf_sock_destroy 原理
|
||||
|
||||
`bpf_sock_destroy` kfunc 在 Linux 6.5 中引入,使 BPF 程序能在迭代器上下文中强制关闭套接字。最初的动机是让网络策略执行器能终止违反新规则的连接,而不需要用户态配合。
|
||||
|
||||
调用时,内核执行协议特定的销毁路径。应用层看到的具体错误取决于被销毁的是哪个端点以及下一次执行的操作。上游 TCP selftest 中,客户端预期收到 `ECONNABORTED`,服务端预期收到 `ECONNRESET`;本教程的测试还接受 `EPIPE` 作为发送端的结果。不要仅凭这些观察推断特定的 TCP 状态转换或报文行为。
|
||||
|
||||
引入此功能的内核 commit: <https://github.com/torvalds/linux/commit/4ddbcb886268af8d12a23e6640b39d1d9c652b1b>
|
||||
|
||||
上游 BPF selftest 提供额外参考:
|
||||
- BPF 程序: <https://github.com/torvalds/linux/blob/v7.1/tools/testing/selftests/bpf/progs/sock_destroy_prog.c>
|
||||
- 用户态测试: <https://github.com/torvalds/linux/blob/v7.1/tools/testing/selftests/bpf/prog_tests/sock_destroy.c>
|
||||
|
||||
## 你现在能做什么
|
||||
|
||||
完成本教程后,你可以:
|
||||
|
||||
- 按目标地址精确终止特定 TCP 连接,无需杀死进程。
|
||||
- 用 dry-run 安全预览影响范围后再执行操作。
|
||||
- 理解 BPF iterator 如何遍历内核数据结构,以及 kfunc 如何扩展 BPF 能力。
|
||||
- 在此模式基础上构建策略驱动的连接管控工具。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- 教程仓库: <https://github.com/eunomia-bpf/bpf-developer-tutorial>
|
||||
- 教程网站: <https://eunomia.dev/tutorials/>
|
||||
- `bpf_sock_destroy` 内核 commit: <https://github.com/torvalds/linux/commit/4ddbcb886268af8d12a23e6640b39d1d9c652b1b>
|
||||
- 上游 sock_destroy selftest (BPF): <https://github.com/torvalds/linux/blob/v7.1/tools/testing/selftests/bpf/progs/sock_destroy_prog.c>
|
||||
- 上游 sock_destroy selftest (用户态): <https://github.com/torvalds/linux/blob/v7.1/tools/testing/selftests/bpf/prog_tests/sock_destroy.c>
|
||||
@@ -89,6 +89,7 @@ Security:
|
||||
- [lesson 27-replace](27-replace/README.md) Transparent Text Replacement in File Reads
|
||||
- [lesson 28-detach](28-detach/README.md) Running eBPF After Application Exits: The Lifecycle of eBPF Programs
|
||||
- [lesson 34-syscall](34-syscall/README.md) Modifying System Call Arguments with eBPF
|
||||
- [lesson 51-tcp-quarantine](51-tcp-quarantine/README.md) Quarantine an Established TCP Connection
|
||||
|
||||
|
||||
Features:
|
||||
|
||||
@@ -89,6 +89,7 @@ GPU:
|
||||
- [lesson 27-replace](27-replace/README.zh.md) 替换任意程序读取或者写入的文本
|
||||
- [lesson 28-detach](28-detach/README.zh.md) 在应用程序退出后运行 eBPF 程序:eBPF 程序的生命周期
|
||||
- [lesson 34-syscall](34-syscall/README.zh.md) eBPF 开发实践:使用 eBPF 修改系统调用参数
|
||||
- [lesson 51-tcp-quarantine](51-tcp-quarantine/README.zh.md) 隔离已建立的 TCP 连接
|
||||
|
||||
|
||||
特性:
|
||||
|
||||
@@ -57,6 +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: 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,6 +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 实例教程:基于 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