요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
============================
BPF_PROG_TYPE_CGROUP_SOCKOPT
============================
``BPF_PROG_TYPE_CGROUP_SOCKOPT`` program type can be attached to two
cgroup hooks:
* ``BPF_CGROUP_GETSOCKOPT`` - called every time process executes ``getsockopt``
system call.
* ``BPF_CGROUP_SETSOCKOPT`` - called every time process executes ``setsockopt``
system call.
The context (``struct bpf_sockopt``) has associated socket (``sk``) and
all input arguments: ``level``, ``optname``, ``optval`` and ``optlen``.
BPF_CGROUP_SETSOCKOPT
=====================
``BPF_CGROUP_SETSOCKOPT`` is triggered *before* the kernel handling of
sockopt and it has writable context: it can modify the supplied arguments
before passing them down to the kernel. This hook has access to the cgroup
and socket local storage.
If BPF program sets ``optlen`` to -1, the control will be returned
back to the userspace after all other BPF programs in the cgroup
chain finish (i.e. kernel ``setsockopt`` handling will *not* be executed).
Note, that ``optlen`` can not be increased beyond the user-supplied
value. It can only be decreased or set to -1. Any other value will
trigger ``EFAULT``.
Return Type
-----------
* ``0`` - reject the syscall, ``EPERM`` will be returned to the userspace.
* ``1`` - success, continue with next BPF program in the cgroup chain.
BPF_CGROUP_GETSOCKOPT
=====================
``BPF_CGROUP_GETSOCKOPT`` is triggered *after* the kernel handing of
sockopt. The BPF hook can observe ``optval``, ``optlen`` and ``retval``
if it's interested in whatever kernel has returned. BPF hook can override
the values above, adjust ``optlen`` and reset ``retval`` to 0. If ``optlen``
has been increased above initial ``getsockopt`` value (i.e. userspace
buffer is too small), ``EFAULT`` is returned.
This hook has access to the cgroup and socket local storage.
Note, that the only acceptable value to set to ``retval`` is 0 and the
original value that the kernel returned. Any other value will trigger
``EFAULT``.
Return Type
-----------
* ``0`` - reject the syscall, ``EPERM`` will be returned to the userspace.
* ``1`` - success: copy ``optval`` and ``optlen`` to userspace, return
``retval`` from the syscall (note that this can be overwritten by
the BPF program from the parent cgroup).
Cgroup Inheritance
==================
Suppose, there is the following cgroup hierarchy where each cgroup
has ``BPF_CGROUP_GETSOCKOPT`` attached at each level with
``BPF_F_ALLOW_MULTI`` flag::
A (root, parent)
\
B (child)
When the application calls ``getsockopt`` syscall from the cgroup B,
the programs are executed from the bottom up: B, A. First program
(B) sees the result of kernel's ``getsockopt``. It can optionally
adjust ``optval``, ``optlen`` and reset ``retval`` to 0. After that
control will be passed to the second (A) program which will see the
same context as B including any potential modifications.
Same for ``BPF_CGROUP_SETSOCKOPT``: if the program is attached to
A and B, the trigger order is B, then A. If B does any changes
to the input arguments (``level``, ``optname``, ``optval``, ``optlen``),
then the next program in the chain (A) will see those changes,
*not* the original input ``setsockopt`` arguments. The potentially
modified values will be then passed down to the kernel.
Large optval
============
When the ``optval`` is greater than the ``PAGE_SIZE``, the BPF program
can access only the first ``PAGE_SIZE`` of that data. So it has to options:
* Set ``optlen`` to zero, which indicates that the kernel should
use the original buffer from the userspace. Any modifications
done by the BPF program to the ``optval`` are ignored.
* Set ``optlen`` to the value less than ``PAGE_SIZE``, which
indicates that the kernel should use BPF's trimmed ``optval``.
When the BPF program returns with the ``optlen`` greater than
``PAGE_SIZE``, the userspace will receive original kernel
buffers without any modifications that the BPF program might have
applied.
Example
=======
Recommended way to handle BPF programs is as follows:
.. code-block:: c
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;
}
See ``tools/testing/selftests/bpf/progs/sockopt_sk.c`` for an example
of BPF program that handles socket options.
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되어 있다고 가정합니다.
원문의 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-162BPF 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`에 있습니다.
요약과 해설
prog_cgroup_sockopt.rst:1-162SETSOCKOPT 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`을 사용합니다.