├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── chapter10
└── README.md
├── chapter2
├── README.md
├── hello-buffer.py
├── hello-map.py
├── hello-tail.py
└── hello.py
├── chapter3
├── Makefile
├── README.md
├── hello-func.bpf.c
└── hello.bpf.c
├── chapter4
├── README.md
├── hello-buffer-config.py
└── hello-ring-buffer-config.py
├── chapter5
├── Makefile
├── README.md
├── find-map.c
├── hello-buffer-config
├── hello-buffer-config.bpf.c
├── hello-buffer-config.c
├── hello-buffer-config.h
└── xdp.bpf.c
├── chapter6
├── Makefile
├── README.md
├── hello-verifier.bpf.c
├── hello-verifier.c
├── hello-verifier.h
└── kprobe_exec.png
├── chapter7
├── Makefile
├── README.md
├── hello.bpf.c
├── hello.c
└── hello.h
├── chapter8
├── Makefile
├── README.md
├── hello.bpf.c
├── network.bpf.c
├── network.h
├── network.py
├── packet.h
├── ping.bpf.c
└── ping.py
├── learning-ebpf-cover.png
└── learning-ebpf.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | .vscode/
3 | hello
4 | *.skel.h
5 | vmlinux.h
6 | chapter6/hello-verifier
7 | chapter5/find-map
8 | chapter5/hello-buffer-config
9 |
10 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "libbpf"]
2 | path = libbpf
3 | url = https://github.com/libbpf/libbpf
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Learning eBPF
2 |
3 | This repo accompanies my new book [Learning
4 | eBPF](https://www.amazon.com/Learning-eBPF-Programming-Observability-Networking/dp/1098135121)
5 | (published by O'Reilly).
6 |
7 |
10 |
11 | Buy your copy of the book from
12 | [Bookshop.org](https://bookshop.org/p/books/learning-ebpf-programming-the-linux-kernel-for-enhanced-observability-networking-and-security-liz-rice/19244244?ean=9781098135126)
13 | or
14 | [Amazon](https://www.amazon.com/Learning-eBPF-Programming-Observability-Networking/dp/1098135121),
15 | view it on the [O'Reilly platform](https://www.oreilly.com/library/view/learning-ebpf/9781098135119/), or download a copy from [Isovalent](https://isovalent.com/learning-ebpf).
16 |
17 | ## Running the example code
18 |
19 | The repo includes the example eBPF programs discussed in the book.
20 |
21 | I've also provided a [Lima](https://github.com/lima-vm/lima) config file with
22 | the packages you need for building the code pre-installed.
23 |
24 | If you have a Linux machine or VM to hand, feel free to use that instead of
25 | Lima, using the `learning-ebpf.yaml` file as a guide for the packages you'll
26 | need to install. The minimum kernel version required varies from chapter to chapter. All
27 | these examples have been tested on an Ubuntu 22.04 distribution using a 5.15 kernel.
28 |
29 |
30 |
31 | ### Install this repo
32 |
33 | ```sh
34 | git clone --recurse-submodules https://github.com/lizrice/learning-ebpf
35 | cd learning-ebpf
36 | ```
37 |
38 | ### Lima VM
39 |
40 | ```sh
41 | limactl start learning-ebpf.yaml
42 | limactl shell learning-ebpf
43 |
44 | # You'll need to be root for most of the examples
45 | sudo -s
46 | ```
47 |
48 | ### Building libbpf and installing header files
49 |
50 | Libbpf is included as a submodule in this repo. You'll need to build and install
51 | it for the C-based examples to build correctly. (See libbpf/README.md for more
52 | details.)
53 |
54 | ```sh
55 | cd libbpf/src
56 | make install
57 | cd ../..
58 | ```
59 |
60 | ### Building bpftool
61 |
62 | There are several examples using `bpftool` throughout the book. To get a version
63 | with libbfd support (which you'll need if you want to see the jited code in the
64 | Chapter 3 examples) you might need to build it from source:
65 |
66 | ```sh
67 | cd ..
68 | git clone --recurse-submodules https://github.com/libbpf/bpftool.git
69 | cd bpftool/src
70 | make install
71 | ```
72 |
73 | `bpftool` binaries are now also available from https://github.com/libbpf/bpftool/releases these days.
74 |
75 | ## Examples
76 |
77 | You won't be surprised to learn that the directories correspond to chapters in
78 | the book. Here are the different examples that accompany each chapter.
79 |
80 | * Chapter 1: What Is eBPF and Why Is It Important?
81 | * [Chapter 2: eBPF's "Hello World"](chapter2/README.md) - Basic examples using the BCC framework.
82 | * [Chapter 3: Anatomy of an eBPF Program](chapter3/README.md) - C-based XDP
83 | examples, used in the book to explore how the source code gets transformed to eBPF bytecode and
84 | machine code. There's also an example of BPF to BPF function calls.
85 | * [Chapter 4: The bpf() System Call](chapter4/README.md) - More BCC-based examples, used in the book to
86 | illustrate what's happening at the syscall level when you use eBPF.
87 | * [Chapter 5: CO-RE, BTF and Libbpf](chapter5/README.md) - Libbpf-based C
88 | example code.
89 | * [Chapter 6: The eBPF Verifier](chapter6/README.md) - Make small edits to the
90 | example code to cause a variety of verifier errors!
91 | * [Chapter 7: eBPF Program and Attachment Types](chapter7/README.md) - Examples
92 | of different eBPF program types.
93 | * [Chapter 8: eBPF for Networking](chapter8/README.md) - Example code that
94 | attaches to various points in the network stack to interfere with ping and
95 | curl requests. *Coming soon, load balancer example*
96 | * Chapter 9: eBPF for Security - *coming soon*
97 | * [Chapter 10: eBPF Programming](chapter10/README.md) - The book explores examples from various eBPF
98 | libraries.
99 | * Chapter 11: The Future Evolution of eBPF
100 |
101 | There are no code examples for Chapters 1 and 11.
102 |
103 | ### Privileges
104 |
105 | You'll need root privileges (well, strictly CAP_BPF and [additional
106 | privileges](https://mdaverde.com/posts/cap-bpf/)) to be able to load BPF
107 | programs into the kernel. `sudo -s` is your friend.
108 |
109 | ### View eBPF trace output
110 |
111 | A couple of ways to see the output from the kernel's trace pipe where eBPF
112 | tracing gets written:
113 |
114 | * `cat /sys/kernel/debug/tracing/trace_pipe`
115 | * `bpftool prog tracelog`
116 |
117 | ## Installing on other Linux distributions
118 |
119 | As noted above, I've tested these examples using Ubuntu 22.04 and a 5.15 kernel. If you're using a different distribution and / or kernel version you might run into incompatibilities between various packages and dependencies. For example:
120 |
121 | - My installation uses Clang 14. If you're using Clang 15 or later (which you can check with `clang --version` you'll need [BCC version 0.27.0](https://github.com/iovisor/bcc/releases) or later
122 |
123 | ## Corrections
124 |
125 | I'd love to hear if you find corrections and improvements for
126 | these examples. Issues and PRs are welcome!
127 |
--------------------------------------------------------------------------------
/chapter10/README.md:
--------------------------------------------------------------------------------
1 | # Chapter 10
2 |
3 | Examples discussed in the book:
4 |
5 | * Iovisor's `bpftrace` version of
6 | [opensnoop](https://github.com/iovisor/bpftrace/blob/master/tools/opensnoop.bt)
7 | * [Kprobe example](https://github.com/cilium/ebpf/tree/master/examples/kprobe)
8 | from cilium/ebpf
9 | * The [Aya XDP
10 | example](https://github.com/aya-rs/book/blob/main/examples/xdp-hello/xdp-hello-ebpf/src/main.rs)
11 | has been renamed from *myapp* to *xdp_hello* since I wrote this chapter
12 |
13 |
--------------------------------------------------------------------------------
/chapter2/README.md:
--------------------------------------------------------------------------------
1 | # Chapter 2
2 |
3 | You'll need [BCC](https://github.com/iovisor/bcc) installed for the examples in this directory.
4 |
5 | * `hello.py` - simple example that emits trace messages triggered by a kprobe
6 | * `hello-map.py` - introduce the concept of a BPF map
7 | * `hello-buffer.py` - use a ring buffer to convey information to user space
8 | * `hello-tail.py` - simple demo of eBPF tail calls
9 |
10 |
--------------------------------------------------------------------------------
/chapter2/hello-buffer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | from bcc import BPF
3 |
4 | program = r"""
5 | BPF_PERF_OUTPUT(output);
6 |
7 | struct data_t {
8 | int pid;
9 | int uid;
10 | char command[16];
11 | char message[12];
12 | };
13 |
14 | int hello(void *ctx) {
15 | struct data_t data = {};
16 | char message[12] = "Hello World";
17 |
18 | data.pid = bpf_get_current_pid_tgid() >> 32;
19 | data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
20 |
21 | bpf_get_current_comm(&data.command, sizeof(data.command));
22 | bpf_probe_read_kernel(&data.message, sizeof(data.message), message);
23 |
24 | output.perf_submit(ctx, &data, sizeof(data));
25 |
26 | return 0;
27 | }
28 | """
29 |
30 | b = BPF(text=program)
31 | syscall = b.get_syscall_fnname("execve")
32 | b.attach_kprobe(event=syscall, fn_name="hello")
33 |
34 | def print_event(cpu, data, size):
35 | data = b["output"].event(data)
36 | print(f"{data.pid} {data.uid} {data.command.decode()} {data.message.decode()}")
37 |
38 | b["output"].open_perf_buffer(print_event)
39 | while True:
40 | b.perf_buffer_poll()
41 |
--------------------------------------------------------------------------------
/chapter2/hello-map.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | from bcc import BPF
3 | from time import sleep
4 |
5 | program = r"""
6 | BPF_HASH(counter_table);
7 |
8 | int hello(void *ctx) {
9 | u64 uid;
10 | u64 counter = 0;
11 | u64 *p;
12 |
13 | uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
14 | p = counter_table.lookup(&uid);
15 | if (p != 0) {
16 | counter = *p;
17 | }
18 | counter++;
19 | counter_table.update(&uid, &counter);
20 | return 0;
21 | }
22 | """
23 |
24 | b = BPF(text=program)
25 | syscall = b.get_syscall_fnname("execve")
26 | b.attach_kprobe(event=syscall, fn_name="hello")
27 |
28 | # Attach to a tracepoint that gets hit for all syscalls
29 | # b.attach_raw_tracepoint(tp="sys_enter", fn_name="hello")
30 |
31 | while True:
32 | sleep(2)
33 | s = ""
34 | for k,v in b["counter_table"].items():
35 | s += f"ID {k.value}: {v.value}\t"
36 | print(s)
37 |
--------------------------------------------------------------------------------
/chapter2/hello-tail.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | from bcc import BPF
3 | import ctypes as ct
4 |
5 | program = r"""
6 | BPF_PROG_ARRAY(syscall, 500);
7 |
8 | int hello(struct bpf_raw_tracepoint_args *ctx) {
9 | int opcode = ctx->args[1];
10 | syscall.call(ctx, opcode);
11 | bpf_trace_printk("Another syscall: %d", opcode);
12 | return 0;
13 | }
14 |
15 | int hello_exec(void *ctx) {
16 | bpf_trace_printk("Executing a program");
17 | return 0;
18 | }
19 |
20 | int hello_timer(struct bpf_raw_tracepoint_args *ctx) {
21 | int opcode = ctx->args[1];
22 | switch (opcode) {
23 | case 222:
24 | bpf_trace_printk("Creating a timer");
25 | break;
26 | case 226:
27 | bpf_trace_printk("Deleting a timer");
28 | break;
29 | default:
30 | bpf_trace_printk("Some other timer operation");
31 | break;
32 | }
33 | return 0;
34 | }
35 |
36 | int ignore_opcode(void *ctx) {
37 | return 0;
38 | }
39 | """
40 |
41 | b = BPF(text=program)
42 | b.attach_raw_tracepoint(tp="sys_enter", fn_name="hello")
43 |
44 | ignore_fn = b.load_func("ignore_opcode", BPF.RAW_TRACEPOINT)
45 | exec_fn = b.load_func("hello_exec", BPF.RAW_TRACEPOINT)
46 | timer_fn = b.load_func("hello_timer", BPF.RAW_TRACEPOINT)
47 |
48 | prog_array = b.get_table("syscall")
49 |
50 | # Ignore all syscalls initially
51 | for i in range(len(prog_array)):
52 | prog_array[ct.c_int(i)] = ct.c_int(ignore_fn.fd)
53 |
54 | # Only enable few syscalls which are of the interest
55 | prog_array[ct.c_int(59)] = ct.c_int(exec_fn.fd)
56 | prog_array[ct.c_int(222)] = ct.c_int(timer_fn.fd)
57 | prog_array[ct.c_int(223)] = ct.c_int(timer_fn.fd)
58 | prog_array[ct.c_int(224)] = ct.c_int(timer_fn.fd)
59 | prog_array[ct.c_int(225)] = ct.c_int(timer_fn.fd)
60 | prog_array[ct.c_int(226)] = ct.c_int(timer_fn.fd)
61 |
62 | b.trace_print()
63 |
--------------------------------------------------------------------------------
/chapter2/hello.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | from bcc import BPF
3 |
4 | program = r"""
5 | int hello(void *ctx) {
6 | bpf_trace_printk("Hello World!");
7 | return 0;
8 | }
9 | """
10 |
11 | b = BPF(text=program)
12 | syscall = b.get_syscall_fnname("execve")
13 | b.attach_kprobe(event=syscall, fn_name="hello")
14 |
15 | b.trace_print()
16 |
--------------------------------------------------------------------------------
/chapter3/Makefile:
--------------------------------------------------------------------------------
1 | TARGETS = hello hello-func
2 |
3 | all: $(TARGETS)
4 | .PHONY: all
5 |
6 | $(TARGETS): %: %.bpf.o
7 |
8 | %.bpf.o: %.bpf.c
9 | clang \
10 | -target bpf \
11 | -I/usr/include/$(shell uname -m)-linux-gnu \
12 | -g \
13 | -O2 -o $@ -c $<
14 |
15 | clean:
16 | - rm *.bpf.o
17 | - rm -f /sys/fs/bpf/hello
18 | - rm -f /sys/fs/bpf/hello-func
19 |
20 |
--------------------------------------------------------------------------------
/chapter3/README.md:
--------------------------------------------------------------------------------
1 | # Chapter 3 - Anatomy of an eBPF Program
2 |
3 | Make sure you have installed libbpf and its header files as described in the
4 | main [README file](../README.md).
5 |
6 | You should then be able to build the example code as an object file by running
7 | `make` in this directory. See Chapter 3 of the book for instructions on what to
8 | do with this object file.
9 |
--------------------------------------------------------------------------------
/chapter3/hello-func.bpf.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | static __attribute((noinline)) int get_opcode(struct bpf_raw_tracepoint_args *ctx) {
5 | return ctx->args[1];
6 | }
7 |
8 | SEC("raw_tp/")
9 | int hello(struct bpf_raw_tracepoint_args *ctx) {
10 | int opcode = get_opcode(ctx);
11 | bpf_printk("Syscall: %d", opcode);
12 | return 0;
13 | }
14 |
15 | char LICENSE[] SEC("license") = "Dual BSD/GPL";
16 |
--------------------------------------------------------------------------------
/chapter3/hello.bpf.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | int counter = 0;
5 |
6 | SEC("xdp")
7 | int hello(struct xdp_md *ctx) {
8 | bpf_printk("Hello World %d", counter);
9 | counter++;
10 | return XDP_PASS;
11 | }
12 |
13 | char LICENSE[] SEC("license") = "Dual BSD/GPL";
14 |
--------------------------------------------------------------------------------
/chapter4/README.md:
--------------------------------------------------------------------------------
1 | # Chapter 4 - The bpf() System Call
2 |
3 | In this chapter I'll walk you through the system calls invoked by these example
4 | programs `hello-buffer-config.py` and `hello-ring-buffer-config.py`.
5 |
6 | ## Exercises
7 |
8 | Example solution to using `bpftool` to update the `config` map:
9 |
10 | ```
11 | bpftool map update name config key 0x2 0 0 0 value hex 48 65 6c 6c 6f 20 32 0 0 0 0 0
12 | ```
--------------------------------------------------------------------------------
/chapter4/hello-buffer-config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | from bcc import BPF
4 | import ctypes as ct
5 |
6 | program = r"""
7 | struct user_msg_t {
8 | char message[13];
9 | };
10 |
11 | BPF_HASH(config, u32, struct user_msg_t);
12 |
13 | BPF_PERF_OUTPUT(output);
14 |
15 | struct data_t {
16 | int pid;
17 | int uid;
18 | char command[16];
19 | char message[12];
20 | };
21 |
22 | int hello(void *ctx) {
23 | struct data_t data = {};
24 | struct user_msg_t *p;
25 | char message[12] = "Hello World";
26 |
27 | data.pid = bpf_get_current_pid_tgid() >> 32;
28 | data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
29 |
30 | bpf_get_current_comm(&data.command, sizeof(data.command));
31 |
32 | p = config.lookup(&data.uid);
33 | if (p != 0) {
34 | bpf_probe_read_kernel(&data.message, sizeof(data.message), p->message);
35 | } else {
36 | bpf_probe_read_kernel(&data.message, sizeof(data.message), message);
37 | }
38 |
39 | output.perf_submit(ctx, &data, sizeof(data));
40 |
41 | return 0;
42 | }
43 | """
44 |
45 | b = BPF(text=program)
46 | syscall = b.get_syscall_fnname("execve")
47 | b.attach_kprobe(event=syscall, fn_name="hello")
48 | b["config"][ct.c_int(0)] = ct.create_string_buffer(b"Hey root!")
49 | b["config"][ct.c_int(501)] = ct.create_string_buffer(b"Hi user 501!")
50 |
51 | def print_event(cpu, data, size):
52 | data = b["output"].event(data)
53 | print(f"{data.pid} {data.uid} {data.command.decode()} {data.message.decode()}")
54 |
55 | b["output"].open_perf_buffer(print_event)
56 | while True:
57 | b.perf_buffer_poll()
58 |
--------------------------------------------------------------------------------
/chapter4/hello-ring-buffer-config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | # -*- coding: utf-8 -*-
3 | from bcc import BPF
4 | import ctypes as ct
5 |
6 | program = r"""
7 | struct user_msg_t {
8 | char message[12];
9 | };
10 |
11 | BPF_HASH(config, u32, struct user_msg_t);
12 |
13 | BPF_RINGBUF_OUTPUT(output, 1);
14 |
15 | struct data_t {
16 | int pid;
17 | int uid;
18 | char command[16];
19 | char message[12];
20 | };
21 |
22 | int hello(void *ctx) {
23 | struct data_t data = {};
24 | char message[12] = "Hello World";
25 | struct user_msg_t *p;
26 |
27 | data.pid = bpf_get_current_pid_tgid() >> 32;
28 | data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
29 |
30 | bpf_get_current_comm(&data.command, sizeof(data.command));
31 |
32 | p = config.lookup(&data.uid);
33 | if (p != 0) {
34 | bpf_probe_read_kernel(&data.message, sizeof(data.message), p->message);
35 | } else {
36 | bpf_probe_read_kernel(&data.message, sizeof(data.message), message);
37 | }
38 |
39 | output.ringbuf_output(&data, sizeof(data), 0);
40 |
41 | return 0;
42 | }
43 | """
44 |
45 | b = BPF(text=program)
46 | b["config"][ct.c_int(0)] = ct.create_string_buffer(b"Hey root!")
47 | syscall = b.get_syscall_fnname("execve")
48 | b.attach_kprobe(event=syscall, fn_name="hello")
49 |
50 | def print_event(cpu, data, size):
51 | data = b["output"].event(data)
52 | print(f"{data.pid} {data.uid} {data.command.decode()} {data.message.decode()}")
53 |
54 | b["output"].open_ring_buffer(print_event)
55 | while True:
56 | b.ring_buffer_poll()
57 |
--------------------------------------------------------------------------------
/chapter5/Makefile:
--------------------------------------------------------------------------------
1 | TARGET = hello-buffer-config
2 | ARCH = $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/')
3 |
4 | BPF_OBJ = ${TARGET:=.bpf.o}
5 | USER_C = ${TARGET:=.c}
6 | USER_SKEL = ${TARGET:=.skel.h}
7 |
8 | all: $(TARGET) $(BPF_OBJ) find-map
9 | .PHONY: all
10 |
11 | $(TARGET): $(USER_C) $(USER_SKEL)
12 | gcc -Wall -o $(TARGET) $(USER_C) -L../libbpf/src -l:libbpf.a -lelf -lz
13 |
14 | %.bpf.o: %.bpf.c vmlinux.h
15 | clang \
16 | -target bpf \
17 | -D __TARGET_ARCH_$(ARCH) \
18 | -Wall \
19 | -O2 -g -o $@ -c $<
20 | llvm-strip -g $@
21 |
22 | $(USER_SKEL): $(BPF_OBJ)
23 | bpftool gen skeleton $< > $@
24 |
25 | vmlinux.h:
26 | bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
27 |
28 | clean:
29 | - rm $(BPF_OBJ)
30 | - rm $(TARGET)
31 | - rm find-map
32 |
33 | find-map: find-map.c
34 | gcc -Wall -o find-map find-map.c -L../libbpf/src -l:libbpf.a -lelf -lz
35 |
--------------------------------------------------------------------------------
/chapter5/README.md:
--------------------------------------------------------------------------------
1 | # Chapter 5 - CO-RE, BTF and libbpf
2 |
3 | Run `make` to build the examples in this file. In the book you'll explore the
4 | BTF data associated with the example code, as well as comparing the libbpf
5 | approach to earlier BCC examples.
--------------------------------------------------------------------------------
/chapter5/find-map.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | // Run this as root
7 | int main()
8 | {
9 | struct bpf_map_info info = {};
10 | unsigned int len = sizeof(info);
11 |
12 | int findme = bpf_obj_get("/sys/fs/bpf/findme");
13 | if (findme <= 0) {
14 | printf("No FD\n");
15 | } else {
16 | bpf_obj_get_info_by_fd(findme, &info, &len);
17 | printf("name %s\n", info.name);
18 | }
19 | }
--------------------------------------------------------------------------------
/chapter5/hello-buffer-config:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizrice/learning-ebpf/ab40e0327deecc2daa09b96b2e38598f1bcd4bdd/chapter5/hello-buffer-config
--------------------------------------------------------------------------------
/chapter5/hello-buffer-config.bpf.c:
--------------------------------------------------------------------------------
1 | #include "vmlinux.h"
2 | #include
3 | #include
4 | #include
5 | #include "hello-buffer-config.h"
6 |
7 | char message[12] = "Hello World";
8 |
9 | struct {
10 | __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
11 | __uint(key_size, sizeof(u32));
12 | __uint(value_size, sizeof(u32));
13 | } output SEC(".maps");
14 |
15 | struct user_msg_t {
16 | char message[12];
17 | };
18 |
19 | struct {
20 | __uint(type, BPF_MAP_TYPE_HASH);
21 | __uint(max_entries, 10240);
22 | __type(key, u32);
23 | __type(value, struct user_msg_t);
24 | } my_config SEC(".maps");
25 |
26 | SEC("ksyscall/execve")
27 | int BPF_KPROBE_SYSCALL(hello, const char *pathname)
28 | {
29 | struct data_t data = {};
30 | struct user_msg_t *p;
31 |
32 | data.pid = bpf_get_current_pid_tgid() >> 32;
33 | data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
34 |
35 | bpf_get_current_comm(&data.command, sizeof(data.command));
36 | bpf_probe_read_user_str(&data.path, sizeof(data.path), pathname);
37 |
38 | p = bpf_map_lookup_elem(&my_config, &data.uid);
39 | if (p != 0) {
40 | bpf_probe_read_kernel_str(&data.message, sizeof(data.message), p->message);
41 | } else {
42 | bpf_probe_read_kernel_str(&data.message, sizeof(data.message), message);
43 | }
44 |
45 | bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU, &data, sizeof(data));
46 | return 0;
47 | }
48 |
49 | char LICENSE[] SEC("license") = "Dual BSD/GPL";
50 |
--------------------------------------------------------------------------------
/chapter5/hello-buffer-config.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "hello-buffer-config.h"
6 | #include "hello-buffer-config.skel.h"
7 |
8 | static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
9 | {
10 | if (level >= LIBBPF_DEBUG)
11 | return 0;
12 |
13 | return vfprintf(stderr, format, args);
14 | }
15 |
16 | void handle_event(void *ctx, int cpu, void *data, unsigned int data_sz)
17 | {
18 | struct data_t *m = data;
19 |
20 | printf("%-6d %-6d %-16s %-16s %s\n", m->pid, m->uid, m->command, m->path, m->message);
21 | }
22 |
23 | void lost_event(void *ctx, int cpu, long long unsigned int data_sz)
24 | {
25 | printf("lost event\n");
26 | }
27 |
28 | int main()
29 | {
30 | struct hello_buffer_config_bpf *skel;
31 | int err;
32 | struct perf_buffer *pb = NULL;
33 |
34 | libbpf_set_print(libbpf_print_fn);
35 |
36 | skel = hello_buffer_config_bpf__open_and_load();
37 | if (!skel) {
38 | printf("Failed to open BPF object\n");
39 | return 1;
40 | }
41 |
42 | err = hello_buffer_config_bpf__attach(skel);
43 | if (err) {
44 | fprintf(stderr, "Failed to attach BPF skeleton: %d\n", err);
45 | hello_buffer_config_bpf__destroy(skel);
46 | return 1;
47 | }
48 |
49 | pb = perf_buffer__new(bpf_map__fd(skel->maps.output), 8, handle_event, lost_event, NULL, NULL);
50 | if (!pb) {
51 | err = -1;
52 | fprintf(stderr, "Failed to create ring buffer\n");
53 | hello_buffer_config_bpf__destroy(skel);
54 | return 1;
55 | }
56 |
57 | while (true) {
58 | err = perf_buffer__poll(pb, 100 /* timeout, ms */);
59 | // Ctrl-C gives -EINTR
60 | if (err == -EINTR) {
61 | err = 0;
62 | break;
63 | }
64 | if (err < 0) {
65 | printf("Error polling perf buffer: %d\n", err);
66 | break;
67 | }
68 | }
69 |
70 | perf_buffer__free(pb);
71 | hello_buffer_config_bpf__destroy(skel);
72 | return -err;
73 | }
74 |
--------------------------------------------------------------------------------
/chapter5/hello-buffer-config.h:
--------------------------------------------------------------------------------
1 | #ifndef HELLO_BUFFER_CONFIG_H
2 | #define HELLO_BUFFER_CONFIG_H
3 |
4 | struct data_t {
5 | int pid;
6 | int uid;
7 | char command[16];
8 | char message[12];
9 | char path[16];
10 | };
11 |
12 | #endif
13 |
--------------------------------------------------------------------------------
/chapter5/xdp.bpf.c:
--------------------------------------------------------------------------------
1 | #include "vmlinux.h"
2 | #include
3 |
4 | u32 count = 0;
5 |
6 | SEC("xdp")
7 | int hello(struct xdp_md *ctx) {
8 | count++;
9 | bpf_printk("Hello World %d", count);
10 | return XDP_PASS;
11 | }
12 |
13 | char LICENSE[] SEC("license") = "Dual BSD/GPL";
--------------------------------------------------------------------------------
/chapter6/Makefile:
--------------------------------------------------------------------------------
1 | TARGET = hello-verifier
2 | ARCH = $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/')
3 |
4 | BPF_TARGET = ${TARGET:=.bpf}
5 | BPF_C = ${BPF_TARGET:=.c}
6 | BPF_OBJ = ${BPF_TARGET:=.o}
7 |
8 | USER_C = ${TARGET:=.c}
9 | USER_SKEL = ${TARGET:=.skel.h}
10 |
11 | COMMON_H = ${TARGET:=.h}
12 |
13 | all: $(TARGET) $(BPF_OBJ)
14 | .PHONY: all
15 |
16 | $(TARGET): $(USER_C) $(USER_SKEL) $(COMMON_H)
17 | gcc -Wall -o $(TARGET) $(USER_C) -L../libbpf/src -l:libbpf.a -lelf -lz
18 |
19 | $(BPF_OBJ): %.o: $(BPF_C) vmlinux.h $(COMMON_H)
20 | clang \
21 | -target bpf \
22 | -D __BPF_TRACING__ \
23 | -D __TARGET_ARCH_$(ARCH) \
24 | -Wall \
25 | -O2 -g -o $@ -c $<
26 | llvm-strip -g $@
27 |
28 | $(USER_SKEL): $(BPF_OBJ)
29 | bpftool gen skeleton $< > $@
30 |
31 | vmlinux.h:
32 | bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
33 |
34 | clean:
35 | - rm $(BPF_OBJ)
36 | - rm $(TARGET)
37 |
38 |
--------------------------------------------------------------------------------
/chapter6/README.md:
--------------------------------------------------------------------------------
1 | # Chapter 6 - The eBPF Verifier
2 |
3 | Use `make` to build the example code. Make edits as suggested in the book to
4 | cause a variety of verification errors when you attempt to load the program into
5 | the kernel.
--------------------------------------------------------------------------------
/chapter6/hello-verifier.bpf.c:
--------------------------------------------------------------------------------
1 | #include "vmlinux.h"
2 | #include
3 | #include
4 | #include
5 | #include "hello-verifier.h"
6 |
7 | int c = 1;
8 | char message[12] = "Hello World";
9 |
10 | struct {
11 | __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
12 | __uint(key_size, sizeof(u32));
13 | __uint(value_size, sizeof(u32));
14 | } output SEC(".maps");
15 |
16 | struct {
17 | __uint(type, BPF_MAP_TYPE_HASH);
18 | __uint(max_entries, 10240);
19 | __type(key, u32);
20 | __type(value, struct msg_t);
21 | } my_config SEC(".maps");
22 |
23 | SEC("ksyscall/execve")
24 | int kprobe_exec(void *ctx)
25 | {
26 | struct data_t data = {};
27 | struct msg_t *p;
28 | u64 uid;
29 |
30 | data.counter = c;
31 | c++;
32 |
33 | data.pid = bpf_get_current_pid_tgid();
34 | uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
35 | data.uid = uid;
36 |
37 | p = bpf_map_lookup_elem(&my_config, &uid);
38 | // The first argument needs to be a pointer to a map; the following won't be accepted
39 | // p = bpf_map_lookup_elem(&data, &uid);
40 |
41 | // Attempt to dereference a potentially null pointer
42 | if (p != 0) {
43 | char a = p->message[0];
44 | bpf_printk("%d", a);
45 | }
46 |
47 | if (p != 0) {
48 | bpf_probe_read_kernel(&data.message, sizeof(data.message), p->message);
49 | } else {
50 | bpf_probe_read_kernel(&data.message, sizeof(data.message), message);
51 | }
52 |
53 | // Changing this to <= means and c could have value beyond the bounds of the
54 | // global message array
55 | // if (c <= sizeof(message)) {
56 | if (c < sizeof(message)) {
57 | char a = message[c];
58 | bpf_printk("%c", a);
59 | }
60 |
61 | // Changing this to <= means and c could have value beyond the bounds of the
62 | // data.message array
63 | // if (c <= sizeof(data.message)) {
64 | if (c < sizeof(data.message)) {
65 | char a = data.message[c];
66 | bpf_printk("%c", a);
67 | }
68 |
69 | bpf_get_current_comm(&data.command, sizeof(data.command));
70 | bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU, &data, sizeof(data));
71 |
72 | return 0;
73 | }
74 |
75 | SEC("xdp")
76 | int xdp_hello(struct xdp_md *ctx) {
77 | void *data = (void *)(long)ctx->data;
78 | void *data_end = (void *)(long)ctx->data_end;
79 |
80 | // Attempt to read outside the packet
81 | // data_end++;
82 |
83 | // This is a loop that will pass the verifier
84 | // for (int i=0; i < 10; i++) {
85 | // bpf_printk("Looping %d", i);
86 | // }
87 |
88 | // This is a loop that will fail the verifier
89 | // for (int i=0; i < c; i++) {
90 | // bpf_printk("Looping %d", i);
91 | // }
92 |
93 | // Comment out the next two lines and there won't be a return code defined
94 | bpf_printk("%x %x", data, data_end);
95 | return XDP_PASS;
96 | }
97 |
98 | // Removing the license section means the verifier won't let you use
99 | // GPL-licensed helpers
100 | char LICENSE[] SEC("license") = "Dual BSD/GPL";
101 |
--------------------------------------------------------------------------------
/chapter6/hello-verifier.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "hello-verifier.h"
7 | #include "hello-verifier.skel.h"
8 |
9 | static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
10 | {
11 | if (level >= LIBBPF_DEBUG)
12 | return 0;
13 |
14 | return vfprintf(stderr, format, args);
15 | }
16 |
17 | void handle_event(void *ctx, int cpu, void *data, unsigned int data_sz)
18 | {
19 | struct data_t *m = data;
20 |
21 | printf("%-6d %-6d %-4d %-16s %s\n", m->pid, m->uid, m->counter, m->command, m->message);
22 | }
23 |
24 | void lost_event(void *ctx, int cpu, long long unsigned int data_sz)
25 | {
26 | printf("lost event\n");
27 | }
28 |
29 | int main()
30 | {
31 | struct hello_verifier_bpf *skel;
32 | int err;
33 | struct perf_buffer *pb = NULL;
34 |
35 | libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
36 | libbpf_set_print(libbpf_print_fn);
37 |
38 | char log_buf[64 * 1024];
39 | LIBBPF_OPTS(bpf_object_open_opts, opts,
40 | .kernel_log_buf = log_buf,
41 | .kernel_log_size = sizeof(log_buf),
42 | .kernel_log_level = 1,
43 | );
44 |
45 | skel = hello_verifier_bpf__open_opts(&opts);
46 | if (!skel) {
47 | printf("Failed to open BPF object\n");
48 | return 1;
49 | }
50 |
51 | err = hello_verifier_bpf__load(skel);
52 | // Print the verifier log
53 | for (int i=0; i < sizeof(log_buf); i++) {
54 | if (log_buf[i] == 0 && log_buf[i+1] == 0) {
55 | break;
56 | }
57 | printf("%c", log_buf[i]);
58 | }
59 | if (err) {
60 | printf("Failed to load BPF object\n");
61 | hello_verifier_bpf__destroy(skel);
62 | return 1;
63 | }
64 |
65 |
66 | // Configure a message to use only if the UID for the event is 501
67 | uint32_t key = 501;
68 | struct msg_t msg;
69 | const char *m = "hello Liz";
70 | strncpy((char *)&msg.message, m, sizeof(msg.message));
71 | bpf_map__update_elem(skel->maps.my_config, &key, sizeof(key), &msg, sizeof(msg), 0);
72 |
73 | // Attach the progam to the event
74 | err = hello_verifier_bpf__attach(skel);
75 | if (err) {
76 | fprintf(stderr, "Failed to attach BPF skeleton: %d\n", err);
77 | hello_verifier_bpf__destroy(skel);
78 | return 1;
79 | }
80 |
81 | pb = perf_buffer__new(bpf_map__fd(skel->maps.output), 8, handle_event, lost_event, NULL, NULL);
82 | if (!pb) {
83 | err = -1;
84 | fprintf(stderr, "Failed to create ring buffer\n");
85 | hello_verifier_bpf__destroy(skel);
86 | return 1;
87 | }
88 |
89 | while (true) {
90 | err = perf_buffer__poll(pb, 100 /* timeout, ms */);
91 | // Ctrl-C gives -EINTR
92 | if (err == -EINTR) {
93 | err = 0;
94 | break;
95 | }
96 | if (err < 0) {
97 | printf("Error polling perf buffer: %d\n", err);
98 | break;
99 | }
100 | }
101 |
102 | perf_buffer__free(pb);
103 | hello_verifier_bpf__destroy(skel);
104 | return -err;
105 | }
106 |
--------------------------------------------------------------------------------
/chapter6/hello-verifier.h:
--------------------------------------------------------------------------------
1 | struct data_t {
2 | int pid;
3 | int uid;
4 | int counter;
5 | char command[16];
6 | char message[12];
7 | };
8 |
9 | struct msg_t {
10 | char message[12];
11 | };
12 |
--------------------------------------------------------------------------------
/chapter6/kprobe_exec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizrice/learning-ebpf/ab40e0327deecc2daa09b96b2e38598f1bcd4bdd/chapter6/kprobe_exec.png
--------------------------------------------------------------------------------
/chapter7/Makefile:
--------------------------------------------------------------------------------
1 | TARGET = hello
2 | ARCH = $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/')
3 |
4 | BPF_OBJ = ${TARGET:=.bpf.o}
5 |
6 | USER_C = ${TARGET:=.c}
7 | USER_SKEL = ${TARGET:=.skel.h}
8 |
9 | COMMON_H = ${TARGET:=.h}
10 |
11 | app: $(TARGET) $(BPF_OBJ)
12 | .PHONY: app
13 |
14 | $(TARGET): $(USER_C) $(USER_SKEL) $(COMMON_H)
15 | gcc -Wall -o $(TARGET) $(USER_C) -L../libbpf/src -l:libbpf.a -lelf -lz
16 |
17 | %.bpf.o: %.bpf.c vmlinux.h $(COMMON_H)
18 | clang \
19 | -target bpf \
20 | -D __BPF_TRACING__ \
21 | -D __TARGET_ARCH_$(ARCH) \
22 | -Wall \
23 | -O2 -g -o $@ -c $<
24 | llvm-strip -g $@
25 |
26 | $(USER_SKEL): $(BPF_OBJ)
27 | bpftool gen skeleton $< > $@
28 |
29 | vmlinux.h:
30 | bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
31 |
32 | clean:
33 | - rm $(BPF_OBJ)
34 | - rm $(TARGET)
35 | - rm $(USER_SKEL)
36 |
37 |
--------------------------------------------------------------------------------
/chapter7/README.md:
--------------------------------------------------------------------------------
1 | # Chapter 7 - eBPF Program and Attachment Types
2 |
3 | Chapter 7 examples not currently running on ARM processor
4 |
5 | * fentry at do_execve() (fentry requires kernel version 6.0 on ARM)
6 | * kprobe at do_execve() - libbpf: prog 'kprobe_do_execve': failed to create kprobe 'do_execve+0x0' perf event: No such file or directory
--------------------------------------------------------------------------------
/chapter7/hello.bpf.c:
--------------------------------------------------------------------------------
1 | #include "vmlinux.h"
2 | #include
3 | #include
4 | #include
5 | #include "hello.h"
6 |
7 | const char kprobe_sys_msg[16] = "sys_execve";
8 | const char kprobe_msg[16] = "do_execve";
9 | const char fentry_msg[16] = "fentry_execve";
10 | const char tp_msg[16] = "tp_execve";
11 | const char tp_btf_exec_msg[16] = "tp_btf_exec";
12 | const char raw_tp_exec_msg[16] = "raw_tp_exec";
13 | struct {
14 | __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
15 | __uint(key_size, sizeof(u32));
16 | __uint(value_size, sizeof(u32));
17 | } output SEC(".maps");
18 |
19 | struct {
20 | __uint(type, BPF_MAP_TYPE_HASH);
21 | __uint(max_entries, 10240);
22 | __type(key, u32);
23 | __type(value, struct msg_t);
24 | } my_config SEC(".maps");
25 |
26 | SEC("ksyscall/execve")
27 | int BPF_KPROBE_SYSCALL(kprobe_sys_execve, const char *pathname)
28 | {
29 | struct data_t data = {};
30 |
31 | bpf_probe_read_kernel(&data.message, sizeof(data.message), kprobe_sys_msg);
32 | bpf_printk("%s: pathname: %s", kprobe_sys_msg, pathname);
33 |
34 | data.pid = bpf_get_current_pid_tgid() >> 32;
35 | data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
36 | bpf_get_current_comm(&data.command, sizeof(data.command));
37 | bpf_probe_read_user(&data.path, sizeof(data.path), pathname);
38 |
39 | bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU, &data, sizeof(data));
40 | return 0;
41 | }
42 |
43 | // TODO!! Work on ARM
44 | #ifndef __TARGET_ARCH_arm64
45 | SEC("kprobe/do_execve")
46 | int BPF_KPROBE(kprobe_do_execve, struct filename *filename) {
47 | struct data_t data = {};
48 |
49 | bpf_probe_read_kernel(&data.message, sizeof(data.message), kprobe_msg);
50 |
51 | data.pid = bpf_get_current_pid_tgid() >> 32;
52 | data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
53 |
54 | bpf_get_current_comm(&data.command, sizeof(data.command));
55 | const char *name = BPF_CORE_READ(filename, name);
56 | bpf_probe_read_kernel(&data.path, sizeof(data.path), name);
57 |
58 | bpf_printk("%s: filename->name: %s", kprobe_msg, name);
59 |
60 | bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU, &data, sizeof(data));
61 | return 0;
62 | }
63 | #endif
64 |
65 | // This should really look at the kernel version, because fentry is supported on
66 | // ARM from Linux 6.0 onwards
67 | #ifndef __TARGET_ARCH_arm64
68 | SEC("fentry/do_execve")
69 | int BPF_PROG(fentry_execve, struct filename *filename) {
70 | struct data_t data = {};
71 |
72 | bpf_probe_read_kernel(&data.message, sizeof(data.message), fentry_msg);
73 |
74 | data.pid = bpf_get_current_pid_tgid() >> 32;
75 | data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
76 |
77 | bpf_get_current_comm(&data.command, sizeof(data.command));
78 | const char *name = BPF_CORE_READ(filename, name);
79 | bpf_probe_read_kernel(&data.path, sizeof(data.path), name);
80 |
81 | bpf_printk("%s: filename->name: %s", fentry_msg, name);
82 |
83 | bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU, &data, sizeof(data));
84 | return 0;
85 | }
86 | #endif
87 |
88 | // name: sys_enter_execve
89 | // ID: 622
90 | // format:
91 | // field:unsigned short common_type; offset:0; size:2; signed:0;
92 | // field:unsigned char common_flags; offset:2; size:1; signed:0;
93 | // field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
94 | // field:int common_pid; offset:4; size:4; signed:1;
95 |
96 | // field:int __syscall_nr; offset:8; size:4; signed:1;
97 | // field:const char * filename; offset:16; size:8; signed:0;
98 | // field:const char *const * argv; offset:24; size:8; signed:0;
99 | // field:const char *const * envp; offset:32; size:8; signed:0;
100 | struct my_syscalls_enter_execve {
101 | unsigned short common_type;
102 | unsigned char common_flags;
103 | unsigned char common_preempt_count;
104 | int common_pid;
105 |
106 | long syscall_nr;
107 | void *filename_ptr;
108 | long argv_ptr;
109 | long envp_ptr;
110 | };
111 |
112 | SEC("tp/syscalls/sys_enter_execve")
113 | int tp_sys_enter_execve(struct my_syscalls_enter_execve *ctx) {
114 | struct data_t data = {};
115 |
116 | bpf_probe_read_kernel(&data.message, sizeof(data.message), tp_msg);
117 | bpf_printk("%s: ctx->filename_ptr: %s", tp_msg, ctx->filename_ptr);
118 |
119 | data.pid = bpf_get_current_pid_tgid() >> 32;
120 | data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
121 |
122 | bpf_get_current_comm(&data.command, sizeof(data.command));
123 | bpf_probe_read_user(&data.path, sizeof(data.path), ctx->filename_ptr);
124 |
125 | bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU, &data, sizeof(data));
126 | return 0;
127 | }
128 |
129 |
130 | // trace_event_raw_sched_process_exec is defined in vmlinux.h
131 | SEC("tp_btf/sched_process_exec")
132 | int tp_btf_exec(struct trace_event_raw_sched_process_exec *ctx)
133 | {
134 | struct data_t data = {};
135 | // pid_t pid = ctx->pid;
136 |
137 | bpf_probe_read_kernel(&data.message, sizeof(data.message), tp_btf_exec_msg);
138 |
139 | data.pid = bpf_get_current_pid_tgid() >> 32;
140 | data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
141 |
142 | // TODO!! Resolve issues accessing data that isn't aligned to an 8-byte boundary
143 | // bpf_printk("%s %d\n", tp_btf_exec_msg, pid);
144 | // bpf_probe_read_kernel_str(&data.command, sizeof(data.command), ctx->pid);
145 |
146 | bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU, &data, sizeof(data));
147 | return 0;
148 | }
149 |
150 | SEC("raw_tp/sched_process_exec")
151 | int raw_tp_exec(struct bpf_raw_tracepoint_args *ctx)
152 | {
153 | struct data_t data = {};
154 |
155 | bpf_probe_read_kernel(&data.message, sizeof(data.message), raw_tp_exec_msg);
156 |
157 | data.pid = bpf_get_current_pid_tgid() >> 32;
158 | data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
159 |
160 | bpf_perf_event_output(ctx, &output, BPF_F_CURRENT_CPU, &data, sizeof(data));
161 | return 0;
162 | }
163 |
164 | char LICENSE[] SEC("license") = "Dual BSD/GPL";
165 |
--------------------------------------------------------------------------------
/chapter7/hello.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "hello.h"
7 | #include "hello.skel.h"
8 |
9 | static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
10 | {
11 | if (level >= LIBBPF_DEBUG)
12 | return 0;
13 |
14 | return vfprintf(stderr, format, args);
15 | }
16 |
17 | void handle_event(void *ctx, int cpu, void *data, unsigned int data_sz)
18 | {
19 | struct data_t *m = data;
20 |
21 | printf("%-6d %-6d %-16s %-16s %s\n", m->pid, m->uid, m->command, m->path, m->message);
22 | }
23 |
24 | void lost_event(void *ctx, int cpu, long long unsigned int data_sz)
25 | {
26 | printf("lost event\n");
27 | }
28 |
29 | int main()
30 | {
31 | struct hello_bpf *skel;
32 | // struct bpf_object_open_opts *o;
33 | int err;
34 | struct perf_buffer *pb = NULL;
35 |
36 | libbpf_set_print(libbpf_print_fn);
37 |
38 | char log_buf[64 * 1024];
39 | LIBBPF_OPTS(bpf_object_open_opts, opts,
40 | .kernel_log_buf = log_buf,
41 | .kernel_log_size = sizeof(log_buf),
42 | .kernel_log_level = 1,
43 | );
44 |
45 | skel = hello_bpf__open_opts(&opts);
46 | if (!skel) {
47 | printf("Failed to open BPF object\n");
48 | return 1;
49 | }
50 |
51 | err = hello_bpf__load(skel);
52 | // Print the verifier log
53 | for (int i=0; i < sizeof(log_buf); i++) {
54 | if (log_buf[i] == 0 && log_buf[i+1] == 0) {
55 | break;
56 | }
57 | printf("%c", log_buf[i]);
58 | }
59 |
60 | if (err) {
61 | printf("Failed to load BPF object\n");
62 | hello_bpf__destroy(skel);
63 | return 1;
64 | }
65 |
66 | // Attach the progams to the events
67 | err = hello_bpf__attach(skel);
68 | if (err) {
69 | fprintf(stderr, "Failed to attach BPF skeleton: %d\n", err);
70 | hello_bpf__destroy(skel);
71 | return 1;
72 | }
73 |
74 | pb = perf_buffer__new(bpf_map__fd(skel->maps.output), 8, handle_event, lost_event, NULL, NULL);
75 | if (!pb) {
76 | err = -1;
77 | fprintf(stderr, "Failed to create ring buffer\n");
78 | hello_bpf__destroy(skel);
79 | return 1;
80 | }
81 |
82 | while (true) {
83 | err = perf_buffer__poll(pb, 100 /* timeout, ms */);
84 | // Ctrl-C gives -EINTR
85 | if (err == -EINTR) {
86 | err = 0;
87 | break;
88 | }
89 | if (err < 0) {
90 | printf("Error polling perf buffer: %d\n", err);
91 | break;
92 | }
93 | }
94 |
95 | perf_buffer__free(pb);
96 | hello_bpf__destroy(skel);
97 | return -err;
98 | }
99 |
--------------------------------------------------------------------------------
/chapter7/hello.h:
--------------------------------------------------------------------------------
1 | struct data_t {
2 | int pid;
3 | int uid;
4 | char command[16];
5 | char message[12];
6 | char path[16];
7 | };
8 |
9 | struct msg_t {
10 | char message[12];
11 | };
12 |
--------------------------------------------------------------------------------
/chapter8/Makefile:
--------------------------------------------------------------------------------
1 | TARGET = hello
2 | ARCH = $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/')
3 |
4 | BPF_OBJ = ${TARGET:=.bpf.o}
5 |
6 | all: $(TARGET) $(BPF_OBJ)
7 | .PHONY: all
8 | .PHONY: $(TARGET)
9 |
10 | $(TARGET): $(BPF_OBJ)
11 | bpftool net detach xdp dev lo
12 | rm -f /sys/fs/bpf/$(TARGET)
13 | bpftool prog load $(BPF_OBJ) /sys/fs/bpf/$(TARGET)
14 | bpftool net attach xdp pinned /sys/fs/bpf/$(TARGET) dev lo
15 |
16 | $(BPF_OBJ): %.o: %.c vmlinux.h
17 | clang \
18 | -target bpf \
19 | -D __BPF_TRACING__ \
20 | -I/usr/include/$(shell uname -m)-linux-gnu \
21 | -Wall \
22 | -O2 -o $@ -c $<
23 |
24 | vmlinux.h:
25 | bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
26 |
27 | clean:
28 | - bpftool net detach xdp dev lo
29 | - bpftool net detach xdp dev docker0
30 | - rm -f /sys/fs/bpf/$(TARGET)
31 | - rm $(BPF_OBJ)
32 | - tc filter delete dev docker0 parent ffff:
33 |
34 |
--------------------------------------------------------------------------------
/chapter8/README.md:
--------------------------------------------------------------------------------
1 | # Chapter 8 - Networking examples
2 |
3 | ## Simple XDP ping example
4 |
5 | This isn't in the book, but I've used it a few times to show how easy it is to
6 | use eBPF to change networking behavior.
7 |
8 | The `ping.bpf.c` file holds a simple XDP program that checks to see if a packet
9 | is a ping (ICMP) request, and drops it if so. The `ping.py` Python program loads
10 | this program and attaches it to the localhost interface.
11 |
12 | * In one terminal, start `ping`. You should see it showing a ping response being
13 | received every second, each one with an incrementing `icmp_seq`
14 | number
15 | * While `ping` is still running in the first terminal, run `ping.py` in a second
16 | terminal. You will see trace showing that a ping packet is detected every
17 | second, but if you look in the first terminal you'll see that the responses
18 | are no longer being received. That's because the requests are being dropped by
19 | the XDP program, so no response is ever generated.
20 | * Modify `png.bpf.c` to return XDP_PASS instead of XDP_DROP when ping packets
21 | are detected. Restart `ping.py` in the second terminal, and you'll see ping
22 | responses start being received again in the first terminal.
23 |
24 | ## XDP
25 |
26 | The basic XDP example code discussed in the book is in hello.bpf.c. Running `make` builds this and also
27 | uses bpftool to load the program and attach it to the `lo` interface. You can
28 | just ping localhost to see it in action.
29 |
30 | For the remaining examples in this chapter you'll need Docker installed in the
31 | Lima VM (or whatever Linux machine you're using for the examples from this
32 | book). Follow the [instructions from the Docker
33 | documentation](https://docs.docker.com/engine/install/ubuntu/#installation-methods)
34 | to install the `docker-ce` package. For the Lima VM, the Ubuntu version codename
35 | is `jammy`.
36 |
37 | ## Socket filter, TC and tcpconnect examples
38 |
39 | The `network.py` file loads a variety of other networking related examples. It uses eBPF code
40 | from the file `network.bpf.c` and attaches them to the docker0 device. You can
41 | comment different examples in and out to see what effect they have.
42 |
43 | If you make changes to the eBPF code, don't forget to re-run `network.py` which
44 | will compile, load and attach the eBPF programs to events.
45 |
46 | ### Traffic-generating container
47 |
48 | Run a container that you can use as a source for generating ping and curl
49 | requests that arrive at the host on the docker0 interface.
50 |
51 | ```
52 | docker run -d --rm --name pingbox -h pingbox --env TERM=xterm-color nginxdemos/hello:plain-text
53 | ```
54 |
55 | The network looks like this
56 |
57 | ```
58 | Host pingbox
59 | 172.17.0.1 <------------veth connection------------>172.17.0.2
60 | docker0 eth0
61 |
62 | --------Traffic flowing in this direction--->
63 | is EGRESS for docker0 on host
64 |
65 | <---Traffic flowing in this direction--------
66 | is INGRESS for docker0 on host
67 | ```
68 |
69 | Run `bpftool prog tracelog` to see the tracing output generated by the example
70 | eBPF programs.
71 |
72 | ### Generate tcpconnect events and socket filter data
73 |
74 | The tcpconnect() eBPF program is a kprobe attached to `tcp_v4_connect` that just
75 | generates a trace message for the tcpconnect event.
76 |
77 | The socket_filter() eBPF program examines packets received at the socket and
78 | traces out if they are TCP or ICMP packets. It also sends a copy of TCP packets
79 | to user space. The Python code in network.py will displays that received data.
80 |
81 | You can trigger these programs by sending TCP traffic, for example:
82 |
83 | `docker exec -it pingbox curl example.com`
84 |
85 | ### XDP example
86 |
87 | xdp() is another example eBPF program that drops ICMP requests. Try commenting this
88 | in and out to see that if it's enabled, these packets won't make it as far as TC.
89 |
90 | ### Have XDP and TC intercept ping requests
91 |
92 | `docker exec -it pingbox ping 172.17.0.1`
93 |
94 | The behaviour at TC depends which function you comment in within network.py
95 |
96 | If you modify the XDP program to drop ping packets, they won't reach the TC
97 | ingress event.
98 |
99 | ## Load balancer example
100 |
101 | See my [lb-from-scratch](https://github.com/lizrice/lb-from-scratch) repo.
102 |
--------------------------------------------------------------------------------
/chapter8/hello.bpf.c:
--------------------------------------------------------------------------------
1 | #include "vmlinux.h"
2 | #include
3 | #include
4 | #include "packet.h"
5 |
6 | SEC("xdp")
7 | int ping(struct xdp_md *ctx) {
8 | long protocol = lookup_protocol(ctx);
9 | if (protocol == 1) // ICMP
10 | {
11 | bpf_printk("Hello ping");
12 | // return XDP_DROP;
13 | }
14 | return XDP_PASS;
15 | }
16 |
17 | char LICENSE[] SEC("license") = "Dual BSD/GPL";
18 |
--------------------------------------------------------------------------------
/chapter8/network.bpf.c:
--------------------------------------------------------------------------------
1 | #include "network.h"
2 |
3 | #include
4 | #include
5 |
6 | int tcpconnect(void *ctx) {
7 | bpf_trace_printk("[tcpconnect]\n");
8 | return 0;
9 | }
10 |
11 | int socket_filter(struct __sk_buff *skb) {
12 | unsigned char *cursor = 0;
13 |
14 | struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet));
15 | // Look for IP packets
16 | if (ethernet->type != 0x0800) {
17 | return 0;
18 | }
19 |
20 | struct ip_t *ip = cursor_advance(cursor, sizeof(*ip));
21 |
22 | if (ip->nextp == 0x01) {
23 | bpf_trace_printk("[socket_filter] ICMP request for %x\n", ip->dst);
24 | }
25 |
26 | if (ip->nextp == 0x06) {
27 | bpf_trace_printk("[socket_filter] TCP packet for %x\n", ip->dst);
28 | // Send TCP packets to userspace
29 | return -1;
30 | }
31 |
32 | return 0;
33 | }
34 |
35 | int xdp(struct xdp_md *ctx) {
36 | void *data = (void *)(long)ctx->data;
37 | void *data_end = (void *)(long)ctx->data_end;
38 |
39 | if (is_icmp_ping_request(data, data_end)) {
40 | struct iphdr *iph = data + sizeof(struct ethhdr);
41 | struct icmphdr *icmp = data + sizeof(struct ethhdr) + sizeof(struct iphdr);
42 | bpf_trace_printk("[xdp] ICMP request for %x type %x DROPPED\n", iph->daddr,
43 | icmp->type);
44 | return XDP_DROP;
45 | }
46 |
47 | return XDP_PASS;
48 | }
49 |
50 | int tc_drop_ping(struct __sk_buff *skb) {
51 | bpf_trace_printk("[tc] ingress got packet\n");
52 |
53 | void *data = (void *)(long)skb->data;
54 | void *data_end = (void *)(long)skb->data_end;
55 |
56 | if (is_icmp_ping_request(data, data_end)) {
57 | struct iphdr *iph = data + sizeof(struct ethhdr);
58 | struct icmphdr *icmp = data + sizeof(struct ethhdr) + sizeof(struct iphdr);
59 | bpf_trace_printk("[tc] ICMP request for %x type %x\n", iph->daddr,
60 | icmp->type);
61 | return TC_ACT_SHOT;
62 | }
63 | return TC_ACT_OK;
64 | }
65 |
66 | int tc_drop(struct __sk_buff *skb) {
67 | bpf_trace_printk("[tc] dropping packet");
68 | return TC_ACT_SHOT;
69 | }
70 |
71 | int tc_pingpong(struct __sk_buff *skb) {
72 | bpf_trace_printk("[tc] ingress got packet");
73 |
74 | void *data = (void *)(long)skb->data;
75 | void *data_end = (void *)(long)skb->data_end;
76 |
77 | if (!is_icmp_ping_request(data, data_end)) {
78 | bpf_trace_printk("[tc] ingress not a ping request");
79 | return TC_ACT_OK;
80 | }
81 |
82 | struct iphdr *iph = data + sizeof(struct ethhdr);
83 | struct icmphdr *icmp = data + sizeof(struct ethhdr) + sizeof(struct iphdr);
84 | bpf_trace_printk("[tc] ICMP request for %x type %x\n", iph->daddr,
85 | icmp->type);
86 |
87 | swap_mac_addresses(skb);
88 | swap_ip_addresses(skb);
89 |
90 | // Change the type of the ICMP packet to 0 (ICMP Echo Reply) (was 8 for ICMP
91 | // Echo request)
92 | update_icmp_type(skb, 8, 0);
93 |
94 | // Redirecting the modified skb on the same interface to be transmitted
95 | // again
96 | bpf_clone_redirect(skb, skb->ifindex, 0);
97 |
98 | // We modified the packet and redirected a clone of it, so drop this one
99 | return TC_ACT_SHOT;
100 | }
--------------------------------------------------------------------------------
/chapter8/network.h:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | static __always_inline unsigned short is_icmp_ping_request(void *data,
6 | void *data_end) {
7 | struct ethhdr *eth = data;
8 | if (data + sizeof(struct ethhdr) > data_end)
9 | return 0;
10 |
11 | if (bpf_ntohs(eth->h_proto) != ETH_P_IP)
12 | return 0;
13 |
14 | struct iphdr *iph = data + sizeof(struct ethhdr);
15 | if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) > data_end)
16 | return 0;
17 |
18 | if (iph->protocol != 0x01)
19 | // We're only interested in ICMP packets
20 | return 0;
21 |
22 | struct icmphdr *icmp = data + sizeof(struct ethhdr) + sizeof(struct iphdr);
23 | if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) +
24 | sizeof(struct icmphdr) >
25 | data_end)
26 | return 0;
27 |
28 | return (icmp->type == 8);
29 | }
30 |
31 | static __always_inline unsigned short ping_request_to_reply(void *data,
32 | void *data_end) {
33 | struct ethhdr *eth = data;
34 | if (data + sizeof(struct ethhdr) > data_end)
35 | return 0;
36 |
37 | if (bpf_ntohs(eth->h_proto) != ETH_P_IP)
38 | return 0;
39 |
40 | struct iphdr *iph = data + sizeof(struct ethhdr);
41 | if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) > data_end)
42 | return 0;
43 |
44 | if (iph->protocol != 0x01)
45 | // We're only interested in ICMP packets
46 | return 0;
47 |
48 | struct icmphdr *icmp = data + sizeof(struct ethhdr) + sizeof(struct iphdr);
49 | if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) +
50 | sizeof(struct icmphdr) >
51 | data_end)
52 | return 0;
53 |
54 | return (icmp->type == 8);
55 | }
56 |
57 | static __always_inline void swap_mac_addresses(struct __sk_buff *skb) {
58 | unsigned char src_mac[6];
59 | unsigned char dst_mac[6];
60 | bpf_skb_load_bytes(skb, offsetof(struct ethhdr, h_source), src_mac, 6);
61 | bpf_skb_load_bytes(skb, offsetof(struct ethhdr, h_dest), dst_mac, 6);
62 | bpf_skb_store_bytes(skb, offsetof(struct ethhdr, h_source), dst_mac, 6, 0);
63 | bpf_skb_store_bytes(skb, offsetof(struct ethhdr, h_dest), src_mac, 6, 0);
64 | }
65 |
66 | #define IP_SRC_OFF (ETH_HLEN + offsetof(struct iphdr, saddr))
67 | #define IP_DST_OFF (ETH_HLEN + offsetof(struct iphdr, daddr))
68 |
69 | static __always_inline void swap_ip_addresses(struct __sk_buff *skb) {
70 | unsigned char src_ip[4];
71 | unsigned char dst_ip[4];
72 | bpf_skb_load_bytes(skb, IP_SRC_OFF, src_ip, 4);
73 | bpf_skb_load_bytes(skb, IP_DST_OFF, dst_ip, 4);
74 | bpf_skb_store_bytes(skb, IP_SRC_OFF, dst_ip, 4, 0);
75 | bpf_skb_store_bytes(skb, IP_DST_OFF, src_ip, 4, 0);
76 | }
77 |
78 | #define ICMP_CSUM_OFF \
79 | (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct icmphdr, checksum))
80 | #define ICMP_TYPE_OFF \
81 | (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct icmphdr, type))
82 |
83 | static __always_inline void update_icmp_type(struct __sk_buff *skb,
84 | unsigned char old_type,
85 | unsigned char new_type) {
86 | bpf_l4_csum_replace(skb, ICMP_CSUM_OFF, old_type, new_type, 2);
87 | bpf_skb_store_bytes(skb, ICMP_TYPE_OFF, &new_type, sizeof(new_type), 0);
88 | }
--------------------------------------------------------------------------------
/chapter8/network.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | from bcc import BPF
3 | import socket
4 | import os
5 | from time import sleep
6 | from pyroute2 import IPRoute
7 |
8 | b = BPF(src_file="network.bpf.c")
9 |
10 | # A kprobe when a TCP connection is started
11 | # You can trigger this by, for example, making a curl request
12 | b.attach_kprobe(event="tcp_v4_connect", fn_name="tcpconnect")
13 |
14 | # Attaching to the host's docker0 interface
15 | # Packets sent from the host to container are egress
16 | # Packets sent from containers to the host are ingress
17 | interface = "docker0"
18 |
19 | # Socket filter. There is a loop at the end of this program that reads data from
20 | # the socket file descriptor
21 | f = b.load_func("socket_filter", BPF.SOCKET_FILTER)
22 | BPF.attach_raw_socket(f, interface)
23 |
24 | fd = f.sock
25 | sock = socket.fromfd(fd, socket.PF_PACKET, socket.SOCK_RAW, socket.IPPROTO_IP)
26 | sock.setblocking(True)
27 |
28 | # XDP will be the first program hit when a packet is received ingress
29 | fx = b.load_func("xdp", BPF.XDP)
30 | # If the xdp() program drops ping packets, they won't get as far as TC ingress
31 | BPF.attach_xdp(interface, fx, 0)
32 |
33 | ipr = IPRoute()
34 | links = ipr.link_lookup(ifname=interface)
35 | idx = links[0]
36 |
37 | try:
38 | ipr.tc("add", "ingress", idx, "ffff:")
39 | except:
40 | print("qdisc ingress already exists")
41 |
42 |
43 | # TC. Choose one program: drop all packets, just drop ping requests, or respond
44 | # to ping requests
45 | # fi = b.load_func("tc_drop", BPF.SCHED_CLS)
46 | # fi = b.load_func("tc_drop_ping", BPF.SCHED_CLS)
47 | fi = b.load_func("tc_pingpong", BPF.SCHED_CLS)
48 |
49 | ipr.tc("add-filter", "bpf", idx, ":1", fd=fi.fd,
50 | name=fi.name, parent="ffff:", action="ok", classid=1, da=True)
51 |
52 | # Remove with sudo tc qdisc del dev docker0 parent ffff:
53 | # (or make clean)
54 |
55 | # Read data from socket filter
56 | while True:
57 | packet_str = os.read(fd, 4096)
58 | print("Userspace got data: %x", packet_str)
59 |
60 |
61 |
--------------------------------------------------------------------------------
/chapter8/packet.h:
--------------------------------------------------------------------------------
1 | #define TC_ACT_UNSPEC (-1)
2 | #define TC_ACT_OK 0
3 | #define TC_ACT_RECLASSIFY 1
4 | #define TC_ACT_SHOT 2
5 |
6 | #define ETH_P_IP 0x0800
7 | #define ICMP_PING 8
8 |
9 | #define ETH_ALEN 6
10 | #define ETH_HLEN 14
11 |
12 | #define IP_SRC_OFF (ETH_HLEN + offsetof(struct iphdr, saddr))
13 | #define IP_DST_OFF (ETH_HLEN + offsetof(struct iphdr, daddr))
14 |
15 | #define ICMP_CSUM_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct icmphdr, checksum))
16 | #define ICMP_TYPE_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct icmphdr, type))
17 | #define ICMP_CSUM_SIZE sizeof(__u16)
18 |
19 | // Returns the protocol byte for an IP packet, 0 for anything else
20 | // static __always_inline unsigned char lookup_protocol(struct xdp_md *ctx)
21 | unsigned char lookup_protocol(struct xdp_md *ctx)
22 | {
23 | unsigned char protocol = 0;
24 |
25 | void *data = (void *)(long)ctx->data;
26 | void *data_end = (void *)(long)ctx->data_end;
27 | struct ethhdr *eth = data;
28 | if (data + sizeof(struct ethhdr) > data_end)
29 | return 0;
30 |
31 | // Check that it's an IP packet
32 | if (bpf_ntohs(eth->h_proto) == ETH_P_IP)
33 | {
34 | // Return the protocol of this packet
35 | // 1 = ICMP
36 | // 6 = TCP
37 | // 17 = UDP
38 | struct iphdr *iph = data + sizeof(struct ethhdr);
39 | if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) <= data_end)
40 | protocol = iph->protocol;
41 | }
42 | return protocol;
43 | }
--------------------------------------------------------------------------------
/chapter8/ping.bpf.c:
--------------------------------------------------------------------------------
1 | #include "network.h"
2 | #include
3 | #include
4 |
5 | int xdp(struct xdp_md *ctx) {
6 | void *data = (void *)(long)ctx->data;
7 | void *data_end = (void *)(long)ctx->data_end;
8 |
9 | if (is_icmp_ping_request(data, data_end)) {
10 | bpf_trace_printk("Got ping packet");
11 | return XDP_DROP;
12 | }
13 |
14 | return XDP_PASS;
15 | }
--------------------------------------------------------------------------------
/chapter8/ping.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | from bcc import BPF
3 | import socket
4 | import os
5 | from time import sleep
6 | import sys
7 |
8 | b = BPF(src_file="ping.bpf.c")
9 | interface = "lo"
10 |
11 | # XDP will be the first program hit when a packet is received ingress
12 | fx = b.load_func("xdp", BPF.XDP)
13 | BPF.attach_xdp(interface, fx, 0)
14 |
15 | try:
16 | b.trace_print()
17 | except KeyboardInterrupt:
18 | sys.exit(0)
19 |
20 |
--------------------------------------------------------------------------------
/learning-ebpf-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lizrice/learning-ebpf/ab40e0327deecc2daa09b96b2e38598f1bcd4bdd/learning-ebpf-cover.png
--------------------------------------------------------------------------------
/learning-ebpf.yaml:
--------------------------------------------------------------------------------
1 | # This example requires Lima v0.8.0 or later
2 | images:
3 | - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img"
4 | arch: "x86_64"
5 | - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-arm64.img"
6 | arch: "aarch64"
7 |
8 | cpus: 4
9 | memory: "10GiB"
10 |
11 | mounts:
12 | - location: "~"
13 | writable: true
14 | - location: "/tmp/lima"
15 | writable: true
16 | provision:
17 | - mode: system
18 | script: |
19 | apt-get update
20 | apt-get install -y apt-transport-https ca-certificates curl clang llvm jq
21 | apt-get install -y libelf-dev libpcap-dev libbfd-dev binutils-dev build-essential make
22 | apt-get install -y linux-tools-common linux-tools-$(uname -r)
23 | apt-get install -y bpfcc-tools
24 | apt-get install -y python3-pip
25 |
--------------------------------------------------------------------------------