diff --git a/docs/book/start/logging-packets.md b/docs/book/start/logging-packets.md index 7c7c0540..8c837b5d 100644 --- a/docs/book/start/logging-packets.md +++ b/docs/book/start/logging-packets.md @@ -1,104 +1,29 @@ # Logging Packets -In the previous chapter, our XDP application ran until Ctrl-C was hit and permitted all the traffic. -Each time a packet was received, the BPF program created a log entry. -Let's expand this program to log the traffic that is being permitted in the user-space application instead of the BPF program. +In the previous chapter, our XDP application ran until Ctrl-C was hit and permitted +all the traffic. Each time a packet was received, the BPF program created a logged +the string "received a packet" for each packet received. In this chapter we're +going to show how to parse packets. + +While we could go all out and extract data all the way up to L7, we'll constrain +our firewall to L3, and to make things easier, IPv4 only. !!! example "Source Code" Full code for the example in this chapter is available [here](https://github.com/aya-rs/book/tree/main/examples/xdp-log) -## Getting Data to User-Space - -### Sharing Data - -To get data from kernel-space to user-space we use an eBPF map. There are numerous types of maps to chose from, but in this example we'll be using a PerfEventArray. - -While we could go all out and extract data all the way up to L7, we'll constrain our firewall to L3, and to make things easier, IPv4 only. -The data structure that we'll need to send information to user-space will need to hold an IPv4 address and an action for Permit/Deny, we'll encode both as a `u32`. - -```rust linenums="1" title="xdp-log-common/src/lib.rs" ---8<-- "examples/xdp-log/xdp-log-common/src/lib.rs" -``` - -1. We implement the `aya::Pod` trait for our struct since it is Plain Old Data as can be safely converted to a byte-slice and back. - -!!! tip "Alignment, padding and verifier errors" - - At program load time, the eBPF verifier checks that all the memory used is - properly initialized. This can be a problem if - to ensure alignment - the - compiler inserts padding bytes between fields in your types. - - **Example:** - - ```rust - #[repr(C)] - struct SourceInfo { - source_port: u16, - source_ip: u32, - } +## Using Network Types - let port = ...; - let ip = ...; - let si = SourceInfo { source_port: port, source_ip: ip }; - ``` - - In the example above, the compiler will insert two extra bytes between the - struct fields `source_port` and `source_ip` to make sure that `source_ip` is - correctly aligned to a 4 bytes address (assuming `mem::align_of::() == - 4`). Since padding bytes are typically not initialized by the compiler, - this will result in the infamous `invalid indirect read from stack` verifier - error. - - To avoid the error, you can either manually ensure that all the fields in - your types are correctly aligned (eg by explicitly adding padding or by - making field types larger to enforce alignment) or use `#[repr(packed)]`. - Since the latter comes with its own footguns and can perform less - efficiently, explicitly adding padding or tweaking alignment is recommended. - - **Solution ensuring alignment using larger types:** - - ```rust - #[repr(C)] - struct SourceInfo { - source_port: u32, - source_ip: u32, - } - - let port = ...; - let ip = ...; - let si = SourceInfo { source_port: port, source_ip: ip }; - ``` - - **Solution with explicit padding:** - - ```rust - #[repr(C)] - struct SourceInfo { - source_port: u16, - padding: u16, - source_ip: u32, - } - - let port = ...; - let ip = ...; - let si = SourceInfo { source_port: port, padding: 0, source_ip: ip }; - ``` - -## Writing Data - -### Using Kernel Network Types - -To get useful data to add to our maps, we first need some useful data structures +To get useful data to log, we first need some useful data structures to to populate with data from the `XdpContext`. We want to log the Source IP Address of incoming traffic, so we'll need to: 1. Read the Ethernet Header to determine if this is an IPv4 Packet 1. Read the Source IP Address from the IPv4 Header -The two structs in the kernel for this are `ethhdr` from `uapi/linux/if_ether.h` -and `iphdr` from `uapi/linux/ip.h`. Rust equivalents of those structures (`EthHdr` -and `Ipv4Hdr`) are provided by the [network-types crate](https://crates.io/crates/network-types). +The [network-types crate](https://crates.io/crates/network-types) provides +networking structs, including `EthHdr` and `Ipv4Hdr` which we are ging to use +in our program. Let's add it to our eBPF crate by adding a dependency on `network-types` in our `xdp-log-ebpf/Cargo.toml`: @@ -109,7 +34,7 @@ Let's add it to our eBPF crate by adding a dependency on `network-types` in our --8<-- "examples/xdp-log/xdp-log-ebpf/Cargo.toml" ``` -### Getting Packet Data From The Context And Into the Map +## Getting Packet Data From The Context And Into the Map The `XdpContext` contains two fields, `data` and `data_end`. `data` is a pointer to the start of the data in kernel memory and `data_end`, a @@ -132,7 +57,9 @@ To do this efficiently we'll add a dependency on `memoffset = "0.8"` in our `mya As there is limited stack space, it's more memory efficient to use the `offset_of!` macro to read a single field from a struct, rather than reading the whole struct and accessing the field by name. -Once we have our IPv4 source address, we can create a `PacketLog` struct and output this to our `PerfEventArray` +Once we have our IPv4 source address, we can log it with macros coming from aya-log. Those macros are +sending the log message and all the logged data to the user-space program, which then does the +actual logging. The resulting code looks like this: @@ -143,17 +70,13 @@ The resulting code looks like this: 1. Create our map 2. Here's `ptr_at`, which gives ensures packet access is bounds checked 3. Using `ptr_at` to read our ethernet header -4. Outputting the event to the `PerfEventArray` +4. Logging the IP address and port Don't forget to rebuild your eBPF program! -## Reading Data - -In order to read from the `AsyncPerfEventArray`, we have to call `AsyncPerfEventArray::open()` for each online CPU, then we have to poll the file descriptor for events. -While this is do-able using `PerfEventArray` and `mio` or `epoll`, the code is much less easy to follow. Instead, we'll use `tokio`, which was added to our template for us. +## Receiving the logs -We'll need to add a dependency on `bytes = "1"` to `xdp-log/Cargo.toml` since this will make it easier -to deal with the chunks of bytes yielded by the `AsyncPerfEventArray`. +In order to receive the logs in the user-space program, we have to call `BpfLogger::init()`. Here's the code: @@ -161,14 +84,8 @@ Here's the code: --8<-- "examples/xdp-log/xdp-log/src/main.rs" ``` -1. Name was not defined in `xdp-log-ebpf/src/main.rs`, so use `xdp` -2. Define our map -3. Call `open()` for each online CPU -4. Spawn a `tokio::task` -5. Create buffers -6. Read events in to buffers -7. Use `read_unaligned` to read our data into a `PacketLog`. -8. Log the event to the console. +1. Initialize `BpfLogger` to receive and process log messages and data from eBPF. +2. Name was not defined in `xdp-log-ebpf/src/main.rs`, so use `xdp` ## Running the program diff --git a/examples/xdp-log/xdp-log-ebpf/src/main.rs b/examples/xdp-log/xdp-log-ebpf/src/main.rs index 95590251..52eba03b 100644 --- a/examples/xdp-log/xdp-log-ebpf/src/main.rs +++ b/examples/xdp-log/xdp-log-ebpf/src/main.rs @@ -7,12 +7,11 @@ use aya_log_ebpf::info; use core::mem; use network_types::{ eth::{EthHdr, EtherType}, - ip::{Ipv4Hdr, IpProto}, + ip::{IpProto, Ipv4Hdr}, tcp::TcpHdr, udp::UdpHdr, }; - #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { unsafe { core::hint::unreachable_unchecked() } @@ -40,7 +39,7 @@ unsafe fn ptr_at(ctx: &XdpContext, offset: usize) -> Result<*const T, ()> { } fn try_xdp_firewall(ctx: XdpContext) -> Result { - let ethhdr: *const EthHdr = unsafe { ptr_at(&ctx, 0)? }; + let ethhdr: *const EthHdr = unsafe { ptr_at(&ctx, 0)? }; // (3) match unsafe { (*ethhdr).ether_type } { EtherType::Ipv4 => {} _ => return Ok(xdp_action::XDP_PASS), @@ -63,6 +62,7 @@ fn try_xdp_firewall(ctx: XdpContext) -> Result { _ => return Err(()), }; + // (4) info!( &ctx, "SRC IP: {:ipv4}, SRC PORT: {}", source_addr, source_port diff --git a/examples/xdp-log/xdp-log/src/main.rs b/examples/xdp-log/xdp-log/src/main.rs index 295ca702..378c54a4 100644 --- a/examples/xdp-log/xdp-log/src/main.rs +++ b/examples/xdp-log/xdp-log/src/main.rs @@ -30,11 +30,12 @@ async fn main() -> Result<(), anyhow::Error> { let mut bpf = Bpf::load(include_bytes_aligned!( "../../target/bpfel-unknown-none/release/xdp-log" ))?; + // (1) if let Err(e) = BpfLogger::init(&mut bpf) { // This can happen if you remove all log statements from your eBPF program. warn!("failed to initialize eBPF logger: {}", e); } - // (1) + // (2) let program: &mut Xdp = bpf.program_mut("xdp").unwrap().try_into()?; program.load()?; program.attach(&opt.iface, XdpFlags::default())