├── .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 | Learning eBPF cover features an image of an Early
  9 | Bumblebee 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 | --------------------------------------------------------------------------------