mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-10-29 03:34:44 +08:00
fix: align tutorial kernel requirements
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
level=Depth
|
||||
type=Security
|
||||
kernel_min=5.8
|
||||
kernel_min=5.12
|
||||
kernel_min_basis=required-feature
|
||||
architectures=x86_64
|
||||
btf=required
|
||||
|
||||
@@ -373,7 +373,7 @@ The egress path first validates IPv4, UDP, fragmentation state, resolver address
|
||||
|
||||
On ingress, parsing starts with the reverse transport tuple. `parse_response_question()` adds the transaction ID, requires a live pending entry, and verifies a successful response containing the configured question. `parse_direct_a_answer()` accepts the compact `0xc00c` name pointer, class IN, type A, four-byte address, and a TTL from 1 to 86400 seconds. Once the answer passes, the pending query is consumed and the IP becomes eligible for connections.
|
||||
|
||||
`enforce_dns_policy()` stays narrow. TCP connects to other destination ports pass immediately. A connect to the protected port looks up `ctx->user_ip4`; a live entry returns `1`, while a missing or expired entry returns `0`, which surfaces to the application as `EPERM`. The compare-and-swap on `expired_reported` keeps the expiry notification to one event even when several threads race on the stale address.
|
||||
`enforce_dns_policy()` stays narrow. TCP connects to other destination ports pass immediately. A connect to the protected port looks up `ctx->user_ip4`; a live entry returns `1`, while a missing or expired entry returns `0`, which surfaces to the application as `EPERM`. A 64-bit compare-and-swap on `expired_reported` keeps the expiry notification to one event even when several threads race on the stale address. BPF atomic compare-and-exchange arrived in Linux 5.12, which sets this tool's minimum kernel version.
|
||||
|
||||
## Loading the Policy and Exercising Its Trust Chain
|
||||
|
||||
@@ -791,70 +791,109 @@ static int expected_demo_events(void)
|
||||
event_counts[DNS_EXPIRED] == 1 ? 0 : -1;
|
||||
}
|
||||
|
||||
static int run_demo(struct ring_buffer *ring, const struct options *options,
|
||||
const unsigned char *qname, unsigned int qname_length)
|
||||
struct demo_context {
|
||||
struct sockaddr_in server_address;
|
||||
struct sockaddr_in response_client_address;
|
||||
unsigned char dns_message[512];
|
||||
size_t query_length;
|
||||
int dns_server;
|
||||
int dns_client;
|
||||
int listener;
|
||||
};
|
||||
|
||||
static int open_demo_sockets(struct demo_context *demo,
|
||||
const struct options *options)
|
||||
{
|
||||
struct sockaddr_in server_address = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
.sin_port = htons(options->dns_port),
|
||||
};
|
||||
struct sockaddr_in client_address = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
};
|
||||
struct timespec wait_time = { .tv_sec = 1, .tv_nsec = 300000000 };
|
||||
struct sockaddr_in response_client_address;
|
||||
unsigned char dns_message[512];
|
||||
size_t query_length;
|
||||
int dns_server = -1, dns_client = -1, listener = -1;
|
||||
int err = -1;
|
||||
|
||||
dns_server = bind_udp(&server_address);
|
||||
dns_client = bind_udp(&client_address);
|
||||
listener = create_tcp_listener(options->port);
|
||||
if (dns_server < 0 || dns_client < 0 || listener < 0)
|
||||
goto cleanup;
|
||||
demo->server_address = (struct sockaddr_in) {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
.sin_port = htons(options->dns_port),
|
||||
};
|
||||
demo->dns_server = bind_udp(&demo->server_address);
|
||||
demo->dns_client = bind_udp(&client_address);
|
||||
demo->listener = create_tcp_listener(options->port);
|
||||
return demo->dns_server < 0 || demo->dns_client < 0 || demo->listener < 0 ?
|
||||
-1 : 0;
|
||||
}
|
||||
|
||||
static int test_rejected_dns_answers(struct ring_buffer *ring,
|
||||
const struct options *options,
|
||||
const unsigned char *qname,
|
||||
unsigned int qname_length,
|
||||
struct demo_context *demo)
|
||||
{
|
||||
if (expect_blocked_connect(ring, options->port, "before-dns"))
|
||||
goto cleanup;
|
||||
|
||||
if (send_unsolicited_dns(dns_server, dns_client, qname, qname_length))
|
||||
goto cleanup;
|
||||
if (poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port, "unsolicited-response"))
|
||||
goto cleanup;
|
||||
|
||||
if (begin_dns_exchange(dns_server, dns_client, &server_address, qname,
|
||||
qname_length, dns_message, &query_length,
|
||||
&response_client_address) ||
|
||||
send_dns_answer(dns_server, dns_client, &response_client_address,
|
||||
dns_message, query_length, DNS_ID + 1, 30))
|
||||
goto cleanup;
|
||||
if (poll_demo_events(ring) ||
|
||||
return -1;
|
||||
if (send_unsolicited_dns(demo->dns_server, demo->dns_client, qname,
|
||||
qname_length) ||
|
||||
poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port,
|
||||
"unsolicited-response"))
|
||||
return -1;
|
||||
if (begin_dns_exchange(demo->dns_server, demo->dns_client,
|
||||
&demo->server_address, qname, qname_length,
|
||||
demo->dns_message, &demo->query_length,
|
||||
&demo->response_client_address) ||
|
||||
send_dns_answer(demo->dns_server, demo->dns_client,
|
||||
&demo->response_client_address, demo->dns_message,
|
||||
demo->query_length, DNS_ID + 1, 30) ||
|
||||
poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port, "wrong-transaction-id"))
|
||||
goto cleanup;
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (send_dns_answer(dns_server, dns_client, &response_client_address,
|
||||
dns_message, query_length, DNS_ID, 1))
|
||||
goto cleanup;
|
||||
if (poll_demo_events(ring) ||
|
||||
expect_allowed_connect(ring, listener, options->port))
|
||||
goto cleanup;
|
||||
static int test_live_and_expired_answer(struct ring_buffer *ring,
|
||||
const struct options *options,
|
||||
struct demo_context *demo)
|
||||
{
|
||||
struct timespec wait_time = { .tv_sec = 1, .tv_nsec = 300000000 };
|
||||
|
||||
if (send_dns_answer(demo->dns_server, demo->dns_client,
|
||||
&demo->response_client_address, demo->dns_message,
|
||||
demo->query_length, DNS_ID, 1) ||
|
||||
poll_demo_events(ring) ||
|
||||
expect_allowed_connect(ring, demo->listener, options->port))
|
||||
return -1;
|
||||
nanosleep(&wait_time, NULL);
|
||||
if (poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port, "expired-answer"))
|
||||
goto cleanup;
|
||||
return -1;
|
||||
return expected_demo_events();
|
||||
}
|
||||
|
||||
if (expected_demo_events())
|
||||
goto cleanup;
|
||||
err = 0;
|
||||
static void close_demo_sockets(struct demo_context *demo)
|
||||
{
|
||||
if (demo->listener >= 0)
|
||||
close(demo->listener);
|
||||
if (demo->dns_client >= 0)
|
||||
close(demo->dns_client);
|
||||
if (demo->dns_server >= 0)
|
||||
close(demo->dns_server);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (listener >= 0) close(listener);
|
||||
if (dns_client >= 0) close(dns_client);
|
||||
if (dns_server >= 0) close(dns_server);
|
||||
static int run_demo(struct ring_buffer *ring, const struct options *options,
|
||||
const unsigned char *qname, unsigned int qname_length)
|
||||
{
|
||||
struct demo_context demo = {
|
||||
.dns_server = -1,
|
||||
.dns_client = -1,
|
||||
.listener = -1,
|
||||
};
|
||||
int err;
|
||||
|
||||
err = open_demo_sockets(&demo, options);
|
||||
if (!err)
|
||||
err = test_rejected_dns_answers(ring, options, qname,
|
||||
qname_length, &demo);
|
||||
if (!err)
|
||||
err = test_live_and_expired_answer(ring, options, &demo);
|
||||
close_demo_sockets(&demo);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1035,7 +1074,7 @@ The three early `denied` events show that neither receiving DNS-shaped traffic n
|
||||
|
||||
| Requirement | Details |
|
||||
|---|---|
|
||||
| Kernel | Linux 5.8 or newer; the newest dependency is BPF ring buffer support |
|
||||
| Kernel | Linux 5.12 or newer; the newest dependency is BPF atomic compare-and-exchange |
|
||||
| Kernel config | `CONFIG_BPF`, `CONFIG_BPF_SYSCALL`, `CONFIG_BPF_JIT`, `CONFIG_CGROUP_BPF`, `CONFIG_DEBUG_INFO_BTF`, `CONFIG_INET` |
|
||||
| cgroup | cgroup v2, with the target workload placed below the attached directory |
|
||||
| Privileges | Root, or an equivalent set of BPF and network-administration capabilities |
|
||||
@@ -1055,5 +1094,6 @@ This example turns an observed DNS result into a time-bounded connect policy. Th
|
||||
|
||||
- [BPF ring buffer](https://docs.kernel.org/bpf/ringbuf.html)
|
||||
- [BPF LRU hash maps](https://docs.kernel.org/bpf/map_hash.html)
|
||||
- [BPF atomic compare-and-exchange commit](https://github.com/torvalds/linux/commit/5ffa25502b5ab3d639829a2d1e316cff7f59a41e)
|
||||
- [Control Group v2](https://docs.kernel.org/admin-guide/cgroup-v2.html)
|
||||
- [RFC 1035: Domain Names — Implementation and Specification](https://www.rfc-editor.org/rfc/rfc1035.html)
|
||||
|
||||
@@ -373,7 +373,7 @@ egress 路径先验证 IPv4、UDP、分片状态、解析器地址和端口,
|
||||
|
||||
ingress 路径从反向 transport tuple 开始解析。`parse_response_question()` 补上 transaction ID,要求 pending entry 仍然有效,并验证这是包含指定 question 的成功响应。`parse_direct_a_answer()` 接受常见的 `0xc00c` 压缩 name pointer、IN class、A type、4 字节地址,以及 1 到 86400 秒的 TTL。应答通过以后,pending query 会被消费,解析出的 IP 才获得连接资格。
|
||||
|
||||
`enforce_dns_policy()` 的范围很窄。发往其他目的端口的 TCP 连接直接通过;发往受保护端口的连接使用 `ctx->user_ip4` 查表,有效条目返回 `1`,缺失或过期条目返回 `0`,应用会收到 `EPERM`。`expired_reported` 上的 compare-and-swap 让多个线程同时访问旧地址时只产生一次 expired 通知。
|
||||
`enforce_dns_policy()` 的范围很窄。发往其他目的端口的 TCP 连接直接通过;发往受保护端口的连接使用 `ctx->user_ip4` 查表,有效条目返回 `1`,缺失或过期条目返回 `0`,应用会收到 `EPERM`。`expired_reported` 上的 64 位 compare-and-swap 让多个线程同时访问旧地址时只产生一次 expired 通知。BPF atomic compare-and-exchange 在 Linux 5.12 引入,这也确定了工具的最低内核版本。
|
||||
|
||||
## 加载策略并验证完整信任链
|
||||
|
||||
@@ -791,70 +791,109 @@ static int expected_demo_events(void)
|
||||
event_counts[DNS_EXPIRED] == 1 ? 0 : -1;
|
||||
}
|
||||
|
||||
static int run_demo(struct ring_buffer *ring, const struct options *options,
|
||||
const unsigned char *qname, unsigned int qname_length)
|
||||
struct demo_context {
|
||||
struct sockaddr_in server_address;
|
||||
struct sockaddr_in response_client_address;
|
||||
unsigned char dns_message[512];
|
||||
size_t query_length;
|
||||
int dns_server;
|
||||
int dns_client;
|
||||
int listener;
|
||||
};
|
||||
|
||||
static int open_demo_sockets(struct demo_context *demo,
|
||||
const struct options *options)
|
||||
{
|
||||
struct sockaddr_in server_address = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
.sin_port = htons(options->dns_port),
|
||||
};
|
||||
struct sockaddr_in client_address = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
};
|
||||
struct timespec wait_time = { .tv_sec = 1, .tv_nsec = 300000000 };
|
||||
struct sockaddr_in response_client_address;
|
||||
unsigned char dns_message[512];
|
||||
size_t query_length;
|
||||
int dns_server = -1, dns_client = -1, listener = -1;
|
||||
int err = -1;
|
||||
|
||||
dns_server = bind_udp(&server_address);
|
||||
dns_client = bind_udp(&client_address);
|
||||
listener = create_tcp_listener(options->port);
|
||||
if (dns_server < 0 || dns_client < 0 || listener < 0)
|
||||
goto cleanup;
|
||||
demo->server_address = (struct sockaddr_in) {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
.sin_port = htons(options->dns_port),
|
||||
};
|
||||
demo->dns_server = bind_udp(&demo->server_address);
|
||||
demo->dns_client = bind_udp(&client_address);
|
||||
demo->listener = create_tcp_listener(options->port);
|
||||
return demo->dns_server < 0 || demo->dns_client < 0 || demo->listener < 0 ?
|
||||
-1 : 0;
|
||||
}
|
||||
|
||||
static int test_rejected_dns_answers(struct ring_buffer *ring,
|
||||
const struct options *options,
|
||||
const unsigned char *qname,
|
||||
unsigned int qname_length,
|
||||
struct demo_context *demo)
|
||||
{
|
||||
if (expect_blocked_connect(ring, options->port, "before-dns"))
|
||||
goto cleanup;
|
||||
|
||||
if (send_unsolicited_dns(dns_server, dns_client, qname, qname_length))
|
||||
goto cleanup;
|
||||
if (poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port, "unsolicited-response"))
|
||||
goto cleanup;
|
||||
|
||||
if (begin_dns_exchange(dns_server, dns_client, &server_address, qname,
|
||||
qname_length, dns_message, &query_length,
|
||||
&response_client_address) ||
|
||||
send_dns_answer(dns_server, dns_client, &response_client_address,
|
||||
dns_message, query_length, DNS_ID + 1, 30))
|
||||
goto cleanup;
|
||||
if (poll_demo_events(ring) ||
|
||||
return -1;
|
||||
if (send_unsolicited_dns(demo->dns_server, demo->dns_client, qname,
|
||||
qname_length) ||
|
||||
poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port,
|
||||
"unsolicited-response"))
|
||||
return -1;
|
||||
if (begin_dns_exchange(demo->dns_server, demo->dns_client,
|
||||
&demo->server_address, qname, qname_length,
|
||||
demo->dns_message, &demo->query_length,
|
||||
&demo->response_client_address) ||
|
||||
send_dns_answer(demo->dns_server, demo->dns_client,
|
||||
&demo->response_client_address, demo->dns_message,
|
||||
demo->query_length, DNS_ID + 1, 30) ||
|
||||
poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port, "wrong-transaction-id"))
|
||||
goto cleanup;
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (send_dns_answer(dns_server, dns_client, &response_client_address,
|
||||
dns_message, query_length, DNS_ID, 1))
|
||||
goto cleanup;
|
||||
if (poll_demo_events(ring) ||
|
||||
expect_allowed_connect(ring, listener, options->port))
|
||||
goto cleanup;
|
||||
static int test_live_and_expired_answer(struct ring_buffer *ring,
|
||||
const struct options *options,
|
||||
struct demo_context *demo)
|
||||
{
|
||||
struct timespec wait_time = { .tv_sec = 1, .tv_nsec = 300000000 };
|
||||
|
||||
if (send_dns_answer(demo->dns_server, demo->dns_client,
|
||||
&demo->response_client_address, demo->dns_message,
|
||||
demo->query_length, DNS_ID, 1) ||
|
||||
poll_demo_events(ring) ||
|
||||
expect_allowed_connect(ring, demo->listener, options->port))
|
||||
return -1;
|
||||
nanosleep(&wait_time, NULL);
|
||||
if (poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port, "expired-answer"))
|
||||
goto cleanup;
|
||||
return -1;
|
||||
return expected_demo_events();
|
||||
}
|
||||
|
||||
if (expected_demo_events())
|
||||
goto cleanup;
|
||||
err = 0;
|
||||
static void close_demo_sockets(struct demo_context *demo)
|
||||
{
|
||||
if (demo->listener >= 0)
|
||||
close(demo->listener);
|
||||
if (demo->dns_client >= 0)
|
||||
close(demo->dns_client);
|
||||
if (demo->dns_server >= 0)
|
||||
close(demo->dns_server);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (listener >= 0) close(listener);
|
||||
if (dns_client >= 0) close(dns_client);
|
||||
if (dns_server >= 0) close(dns_server);
|
||||
static int run_demo(struct ring_buffer *ring, const struct options *options,
|
||||
const unsigned char *qname, unsigned int qname_length)
|
||||
{
|
||||
struct demo_context demo = {
|
||||
.dns_server = -1,
|
||||
.dns_client = -1,
|
||||
.listener = -1,
|
||||
};
|
||||
int err;
|
||||
|
||||
err = open_demo_sockets(&demo, options);
|
||||
if (!err)
|
||||
err = test_rejected_dns_answers(ring, options, qname,
|
||||
qname_length, &demo);
|
||||
if (!err)
|
||||
err = test_live_and_expired_answer(ring, options, &demo);
|
||||
close_demo_sockets(&demo);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -1035,7 +1074,7 @@ demo step=expired-answer result=blocked
|
||||
|
||||
| 要求 | 说明 |
|
||||
|---|---|
|
||||
| 内核 | Linux 5.8 或更高版本,最新依赖来自 BPF ring buffer |
|
||||
| 内核 | Linux 5.12 或更高版本,最新依赖来自 BPF atomic compare-and-exchange |
|
||||
| 内核配置 | `CONFIG_BPF`、`CONFIG_BPF_SYSCALL`、`CONFIG_BPF_JIT`、`CONFIG_CGROUP_BPF`、`CONFIG_DEBUG_INFO_BTF`、`CONFIG_INET` |
|
||||
| cgroup | cgroup v2,目标工作负载位于挂载目录之下 |
|
||||
| 权限 | root,或者等价的 BPF 与网络管理 capability |
|
||||
@@ -1055,5 +1094,6 @@ demo step=expired-answer result=blocked
|
||||
|
||||
- [BPF ring buffer](https://docs.kernel.org/bpf/ringbuf.html)
|
||||
- [BPF LRU hash map](https://docs.kernel.org/bpf/map_hash.html)
|
||||
- [BPF atomic compare-and-exchange 引入 commit](https://github.com/torvalds/linux/commit/5ffa25502b5ab3d639829a2d1e316cff7f59a41e)
|
||||
- [Control Group v2](https://docs.kernel.org/admin-guide/cgroup-v2.html)
|
||||
- [RFC 1035:Domain Names — Implementation and Specification](https://www.rfc-editor.org/rfc/rfc1035.html)
|
||||
|
||||
@@ -409,70 +409,109 @@ static int expected_demo_events(void)
|
||||
event_counts[DNS_EXPIRED] == 1 ? 0 : -1;
|
||||
}
|
||||
|
||||
static int run_demo(struct ring_buffer *ring, const struct options *options,
|
||||
const unsigned char *qname, unsigned int qname_length)
|
||||
struct demo_context {
|
||||
struct sockaddr_in server_address;
|
||||
struct sockaddr_in response_client_address;
|
||||
unsigned char dns_message[512];
|
||||
size_t query_length;
|
||||
int dns_server;
|
||||
int dns_client;
|
||||
int listener;
|
||||
};
|
||||
|
||||
static int open_demo_sockets(struct demo_context *demo,
|
||||
const struct options *options)
|
||||
{
|
||||
struct sockaddr_in server_address = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
.sin_port = htons(options->dns_port),
|
||||
};
|
||||
struct sockaddr_in client_address = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
};
|
||||
struct timespec wait_time = { .tv_sec = 1, .tv_nsec = 300000000 };
|
||||
struct sockaddr_in response_client_address;
|
||||
unsigned char dns_message[512];
|
||||
size_t query_length;
|
||||
int dns_server = -1, dns_client = -1, listener = -1;
|
||||
int err = -1;
|
||||
|
||||
dns_server = bind_udp(&server_address);
|
||||
dns_client = bind_udp(&client_address);
|
||||
listener = create_tcp_listener(options->port);
|
||||
if (dns_server < 0 || dns_client < 0 || listener < 0)
|
||||
goto cleanup;
|
||||
demo->server_address = (struct sockaddr_in) {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
.sin_port = htons(options->dns_port),
|
||||
};
|
||||
demo->dns_server = bind_udp(&demo->server_address);
|
||||
demo->dns_client = bind_udp(&client_address);
|
||||
demo->listener = create_tcp_listener(options->port);
|
||||
return demo->dns_server < 0 || demo->dns_client < 0 || demo->listener < 0 ?
|
||||
-1 : 0;
|
||||
}
|
||||
|
||||
static int test_rejected_dns_answers(struct ring_buffer *ring,
|
||||
const struct options *options,
|
||||
const unsigned char *qname,
|
||||
unsigned int qname_length,
|
||||
struct demo_context *demo)
|
||||
{
|
||||
if (expect_blocked_connect(ring, options->port, "before-dns"))
|
||||
goto cleanup;
|
||||
|
||||
if (send_unsolicited_dns(dns_server, dns_client, qname, qname_length))
|
||||
goto cleanup;
|
||||
if (poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port, "unsolicited-response"))
|
||||
goto cleanup;
|
||||
|
||||
if (begin_dns_exchange(dns_server, dns_client, &server_address, qname,
|
||||
qname_length, dns_message, &query_length,
|
||||
&response_client_address) ||
|
||||
send_dns_answer(dns_server, dns_client, &response_client_address,
|
||||
dns_message, query_length, DNS_ID + 1, 30))
|
||||
goto cleanup;
|
||||
if (poll_demo_events(ring) ||
|
||||
return -1;
|
||||
if (send_unsolicited_dns(demo->dns_server, demo->dns_client, qname,
|
||||
qname_length) ||
|
||||
poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port,
|
||||
"unsolicited-response"))
|
||||
return -1;
|
||||
if (begin_dns_exchange(demo->dns_server, demo->dns_client,
|
||||
&demo->server_address, qname, qname_length,
|
||||
demo->dns_message, &demo->query_length,
|
||||
&demo->response_client_address) ||
|
||||
send_dns_answer(demo->dns_server, demo->dns_client,
|
||||
&demo->response_client_address, demo->dns_message,
|
||||
demo->query_length, DNS_ID + 1, 30) ||
|
||||
poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port, "wrong-transaction-id"))
|
||||
goto cleanup;
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (send_dns_answer(dns_server, dns_client, &response_client_address,
|
||||
dns_message, query_length, DNS_ID, 1))
|
||||
goto cleanup;
|
||||
if (poll_demo_events(ring) ||
|
||||
expect_allowed_connect(ring, listener, options->port))
|
||||
goto cleanup;
|
||||
static int test_live_and_expired_answer(struct ring_buffer *ring,
|
||||
const struct options *options,
|
||||
struct demo_context *demo)
|
||||
{
|
||||
struct timespec wait_time = { .tv_sec = 1, .tv_nsec = 300000000 };
|
||||
|
||||
if (send_dns_answer(demo->dns_server, demo->dns_client,
|
||||
&demo->response_client_address, demo->dns_message,
|
||||
demo->query_length, DNS_ID, 1) ||
|
||||
poll_demo_events(ring) ||
|
||||
expect_allowed_connect(ring, demo->listener, options->port))
|
||||
return -1;
|
||||
nanosleep(&wait_time, NULL);
|
||||
if (poll_demo_events(ring) ||
|
||||
expect_blocked_connect(ring, options->port, "expired-answer"))
|
||||
goto cleanup;
|
||||
return -1;
|
||||
return expected_demo_events();
|
||||
}
|
||||
|
||||
if (expected_demo_events())
|
||||
goto cleanup;
|
||||
err = 0;
|
||||
static void close_demo_sockets(struct demo_context *demo)
|
||||
{
|
||||
if (demo->listener >= 0)
|
||||
close(demo->listener);
|
||||
if (demo->dns_client >= 0)
|
||||
close(demo->dns_client);
|
||||
if (demo->dns_server >= 0)
|
||||
close(demo->dns_server);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (listener >= 0) close(listener);
|
||||
if (dns_client >= 0) close(dns_client);
|
||||
if (dns_server >= 0) close(dns_server);
|
||||
static int run_demo(struct ring_buffer *ring, const struct options *options,
|
||||
const unsigned char *qname, unsigned int qname_length)
|
||||
{
|
||||
struct demo_context demo = {
|
||||
.dns_server = -1,
|
||||
.dns_client = -1,
|
||||
.listener = -1,
|
||||
};
|
||||
int err;
|
||||
|
||||
err = open_demo_sockets(&demo, options);
|
||||
if (!err)
|
||||
err = test_rejected_dns_answers(ring, options, qname,
|
||||
qname_length, &demo);
|
||||
if (!err)
|
||||
err = test_live_and_expired_answer(ring, options, &demo);
|
||||
close_demo_sockets(&demo);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
level=Depth
|
||||
type=Networking
|
||||
kernel_min=5.3
|
||||
kernel_min=5.7
|
||||
kernel_min_basis=required-feature
|
||||
architectures=x86_64
|
||||
btf=required
|
||||
|
||||
@@ -10,7 +10,7 @@ This tutorial builds that path from the AF_XDP ABI rather than hiding it behind
|
||||
|
||||
eBPF runs verifier-checked programs at kernel hooks, and XDP places one of those hooks at the earliest receive point in the Linux network path. AF_XDP, added in Linux 4.18, connects an XDP redirect to a socket backed by user-registered memory. An XSKMAP supplies the missing association between an RX queue number and the AF_XDP socket that serves it.
|
||||
|
||||
The roles are deliberately separate. The XDP program parses just enough of the packet to decide whether it belongs to the tool. The AF_XDP socket owns the shared-memory rings and carries selected bytes to user space. This example also checks XSKMAP with `bpf_map_lookup_elem()` before redirecting, support added in Linux 5.3, so Linux 5.3 is its minimum kernel version.
|
||||
The roles are deliberately separate. The XDP program parses just enough of the packet to decide whether it belongs to the tool. The AF_XDP socket owns the shared-memory rings and carries selected bytes to user space. This example checks XSKMAP with `bpf_map_lookup_elem()` before redirecting, support added in Linux 5.3. Its compare-and-detach cleanup uses the expected-program FD added in Linux 5.7, which sets the complete tool's minimum kernel version.
|
||||
|
||||
Follow one packet and one frame. User space allocates 64 frames of 4096 bytes in UMEM and posts their addresses to the fill ring. A UDP packet for the configured port reaches the XDP hook. The program validates Ethernet, IPv4, and UDP lengths, finds the socket registered for `ctx->rx_queue_index`, and returns `XDP_REDIRECT`. In copy mode the kernel copies the packet into one posted frame and publishes an `xdp_desc` on the RX ring. User space reads the descriptor, prints the packet, advances the consumer index, and puts the same address back on the fill ring.
|
||||
|
||||
@@ -647,7 +647,7 @@ AF_XDP dump integration test: PASS
|
||||
|
||||
| Requirement | Details |
|
||||
|---|---|
|
||||
| Kernel | Linux 5.3 or newer; AF_XDP arrived in 4.18 and this example also uses XSKMAP lookup from XDP |
|
||||
| Kernel | Linux 5.7 or newer; AF_XDP arrived in 4.18, XSKMAP lookup from XDP in 5.3, and safe expected-FD detach in 5.7 |
|
||||
| Kernel config | `CONFIG_BPF`, `CONFIG_BPF_SYSCALL`, `CONFIG_BPF_JIT`, `CONFIG_XDP_SOCKETS`, `CONFIG_DEBUG_INFO_BTF` |
|
||||
| Privileges | Root, or equivalent BPF and network-administration capabilities |
|
||||
| Interface | An interface with the selected RX queue; native XDP is optional because generic XDP is available |
|
||||
@@ -668,3 +668,4 @@ This example exposes the complete AF_XDP receive contract. XDP selects one UDP f
|
||||
- [Linux AF_XDP documentation](https://docs.kernel.org/networking/af_xdp.html)
|
||||
- [AF_XDP introduction commit](https://github.com/torvalds/linux/commit/c0c77d8fb787cfe0c3fca689c2a30d1dad4eaba7)
|
||||
- [XSKMAP lookup from XDP commit](https://github.com/torvalds/linux/commit/fada7fdc83c0)
|
||||
- [Expected-program FD for XDP replacement and detach](https://github.com/torvalds/linux/commit/92234c8f15c8d96ad7e52afdc5994cba6be68eb9)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
eBPF 可以让经过验证器检查的程序运行在内核 hook 上,XDP 则把其中一个 hook 放在 Linux 网络接收路径的最前端。Linux 4.18 引入的 AF_XDP 把 XDP redirect 连接到由用户态注册内存支撑的 socket,XSKMAP 再建立 RX queue number 与对应 AF_XDP socket 之间的关系。
|
||||
|
||||
两边的职责很明确。XDP 程序只解析足够的信息,判断报文是否属于这个工具;AF_XDP socket 管理共享内存 ring,并把选中的字节交给用户态。本例还会在 redirect 之前用 `bpf_map_lookup_elem()` 检查 XSKMAP,这项能力从 Linux 5.3 开始可用,因此最低内核版本是 5.3。
|
||||
两边的职责很明确。XDP 程序只解析足够的信息,判断报文是否属于这个工具;AF_XDP socket 管理共享内存 ring,并把选中的字节交给用户态。本例还会在 redirect 之前用 `bpf_map_lookup_elem()` 检查 XSKMAP,这项能力从 Linux 5.3 开始可用。清理阶段的 compare-and-detach 使用 Linux 5.7 加入的 expected-program FD,因此完整工具的最低内核版本是 5.7。
|
||||
|
||||
跟着一个报文和一个 frame 走一遍。用户态在 UMEM 中分配 64 个 4096 字节 frame,把它们的地址发布到 fill ring。发往指定端口的 UDP 报文到达 XDP hook 后,程序检查 Ethernet、IPv4 与 UDP 长度,通过 `ctx->rx_queue_index` 找到注册 socket,再返回 `XDP_REDIRECT`。copy mode 下,内核把报文复制到一个已发布的 frame,并把 `xdp_desc` 放入 RX ring。用户态读取 descriptor、打印报文、推进 consumer index,最后把同一个地址放回 fill ring。
|
||||
|
||||
@@ -647,7 +647,7 @@ AF_XDP dump integration test: PASS
|
||||
|
||||
| 要求 | 说明 |
|
||||
|---|---|
|
||||
| 内核 | Linux 5.3 或更高版本,AF_XDP 在 4.18 引入,本例还使用 XDP 对 XSKMAP 的 lookup |
|
||||
| 内核 | Linux 5.7 或更高版本,AF_XDP 在 4.18 引入,XDP 对 XSKMAP 的 lookup 在 5.3 引入,安全的 expected-FD detach 在 5.7 引入 |
|
||||
| 内核配置 | `CONFIG_BPF`、`CONFIG_BPF_SYSCALL`、`CONFIG_BPF_JIT`、`CONFIG_XDP_SOCKETS`、`CONFIG_DEBUG_INFO_BTF` |
|
||||
| 权限 | root,或者等价的 BPF 与网络管理 capability |
|
||||
| 网络接口 | 接口包含所选 RX queue,native XDP 可选,工具也支持 generic XDP |
|
||||
@@ -668,3 +668,4 @@ AF_XDP dump integration test: PASS
|
||||
- [Linux AF_XDP 文档](https://docs.kernel.org/networking/af_xdp.html)
|
||||
- [AF_XDP 引入 commit](https://github.com/torvalds/linux/commit/c0c77d8fb787cfe0c3fca689c2a30d1dad4eaba7)
|
||||
- [XDP 支持 XSKMAP lookup 的 commit](https://github.com/torvalds/linux/commit/fada7fdc83c0)
|
||||
- [XDP replace 与 detach 的 expected-program FD](https://github.com/torvalds/linux/commit/92234c8f15c8d96ad7e52afdc5994cba6be68eb9)
|
||||
|
||||
@@ -61,10 +61,10 @@ This table is generated from each tutorial's `.config` metadata. `Minimum kernel
|
||||
| [eBPF Tutorial: Tracing Slow vfs_read Calls with fsession](52-fsession-latency/README.md) | 7.0 | Tutorial docs | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_BPF_EVENTS=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y | None | Required | CI build |
|
||||
| [eBPF Tutorial: Building an Egress Pacer with BPF Qdisc](53-egress-pacer/README.md) | 6.16 | Tutorial docs | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_NET_SCHED=y, CONFIG_NET_SCH_BPF=y | Network interface | Required | CI build |
|
||||
| [eBPF Tutorial: Inspecting the Executable Image After exec](54-exec-image-inspector/README.md) | 6.19 | Required feature | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_BPF_LSM=y, CONFIG_SECURITY=y, CONFIG_DEBUG_INFO_BTF=y | None | Required | CI build |
|
||||
| [eBPF Tutorial by Example: Build a DNS-Derived IP Allowlist with cgroup BPF](55-dns-egress/README.md) | 5.8 | Required feature | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_CGROUP_BPF=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_INET=y | None | Required | CI build |
|
||||
| [eBPF Tutorial by Example: Build a DNS-Derived IP Allowlist with cgroup BPF](55-dns-egress/README.md) | 5.12 | Required feature | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_CGROUP_BPF=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_INET=y | None | Required | CI build |
|
||||
| [eBPF Tutorial by Example: Index TC Flows in Two Refcounted rbtrees](56-tc-flow-index/README.md) | 6.16 | Required feature | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_NET_SCHED=y, CONFIG_NET_CLS_BPF=y | Network interface | Required | CI build |
|
||||
| [eBPF Tutorial by Example: Profile memcg Reclaim Before an OOM Kill](57-oom-watch/README.md) | 7.1 | Required feature | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_BPF_EVENTS=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_MEMCG=y | None | Required | CI build |
|
||||
| [eBPF Tutorial by Example: Receive UDP Packets with AF_XDP](60-afxdp-dump/README.md) | 5.3 | Required feature | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_XDP_SOCKETS=y | Network interface | Required | CI build |
|
||||
| [eBPF Tutorial by Example: Receive UDP Packets with AF_XDP](60-afxdp-dump/README.md) | 5.7 | Required feature | x86_64 | Required | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_XDP_SOCKETS=y | Network interface | Required | CI build |
|
||||
| [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 |
|
||||
|
||||
@@ -61,10 +61,10 @@
|
||||
| [eBPF 教程:使用 fsession 追踪慢速 vfs_read 调用](52-fsession-latency/README.zh.md) | 7.0 | 教程文档 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_BPF_EVENTS=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y | 无 | 需要 | CI 构建测试 |
|
||||
| [eBPF 教程:用 BPF Qdisc 实现出口限速](53-egress-pacer/README.zh.md) | 6.16 | 教程文档 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_NET_SCHED=y, CONFIG_NET_SCH_BPF=y | 网络接口 | 需要 | CI 构建测试 |
|
||||
| [eBPF 教程:检查 exec 后实际安装的可执行镜像](54-exec-image-inspector/README.zh.md) | 6.19 | 必需特性 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_BPF_LSM=y, CONFIG_SECURITY=y, CONFIG_DEBUG_INFO_BTF=y | 无 | 需要 | CI 构建测试 |
|
||||
| [eBPF 实战教程:用 cgroup BPF 构建 DNS 派生的 IP 允许列表](55-dns-egress/README.zh.md) | 5.8 | 必需特性 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_CGROUP_BPF=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_INET=y | 无 | 需要 | CI 构建测试 |
|
||||
| [eBPF 实战教程:用 cgroup BPF 构建 DNS 派生的 IP 允许列表](55-dns-egress/README.zh.md) | 5.12 | 必需特性 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_CGROUP_BPF=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_INET=y | 无 | 需要 | CI 构建测试 |
|
||||
| [eBPF 实战教程:用两棵 refcounted rbtree 索引 TC 流量](56-tc-flow-index/README.zh.md) | 6.16 | 必需特性 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_NET_SCHED=y, CONFIG_NET_CLS_BPF=y | 网络接口 | 需要 | CI 构建测试 |
|
||||
| [eBPF 实战教程:在 OOM kill 之前分析 memcg reclaim](57-oom-watch/README.zh.md) | 7.1 | 必需特性 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_BPF_EVENTS=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_MEMCG=y | 无 | 需要 | CI 构建测试 |
|
||||
| [eBPF 实战教程:用 AF_XDP 在用户态接收 UDP 报文](60-afxdp-dump/README.zh.md) | 5.3 | 必需特性 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_XDP_SOCKETS=y | 网络接口 | 需要 | CI 构建测试 |
|
||||
| [eBPF 实战教程:用 AF_XDP 在用户态接收 UDP 报文](60-afxdp-dump/README.zh.md) | 5.7 | 必需特性 | x86_64 | 需要 | CONFIG_BPF=y, CONFIG_BPF_SYSCALL=y, CONFIG_BPF_JIT=y, CONFIG_DEBUG_INFO_BTF=y, CONFIG_XDP_SOCKETS=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