Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aya,aya-ebpf: implement RingBuf #629

Merged
merged 3 commits into from Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Expand Up @@ -62,10 +62,13 @@ bitflags = { version = "2.2.1", default-features = false }
bytes = { version = "1", default-features = false }
cargo_metadata = { version = "0.18.0", default-features = false }
clap = { version = "4", default-features = false }
const-assert = { version = "1.0.1", default-features = false }
core-error = { version = "0.0.0", default-features = false }
dialoguer = { version = "0.11", default-features = false }
diff = { version = "0.1.13", default-features = false }
env_logger = { version = "0.10", default-features = false }
epoll = { version = "4.3.3", default-features = false }
futures = { version = "0.3.28", default-features = false }
hashbrown = { version = "0.14", default-features = false }
indoc = { version = "2.0", default-features = false }
integration-ebpf = { path = "test/integration-ebpf", default-features = false }
Expand All @@ -80,6 +83,7 @@ proc-macro-error = { version = "1.0", default-features = false }
proc-macro2 = { version = "1", default-features = false }
public-api = { version = "0.32.0", default-features = false }
quote = { version = "1", default-features = false }
rand = { version = "0.8", default-features = false }
rbpf = { version = "0.2.0", default-features = false }
rustdoc-json = { version = "0.8.6", default-features = false }
rustup-toolchain = { version = "0.1.5", default-features = false }
Expand Down
144 changes: 120 additions & 24 deletions aya/src/bpf.rs
Expand Up @@ -43,7 +43,7 @@ use crate::{
is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported,
retry_with_verifier_logs,
},
util::{bytes_of, bytes_of_slice, possible_cpus, POSSIBLE_CPUS},
util::{bytes_of, bytes_of_slice, page_size, possible_cpus, POSSIBLE_CPUS},
};

