diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 6004c207..c9d84991 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -213,7 +213,7 @@ impl Bpf { /// Returns a reference to the map with the given name. /// /// The returned type is mostly opaque. In order to do anything useful with it you need to - /// convert it to a [concrete map type](crate::maps). + /// convert it to a [typed map](crate::maps). /// /// For more details and examples on maps and their usage, see the [maps module /// documentation][crate::maps]. @@ -238,7 +238,7 @@ impl Bpf { /// Returns a mutable reference to the map with the given name. /// /// The returned type is mostly opaque. In order to do anything useful with it you need to - /// convert it to a [concrete map type](crate::maps). + /// convert it to a [typed map](crate::maps). /// /// For more details and examples on maps and their usage, see the [maps module /// documentation][crate::maps]. @@ -361,6 +361,7 @@ impl Bpf { } } +/// The error type returned by [`Bpf::load_file`] and [`Bpf::load`]. #[derive(Debug, Error)] pub enum BpfError { #[error("error loading {path}")] diff --git a/aya/src/lib.rs b/aya/src/lib.rs index 74a2e03c..4483cc16 100644 --- a/aya/src/lib.rs +++ b/aya/src/lib.rs @@ -10,28 +10,25 @@ //! crate to execute syscalls. With BTF support and when linked with musl, it offers a true //! [compile once, run everywhere //! solution](https://facebookmicrosites.github.io/bpf/blog/2020/02/19/bpf-portability-and-co-re.html), -//! where one self-contained binary can be deployed on many linux distributions +//! where a single self-contained binary can be deployed on many linux distributions //! and kernel versions. //! //! Some of the major features provided include: //! -//! * Support for the BPF Type Format (BTF), which is transparently enabled when +//! * Support for the **BPF Type Format** (BTF), which is transparently enabled when //! supported by the target kernel. This allows eBPF programs compiled against //! one kernel version to run on different kernel versions without the need to //! recompile. -//! * Support for global data maps, which allows eBPF programs to make use of global data and -//! variables. This is especially useful when the eBPF code itself is written in Rust, and makes -//! use of byte literals and other initializers that get place in global data sections. -//! * Support for function calls, so eBPF programs can call other functions and are not -//! forced to inline everything. -//! * Async support with both [tokio](https://docs.rs/tokio) and [async-std](https://docs.rs/async-std). +//! * Support for function call relocation and global data maps, which +//! allows eBPF programs to make **function calls** and use **global variables +//! and initializers**. +//! * **Async support** with both [tokio] and [async-std]. //! * Easy to deploy and fast to build: aya doesn't require a kernel build or //! compiled headers, and not even a C toolchain; a release build completes in a matter //! of seconds. //! -//! # Minimum kernel version -//! -//! Aya currently supports kernels version 5.4 (latest LTS) and newer. +//! [tokio]: https://docs.rs/tokio +//! [async-std]: https://docs.rs/async-std #![deny(clippy::all)] #[macro_use] diff --git a/aya/src/maps/array/array.rs b/aya/src/maps/array/array.rs index 8113d8c9..31897bf1 100644 --- a/aya/src/maps/array/array.rs +++ b/aya/src/maps/array/array.rs @@ -16,6 +16,18 @@ use crate::{ /// /// The size of the array is defined on the eBPF side using the `bpf_map_def::max_entries` field. /// All the entries are zero-initialized when the map is created. +/// +/// # Example +/// ```no_run +/// # let bpf = aya::Bpf::load(&[], None)?; +/// use aya::maps::Array; +/// use std::convert::TryFrom; +/// +/// let mut array = Array::try_from(bpf.map_mut("ARRAY")?)?; +/// array.set(1, 42, 0)?; +/// assert_eq!(array.get(&1, 0)?, 42); +/// # Ok::<(), aya::BpfError>(()) +/// ``` pub struct Array, V: Pod> { inner: T, _v: PhantomData, @@ -98,18 +110,6 @@ impl + DerefMut, V: Pod> Array { /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] /// if `bpf_map_update_elem` fails. - /// - /// # Example - /// ```no_run - /// # let bpf = aya::Bpf::load(&[], None)?; - /// use aya::maps::Array; - /// use std::convert::TryFrom; - /// - /// let mut array = Array::try_from(bpf.map_mut("ARRAY")?)?; - /// array.set(1, 42, 0)?; - /// assert_eq!(array.get(&1, 0)?, 42); - /// # Ok::<(), aya::BpfError>(()) - /// ``` pub fn set(&mut self, index: u32, value: V, flags: u64) -> Result<(), MapError> { let fd = self.inner.fd_or_err()?; self.check_bounds(index)?; diff --git a/aya/src/maps/hash_map/hash_map.rs b/aya/src/maps/hash_map/hash_map.rs index 47e4e897..9ae6ae50 100644 --- a/aya/src/maps/hash_map/hash_map.rs +++ b/aya/src/maps/hash_map/hash_map.rs @@ -13,8 +13,6 @@ use crate::{ /// A hash map that can be shared between eBPF programs and user space. /// -/// It is required that both keys and values implement the [`Pod`] trait. -/// /// # Example /// /// ```no_run @@ -24,8 +22,10 @@ use crate::{ /// /// let mut redirect_ports = HashMap::try_from(bpf.map_mut("REDIRECT_PORTS")?)?; /// -/// redirect_ports.insert(80, 8080, 0 /* flags */); -/// redirect_ports.insert(443, 8443, 0 /* flags */); +/// // redirect port 80 to 8080 +/// redirect_ports.insert(80, 8080, 0); +/// // redirect port 443 to 8443 +/// redirect_ports.insert(443, 8443, 0); /// # Ok::<(), aya::BpfError>(()) /// ``` #[doc(alias = "BPF_MAP_TYPE_HASH")] diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index 1930979d..4b8e6586 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -1,34 +1,37 @@ -//! Data structures used to exchange data with eBPF programs. +//! Data structures used to setup and share data with eBPF programs. //! -//! The eBPF platform provides data structures - maps in eBPF speak - that can be used by eBPF -//! programs and user-space to exchange data. When you call -//! [`Bpf::load_file`](crate::Bpf::load_file) or [`Bpf::load`](crate::Bpf::load), all the maps -//! defined in the eBPF code get initialized and can then be accessed using -//! [`Bpf::map`](crate::Bpf::map) and [`Bpf::map_mut`](crate::Bpf::map_mut). +//! The eBPF platform provides data structures - maps in eBPF speak - that are +//! used to setup and share data with eBPF programs. When you call +//! [`Bpf::load_file`](crate::Bpf::load_file) or +//! [`Bpf::load`](crate::Bpf::load), all the maps defined in the eBPF code get +//! initialized and can then be accessed using [`Bpf::map`](crate::Bpf::map) and +//! [`Bpf::map_mut`](crate::Bpf::map_mut). //! -//! # Concrete map types +//! # Typed maps //! -//! The eBPF platform provides many map types each supporting different operations. +//! The eBPF API includes many map types each supporting different operations. //! [`Bpf::map`](crate::Bpf::map) and [`Bpf::map_mut`](crate::Bpf::map_mut) always return the //! opaque [`MapRef`] and [`MapRefMut`] types respectively. Those two types can be converted to -//! *concrete map types* using the [`TryFrom`](std::convert::TryFrom) trait. For example: +//! *typed maps* using the [`TryFrom`](std::convert::TryFrom) trait. For example: //! //! ```no_run -//! # let bpf = aya::Bpf::load(&[], None)?; -//! use aya::maps::HashMap; -//! use std::convert::TryFrom; +//! # let mut bpf = aya::Bpf::load(&[], None)?; +//! use std::convert::{TryFrom, TryInto}; +//! use aya::maps::SockMap; +//! use aya::programs::SkMsg; //! -//! const CONFIG_KEY_NUM_RETRIES: u8 = 1; -//! -//! // HashMap::try_from() converts MapRefMut to HashMap. It will fail if CONFIG is not an eBPF -//! // hash map. -//! let mut hm = HashMap::try_from(bpf.map_mut("CONFIG")?)?; -//! hm.insert(CONFIG_KEY_NUM_RETRIES, 3, 0 /* flags */); +//! let intercept_egress = SockMap::try_from(bpf.map_mut("INTERCEPT_EGRESS")?)?; +//! let prog: &mut SkMsg = bpf.program_mut("intercept_egress_packet")?.try_into()?; +//! prog.load()?; +//! prog.attach(&intercept_egress)?; //! # Ok::<(), aya::BpfError>(()) //! ``` //! -//! The code above uses `HashMap`, but all the concrete map types implement the -//! `TryFrom` trait. +//! # Maps and `Pod` values +//! +//! Many map operations copy data from kernel space to user space and vice +//! versa. Because of that, all map values must be plain old data and therefore +//! implement the [Pod] trait. use std::{ convert::TryFrom, ffi::CString, io, marker::PhantomData, mem, ops::Deref, os::unix::io::RawFd, ptr, @@ -118,6 +121,8 @@ pub enum MapError { } /// A generic handle to a BPF map. +/// +/// You should never need to use this unless you're implementing a new map type. #[derive(Debug)] pub struct Map { pub(crate) obj: obj::Map, diff --git a/aya/src/maps/sock/sock_map.rs b/aya/src/maps/sock/sock_map.rs index 6883475f..e7477dc4 100644 --- a/aya/src/maps/sock/sock_map.rs +++ b/aya/src/maps/sock/sock_map.rs @@ -28,12 +28,12 @@ use crate::{ /// # let mut bpf = aya::Bpf::load(&[], None)?; /// use std::convert::{TryFrom, TryInto}; /// use aya::maps::SockMap; -/// use aya::programs::SkMsg; +/// use aya::programs::SkSkb; /// -/// let intercept_egress = SockMap::try_from(bpf.map_mut("INTERCEPT_EGRESS")?)?; -/// let prog: &mut SkMsg = bpf.program_mut("intercept_egress_packet")?.try_into()?; +/// let intercept_ingress = SockMap::try_from(bpf.map_mut("INTERCEPT_INGRESS")?)?; +/// let prog: &mut SkSkb = bpf.program_mut("intercept_ingress_packet")?.try_into()?; /// prog.load()?; -/// prog.attach(&intercept_egress)?; +/// prog.attach(&intercept_ingress)?; /// # Ok::<(), aya::BpfError>(()) /// ``` pub struct SockMap> { diff --git a/aya/src/obj/btf/btf.rs b/aya/src/obj/btf/btf.rs index 02e6cbde..6dcda54d 100644 --- a/aya/src/obj/btf/btf.rs +++ b/aya/src/obj/btf/btf.rs @@ -18,6 +18,7 @@ use crate::{ pub(crate) const MAX_RESOLVE_DEPTH: u8 = 32; pub(crate) const MAX_SPEC_LEN: usize = 64; +/// The error type returned when `BTF` operations fail. #[derive(Error, Debug)] pub enum BtfError { #[error("error parsing {path}")] diff --git a/aya/src/programs/cgroup_skb.rs b/aya/src/programs/cgroup_skb.rs index ba8a48b2..e7d7078f 100644 --- a/aya/src/programs/cgroup_skb.rs +++ b/aya/src/programs/cgroup_skb.rs @@ -14,9 +14,36 @@ use super::FdLink; /// A program used to inspect or filter network activity for a given cgroup. /// /// [`CgroupSkb`] programs can be used to inspect or filter network activity -/// generated on all the sockets belonging to a given [cgroup]. +/// generated on all the sockets belonging to a given [cgroup]. They can be +/// attached to both _ingress_ and _egress_. /// /// [cgroup]: https://man7.org/linux/man-pages/man7/cgroups.7.html +/// +/// # Example +/// +/// ```no_run +/// # #[derive(thiserror::Error, Debug)] +/// # enum Error { +/// # #[error(transparent)] +/// # IO(#[from] std::io::Error), +/// # #[error(transparent)] +/// # Map(#[from] aya::maps::MapError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Bpf(#[from] aya::BpfError) +/// # } +/// # let mut bpf = aya::Bpf::load(&[], None)?; +/// use std::fs::File; +/// use std::convert::TryInto; +/// use aya::programs::{CgroupSkb, CgroupSkbAttachType}; +/// +/// let file = File::open("/sys/fs/cgroup/unified")?; +/// let egress: &mut CgroupSkb = bpf.program_mut("egress_filter")?.try_into()?; +/// egress.load()?; +/// egress.attach(file, CgroupSkbAttachType::Egress)?; +/// # Ok::<(), Error>(()) +/// ``` #[derive(Debug)] pub struct CgroupSkb { pub(crate) data: ProgramData, @@ -47,32 +74,6 @@ impl CgroupSkb { } /// Attaches the program to the given cgroup. - /// - /// # Example - /// - /// ```no_run - /// # #[derive(thiserror::Error, Debug)] - /// # enum Error { - /// # #[error(transparent)] - /// # IO(#[from] std::io::Error), - /// # #[error(transparent)] - /// # Map(#[from] aya::maps::MapError), - /// # #[error(transparent)] - /// # Program(#[from] aya::programs::ProgramError), - /// # #[error(transparent)] - /// # Bpf(#[from] aya::BpfError) - /// # } - /// # let mut bpf = aya::Bpf::load(&[], None)?; - /// use std::fs::File; - /// use std::convert::TryInto; - /// use aya::programs::{CgroupSkb, CgroupSkbAttachType}; - /// - /// let file = File::open("/sys/fs/cgroup/unified")?; - /// let egress: &mut CgroupSkb = bpf.program_mut("egress_filter")?.try_into()?; - /// egress.load()?; - /// egress.attach(file, CgroupSkbAttachType::Egress)?; - /// # Ok::<(), Error>(()) - /// ``` pub fn attach( &mut self, cgroup: T, @@ -110,8 +111,11 @@ impl CgroupSkb { } } +/// Defines where to attach a [`CgroupSkb`] program. #[derive(Copy, Clone, Debug)] pub enum CgroupSkbAttachType { + /// Attach to ingress. Ingress, + /// Attach to egress. Egress, } diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs index d3719985..38bbd255 100644 --- a/aya/src/programs/kprobe.rs +++ b/aya/src/programs/kprobe.rs @@ -19,6 +19,18 @@ use crate::{ /// /// - `kprobe`: get attached to the *start* of the target functions /// - `kretprobe`: get attached to the *return address* of the target functions +/// +/// # Example +/// +/// ```no_run +/// # let mut bpf = Bpf::load_file("ebpf_programs.o")?; +/// use aya::{Bpf, programs::KProbe}; +/// use std::convert::TryInto; +/// +/// let program: &mut KProbe = bpf.program_mut("intercept_wakeups")?.try_into()?; +/// program.attach("try_to_wake_up", 0, None)?; +/// # Ok::<(), aya::BpfError>(()) +/// ``` #[derive(Debug)] pub struct KProbe { pub(crate) data: ProgramData, @@ -54,20 +66,6 @@ impl KProbe { /// If the program is a `kprobe`, it is attached to the *start* address of the target function. /// Conversely if the program is a `kretprobe`, it is attached to the return address of the /// target function. - /// - /// - /// # Example - /// - /// ```no_run - /// # let mut bpf = Bpf::load_file("ebpf_programs.o")?; - /// use aya::{Bpf, programs::KProbe}; - /// use std::convert::TryInto; - /// - /// let program: &mut KProbe = bpf.program_mut("intercept_wakeups")?.try_into()?; - /// program.attach("try_to_wake_up", 0, None)?; - /// # Ok::<(), aya::BpfError>(()) - /// ``` - /// pub fn attach( &mut self, fn_name: &str, @@ -78,6 +76,7 @@ impl KProbe { } } +/// The type returned when attaching a [`KProbe`] fails. #[derive(Debug, Error)] pub enum KProbeError { #[error("`{filename}`")] diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 9e91ef12..7709b14d 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -1,14 +1,17 @@ //! eBPF program types. //! -//! eBPF programs are loaded inside the kernel and attached to one or more hook points. Whenever -//! the kernel or an application reaches those hook points, the programs are executed. +//! eBPF programs are loaded inside the kernel and attached to one or more hook +//! points. Whenever the hook points are reached, the programs are executed. //! -//! # Loading programs +//! # Loading and attaching programs //! -//! When you call [`Bpf::load_file`] or [`Bpf::load`], all the programs present in the code are -//! parsed and can be retrieved using the [`Bpf::program`] and [`Bpf::program_mut`] methods. In -//! order to load a program, you need to get a handle to it and call the `load()` method, for -//! example: +//! When you call [`Bpf::load_file`] or [`Bpf::load`], all the programs included +//! in the object code are parsed and relocated. Programs are not loaded +//! automatically though, since often you will need to do some application +//! specific setup before you can actually load them. +//! +//! In order to load and attach a program, you need to retrieve it using [`Bpf::program_mut`], +//! then call the `load()` and `attach()` methods, for example: //! //! ```no_run //! use aya::{Bpf, programs::KProbe}; @@ -18,32 +21,21 @@ //! // intercept_wakeups is the name of the program we want to load //! let program: &mut KProbe = bpf.program_mut("intercept_wakeups")?.try_into()?; //! program.load()?; +//! // intercept_wakeups will be called every time try_to_wake_up() is called +//! // inside the kernel +//! program.attach("try_to_wake_up", 0, None)?; //! # Ok::<(), aya::BpfError>(()) //! ``` //! -//! # Attaching programs -//! -//! After being loaded, programs must be attached to their target hook points to be executed. The -//! eBPF platform supports many different program types, with each type providing different -//! attachment options. For example when attaching a [`KProbe`], you must provide the name of the -//! kernel function you want instrument; when loading an [`Xdp`] program, you need to specify the -//! network card name you want to hook into, and so forth. -//! -//! Currently aya supports [`KProbe`], [`UProbe`], [`SocketFilter`], [`TracePoint`] and [`Xdp`] -//! programs. To see how to attach them, see the documentation of the respective `attach()` method. -//! -//! # Interacting with programs -//! -//! eBPF programs are event-driven and execute when the hook points they are attached to are hit. -//! To communicate with user-space, programs use data structures provided by the eBPF platform, -//! which can be found in the [maps] module. +//! The signature of the `attach()` method varies depending on what kind of +//! program you're trying to attach. //! //! [`Bpf::load_file`]: crate::Bpf::load_file //! [`Bpf::load`]: crate::Bpf::load //! [`Bpf::programs`]: crate::Bpf::programs //! [`Bpf::program`]: crate::Bpf::program //! [`Bpf::program_mut`]: crate::Bpf::program_mut -//! [maps]: crate::maps +//! [`maps`]: crate::maps mod cgroup_skb; mod kprobe; mod perf_attach; @@ -80,61 +72,83 @@ use crate::{ obj::{self, Function}, sys::{bpf_load_program, bpf_prog_detach}, }; + +/// Error type returned when working with programs. #[derive(Debug, Error)] pub enum ProgramError { + /// The program could not be found in the object code. #[error("program `{name}` not found")] NotFound { name: String }, + /// The program is already loaded. #[error("the program is already loaded")] AlreadyLoaded, + /// The program is not loaded. #[error("the program is not loaded")] NotLoaded, + /// The program is already detached. #[error("the program was already detached")] AlreadyDetached, + /// The program is not attached. #[error("the program is not attached")] NotAttached, + /// Loading the program failed. #[error("the BPF_PROG_LOAD syscall failed. Verifier output: {verifier_log}")] LoadError { + /// The [`io::Error`] returned by the `BPF_PROG_LOAD` syscall. #[source] io_error: io::Error, + /// The error log produced by the kernel verifier. verifier_log: String, }, + /// A syscall failed. #[error("`{call}` failed")] SyscallError { + /// The name of the syscall which failed. call: String, + /// The [`io::Error`] returned by the syscall. #[source] io_error: io::Error, }, + /// The network interface does not exist. #[error("unknown network interface {name}")] UnknownInterface { name: String }, + /// The program is not of the expected type. #[error("unexpected program type")] UnexpectedProgramType, + /// A map error occurred while loading or attaching a program. #[error(transparent)] MapError(#[from] MapError), + /// An error occurred while working with a [`KProbe`]. #[error(transparent)] KProbeError(#[from] KProbeError), + /// An error occurred while working with an [`UProbe`]. #[error(transparent)] UProbeError(#[from] UProbeError), + /// An error occurred while working with a [`TracePoint`]. #[error(transparent)] TracePointError(#[from] TracePointError), + /// An error occurred while working with a [`SocketFilter`]. #[error(transparent)] SocketFilterError(#[from] SocketFilterError), + /// An error occurred while working with an [`Xdp`] program. #[error(transparent)] XdpError(#[from] XdpError), + /// An error occurred while working with a TC program. #[error(transparent)] TcError(#[from] TcError), } @@ -353,10 +367,15 @@ fn load_program(prog_type: bpf_prog_type, data: &mut ProgramData) -> Result<(), Ok(()) } +/// Detach an attached program. pub trait Link: std::fmt::Debug { fn detach(&mut self) -> Result<(), ProgramError>; } +/// The return type of `program.attach(...)`. +/// +/// [`LinkRef`] implements the [`Link`] trait and can be used to detach a +/// program. #[derive(Debug)] pub struct LinkRef { inner: Rc>, diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs index f7cfc312..ec260899 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -10,9 +10,13 @@ use crate::{ #[derive(Debug, Copy, Clone)] pub enum ProbeKind { + /// Kernel probe KProbe, + /// Kernel return probe KRetProbe, + /// User space probe UProbe, + /// User space return probe URetProbe, } diff --git a/aya/src/programs/sk_msg.rs b/aya/src/programs/sk_msg.rs index ceaee543..0677a94e 100644 --- a/aya/src/programs/sk_msg.rs +++ b/aya/src/programs/sk_msg.rs @@ -5,14 +5,50 @@ use crate::{ sys::bpf_prog_attach, }; -/// A socket buffer program. +/// A program used to intercept messages sent with `sendmsg()`/`sendfile()`. /// -/// Socket buffer programs are attached to [sockmaps], and can be used to -/// redirect or drop packets. See the [`SockMap` documentation] for more info -/// and examples. +/// [`SkMsg`] programs are attached to [socket maps], and can be used inspect, +/// filter and redirect messages sent on sockets. See also [`SockMap`] and +/// [`SockHash`]. /// -/// [sockmaps]: crate::maps::SockMap -/// [`SockMap` documentation]: crate::maps::SockMap +/// # Example +/// +/// ```no_run +/// ##[derive(Debug, thiserror::Error)] +/// # enum Error { +/// # #[error(transparent)] +/// # IO(#[from] std::io::Error), +/// # #[error(transparent)] +/// # Map(#[from] aya::maps::MapError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Bpf(#[from] aya::BpfError) +/// # } +/// # let mut bpf = aya::Bpf::load(&[], None)?; +/// use std::convert::{TryFrom, TryInto}; +/// use std::io::Write; +/// use std::net::TcpStream; +/// use std::os::unix::io::AsRawFd; +/// use aya::maps::SockHash; +/// use aya::programs::SkMsg; +/// +/// let mut intercept_egress = SockHash::try_from(bpf.map_mut("INTERCEPT_EGRESS")?)?; +/// let prog: &mut SkMsg = bpf.program_mut("intercept_egress_packet")?.try_into()?; +/// prog.load()?; +/// prog.attach(&intercept_egress)?; +/// +/// let mut client = TcpStream::connect("127.0.0.1:1234")?; +/// intercept_egress.insert(1234, client.as_raw_fd(), 0)?; +/// +/// // the write will be intercepted +/// client.write_all(b"foo")?; +/// # Ok::<(), Error>(()) +/// ``` +/// +/// [socket maps]: crate::maps::sock +/// [`SockMap`]: crate::maps::SockMap +/// [`SockHash`]: crate::maps::SockHash #[derive(Debug)] pub struct SkMsg { pub(crate) data: ProgramData, diff --git a/aya/src/programs/sk_skb.rs b/aya/src/programs/sk_skb.rs index f89b1394..99bcb7ed 100644 --- a/aya/src/programs/sk_skb.rs +++ b/aya/src/programs/sk_skb.rs @@ -8,21 +8,37 @@ use crate::{ sys::bpf_prog_attach, }; +/// The kind of [`SkSkb`] program. #[derive(Copy, Clone, Debug)] pub enum SkSkbKind { StreamParser, StreamVerdict, } -/// A socket buffer program. +/// A program used to intercept ingress socket buffers. /// -/// Socket buffer programs are attached to [socket maps], and can be used to -/// inspect, redirect or filter packet. See [SockMap] and [SockHash] for more -/// info and examples. +/// [`SkSkb`] programs are attached to [socket maps], and can be used to +/// inspect, redirect or filter incoming packet. See also [`SockMap`] and +/// [`SockHash`]. +/// +/// # Example +/// +/// ```no_run +/// # let mut bpf = aya::Bpf::load(&[], None)?; +/// use std::convert::{TryFrom, TryInto}; +/// use aya::maps::SockMap; +/// use aya::programs::SkSkb; +/// +/// let intercept_ingress = SockMap::try_from(bpf.map_mut("INTERCEPT_INGRESS")?)?; +/// let prog: &mut SkSkb = bpf.program_mut("intercept_ingress_packet")?.try_into()?; +/// prog.load()?; +/// prog.attach(&intercept_ingress)?; +/// # Ok::<(), aya::BpfError>(()) +/// ``` /// /// [socket maps]: crate::maps::sock -/// [SockMap]: crate::maps::SockMap -/// [SockHash]: crate::maps::SockHash +/// [`SockMap`]: crate::maps::SockMap +/// [`SockHash`]: crate::maps::SockHash #[derive(Debug)] pub struct SkSkb { pub(crate) data: ProgramData, diff --git a/aya/src/programs/sock_ops.rs b/aya/src/programs/sock_ops.rs index 9bc505bf..4c785409 100644 --- a/aya/src/programs/sock_ops.rs +++ b/aya/src/programs/sock_ops.rs @@ -6,6 +6,36 @@ use crate::{ sys::bpf_prog_attach, }; +/// A program used to work with sockets. +/// +/// [`SockOps`] programs can access or set socket options, connection +/// parameters, watch connection state changes and more. They are attached to +/// cgroups. +/// +/// # Example +/// +/// ```no_run +/// # #[derive(thiserror::Error, Debug)] +/// # enum Error { +/// # #[error(transparent)] +/// # IO(#[from] std::io::Error), +/// # #[error(transparent)] +/// # Map(#[from] aya::maps::MapError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Bpf(#[from] aya::BpfError) +/// # } +/// # let mut bpf = aya::Bpf::load(&[], None)?; +/// use std::fs::File; +/// use std::convert::TryInto; +/// use aya::programs::SockOps; +/// +/// let file = File::open("/sys/fs/cgroup/unified")?; +/// let prog: &mut SockOps = bpf.program_mut("intercept_active_sockets")?.try_into()?; +/// prog.load()?; +/// prog.attach(file)?; +/// # Ok::<(), Error>(()) #[derive(Debug)] pub struct SockOps { pub(crate) data: ProgramData, @@ -24,7 +54,7 @@ impl SockOps { self.data.name.to_string() } - /// Attaches the program to the given sockmap. + /// Attaches the program to the given cgroup. pub fn attach(&mut self, cgroup: T) -> Result { let prog_fd = self.data.fd_or_err()?; let cgroup_fd = cgroup.as_raw_fd(); diff --git a/aya/src/programs/socket_filter.rs b/aya/src/programs/socket_filter.rs index 724b3f6a..e60760e4 100644 --- a/aya/src/programs/socket_filter.rs +++ b/aya/src/programs/socket_filter.rs @@ -1,5 +1,8 @@ use libc::{setsockopt, SOL_SOCKET}; -use std::{io, mem, os::unix::prelude::RawFd}; +use std::{ + io, mem, + os::unix::prelude::{AsRawFd, RawFd}, +}; use thiserror::Error; use crate::{ @@ -7,8 +10,10 @@ use crate::{ programs::{load_program, Link, LinkRef, ProgramData, ProgramError}, }; +/// The type returned when attaching a [`SocketFilter`] fails. #[derive(Debug, Error)] pub enum SocketFilterError { + /// Setting the `SO_ATTACH_BPF` socket option failed. #[error("setsockopt SO_ATTACH_BPF failed")] SoAttachBpfError { #[source] @@ -16,18 +21,56 @@ pub enum SocketFilterError { }, } +/// A program used to inspect and filter incoming packets on a socket. +/// +/// [`SocketFilter`] programs are attached on sockets and can be used to inspect +/// and filter incoming packets. +/// +/// +/// # Example +/// +/// ```no_run +/// ##[derive(Debug, thiserror::Error)] +/// # enum Error { +/// # #[error(transparent)] +/// # IO(#[from] std::io::Error), +/// # #[error(transparent)] +/// # Map(#[from] aya::maps::MapError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Bpf(#[from] aya::BpfError) +/// # } +/// # let mut bpf = aya::Bpf::load(&[], None)?; +/// use std::convert::{TryFrom, TryInto}; +/// use std::io::Write; +/// use std::net::TcpStream; +/// use std::os::unix::io::AsRawFd; +/// use aya::programs::SocketFilter; +/// +/// let mut client = TcpStream::connect("127.0.0.1:1234")?; +/// let prog: &mut SocketFilter = bpf.program_mut("filter_packets")?.try_into()?; +/// prog.load()?; +/// prog.attach(client.as_raw_fd())?; +/// # Ok::<(), Error>(()) +/// ``` #[derive(Debug)] pub struct SocketFilter { pub(crate) data: ProgramData, } impl SocketFilter { + /// Loads the program inside the kernel. + /// + /// See also [`Program::load`](crate::programs::Program::load). pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_SOCKET_FILTER, &mut self.data) } - pub fn attach(&mut self, socket: RawFd) -> Result { + /// Attaches the filter on the given socket. + pub fn attach(&mut self, socket: T) -> Result { let prog_fd = self.data.fd_or_err()?; + let socket = socket.as_raw_fd(); let ret = unsafe { setsockopt( diff --git a/aya/src/programs/trace_point.rs b/aya/src/programs/trace_point.rs index 5aae6a48..90eee8b3 100644 --- a/aya/src/programs/trace_point.rs +++ b/aya/src/programs/trace_point.rs @@ -5,6 +5,7 @@ use crate::{generated::bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT, sys::perf_event_ use super::{load_program, perf_attach, LinkRef, ProgramData, ProgramError}; +/// The type returned when attaching a [`TracePoint`] fails. #[derive(Debug, Error)] pub enum TracePointError { #[error("`{filename}`")] @@ -15,16 +16,52 @@ pub enum TracePointError { }, } +/// A program that can be attached at a pre-defined kernel trace point. +/// +/// The kernel provides a set of pre-defined trace points that eBPF programs can +/// be attached to. See `/sys/kernel/debug/tracing/events` for a list of which +/// events can be traced. +/// +/// # Example +/// +/// ```no_run +/// ##[derive(Debug, thiserror::Error)] +/// # enum Error { +/// # #[error(transparent)] +/// # IO(#[from] std::io::Error), +/// # #[error(transparent)] +/// # Map(#[from] aya::maps::MapError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Bpf(#[from] aya::BpfError) +/// # } +/// # let mut bpf = aya::Bpf::load(&[], None)?; +/// use std::convert::{TryFrom, TryInto}; +/// use aya::programs::TracePoint; +/// +/// let prog: &mut TracePoint = bpf.program_mut("trace_context_switch")?.try_into()?; +/// prog.load()?; +/// prog.attach("sched", "sched_switch")?; +/// # Ok::<(), Error>(()) +/// ``` #[derive(Debug)] pub struct TracePoint { pub(crate) data: ProgramData, } impl TracePoint { + /// Loads the program inside the kernel. + /// + /// See also [`Program::load`](crate::programs::Program::load). pub fn load(&mut self) -> Result<(), ProgramError> { load_program(BPF_PROG_TYPE_TRACEPOINT, &mut self.data) } + /// Attaches to a given trace point. + /// + /// For a list of the available event categories and names, see + /// `/sys/kernel/debug/tracing/events`. pub fn attach(&mut self, category: &str, name: &str) -> Result { let id = read_sys_fs_trace_point_id(category, name)?; let fd = perf_event_open_trace_point(id).map_err(|(_code, io_error)| { diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index 9b2019ff..1468f273 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -127,17 +127,21 @@ impl UProbe { } } +/// The type returned when attaching an [`UProbe`] fails. #[derive(Debug, Error)] pub enum UProbeError { + /// There was an error parsing `/etc/ld.so.cache`. #[error("error reading `{}` file", LD_SO_CACHE_FILE)] InvalidLdSoCache { #[source] io_error: Arc, }, + /// The target program could not be found. #[error("could not resolve uprobe target `{path}`")] InvalidTarget { path: PathBuf }, + /// There was an error resolving the target symbol. #[error("error resolving symbol")] SymbolError { symbol: String, @@ -145,6 +149,7 @@ pub enum UProbeError { error: Box, }, + /// There was an error accessing `filename`. #[error("`{filename}`")] FileError { filename: String, diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs index 131b3efa..898a0368 100644 --- a/aya/src/programs/xdp.rs +++ b/aya/src/programs/xdp.rs @@ -12,6 +12,7 @@ use crate::{ sys::{bpf_link_create, kernel_version, netlink_set_xdp_fd}, }; +/// The type returned when attaching an [`Xdp`] program fails on kernels `< 5.9`. #[derive(Debug, Error)] pub enum XdpError { #[error("netlink error while attaching XDP program")] @@ -22,12 +23,18 @@ pub enum XdpError { } bitflags! { + /// Flags passed to [`Xdp::attach()`]. #[derive(Default)] pub struct XdpFlags: u32 { + /// Skb mode. const SKB_MODE = XDP_FLAGS_SKB_MODE; + /// Driver mode. const DRV_MODE = XDP_FLAGS_DRV_MODE; + /// Hardware mode. const HW_MODE = XDP_FLAGS_HW_MODE; + /// Replace a previously attached XDP program. const REPLACE = XDP_FLAGS_REPLACE; + /// Only attach if there isn't another XDP program already attached. const UPDATE_IF_NOEXIST = XDP_FLAGS_UPDATE_IF_NOEXIST; } } @@ -38,6 +45,18 @@ bitflags! { /// processing, where they can apply custom packet processing logic. When supported by the /// underlying network driver, XDP programs can execute directly on network cards, greatly /// reducing CPU load. +/// +/// # Example +/// +/// ```no_run +/// # let mut bpf = Bpf::load_file("ebpf_programs.o")?; +/// use aya::{Bpf, programs::{Xdp, XdpFlags}}; +/// use std::convert::TryInto; +/// +/// let program: &mut Xdp = bpf.program_mut("intercept_packets")?.try_into()?; +/// program.attach("eth0", XdpFlags::default())?; +/// # Ok::<(), aya::BpfError>(()) +/// ``` #[derive(Debug)] pub struct Xdp { pub(crate) data: ProgramData, @@ -56,8 +75,6 @@ impl Xdp { self.data.name.to_string() } - /// Attaches the program. - /// /// Attaches the program to the given `interface`. /// /// # Errors @@ -69,18 +86,6 @@ impl Xdp { /// kernels `>= 5.9.0`, and instead /// [`XdpError::NetlinkError`] is returned for older /// kernels. - /// - /// # Example - /// - /// ```no_run - /// # let mut bpf = Bpf::load_file("ebpf_programs.o")?; - /// use aya::{Bpf, programs::{Xdp, XdpFlags}}; - /// use std::convert::TryInto; - /// - /// let program: &mut Xdp = bpf.program_mut("intercept_packets")?.try_into()?; - /// program.attach("eth0", XdpFlags::default())?; - /// # Ok::<(), aya::BpfError>(()) - /// ``` pub fn attach(&mut self, interface: &str, flags: XdpFlags) -> Result { let prog_fd = self.data.fd_or_err()?; diff --git a/aya/src/util.rs b/aya/src/util.rs index f200b5f2..dc4082dc 100644 --- a/aya/src/util.rs +++ b/aya/src/util.rs @@ -1,16 +1,14 @@ //! Utility functions. use std::{ collections::BTreeMap, - fs::{self, File}, ffi::CString, - os::raw::c_char, + fs::{self, File}, io::{self, BufReader}, + os::raw::c_char, str::FromStr, }; -use crate::{ - generated::{TC_H_MAJ_MASK, TC_H_MIN_MASK}, -}; +use crate::generated::{TC_H_MAJ_MASK, TC_H_MIN_MASK}; use libc::if_nametoindex; @@ -19,7 +17,7 @@ use io::BufRead; const ONLINE_CPUS: &str = "/sys/devices/system/cpu/online"; pub(crate) const POSSIBLE_CPUS: &str = "/sys/devices/system/cpu/possible"; -/// Returns the numeric IDs of the available CPUs. +/// Returns the numeric IDs of the CPUs currently online. pub fn online_cpus() -> Result, io::Error> { let data = fs::read_to_string(ONLINE_CPUS)?; parse_cpu_ranges(data.trim()).map_err(|_| { @@ -30,6 +28,9 @@ pub fn online_cpus() -> Result, io::Error> { }) } +/// Get the number of possible cpus. +/// +/// See `/sys/devices/system/cpu/possible`. pub fn nr_cpus() -> Result { Ok(possible_cpus()?.len()) } @@ -38,6 +39,9 @@ pub(crate) fn tc_handler_make(major: u32, minor: u32) -> u32 { (major & TC_H_MAJ_MASK) | (minor & TC_H_MIN_MASK) } +/// Get the list of possible cpus. +/// +/// See `/sys/devices/system/cpu/possible`. pub(crate) fn possible_cpus() -> Result, io::Error> { let data = fs::read_to_string(POSSIBLE_CPUS)?; parse_cpu_ranges(data.trim()).map_err(|_| { @@ -75,20 +79,19 @@ pub unsafe fn ifindex_from_ifname(if_name: &str) -> Result { let c_if_name: *const c_char = c_str_if_name.as_ptr() as *const c_char; // unsafe libc wrapper let if_index = if_nametoindex(c_if_name); - if if_index == 0 { + if if_index == 0 { return Err(io::Error::last_os_error()); } Ok(if_index) } - /// htons and ntohs util functions pub fn htons(u: u16) -> u16 { - u.to_be() + u.to_be() } pub fn ntohs(u: u16) -> u16 { - u16::from_be(u) + u16::from_be(u) } /// Loads kernel symbols from `/proc/kallsyms`.