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, optvaloptlen

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 钩子可以观察 optvaloptlenretval,如果它对内核返回的任何值感兴趣。BPF 钩子可以覆盖上述值,调整 optlen 并将 retval 重置为 0。如果 optlen 增加到超过初始 getsockopt 值(即用户空间缓冲区太小),则返回 EFAULT

此钩子可以访问 cgroup 和套接字局部存储。

请注意,retval 唯一可接受的设置值是 0 和内核返回的原始值。任何其他值都将触发 EFAULT

返回类型

  • 0 - 拒绝系统调用,EPERM 将返回给用户空间。

  • 1 - 成功:将 optvaloptlen 复制到用户空间,从系统调用返回 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