← Documents Documentation/bpf/prog_cgroup_sockopt.rst GitHub 원문 ↗

Linux 6.18.37 · BPF

BPF_PROG_TYPE_CGROUP_SOCKOPT

Cgroup getsockopt·setsockopt BPF hook의 실행 시점, context 수정 규칙, 상속 순서, 큰 optval 처리와 예제를 설명합니다.

Source pathDocumentation/bpf/prog_cgroup_sockopt.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

prog_cgroup_sockopt.rst:1-162

SETSOCKOPT hook은 kernel 처리 전에 argument를 바꾸고, GETSOCKOPT hook은 kernel 처리 후 결과를 관찰하거나 제한적으로 덮어씁니다. 두 hook 모두 cgroup 및 socket local storage를 사용할 수 있습니다.

Child와 parent에 program이 함께 있으면 child부터 parent 순으로 실행되며 수정된 context가 다음 program으로 전달됩니다. 따라서 parent policy는 child의 변경까지 포함한 상태를 검증할 수 있습니다.

`optlen`, `retval`, `PAGE_SIZE`에 관한 제약을 어기면 `EFAULT`가 발생하거나 BPF 수정이 무시됩니다. Custom option을 완전히 처리할 때는 SETSOCKOPT에서 `optlen = -1`을 사용합니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 ============================
4 BPF_PROG_TYPE_CGROUP_SOCKOPT
5 ============================
6
7 ``BPF_PROG_TYPE_CGROUP_SOCKOPT`` program type can be attached to two
8 cgroup hooks:
9
10 * ``BPF_CGROUP_GETSOCKOPT`` - called every time process executes ``getsockopt``
11 system call.
12 * ``BPF_CGROUP_SETSOCKOPT`` - called every time process executes ``setsockopt``
13 system call.
14
15 The context (``struct bpf_sockopt``) has associated socket (``sk``) and
16 all input arguments: ``level``, ``optname``, ``optval`` and ``optlen``.
17
18 BPF_CGROUP_SETSOCKOPT
19 =====================
20
21 ``BPF_CGROUP_SETSOCKOPT`` is triggered *before* the kernel handling of
22 sockopt and it has writable context: it can modify the supplied arguments
23 before passing them down to the kernel. This hook has access to the cgroup
24 and socket local storage.
25
26 If BPF program sets ``optlen`` to -1, the control will be returned
27 back to the userspace after all other BPF programs in the cgroup
28 chain finish (i.e. kernel ``setsockopt`` handling will *not* be executed).
29
30 Note, that ``optlen`` can not be increased beyond the user-supplied
31 value. It can only be decreased or set to -1. Any other value will
32 trigger ``EFAULT``.
33
34 Return Type
35 -----------
36
37 * ``0`` - reject the syscall, ``EPERM`` will be returned to the userspace.
38 * ``1`` - success, continue with next BPF program in the cgroup chain.
39
40 BPF_CGROUP_GETSOCKOPT
41 =====================
42
43 ``BPF_CGROUP_GETSOCKOPT`` is triggered *after* the kernel handing of
44 sockopt. The BPF hook can observe ``optval``, ``optlen`` and ``retval``
45 if it's interested in whatever kernel has returned. BPF hook can override
46 the values above, adjust ``optlen`` and reset ``retval`` to 0. If ``optlen``
47 has been increased above initial ``getsockopt`` value (i.e. userspace
48 buffer is too small), ``EFAULT`` is returned.
49
50 This hook has access to the cgroup and socket local storage.
51
52 Note, that the only acceptable value to set to ``retval`` is 0 and the
53 original value that the kernel returned. Any other value will trigger
54 ``EFAULT``.
55
56 Return Type
57 -----------
58
59 * ``0`` - reject the syscall, ``EPERM`` will be returned to the userspace.
60 * ``1`` - success: copy ``optval`` and ``optlen`` to userspace, return
61 ``retval`` from the syscall (note that this can be overwritten by
62 the BPF program from the parent cgroup).
63
64 Cgroup Inheritance
65 ==================
66
67 Suppose, there is the following cgroup hierarchy where each cgroup
68 has ``BPF_CGROUP_GETSOCKOPT`` attached at each level with
69 ``BPF_F_ALLOW_MULTI`` flag::
70
71 A (root, parent)
72 \
73 B (child)
74
75 When the application calls ``getsockopt`` syscall from the cgroup B,
76 the programs are executed from the bottom up: B, A. First program
77 (B) sees the result of kernel's ``getsockopt``. It can optionally
78 adjust ``optval``, ``optlen`` and reset ``retval`` to 0. After that
79 control will be passed to the second (A) program which will see the
80 same context as B including any potential modifications.
81
82 Same for ``BPF_CGROUP_SETSOCKOPT``: if the program is attached to
83 A and B, the trigger order is B, then A. If B does any changes
84 to the input arguments (``level``, ``optname``, ``optval``, ``optlen``),
85 then the next program in the chain (A) will see those changes,
86 *not* the original input ``setsockopt`` arguments. The potentially
87 modified values will be then passed down to the kernel.
88
89 Large optval
90 ============
91 When the ``optval`` is greater than the ``PAGE_SIZE``, the BPF program
92 can access only the first ``PAGE_SIZE`` of that data. So it has to options:
93
94 * Set ``optlen`` to zero, which indicates that the kernel should
95 use the original buffer from the userspace. Any modifications
96 done by the BPF program to the ``optval`` are ignored.
97 * Set ``optlen`` to the value less than ``PAGE_SIZE``, which
98 indicates that the kernel should use BPF's trimmed ``optval``.
99
100 When the BPF program returns with the ``optlen`` greater than
101 ``PAGE_SIZE``, the userspace will receive original kernel
102 buffers without any modifications that the BPF program might have
103 applied.
104
105 Example
106 =======
107
108 Recommended way to handle BPF programs is as follows:
109
110 .. code-block:: c
111
112 SEC("cgroup/getsockopt")
113 int getsockopt(struct bpf_sockopt *ctx)
114 {
115 /* Custom socket option. */
116 if (ctx->level == MY_SOL && ctx->optname == MY_OPTNAME) {
117 ctx->retval = 0;
118 optval[0] = ...;
119 ctx->optlen = 1;
120 return 1;
121 }
122
123 /* Modify kernel's socket option. */
124 if (ctx->level == SOL_IP && ctx->optname == IP_FREEBIND) {
125 ctx->retval = 0;
126 optval[0] = ...;
127 ctx->optlen = 1;
128 return 1;
129 }
130
131 /* optval larger than PAGE_SIZE use kernel's buffer. */
132 if (ctx->optlen > PAGE_SIZE)
133 ctx->optlen = 0;
134
135 return 1;
136 }
137
138 SEC("cgroup/setsockopt")
139 int setsockopt(struct bpf_sockopt *ctx)
140 {
141 /* Custom socket option. */
142 if (ctx->level == MY_SOL && ctx->optname == MY_OPTNAME) {
143 /* do something */
144 ctx->optlen = -1;
145 return 1;
146 }
147
148 /* Modify kernel's socket option. */
149 if (ctx->level == SOL_IP && ctx->optname == IP_FREEBIND) {
150 optval[0] = ...;
151 return 1;
152 }
153
154 /* optval larger than PAGE_SIZE use kernel's buffer. */
155 if (ctx->optlen > PAGE_SIZE)
156 ctx->optlen = 0;
157
158 return 1;
159 }
160
161 See ``tools/testing/selftests/bpf/progs/sockopt_sk.c`` for an example
162 of BPF program that handles socket options.
163

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

