요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
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/20200218171023.844439-7-jakub@cloudflare.com/
.. _`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. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 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/20200218171023.844439-7-jakub@cloudflare.com/](https://lore.kernel.org/bpf/20200218171023.844439-7-jakub@cloudflare.com/)
- [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를 다시 검사해야 합니다.