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_CGROUP_GETSOCKOPT
,并且带有 BPF_F_ALLOW_MULTI
标志
A (root, parent)
\
B (child)
当应用程序从 cgroup B 调用 getsockopt
系统调用时,程序从下往上执行:B、A。第一个程序 (B) 查看内核 getsockopt
的结果。 它可以选择调整 optval
、optlen
并将 retval
重置为 0。之后,控制权将传递给第二个 (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
。