Program type과 두 cgroup hook

1-17

`BPF_PROG_TYPE_CGROUP_SOCKOPT` 문서는 `GPL-2.0` 라이선스를 사용합니다. 이 program type은 두 cgroup hook에 attach할 수 있습니다.

  • `BPF_CGROUP_GETSOCKOPT`: process가 `getsockopt` system call을 실행할 때마다 호출됩니다.
  • `BPF_CGROUP_SETSOCKOPT`: process가 `setsockopt` system call을 실행할 때마다 호출됩니다.

Context인 `struct bpf_sockopt`에는 연결된 socket `sk`와 모든 input argument인 `level`, `optname`, `optval`, `optlen`이 들어 있습니다.

BPF_CGROUP_SETSOCKOPT

18-39

`BPF_CGROUP_SETSOCKOPT`은 kernel의 sockopt 처리 전에 trigger됩니다. Writable context이므로 전달된 argument를 kernel로 넘기기 전에 수정할 수 있으며, cgroup local storage와 socket local storage에 접근할 수 있습니다.

BPF program이 `optlen`을 -1로 설정하면 cgroup chain의 나머지 BPF program이 모두 끝난 뒤 control이 user space로 돌아갑니다. 즉 kernel의 `setsockopt` 처리는 실행되지 않습니다.

`optlen`은 user가 제공한 값보다 늘릴 수 없습니다. 줄이거나 -1로 설정할 수만 있으며, 그 밖의 값은 `EFAULT`를 발생시킵니다.

