BPF_PROG_TYPE_CGROUP_SOCKOPT

BPF_PROG_TYPE_CGROUP_SOCKOPT 程序类型可以附加到两个 cgroup 钩子

  • BPF_CGROUP_GETSOCKOPT - 每次进程执行 getsockopt 系统调用时调用。

  • BPF_CGROUP_SETSOCKOPT - 每次进程执行 setsockopt 系统调用时调用。

上下文(struct bpf_sockopt)具有关联的套接字(sk)和所有输入参数: leveloptnameoptvaloptlen

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_CGROUP_GETSOCKOPT,并且带有 BPF_F_ALLOW_MULTI 标志

A (root, parent)
 \
  B (child)

当应用程序从 cgroup B 调用 getsockopt 系统调用时,程序从下往上执行:B、A。第一个程序 (B) 查看内核 getsockopt 的结果。 它可以选择调整 optvaloptlen 并将 retval 重置为 0。之后,控制权将传递给第二个 (A) 程序,该程序将看到与 B 相同的上下文,包括任何潜在的修改。

对于 BPF_CGROUP_SETSOCKOPT 也是如此:如果该程序附加到 A 和 B,则触发顺序为 B,然后是 A。如果 B 对输入参数(leveloptnameoptvaloptlen)进行任何更改,则链中的下一个程序 (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