diff --git a/aya-obj/src/maps.rs b/aya-obj/src/maps.rs index dbffed0c..aede3cfe 100644 --- a/aya-obj/src/maps.rs +++ b/aya-obj/src/maps.rs @@ -176,6 +176,14 @@ impl Map { } } + /// Set the value size in bytes + pub fn set_value_size(&mut self, size: u32) { + match self { + Map::Legacy(m) => m.def.value_size = size, + Map::Btf(m) => m.def.value_size = size, + } + } + /// Returns the max entry number pub fn max_entries(&self) -> u32 { match self { diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index ce599d3c..6909a229 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -48,17 +48,24 @@ pub struct Features { bpf_perf_link: bool, bpf_global_data: bool, bpf_cookie: bool, + cpumap_prog_id: bool, + devmap_prog_id: bool, + devmap_hash_prog_id: bool, btf: Option, } impl Features { #[doc(hidden)] + #[allow(clippy::too_many_arguments)] pub fn new( bpf_name: bool, bpf_probe_read_kernel: bool, bpf_perf_link: bool, bpf_global_data: bool, bpf_cookie: bool, + cpumap_prog_id: bool, + devmap_prog_id: bool, + devmap_hash_prog_id: bool, btf: Option, ) -> Self { Self { @@ -67,6 +74,9 @@ impl Features { bpf_perf_link, bpf_global_data, bpf_cookie, + cpumap_prog_id, + devmap_prog_id, + devmap_hash_prog_id, btf, } } @@ -96,6 +106,21 @@ impl Features { self.bpf_cookie } + /// Returns whether XDP CPU Maps support chained program IDs. + pub fn cpumap_prog_id(&self) -> bool { + self.cpumap_prog_id + } + + /// Returns whether XDP Device Maps support chained program IDs. + pub fn devmap_prog_id(&self) -> bool { + self.devmap_prog_id + } + + /// Returns whether XDP Hash Device Maps support chained program IDs. + pub fn devmap_hash_prog_id(&self) -> bool { + self.devmap_hash_prog_id + } + /// If BTF is supported, returns which BTF features are supported. pub fn btf(&self) -> Option<&BtfFeatures> { self.btf.as_ref() diff --git a/aya/Cargo.toml b/aya/Cargo.toml index 59025b2d..9cdae143 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" rust-version = "1.66" [dependencies] +assert_matches = { workspace = true } async-io = { workspace = true, optional = true } aya-obj = { workspace = true, features = ["std"] } bitflags = { workspace = true } @@ -28,7 +29,6 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["rt"], optional = true } [dev-dependencies] -assert_matches = { workspace = true } tempfile = { workspace = true } [features] diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 0140886d..181d971f 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -40,7 +40,8 @@ use crate::{ 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_perf_link_supported, - is_probe_read_kernel_supported, is_prog_name_supported, retry_with_verifier_logs, + 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}, }; @@ -93,6 +94,9 @@ fn detect_features() -> Features { is_perf_link_supported(), is_bpf_global_data_supported(), is_bpf_cookie_supported(), + is_prog_id_supported(BPF_MAP_TYPE_CPUMAP), + is_prog_id_supported(BPF_MAP_TYPE_DEVMAP), + is_prog_id_supported(BPF_MAP_TYPE_DEVMAP_HASH), btf, ); debug!("BPF Feature Detection: {:#?}", f); @@ -476,6 +480,18 @@ impl<'a> BpfLoader<'a> { } } } + match obj.map_type().try_into() { + Ok(BPF_MAP_TYPE_CPUMAP) => { + obj.set_value_size(if FEATURES.cpumap_prog_id() { 8 } else { 4 }) + } + Ok(BPF_MAP_TYPE_DEVMAP) => { + obj.set_value_size(if FEATURES.devmap_prog_id() { 8 } else { 4 }) + } + Ok(BPF_MAP_TYPE_DEVMAP_HASH) => { + obj.set_value_size(if FEATURES.devmap_hash_prog_id() { 8 } else { 4 }) + } + _ => (), + } let btf_fd = btf_fd.as_deref().map(|fd| fd.as_fd()); let mut map = match obj.pinning() { PinningType::None => MapData::create(obj, &name, btf_fd)?, diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index e0d2e0ff..d41600fe 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -181,6 +181,10 @@ pub enum MapError { error: PinError, }, + /// Program IDs are not supported + #[error("program ids are not supported by the current kernel")] + ProgIdNotSupported, + /// Unsupported Map type #[error("Unsupported map type found {map_type}")] Unsupported { diff --git a/aya/src/maps/xdp/cpu_map.rs b/aya/src/maps/xdp/cpu_map.rs index a3c0a585..e6349163 100644 --- a/aya/src/maps/xdp/cpu_map.rs +++ b/aya/src/maps/xdp/cpu_map.rs @@ -12,7 +12,7 @@ use crate::{ maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError}, programs::ProgramFd, sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError}, - Pod, + Pod, FEATURES, }; /// An array of available CPUs. @@ -50,7 +50,12 @@ pub struct CpuMap { impl> CpuMap { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); - check_kv_size::(data)?; + + if FEATURES.cpumap_prog_id() { + check_kv_size::(data)?; + } else { + check_kv_size::(data)?; + } Ok(Self { inner: map }) } @@ -73,19 +78,29 @@ impl> CpuMap { check_bounds(data, cpu_index)?; let fd = data.fd().as_fd(); - let value = - bpf_map_lookup_elem(fd, &cpu_index, flags).map_err(|(_, io_error)| SyscallError { + let value = if FEATURES.cpumap_prog_id() { + bpf_map_lookup_elem::<_, bpf_cpumap_val>(fd, &cpu_index, flags).map(|value| { + value.map(|value| CpuMapValue { + qsize: value.qsize, + // SAFETY: map writes use fd, map reads use id. + // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6241 + prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }), + }) + }) + } else { + bpf_map_lookup_elem::<_, u32>(fd, &cpu_index, flags).map(|value| { + value.map(|qsize| CpuMapValue { + qsize, + prog_id: None, + }) + }) + }; + value + .map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, - })?; - let value: bpf_cpumap_val = value.ok_or(MapError::KeyNotFound)?; - - // SAFETY: map writes use fd, map reads use id. - // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6241 - Ok(CpuMapValue { - qsize: value.qsize, - prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }), - }) + })? + .ok_or(MapError::KeyNotFound) } /// An iterator over the elements of the map. @@ -111,7 +126,8 @@ impl> CpuMap { /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] - /// if `bpf_map_update_elem` fails. + /// if `bpf_map_update_elem` fails, [`MapError::ProgIdNotSupported`] if the kernel does not + /// support program ids and one is provided. pub fn set( &mut self, cpu_index: u32, @@ -123,21 +139,28 @@ impl> CpuMap { check_bounds(data, cpu_index)?; let fd = data.fd().as_fd(); - let value = bpf_cpumap_val { - qsize: queue_size, - bpf_prog: bpf_cpumap_val__bindgen_ty_1 { - // Default is valid as the kernel will only consider fd > 0: - // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/cpumap.c#L466 - fd: program - .map(|prog| prog.as_fd().as_raw_fd()) - .unwrap_or_default(), - }, - }; - bpf_map_update_elem(fd, Some(&cpu_index), &value, flags).map_err(|(_, io_error)| { - SyscallError { - call: "bpf_map_update_elem", - io_error, + let res = if FEATURES.cpumap_prog_id() { + let value = bpf_cpumap_val { + qsize: queue_size, + bpf_prog: bpf_cpumap_val__bindgen_ty_1 { + // Default is valid as the kernel will only consider fd > 0: + // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/cpumap.c#L466 + fd: program + .map(|prog| prog.as_fd().as_raw_fd()) + .unwrap_or_default(), + }, + }; + bpf_map_update_elem(fd, Some(&cpu_index), &value, flags) + } else { + if program.is_some() { + return Err(MapError::ProgIdNotSupported); } + bpf_map_update_elem(fd, Some(&cpu_index), &queue_size, flags) + }; + + res.map_err(|(_, io_error)| SyscallError { + call: "bpf_map_update_elem", + io_error, })?; Ok(()) } diff --git a/aya/src/maps/xdp/dev_map.rs b/aya/src/maps/xdp/dev_map.rs index e7a487d4..df43461c 100644 --- a/aya/src/maps/xdp/dev_map.rs +++ b/aya/src/maps/xdp/dev_map.rs @@ -12,7 +12,7 @@ use crate::{ maps::{check_bounds, check_kv_size, IterableMap, MapData, MapError}, programs::ProgramFd, sys::{bpf_map_lookup_elem, bpf_map_update_elem, SyscallError}, - Pod, + Pod, FEATURES, }; /// An array of network devices. @@ -44,7 +44,12 @@ pub struct DevMap { impl> DevMap { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); - check_kv_size::(data)?; + + if FEATURES.devmap_prog_id() { + check_kv_size::(data)?; + } else { + check_kv_size::(data)?; + } Ok(Self { inner: map }) } @@ -67,19 +72,29 @@ impl> DevMap { check_bounds(data, index)?; let fd = data.fd().as_fd(); - let value = - bpf_map_lookup_elem(fd, &index, flags).map_err(|(_, io_error)| SyscallError { + let value = if FEATURES.devmap_prog_id() { + bpf_map_lookup_elem::<_, bpf_devmap_val>(fd, &index, flags).map(|value| { + value.map(|value| DevMapValue { + ifindex: value.ifindex, + // SAFETY: map writes use fd, map reads use id. + // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228 + prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }), + }) + }) + } else { + bpf_map_lookup_elem::<_, u32>(fd, &index, flags).map(|value| { + value.map(|ifindex| DevMapValue { + ifindex, + prog_id: None, + }) + }) + }; + value + .map_err(|(_, io_error)| SyscallError { call: "bpf_map_lookup_elem", io_error, - })?; - let value: bpf_devmap_val = value.ok_or(MapError::KeyNotFound)?; - - // SAFETY: map writes use fd, map reads use id. - // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228 - Ok(DevMapValue { - ifindex: value.ifindex, - prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }), - }) + })? + .ok_or(MapError::KeyNotFound) } /// An iterator over the elements of the array. @@ -104,7 +119,8 @@ impl> DevMap { /// # Errors /// /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] - /// if `bpf_map_update_elem` fails. + /// if `bpf_map_update_elem` fails, [`MapError::ProgIdNotSupported`] if the kernel does not + /// support program ids and one is provided. pub fn set( &mut self, index: u32, @@ -116,22 +132,29 @@ impl> DevMap { check_bounds(data, index)?; let fd = data.fd().as_fd(); - let value = bpf_devmap_val { - ifindex, - bpf_prog: bpf_devmap_val__bindgen_ty_1 { - // Default is valid as the kernel will only consider fd > 0: - // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866 - // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918 - fd: program - .map(|prog| prog.as_fd().as_raw_fd()) - .unwrap_or_default(), - }, - }; - bpf_map_update_elem(fd, Some(&index), &value, flags).map_err(|(_, io_error)| { - SyscallError { - call: "bpf_map_update_elem", - io_error, + let res = if FEATURES.devmap_prog_id() { + let value = bpf_devmap_val { + ifindex, + bpf_prog: bpf_devmap_val__bindgen_ty_1 { + // Default is valid as the kernel will only consider fd > 0: + // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866 + // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918 + fd: program + .map(|prog| prog.as_fd().as_raw_fd()) + .unwrap_or_default(), + }, + }; + bpf_map_update_elem(fd, Some(&index), &value, flags) + } else { + if program.is_some() { + return Err(MapError::ProgIdNotSupported); } + bpf_map_update_elem(fd, Some(&index), &ifindex, flags) + }; + + res.map_err(|(_, io_error)| SyscallError { + call: "bpf_map_update_elem", + io_error, })?; Ok(()) } diff --git a/aya/src/maps/xdp/dev_map_hash.rs b/aya/src/maps/xdp/dev_map_hash.rs index e5c91dde..8311a29b 100644 --- a/aya/src/maps/xdp/dev_map_hash.rs +++ b/aya/src/maps/xdp/dev_map_hash.rs @@ -12,6 +12,7 @@ use crate::{ maps::{check_kv_size, hash_map, IterableMap, MapData, MapError, MapIter, MapKeys}, programs::ProgramFd, sys::{bpf_map_lookup_elem, SyscallError}, + FEATURES, }; use super::dev_map::DevMapValue; @@ -45,7 +46,12 @@ pub struct DevMapHash { impl> DevMapHash { pub(crate) fn new(map: T) -> Result { let data = map.borrow(); - check_kv_size::(data)?; + + if FEATURES.devmap_hash_prog_id() { + check_kv_size::(data)?; + } else { + check_kv_size::(data)?; + } Ok(Self { inner: map }) } @@ -57,18 +63,30 @@ impl> DevMapHash { /// Returns [`MapError::SyscallError`] if `bpf_map_lookup_elem` fails. pub fn get(&self, key: u32, flags: u64) -> Result { let fd = self.inner.borrow().fd().as_fd(); - let value = bpf_map_lookup_elem(fd, &key, flags).map_err(|(_, io_error)| SyscallError { - call: "bpf_map_lookup_elem", - io_error, - })?; - let value: bpf_devmap_val = value.ok_or(MapError::KeyNotFound)?; - - // SAFETY: map writes use fd, map reads use id. - // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228 - Ok(DevMapValue { - ifindex: value.ifindex, - prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }), - }) + + let value = if FEATURES.devmap_hash_prog_id() { + bpf_map_lookup_elem::<_, bpf_devmap_val>(fd, &key, flags).map(|value| { + value.map(|value| DevMapValue { + ifindex: value.ifindex, + // SAFETY: map writes use fd, map reads use id. + // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/include/uapi/linux/bpf.h#L6228 + prog_id: NonZeroU32::new(unsafe { value.bpf_prog.id }), + }) + }) + } else { + bpf_map_lookup_elem::<_, u32>(fd, &key, flags).map(|value| { + value.map(|ifindex| DevMapValue { + ifindex, + prog_id: None, + }) + }) + }; + value + .map_err(|(_, io_error)| SyscallError { + call: "bpf_map_lookup_elem", + io_error, + })? + .ok_or(MapError::KeyNotFound) } /// An iterator over the elements of the devmap in arbitrary order. @@ -96,7 +114,9 @@ impl> DevMapHash { /// /// # Errors /// - /// Returns [`MapError::SyscallError`] if `bpf_map_update_elem` fails. + /// Returns [`MapError::SyscallError`] if `bpf_map_update_elem` fails, + /// [`MapError::ProgIdNotSupported`] if the kernel does not support program ids and one is + /// provided. pub fn insert( &mut self, key: u32, @@ -104,18 +124,25 @@ impl> DevMapHash { program: Option<&ProgramFd>, flags: u64, ) -> Result<(), MapError> { - let value = bpf_devmap_val { - ifindex, - bpf_prog: bpf_devmap_val__bindgen_ty_1 { - // Default is valid as the kernel will only consider fd > 0: - // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866 - // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918 - fd: program - .map(|prog| prog.as_fd().as_raw_fd()) - .unwrap_or_default(), - }, - }; - hash_map::insert(self.inner.borrow_mut(), &key, &value, flags) + if FEATURES.devmap_hash_prog_id() { + let value = bpf_devmap_val { + ifindex, + bpf_prog: bpf_devmap_val__bindgen_ty_1 { + // Default is valid as the kernel will only consider fd > 0: + // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L866 + // https://github.com/torvalds/linux/blob/2dde18cd1d8fac735875f2e4987f11817cc0bc2c/kernel/bpf/devmap.c#L918 + fd: program + .map(|prog| prog.as_fd().as_raw_fd()) + .unwrap_or_default(), + }, + }; + hash_map::insert(self.inner.borrow_mut(), &key, &value, flags) + } else { + if program.is_some() { + return Err(MapError::ProgIdNotSupported); + } + hash_map::insert(self.inner.borrow_mut(), &key, &ifindex, flags) + } } /// Removes a value from the map. diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index d3cca3ee..cf41b74a 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -8,6 +8,7 @@ use std::{ }; use crate::util::KernelVersion; +use assert_matches::assert_matches; use libc::{c_char, c_long, ENOENT, ENOSPC}; use obj::{ btf::{BtfEnum64, Enum64}, @@ -793,6 +794,28 @@ pub(crate) fn is_bpf_cookie_supported() -> bool { bpf_prog_load(&mut attr).is_ok() } +/// Tests whether CpuMap, DevMap and DevMapHash support program ids +pub(crate) fn is_prog_id_supported(map_type: bpf_map_type) -> bool { + assert_matches!( + map_type, + bpf_map_type::BPF_MAP_TYPE_CPUMAP + | bpf_map_type::BPF_MAP_TYPE_DEVMAP + | bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH + ); + + let mut attr = unsafe { mem::zeroed::() }; + let u = unsafe { &mut attr.__bindgen_anon_1 }; + + u.map_type = map_type as u32; + u.key_size = 4; + u.value_size = 8; // 4 for CPU ID, 8 for CPU ID + prog ID + u.max_entries = 1; + u.map_flags = 0; + + // SAFETY: BPF_MAP_CREATE returns a new file descriptor. + unsafe { fd_sys_bpf(bpf_cmd::BPF_MAP_CREATE, &mut attr) }.is_ok() +} + pub(crate) fn is_btf_supported() -> bool { let mut btf = Btf::new(); let name_offset = btf.add_string("int"); @@ -1072,4 +1095,28 @@ mod tests { let supported = is_perf_link_supported(); assert!(!supported); } + + #[test] + fn test_prog_id_supported() { + override_syscall(|_call| Ok(42)); + + // Ensure that the three map types we can check are accepted + let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_CPUMAP); + assert!(supported); + let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_DEVMAP); + assert!(supported); + let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH); + assert!(supported); + + override_syscall(|_call| Err((-1, io::Error::from_raw_os_error(EINVAL)))); + let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_CPUMAP); + assert!(!supported); + } + + #[test] + #[should_panic = "assertion failed: `BPF_MAP_TYPE_HASH` does not match `bpf_map_type::BPF_MAP_TYPE_CPUMAP | bpf_map_type::BPF_MAP_TYPE_DEVMAP | +bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH`"] + fn test_prog_id_supported_reject_types() { + is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_HASH); + } }