BPF_PROG_TYPE_CGROUP_SOCKOPT¶
BPF_PROG_TYPE_CGROUP_SOCKOPT
程序类型可以附加到两个 cgroup 钩子:
BPF_CGROUP_GETSOCKOPT
- 每当进程执行getsockopt
系统调用时调用。BPF_CGROUP_SETSOCKOPT
- 每当进程执行setsockopt
系统调用时调用。
上下文 (struct bpf_sockopt
) 关联着一个套接字 (sk
) 和所有输入参数:level
, optname
, optval
和 optlen
。
BPF_CGROUP_SETSOCKOPT¶
BPF_CGROUP_SETSOCKOPT
在内核处理 sockopt 之前触发,并且它具有可写上下文:它可以在将参数传递给内核之前修改它们。这个钩子可以访问 cgroup 和套接字局部存储。
如果 BPF 程序将 optlen
设置为 -1,则在 cgroup 链中所有其他 BPF 程序完成之后,控制权将返回给用户空间(即内核 setsockopt
处理将不会执行)。
请注意,optlen
不能增加超过用户提供的值。它只能减小或设置为 -1。任何其他值都将触发 EFAULT
。
返回类型¶
0
- 拒绝系统调用,EPERM
将返回给用户空间。1
- 成功,继续 cgroup 链中的下一个 BPF 程序。
BPF_CGROUP_GETSOCKOPT¶
BPF_CGROUP_GETSOCKOPT
在内核处理 sockopt 之后触发。BPF 钩子可以观察 optval
、optlen
和 retval
,如果它对内核返回的任何值感兴趣。BPF 钩子可以覆盖上述值,调整 optlen
并将 retval
重置为 0。如果 optlen
增加到超过初始 getsockopt
值(即用户空间缓冲区太小),则返回 EFAULT
。
此钩子可以访问 cgroup 和套接字局部存储。
请注意,retval
唯一可接受的设置值是 0 和内核返回的原始值。任何其他值都将触发 EFAULT
。
返回类型¶
0
- 拒绝系统调用,EPERM
将返回给用户空间。1
- 成功:将optval
和optlen
复制到用户空间,从系统调用返回retval
(请注意,这可能被父 cgroup 中的 BPF 程序覆盖)。
Cgroup 继承¶
假设存在以下 cgroup 层次结构,其中每个 cgroup 都以 BPF_F_ALLOW_MULTI
标志在每个级别附加了 BPF_CGROUP_GETSOCKOPT
。
A (root, parent)
\
B (child)
当应用程序从 cgroup B 调用 getsockopt
系统调用时,程序从下到上执行:B,A。第一个程序 (B) 看到内核 getsockopt
的结果。它可以选择性地调整 optval
, optlen
并将 retval
重置为 0。之后,控制权将传递给第二个程序 (A),A 将看到与 B 相同的上下文,包括任何潜在的修改。
BPF_CGROUP_SETSOCKOPT
也是如此:如果程序附加到 A 和 B,触发顺序是 B,然后是 A。如果 B 对输入参数(level
, optname
, optval
, optlen
)进行任何更改,则链中的下一个程序 (A) 将看到这些更改,而不是原始的 setsockopt
输入参数。潜在修改后的值将被传递给内核。
大型 optval¶
当 optval
大于 PAGE_SIZE
时,BPF 程序只能访问该数据的前 PAGE_SIZE
。因此它有两种选择:
将
optlen
设置为零,这表示内核应使用用户空间中的原始缓冲区。BPF 程序对optval
所做的任何修改都将被忽略。将
optlen
设置为小于PAGE_SIZE
的值,这表示内核应使用 BPF 裁剪后的optval
。
当 BPF 程序返回的 optlen
大于 PAGE_SIZE
时,用户空间将接收原始的内核缓冲区,而不会包含 BPF 程序可能应用的任何修改。
示例¶
处理 BPF 程序的推荐方法如下:
SEC("cgroup/getsockopt")
int getsockopt(struct bpf_sockopt *ctx)
{
/* Custom socket option. */
if (ctx->level == MY_SOL && ctx->optname == MY_OPTNAME) {
ctx->retval = 0;
optval[0] = ...;
ctx->optlen = 1;
return 1;
}
/* Modify kernel's socket option. */
if (ctx->level == SOL_IP && ctx->optname == IP_FREEBIND) {
ctx->retval = 0;
optval[0] = ...;
ctx->optlen = 1;
return 1;
}
/* optval larger than PAGE_SIZE use kernel's buffer. */
if (ctx->optlen > PAGE_SIZE)
ctx->optlen = 0;
return 1;
}
SEC("cgroup/setsockopt")
int setsockopt(struct bpf_sockopt *ctx)
{
/* Custom socket option. */
if (ctx->level == MY_SOL && ctx->optname == MY_OPTNAME) {
/* do something */
ctx->optlen = -1;
return 1;
}
/* Modify kernel's socket option. */
if (ctx->level == SOL_IP && ctx->optname == IP_FREEBIND) {
optval[0] = ...;
return 1;
}
/* optval larger than PAGE_SIZE use kernel's buffer. */
if (ctx->optlen > PAGE_SIZE)
ctx->optlen = 0;
return 1;
}
有关处理套接字选项的 BPF 程序的示例,请参阅 tools/testing/selftests/bpf/progs/sockopt_sk.c
。