diff --git a/cargo-bpf/src/accessors.rs b/cargo-bpf/src/accessors.rs index d72d1724..cbd7d35d 100644 --- a/cargo-bpf/src/accessors.rs +++ b/cargo-bpf/src/accessors.rs @@ -196,6 +196,7 @@ pub fn generate_read_accessors(bindings: &str, whitelist: &[&str]) -> String { let accessors = functions.values(); let ident = Ident::new(item_id, Span::call_site()); let item = quote! { + /// Auto-generated read-accessors by cargo_bpf::accessors::generate_read_accessors impl #ident { #(#accessors)* } diff --git a/examples/example-probes/Cargo.toml b/examples/example-probes/Cargo.toml index 75a22135..57011bd3 100644 --- a/examples/example-probes/Cargo.toml +++ b/examples/example-probes/Cargo.toml @@ -38,3 +38,8 @@ required-features = ["probes"] name = "echo" path = "src/echo/main.rs" required-features = ["probes"] + +[[bin]] +name = "biolatpcts" +path = "src/biolatpcts/main.rs" +required-features = ["probes"] diff --git a/examples/example-probes/build.rs b/examples/example-probes/build.rs new file mode 100644 index 00000000..bbe03846 --- /dev/null +++ b/examples/example-probes/build.rs @@ -0,0 +1,73 @@ +use std::env; +use std::fs::File; +use std::io::{self, Write}; +use std::path::PathBuf; +use std::process::Command; + +use cargo_bpf_lib::bindgen as bpf_bindgen; + +fn create_module(path: PathBuf, name: &str, bindings: &str) -> io::Result<()> { + let mut file = File::create(&path)?; + let res = writeln!( + &mut file, + r" +/// Auto generated by BPF bindgen for example-probes +mod {name} {{ +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(non_snake_case)] +#![allow(unused_unsafe)] +#![allow(clippy::all)] +{bindings} +}} +pub use {name}::*; +", + name = name, + bindings = bindings + ); + + // application of the rustfmt is optional and is only for debugging + // auto-generated code. + let _ = Command::new("rustfmt") + .arg("--edition=2018") + .arg("--emit=files") + .arg(&path) + .status(); + + res +} + +fn main() { + // Compilation of example-probes is executed twice. + // - Once for building probes by a build-script of example-userspace + // - Once for compiling modules by a normal cargo + if env::var("CARGO_FEATURE_PROBES").is_err() { + eprintln!("`probes' feature is not set. abort. This build script is designed to be called by cargo-bpf and to build BPF programs (= probes)"); + return; + } + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + + let mut builder = bpf_bindgen::builder(); + // specify kernel headers in include/bindings.h, not here. + builder = builder.header("include/bindings.h"); + // designate whitelist types + let types = ["request"]; + // allowing variables + let variables = ["NSEC_PER_MSEC", "NSEC_PER_USEC"]; + for &ty in types.iter() { + builder = builder.whitelist_type(ty); + } + for &var in variables.iter() { + builder = builder.whitelist_var(var); + } + + let mut bindings = builder + .generate_inline_functions(true) + .generate() + .expect("failed to generate bindings") + .to_string(); + let accessors = bpf_bindgen::generate_read_accessors(&bindings, &types); + bindings.push_str("use redbpf_probes::helpers::bpf_probe_read;"); + bindings.push_str(&accessors); + create_module(out_dir.join("gen_bindings.rs"), "gen_bindings", &bindings).unwrap(); +} diff --git a/examples/example-probes/include/bindings.h b/examples/example-probes/include/bindings.h new file mode 100644 index 00000000..5062ec83 --- /dev/null +++ b/examples/example-probes/include/bindings.h @@ -0,0 +1,10 @@ +// include kernel headers here to generate rust bindings of kernel data types +// which are used by kprobe BPF programs. + +#define BITS_PER_LONG 64 +#include +#include +#include +#include +#include +#include diff --git a/examples/example-probes/src/bindings.rs b/examples/example-probes/src/bindings.rs new file mode 100644 index 00000000..4c1fe7ed --- /dev/null +++ b/examples/example-probes/src/bindings.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/gen_bindings.rs")); diff --git a/examples/example-probes/src/biolatpcts/main.rs b/examples/example-probes/src/biolatpcts/main.rs new file mode 100644 index 00000000..236350a3 --- /dev/null +++ b/examples/example-probes/src/biolatpcts/main.rs @@ -0,0 +1,71 @@ +// This is a remake of examples/tracing/biolatpcts.py of bcc using RedBPF. The +// purpose of this code is to show how to use percpu array and how to write +// kprobe program. This source code implements a half part, a BPF +// program running inside kernel. The other part of userspace is implemented at +// `example-userspace/examples/biolatpcts.rs` + +#![no_std] +#![no_main] +use core::cmp; +use core::mem; +use core::ptr; + +use redbpf_probes::kprobe::prelude::*; + +// You can use types and variables defined in Linux kernel headers. In order to +// use kernel data types, you may include specific header into +// `example-probes/include/bindings.h` and add some stuff to +// `example-probes/build.rs` first. Please take a look at those files. +use example_probes::bindings::{request, NSEC_PER_MSEC, NSEC_PER_USEC}; + +program!(0xFFFFFFFE, "GPL"); + +#[map("lat_100ms")] +static mut LAT_100MS: PerCpuArray = PerCpuArray::with_max_entries(100); + +#[map("lat_1ms")] +static mut LAT_1MS: PerCpuArray = PerCpuArray::with_max_entries(100); + +#[map("lat_10us")] +static mut LAT_10US: PerCpuArray = PerCpuArray::with_max_entries(100); + +#[kprobe("blk_account_io_done")] +fn blk_account_io_done(regs: Registers) { + let rq: &request = unsafe { (regs.parm1() as *const request).as_ref().unwrap() }; + + let stime = rq.io_start_time_ns().unwrap(); + if stime == 0 { + return; + } + let now = regs.parm2(); + let dur = now - stime; + let slot = cmp::min(dur / (100 * NSEC_PER_MSEC) as u64, 99); + unsafe { + match LAT_100MS.get_mut(slot as i32) { + Some(mut val) => *val += 1, + _ => (), + } + } + if slot > 0 { + return; + } + + let slot = cmp::min(dur / NSEC_PER_MSEC as u64, 99); + unsafe { + match LAT_1MS.get_mut(slot as i32) { + Some(mut val) => *val += 1, + _ => (), + } + } + if slot > 0 { + return; + } + + let slot = cmp::min(dur / (10 * NSEC_PER_USEC) as u64, 99); + unsafe { + match LAT_10US.get_mut(slot as i32) { + Some(mut val) => *val += 1, + _ => (), + } + } +} diff --git a/examples/example-probes/src/lib.rs b/examples/example-probes/src/lib.rs index a8774b29..53d686ed 100644 --- a/examples/example-probes/src/lib.rs +++ b/examples/example-probes/src/lib.rs @@ -1,4 +1,16 @@ #![no_std] +// bindings module is used only for compiling BPF programs (= probes). +// And the compilation of BPF programs is executed by cargo-bpf on behalf of +// example-userspace. +// +// This source code is subject to be compiled twice during building example +// programs. +// - Once for building BPF programs by a cargo-bpf executed by a build-script +// of example-userspace +// - Once for compiling modules of probes by a normal cargo +#[cfg(feature = "probes")] +pub mod bindings; + pub mod echo; pub mod mallocstacks; pub mod tcp_lifetime; diff --git a/examples/example-userspace/examples/biolatpcts.rs b/examples/example-userspace/examples/biolatpcts.rs new file mode 100644 index 00000000..1d653ca8 --- /dev/null +++ b/examples/example-userspace/examples/biolatpcts.rs @@ -0,0 +1,133 @@ +// This example is a remake of examples/tracing/biolatpcts.py of bcc using +// RedBPF. The purpose of this program is to show how to use kprobe and percpu +// array in userspace. This source code deals with userspace part only. You can +// find another half, a BPF program, at example-probes/biolatpcts/main.rs +use std::cmp; +use std::time::Duration; +use tokio::time::delay_for; + +use redbpf::load::Loader; +use redbpf::PerCpuArray; + +fn find_pct(req: f32, total: u64, slots: &[u64], mut idx: usize, mut counted: u64) -> (usize, u64) { + while idx > 0 { + idx -= 1; + if slots[idx] > 0 { + counted += slots[idx]; + if counted as f32 / total as f32 * 100.0 >= 100.0 - req { + break; + } + } + } + (idx, counted) +} + +fn calc_lat_pct( + req_pcts: &[u64], + total: u64, + lat_100ms: &[u64], + lat_1ms: &[u64], + lat_10us: &[u64], +) -> Box<[u64]> { + let mut pcts = vec![0u64; req_pcts.len()].into_boxed_slice(); + + if total == 0 { + return pcts; + } + + let data = [(100_000, lat_100ms), (1000, lat_1ms), (10, lat_10us)]; + let mut data_sel = 0; + let mut idx = 100; + let mut counted = 0; + + for pct_idx in (0..req_pcts.len()).rev() { + let req = req_pcts[pct_idx] as f32; + let mut gran; + loop { + let last_counted = counted; + gran = data[data_sel].0; + let slots = data[data_sel].1; + let pct = find_pct(req, total, &slots, idx, counted); + idx = pct.0; + counted = pct.1; + if idx > 0 || data_sel == data.len() - 1 { + break; + } + counted = last_counted; + data_sel += 1; + idx = 100; + } + + pcts[pct_idx] = gran * idx as u64 + gran / 2; + } + + return pcts; +} + +#[tokio::main] +async fn main() -> ! { + if unsafe { libc::getuid() != 0 } { + eprintln!("You must be root to use eBPF!"); + std::process::exit(1); + } + + let mut loaded = Loader::load(include_bytes!(concat!( + env!("OUT_DIR"), + "/target/bpf/programs/biolatpcts/biolatpcts.elf" + ))) + .expect("error loading BPF program"); + + for kp in loaded.kprobes_mut() { + kp.attach_kprobe(&kp.name(), 0).expect(&format!( + "error attaching kprobe BPF program to kernel function {}", + kp.name() + )); + } + let cur_lat_100ms = + PerCpuArray::::new(loaded.map("lat_100ms").expect("array lat_100ms not found")) + .expect("error creating PerCpuArray in userspace"); + let cur_lat_1ms = + PerCpuArray::::new(loaded.map("lat_1ms").expect("array lat_1ms not found")) + .expect("error creating PerCpuArray in userspace"); + let cur_lat_10us = + PerCpuArray::::new(loaded.map("lat_10us").expect("array lat_10us not found")) + .expect("error creating PerCpuArray in userspace"); + + let mut last_lat_100ms = [0; 100]; + let mut last_lat_1ms = [0; 100]; + let mut last_lat_10us = [0; 100]; + + let mut lat_100ms = [0; 100]; + let mut lat_1ms = [0; 100]; + let mut lat_10us = [0; 100]; + + loop { + delay_for(Duration::from_secs(3)).await; + + let mut lat_total = 0; + + for i in 0usize..100 { + let v: u64 = cur_lat_100ms.get(i as i32).unwrap().iter().sum(); + lat_100ms[i] = cmp::max(v - last_lat_100ms[i], 0); + last_lat_100ms[i] = v; + + let v: u64 = cur_lat_1ms.get(i as i32).unwrap().iter().sum(); + lat_1ms[i] = cmp::max(v - last_lat_1ms[i], 0); + last_lat_1ms[i] = v; + + let v: u64 = cur_lat_10us.get(i as i32).unwrap().iter().sum(); + lat_10us[i] = cmp::max(v - last_lat_10us[i], 0); + last_lat_10us[i] = v; + + lat_total += lat_100ms[i]; + } + + let target_pcts = [50, 75, 90, 99]; + let pcts = calc_lat_pct(&target_pcts, lat_total, &lat_100ms, &lat_1ms, &lat_10us); + + for i in 0..target_pcts.len() { + print!("p{}={}us ", target_pcts[i], pcts[i]); + } + println!(); + } +} diff --git a/redbpf-probes/src/maps.rs b/redbpf-probes/src/maps.rs index 69ee480f..37e769ba 100644 --- a/redbpf-probes/src/maps.rs +++ b/redbpf-probes/src/maps.rs @@ -21,87 +21,190 @@ use cty::*; use crate::bindings::*; use crate::helpers::*; -/// Hash table map. -/// -/// High level API for BPF_MAP_TYPE_HASH maps. -#[repr(transparent)] -pub struct HashMap { - def: bpf_map_def, - _k: PhantomData, - _v: PhantomData, -} - -impl HashMap { - /// Creates a map with the specified maximum number of elements. - pub const fn with_max_entries(max_entries: u32) -> Self { - Self { - def: bpf_map_def { - type_: bpf_map_type_BPF_MAP_TYPE_HASH, - key_size: mem::size_of::() as u32, - value_size: mem::size_of::() as u32, - max_entries, - map_flags: 0, - }, - _k: PhantomData, - _v: PhantomData, +macro_rules! define_hashmap { + ($(#[$attr:meta])* $name:ident, $map_type:expr) => { + $(#[$attr])* + #[repr(transparent)] + pub struct $name { + def: bpf_map_def, + _k: PhantomData, + _v: PhantomData, } - } - /// Returns a reference to the value corresponding to the key. - #[inline] - pub fn get(&mut self, key: &K) -> Option<&V> { - unsafe { - let value = bpf_map_lookup_elem( - &mut self.def as *mut _ as *mut c_void, - key as *const _ as *const c_void, - ); - if value.is_null() { - None - } else { - Some(&*(value as *const V)) + impl $name { + /// Creates a map with the specified maximum number of elements. + pub const fn with_max_entries(max_entries: u32) -> Self { + Self { + def: bpf_map_def { + type_: $map_type, + key_size: mem::size_of::() as u32, + value_size: mem::size_of::() as u32, + max_entries, + map_flags: 0, + }, + _k: PhantomData, + _v: PhantomData, + } + } + /// Returns a reference to the value corresponding to the key. + #[inline] + pub fn get(&mut self, key: &K) -> Option<&V> { + unsafe { + let value = bpf_map_lookup_elem( + &mut self.def as *mut _ as *mut c_void, + key as *const _ as *const c_void, + ); + if value.is_null() { + None + } else { + Some(&*(value as *const V)) + } + } } - } - } - #[inline] - pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { - unsafe { - let value = bpf_map_lookup_elem( - &mut self.def as *mut _ as *mut c_void, - key as *const _ as *const c_void, - ); - if value.is_null() { - None - } else { - Some(&mut *(value as *mut V)) + #[inline] + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + unsafe { + let value = bpf_map_lookup_elem( + &mut self.def as *mut _ as *mut c_void, + key as *const _ as *const c_void, + ); + if value.is_null() { + None + } else { + Some(&mut *(value as *mut V)) + } + } + } + + /// Set the `value` in the map for `key` + #[inline] + pub fn set(&mut self, key: &K, value: &V) { + unsafe { + bpf_map_update_elem( + &mut self.def as *mut _ as *mut c_void, + key as *const _ as *const c_void, + value as *const _ as *const c_void, + BPF_ANY.into(), + ); + } + } + + /// Delete the entry indexed by `key` + #[inline] + pub fn delete(&mut self, key: &K) { + unsafe { + bpf_map_delete_elem( + &mut self.def as *mut _ as *mut c_void, + key as *const _ as *const c_void, + ); + } } } - } + }; +} - /// Set the `value` in the map for `key` - #[inline] - pub fn set(&mut self, key: &K, value: &V) { - unsafe { - bpf_map_update_elem( - &mut self.def as *mut _ as *mut c_void, - key as *const _ as *const c_void, - value as *const _ as *const c_void, - BPF_ANY.into(), - ); +macro_rules! define_array { + ($(#[$attr:meta])* $name:ident, $map_type:expr) => { + $(#[$attr])* + #[repr(transparent)] + pub struct $name { + def: bpf_map_def, + _element: PhantomData, } - } - /// Delete the entry indexed by `key` - #[inline] - pub fn delete(&mut self, key: &K) { - unsafe { - bpf_map_delete_elem( - &mut self.def as *mut _ as *mut c_void, - key as *const _ as *const c_void, - ); + impl $name { + /// Create array map of which length is `max_entries` + pub const fn with_max_entries(max_entries: u32) -> Self { + Self { + def: bpf_map_def { + type_: $map_type, + key_size: mem::size_of::() as u32, + value_size: mem::size_of::() as u32, + max_entries, + map_flags: 0, + }, + _element: PhantomData, + } + } + + /// Returns a reference to the value at `index`. + #[inline] + pub fn get(&mut self, index: i32) -> Option<&T> { + unsafe { + let value = bpf_map_lookup_elem( + &mut self.def as *mut _ as *mut c_void, + &index as *const _ as *const c_void, + ); + if value.is_null() { + None + } else { + Some(&*(value as *const T)) + } + } + } + + /// Returns a mutable reference to the value at `index`. + #[inline] + pub fn get_mut(&mut self, index: i32) -> Option<&mut T> { + unsafe { + let value = bpf_map_lookup_elem( + &mut self.def as *mut _ as *mut c_void, + &index as *const _ as *const c_void, + ); + if value.is_null() { + None + } else { + Some(&mut *(value as *mut T)) + } + } + } + + /// Set the `value` at `index`. + #[inline] + pub fn set(&mut self, index: i32, value: &T) { + unsafe { + bpf_map_update_elem( + &mut self.def as *mut _ as *mut c_void, + &index as *const _ as *const c_void, + value as *const _ as *const c_void, + BPF_ANY.into(), + ); + } + } } - } + }; } +define_hashmap!( + /// Hash table map. + /// + /// High level API for BPF_MAP_TYPE_HASH maps. + /// + /// For userspace API, see [`redbpf::HashMap`](../../redbpf/struct.HashMap.html) + HashMap, + bpf_map_type_BPF_MAP_TYPE_HASH +); +// define_hashmap!(PerCpuHashMap, bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH); // userspace part is not implemented yet +// define_hashmap!(LruHashMap, bpf_map_type_BPF_MAP_TYPE_LRU_HASH); // userspace part is not implemented yet +// define_hashmap!(LruPerCpuHashMap, bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH); // userspace part is not implemented yet +define_array!( + /// BPF array map for BPF programs + /// + /// High-level API of BPF_MAP_TYPE_ARRAY maps used by BPF programs. + /// + /// For userspace API, see [`redbpf::Array`](../../redbpf/struct.Array.html) + Array, + bpf_map_type_BPF_MAP_TYPE_ARRAY +); +define_array!( + /// BPF per-cpu array map for BPF programs + /// + /// High-level API of BPF_MAP_TYPE_PERCPU_ARRAY maps used by BPF programs. + /// + /// For userspace API, see [`redbpf::PerCpuArray`](../../redbpf/struct.PerCpuArray.html) + PerCpuArray, + bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY +); /// Flags that can be passed to `PerfMap::insert_with_flags`. #[derive(Debug, Copy, Clone)] diff --git a/redbpf-tools/probes/src/bindings.rs b/redbpf-tools/probes/src/bindings.rs index 342defff..4c1fe7ed 100644 --- a/redbpf-tools/probes/src/bindings.rs +++ b/redbpf-tools/probes/src/bindings.rs @@ -1 +1 @@ -include!(concat!(env!("OUT_DIR"), "/gen_bindings.rs")); \ No newline at end of file +include!(concat!(env!("OUT_DIR"), "/gen_bindings.rs")); diff --git a/redbpf/src/cpus.rs b/redbpf/src/cpus.rs index 611fff1d..34376e65 100644 --- a/redbpf/src/cpus.rs +++ b/redbpf/src/cpus.rs @@ -10,6 +10,7 @@ use std::io::Error; use std::str::FromStr; const SYS_CPU_ONLINE: &str = "/sys/devices/system/cpu/online"; +const SYS_CPU_POSSIBLE: &str = "/sys/devices/system/cpu/possible"; pub type CpuId = i32; @@ -27,6 +28,29 @@ pub fn get_online() -> Result, Error> { Ok(list_from_string(&cpus.trim())) } +/// Returns a list of possible CPU IDs. +/// +/// Possible CPUs are fixed at boot time. +/// cf., +pub fn get_possible() -> Result, Error> { + let cpus = unsafe { + String::from_utf8_unchecked( + read(SYS_CPU_POSSIBLE).expect("error figuring out possible cpus"), + ) + }; + Ok(list_from_string(&cpus.trim())) +} + +/// Returns the number of possible CPUs. +/// +/// The number of possible CPUs is static after it is set during boot time +/// discovery phase. +/// For reference, see comments in kernel source: +pub fn get_possible_num() -> usize { + // get_possible() always returns Ok + get_possible().unwrap().len() +} + fn list_from_string(cpus: &str) -> Vec { let cpu_list = cpus.split(',').flat_map(|group| { let mut split = group.split('-'); diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 5de44850..3c144cb6 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -56,7 +56,8 @@ pub mod xdp; pub use bpf_sys::uname; use bpf_sys::{ bpf_attach_type_BPF_SK_SKB_STREAM_PARSER, bpf_attach_type_BPF_SK_SKB_STREAM_VERDICT, bpf_insn, - bpf_map_def, bpf_probe_attach_type, bpf_probe_attach_type_BPF_PROBE_ENTRY, + bpf_map_def, bpf_map_type_BPF_MAP_TYPE_ARRAY, bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY, + bpf_probe_attach_type, bpf_probe_attach_type_BPF_PROBE_ENTRY, bpf_probe_attach_type_BPF_PROBE_RETURN, bpf_prog_type, BPF_ANY, }; use goblin::elf::{reloc::RelocSection, section_header as hdr, Elf, SectionHeader, Sym}; @@ -69,7 +70,9 @@ use std::io; use std::marker::PhantomData; use std::mem; use std::mem::MaybeUninit; +use std::ops::{Deref, DerefMut}; use std::os::unix::io::RawFd; +use std::ptr; pub use crate::error::{Error, Result}; pub use crate::perf::*; @@ -178,6 +181,41 @@ pub struct SockMap<'a> { base: &'a Map, } +/// Array map corresponding to BPF_MAP_TYPE_ARRAY +/// +/// # Example +/// ```no_run +/// use redbpf::{load::Loader, Array}; +/// let loaded = Loader::load(b"biolatpcts.elf").expect("error loading BPF program"); +/// let biolat = Array::::new(loaded.map("biolat").expect("arr not found")).expect("error creating Array in userspace"); +/// let v = biolat.get(0).unwrap(); +/// ``` +/// +/// This structure is used by userspace programs. For BPF program's API, see [`redbpf_probes::maps::Array`](../redbpf_probes/maps/struct.Array.html) +pub struct Array<'a, T: Clone> { + base: &'a Map, + _element: PhantomData, +} + +/// Per-cpu array map corresponding to BPF_MAP_TYPE_PERCPU_ARRAY +/// +/// # Example +/// ```no_run +/// use redbpf::{load::Loader, PerCpuArray, PerCpuValues}; +/// let loaded = Loader::load(b"biolatpcts.elf").expect("error loading BPF program"); +/// let biolat = PerCpuArray::::new(loaded.map("biolat").expect("arr not found")).expect("error creating Array in userspace"); +/// let mut values = PerCpuValues::new(0); +/// values[0] = 1; +/// values[1] = 10; +/// biolat.set(0, &values); +/// ``` +/// +/// This structure is used by userspace programs. For BPF program's API, see [`redbpf_probes::maps::PerCpuArray`](../redbpf_probes/maps/struct.PerCpuArray.html) +pub struct PerCpuArray<'a, T: Clone> { + base: &'a Map, + _element: PhantomData, +} + // TODO Use PERF_MAX_STACK_DEPTH const BPF_MAX_STACK_DEPTH: usize = 127; @@ -929,6 +967,210 @@ impl<'base, K: Clone, V: Clone> HashMap<'base, K, V> { } } +impl<'base, T: Clone> Array<'base, T> { + /// Create `Array` map from `base` + pub fn new(base: &Map) -> Result> { + if mem::size_of::() != base.config.value_size as usize + || bpf_map_type_BPF_MAP_TYPE_ARRAY != base.config.type_ + { + return Err(Error::Map); + } + + Ok(Array { + base, + _element: PhantomData, + }) + } + + /// Set `value` into this array map at `index` + /// + /// This method can fail if `index` is out of bound + pub fn set(&self, mut index: i32, mut value: T) -> Result<()> { + let rv = unsafe { + bpf_sys::bpf_update_elem( + self.base.fd, + &mut index as *mut _ as *mut _, + &mut value as *mut _ as *mut _, + 0, + ) + }; + if rv < 0 { + Err(Error::Map) + } else { + Ok(()) + } + } + + /// Get an element at `index` from this array map + /// + /// This method always returns a `Some(T)` if `index` is valid, but `None` + /// can be returned if `index` is out of bound. + pub fn get(&self, mut index: i32) -> Option { + let mut value = MaybeUninit::zeroed(); + if unsafe { + bpf_sys::bpf_lookup_elem( + self.base.fd, + &mut index as *mut _ as *mut _, + &mut value as *mut _ as *mut _, + ) + } < 0 + { + return None; + } + Some(unsafe { value.assume_init() }) + } + + /// Get length of this array map. + pub fn len(&self) -> usize { + self.base.config.max_entries as usize + } +} + +// round up to multiple of `unit_size` +// +// `unit_size` must be power of 2 +fn round_up(unit_size: usize) -> usize { + let value_size = std::mem::size_of::(); + ((value_size - 1) | (unit_size - 1)) + 1 +} + +/// A structure representing values of per-cpu map structures such as [`PerCpuArray`](./struct.PerCpuArray.html) +/// +/// It is a kind of newtype of `Box<[T]>`. The length of the slice is always +/// the same with [`cpus::get_possible_num`](./cpus/fn.get_possible_num.html). +/// It also implements `Deref` and `DerefMut` so it can be used as a normal +/// array. +/// # Example +/// ```no_run +/// use redbpf::PerCpuValues; +/// let mut values = PerCpuValues::::new(0); +/// values[0] = 1; +/// ``` +pub struct PerCpuValues(Box<[T]>); + +impl PerCpuValues { + /// Create a `PerCpuValues` instance + /// + /// The created instance contains the fixed number of elements filled with + /// `default_value` + pub fn new(default_value: T) -> Self { + let count = cpus::get_possible_num(); + let v = vec![default_value; count]; + Self(v.into_boxed_slice()) + } + + // This is called by `get` methods of per-cpu map structures + fn from_boxed_slice(v: Box<[T]>) -> Self { + Self(v) + } +} + +impl Deref for PerCpuValues { + type Target = Box<[T]>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for PerCpuValues { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'base, T: Clone> PerCpuArray<'base, T> { + pub fn new(base: &Map) -> Result> { + if mem::size_of::() != base.config.value_size as usize + || bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY != base.config.type_ + { + return Err(Error::Map); + } + + Ok(PerCpuArray { + base, + _element: PhantomData, + }) + } + + /// Set per-cpu `values` to the BPF map + /// + /// The number of elements in `values` should be equal to the number of + /// possible CPUs. It is guranteed if `values` is created by + /// [`PerCpuValues::new`](./struct.PerCpuValues.html#method.new) + /// + /// This method can fail if `index` is out of bound of array map. + pub fn set(&self, mut index: i32, values: &PerCpuValues) -> Result<()> { + let count = cpus::get_possible_num(); + if values.len() != count { + return Err(Error::Map); + } + // It is needed to round up the value size to 8*N bytes + // cf., https://elixir.bootlin.com/linux/v5.8/source/kernel/bpf/syscall.c#L1103 + let value_size = round_up::(8); + let alloc_size = value_size * count; + let mut alloc = vec![0u8; alloc_size]; + let mut ptr = alloc.as_mut_ptr(); + for i in 0..count { + unsafe { + let dst_ptr = ptr.offset((value_size * i) as isize) as *const T as *mut T; + ptr::write_unaligned::(dst_ptr, values[i].clone()); + } + } + let rv = unsafe { + bpf_sys::bpf_update_elem( + self.base.fd, + &mut index as *mut _ as *mut _, + &mut ptr as *mut _ as *mut _, + 0, + ) + }; + + if rv < 0 { + Err(Error::Map) + } else { + Ok(()) + } + } + + /// Get per-cpu values from the BPF map + /// + /// Get per-cpu values at `index`. This method returns + /// [`PerCpuValues`](./struct.PerCpuValues.html) + /// + /// This method can return None if `index` is out of bound. + pub fn get(&self, mut index: i32) -> Option> { + // It is needed to round up the value size to 8*N + // cf., https://elixir.bootlin.com/linux/v5.8/source/kernel/bpf/syscall.c#L1035 + let value_size = round_up::(8); + let count = cpus::get_possible_num(); + let alloc_size = value_size * count; + let mut alloc = vec![0u8; alloc_size]; + let ptr = alloc.as_mut_ptr(); + if unsafe { + bpf_sys::bpf_lookup_elem(self.base.fd, &mut index as *mut _ as *mut _, ptr as *mut _) + } < 0 + { + return None; + } + + let mut values = Vec::with_capacity(count); + for i in 0..count { + unsafe { + let elem_ptr = ptr.offset((value_size * i) as isize) as *const T; + values.push(ptr::read_unaligned(elem_ptr)); + } + } + + Some(PerCpuValues::from_boxed_slice(values.into_boxed_slice())) + } + + /// Get length of array map + pub fn len(&self) -> usize { + self.base.config.max_entries as usize + } +} + impl<'base> ProgramArray<'base> { pub fn new(base: &Map) -> Result { if mem::size_of::() != base.config.key_size as usize