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