From 990f45e8941df5fc34756b0fc745e6e3e0e993e8 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/src/bpf.rs | 19 +++++++- aya/src/maps/mod.rs | 4 ++ aya/src/maps/xdp/cpu_map.rs | 71 +++++++++++++++++++----------- aya/src/maps/xdp/dev_map.rs | 75 +++++++++++++++++++++----------- aya/src/maps/xdp/dev_map_hash.rs | 73 +++++++++++++++++++++---------- aya/src/sys/bpf.rs | 54 +++++++++++++++++++++++ 8 files changed, 252 insertions(+), 77 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 f45d07bd..8f0ea2d0 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/src/bpf.rs b/aya/src/bpf.rs index 76604500..5881da57 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -40,8 +40,8 @@ use crate::{ 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_perf_link_supported, - is_probe_read_kernel_supported, is_prog_name_supported, retry_with_verifier_logs, - SyscallError, + is_probe_read_kernel_supported, is_prog_id_supported, is_prog_name_supported, + retry_with_verifier_logs, SyscallError, }, util::{bytes_of, bytes_of_slice, possible_cpus, POSSIBLE_CPUS}, }; @@ -94,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); @@ -477,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 072958fd..df4e4a57 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -170,6 +170,10 @@ pub enum MapError { error: PinError, }, + /// Program IDs are not supported + #[error("program ids are not supported by your 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 50cdcd4c..5b7c1499 100644 --- a/aya/src/maps/xdp/cpu_map.rs +++ b/aya/src/maps/xdp/cpu_map.rs @@ -11,7 +11,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. @@ -49,7 +49,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 }) } @@ -72,19 +77,25 @@ impl> CpuMap { check_bounds(data, cpu_index)?; let fd = data.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://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6149 + prog_id: unsafe { value.bpf_prog.id }, + }) + }) + } else { + bpf_map_lookup_elem::<_, u32>(fd, &cpu_index, flags) + .map(|value| value.map(|qsize| CpuMapValue { qsize, prog_id: 0 })) + }; + 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://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6149 - Ok(CpuMapValue { - qsize: value.qsize, - prog_id: unsafe { value.bpf_prog.id }, - }) + })? + .ok_or(MapError::KeyNotFound) } /// An iterator over the elements of the map. The iterator item type is `Result> 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,19 +135,26 @@ impl> CpuMap { check_bounds(data, cpu_index)?; let fd = data.fd; - let value = bpf_cpumap_val { - qsize: queue_size, - bpf_prog: bpf_cpumap_val__bindgen_ty_1 { - 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 { + 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 09c6e026..cabddde2 100644 --- a/aya/src/maps/xdp/dev_map.rs +++ b/aya/src/maps/xdp/dev_map.rs @@ -11,7 +11,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. @@ -43,7 +43,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 }) } @@ -66,19 +71,29 @@ impl> DevMap { check_bounds(data, index)?; let fd = data.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://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6149 + prog_id: unsafe { value.bpf_prog.id }, + }) + }) + } else { + bpf_map_lookup_elem::<_, u32>(fd, &index, flags).map(|value| { + value.map(|ifindex| DevMapValue { + ifindex, + prog_id: 0, + }) + }) + }; + 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://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6136 - Ok(DevMapValue { - ifindex: value.ifindex, - prog_id: unsafe { value.bpf_prog.id }, - }) + })? + .ok_or(MapError::KeyNotFound) } /// An iterator over the elements of the array. The iterator item type is `Result> 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,19 +132,26 @@ impl> DevMap { check_bounds(data, index)?; let fd = data.fd; - let value = bpf_devmap_val { - ifindex, - bpf_prog: bpf_devmap_val__bindgen_ty_1 { - 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 { + 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 6c1a1f16..db457b8c 100644 --- a/aya/src/maps/xdp/dev_map_hash.rs +++ b/aya/src/maps/xdp/dev_map_hash.rs @@ -11,6 +11,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; @@ -44,7 +45,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 }) } @@ -56,18 +62,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; - 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://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6136 - Ok(DevMapValue { - ifindex: value.ifindex, - prog_id: 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://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/bpf.h#L6149 + prog_id: unsafe { value.bpf_prog.id }, + }) + }) + } else { + bpf_map_lookup_elem::<_, u32>(fd, &key, flags).map(|value| { + value.map(|ifindex| DevMapValue { + ifindex, + prog_id: 0, + }) + }) + }; + 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. The iterator item type is @@ -97,7 +115,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, @@ -105,15 +125,22 @@ impl> DevMapHash { program: Option<&ProgramFd>, flags: u64, ) -> Result<(), MapError> { - let value = bpf_devmap_val { - ifindex, - bpf_prog: bpf_devmap_val__bindgen_ty_1 { - 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 { + 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 e142c6c4..2a5f15b7 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -757,6 +757,31 @@ 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 + ), + "is_prog_id_supported works for CpuMap, DevMap or DevMapHash", + ); + + 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"); @@ -1036,4 +1061,33 @@ 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| match call { + Syscall::Bpf { + cmd: bpf_cmd::BPF_MAP_CREATE, + .. + } => Err((-1, io::Error::from_raw_os_error(EINVAL))), + _ => Ok(42), + }); + let supported = is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_CPUMAP); + assert!(!supported); + } + + #[test] + #[should_panic] + fn test_prog_id_supported_reject_types() { + is_prog_id_supported(bpf_map_type::BPF_MAP_TYPE_HASH); + } }