요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
============================
BPF_PROG_TYPE_FLOW_DISSECTOR
============================
Overview
========
Flow dissector is a routine that parses metadata out of the packets. It's
used in the various places in the networking subsystem (RFS, flow hash, etc).
BPF flow dissector is an attempt to reimplement C-based flow dissector logic
in BPF to gain all the benefits of BPF verifier (namely, limits on the
number of instructions and tail calls).
API
===
BPF flow dissector programs operate on an ``__sk_buff``. However, only the
limited set of fields is allowed: ``data``, ``data_end`` and ``flow_keys``.
``flow_keys`` is ``struct bpf_flow_keys`` and contains flow dissector input
and output arguments.
The inputs are:
* ``nhoff`` - initial offset of the networking header
* ``thoff`` - initial offset of the transport header, initialized to nhoff
* ``n_proto`` - L3 protocol type, parsed out of L2 header
* ``flags`` - optional flags
Flow dissector BPF program should fill out the rest of the ``struct
bpf_flow_keys`` fields. Input arguments ``nhoff/thoff/n_proto`` should be
also adjusted accordingly.
The return code of the BPF program is either BPF_OK to indicate successful
dissection, or BPF_DROP to indicate parsing error.
__sk_buff->data
===============
In the VLAN-less case, this is what the initial state of the BPF flow
dissector looks like::
+------+------+------------+-----------+
| DMAC | SMAC | ETHER_TYPE | L3_HEADER |
+------+------+------------+-----------+
^
|
+-- flow dissector starts here
.. code:: c
skb->data + flow_keys->nhoff point to the first byte of L3_HEADER
flow_keys->thoff = nhoff
flow_keys->n_proto = ETHER_TYPE
In case of VLAN, flow dissector can be called with the two different states.
Pre-VLAN parsing::
+------+------+------+-----+-----------+-----------+
| DMAC | SMAC | TPID | TCI |ETHER_TYPE | L3_HEADER |
+------+------+------+-----+-----------+-----------+
^
|
+-- flow dissector starts here
.. code:: c
skb->data + flow_keys->nhoff point the to first byte of TCI
flow_keys->thoff = nhoff
flow_keys->n_proto = TPID
Please note that TPID can be 802.1AD and, hence, BPF program would
have to parse VLAN information twice for double tagged packets.
Post-VLAN parsing::
+------+------+------+-----+-----------+-----------+
| DMAC | SMAC | TPID | TCI |ETHER_TYPE | L3_HEADER |
+------+------+------+-----+-----------+-----------+
^
|
+-- flow dissector starts here
.. code:: c
skb->data + flow_keys->nhoff point the to first byte of L3_HEADER
flow_keys->thoff = nhoff
flow_keys->n_proto = ETHER_TYPE
In this case VLAN information has been processed before the flow dissector
and BPF flow dissector is not required to handle it.
The takeaway here is as follows: BPF flow dissector program can be called with
the optional VLAN header and should gracefully handle both cases: when single
or double VLAN is present and when it is not present. The same program
can be called for both cases and would have to be written carefully to
handle both cases.
Flags
=====
``flow_keys->flags`` might contain optional input flags that work as follows:
* ``BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG`` - tells BPF flow dissector to
continue parsing first fragment; the default expected behavior is that
flow dissector returns as soon as it finds out that the packet is fragmented;
used by ``eth_get_headlen`` to estimate length of all headers for GRO.
* ``BPF_FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL`` - tells BPF flow dissector to
stop parsing as soon as it reaches IPv6 flow label; used by
``___skb_get_hash`` to get flow hash.
* ``BPF_FLOW_DISSECTOR_F_STOP_AT_ENCAP`` - tells BPF flow dissector to stop
parsing as soon as it reaches encapsulated headers; used by routing
infrastructure.
Reference Implementation
========================
See ``tools/testing/selftests/bpf/progs/bpf_flow.c`` for the reference
implementation and ``tools/testing/selftests/bpf/flow_dissector_load.[hc]``
for the loader. bpftool can be used to load BPF flow dissector program as well.
The reference implementation is organized as follows:
* ``jmp_table`` map that contains sub-programs for each supported L3 protocol
* ``_dissect`` routine - entry point; it does input ``n_proto`` parsing and
does ``bpf_tail_call`` to the appropriate L3 handler
Since BPF at this point doesn't support looping (or any jumping back),
jmp_table is used instead to handle multiple levels of encapsulation (and
IPv6 options).
Current Limitations
===================
BPF flow dissector doesn't support exporting all the metadata that in-kernel
C-based implementation can export. Notable example is single VLAN (802.1Q)
and double VLAN (802.1AD) tags. Please refer to the ``struct bpf_flow_keys``
for a set of information that's currently can be exported from the BPF context.
When BPF flow dissector is attached to the root network namespace (machine-wide
policy), users can't override it in their child network namespaces.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Flow dissector와 BPF verifier
1-16`BPF_PROG_TYPE_FLOW_DISSECTOR` 문서는 `GPL-2.0` 라이선스를 사용합니다.
Flow dissector는 packet에서 metadata를 parse하는 routine입니다. Networking subsystem의 RFS, flow hash 등 여러 위치에서 사용됩니다.
BPF flow dissector는 C 기반 flow dissector logic을 BPF로 다시 구현해 BPF verifier의 이점, 특히 instruction 수와 tail call 수 제한을 얻으려는 방식입니다.
__sk_buff와 struct bpf_flow_keys
17-37BPF flow dissector program은 `__sk_buff`에서 동작하지만 사용할 수 있는 field는 `data`, `data_end`, `flow_keys`로 제한됩니다. `flow_keys`는 `struct bpf_flow_keys`이며 flow dissector의 input 및 output argument를 담습니다.
Input은 다음과 같습니다.
- `nhoff`: networking header의 initial offset
- `thoff`: transport header의 initial offset이며 처음에는 `nhoff`로 초기화됩니다.
- `n_proto`: L2 header에서 parse한 L3 protocol type
- `flags`: optional flag
Flow dissector BPF program은 `struct bpf_flow_keys`의 나머지 field를 채워야 하며 input argument `nhoff`, `thoff`, `n_proto`도 그에 맞게 조정해야 합니다.
BPF program의 return code는 dissection 성공을 나타내는 `BPF_OK` 또는 parsing error를 나타내는 `BPF_DROP`입니다.
VLAN이 없는 packet layout
38-57VLAN이 없는 경우 BPF flow dissector의 initial state는 다음과 같습니다.
원문의 packet ASCII를 field 순서와 flow dissector 시작점이 드러나도록 구조화했습니다.
skb->data + flow_keys->nhoff point to the first byte of L3_HEADER
flow_keys->thoff = nhoff
flow_keys->n_proto = ETHER_TYPE
`skb->data + flow_keys->nhoff`는 `L3_HEADER`의 첫 byte를 가리키고, `flow_keys->thoff = nhoff`, `flow_keys->n_proto = ETHER_TYPE`으로 시작합니다.
Pre-VLAN parsing
58-77VLAN이 있으면 flow dissector는 서로 다른 두 상태로 호출될 수 있습니다. Pre-VLAN parsing 상태는 다음과 같습니다.
TCI 첫 byte에서 dissector가 시작하며 VLAN TPID가 아직 protocol input으로 남아 있는 상태입니다.
skb->data + flow_keys->nhoff point the to first byte of TCI
flow_keys->thoff = nhoff
flow_keys->n_proto = TPID
`skb->data + flow_keys->nhoff`는 `TCI`의 첫 byte를 가리키고, `flow_keys->thoff = nhoff`, `flow_keys->n_proto = TPID`입니다.
`TPID`는 802.1AD일 수 있으므로 double tagged packet에서는 BPF program이 VLAN information을 두 번 parse해야 합니다.
Post-VLAN parsing과 공통 처리
78-103Post-VLAN parsing 상태는 다음과 같습니다.
VLAN information이 이미 처리되어 dissector가 L3 header에서 시작하는 상태입니다.
skb->data + flow_keys->nhoff point the to first byte of L3_HEADER
flow_keys->thoff = nhoff
flow_keys->n_proto = ETHER_TYPE
`skb->data + flow_keys->nhoff`는 `L3_HEADER`의 첫 byte를 가리키고, `flow_keys->thoff = nhoff`, `flow_keys->n_proto = ETHER_TYPE`입니다.
이 경우 VLAN information은 flow dissector 호출 전에 처리되었으므로 BPF flow dissector가 직접 처리할 필요가 없습니다.
핵심은 BPF flow dissector program이 optional VLAN header가 있는 상태와 없는 상태 모두로 호출될 수 있다는 점입니다. 동일한 program이 VLAN 없음, single VLAN, double VLAN을 모두 안전하게 처리하도록 신중하게 작성해야 합니다.
Optional input flags
104-120`flow_keys->flags`에는 다음 optional input flag가 들어갈 수 있습니다.
- `BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG`: 첫 fragment parsing을 계속합니다. 기본 동작은 packet이 fragmented임을 확인하면 즉시 반환하는 것입니다. `eth_get_headlen`이 GRO를 위한 전체 header 길이를 추정할 때 사용합니다.
- `BPF_FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL`: IPv6 flow label에 도달하면 parsing을 중지합니다. `___skb_get_hash`가 flow hash를 얻을 때 사용합니다.
- `BPF_FLOW_DISSECTOR_F_STOP_AT_ENCAP`: encapsulated header에 도달하면 parsing을 중지합니다. Routing infrastructure가 사용합니다.
Reference implementation
121-138Reference implementation은 `tools/testing/selftests/bpf/progs/bpf_flow.c`에 있고 loader는 `tools/testing/selftests/bpf/flow_dissector_load.[hc]`에 있습니다. `bpftool`로도 BPF flow dissector program을 load할 수 있습니다.
Reference implementation의 구성은 다음과 같습니다.
- `jmp_table`: 지원하는 각 L3 protocol의 sub-program을 담는 map
- `_dissect` routine: entry point입니다. Input `n_proto`를 parse하고 적절한 L3 handler로 `bpf_tail_call`을 수행합니다.
이 시점의 BPF는 loop 또는 뒤로 jump하는 동작을 지원하지 않으므로 여러 encapsulation level과 IPv6 option을 처리하는 데 `jmp_table`을 사용합니다.
Current limitations와 namespace policy
139-147BPF flow dissector는 in-kernel C 구현이 export할 수 있는 metadata 전부를 지원하지 않습니다. 대표적인 예가 single VLAN 802.1Q와 double VLAN 802.1AD tag입니다. 현재 BPF context에서 export할 수 있는 정보 집합은 `struct bpf_flow_keys`를 참고하십시오.
BPF flow dissector가 root network namespace에 attach되어 machine-wide policy가 되면 child network namespace의 user는 이를 override할 수 없습니다.
요약과 해설
prog_flow_dissector.rst:1-147Flow dissector BPF program은 제한된 `__sk_buff` field와 `struct bpf_flow_keys`를 사용해 packet metadata를 parse합니다. 성공 시 `BPF_OK`, parsing error 시 `BPF_DROP`을 반환합니다.
Program은 VLAN header가 아직 남아 TCI에서 시작하는 경우와 VLAN 처리가 끝나 L3 header에서 시작하는 경우를 모두 처리해야 합니다. Double VLAN의 802.1AD도 고려해야 합니다.
Reference 구현은 protocol별 sub-program을 `jmp_table`에 두고 `bpf_tail_call`로 dispatch합니다. Root network namespace에 attach한 program은 child namespace가 덮어쓸 수 없습니다.