Skip to content

Commit

Permalink
aya,aya-obj: cache feat probed info fields
Browse files Browse the repository at this point in the history
Cached probed value for ProgramInfo fields instead of exposing it
through global FEATURE. Probing will occur on cache miss, which is on
first access and if the field is 0.

Retries are performed internally when probing since flaky error EPERM
was observed on kernels at and below 5.7.
  • Loading branch information
tyrone-wu committed Oct 22, 2024
1 parent 4d65c04 commit 0e67fa4
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 106 deletions.
16 changes: 0 additions & 16 deletions aya-obj/src/obj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ pub struct Features {
bpf_cookie: bool,
cpumap_prog_id: bool,
devmap_prog_id: bool,
prog_info_map_ids: bool,
prog_info_gpl_compatible: bool,
btf: Option<BtfFeatures>,
}

Expand All @@ -63,8 +61,6 @@ impl Features {
bpf_cookie: bool,
cpumap_prog_id: bool,
devmap_prog_id: bool,
prog_info_map_ids: bool,
prog_info_gpl_compatible: bool,
btf: Option<BtfFeatures>,
) -> Self {
Self {
Expand All @@ -75,8 +71,6 @@ impl Features {
bpf_cookie,
cpumap_prog_id,
devmap_prog_id,
prog_info_map_ids,
prog_info_gpl_compatible,
btf,
}
}
Expand Down Expand Up @@ -119,16 +113,6 @@ impl Features {
self.devmap_prog_id
}

/// Returns whether `bpf_prog_info` supports `nr_map_ids` & `map_ids` fields.
pub fn prog_info_map_ids(&self) -> bool {
self.prog_info_map_ids
}

/// Returns whether `bpf_prog_info` supports `gpl_compatible` field.
pub fn prog_info_gpl_compatible(&self) -> bool {
self.prog_info_gpl_compatible
}

/// If BTF is supported, returns which BTF features are supported.
pub fn btf(&self) -> Option<&BtfFeatures> {
self.btf.as_ref()
Expand Down
2 changes: 1 addition & 1 deletion aya/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ bytes = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
object = { workspace = true, features = ["elf", "read_core", "std", "write"] }
once_cell = { workspace = true }
once_cell = { workspace = true, features = ["race"]}
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt"], optional = true }

Expand Down
8 changes: 3 additions & 5 deletions aya/src/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ use crate::{
bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported,
is_btf_datasec_supported, is_btf_decl_tag_supported, is_btf_enum64_supported,
is_btf_float_supported, is_btf_func_global_supported, is_btf_func_supported,
is_btf_supported, is_btf_type_tag_supported, is_info_gpl_compatible_supported,
is_info_map_ids_supported, is_perf_link_supported, is_probe_read_kernel_supported,
is_prog_id_supported, is_prog_name_supported, retry_with_verifier_logs,
is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported,
is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported,
retry_with_verifier_logs,
},
util::{bytes_of, bytes_of_slice, nr_cpus, page_size},
};
Expand Down Expand Up @@ -90,8 +90,6 @@ fn detect_features() -> Features {
is_bpf_cookie_supported(),
is_prog_id_supported(BPF_MAP_TYPE_CPUMAP),
is_prog_id_supported(BPF_MAP_TYPE_DEVMAP),
is_info_map_ids_supported(),
is_info_gpl_compatible_supported(),
btf,
);
debug!("BPF Feature Detection: {:#?}", f);
Expand Down
45 changes: 40 additions & 5 deletions aya/src/programs/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ use std::{
};

use aya_obj::generated::{bpf_prog_info, bpf_prog_type};
use once_cell::race::OnceBool;

use super::{
utils::{boot_time, get_fdinfo},
ProgramError, ProgramFd,
};
use crate::{
sys::{
bpf_get_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, iter_prog_ids, SyscallError,
bpf_get_object, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd,
feature_probe::{is_prog_info_license_supported, is_prog_info_map_ids_supported},
iter_prog_ids, SyscallError,
},
util::bytes_of_bpf_name,
FEATURES,
Expand Down Expand Up @@ -108,7 +111,22 @@ impl ProgramInfo {
///
/// Introduced in kernel v4.15.
pub fn map_ids(&self) -> Result<Option<Vec<u32>>, ProgramError> {
if FEATURES.prog_info_map_ids() {
if self.0.nr_map_ids > 0 || {
static CACHE: OnceBool = OnceBool::new();
CACHE.get_or_init(|| {
// Kernels at and below 5.7 can produce flaky `EPERM` error.
for _ in 0..10 {
match is_prog_info_map_ids_supported() {
Ok(true) => return true,
Err(err) if matches!(err.io_error.raw_os_error(), Some(libc::EPERM)) => {
/* retry */
}
_ => return false,
}
}
false
})
} {
let mut map_ids = vec![0u32; self.0.nr_map_ids as usize];
bpf_prog_get_info_by_fd(self.fd()?.as_fd(), &mut map_ids)?;
Ok(Some(map_ids))
Expand Down Expand Up @@ -140,9 +158,26 @@ impl ProgramInfo {
///
/// Introduced in kernel v4.18.
pub fn gpl_compatible(&self) -> Option<bool> {
FEATURES
.prog_info_gpl_compatible()
.then_some(self.0.gpl_compatible() != 0)
static CACHE: OnceBool = OnceBool::new();
if self.0.gpl_compatible() != 0 {
Some(true)
} else if CACHE.get_or_init(|| {
// Kernels at and below 5.7 can produce flaky `EPERM` error.
for _ in 0..10 {
match is_prog_info_license_supported() {
Ok(true) => return true,
Err(err) if matches!(err.io_error.raw_os_error(), Some(libc::EPERM)) => {
/* retry */
}
_ => break,
}
}
false
}) {
Some(false)
} else {
None
}
}

/// The BTF ID for the program.
Expand Down
62 changes: 3 additions & 59 deletions aya/src/sys/bpf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::{
programs::links::LinkRef,
sys::{syscall, SysResult, Syscall, SyscallError},
util::KernelVersion,
Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN, FEATURES,
Btf, Pod, VerifierLogLevel, BPF_OBJ_NAME_LEN,
};

pub(crate) fn bpf_create_map(
Expand Down Expand Up @@ -587,7 +587,7 @@ pub(crate) fn bpf_prog_get_info_by_fd(
// An `E2BIG` error can occur on kernels below v4.15 when handing over a large struct where the
// extra space is not all-zero bytes.
bpf_obj_get_info_by_fd(fd, |info: &mut bpf_prog_info| {
if FEATURES.prog_info_map_ids() {
if !map_ids.is_empty() {
info.nr_map_ids = map_ids.len() as _;
info.map_ids = map_ids.as_mut_ptr() as _;
}
Expand Down Expand Up @@ -738,62 +738,6 @@ pub(crate) fn is_prog_name_supported() -> bool {
bpf_prog_load(&mut attr).is_ok()
}

/// Tests whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` is available.
pub(crate) fn is_info_map_ids_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };

u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;

let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let insns = copy_instructions(prog).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;

let gpl = b"GPL\0";
u.license = gpl.as_ptr() as u64;

let prog_fd = match bpf_prog_load(&mut attr) {
Ok(fd) => fd,
Err(_) => return false,
};
bpf_obj_get_info_by_fd(prog_fd.as_fd(), |info: &mut bpf_prog_info| {
info.nr_map_ids = 1
})
.is_ok()
}

/// Tests whether `gpl_compatible` field in `bpf_prog_info` is available.
pub(crate) fn is_info_gpl_compatible_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };

u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32;

let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let insns = copy_instructions(prog).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;

let gpl = b"GPL\0";
u.license = gpl.as_ptr() as u64;

let prog_fd = match bpf_prog_load(&mut attr) {
Ok(fd) => fd,
Err(_) => return false,
};
if let Ok::<bpf_prog_info, _>(info) = bpf_obj_get_info_by_fd(prog_fd.as_fd(), |_| {}) {
return info.gpl_compatible() != 0;
}
false
}

pub(crate) fn is_probe_read_kernel_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
Expand Down Expand Up @@ -1144,7 +1088,7 @@ pub(super) fn bpf_prog_load(attr: &mut bpf_attr) -> SysResult<crate::MockableFd>
unsafe { fd_sys_bpf(bpf_cmd::BPF_PROG_LOAD, attr) }
}

fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult<i64> {
pub(super) fn sys_bpf(cmd: bpf_cmd, attr: &mut bpf_attr) -> SysResult<i64> {
syscall(Syscall::Ebpf { cmd, attr })
}

Expand Down
66 changes: 64 additions & 2 deletions aya/src/sys/feature_probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
use std::{mem, os::fd::AsRawFd};

use aya_obj::generated::{
bpf_attach_type, bpf_attr, bpf_cmd, bpf_insn, BPF_F_MMAPABLE, BPF_F_NO_PREALLOC,
bpf_attach_type, bpf_attr, bpf_cmd, bpf_insn, bpf_prog_info, BPF_F_MMAPABLE, BPF_F_NO_PREALLOC,
BPF_F_SLEEPABLE,
};
use libc::{E2BIG, EINVAL};

use super::{bpf_prog_load, fd_sys_bpf, SyscallError};
use super::{bpf_prog_load, fd_sys_bpf, sys_bpf, SyscallError};
use crate::{
maps::MapType,
programs::ProgramType,
util::{page_size, KernelVersion},
MockableFd,
};

const RETURN_ZERO_INSNS: &[bpf_insn] = &[
Expand Down Expand Up @@ -254,3 +255,64 @@ fn dummy_map() -> Result<crate::MockableFd, SyscallError> {
}
})
}

/// Whether `nr_map_ids` & `map_ids` fields in `bpf_prog_info` are supported.
pub(crate) fn is_prog_info_map_ids_supported() -> Result<bool, SyscallError> {
let fd = dummy_prog()?;

// SAFETY: all-zero byte-pattern valid for `bpf_prog_info`
let mut info = unsafe { mem::zeroed::<bpf_prog_info>() };
info.nr_map_ids = 1;

probe_bpf_info(fd, info)
}

/// Tests whether `bpf_prog_info.gpl_compatible` field is supported.
pub(crate) fn is_prog_info_license_supported() -> Result<bool, SyscallError> {
let fd = dummy_prog()?;

// SAFETY: all-zero byte-pattern valid for `bpf_prog_info`
let mut info = unsafe { mem::zeroed::<bpf_prog_info>() };
info.set_gpl_compatible(1);

probe_bpf_info(fd, info)
}

/// Probes program and map info.
fn probe_bpf_info<T>(fd: MockableFd, info: T) -> Result<bool, SyscallError> {
// SAFETY: all-zero byte-pattern valid for `bpf_attr`
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
attr.info.bpf_fd = fd.as_raw_fd() as u32;
attr.info.info_len = mem::size_of_val(&info) as u32;
attr.info.info = &info as *const _ as u64;

let io_error = match sys_bpf(bpf_cmd::BPF_OBJ_GET_INFO_BY_FD, &mut attr) {
Ok(_) => return Ok(true),
Err((_, io_error)) => io_error,
};
match io_error.raw_os_error() {
// `E2BIG` from `bpf_check_uarg_tail_zero()`
Some(E2BIG) => Ok(false),
_ => Err(SyscallError {
call: "bpf_obj_get_info_by_fd",
io_error,
}),
}
}

/// Create a program and returns its fd.
fn dummy_prog() -> Result<crate::MockableFd, SyscallError> {
// SAFETY: all-zero byte-pattern valid for `bpf_attr`
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
// SAFETY: union access
let u = unsafe { &mut attr.__bindgen_anon_3 };
u.prog_type = 1;
u.insn_cnt = 2;
u.insns = RETURN_ZERO_INSNS.as_ptr() as u64;
u.license = GPL_COMPATIBLE.as_ptr() as u64;

bpf_prog_load(&mut attr).map_err(|(_, io_error)| SyscallError {
call: "bpf_prog_load",
io_error,
})
}
Loading

0 comments on commit 0e67fa4

Please sign in to comment.