요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
.. _networking-filter:
=======================================================
Linux Socket Filtering aka Berkeley Packet Filter (BPF)
=======================================================
Notice
------
This file used to document the eBPF format and mechanisms even when not
related to socket filtering. The ../bpf/index.rst has more details
on eBPF.
Introduction
------------
Linux Socket Filtering (LSF) is derived from the Berkeley Packet Filter.
Though there are some distinct differences between the BSD and Linux
Kernel filtering, but when we speak of BPF or LSF in Linux context, we
mean the very same mechanism of filtering in the Linux kernel.
BPF allows a user-space program to attach a filter onto any socket and
allow or disallow certain types of data to come through the socket. LSF
follows exactly the same filter code structure as BSD's BPF, so referring
to the BSD bpf.4 manpage is very helpful in creating filters.
On Linux, BPF is much simpler than on BSD. One does not have to worry
about devices or anything like that. You simply create your filter code,
send it to the kernel via the SO_ATTACH_FILTER option and if your filter
code passes the kernel check on it, you then immediately begin filtering
data on that socket.
You can also detach filters from your socket via the SO_DETACH_FILTER
option. This will probably not be used much since when you close a socket
that has a filter on it the filter is automagically removed. The other
less common case may be adding a different filter on the same socket where
you had another filter that is still running: the kernel takes care of
removing the old one and placing your new one in its place, assuming your
filter has passed the checks, otherwise if it fails the old filter will
remain on that socket.
SO_LOCK_FILTER option allows to lock the filter attached to a socket. Once
set, a filter cannot be removed or changed. This allows one process to
setup a socket, attach a filter, lock it then drop privileges and be
assured that the filter will be kept until the socket is closed.
The biggest user of this construct might be libpcap. Issuing a high-level
filter command like `tcpdump -i em1 port 22` passes through the libpcap
internal compiler that generates a structure that can eventually be loaded
via SO_ATTACH_FILTER to the kernel. `tcpdump -i em1 port 22 -ddd`
displays what is being placed into this structure.
Although we were only speaking about sockets here, BPF in Linux is used
in many more places. There's xt_bpf for netfilter, cls_bpf in the kernel
qdisc layer, SECCOMP-BPF (SECure COMPuting [1]_), and lots of other places
such as team driver, PTP code, etc where BPF is being used.
.. [1] Documentation/userspace-api/seccomp_filter.rst
Original BPF paper:
Steven McCanne and Van Jacobson. 1993. The BSD packet filter: a new
architecture for user-level packet capture. In Proceedings of the
USENIX Winter 1993 Conference Proceedings on USENIX Winter 1993
Conference Proceedings (USENIX'93). USENIX Association, Berkeley,
CA, USA, 2-2. [http://www.tcpdump.org/papers/bpf-usenix93.pdf]
Structure
---------
User space applications include <linux/filter.h> which contains the
following relevant structures::
struct sock_filter { /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
Such a structure is assembled as an array of 4-tuples, that contains
a code, jt, jf and k value. jt and jf are jump offsets and k a generic
value to be used for a provided code::
struct sock_fprog { /* Required for SO_ATTACH_FILTER. */
unsigned short len; /* Number of filter blocks */
struct sock_filter __user *filter;
};
For socket filtering, a pointer to this structure (as shown in
follow-up example) is being passed to the kernel through setsockopt(2).
Example
-------
::
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
/* ... */
/* From the example above: tcpdump -i em1 port 22 -dd */
struct sock_filter code[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 8, 0x000086dd },
{ 0x30, 0, 0, 0x00000014 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 17, 0x00000011 },
{ 0x28, 0, 0, 0x00000036 },
{ 0x15, 14, 0, 0x00000016 },
{ 0x28, 0, 0, 0x00000038 },
{ 0x15, 12, 13, 0x00000016 },
{ 0x15, 0, 12, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 8, 0x00000011 },
{ 0x28, 0, 0, 0x00000014 },
{ 0x45, 6, 0, 0x00001fff },
{ 0xb1, 0, 0, 0x0000000e },
{ 0x48, 0, 0, 0x0000000e },
{ 0x15, 2, 0, 0x00000016 },
{ 0x48, 0, 0, 0x00000010 },
{ 0x15, 0, 1, 0x00000016 },
{ 0x06, 0, 0, 0x0000ffff },
{ 0x06, 0, 0, 0x00000000 },
};
struct sock_fprog bpf = {
.len = ARRAY_SIZE(code),
.filter = code,
};
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0)
/* ... bail out ... */
ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
if (ret < 0)
/* ... bail out ... */
/* ... */
close(sock);
The above example code attaches a socket filter for a PF_PACKET socket
in order to let all IPv4/IPv6 packets with port 22 pass. The rest will
be dropped for this socket.
The setsockopt(2) call to SO_DETACH_FILTER doesn't need any arguments
and SO_LOCK_FILTER for preventing the filter to be detached, takes an
integer value with 0 or 1.
Note that socket filters are not restricted to PF_PACKET sockets only,
but can also be used on other socket families.
Summary of system calls:
* setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &val, sizeof(val));
* setsockopt(sockfd, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val));
* setsockopt(sockfd, SOL_SOCKET, SO_LOCK_FILTER, &val, sizeof(val));
Normally, most use cases for socket filtering on packet sockets will be
covered by libpcap in high-level syntax, so as an application developer
you should stick to that. libpcap wraps its own layer around all that.
Unless i) using/linking to libpcap is not an option, ii) the required BPF
filters use Linux extensions that are not supported by libpcap's compiler,
iii) a filter might be more complex and not cleanly implementable with
libpcap's compiler, or iv) particular filter codes should be optimized
differently than libpcap's internal compiler does; then in such cases
writing such a filter "by hand" can be of an alternative. For example,
xt_bpf and cls_bpf users might have requirements that could result in
more complex filter code, or one that cannot be expressed with libpcap
(e.g. different return codes for various code paths). Moreover, BPF JIT
implementors may wish to manually write test cases and thus need low-level
access to BPF code as well.
BPF engine and instruction set
------------------------------
Under tools/bpf/ there's a small helper tool called bpf_asm which can
be used to write low-level filters for example scenarios mentioned in the
previous section. Asm-like syntax mentioned here has been implemented in
bpf_asm and will be used for further explanations (instead of dealing with
less readable opcodes directly, principles are the same). The syntax is
closely modelled after Steven McCanne's and Van Jacobson's BPF paper.
The BPF architecture consists of the following basic elements:
======= ====================================================
Element Description
======= ====================================================
A 32 bit wide accumulator
X 32 bit wide X register
M[] 16 x 32 bit wide misc registers aka "scratch memory
store", addressable from 0 to 15
======= ====================================================
A program, that is translated by bpf_asm into "opcodes" is an array that
consists of the following elements (as already mentioned)::
op:16, jt:8, jf:8, k:32
The element op is a 16 bit wide opcode that has a particular instruction
encoded. jt and jf are two 8 bit wide jump targets, one for condition
"jump if true", the other one "jump if false". Eventually, element k
contains a miscellaneous argument that can be interpreted in different
ways depending on the given instruction in op.
The instruction set consists of load, store, branch, alu, miscellaneous
and return instructions that are also represented in bpf_asm syntax. This
table lists all bpf_asm instructions available resp. what their underlying
opcodes as defined in linux/filter.h stand for:
=========== =================== =====================
Instruction Addressing mode Description
=========== =================== =====================
ld 1, 2, 3, 4, 12 Load word into A
ldi 4 Load word into A
ldh 1, 2 Load half-word into A
ldb 1, 2 Load byte into A
ldx 3, 4, 5, 12 Load word into X
ldxi 4 Load word into X
ldxb 5 Load byte into X
st 3 Store A into M[]
stx 3 Store X into M[]
jmp 6 Jump to label
ja 6 Jump to label
jeq 7, 8, 9, 10 Jump on A == <x>
jneq 9, 10 Jump on A != <x>
jne 9, 10 Jump on A != <x>
jlt 9, 10 Jump on A < <x>
jle 9, 10 Jump on A <= <x>
jgt 7, 8, 9, 10 Jump on A > <x>
jge 7, 8, 9, 10 Jump on A >= <x>
jset 7, 8, 9, 10 Jump on A & <x>
add 0, 4 A + <x>
sub 0, 4 A - <x>
mul 0, 4 A * <x>
div 0, 4 A / <x>
mod 0, 4 A % <x>
neg !A
and 0, 4 A & <x>
or 0, 4 A | <x>
xor 0, 4 A ^ <x>
lsh 0, 4 A << <x>
rsh 0, 4 A >> <x>
tax Copy A into X
txa Copy X into A
ret 4, 11 Return
=========== =================== =====================
The next table shows addressing formats from the 2nd column:
=============== =================== ===============================================
Addressing mode Syntax Description
=============== =================== ===============================================
0 x/%x Register X
1 [k] BHW at byte offset k in the packet
2 [x + k] BHW at the offset X + k in the packet
3 M[k] Word at offset k in M[]
4 #k Literal value stored in k
5 4*([k]&0xf) Lower nibble * 4 at byte offset k in the packet
6 L Jump label L
7 #k,Lt,Lf Jump to Lt if true, otherwise jump to Lf
8 x/%x,Lt,Lf Jump to Lt if true, otherwise jump to Lf
9 #k,Lt Jump to Lt if predicate is true
10 x/%x,Lt Jump to Lt if predicate is true
11 a/%a Accumulator A
12 extension BPF extension
=============== =================== ===============================================
The Linux kernel also has a couple of BPF extensions that are used along
with the class of load instructions by "overloading" the k argument with
a negative offset + a particular extension offset. The result of such BPF
extensions are loaded into A.
Possible BPF extensions are shown in the following table:
=================================== =================================================
Extension Description
=================================== =================================================
len skb->len
proto skb->protocol
type skb->pkt_type
poff Payload start offset
ifidx skb->dev->ifindex
nla Netlink attribute of type X with offset A
nlan Nested Netlink attribute of type X with offset A
mark skb->mark
queue skb->queue_mapping
hatype skb->dev->type
rxhash skb->hash
cpu raw_smp_processor_id()
vlan_tci skb_vlan_tag_get(skb)
vlan_avail skb_vlan_tag_present(skb)
vlan_tpid skb->vlan_proto
rand get_random_u32()
=================================== =================================================
These extensions can also be prefixed with '#'.
Examples for low-level BPF:
**ARP packets**::
ldh [12]
jne #0x806, drop
ret #-1
drop: ret #0
**IPv4 TCP packets**::
ldh [12]
jne #0x800, drop
ldb [23]
jneq #6, drop
ret #-1
drop: ret #0
**icmp random packet sampling, 1 in 4**::
ldh [12]
jne #0x800, drop
ldb [23]
jneq #1, drop
# get a random uint32 number
ld rand
mod #4
jneq #1, drop
ret #-1
drop: ret #0
**SECCOMP filter example**::
ld [4] /* offsetof(struct seccomp_data, arch) */
jne #0xc000003e, bad /* AUDIT_ARCH_X86_64 */
ld [0] /* offsetof(struct seccomp_data, nr) */
jeq #15, good /* __NR_rt_sigreturn */
jeq #231, good /* __NR_exit_group */
jeq #60, good /* __NR_exit */
jeq #0, good /* __NR_read */
jeq #1, good /* __NR_write */
jeq #5, good /* __NR_fstat */
jeq #9, good /* __NR_mmap */
jeq #14, good /* __NR_rt_sigprocmask */
jeq #13, good /* __NR_rt_sigaction */
jeq #35, good /* __NR_nanosleep */
bad: ret #0 /* SECCOMP_RET_KILL_THREAD */
good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
Examples for low-level BPF extension:
**Packet for interface index 13**::
ld ifidx
jneq #13, drop
ret #-1
drop: ret #0
**(Accelerated) VLAN w/ id 10**::
ld vlan_tci
jneq #10, drop
ret #-1
drop: ret #0
The above example code can be placed into a file (here called "foo"), and
then be passed to the bpf_asm tool for generating opcodes, output that xt_bpf
and cls_bpf understands and can directly be loaded with. Example with above
ARP code::
$ ./bpf_asm foo
4,40 0 0 12,21 0 1 2054,6 0 0 4294967295,6 0 0 0,
In copy and paste C-like output::
$ ./bpf_asm -c foo
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 1, 0x00000806 },
{ 0x06, 0, 0, 0xffffffff },
{ 0x06, 0, 0, 0000000000 },
In particular, as usage with xt_bpf or cls_bpf can result in more complex BPF
filters that might not be obvious at first, it's good to test filters before
attaching to a live system. For that purpose, there's a small tool called
bpf_dbg under tools/bpf/ in the kernel source directory. This debugger allows
for testing BPF filters against given pcap files, single stepping through the
BPF code on the pcap's packets and to do BPF machine register dumps.
Starting bpf_dbg is trivial and just requires issuing::
# ./bpf_dbg
In case input and output do not equal stdin/stdout, bpf_dbg takes an
alternative stdin source as a first argument, and an alternative stdout
sink as a second one, e.g. `./bpf_dbg test_in.txt test_out.txt`.
Other than that, a particular libreadline configuration can be set via
file "~/.bpf_dbg_init" and the command history is stored in the file
"~/.bpf_dbg_history".
Interaction in bpf_dbg happens through a shell that also has auto-completion
support (follow-up example commands starting with '>' denote bpf_dbg shell).
The usual workflow would be to ...
* load bpf 6,40 0 0 12,21 0 3 2048,48 0 0 23,21 0 1 1,6 0 0 65535,6 0 0 0
Loads a BPF filter from standard output of bpf_asm, or transformed via
e.g. ``tcpdump -iem1 -ddd port 22 | tr '\n' ','``. Note that for JIT
debugging (next section), this command creates a temporary socket and
loads the BPF code into the kernel. Thus, this will also be useful for
JIT developers.
* load pcap foo.pcap
Loads standard tcpdump pcap file.
* run [<n>]
bpf passes:1 fails:9
Runs through all packets from a pcap to account how many passes and fails
the filter will generate. A limit of packets to traverse can be given.
* disassemble::
l0: ldh [12]
l1: jeq #0x800, l2, l5
l2: ldb [23]
l3: jeq #0x1, l4, l5
l4: ret #0xffff
l5: ret #0
Prints out BPF code disassembly.
* dump::
/* { op, jt, jf, k }, */
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 3, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 1, 0x00000001 },
{ 0x06, 0, 0, 0x0000ffff },
{ 0x06, 0, 0, 0000000000 },
Prints out C-style BPF code dump.
* breakpoint 0::
breakpoint at: l0: ldh [12]
* breakpoint 1::
breakpoint at: l1: jeq #0x800, l2, l5
...
Sets breakpoints at particular BPF instructions. Issuing a `run` command
will walk through the pcap file continuing from the current packet and
break when a breakpoint is being hit (another `run` will continue from
the currently active breakpoint executing next instructions):
* run::
-- register dump --
pc: [0] <-- program counter
code: [40] jt[0] jf[0] k[12] <-- plain BPF code of current instruction
curr: l0: ldh [12] <-- disassembly of current instruction
A: [00000000][0] <-- content of A (hex, decimal)
X: [00000000][0] <-- content of X (hex, decimal)
M[0,15]: [00000000][0] <-- folded content of M (hex, decimal)
-- packet dump -- <-- Current packet from pcap (hex)
len: 42
0: 00 19 cb 55 55 a4 00 14 a4 43 78 69 08 06 00 01
16: 08 00 06 04 00 01 00 14 a4 43 78 69 0a 3b 01 26
32: 00 00 00 00 00 00 0a 3b 01 01
(breakpoint)
>
* breakpoint::
breakpoints: 0 1
Prints currently set breakpoints.
* step [-<n>, +<n>]
Performs single stepping through the BPF program from the current pc
offset. Thus, on each step invocation, above register dump is issued.
This can go forwards and backwards in time, a plain `step` will break
on the next BPF instruction, thus +1. (No `run` needs to be issued here.)
* select <n>
Selects a given packet from the pcap file to continue from. Thus, on
the next `run` or `step`, the BPF program is being evaluated against
the user pre-selected packet. Numbering starts just as in Wireshark
with index 1.
* quit
Exits bpf_dbg.
JIT compiler
------------
The Linux kernel has a built-in BPF JIT compiler for x86_64, SPARC,
PowerPC, ARM, ARM64, MIPS, RISC-V, s390, and ARC and can be enabled through
CONFIG_BPF_JIT. The JIT compiler is transparently invoked for each
attached filter from user space or for internal kernel users if it has
been previously enabled by root::
echo 1 > /proc/sys/net/core/bpf_jit_enable
For JIT developers, doing audits etc, each compile run can output the generated
opcode image into the kernel log via::
echo 2 > /proc/sys/net/core/bpf_jit_enable
Example output from dmesg::
[ 3389.935842] flen=6 proglen=70 pass=3 image=ffffffffa0069c8f
[ 3389.935847] JIT code: 00000000: 55 48 89 e5 48 83 ec 60 48 89 5d f8 44 8b 4f 68
[ 3389.935849] JIT code: 00000010: 44 2b 4f 6c 4c 8b 87 d8 00 00 00 be 0c 00 00 00
[ 3389.935850] JIT code: 00000020: e8 1d 94 ff e0 3d 00 08 00 00 75 16 be 17 00 00
[ 3389.935851] JIT code: 00000030: 00 e8 28 94 ff e0 83 f8 01 75 07 b8 ff ff 00 00
[ 3389.935852] JIT code: 00000040: eb 02 31 c0 c9 c3
When CONFIG_BPF_JIT_ALWAYS_ON is enabled, bpf_jit_enable is permanently set to 1 and
setting any other value than that will return in failure. This is even the case for
setting bpf_jit_enable to 2, since dumping the final JIT image into the kernel log
is discouraged and introspection through bpftool (under tools/bpf/bpftool/) is the
generally recommended approach instead.
In the kernel source tree under tools/bpf/, there's bpf_jit_disasm for
generating disassembly out of the kernel log's hexdump::
# ./bpf_jit_disasm
70 bytes emitted from JIT compiler (pass:3, flen:6)
ffffffffa0069c8f + <x>:
0: push %rbp
1: mov %rsp,%rbp
4: sub $0x60,%rsp
8: mov %rbx,-0x8(%rbp)
c: mov 0x68(%rdi),%r9d
10: sub 0x6c(%rdi),%r9d
14: mov 0xd8(%rdi),%r8
1b: mov $0xc,%esi
20: callq 0xffffffffe0ff9442
25: cmp $0x800,%eax
2a: jne 0x0000000000000042
2c: mov $0x17,%esi
31: callq 0xffffffffe0ff945e
36: cmp $0x1,%eax
39: jne 0x0000000000000042
3b: mov $0xffff,%eax
40: jmp 0x0000000000000044
42: xor %eax,%eax
44: leaveq
45: retq
Issuing option `-o` will "annotate" opcodes to resulting assembler
instructions, which can be very useful for JIT developers:
# ./bpf_jit_disasm -o
70 bytes emitted from JIT compiler (pass:3, flen:6)
ffffffffa0069c8f + <x>:
0: push %rbp
55
1: mov %rsp,%rbp
48 89 e5
4: sub $0x60,%rsp
48 83 ec 60
8: mov %rbx,-0x8(%rbp)
48 89 5d f8
c: mov 0x68(%rdi),%r9d
44 8b 4f 68
10: sub 0x6c(%rdi),%r9d
44 2b 4f 6c
14: mov 0xd8(%rdi),%r8
4c 8b 87 d8 00 00 00
1b: mov $0xc,%esi
be 0c 00 00 00
20: callq 0xffffffffe0ff9442
e8 1d 94 ff e0
25: cmp $0x800,%eax
3d 00 08 00 00
2a: jne 0x0000000000000042
75 16
2c: mov $0x17,%esi
be 17 00 00 00
31: callq 0xffffffffe0ff945e
e8 28 94 ff e0
36: cmp $0x1,%eax
83 f8 01
39: jne 0x0000000000000042
75 07
3b: mov $0xffff,%eax
b8 ff ff 00 00
40: jmp 0x0000000000000044
eb 02
42: xor %eax,%eax
31 c0
44: leaveq
c9
45: retq
c3
For BPF JIT developers, bpf_jit_disasm, bpf_asm and bpf_dbg provides a useful
toolchain for developing and testing the kernel's JIT compiler.
BPF kernel internals
--------------------
Internally, for the kernel interpreter, a different instruction set
format with similar underlying principles from BPF described in previous
paragraphs is being used. However, the instruction set format is modelled
closer to the underlying architecture to mimic native instruction sets, so
that a better performance can be achieved (more details later). This new
ISA is called eBPF. See the ../bpf/index.rst for details. (Note: eBPF which
originates from [e]xtended BPF is not the same as BPF extensions! While
eBPF is an ISA, BPF extensions date back to classic BPF's 'overloading'
of BPF_LD | BPF_{B,H,W} | BPF_ABS instruction.)
The new instruction set was originally designed with the possible goal in
mind to write programs in "restricted C" and compile into eBPF with a optional
GCC/LLVM backend, so that it can just-in-time map to modern 64-bit CPUs with
minimal performance overhead over two steps, that is, C -> eBPF -> native code.
Currently, the new format is being used for running user BPF programs, which
includes seccomp BPF, classic socket filters, cls_bpf traffic classifier,
team driver's classifier for its load-balancing mode, netfilter's xt_bpf
extension, PTP dissector/classifier, and much more. They are all internally
converted by the kernel into the new instruction set representation and run
in the eBPF interpreter. For in-kernel handlers, this all works transparently
by using bpf_prog_create() for setting up the filter, resp.
bpf_prog_destroy() for destroying it. The function
bpf_prog_run(filter, ctx) transparently invokes eBPF interpreter or JITed
code to run the filter. 'filter' is a pointer to struct bpf_prog that we
got from bpf_prog_create(), and 'ctx' the given context (e.g.
skb pointer). All constraints and restrictions from bpf_check_classic() apply
before a conversion to the new layout is being done behind the scenes!
Currently, the classic BPF format is being used for JITing on most
32-bit architectures, whereas x86-64, aarch64, s390x, powerpc64,
sparc64, arm32, riscv64, riscv32, loongarch64, arc perform JIT compilation
from eBPF instruction set.
Testing
-------
Next to the BPF toolchain, the kernel also ships a test module that contains
various test cases for classic and eBPF that can be executed against
the BPF interpreter and JIT compiler. It can be found in lib/test_bpf.c and
enabled via Kconfig::
CONFIG_TEST_BPF=m
After the module has been built and installed, the test suite can be executed
via insmod or modprobe against 'test_bpf' module. Results of the test cases
including timings in nsec can be found in the kernel log (dmesg).
Misc
----
Also trinity, the Linux syscall fuzzer, has built-in support for BPF and
SECCOMP-BPF kernel fuzzing.
Written by
----------
The document was written in the hope that it is found useful and in order
to give potential BPF hackers or security auditors a better overview of
the underlying architecture.
- Jay Schulist <[email protected]>
- Daniel Borkmann <[email protected]>
- Alexei Starovoitov <[email protected]>
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
문서 범위 안내
1-15이 문서는 Linux 소켓 필터링과 고전 BPF를 설명합니다. 과거에는 소켓 필터링과 직접 관련이 없는 eBPF 형식과 메커니즘도 함께 다뤘지만, 현재 eBPF의 자세한 내용은 `../bpf/index.rst`에 정리되어 있습니다.
.. SPDX-License-Identifier: GPL-2.0
.. _networking-filter:
=======================================================
Linux Socket Filtering aka Berkeley Packet Filter (BPF)
=======================================================
Notice
------
This file used to document the eBPF format and mechanisms even when not
related to socket filtering. The ../bpf/index.rst has more details
on eBPF.
소개
16-69Linux Socket Filtering(LSF)은 Berkeley Packet Filter에서 유래했습니다. BSD와 Linux 커널의 필터링에는 차이가 있지만 Linux 문맥에서 BPF 또는 LSF라고 하면 같은 커널 필터링 메커니즘을 뜻합니다. 사용자 공간 프로그램은 임의의 소켓에 필터를 붙여 특정 데이터가 소켓을 통과하도록 허용하거나 차단할 수 있으며, Linux의 필터 코드 구조는 BSD BPF와 같아서 BSD `bpf(4)` 매뉴얼도 필터 작성에 도움이 됩니다.
Linux에서는 장치를 따로 관리할 필요 없이 필터 코드를 만든 뒤 `SO_ATTACH_FILTER`로 커널에 전달합니다. 커널 검사를 통과하면 즉시 해당 소켓의 데이터를 필터링합니다. `SO_DETACH_FILTER`로 필터를 떼어낼 수 있고 소켓을 닫으면 자동으로 제거됩니다. 같은 소켓에 새 필터를 붙이면 검사를 통과한 경우에만 기존 필터를 교체하며, 실패하면 기존 필터가 유지됩니다.
`SO_LOCK_FILTER`는 붙어 있는 필터를 잠가 이후 제거하거나 변경하지 못하게 합니다. 권한 있는 프로세스가 소켓을 만들고 필터를 붙여 잠근 다음 권한을 내려도 소켓이 닫힐 때까지 필터가 유지됩니다. 대표 사용자는 libpcap입니다. 예를 들어 `tcpdump -i em1 port 22`는 libpcap 컴파일러를 거쳐 `SO_ATTACH_FILTER`로 적재할 구조를 만들고, `-ddd` 옵션은 실제 구조 내용을 표시합니다.
BPF는 소켓 외에도 netfilter의 `xt_bpf`, qdisc 계층의 `cls_bpf`, `SECCOMP-BPF`, team 드라이버와 PTP 코드 등 여러 곳에서 쓰입니다. 원전은 Steven McCanne과 Van Jacobson이 1993년 발표한 BSD packet filter 논문이며, seccomp 관련 참고 문서는 `Documentation/userspace-api/seccomp_filter.rst`입니다.
Introduction
------------
Linux Socket Filtering (LSF) is derived from the Berkeley Packet Filter.
Though there are some distinct differences between the BSD and Linux
Kernel filtering, but when we speak of BPF or LSF in Linux context, we
mean the very same mechanism of filtering in the Linux kernel.
BPF allows a user-space program to attach a filter onto any socket and
allow or disallow certain types of data to come through the socket. LSF
follows exactly the same filter code structure as BSD's BPF, so referring
to the BSD bpf.4 manpage is very helpful in creating filters.
On Linux, BPF is much simpler than on BSD. One does not have to worry
about devices or anything like that. You simply create your filter code,
send it to the kernel via the SO_ATTACH_FILTER option and if your filter
code passes the kernel check on it, you then immediately begin filtering
data on that socket.
You can also detach filters from your socket via the SO_DETACH_FILTER
option. This will probably not be used much since when you close a socket
that has a filter on it the filter is automagically removed. The other
less common case may be adding a different filter on the same socket where
you had another filter that is still running: the kernel takes care of
removing the old one and placing your new one in its place, assuming your
filter has passed the checks, otherwise if it fails the old filter will
remain on that socket.
SO_LOCK_FILTER option allows to lock the filter attached to a socket. Once
set, a filter cannot be removed or changed. This allows one process to
setup a socket, attach a filter, lock it then drop privileges and be
assured that the filter will be kept until the socket is closed.
The biggest user of this construct might be libpcap. Issuing a high-level
filter command like `tcpdump -i em1 port 22` passes through the libpcap
internal compiler that generates a structure that can eventually be loaded
via SO_ATTACH_FILTER to the kernel. `tcpdump -i em1 port 22 -ddd`
displays what is being placed into this structure.
Although we were only speaking about sockets here, BPF in Linux is used
in many more places. There's xt_bpf for netfilter, cls_bpf in the kernel
qdisc layer, SECCOMP-BPF (SECure COMPuting [1]_), and lots of other places
such as team driver, PTP code, etc where BPF is being used.
.. [1] Documentation/userspace-api/seccomp_filter.rst
Original BPF paper:
Steven McCanne and Van Jacobson. 1993. The BSD packet filter: a new
architecture for user-level packet capture. In Proceedings of the
USENIX Winter 1993 Conference Proceedings on USENIX Winter 1993
Conference Proceedings (USENIX'93). USENIX Association, Berkeley,
CA, USA, 2-2. [http://www.tcpdump.org/papers/bpf-usenix93.pdf]
필터 구조체
70-94사용자 공간 프로그램은 `<linux/filter.h>`의 `struct sock_filter`를 사용합니다. 각 필터 블록은 실제 명령 코드인 16비트 `code`, 참과 거짓 분기 오프셋인 8비트 `jt`와 `jf`, 명령별 범용 인수인 32비트 `k`의 4튜플입니다.
`SO_ATTACH_FILTER`에 전달하는 `struct sock_fprog`는 필터 블록 수 `len`과 `struct sock_filter __user *filter`를 담습니다. 소켓 필터링에서는 이 구조체를 가리키는 포인터를 `setsockopt(2)`로 커널에 넘깁니다.
한 명령을 이루는 네 필드를 구조화했습니다.
Structure
---------
User space applications include <linux/filter.h> which contains the
following relevant structures::
struct sock_filter { /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
Such a structure is assembled as an array of 4-tuples, that contains
a code, jt, jf and k value. jt and jf are jump offsets and k a generic
value to be used for a provided code::
struct sock_fprog { /* Required for SO_ATTACH_FILTER. */
unsigned short len; /* Number of filter blocks */
struct sock_filter __user *filter;
};
For socket filtering, a pointer to this structure (as shown in
follow-up example) is being passed to the kernel through setsockopt(2).
소켓 필터 예제
95-182예제는 `PF_PACKET`, `SOCK_RAW`, `ETH_P_ALL` 소켓을 만들고 `tcpdump -i em1 port 22 -dd`가 만든 `struct sock_filter` 배열을 `sock_fprog`에 넣어 `SO_ATTACH_FILTER`로 부착합니다. 이 필터는 포트 22를 사용하는 IPv4와 IPv6 패킷만 통과시키고 나머지 패킷은 해당 소켓에서 버립니다.
`SO_DETACH_FILTER`를 사용하는 `setsockopt(2)` 호출에는 별도 인수가 필요하지 않으며, 필터 분리를 막는 `SO_LOCK_FILTER`에는 0 또는 1의 정수를 전달합니다. 소켓 필터는 `PF_PACKET`에만 제한되지 않고 다른 소켓 주소군에도 사용할 수 있습니다.
패킷 소켓의 일반적인 필터링은 고수준 문법을 제공하는 libpcap으로 처리하는 편이 좋습니다. 직접 작성은 libpcap을 연결할 수 없거나, Linux 전용 확장이 필요하거나, libpcap 컴파일러로 깔끔하게 표현하기 어려운 복잡한 필터가 있거나, 생성 코드를 다르게 최적화해야 할 때 유용합니다. `xt_bpf`, `cls_bpf`, 여러 반환 코드를 가진 경로, BPF JIT 시험 사례가 대표적입니다.
필터의 생명 주기를 담당하는 소켓 옵션입니다.
Example
-------
::
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
/* ... */
/* From the example above: tcpdump -i em1 port 22 -dd */
struct sock_filter code[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 8, 0x000086dd },
{ 0x30, 0, 0, 0x00000014 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 17, 0x00000011 },
{ 0x28, 0, 0, 0x00000036 },
{ 0x15, 14, 0, 0x00000016 },
{ 0x28, 0, 0, 0x00000038 },
{ 0x15, 12, 13, 0x00000016 },
{ 0x15, 0, 12, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 8, 0x00000011 },
{ 0x28, 0, 0, 0x00000014 },
{ 0x45, 6, 0, 0x00001fff },
{ 0xb1, 0, 0, 0x0000000e },
{ 0x48, 0, 0, 0x0000000e },
{ 0x15, 2, 0, 0x00000016 },
{ 0x48, 0, 0, 0x00000010 },
{ 0x15, 0, 1, 0x00000016 },
{ 0x06, 0, 0, 0x0000ffff },
{ 0x06, 0, 0, 0x00000000 },
};
struct sock_fprog bpf = {
.len = ARRAY_SIZE(code),
.filter = code,
};
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0)
/* ... bail out ... */
ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
if (ret < 0)
/* ... bail out ... */
/* ... */
close(sock);
The above example code attaches a socket filter for a PF_PACKET socket
in order to let all IPv4/IPv6 packets with port 22 pass. The rest will
be dropped for this socket.
The setsockopt(2) call to SO_DETACH_FILTER doesn't need any arguments
and SO_LOCK_FILTER for preventing the filter to be detached, takes an
integer value with 0 or 1.
Note that socket filters are not restricted to PF_PACKET sockets only,
but can also be used on other socket families.
Summary of system calls:
* setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &val, sizeof(val));
* setsockopt(sockfd, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val));
* setsockopt(sockfd, SOL_SOCKET, SO_LOCK_FILTER, &val, sizeof(val));
Normally, most use cases for socket filtering on packet sockets will be
covered by libpcap in high-level syntax, so as an application developer
you should stick to that. libpcap wraps its own layer around all that.
Unless i) using/linking to libpcap is not an option, ii) the required BPF
filters use Linux extensions that are not supported by libpcap's compiler,
iii) a filter might be more complex and not cleanly implementable with
libpcap's compiler, or iv) particular filter codes should be optimized
differently than libpcap's internal compiler does; then in such cases
writing such a filter "by hand" can be of an alternative. For example,
xt_bpf and cls_bpf users might have requirements that could result in
more complex filter code, or one that cannot be expressed with libpcap
(e.g. different return codes for various code paths). Moreover, BPF JIT
implementors may wish to manually write test cases and thus need low-level
access to BPF code as well.
BPF 엔진과 명령 형식
183-219커널 소스의 `tools/bpf/`에는 저수준 필터를 작성하는 `bpf_asm` 도구가 있습니다. 이 문서는 읽기 어려운 원시 opcode 대신 Steven McCanne과 Van Jacobson의 논문을 본뜬 어셈블리형 문법을 사용합니다. 원리는 원시 opcode와 같습니다.
고전 BPF 아키텍처는 32비트 누산기 `A`, 32비트 인덱스 레지스터 `X`, 0부터 15까지 접근할 수 있는 16개의 32비트 임시 레지스터 `M[]`로 구성됩니다. `bpf_asm`이 opcode로 바꾸는 각 명령은 `op:16, jt:8, jf:8, k:32` 형식입니다. `op`는 명령, `jt`와 `jf`는 참/거짓 점프 대상, `k`는 명령에 따라 다르게 해석되는 인수입니다.
명령 집합에는 적재, 저장, 분기, 산술/논리, 기타 변환, 반환 명령이 있습니다. 이어지는 표의 `bpf_asm` 문법은 `<linux/filter.h>`에 정의된 실제 opcode와 대응합니다.
BPF engine and instruction set
------------------------------
Under tools/bpf/ there's a small helper tool called bpf_asm which can
be used to write low-level filters for example scenarios mentioned in the
previous section. Asm-like syntax mentioned here has been implemented in
bpf_asm and will be used for further explanations (instead of dealing with
less readable opcodes directly, principles are the same). The syntax is
closely modelled after Steven McCanne's and Van Jacobson's BPF paper.
The BPF architecture consists of the following basic elements:
======= ====================================================
Element Description
======= ====================================================
A 32 bit wide accumulator
X 32 bit wide X register
M[] 16 x 32 bit wide misc registers aka "scratch memory
store", addressable from 0 to 15
======= ====================================================
A program, that is translated by bpf_asm into "opcodes" is an array that
consists of the following elements (as already mentioned)::
op:16, jt:8, jf:8, k:32
The element op is a 16 bit wide opcode that has a particular instruction
encoded. jt and jf are two 8 bit wide jump targets, one for condition
"jump if true", the other one "jump if false". Eventually, element k
contains a miscellaneous argument that can be interpreted in different
ways depending on the given instruction in op.
The instruction set consists of load, store, branch, alu, miscellaneous
and return instructions that are also represented in bpf_asm syntax. This
table lists all bpf_asm instructions available resp. what their underlying
opcodes as defined in linux/filter.h stand for:
명령, 주소 지정, 커널 확장
220-309`ld`, `ldh`, `ldb`는 패킷이나 메모리에서 word, half-word, byte를 `A`로 읽고 `ldx`, `ldxb`는 `X`로 읽습니다. `st`와 `stx`는 `A`와 `X`를 `M[]`에 저장합니다. `jmp`/`ja`는 무조건 분기하고 `jeq`, `jneq`/`jne`, `jlt`, `jle`, `jgt`, `jge`, `jset`은 비교 또는 비트 검사의 결과에 따라 분기합니다.
산술/논리 명령은 `add`, `sub`, `mul`, `div`, `mod`, `neg`, `and`, `or`, `xor`, `lsh`, `rsh`입니다. `tax`는 `A`를 `X`로, `txa`는 `X`를 `A`로 복사하고 `ret`은 필터 결과를 반환합니다. 주소 지정은 `X`, 패킷의 고정 오프셋 `[k]`, 인덱스 오프셋 `[x + k]`, 임시 메모리 `M[k]`, 리터럴 `#k`, IPv4 헤더 길이형 `4*([k]&0xf)`, 레이블과 조건 분기 대상, `A`, BPF 확장을 지원합니다.
Linux 커널 확장은 적재 명령의 `k`를 음수 오프셋과 확장 오프셋 조합으로 오버로드하며 결과를 `A`에 넣습니다. `len`, `proto`, `type`, `poff`, `ifidx`, `nla`, `nlan`, `mark`, `queue`, `hatype`, `rxhash`, `cpu`, `vlan_tci`, `vlan_avail`, `vlan_tpid`, `rand`가 각각 `skb` 길이와 프로토콜, 패킷 유형, payload 시작점, 인터페이스, netlink 속성, mark/queue, 장치 유형, 해시, CPU, VLAN 정보, 난수에 접근합니다. 확장 이름 앞에는 `#`도 붙일 수 있습니다.
명령 표를 실행 관점에서 묶었습니다.
=========== =================== =====================
Instruction Addressing mode Description
=========== =================== =====================
ld 1, 2, 3, 4, 12 Load word into A
ldi 4 Load word into A
ldh 1, 2 Load half-word into A
ldb 1, 2 Load byte into A
ldx 3, 4, 5, 12 Load word into X
ldxi 4 Load word into X
ldxb 5 Load byte into X
st 3 Store A into M[]
stx 3 Store X into M[]
jmp 6 Jump to label
ja 6 Jump to label
jeq 7, 8, 9, 10 Jump on A == <x>
jneq 9, 10 Jump on A != <x>
jne 9, 10 Jump on A != <x>
jlt 9, 10 Jump on A < <x>
jle 9, 10 Jump on A <= <x>
jgt 7, 8, 9, 10 Jump on A > <x>
jge 7, 8, 9, 10 Jump on A >= <x>
jset 7, 8, 9, 10 Jump on A & <x>
add 0, 4 A + <x>
sub 0, 4 A - <x>
mul 0, 4 A * <x>
div 0, 4 A / <x>
mod 0, 4 A % <x>
neg !A
and 0, 4 A & <x>
or 0, 4 A | <x>
xor 0, 4 A ^ <x>
lsh 0, 4 A << <x>
rsh 0, 4 A >> <x>
tax Copy A into X
txa Copy X into A
ret 4, 11 Return
=========== =================== =====================
The next table shows addressing formats from the 2nd column:
=============== =================== ===============================================
Addressing mode Syntax Description
=============== =================== ===============================================
0 x/%x Register X
1 [k] BHW at byte offset k in the packet
2 [x + k] BHW at the offset X + k in the packet
3 M[k] Word at offset k in M[]
4 #k Literal value stored in k
5 4*([k]&0xf) Lower nibble * 4 at byte offset k in the packet
6 L Jump label L
7 #k,Lt,Lf Jump to Lt if true, otherwise jump to Lf
8 x/%x,Lt,Lf Jump to Lt if true, otherwise jump to Lf
9 #k,Lt Jump to Lt if predicate is true
10 x/%x,Lt Jump to Lt if predicate is true
11 a/%a Accumulator A
12 extension BPF extension
=============== =================== ===============================================
The Linux kernel also has a couple of BPF extensions that are used along
with the class of load instructions by "overloading" the k argument with
a negative offset + a particular extension offset. The result of such BPF
extensions are loaded into A.
Possible BPF extensions are shown in the following table:
=================================== =================================================
Extension Description
=================================== =================================================
len skb->len
proto skb->protocol
type skb->pkt_type
poff Payload start offset
ifidx skb->dev->ifindex
nla Netlink attribute of type X with offset A
nlan Nested Netlink attribute of type X with offset A
mark skb->mark
queue skb->queue_mapping
hatype skb->dev->type
rxhash skb->hash
cpu raw_smp_processor_id()
vlan_tci skb_vlan_tag_get(skb)
vlan_avail skb_vlan_tag_present(skb)
vlan_tpid skb->vlan_proto
rand get_random_u32()
=================================== =================================================
저수준 필터 예제
310-376ARP 예제는 이더넷 유형 필드 `[12]`를 half-word로 읽어 `0x806`이 아니면 버리고, 맞으면 `-1`을 반환해 통과시킵니다. IPv4 TCP 예제는 EtherType `0x800`과 IP 프로토콜 번호 6을 차례로 확인합니다. ICMP 무작위 표본 예제는 IPv4와 ICMP를 확인한 뒤 `rand`를 4로 나눈 나머지가 1인 패킷만 통과시켜 약 4개 중 1개를 선택합니다.
SECCOMP 예제는 `struct seccomp_data`에서 아키텍처와 시스템 호출 번호를 읽습니다. `AUDIT_ARCH_X86_64`가 아니면 스레드를 종료하고, `rt_sigreturn`, `exit_group`, `exit`, `read`, `write`, `fstat`, `mmap`, `rt_sigprocmask`, `rt_sigaction`, `nanosleep`만 `SECCOMP_RET_ALLOW`로 허용합니다.
확장 예제는 `ifidx`가 13인 패킷과 가속 VLAN 태그 ID가 10인 패킷을 각각 선택합니다. 모든 어셈블리와 정확한 opcode, 상수, 레이블은 아래 원문 블록에 그대로 보존되어 있습니다.
These extensions can also be prefixed with '#'.
Examples for low-level BPF:
**ARP packets**::
ldh [12]
jne #0x806, drop
ret #-1
drop: ret #0
**IPv4 TCP packets**::
ldh [12]
jne #0x800, drop
ldb [23]
jneq #6, drop
ret #-1
drop: ret #0
**icmp random packet sampling, 1 in 4**::
ldh [12]
jne #0x800, drop
ldb [23]
jneq #1, drop
# get a random uint32 number
ld rand
mod #4
jneq #1, drop
ret #-1
drop: ret #0
**SECCOMP filter example**::
ld [4] /* offsetof(struct seccomp_data, arch) */
jne #0xc000003e, bad /* AUDIT_ARCH_X86_64 */
ld [0] /* offsetof(struct seccomp_data, nr) */
jeq #15, good /* __NR_rt_sigreturn */
jeq #231, good /* __NR_exit_group */
jeq #60, good /* __NR_exit */
jeq #0, good /* __NR_read */
jeq #1, good /* __NR_write */
jeq #5, good /* __NR_fstat */
jeq #9, good /* __NR_mmap */
jeq #14, good /* __NR_rt_sigprocmask */
jeq #13, good /* __NR_rt_sigaction */
jeq #35, good /* __NR_nanosleep */
bad: ret #0 /* SECCOMP_RET_KILL_THREAD */
good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
Examples for low-level BPF extension:
**Packet for interface index 13**::
ld ifidx
jneq #13, drop
ret #-1
drop: ret #0
**(Accelerated) VLAN w/ id 10**::
ld vlan_tci
jneq #10, drop
ret #-1
drop: ret #0
bpf_asm과 bpf_dbg 시작
377-421저수준 코드는 파일에 저장한 뒤 `bpf_asm`에 전달할 수 있습니다. 기본 출력은 `xt_bpf`와 `cls_bpf`가 바로 읽는 쉼표 구분 opcode이고, `-c`는 복사해 C 코드에 넣을 수 있는 `sock_filter` 초기화 형식을 출력합니다.
복잡한 필터는 운영 시스템에 붙이기 전에 `tools/bpf/bpf_dbg`로 시험하는 편이 좋습니다. 이 도구는 pcap 패킷을 대상으로 필터를 실행하고, 단일 단계로 명령을 진행하며 BPF 머신 레지스터를 덤프합니다. 인수 없이 실행하면 표준 입출력을 쓰고, 첫째와 둘째 인수로 대체 입력과 출력을 지정할 수 있습니다. readline 설정은 `~/.bpf_dbg_init`, 명령 기록은 `~/.bpf_dbg_history`에 저장됩니다.
`load bpf`는 `bpf_asm` 또는 `tcpdump -ddd`를 변환한 명령열을 읽습니다. JIT 디버깅 때는 임시 소켓을 만들고 코드를 커널에 적재하므로 JIT 개발자에게도 유용합니다. `load pcap`은 표준 tcpdump pcap 파일을 엽니다.
The above example code can be placed into a file (here called "foo"), and
then be passed to the bpf_asm tool for generating opcodes, output that xt_bpf
and cls_bpf understands and can directly be loaded with. Example with above
ARP code::
$ ./bpf_asm foo
4,40 0 0 12,21 0 1 2054,6 0 0 4294967295,6 0 0 0,
In copy and paste C-like output::
$ ./bpf_asm -c foo
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 1, 0x00000806 },
{ 0x06, 0, 0, 0xffffffff },
{ 0x06, 0, 0, 0000000000 },
In particular, as usage with xt_bpf or cls_bpf can result in more complex BPF
filters that might not be obvious at first, it's good to test filters before
attaching to a live system. For that purpose, there's a small tool called
bpf_dbg under tools/bpf/ in the kernel source directory. This debugger allows
for testing BPF filters against given pcap files, single stepping through the
BPF code on the pcap's packets and to do BPF machine register dumps.
Starting bpf_dbg is trivial and just requires issuing::
# ./bpf_dbg
In case input and output do not equal stdin/stdout, bpf_dbg takes an
alternative stdin source as a first argument, and an alternative stdout
sink as a second one, e.g. `./bpf_dbg test_in.txt test_out.txt`.
Other than that, a particular libreadline configuration can be set via
file "~/.bpf_dbg_init" and the command history is stored in the file
"~/.bpf_dbg_history".
Interaction in bpf_dbg happens through a shell that also has auto-completion
support (follow-up example commands starting with '>' denote bpf_dbg shell).
The usual workflow would be to ...
* load bpf 6,40 0 0 12,21 0 3 2048,48 0 0 23,21 0 1 1,6 0 0 65535,6 0 0 0
Loads a BPF filter from standard output of bpf_asm, or transformed via
e.g. ``tcpdump -iem1 -ddd port 22 | tr '\n' ','``. Note that for JIT
debugging (next section), this command creates a temporary socket and
loads the BPF code into the kernel. Thus, this will also be useful for
JIT developers.
bpf_dbg 명령
422-510`run [n]`은 pcap의 전체 또는 지정한 수의 패킷에 필터를 실행해 통과와 실패 수를 집계합니다. `disassemble`은 레이블이 붙은 BPF 어셈블리를, `dump`는 C 형식의 `{ op, jt, jf, k }` 배열을 출력합니다.
`breakpoint n`은 지정한 BPF 명령에 중단점을 설정합니다. 이후 `run`은 현재 패킷부터 실행하다 중단점에서 멈추며, 다시 실행하면 다음 명령부터 계속합니다. 멈출 때는 프로그램 카운터, 현재 원시 명령, 디스어셈블 결과, `A`, `X`, `M[0..15]`, 현재 pcap 패킷의 16진 덤프가 표시됩니다. 인수 없는 `breakpoint`는 설정된 중단점 목록을 보여 줍니다.
`step [-n, +n]`은 현재 프로그램 카운터를 기준으로 앞이나 뒤로 단일 단계 실행하며 매번 레지스터 덤프를 냅니다. 인수 없는 `step`은 다음 명령으로 한 단계 이동합니다. `select n`은 Wireshark처럼 1부터 번호를 매긴 pcap 패킷을 골라 다음 `run` 또는 `step`의 입력으로 사용하고, `quit`은 디버거를 종료합니다.
필터와 패킷을 불러와 실행 상태를 조사하는 순서입니다.
* load pcap foo.pcap
Loads standard tcpdump pcap file.
* run [<n>]
bpf passes:1 fails:9
Runs through all packets from a pcap to account how many passes and fails
the filter will generate. A limit of packets to traverse can be given.
* disassemble::
l0: ldh [12]
l1: jeq #0x800, l2, l5
l2: ldb [23]
l3: jeq #0x1, l4, l5
l4: ret #0xffff
l5: ret #0
Prints out BPF code disassembly.
* dump::
/* { op, jt, jf, k }, */
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 3, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 1, 0x00000001 },
{ 0x06, 0, 0, 0x0000ffff },
{ 0x06, 0, 0, 0000000000 },
Prints out C-style BPF code dump.
* breakpoint 0::
breakpoint at: l0: ldh [12]
* breakpoint 1::
breakpoint at: l1: jeq #0x800, l2, l5
...
Sets breakpoints at particular BPF instructions. Issuing a `run` command
will walk through the pcap file continuing from the current packet and
break when a breakpoint is being hit (another `run` will continue from
the currently active breakpoint executing next instructions):
* run::
-- register dump --
pc: [0] <-- program counter
code: [40] jt[0] jf[0] k[12] <-- plain BPF code of current instruction
curr: l0: ldh [12] <-- disassembly of current instruction
A: [00000000][0] <-- content of A (hex, decimal)
X: [00000000][0] <-- content of X (hex, decimal)
M[0,15]: [00000000][0] <-- folded content of M (hex, decimal)
-- packet dump -- <-- Current packet from pcap (hex)
len: 42
0: 00 19 cb 55 55 a4 00 14 a4 43 78 69 08 06 00 01
16: 08 00 06 04 00 01 00 14 a4 43 78 69 0a 3b 01 26
32: 00 00 00 00 00 00 0a 3b 01 01
(breakpoint)
>
* breakpoint::
breakpoints: 0 1
Prints currently set breakpoints.
* step [-<n>, +<n>]
Performs single stepping through the BPF program from the current pc
offset. Thus, on each step invocation, above register dump is issued.
This can go forwards and backwards in time, a plain `step` will break
on the next BPF instruction, thus +1. (No `run` needs to be issued here.)
* select <n>
Selects a given packet from the pcap file to continue from. Thus, on
the next `run` or `step`, the BPF program is being evaluated against
the user pre-selected packet. Numbering starts just as in Wireshark
with index 1.
* quit
Exits bpf_dbg.
JIT 컴파일러 제어
511-541Linux 커널에는 x86_64, SPARC, PowerPC, ARM, ARM64, MIPS, RISC-V, s390, ARC용 BPF JIT 컴파일러가 있으며 `CONFIG_BPF_JIT`로 활성화합니다. root가 `/proc/sys/net/core/bpf_jit_enable`에 1을 쓰면 사용자 공간이나 커널 내부 사용자가 붙이는 필터마다 JIT가 투명하게 호출됩니다.
개발과 감사를 위해 값을 2로 설정하면 각 컴파일의 생성 opcode 이미지를 커널 로그에 기록합니다. `CONFIG_BPF_JIT_ALWAYS_ON`이면 값이 1로 고정되고 2를 포함한 다른 값은 거부됩니다. 최종 JIT 이미지를 커널 로그에 덤프하는 방식은 권장되지 않으며, 일반적인 검사는 `tools/bpf/bpftool/`의 bpftool을 사용하는 편이 좋습니다.
JIT compiler
------------
The Linux kernel has a built-in BPF JIT compiler for x86_64, SPARC,
PowerPC, ARM, ARM64, MIPS, RISC-V, s390, and ARC and can be enabled through
CONFIG_BPF_JIT. The JIT compiler is transparently invoked for each
attached filter from user space or for internal kernel users if it has
been previously enabled by root::
echo 1 > /proc/sys/net/core/bpf_jit_enable
For JIT developers, doing audits etc, each compile run can output the generated
opcode image into the kernel log via::
echo 2 > /proc/sys/net/core/bpf_jit_enable
Example output from dmesg::
[ 3389.935842] flen=6 proglen=70 pass=3 image=ffffffffa0069c8f
[ 3389.935847] JIT code: 00000000: 55 48 89 e5 48 83 ec 60 48 89 5d f8 44 8b 4f 68
[ 3389.935849] JIT code: 00000010: 44 2b 4f 6c 4c 8b 87 d8 00 00 00 be 0c 00 00 00
[ 3389.935850] JIT code: 00000020: e8 1d 94 ff e0 3d 00 08 00 00 75 16 be 17 00 00
[ 3389.935851] JIT code: 00000030: 00 e8 28 94 ff e0 83 f8 01 75 07 b8 ff ff 00 00
[ 3389.935852] JIT code: 00000040: eb 02 31 c0 c9 c3
When CONFIG_BPF_JIT_ALWAYS_ON is enabled, bpf_jit_enable is permanently set to 1 and
setting any other value than that will return in failure. This is even the case for
setting bpf_jit_enable to 2, since dumping the final JIT image into the kernel log
is discouraged and introspection through bpftool (under tools/bpf/bpftool/) is the
generally recommended approach instead.
JIT 코드 디스어셈블
542-619커널 소스의 `tools/bpf/bpf_jit_disasm`은 커널 로그의 16진 JIT 덤프를 어셈블리로 변환합니다. 예제에서는 6개 BPF 명령이 세 번의 패스를 거쳐 70바이트 x86-64 코드가 되었고, 출력은 이미지 주소를 기준으로 push, move, call, compare, branch, return 명령을 보여 줍니다.
`bpf_jit_disasm -o`는 각 어셈블리 명령 아래에 대응하는 기계어 바이트를 함께 붙입니다. `bpf_jit_disasm`, `bpf_asm`, `bpf_dbg`는 BPF JIT 개발자가 필터 작성, 커널 적재, 실행 추적, 생성 코드 검사를 이어서 수행할 수 있는 도구 체인을 이룹니다.
In the kernel source tree under tools/bpf/, there's bpf_jit_disasm for
generating disassembly out of the kernel log's hexdump::
# ./bpf_jit_disasm
70 bytes emitted from JIT compiler (pass:3, flen:6)
ffffffffa0069c8f + <x>:
0: push %rbp
1: mov %rsp,%rbp
4: sub $0x60,%rsp
8: mov %rbx,-0x8(%rbp)
c: mov 0x68(%rdi),%r9d
10: sub 0x6c(%rdi),%r9d
14: mov 0xd8(%rdi),%r8
1b: mov $0xc,%esi
20: callq 0xffffffffe0ff9442
25: cmp $0x800,%eax
2a: jne 0x0000000000000042
2c: mov $0x17,%esi
31: callq 0xffffffffe0ff945e
36: cmp $0x1,%eax
39: jne 0x0000000000000042
3b: mov $0xffff,%eax
40: jmp 0x0000000000000044
42: xor %eax,%eax
44: leaveq
45: retq
Issuing option `-o` will "annotate" opcodes to resulting assembler
instructions, which can be very useful for JIT developers:
# ./bpf_jit_disasm -o
70 bytes emitted from JIT compiler (pass:3, flen:6)
ffffffffa0069c8f + <x>:
0: push %rbp
55
1: mov %rsp,%rbp
48 89 e5
4: sub $0x60,%rsp
48 83 ec 60
8: mov %rbx,-0x8(%rbp)
48 89 5d f8
c: mov 0x68(%rdi),%r9d
44 8b 4f 68
10: sub 0x6c(%rdi),%r9d
44 2b 4f 6c
14: mov 0xd8(%rdi),%r8
4c 8b 87 d8 00 00 00
1b: mov $0xc,%esi
be 0c 00 00 00
20: callq 0xffffffffe0ff9442
e8 1d 94 ff e0
25: cmp $0x800,%eax
3d 00 08 00 00
2a: jne 0x0000000000000042
75 16
2c: mov $0x17,%esi
be 17 00 00 00
31: callq 0xffffffffe0ff945e
e8 28 94 ff e0
36: cmp $0x1,%eax
83 f8 01
39: jne 0x0000000000000042
75 07
3b: mov $0xffff,%eax
b8 ff ff 00 00
40: jmp 0x0000000000000044
eb 02
42: xor %eax,%eax
31 c0
44: leaveq
c9
45: retq
c3
For BPF JIT developers, bpf_jit_disasm, bpf_asm and bpf_dbg provides a useful
toolchain for developing and testing the kernel's JIT compiler.
BPF 커널 내부
620-655커널 인터프리터 내부에서는 앞에서 설명한 고전 BPF와 원리는 비슷하지만 형식이 다른 명령 집합을 사용합니다. 네이티브 명령 집합에 더 가깝게 설계해 성능을 높인 이 ISA가 eBPF입니다. eBPF의 `e`는 extended를 뜻하지만, 고전 BPF 적재 명령의 `k`를 오버로드하는 'BPF 확장'과는 다른 개념입니다.
새 형식은 제한된 C 프로그램을 GCC/LLVM 백엔드로 eBPF에 컴파일한 뒤 64비트 CPU 네이티브 코드로 JIT 변환해 `C -> eBPF -> native` 두 단계의 오버헤드를 최소화하려는 목표로 설계되었습니다.
seccomp BPF, 고전 소켓 필터, `cls_bpf`, team 드라이버 분류기, netfilter `xt_bpf`, PTP 분류기 등 사용자 BPF 프로그램은 커널 내부에서 eBPF 표현으로 변환되어 인터프리터 또는 JIT 코드에서 실행됩니다. 커널 내부 사용자는 `bpf_prog_create()`와 `bpf_prog_destroy()`로 프로그램을 관리하고 `bpf_prog_run(filter, ctx)`로 실행합니다. 변환 전에는 `bpf_check_classic()`의 모든 제약과 검사가 적용됩니다.
대부분의 32비트 아키텍처는 고전 BPF 형식에서 JIT하지만 x86-64, aarch64, s390x, powerpc64, sparc64, arm32, riscv64, riscv32, loongarch64, arc는 eBPF 명령 집합에서 JIT 컴파일합니다.
BPF kernel internals
--------------------
Internally, for the kernel interpreter, a different instruction set
format with similar underlying principles from BPF described in previous
paragraphs is being used. However, the instruction set format is modelled
closer to the underlying architecture to mimic native instruction sets, so
that a better performance can be achieved (more details later). This new
ISA is called eBPF. See the ../bpf/index.rst for details. (Note: eBPF which
originates from [e]xtended BPF is not the same as BPF extensions! While
eBPF is an ISA, BPF extensions date back to classic BPF's 'overloading'
of BPF_LD | BPF_{B,H,W} | BPF_ABS instruction.)
The new instruction set was originally designed with the possible goal in
mind to write programs in "restricted C" and compile into eBPF with a optional
GCC/LLVM backend, so that it can just-in-time map to modern 64-bit CPUs with
minimal performance overhead over two steps, that is, C -> eBPF -> native code.
Currently, the new format is being used for running user BPF programs, which
includes seccomp BPF, classic socket filters, cls_bpf traffic classifier,
team driver's classifier for its load-balancing mode, netfilter's xt_bpf
extension, PTP dissector/classifier, and much more. They are all internally
converted by the kernel into the new instruction set representation and run
in the eBPF interpreter. For in-kernel handlers, this all works transparently
by using bpf_prog_create() for setting up the filter, resp.
bpf_prog_destroy() for destroying it. The function
bpf_prog_run(filter, ctx) transparently invokes eBPF interpreter or JITed
code to run the filter. 'filter' is a pointer to struct bpf_prog that we
got from bpf_prog_create(), and 'ctx' the given context (e.g.
skb pointer). All constraints and restrictions from bpf_check_classic() apply
before a conversion to the new layout is being done behind the scenes!
Currently, the classic BPF format is being used for JITing on most
32-bit architectures, whereas x86-64, aarch64, s390x, powerpc64,
sparc64, arm32, riscv64, riscv32, loongarch64, arc perform JIT compilation
from eBPF instruction set.
시험
656-669BPF 도구 체인 외에도 커널은 고전 BPF와 eBPF를 인터프리터와 JIT에서 실행하는 여러 시험 사례를 `lib/test_bpf.c`에 제공합니다. `CONFIG_TEST_BPF=m`으로 모듈을 빌드하고 설치한 뒤 `insmod` 또는 `modprobe test_bpf`로 시험 모음을 실행합니다. 각 시험 결과와 나노초 단위 실행 시간은 커널 로그에서 확인할 수 있습니다.
Testing
-------
Next to the BPF toolchain, the kernel also ships a test module that contains
various test cases for classic and eBPF that can be executed against
the BPF interpreter and JIT compiler. It can be found in lib/test_bpf.c and
enabled via Kconfig::
CONFIG_TEST_BPF=m
After the module has been built and installed, the test suite can be executed
via insmod or modprobe against 'test_bpf' module. Results of the test cases
including timings in nsec can be found in the kernel log (dmesg).
기타 사항과 작성자
670-685Linux 시스템 호출 퍼저 trinity도 BPF와 `SECCOMP-BPF` 커널 퍼징을 기본 지원합니다. 이 문서는 잠재적인 BPF 개발자와 보안 감사자가 기반 아키텍처를 더 잘 이해할 수 있도록 Jay Schulist, Daniel Borkmann, Alexei Starovoitov가 작성했습니다.
Misc
----
Also trinity, the Linux syscall fuzzer, has built-in support for BPF and
SECCOMP-BPF kernel fuzzing.
Written by
----------
The document was written in the hope that it is found useful and in order
to give potential BPF hackers or security auditors a better overview of
the underlying architecture.
- Jay Schulist <[email protected]>
- Daniel Borkmann <[email protected]>
- Alexei Starovoitov <[email protected]>
요약·해설
filter.rst:1-685이 문서는 `sock_filter` 4튜플에서 시작해 필터를 소켓에 부착하고 잠그는 방법, 고전 BPF 가상 머신의 레지스터와 명령, Linux 전용 적재 확장, 디버거와 JIT 검사 방법까지 한 흐름으로 연결합니다. 현대 커널이 고전 프로그램을 eBPF 내부 표현으로 바꿔 실행하는 경계도 함께 설명합니다.
사용자 필터에서 커널 실행까지의 층을 정리했습니다.