pub(crate) const BPF_OBJ_NAME_LEN: usize = 16;
Expand Down Expand Up @@ -461,23 +461,23 @@ impl<'a> BpfLoader<'a> {
{
continue;
}

match max_entries.get(name.as_str()) {
Some(size) => obj.set_max_entries(*size),
None => {
if obj.map_type() == BPF_MAP_TYPE_PERF_EVENT_ARRAY as u32
&& obj.max_entries() == 0
{
obj.set_max_entries(
possible_cpus()
.map_err(|error| BpfError::FileError {
path: PathBuf::from(POSSIBLE_CPUS),
error,
})?
.len() as u32,
);
}
}
let num_cpus = || -> Result<u32, BpfError> {
Ok(possible_cpus()
.map_err(|error| BpfError::FileError {
path: PathBuf::from(POSSIBLE_CPUS),
error,
})?
.len() as u32)
};
let map_type: bpf_map_type = obj.map_type().try_into().map_err(MapError::from)?;
if let Some(max_entries) = max_entries_override(
map_type,
max_entries.get(name.as_str()).copied(),
|| obj.max_entries(),
num_cpus,
|| page_size() as u32,
)? {
obj.set_max_entries(max_entries)
}
match obj.map_type().try_into() {
Ok(BPF_MAP_TYPE_CPUMAP) => {
Expand Down Expand Up @@ -710,12 +710,8 @@ impl<'a> BpfLoader<'a> {
}

fn parse_map(data: (String, MapData)) -> Result<(String, Map), BpfError> {
let name = data.0;
let map = data.1;
let map_type =
bpf_map_type::try_from(map.obj().map_type()).map_err(|e| MapError::InvalidMapType {
map_type: e.map_type,
})?;
let (name, map) = data;
let map_type = bpf_map_type::try_from(map.obj().map_type()).map_err(MapError::from)?;
let map = match map_type {
BPF_MAP_TYPE_ARRAY => Map::Array(map),
BPF_MAP_TYPE_PERCPU_ARRAY => Map::PerCpuArray(map),
Expand All @@ -725,6 +721,7 @@ fn parse_map(data: (String, MapData)) -> Result<(String, Map), BpfError> {
BPF_MAP_TYPE_PERCPU_HASH => Map::PerCpuHashMap(map),
BPF_MAP_TYPE_LRU_PERCPU_HASH => Map::PerCpuLruHashMap(map),
BPF_MAP_TYPE_PERF_EVENT_ARRAY => Map::PerfEventArray(map),
BPF_MAP_TYPE_RINGBUF => Map::RingBuf(map),
BPF_MAP_TYPE_SOCKHASH => Map::SockHash(map),
BPF_MAP_TYPE_SOCKMAP => Map::SockMap(map),
BPF_MAP_TYPE_BLOOM_FILTER => Map::BloomFilter(map),
Expand All @@ -745,6 +742,105 @@ fn parse_map(data: (String, MapData)) -> Result<(String, Map), BpfError> {
Ok((name, map))
}

/// Computes the value which should be used to override the max_entries value of the map
/// based on the user-provided override and the rules for that map type.
fn max_entries_override(
map_type: bpf_map_type,
user_override: Option<u32>,
current_value: impl Fn() -> u32,
num_cpus: impl Fn() -> Result<u32, BpfError>,
page_size: impl Fn() -> u32,
) -> Result<Option<u32>, BpfError> {
let max_entries = || user_override.unwrap_or_else(&current_value);
Ok(match map_type {
BPF_MAP_TYPE_PERF_EVENT_ARRAY if max_entries() == 0 => Some(num_cpus()?),
BPF_MAP_TYPE_RINGBUF => Some(adjust_to_page_size(max_entries(), page_size()))
.filter(|adjusted| *adjusted != max_entries())
.or(user_override),
_ => user_override,
})
}

// Adjusts the byte size of a RingBuf map to match a power-of-two multiple of the page size.
//
// This mirrors the logic used by libbpf.
// See https://github.com/libbpf/libbpf/blob/ec6f716eda43/src/libbpf.c#L2461-L2463
fn adjust_to_page_size(byte_size: u32, page_size: u32) -> u32 {
// If the byte_size is zero, return zero and let the verifier reject the map
// when it is loaded. This is the behavior of libbpf.
if byte_size == 0 {
return 0;
}
// TODO: Replace with primitive method when int_roundings (https://github.com/rust-lang/rust/issues/88581)
// is stabilized.
fn div_ceil(n: u32, rhs: u32) -> u32 {
let d = n / rhs;
let r = n % rhs;
if r > 0 && rhs > 0 {
d + 1
} else {
d
}
}
let pages_needed = div_ceil(byte_size, page_size);
page_size * pages_needed.next_power_of_two()
}

#[cfg(test)]
mod tests {
use crate::generated::bpf_map_type::*;

const PAGE_SIZE: u32 = 4096;
const NUM_CPUS: u32 = 4;

#[test]
fn test_adjust_to_page_size() {
use super::adjust_to_page_size;
[
(0, 0),
(4096, 1),
(4096, 4095),
(4096, 4096),
(8192, 4097),
(8192, 8192),
(16384, 8193),
]
.into_iter()
.for_each(|(exp, input)| assert_eq!(exp, adjust_to_page_size(input, PAGE_SIZE)))
}

#[test]
fn test_max_entries_override() {
use super::max_entries_override;
[
(BPF_MAP_TYPE_RINGBUF, Some(1), 1, Some(PAGE_SIZE)),
(BPF_MAP_TYPE_RINGBUF, None, 1, Some(PAGE_SIZE)),
(BPF_MAP_TYPE_RINGBUF, None, PAGE_SIZE, None),
(BPF_MAP_TYPE_PERF_EVENT_ARRAY, None, 1, None),
(BPF_MAP_TYPE_PERF_EVENT_ARRAY, Some(42), 1, Some(42)),
(BPF_MAP_TYPE_PERF_EVENT_ARRAY, Some(0), 1, Some(NUM_CPUS)),
(BPF_MAP_TYPE_PERF_EVENT_ARRAY, None, 0, Some(NUM_CPUS)),
(BPF_MAP_TYPE_PERF_EVENT_ARRAY, None, 42, None),
(BPF_MAP_TYPE_ARRAY, None, 1, None),
(BPF_MAP_TYPE_ARRAY, Some(2), 1, Some(2)),
]
.into_iter()
.for_each(|(map_type, user_override, current_value, exp)| {
assert_eq!(
exp,
max_entries_override(
map_type,
user_override,
|| { current_value },
|| Ok(NUM_CPUS),
|| PAGE_SIZE
)
.unwrap()
)
})
}
}

impl Default for BpfLoader<'_> {
fn default() -> Self {
BpfLoader::new()
Expand Down
20 changes: 19 additions & 1 deletion aya/src/maps/mod.rs
Expand Up @@ -61,6 +61,7 @@ use std::{

use libc::{getrlimit, rlim_t, rlimit, RLIMIT_MEMLOCK, RLIM_INFINITY};
use log::warn;
use obj::maps::InvalidMapTypeError;
use thiserror::Error;

use crate::{
Expand All @@ -80,6 +81,7 @@ pub mod hash_map;
pub mod lpm_trie;
pub mod perf;
pub mod queue;
pub mod ring_buf;
pub mod sock;
pub mod stack;
pub mod stack_trace;
Expand All @@ -94,6 +96,7 @@ pub use lpm_trie::LpmTrie;
pub use perf::AsyncPerfEventArray;
pub use perf::PerfEventArray;
pub use queue::Queue;
pub use ring_buf::RingBuf;
pub use sock::{SockHash, SockMap};
pub use stack::Stack;
pub use stack_trace::StackTraceMap;
Expand Down Expand Up @@ -193,6 +196,16 @@ pub enum MapError {
},
}

// Note that this is not just derived using #[from] because InvalidMapTypeError cannot implement
// Error due the the fact that aya-obj is no_std and error_in_core is not stabilized
// (https://github.com/rust-lang/rust/issues/103765).
impl From<InvalidMapTypeError> for MapError {
fn from(e: InvalidMapTypeError) -> Self {
let InvalidMapTypeError { map_type } = e;
Self::InvalidMapType { map_type }
}
}

/// A map file descriptor.
#[derive(Debug)]
pub struct MapFd(OwnedFd);
Expand Down Expand Up @@ -269,7 +282,9 @@ pub enum Map {
ProgramArray(MapData),
/// A [`Queue`] map.
Queue(MapData),
/// A [`SockHash`] map.
/// A [`RingBuf`] map.
RingBuf(MapData),
/// A [`SockHash`] map
SockHash(MapData),
/// A [`SockMap`] map.
SockMap(MapData),
Expand Down Expand Up @@ -301,6 +316,7 @@ impl Map {
Self::PerfEventArray(map) => map.obj.map_type(),
Self::ProgramArray(map) => map.obj.map_type(),
Self::Queue(map) => map.obj.map_type(),
Self::RingBuf(map) => map.obj.map_type(),
Self::SockHash(map) => map.obj.map_type(),
Self::SockMap(map) => map.obj.map_type(),
Self::Stack(map) => map.obj.map_type(),
Expand Down Expand Up @@ -330,6 +346,7 @@ impl Map {
Self::PerfEventArray(map) => map.pin(path),
Self::ProgramArray(map) => map.pin(path),
Self::Queue(map) => map.pin(path),
Self::RingBuf(map) => map.pin(path),
Self::SockHash(map) => map.pin(path),
Self::SockMap(map) => map.pin(path),
Self::Stack(map) => map.pin(path),
Expand Down Expand Up @@ -447,6 +464,7 @@ impl_try_from_map!(() {
DevMapHash,
PerfEventArray,
ProgramArray,
RingBuf,
SockMap,
StackTraceMap,
XskMap,
Expand Down
23 changes: 2 additions & 21 deletions aya/src/maps/perf/perf_buffer.rs
@@ -1,5 +1,5 @@
use std::{
ffi::{c_int, c_void},
ffi::c_void,
io, mem,
os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd},
ptr, slice,
Expand All @@ -15,7 +15,7 @@ use crate::{
perf_event_header, perf_event_mmap_page,
perf_event_type::{PERF_RECORD_LOST, PERF_RECORD_SAMPLE},
},
sys::{perf_event_ioctl, perf_event_open_bpf, SysResult},
sys::{mmap, perf_event_ioctl, perf_event_open_bpf, SysResult},
PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE,
};

Expand Down Expand Up @@ -282,25 +282,6 @@ impl Drop for PerfBuffer {
}
}

#[cfg_attr(test, allow(unused_variables))]
unsafe fn mmap(
addr: *mut c_void,
len: usize,
prot: c_int,
flags: c_int,
fd: BorrowedFd<'_>,
offset: libc::off_t,
) -> *mut c_void {
#[cfg(not(test))]
return libc::mmap(addr, len, prot, flags, fd.as_raw_fd(), offset);

#[cfg(test)]
use crate::sys::TEST_MMAP_RET;

#[cfg(test)]
TEST_MMAP_RET.with(|ret| *ret.borrow())
}

#[derive(Debug)]
#[repr(C)]
struct Sample {
Expand Down