Return type은 다음과 같습니다.

  • `0`: syscall을 reject하고 user space에 `EPERM`을 반환합니다.
  • `1`: 성공이며 cgroup chain의 다음 BPF program으로 계속합니다.

BPF_CGROUP_GETSOCKOPT

40-63

`BPF_CGROUP_GETSOCKOPT`은 kernel의 sockopt 처리 후에 trigger됩니다. BPF hook은 kernel이 반환한 결과에 관심이 있다면 `optval`, `optlen`, `retval`을 관찰할 수 있습니다.

Hook은 위 값을 덮어쓰고 `optlen`을 조정하며 `retval`을 0으로 reset할 수 있습니다. `optlen`을 최초 `getsockopt` 값보다 늘려 user space buffer가 부족해지면 `EFAULT`를 반환합니다. 이 hook도 cgroup 및 socket local storage에 접근할 수 있습니다.

`retval`에 설정할 수 있는 값은 0과 kernel이 반환한 original value뿐입니다. 그 밖의 값은 `EFAULT`를 발생시킵니다.

Return type은 다음과 같습니다.

  • `0`: syscall을 reject하고 user space에 `EPERM`을 반환합니다.
  • `1`: 성공이며 `optval`과 `optlen`을 user space로 복사하고 syscall에서 `retval`을 반환합니다. Parent cgroup의 BPF program이 이 값을 덮어쓸 수 있습니다.

Cgroup inheritance와 bottom-up 실행

64-88

다음 계층에서는 `A (root, parent)`와 그 아래 `B (child)` 각각에 `BPF_F_ALLOW_MULTI` flag로 `BPF_CGROUP_GETSOCKOPT`이 attach되어 있다고 가정합니다.

Cgroup sockopt program 실행 순서
Application in cgroup BKernel getsockopt resultB program modifies contextA program sees B changesUser space result
Application in cgroup BB SETSOCKOPT programA SETSOCKOPT programKernel sees modified arguments

원문의 A(root, parent) 아래 B(child) 계층을 `getsockopt` 호출 위치와 bottom-up context 전파 순서로 구조화했습니다.

Application이 cgroup B에서 `getsockopt`을 호출하면 program은 B, A 순서로 `bottom up`(bottom-up) 실행됩니다. B는 먼저 kernel `getsockopt` 결과를 보고 필요하면 `optval`, `optlen`을 조정하고 `retval`을 0으로 reset합니다. 이어 A는 B가 수정했을 수 있는 동일 context를 봅니다.

`BPF_CGROUP_SETSOCKOPT`도 A와 B에 attach되어 있으면 B, A 순서로 trigger됩니다. B가 input argument `level`, `optname`, `optval`, `optlen`을 수정하면 A는 original `setsockopt` argument가 아니라 수정된 값을 봅니다. 최종적으로 이 값이 kernel에 전달됩니다.

PAGE_SIZE보다 큰 optval

89-104

`optval`이 `PAGE_SIZE`보다 크면 BPF program은 data의 첫 `PAGE_SIZE`까지만 접근할 수 있습니다. 이 경우 두 선택지가 있습니다.

  • `optlen`을 0으로 설정하면 kernel이 user space의 original buffer를 사용합니다. BPF program이 `optval`에 적용한 수정은 무시됩니다.
  • `optlen`을 `PAGE_SIZE`보다 작은 값으로 설정하면 kernel이 BPF가 잘라낸 `optval`을 사용합니다.

BPF program이 `PAGE_SIZE`보다 큰 `optlen`으로 반환하면 user space는 BPF program의 수정이 적용되지 않은 original kernel buffer를 받습니다.

getsockopt과 setsockopt program 예제

105-162

BPF program을 처리할 때 권장되는 방식은 다음과 같습니다.

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;
}

`cgroup/getsockopt` program은 custom socket option이면 `retval`을 0으로 만들고 `optval[0]`, `optlen`을 설정합니다. Kernel의 `IP_FREEBIND` option을 수정할 때도 같은 방식으로 결과를 교체합니다.

`ctx->optlen > PAGE_SIZE`이면 `ctx->optlen = 0`으로 만들어 kernel buffer를 사용하고, 모든 허용 경로에서 1을 반환합니다.

`cgroup/setsockopt` program은 custom option을 직접 처리한 뒤 `ctx->optlen = -1`로 kernel 처리를 건너뛸 수 있습니다. `IP_FREEBIND` argument를 수정할 수도 있으며 큰 `optval`은 getsockopt 예제와 마찬가지로 `optlen` 0으로 처리합니다.

Socket option을 처리하는 BPF program의 실제 예제는 `tools/testing/selftests/bpf/progs/sockopt_sk.c`에 있습니다.