|
| 1 | +# Getting data to user-space |
| 2 | + |
| 3 | +In previous chapters we were logging packets with aya-log. However, what if we |
| 4 | +need to send additional data about the packet or any other type of information |
| 5 | +for the user-space program to utilize? In this chapter, we will explore how to |
| 6 | +leverage perf buffers and define data structures in the *-common* crate to |
| 7 | +transfer data from eBPF program to user-space applications. By doing so, |
| 8 | +user-space programs can access and utilize the transferred data effectively. |
| 9 | + |
| 10 | +!!! example "Source code" |
| 11 | + |
| 12 | + Full code for the example in this chapter is available |
| 13 | + [here](https://github.com/aya-rs/book/tree/main/examples/xdp-perfbuf-custom-data) |
| 14 | + |
| 15 | +## Sharing data |
| 16 | + |
| 17 | +In this chapter, we will be sending data from the kernel space to the user space |
| 18 | +by writing it into a struct and outputting it to user space. We can achieve this |
| 19 | +by using an eBPF map. There are different types of maps available, but in this |
| 20 | +case, we will use `PerfEventArray`. |
| 21 | + |
| 22 | +`PerfEventArray` is a collection of per-CPU circular buffers that enable the |
| 23 | +kernel to emit events (defined as custom structs) to user space. Each CPU has |
| 24 | +its own buffer, and the eBPF program emits an event to the buffer of the CPU |
| 25 | +it's currently running on. The events are out of order, meaning that they arrive |
| 26 | +in the user-space in a different order than they were created and sent from the |
| 27 | +eBPF program. |
| 28 | + |
| 29 | +To gather events from all CPUs, a user space program needs to spawn a thread for |
| 30 | +each CPU to poll for the events and then iterate over them. |
| 31 | + |
| 32 | +The data structure we'll be using needs to hold an IPv4 address and a port. |
| 33 | + |
| 34 | +```rust linenums="1" title="xdp-perfbuf-custom-data-common/src/lib.rs" |
| 35 | +--8<-- "examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-common/src/lib.rs" |
| 36 | +``` |
| 37 | + |
| 38 | +1. We implement the `aya::Pod` trait for our struct since it is Plain Old Data |
| 39 | +as can be safely converted to a byte slice and back. |
| 40 | + |
| 41 | +!!! tip "Alignment, padding and verifier errors" |
| 42 | + |
| 43 | + At program load time, the eBPF verifier checks that all the memory used is |
| 44 | + properly initialized. This can be a problem if - to ensure alignment - the |
| 45 | + compiler inserts padding bytes between fields in your types. |
| 46 | + |
| 47 | + **Example:** |
| 48 | + |
| 49 | + ```rust |
| 50 | + #[repr(C)] |
| 51 | + struct SourceInfo { |
| 52 | + source_port: u16, |
| 53 | + source_ip: u32, |
| 54 | + } |
| 55 | + |
| 56 | + let source_port = ...; |
| 57 | + let source_ip = ...; |
| 58 | + let si = SourceInfo { source_port, source_ip }; |
| 59 | + ``` |
| 60 | + |
| 61 | + In the example above, the compiler will insert two extra bytes between the |
| 62 | + struct fields `source_port` and `source_ip` to make sure that `source_ip` is |
| 63 | + correctly aligned to a 4-byte address (assuming `mem::align_of::<u32>() == |
| 64 | + 4`). Since padding bytes are typically not initialized by the compiler, |
| 65 | + this will result in the infamous `invalid indirect read from stack` verifier |
| 66 | + error. |
| 67 | + |
| 68 | + To avoid the error, you can either manually ensure that all the fields in |
| 69 | + your types are correctly aligned (e.g. by explicitly adding padding or by |
| 70 | + making field types larger to enforce alignment) or use `#[repr(packed)]`. |
| 71 | + Since the latter comes with its own foot-guns and can perform less |
| 72 | + efficiently, explicitly adding padding or tweaking alignment is recommended. |
| 73 | + |
| 74 | + **Solution ensuring alignment using larger types:** |
| 75 | + |
| 76 | + ```rust |
| 77 | + #[repr(C)] |
| 78 | + struct SourceInfo { |
| 79 | + source_port: u32, |
| 80 | + source_ip: u32, |
| 81 | + } |
| 82 | + |
| 83 | + let source_port = ...; |
| 84 | + let source_ip = ...; |
| 85 | + let si = SourceInfo { source_port, source_ip }; |
| 86 | + ``` |
| 87 | + |
| 88 | + **Solution with explicit padding:** |
| 89 | + |
| 90 | + ```rust |
| 91 | + #[repr(C)] |
| 92 | + struct SourceInfo { |
| 93 | + source_port: u16, |
| 94 | + padding: u16, |
| 95 | + source_ip: u32, |
| 96 | + } |
| 97 | + |
| 98 | + let source_port = ...; |
| 99 | + let source_ip = ...; |
| 100 | + let si = SourceInfo { source_port, padding: 0, source_ip }; |
| 101 | + ``` |
| 102 | + |
| 103 | +## Extracting packet data from the context and into the map |
| 104 | + |
| 105 | +The eBPF program code in this section is similar to the one in the previous |
| 106 | +chapters. It extracts the source IP address and port information from packet |
| 107 | +headers. |
| 108 | + |
| 109 | +The difference is that after obtaining the data from the headers, we create a |
| 110 | +`PacketLog` struct and output it to our `PerfEventArray` instead of logging data |
| 111 | +directly. |
| 112 | + |
| 113 | +The resulting code looks like this: |
| 114 | + |
| 115 | +```rust linenums="1" title="xdp-perfbuf-custom-data-ebpf/src/main.rs" |
| 116 | +--8<-- "examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data-ebpf/src/main.rs" |
| 117 | +``` |
| 118 | + |
| 119 | +1. Create our map. |
| 120 | +2. Output the event to the map. |
| 121 | + |
| 122 | +## Reading data |
| 123 | + |
| 124 | +To read from the perf event array in user space, we need to use one of the |
| 125 | +following types: |
| 126 | + |
| 127 | +* `AsyncPerfEventArray` which is intended to be used in |
| 128 | + [async Rust](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html). |
| 129 | +* `PerfEventArray` for sync Rust. |
| 130 | + |
| 131 | +By default, [our project template](https://github.com/aya-rs/aya-template) is |
| 132 | +written in async Rust and uses [Tokio runtime](https://tokio.rs/), so we are |
| 133 | +going to use `AsyncPerfEventArray` in this chapter. |
| 134 | + |
| 135 | +In order to read from the `AsyncPerfEventArray`, we have to call |
| 136 | +`AsyncPerfEventArray::open()` for each online CPU, then we have to poll the file |
| 137 | +descriptor for events. |
| 138 | + |
| 139 | +We will also need to add a dependency on `bytes` to `xdp-log/Cargo.toml` since |
| 140 | +it will make handling the chunks of bytes yielded by the `AsyncPerfEventArray` |
| 141 | +easier. |
| 142 | + |
| 143 | +Here's the code: |
| 144 | + |
| 145 | +```rust linenums="1" title="xdp-perfbuf-custom-data/src/main.rs" |
| 146 | +--8<-- "examples/xdp-perfbuf-custom-data/xdp-perfbuf-custom-data/src/main.rs" |
| 147 | +``` |
| 148 | + |
| 149 | +1. Define our map. |
| 150 | +2. Call `open()` for each online CPU. |
| 151 | +3. Spawn a `tokio::task`. |
| 152 | +4. Create buffers. |
| 153 | +5. Read events in to buffers. |
| 154 | +6. Use `read_unaligned` to read the event data into a `PacketLog`. |
| 155 | +7. Log the packet data. |
| 156 | + |
| 157 | +## Running the program |
| 158 | + |
| 159 | +As before, you can overwrite the interface by by providing the interface name as |
| 160 | +a parameter, for example, `RUST_LOG=info cargo xtask run -- --iface wlp2s0`. |
| 161 | + |
| 162 | +```console |
| 163 | +$ RUST_LOG=info cargo xtask run |
| 164 | +[2023-01-25T08:57:41Z INFO xdp_perfbuf_custom_data] SRC IP: 60.235.240.157, SRC_PORT: 443 |
| 165 | +[2023-01-25T08:57:41Z INFO xdp_perfbuf_custom_data] SRC IP: 98.21.76.76, SRC_PORT: 443 |
| 166 | +[2023-01-25T08:57:41Z INFO xdp_perfbuf_custom_data] SRC IP: 95.194.217.172, SRC_PORT: 443 |
| 167 | +[2023-01-25T08:57:41Z INFO xdp_perfbuf_custom_data] SRC IP: 95.194.217.172, SRC_PORT: 443 |
| 168 | +[2023-01-25T08:57:41Z INFO xdp_perfbuf_custom_data] SRC IP: 95.10.251.142, SRC_PORT: 443 |
| 169 | +``` |
0 commit comments