요약·해설, 영어 원문, 전체 번역을 구분해 실었습니다. API 이름, 코드 심벌, 소스 경로는 원문 표기를 유지합니다.
1. 요약·해설
원문의 핵심 내용을 정리하고 커널 프로그래밍에 필요한 설명을 덧붙였습니다. 아래의 전체 번역과는 별도로 작성한 해설입니다.
느린 수신자와 backpressure: 2026년 발표 제안 읽기
Hemanth Malla의 LSF/MM/BPF 2026 발표는 socket redirection 대상이 느리게 읽을 때 누가 메모리 증가와 발신 속도를 제한하는지 다룹니다. 아래의 기존 API 설명과 구분해야 할 연구 제안입니다. 중앙 검토에서 읽은 Linux v6.18 UAPI에는 BPF_F_PERMANENT가 없고 메시지 redirect의 flag 설명은 BPF_F_INGRESS를 다룹니다. 발표에 이름이 등장했다고 바로 컴파일 가능한 표준 옵션으로 사용할 수는 없습니다. 다른 버전이나 패치 트리는 해당 소스와 병합 상태를 다시 확인해야 합니다.
발표의 첫 실험은 200 KB/s와 2000 KB/s의 두 producer 및 느린 consumer를 둡니다. 9쪽 그래프에서 classic sockmap의 관찰 메모리는 10 MB를 넘어 증가하고, TCP baseline과 제안 PoC는 더 낮게 유지됩니다. 점선은 sk_rcvbuf 64 KB이며 제안의 선도 그 아래에 붙는 것은 아닙니다. 제한된 시간의 그래프는 문제를 보여주는 사례이지 무한 시간·모든 workload에서의 무한 증가나 엄밀한 64 KB 상한을 입증하지 않습니다.
제안은 첫 SK_MSG verdict로 source→target 관계를 예약하고 첫 데이터가 성공적으로 queue된 뒤 활성화하는 구상입니다. 이후 source의 send가 매번 BPF verdict를 실행하지 않고 target으로 가며, target이 가득 차면 local sender를 기다리게 하고 recv가 공간을 반환하면 깨웁니다. 12쪽 도식의 dst_psock->ingress_msg와 dst_sk->sk_receive_queue는 합산 accounting 대상으로 그려져 있습니다. 예약과 commit의 정확한 위치는 14쪽에서 질문으로 남아 있으므로 확정된 커널 구현으로 서술하지 않습니다.
SK_MSG의 발신 문맥과 SK_SKB의 수신 처리 문맥도 다릅니다. 후자에서는 sender가 원격 host일 수 있어 같은 방식으로 sender를 재울 수 없습니다. 발표는 source TCP buffer를 통한 backpressure, destination 공간 확인과 queue 배출 방법을 논의합니다. kTLS의 완전한 record 공간, one-to-many 관계, map과 redirection API의 분리도 열린 설계 질문입니다.
21쪽 TCP_RR의 1-byte 요청·응답 결과는 baseline/기존 sockmap/제안 순으로 평균 24.0/19.9/19.6 µs, p99 31/27/26 µs, 처리율 41.6/50.2/50.9 K회/s입니다. 22쪽 TCP_STREAM에서 8 KiB의 baseline 대비 향상은 기존 +83%, 제안 +88%이지만 256 KiB는 기존 +71%, 제안 +18%입니다. 23쪽의 64–256 byte 구간은 두 sockmap 경로가 baseline보다 낮습니다. 따라서 일률적 성능 개선으로 요약하지 않습니다. CPU·커널 revision·반복 횟수·오차 범위를 이 자료만으로 재구성할 수 없고, 여기서 독립 재현하지 않았습니다.
24쪽의 최대 160.92%는 별도의 message-corking 패치 소개입니다. 이를 permanent redirection의 개선율로 합치지 않습니다. 실제 검증에는 source·destination queue 메모리, slow-reader 동작, wait/wakeup, 종료·연결 해제, 작은 메시지의 지연과 처리율을 각각 기록해야 합니다.
출처: Hemanth Malla의 발표 게시물에 연결된 저장 PDF 25쪽. 중앙에서는 전체 텍스트와 9·12·21–24쪽 도면·그래프를 직접 확인했습니다. 원본 최초 업로드 파일과의 바이트 동일성, PoC 실행, 전체 최신 커널의 채택 여부는 주장하지 않으며 원 그래프를 재게시하지 않습니다.
English summary. Permanent redirection is a research proposal, not an option present in the checked v6.18 UAPI. Its queue-accounting and wait/wakeup questions differ by execution context. Reported PoC gains depend on message size; the unrelated corking result must not be merged with them.
2. 영어 원문 전체
번역에 사용한 Linux v6.18.37 원문입니다. 줄 번호는 해당 버전의 소스 파일을 기준으로 합니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0-only
.. Copyright Red Hat
==============================================
BPF_MAP_TYPE_SOCKMAP and BPF_MAP_TYPE_SOCKHASH
==============================================
.. note::
- ``BPF_MAP_TYPE_SOCKMAP`` was introduced in kernel version 4.14
- ``BPF_MAP_TYPE_SOCKHASH`` was introduced in kernel version 4.18
``BPF_MAP_TYPE_SOCKMAP`` and ``BPF_MAP_TYPE_SOCKHASH`` maps can be used to
redirect skbs between sockets or to apply policy at the socket level based on
the result of a BPF (verdict) program with the help of the BPF helpers
``bpf_sk_redirect_map()``, ``bpf_sk_redirect_hash()``,
``bpf_msg_redirect_map()`` and ``bpf_msg_redirect_hash()``.
``BPF_MAP_TYPE_SOCKMAP`` is backed by an array that uses an integer key as the
index to look up a reference to a ``struct sock``. The map values are socket
descriptors. Similarly, ``BPF_MAP_TYPE_SOCKHASH`` is a hash backed BPF map that
holds references to sockets via their socket descriptors.
.. note::
The value type is either __u32 or __u64; the latter (__u64) is to support
returning socket cookies to userspace. Returning the ``struct sock *`` that
the map holds to user-space is neither safe nor useful.
These maps may have BPF programs attached to them, specifically a parser program
and a verdict program. The parser program determines how much data has been
parsed and therefore how much data needs to be queued to come to a verdict. The
verdict program is essentially the redirect program and can return a verdict
of ``__SK_DROP``, ``__SK_PASS``, or ``__SK_REDIRECT``.
When a socket is inserted into one of these maps, its socket callbacks are
replaced and a ``struct sk_psock`` is attached to it. Additionally, this
``sk_psock`` inherits the programs that are attached to the map.
A sock object may be in multiple maps, but can only inherit a single
parse or verdict program. If adding a sock object to a map would result
in having multiple parser programs the update will return an EBUSY error.
The supported programs to attach to these maps are:
.. code-block:: c
struct sk_psock_progs {
struct bpf_prog *msg_parser;
struct bpf_prog *stream_parser;
struct bpf_prog *stream_verdict;
struct bpf_prog *skb_verdict;
};
.. note::
Users are not allowed to attach ``stream_verdict`` and ``skb_verdict``
programs to the same map.
The attach types for the map programs are:
- ``msg_parser`` program - ``BPF_SK_MSG_VERDICT``.
- ``stream_parser`` program - ``BPF_SK_SKB_STREAM_PARSER``.
- ``stream_verdict`` program - ``BPF_SK_SKB_STREAM_VERDICT``.
- ``skb_verdict`` program - ``BPF_SK_SKB_VERDICT``.
There are additional helpers available to use with the parser and verdict
programs: ``bpf_msg_apply_bytes()`` and ``bpf_msg_cork_bytes()``. With
``bpf_msg_apply_bytes()`` BPF programs can tell the infrastructure how many
bytes the given verdict should apply to. The helper ``bpf_msg_cork_bytes()``
handles a different case where a BPF program cannot reach a verdict on a msg
until it receives more bytes AND the program doesn't want to forward the packet
until it is known to be good.
Finally, the helpers ``bpf_msg_pull_data()`` and ``bpf_msg_push_data()`` are
available to ``BPF_PROG_TYPE_SK_MSG`` BPF programs to pull in data and set the
start and end pointers to given values or to add metadata to the ``struct
sk_msg_buff *msg``.
All these helpers will be described in more detail below.
Usage
=====
Kernel BPF
----------
bpf_msg_redirect_map()
^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_msg_redirect_map(struct sk_msg_buff *msg, struct bpf_map *map, u32 key, u64 flags)
This helper is used in programs implementing policies at the socket level. If
the message ``msg`` is allowed to pass (i.e., if the verdict BPF program
returns ``SK_PASS``), redirect it to the socket referenced by ``map`` (of type
``BPF_MAP_TYPE_SOCKMAP``) at index ``key``. Both ingress and egress interfaces
can be used for redirection. The ``BPF_F_INGRESS`` value in ``flags`` is used
to select the ingress path otherwise the egress path is selected. This is the
only flag supported for now.
Returns ``SK_PASS`` on success, or ``SK_DROP`` on error.
bpf_sk_redirect_map()
^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_sk_redirect_map(struct sk_buff *skb, struct bpf_map *map, u32 key u64 flags)
Redirect the packet to the socket referenced by ``map`` (of type
``BPF_MAP_TYPE_SOCKMAP``) at index ``key``. Both ingress and egress interfaces
can be used for redirection. The ``BPF_F_INGRESS`` value in ``flags`` is used
to select the ingress path otherwise the egress path is selected. This is the
only flag supported for now.
Returns ``SK_PASS`` on success, or ``SK_DROP`` on error.
bpf_map_lookup_elem()
^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
socket entries of type ``struct sock *`` can be retrieved using the
``bpf_map_lookup_elem()`` helper.
bpf_sock_map_update()
^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_sock_map_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags)
Add an entry to, or update a ``map`` referencing sockets. The ``skops`` is used
as a new value for the entry associated to ``key``. The ``flags`` argument can
be one of the following:
- ``BPF_ANY``: Create a new element or update an existing element.
- ``BPF_NOEXIST``: Create a new element only if it did not exist.
- ``BPF_EXIST``: Update an existing element.
If the ``map`` has BPF programs (parser and verdict), those will be inherited
by the socket being added. If the socket is already attached to BPF programs,
this results in an error.
Returns 0 on success, or a negative error in case of failure.
bpf_sock_hash_update()
^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_sock_hash_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags)
Add an entry to, or update a sockhash ``map`` referencing sockets. The ``skops``
is used as a new value for the entry associated to ``key``.
The ``flags`` argument can be one of the following:
- ``BPF_ANY``: Create a new element or update an existing element.
- ``BPF_NOEXIST``: Create a new element only if it did not exist.
- ``BPF_EXIST``: Update an existing element.
If the ``map`` has BPF programs (parser and verdict), those will be inherited
by the socket being added. If the socket is already attached to BPF programs,
this results in an error.
Returns 0 on success, or a negative error in case of failure.
bpf_msg_redirect_hash()
^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_msg_redirect_hash(struct sk_msg_buff *msg, struct bpf_map *map, void *key, u64 flags)
This helper is used in programs implementing policies at the socket level. If
the message ``msg`` is allowed to pass (i.e., if the verdict BPF program returns
``SK_PASS``), redirect it to the socket referenced by ``map`` (of type
``BPF_MAP_TYPE_SOCKHASH``) using hash ``key``. Both ingress and egress
interfaces can be used for redirection. The ``BPF_F_INGRESS`` value in
``flags`` is used to select the ingress path otherwise the egress path is
selected. This is the only flag supported for now.
Returns ``SK_PASS`` on success, or ``SK_DROP`` on error.
bpf_sk_redirect_hash()
^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_sk_redirect_hash(struct sk_buff *skb, struct bpf_map *map, void *key, u64 flags)
This helper is used in programs implementing policies at the skb socket level.
If the sk_buff ``skb`` is allowed to pass (i.e., if the verdict BPF program
returns ``SK_PASS``), redirect it to the socket referenced by ``map`` (of type
``BPF_MAP_TYPE_SOCKHASH``) using hash ``key``. Both ingress and egress
interfaces can be used for redirection. The ``BPF_F_INGRESS`` value in
``flags`` is used to select the ingress path otherwise the egress path is
selected. This is the only flag supported for now.
Returns ``SK_PASS`` on success, or ``SK_DROP`` on error.
bpf_msg_apply_bytes()
^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_msg_apply_bytes(struct sk_msg_buff *msg, u32 bytes)
For socket policies, apply the verdict of the BPF program to the next (number
of ``bytes``) of message ``msg``. For example, this helper can be used in the
following cases:
- A single ``sendmsg()`` or ``sendfile()`` system call contains multiple
logical messages that the BPF program is supposed to read and for which it
should apply a verdict.
- A BPF program only cares to read the first ``bytes`` of a ``msg``. If the
message has a large payload, then setting up and calling the BPF program
repeatedly for all bytes, even though the verdict is already known, would
create unnecessary overhead.
Returns 0
bpf_msg_cork_bytes()
^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_msg_cork_bytes(struct sk_msg_buff *msg, u32 bytes)
For socket policies, prevent the execution of the verdict BPF program for
message ``msg`` until the number of ``bytes`` have been accumulated.
This can be used when one needs a specific number of bytes before a verdict can
be assigned, even if the data spans multiple ``sendmsg()`` or ``sendfile()``
calls.
Returns 0
bpf_msg_pull_data()
^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_msg_pull_data(struct sk_msg_buff *msg, u32 start, u32 end, u64 flags)
For socket policies, pull in non-linear data from user space for ``msg`` and set
pointers ``msg->data`` and ``msg->data_end`` to ``start`` and ``end`` bytes
offsets into ``msg``, respectively.
If a program of type ``BPF_PROG_TYPE_SK_MSG`` is run on a ``msg`` it can only
parse data that the (``data``, ``data_end``) pointers have already consumed.
For ``sendmsg()`` hooks this is likely the first scatterlist element. But for
calls relying on MSG_SPLICE_PAGES (e.g., ``sendfile()``) this will be the
range (**0**, **0**) because the data is shared with user space and by default
the objective is to avoid allowing user space to modify data while (or after)
BPF verdict is being decided. This helper can be used to pull in data and to
set the start and end pointers to given values. Data will be copied if
necessary (i.e., if data was not linear and if start and end pointers do not
point to the same chunk).
A call to this helper is susceptible to change the underlying packet buffer.
Therefore, at load time, all checks on pointers previously done by the verifier
are invalidated and must be performed again, if the helper is used in
combination with direct packet access.
All values for ``flags`` are reserved for future usage, and must be left at
zero.
Returns 0 on success, or a negative error in case of failure.
bpf_map_lookup_elem()
^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
Look up a socket entry in the sockmap or sockhash map.
Returns the socket entry associated to ``key``, or NULL if no entry was found.
bpf_map_update_elem()
^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)
Add or update a socket entry in a sockmap or sockhash.
The flags argument can be one of the following:
- BPF_ANY: Create a new element or update an existing element.
- BPF_NOEXIST: Create a new element only if it did not exist.
- BPF_EXIST: Update an existing element.
Returns 0 on success, or a negative error in case of failure.
bpf_map_delete_elem()
^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
long bpf_map_delete_elem(struct bpf_map *map, const void *key)
Delete a socket entry from a sockmap or a sockhash.
Returns 0 on success, or a negative error in case of failure.
User space
----------
bpf_map_update_elem()
^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags)
Sockmap entries can be added or updated using the ``bpf_map_update_elem()``
function. The ``key`` parameter is the index value of the sockmap array. And the
``value`` parameter is the FD value of that socket.
Under the hood, the sockmap update function uses the socket FD value to
retrieve the associated socket and its attached psock.
The flags argument can be one of the following:
- BPF_ANY: Create a new element or update an existing element.
- BPF_NOEXIST: Create a new element only if it did not exist.
- BPF_EXIST: Update an existing element.
bpf_map_lookup_elem()
^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
int bpf_map_lookup_elem(int fd, const void *key, void *value)
Sockmap entries can be retrieved using the ``bpf_map_lookup_elem()`` function.
.. note::
The entry returned is a socket cookie rather than a socket itself.
bpf_map_delete_elem()
^^^^^^^^^^^^^^^^^^^^^
.. code-block:: c
int bpf_map_delete_elem(int fd, const void *key)
Sockmap entries can be deleted using the ``bpf_map_delete_elem()``
function.
Returns 0 on success, or negative error in case of failure.
Examples
========
Kernel BPF
----------
Several examples of the use of sockmap APIs can be found in:
- `tools/testing/selftests/bpf/progs/test_sockmap_kern.h`_
- `tools/testing/selftests/bpf/progs/sockmap_parse_prog.c`_
- `tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c`_
- `tools/testing/selftests/bpf/progs/test_sockmap_listen.c`_
- `tools/testing/selftests/bpf/progs/test_sockmap_update.c`_
The following code snippet shows how to declare a sockmap.
.. code-block:: c
struct {
__uint(type, BPF_MAP_TYPE_SOCKMAP);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, __u64);
} sock_map_rx SEC(".maps");
The following code snippet shows a sample parser program.
.. code-block:: c
SEC("sk_skb/stream_parser")
int bpf_prog_parser(struct __sk_buff *skb)
{
return skb->len;
}
The following code snippet shows a simple verdict program that interacts with a
sockmap to redirect traffic to another socket based on the local port.
.. code-block:: c
SEC("sk_skb/stream_verdict")
int bpf_prog_verdict(struct __sk_buff *skb)
{
__u32 lport = skb->local_port;
__u32 idx = 0;
if (lport == 10000)
return bpf_sk_redirect_map(skb, &sock_map_rx, idx, 0);
return SK_PASS;
}
The following code snippet shows how to declare a sockhash map.
.. code-block:: c
struct socket_key {
__u32 src_ip;
__u32 dst_ip;
__u32 src_port;
__u32 dst_port;
};
struct {
__uint(type, BPF_MAP_TYPE_SOCKHASH);
__uint(max_entries, 1);
__type(key, struct socket_key);
__type(value, __u64);
} sock_hash_rx SEC(".maps");
The following code snippet shows a simple verdict program that interacts with a
sockhash to redirect traffic to another socket based on a hash of some of the
skb parameters.
.. code-block:: c
static inline
void extract_socket_key(struct __sk_buff *skb, struct socket_key *key)
{
key->src_ip = skb->remote_ip4;
key->dst_ip = skb->local_ip4;
key->src_port = skb->remote_port >> 16;
key->dst_port = (bpf_htonl(skb->local_port)) >> 16;
}
SEC("sk_skb/stream_verdict")
int bpf_prog_verdict(struct __sk_buff *skb)
{
struct socket_key key;
extract_socket_key(skb, &key);
return bpf_sk_redirect_hash(skb, &sock_hash_rx, &key, 0);
}
User space
----------
Several examples of the use of sockmap APIs can be found in:
- `tools/testing/selftests/bpf/prog_tests/sockmap_basic.c`_
- `tools/testing/selftests/bpf/test_sockmap.c`_
- `tools/testing/selftests/bpf/test_maps.c`_
The following code sample shows how to create a sockmap, attach a parser and
verdict program, as well as add a socket entry.
.. code-block:: c
int create_sample_sockmap(int sock, int parse_prog_fd, int verdict_prog_fd)
{
int index = 0;
int map, err;
map = bpf_map_create(BPF_MAP_TYPE_SOCKMAP, NULL, sizeof(int), sizeof(int), 1, NULL);
if (map < 0) {
fprintf(stderr, "Failed to create sockmap: %s\n", strerror(errno));
return -1;
}
err = bpf_prog_attach(parse_prog_fd, map, BPF_SK_SKB_STREAM_PARSER, 0);
if (err){
fprintf(stderr, "Failed to attach_parser_prog_to_map: %s\n", strerror(errno));
goto out;
}
err = bpf_prog_attach(verdict_prog_fd, map, BPF_SK_SKB_STREAM_VERDICT, 0);
if (err){
fprintf(stderr, "Failed to attach_verdict_prog_to_map: %s\n", strerror(errno));
goto out;
}
err = bpf_map_update_elem(map, &index, &sock, BPF_NOEXIST);
if (err) {
fprintf(stderr, "Failed to update sockmap: %s\n", strerror(errno));
goto out;
}
out:
close(map);
return err;
}
References
===========
- https://github.com/jrfastab/linux-kernel-xdp/commit/c89fd73cb9d2d7f3c716c3e00836f07b1aeb261f
- https://lwn.net/Articles/731133/
- http://vger.kernel.org/lpc_net2018_talks/ktls_bpf_paper.pdf
- https://lwn.net/Articles/748628/
- https://lore.kernel.org/bpf/[email protected]/
.. _`tools/testing/selftests/bpf/progs/test_sockmap_kern.h`: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/test_sockmap_kern.h
.. _`tools/testing/selftests/bpf/progs/sockmap_parse_prog.c`: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c
.. _`tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c`: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c
.. _`tools/testing/selftests/bpf/prog_tests/sockmap_basic.c`: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
.. _`tools/testing/selftests/bpf/test_sockmap.c`: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/test_sockmap.c
.. _`tools/testing/selftests/bpf/test_maps.c`: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/test_maps.c
.. _`tools/testing/selftests/bpf/progs/test_sockmap_listen.c`: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/test_sockmap_listen.c
.. _`tools/testing/selftests/bpf/progs/test_sockmap_update.c`: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/test_sockmap_update.c
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미에 맞춰 전체 내용을 번역했습니다. 코드, 함수명, 코드 심벌, URL은 원문 표기를 유지합니다.
SOCKMAP, SOCKHASH와 attached program
1-78`BPF_MAP_TYPE_SOCKMAP`과 `BPF_MAP_TYPE_SOCKHASH` 문서는 `GPL-2.0-only` 라이선스와 `Copyright Red Hat`을 명시합니다.
`BPF_MAP_TYPE_SOCKMAP`은 `kernel version 4.14`, `BPF_MAP_TYPE_SOCKHASH`는 `kernel version 4.18`에 도입되었습니다.
두 map은 `bpf_sk_redirect_map()`, `bpf_sk_redirect_hash()`, `bpf_msg_redirect_map()`, `bpf_msg_redirect_hash()` helper와 verdict program 결과를 이용해 socket 사이에서 skb를 redirect하거나 socket-level policy를 적용합니다.
`BPF_MAP_TYPE_SOCKMAP`은 integer key를 array index로 사용해 `struct sock` reference를 찾으며 value는 socket descriptor입니다. `BPF_MAP_TYPE_SOCKHASH`는 socket descriptor를 통해 socket reference를 보관하는 hash-backed map입니다.
Value type은 `__u32` 또는 `__u64`입니다. `__u64`는 userspace에 socket cookie를 반환하기 위한 type이며 map이 가진 `struct sock *`를 userspace에 반환하는 것은 안전하지도 유용하지도 않습니다.
Map에는 parser program과 verdict program을 attach할 수 있습니다. Parser는 verdict를 내릴 만큼 queue해야 할 data 양을 정하고 verdict program은 redirect를 수행하며 `__SK_DROP`, `__SK_PASS`, `__SK_REDIRECT` 중 하나를 반환합니다.
Socket을 map에 삽입하면 callback이 교체되고 `struct sk_psock`이 attach됩니다. 이 `sk_psock`은 map에 attach된 program도 상속합니다. Socket object는 여러 map에 들어갈 수 있지만 parser 또는 verdict program은 하나만 상속할 수 있으며, 중복 parser가 생기면 update는 `EBUSY`를 반환합니다.
Map에 attach할 수 있는 program slot은 다음과 같습니다.
struct sk_psock_progs {
struct bpf_prog *msg_parser;
struct bpf_prog *stream_parser;
struct bpf_prog *stream_verdict;
struct bpf_prog *skb_verdict;
};
같은 map에 `stream_verdict`와 `skb_verdict` program을 동시에 attach할 수 없습니다.
각 map program의 attach type은 다음과 같습니다.
- `msg_parser` program: `BPF_SK_MSG_VERDICT`
- `stream_parser` program: `BPF_SK_SKB_STREAM_PARSER`
- `stream_verdict` program: `BPF_SK_SKB_STREAM_VERDICT`
- `skb_verdict` program: `BPF_SK_SKB_VERDICT`
Parser와 verdict program에서는 `bpf_msg_apply_bytes()`로 verdict 적용 byte 수를 정하고 `bpf_msg_cork_bytes()`로 verdict에 필요한 byte가 더 모일 때까지 forwarding을 보류할 수 있습니다.
`BPF_PROG_TYPE_SK_MSG` program은 `bpf_msg_pull_data()`로 data를 가져와 start/end pointer를 설정하고 `bpf_msg_push_data()`로 `struct sk_msg_buff *msg`에 metadata를 추가할 수 있습니다.
Redirect와 socket 등록 helper
79-193`bpf_msg_redirect_map()`은 socket policy program에서 `msg` verdict가 `SK_PASS`일 때 SOCKMAP의 `key`가 가리키는 socket으로 message를 redirect합니다.
long bpf_msg_redirect_map(struct sk_msg_buff *msg, struct bpf_map *map, u32 key, u64 flags)
`BPF_F_INGRESS`를 지정하면 ingress path, 아니면 egress path를 사용합니다. 현재 지원되는 flag는 이것뿐이며 성공하면 `SK_PASS`, error이면 `SK_DROP`을 반환합니다.
`bpf_sk_redirect_map()`은 skb packet을 SOCKMAP의 integer `key`가 가리키는 socket으로 redirect합니다.
long bpf_sk_redirect_map(struct sk_buff *skb, struct bpf_map *map, u32 key u64 flags)
Ingress/egress 선택과 return value는 message redirect와 같아서 `BPF_F_INGRESS`, `SK_PASS`, `SK_DROP` semantics를 사용합니다.
Socket entry의 `struct sock *`를 조회할 때는 다음 helper를 사용합니다.
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
`bpf_sock_map_update()`는 `bpf_sock_ops`의 socket을 SOCKMAP key에 추가하거나 갱신합니다.
long bpf_sock_map_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags)
- `BPF_ANY`: 새 element를 만들거나 existing element를 갱신합니다.
- `BPF_NOEXIST`: element가 없을 때만 새로 만듭니다.
- `BPF_EXIST`: existing element만 갱신합니다.
Map에 parser와 verdict program이 있으면 새 socket이 상속합니다. Socket에 이미 BPF program이 attach되어 있으면 error가 발생합니다. 성공하면 0, 실패하면 negative error를 반환합니다.
`bpf_sock_hash_update()`는 같은 방식으로 `bpf_sock_ops`의 socket을 SOCKHASH key에 추가하거나 갱신합니다.
long bpf_sock_hash_update(struct bpf_sock_ops *skops, struct bpf_map *map, void *key, u64 flags)
- `BPF_ANY`: 새 hash element를 만들거나 existing element를 갱신합니다.
- `BPF_NOEXIST`: hash element가 없을 때만 새로 만듭니다.
- `BPF_EXIST`: existing hash element만 갱신합니다.
Program inheritance와 existing attachment error, 0 또는 negative error return semantics는 sockmap update와 같습니다.
`bpf_msg_redirect_hash()`는 `SK_PASS` message를 SOCKHASH의 hash `key`가 가리키는 socket으로 redirect합니다.
long bpf_msg_redirect_hash(struct sk_msg_buff *msg, struct bpf_map *map, void *key, u64 flags)
`BPF_F_INGRESS`로 ingress path를 선택하며, 지정하지 않으면 egress path를 사용합니다. 성공하면 `SK_PASS`, error이면 `SK_DROP`을 반환합니다.
`bpf_sk_redirect_hash()`는 skb verdict가 `SK_PASS`일 때 SOCKHASH key가 가리키는 socket으로 packet을 redirect합니다.
long bpf_sk_redirect_hash(struct sk_buff *skb, struct bpf_map *map, void *key, u64 flags)
이 helper도 `BPF_F_INGRESS`로 path를 선택하고 성공 시 `SK_PASS`, error 시 `SK_DROP`을 반환합니다.
Message verdict byte 범위와 data pull
194-260`bpf_msg_apply_bytes()`는 BPF verdict를 message `msg`의 다음 `bytes`개 byte에 적용합니다.
long bpf_msg_apply_bytes(struct sk_msg_buff *msg, u32 bytes)
이 helper는 다음 상황에서 유용합니다.
- 한 `sendmsg()` 또는 `sendfile()` call에 여러 logical message가 있어 각각 verdict가 필요합니다.
- Program이 `msg`의 첫 `bytes`만 읽으면 되므로 이미 verdict를 아는 큰 payload 전체에 program을 반복 호출하는 overhead를 피합니다.
`bpf_msg_apply_bytes()`는 0을 반환합니다.
`bpf_msg_cork_bytes()`는 지정한 `bytes`가 모일 때까지 message의 verdict BPF program 실행을 막습니다.
long bpf_msg_cork_bytes(struct sk_msg_buff *msg, u32 bytes)
여러 `sendmsg()` 또는 `sendfile()` call에 걸쳐 data가 들어오더라도 verdict 전에 특정 byte 수가 필요할 때 사용할 수 있으며 0을 반환합니다.
`bpf_msg_pull_data()`는 userspace의 non-linear data를 `msg`로 가져오고 `msg->data`와 `msg->data_end`를 각각 `start`, `end` offset에 맞춥니다.
long bpf_msg_pull_data(struct sk_msg_buff *msg, u32 start, u32 end, u64 flags)
`BPF_PROG_TYPE_SK_MSG` program은 현재 `(data, data_end)` pointer가 포괄하는 data만 parse할 수 있습니다. `sendmsg()` hook에서는 대개 첫 scatterlist element이고, `MSG_SPLICE_PAGES`를 사용하는 `sendfile()` 등에서는 userspace와 data를 공유하므로 기본 range가 `(0, 0)`입니다.
Helper는 필요한 range를 pull하고 pointer를 설정하며 non-linear data이거나 start/end가 같은 chunk를 가리키지 않으면 data를 copy합니다.
Underlying packet buffer가 바뀔 수 있으므로 direct packet access와 함께 쓰면 verifier가 이전 pointer check를 invalidate하고 다시 검증해야 합니다. `flags`는 future use를 위해 reserve되어 있어 반드시 0이어야 합니다. 성공하면 0, 실패하면 negative error를 반환합니다.
Kernel BPF map CRUD
261-297Sockmap 또는 sockhash에서 socket entry를 찾을 때는 다음 `bpf_map_lookup_elem()` helper를 사용합니다.
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
Helper는 key에 연결된 socket entry를 반환하며 entry가 없으면 `NULL`을 반환합니다.
Socket entry를 추가하거나 갱신할 때는 다음 `bpf_map_update_elem()` helper를 사용합니다.
long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)
- `BPF_ANY`: 새 element를 만들거나 existing element를 갱신합니다.
- `BPF_NOEXIST`: element가 없을 때만 새로 만듭니다.
- `BPF_EXIST`: existing element만 갱신합니다.
Update는 성공하면 0, 실패하면 negative error를 반환합니다.
Socket entry를 삭제할 때는 다음 `bpf_map_delete_elem()` helper를 사용합니다.
long bpf_map_delete_elem(struct bpf_map *map, const void *key)
Delete는 성공하면 0, 실패하면 negative error를 반환합니다.
Userspace map CRUD와 socket cookie
298-340Userspace에서 sockmap entry를 추가하거나 갱신할 때는 다음 `bpf_map_update_elem()` function을 사용합니다.
int bpf_map_update_elem(int fd, const void *key, const void *value, __u64 flags)
`key`는 sockmap array index이고 `value`는 socket fd입니다. 내부적으로 update function은 fd에서 socket과 attached psock을 찾습니다.
- `BPF_ANY`: 새 element를 만들거나 existing element를 갱신합니다.
- `BPF_NOEXIST`: element가 없을 때만 새로 만듭니다.
- `BPF_EXIST`: existing element만 갱신합니다.
Sockmap entry를 조회할 때는 다음 `bpf_map_lookup_elem()` function을 사용합니다.
int bpf_map_lookup_elem(int fd, const void *key, void *value)
Userspace lookup이 반환하는 entry는 socket 자체가 아니라 socket cookie입니다.
Sockmap entry를 삭제할 때는 다음 `bpf_map_delete_elem()` function을 사용합니다.
int bpf_map_delete_elem(int fd, const void *key)
Delete는 성공하면 0, 실패하면 negative error를 반환합니다.
Sockmap·sockhash parser와 verdict 예제
341-434Kernel sockmap API example은 다음 source path에 있습니다.
- `tools/testing/selftests/bpf/progs/test_sockmap_kern.h`
- `tools/testing/selftests/bpf/progs/sockmap_parse_prog.c`
- `tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c`
- `tools/testing/selftests/bpf/progs/test_sockmap_listen.c`
- `tools/testing/selftests/bpf/progs/test_sockmap_update.c`
다음 declaration은 `__u32` key와 `__u64` value를 갖는 `BPF_MAP_TYPE_SOCKMAP`을 만듭니다.
struct {
__uint(type, BPF_MAP_TYPE_SOCKMAP);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, __u64);
} sock_map_rx SEC(".maps");
다음 `sk_skb/stream_parser` program은 skb 전체 length를 parsed length로 반환합니다.
SEC("sk_skb/stream_parser")
int bpf_prog_parser(struct __sk_buff *skb)
{
return skb->len;
}
다음 stream verdict program은 local port가 10000이면 index 0 socket으로 redirect하고, 그렇지 않으면 `SK_PASS`를 반환합니다.
SEC("sk_skb/stream_verdict")
int bpf_prog_verdict(struct __sk_buff *skb)
{
__u32 lport = skb->local_port;
__u32 idx = 0;
if (lport == 10000)
return bpf_sk_redirect_map(skb, &sock_map_rx, idx, 0);
return SK_PASS;
}
다음 declaration은 source/destination IPv4 address와 port로 구성한 `struct socket_key`를 사용하는 `BPF_MAP_TYPE_SOCKHASH`를 만듭니다.
struct socket_key {
__u32 src_ip;
__u32 dst_ip;
__u32 src_port;
__u32 dst_port;
};
struct {
__uint(type, BPF_MAP_TYPE_SOCKHASH);
__uint(max_entries, 1);
__type(key, struct socket_key);
__type(value, __u64);
} sock_hash_rx SEC(".maps");
다음 verdict program은 skb에서 socket key를 추출하고 `bpf_sk_redirect_hash()`로 해당 hash entry의 socket에 redirect합니다.
static inline
void extract_socket_key(struct __sk_buff *skb, struct socket_key *key)
{
key->src_ip = skb->remote_ip4;
key->dst_ip = skb->local_ip4;
key->src_port = skb->remote_port >> 16;
key->dst_port = (bpf_htonl(skb->local_port)) >> 16;
}
SEC("sk_skb/stream_verdict")
int bpf_prog_verdict(struct __sk_buff *skb)
{
struct socket_key key;
extract_socket_key(skb, &key);
return bpf_sk_redirect_hash(skb, &sock_hash_rx, &key, 0);
}
Userspace map 생성과 program attach
435-481Userspace sockmap API example은 다음 source path에 있습니다.
- `tools/testing/selftests/bpf/prog_tests/sockmap_basic.c`
- `tools/testing/selftests/bpf/test_sockmap.c`
- `tools/testing/selftests/bpf/test_maps.c`
다음 code는 sockmap을 만들고 parser와 verdict program을 attach한 뒤 socket entry를 추가합니다.
int create_sample_sockmap(int sock, int parse_prog_fd, int verdict_prog_fd)
{
int index = 0;
int map, err;
map = bpf_map_create(BPF_MAP_TYPE_SOCKMAP, NULL, sizeof(int), sizeof(int), 1, NULL);
if (map < 0) {
fprintf(stderr, "Failed to create sockmap: %s\n", strerror(errno));
return -1;
}
err = bpf_prog_attach(parse_prog_fd, map, BPF_SK_SKB_STREAM_PARSER, 0);
if (err){
fprintf(stderr, "Failed to attach_parser_prog_to_map: %s\n", strerror(errno));
goto out;
}
err = bpf_prog_attach(verdict_prog_fd, map, BPF_SK_SKB_STREAM_VERDICT, 0);
if (err){
fprintf(stderr, "Failed to attach_verdict_prog_to_map: %s\n", strerror(errno));
goto out;
}
err = bpf_map_update_elem(map, &index, &sock, BPF_NOEXIST);
if (err) {
fprintf(stderr, "Failed to update sockmap: %s\n", strerror(errno));
goto out;
}
out:
close(map);
return err;
}
`bpf_map_create()`로 entry 하나인 SOCKMAP을 만들고 `BPF_SK_SKB_STREAM_PARSER`, `BPF_SK_SKB_STREAM_VERDICT` attach type으로 program fd를 연결합니다. 마지막으로 socket fd를 `BPF_NOEXIST`로 index 0에 추가하며 모든 path에서 map fd를 close합니다.
참고 자료와 selftest source link
482-498Sockmap과 sockhash 구현, kTLS 연계, selftest source에 관한 참고 자료는 다음과 같습니다.
- [https://github.com/jrfastab/linux-kernel-xdp/commit/c89fd73cb9d2d7f3c716c3e00836f07b1aeb261f](https://github.com/jrfastab/linux-kernel-xdp/commit/c89fd73cb9d2d7f3c716c3e00836f07b1aeb261f)
- [https://lwn.net/Articles/731133/](https://lwn.net/Articles/731133/)
- [http://vger.kernel.org/lpc_net2018_talks/ktls_bpf_paper.pdf](http://vger.kernel.org/lpc_net2018_talks/ktls_bpf_paper.pdf)
- [https://lwn.net/Articles/748628/](https://lwn.net/Articles/748628/)
- [https://lore.kernel.org/bpf/[email protected]/](https://lore.kernel.org/bpf/[email protected]/)
- [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/test_sockmap_kern.h](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/test_sockmap_kern.h)
- [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/sockmap_parse_prog.c)
- [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/sockmap_verdict_prog.c)
- [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c)
- [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/test_sockmap.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/test_sockmap.c)
- [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/test_maps.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/test_maps.c)
- [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/test_sockmap_listen.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/test_sockmap_listen.c)
- [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/test_sockmap_update.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/progs/test_sockmap_update.c)
요약과 해설
map_sockmap.rst:1-498SOCKMAP은 integer array index로, SOCKHASH는 composite hash key로 socket을 참조합니다. Parser가 message boundary를 정하고 verdict program이 pass, drop, redirect policy를 적용합니다.
Socket을 map에 넣으면 `sk_psock`과 attached program을 상속합니다. Socket 하나에는 parser/verdict program set 하나만 허용되므로 여러 map 사이 program 조합에서 `EBUSY`가 발생할 수 있습니다.
Message helper는 verdict 적용 byte 수, corking, non-linear data pull을 제어합니다. Buffer 변경 helper 뒤에는 direct packet pointer 검증이 무효화되므로 pointer를 다시 검사해야 합니다.