From 00dc7a5bd4468b7d86d7f167a49e78d89016e2ac Mon Sep 17 00:00:00 2001 From: Tuetuopay Date: Sat, 5 Aug 2023 00:16:43 +0200 Subject: [PATCH] maps/xdp: make maps work on kernels not supporting ProgIds On startup, the kernel is probed for support of chained program ids for CpuMap, DevMap and DevMapHash, and will patch maps at load time to have the proper size. Then, at runtime, the support is checked and will error out if a program id is passed when the kernel does not support it. --- aya-obj/src/maps.rs | 8 ++++ aya-obj/src/obj.rs | 25 ++++++++++ aya/Cargo.toml | 2 +- aya/src/bpf.rs | 18 ++++++- aya/src/maps/mod.rs | 4 ++ aya/src/maps/xdp/cpu_map.rs | 79 ++++++++++++++++++++----------- aya/src/maps/xdp/dev_map.rs | 81 ++++++++++++++++++++------------ aya/src/maps/xdp/dev_map_hash.rs | 79 +++++++++++++++++++++---------- aya/src/sys/bpf.rs | 47 ++++++++++++++++++ 9 files changed, 258 insertions(+), 85 deletions(-) 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); + } }