From f9edaf9c23cd5ca9c98cac70863d315c89b99aa1 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Fri, 24 Jan 2025 16:34:46 +0000 Subject: [PATCH 1/2] feat(aya): Add is_program_type_supported This adds a new API to test whether a given program type is supported. This is to support 3 usecases: 1. A project like bpfman (which uses Aya) may wish to prevent users with a list of program types that are supported on the target system 2. A user of Aya may wish to test whether Fentry/Fexit programs are supported and code their own behaviour to fallback to Kprobes 3. Our own integration tests can be made to skip certain program tests when kernel features are missing. Signed-off-by: Dave Tucker --- aya/src/bpf.rs | 28 +++++++- aya/src/programs/info.rs | 40 +++++++++++ aya/src/sys/bpf.rs | 88 +++++++++++++++--------- test/integration-test/src/tests/smoke.rs | 15 +++- 4 files changed, 134 insertions(+), 37 deletions(-) diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index acce6c16..120b605c 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -30,8 +30,9 @@ use crate::{ programs::{ BtfTracePoint, CgroupDevice, CgroupSkb, CgroupSkbAttachType, CgroupSock, CgroupSockAddr, CgroupSockopt, CgroupSysctl, Extension, FEntry, FExit, Iter, KProbe, LircMode2, Lsm, - PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier, - SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, + PerfEvent, ProbeKind, Program, ProgramData, ProgramError, ProgramType, RawTracePoint, + SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, + UProbe, Xdp, }, sys::{ bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, @@ -39,7 +40,8 @@ use crate::{ 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_prog_id_supported, is_prog_name_supported, is_prog_type_supported, + retry_with_verifier_logs, }, util::{bytes_of, bytes_of_slice, nr_cpus, page_size}, }; @@ -105,6 +107,26 @@ pub fn features() -> &'static Features { &FEATURES } +/// Returns whether a program type is supported by the running kernel. +/// +/// # Errors +/// +/// Returns an error if an unexpected error occurs while checking the program +/// type support. +/// +/// # Example +/// +/// ```no_run +/// use aya::{ProgramType, is_program_type_supported}; +/// +/// let supported = is_program_type_supported(ProgramType::Xdp)?; +/// # Ok::<(), aya::EbpfError>(()) +/// ``` +pub fn is_program_type_supported(program_type: ProgramType) -> Result { + is_prog_type_supported(program_type.into()) + .map_err(|e| EbpfError::ProgramError(ProgramError::from(e))) +} + /// Builder style API for advanced loading of eBPF programs. /// /// Loading eBPF code involves a few steps, including loading maps and applying diff --git a/aya/src/programs/info.rs b/aya/src/programs/info.rs index 3bfa89f3..4f9b45b8 100644 --- a/aya/src/programs/info.rs +++ b/aya/src/programs/info.rs @@ -475,6 +475,46 @@ pub enum ProgramType { Netfilter = bpf_prog_type::BPF_PROG_TYPE_NETFILTER as isize, } +impl From for bpf_prog_type { + fn from(value: ProgramType) -> Self { + match value { + ProgramType::Unspecified => Self::BPF_PROG_TYPE_UNSPEC, + ProgramType::SocketFilter => Self::BPF_PROG_TYPE_SOCKET_FILTER, + ProgramType::KProbe => Self::BPF_PROG_TYPE_KPROBE, + ProgramType::SchedClassifier => Self::BPF_PROG_TYPE_SCHED_CLS, + ProgramType::SchedAction => Self::BPF_PROG_TYPE_SCHED_ACT, + ProgramType::TracePoint => Self::BPF_PROG_TYPE_TRACEPOINT, + ProgramType::Xdp => Self::BPF_PROG_TYPE_XDP, + ProgramType::PerfEvent => Self::BPF_PROG_TYPE_PERF_EVENT, + ProgramType::CgroupSkb => Self::BPF_PROG_TYPE_CGROUP_SKB, + ProgramType::CgroupSock => Self::BPF_PROG_TYPE_CGROUP_SOCK, + ProgramType::LwtInput => Self::BPF_PROG_TYPE_LWT_IN, + ProgramType::LwtOutput => Self::BPF_PROG_TYPE_LWT_OUT, + ProgramType::LwtXmit => Self::BPF_PROG_TYPE_LWT_XMIT, + ProgramType::SockOps => Self::BPF_PROG_TYPE_SOCK_OPS, + ProgramType::SkSkb => Self::BPF_PROG_TYPE_SK_SKB, + ProgramType::CgroupDevice => Self::BPF_PROG_TYPE_CGROUP_DEVICE, + ProgramType::SkMsg => Self::BPF_PROG_TYPE_SK_MSG, + ProgramType::RawTracePoint => Self::BPF_PROG_TYPE_RAW_TRACEPOINT, + ProgramType::CgroupSockAddr => Self::BPF_PROG_TYPE_CGROUP_SOCK_ADDR, + ProgramType::LwtSeg6local => Self::BPF_PROG_TYPE_LWT_SEG6LOCAL, + ProgramType::LircMode2 => Self::BPF_PROG_TYPE_LIRC_MODE2, + ProgramType::SkReuseport => Self::BPF_PROG_TYPE_SK_REUSEPORT, + ProgramType::FlowDissector => Self::BPF_PROG_TYPE_FLOW_DISSECTOR, + ProgramType::CgroupSysctl => Self::BPF_PROG_TYPE_CGROUP_SYSCTL, + ProgramType::RawTracePointWritable => Self::BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, + ProgramType::CgroupSockopt => Self::BPF_PROG_TYPE_CGROUP_SOCKOPT, + ProgramType::Tracing => Self::BPF_PROG_TYPE_TRACING, + ProgramType::StructOps => Self::BPF_PROG_TYPE_STRUCT_OPS, + ProgramType::Extension => Self::BPF_PROG_TYPE_EXT, + ProgramType::Lsm => Self::BPF_PROG_TYPE_LSM, + ProgramType::SkLookup => Self::BPF_PROG_TYPE_SK_LOOKUP, + ProgramType::Syscall => Self::BPF_PROG_TYPE_SYSCALL, + ProgramType::Netfilter => Self::BPF_PROG_TYPE_NETFILTER, + } + } +} + impl TryFrom for ProgramType { type Error = ProgramError; diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 1c2fadef..08596a45 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -8,7 +8,7 @@ use std::{ }; use assert_matches::assert_matches; -use libc::{ENOENT, ENOSPC}; +use libc::{E2BIG, EINVAL, ENOENT, ENOSPC}; use obj::{ btf::{BtfEnum64, Enum64}, generated::bpf_stats_type, @@ -736,6 +736,56 @@ pub(crate) fn bpf_btf_get_fd_by_id(id: u32) -> Result Result { + let mut attr = unsafe { mem::zeroed::() }; + let u = unsafe { &mut attr.__bindgen_anon_3 }; + u.prog_type = kind as u32; + let mut name: [c_char; 16] = [0; 16]; + let cstring = CString::new("aya_prog_check").unwrap(); + let name_bytes = cstring.to_bytes(); + let len = cmp::min(name.len(), name_bytes.len()); + name[..len].copy_from_slice(unsafe { + slice::from_raw_parts(name_bytes.as_ptr() as *const c_char, len) + }); + u.prog_name = name; + + let gpl = b"GPL\0"; + u.license = gpl.as_ptr() as u64; + + let insns = copy_instructions(TEST_PROG).unwrap(); + u.insn_cnt = insns.len() as u32; + u.insns = insns.as_ptr() as u64; + u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32; + + let res = bpf_prog_load(&mut attr); + match res { + Ok(_) => Ok(true), + Err((_, e)) => { + if e.raw_os_error() == Some(EINVAL) || e.raw_os_error() == Some(E2BIG) { + Ok(false) + } else { + Err(SyscallError { + call: "bpf_prog_load", + io_error: e, + }) + } + } + } +} + pub(crate) fn is_prog_name_supported() -> bool { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; @@ -748,20 +798,10 @@ pub(crate) fn is_prog_name_supported() -> bool { }); u.prog_name = name; - // The fields conforming an encoded basic instruction are stored in the following order: - // opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF. - // opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF. - // Multi-byte fields ('imm' and 'offset') are stored using endian order. - // https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding - let prog: &[u8] = &[ - 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0 - 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit - ]; - let gpl = b"GPL\0"; u.license = gpl.as_ptr() as u64; - let insns = copy_instructions(prog).unwrap(); + let insns = copy_instructions(TEST_PROG).unwrap(); u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; u.prog_type = bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER as u32; @@ -776,11 +816,7 @@ pub(crate) fn is_info_map_ids_supported() -> bool { 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(); + let insns = copy_instructions(TEST_PROG).unwrap(); u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; @@ -804,11 +840,7 @@ pub(crate) fn is_info_gpl_compatible_supported() -> bool { 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(); + let insns = copy_instructions(TEST_PROG).unwrap(); u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; @@ -868,20 +900,10 @@ pub(crate) fn is_perf_link_supported() -> bool { let mut attr = unsafe { mem::zeroed::() }; let u = unsafe { &mut attr.__bindgen_anon_3 }; - // The fields conforming an encoded basic instruction are stored in the following order: - // opcode:8 src_reg:4 dst_reg:4 offset:16 imm:32 - In little-endian BPF. - // opcode:8 dst_reg:4 src_reg:4 offset:16 imm:32 - In big-endian BPF. - // Multi-byte fields ('imm' and 'offset') are stored using endian order. - // https://www.kernel.org/doc/html/v6.4-rc7/bpf/instruction-set.html#instruction-encoding - let prog: &[u8] = &[ - 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0 - 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit - ]; - let gpl = b"GPL\0"; u.license = gpl.as_ptr() as u64; - let insns = copy_instructions(prog).unwrap(); + let insns = copy_instructions(TEST_PROG).unwrap(); u.insn_cnt = insns.len() as u32; u.insns = insns.as_ptr() as u64; u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32; diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/src/tests/smoke.rs index 0d57aea9..34b20e9c 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/src/tests/smoke.rs @@ -1,5 +1,6 @@ use aya::{ - programs::{Extension, TracePoint, Xdp, XdpFlags}, + is_program_type_supported, + programs::{Extension, ProgramType, TracePoint, Xdp, XdpFlags}, util::KernelVersion, Ebpf, EbpfLoader, }; @@ -7,6 +8,18 @@ use test_log::test; use crate::utils::NetNsGuard; +#[test] +fn progam_is_supported() { + // All of these program types have been supported for a long time and are + // used in our tests without any special checks. + + assert!(is_program_type_supported(ProgramType::Xdp).unwrap()); + assert!(is_program_type_supported(ProgramType::TracePoint).unwrap()); + // Kprobe and uprobe are the same program type + assert!(is_program_type_supported(ProgramType::KProbe).unwrap()); + assert!(is_program_type_supported(ProgramType::SchedClassifier).unwrap()); +} + #[test] fn xdp() { let kernel_version = KernelVersion::current().unwrap(); From 078c6381ec068597dfb91f03ae54e6ec3eaca0f0 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Mon, 11 Oct 2021 11:09:13 +0100 Subject: [PATCH 2/2] feat(aya): Implement Maps/Arrays of Maps This commit implements the Array of Maps and Hash of Maps to eBPF. Signed-off-by: Dave Tucker --- aya-obj/src/btf/btf.rs | 20 +- aya-obj/src/lib.rs | 2 +- aya-obj/src/maps.rs | 68 +++++- aya-obj/src/obj.rs | 132 +++++++++--- aya-obj/src/relocation.rs | 240 +++++++++++++++++++-- aya/src/bpf.rs | 131 ++++++++++- aya/src/maps/mod.rs | 30 +++ aya/src/maps/of_maps/array.rs | 82 +++++++ aya/src/maps/of_maps/hash_map.rs | 75 +++++++ aya/src/maps/of_maps/mod.rs | 6 + aya/src/sys/bpf.rs | 21 ++ ebpf/aya-ebpf/src/maps/array.rs | 13 +- ebpf/aya-ebpf/src/maps/array_of_maps.rs | 66 ++++++ ebpf/aya-ebpf/src/maps/bloom_filter.rs | 5 +- ebpf/aya-ebpf/src/maps/hash_map.rs | 16 +- ebpf/aya-ebpf/src/maps/hash_of_maps.rs | 64 ++++++ ebpf/aya-ebpf/src/maps/lpm_trie.rs | 3 +- ebpf/aya-ebpf/src/maps/mod.rs | 7 + ebpf/aya-ebpf/src/maps/per_cpu_array.rs | 3 +- ebpf/aya-ebpf/src/maps/queue.rs | 3 +- ebpf/aya-ebpf/src/maps/ring_buf.rs | 3 +- ebpf/aya-ebpf/src/maps/sock_hash.rs | 3 +- ebpf/aya-ebpf/src/maps/sock_map.rs | 3 +- ebpf/aya-ebpf/src/maps/stack.rs | 5 +- ebpf/aya-ebpf/src/maps/stack_trace.rs | 3 +- ebpf/aya-ebpf/src/maps/xdp/cpu_map.rs | 3 +- ebpf/aya-ebpf/src/maps/xdp/dev_map.rs | 3 +- ebpf/aya-ebpf/src/maps/xdp/dev_map_hash.rs | 3 +- ebpf/aya-ebpf/src/maps/xdp/xsk_map.rs | 3 +- test/integration-ebpf/Cargo.toml | 4 + test/integration-ebpf/src/map_of_maps.rs | 44 ++++ test/integration-test/bpf/ofmaps.bpf.c | 64 ++++++ test/integration-test/build.rs | 1 + test/integration-test/src/lib.rs | 2 + test/integration-test/src/tests/load.rs | 62 +++++- 35 files changed, 1110 insertions(+), 83 deletions(-) create mode 100644 aya/src/maps/of_maps/array.rs create mode 100644 aya/src/maps/of_maps/hash_map.rs create mode 100644 aya/src/maps/of_maps/mod.rs create mode 100644 ebpf/aya-ebpf/src/maps/array_of_maps.rs create mode 100644 ebpf/aya-ebpf/src/maps/hash_of_maps.rs create mode 100644 test/integration-ebpf/src/map_of_maps.rs create mode 100644 test/integration-test/bpf/ofmaps.bpf.c diff --git a/aya-obj/src/btf/btf.rs b/aya-obj/src/btf/btf.rs index 51f9594b..fd35de59 100644 --- a/aya-obj/src/btf/btf.rs +++ b/aya-obj/src/btf/btf.rs @@ -155,6 +155,10 @@ pub enum BtfError { /// unable to get symbol name #[error("Unable to get symbol name")] InvalidSymbolName, + + /// an error occurred while parsing a the BTF data + #[error("BTF error: {0}")] + BtfError(String), } /// Available BTF features @@ -591,14 +595,6 @@ impl Btf { for e in entries.iter_mut() { if let BtfType::Var(var) = types.type_by_id(e.btf_type)? { let var_name = self.string_at(var.name_offset)?; - if var.linkage == VarLinkage::Static { - debug!( - "{} {}: VAR {}: fixup not required", - kind, name, var_name - ); - continue; - } - let offset = match symbol_offsets.get(var_name.as_ref()) { Some(offset) => offset, None => { @@ -612,6 +608,14 @@ impl Btf { "{} {}: VAR {}: fixup offset {}", kind, name, var_name, offset ); + + if var.linkage == VarLinkage::Static { + debug!( + "{} {}: VAR {}: linkage fixup not required", + kind, name, var_name + ); + continue; + } } else { return Err(BtfError::InvalidDatasec); } diff --git a/aya-obj/src/lib.rs b/aya-obj/src/lib.rs index f1054707..bb84845c 100644 --- a/aya-obj/src/lib.rs +++ b/aya-obj/src/lib.rs @@ -42,7 +42,7 @@ //! #[cfg(not(feature = "std"))] //! let text_sections = hashbrown::HashSet::new(); //! object.relocate_calls(&text_sections).unwrap(); -//! object.relocate_maps(std::iter::empty(), &text_sections).unwrap(); +//! object.relocate_map_references(std::iter::empty(), &text_sections).unwrap(); //! //! // Run with rbpf //! let function = object.functions.get(&object.programs["prog_name"].function_key()).unwrap(); diff --git a/aya-obj/src/maps.rs b/aya-obj/src/maps.rs index 902c5b17..90bf2b7a 100644 --- a/aya-obj/src/maps.rs +++ b/aya-obj/src/maps.rs @@ -1,6 +1,6 @@ //! Map struct and type bindings. -use alloc::vec::Vec; +use alloc::{collections::BTreeMap, vec::Vec}; use core::mem; use crate::{EbpfSectionKind, InvalidTypeBinding}; @@ -248,6 +248,64 @@ impl Map { Map::Btf(m) => Some(m.symbol_index), } } + + /// Sets the inner map definition, in case of a map of maps + pub fn set_legacy_inner(&mut self, inner_def: &Map) { + match self { + Map::Legacy(m) => { + if let Map::Legacy(inner_def) = inner_def { + m.inner_def = Some(inner_def.def); + } else { + panic!("inner map is not a legacy map"); + } + } + Map::Btf(_) => panic!("inner map already set"), + } + } + + /// Returns the inner map definition, in case of a map of maps + pub fn inner(&self) -> Option { + match self { + Map::Legacy(m) => m.inner_def.as_ref().map(|inner_def| { + Map::Legacy(LegacyMap { + def: *inner_def, + inner_def: None, + section_index: m.section_index, + section_kind: m.section_kind, + symbol_index: m.symbol_index, + data: Vec::new(), + initial_slots: BTreeMap::new(), + }) + }), + Map::Btf(m) => m.inner_def.as_ref().map(|inner_def| { + Map::Btf(BtfMap { + def: *inner_def, + inner_def: None, + section_index: m.section_index, + symbol_index: m.symbol_index, + data: Vec::new(), + initial_slots: BTreeMap::new(), + }) + }), + } + } + + /// Places the file descriptor of an inner map into the initial slots of a + /// map-of-maps. The map is placed at the given index. + pub(crate) fn set_initial_map_fd(&mut self, index: usize, inner_map_fd: i32) -> bool { + match self { + Map::Legacy(m) => m.initial_slots.insert(index, inner_map_fd).is_none(), + Map::Btf(m) => m.initial_slots.insert(index, inner_map_fd).is_none(), + } + } + + /// Returns the initial slots of a map-of-maps + pub fn initial_map_fds(&self) -> &BTreeMap { + match self { + Map::Legacy(m) => &m.initial_slots, + Map::Btf(m) => &m.initial_slots, + } + } } /// A map declared with legacy BPF map declaration style, most likely from a `maps` section. @@ -258,6 +316,8 @@ impl Map { pub struct LegacyMap { /// The definition of the map pub def: bpf_map_def, + /// The defintion of the inner map, in case of a map of maps + pub inner_def: Option, /// The section index pub section_index: usize, /// The section kind @@ -270,6 +330,8 @@ pub struct LegacyMap { pub symbol_index: Option, /// The map data pub data: Vec, + /// The initial slots of a map-of-maps + pub initial_slots: BTreeMap, } /// A BTF-defined map, most likely from a `.maps` section. @@ -277,7 +339,11 @@ pub struct LegacyMap { pub struct BtfMap { /// The definition of the map pub def: BtfMapDef, + /// The defintion of the inner map, in case of a map of maps + pub inner_def: Option, pub(crate) section_index: usize, pub(crate) symbol_index: usize, pub(crate) data: Vec, + /// The initial slots of a map-of-maps + pub initial_slots: BTreeMap, } diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index cfa3f613..ac417528 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -4,6 +4,7 @@ use alloc::{ borrow::ToOwned, collections::BTreeMap, ffi::CString, + format, string::{String, ToString}, vec, vec::Vec, @@ -20,10 +21,12 @@ use object::{ use crate::{ btf::{ Array, Btf, BtfError, BtfExt, BtfFeatures, BtfType, DataSecEntry, FuncSecInfo, LineSecInfo, + Struct, }, generated::{ - bpf_insn, bpf_map_info, bpf_map_type::BPF_MAP_TYPE_ARRAY, BPF_CALL, BPF_F_RDONLY_PROG, - BPF_JMP, BPF_K, + bpf_insn, bpf_map_info, + bpf_map_type::{BPF_MAP_TYPE_ARRAY, *}, + BPF_CALL, BPF_F_RDONLY_PROG, BPF_JMP, BPF_K, }, maps::{bpf_map_def, BtfMap, BtfMapDef, LegacyMap, Map, PinningType, MINIMUM_MAP_SIZE}, programs::{ @@ -675,7 +678,7 @@ impl Object { )) } - fn parse_text_section(&mut self, section: Section) -> Result<(), ParseError> { + fn parse_text_section(&mut self, section: &Section) -> Result<(), ParseError> { let mut symbols_by_address = HashMap::new(); for sym in self.symbol_table.values() { @@ -710,7 +713,7 @@ impl Object { } let (func_info, line_info, func_info_rec_size, line_info_rec_size) = - get_func_and_line_info(self.btf_ext.as_ref(), sym, §ion, offset, false); + get_func_and_line_info(self.btf_ext.as_ref(), sym, section, offset, false); self.functions.insert( (section.index.0, sym.address), @@ -732,17 +735,6 @@ impl Object { offset += sym.size as usize; } - if !section.relocations.is_empty() { - self.relocations.insert( - section.index, - section - .relocations - .into_iter() - .map(|rel| (rel.offset, rel)) - .collect(), - ); - } - Ok(()) } @@ -773,7 +765,7 @@ impl Object { if type_name == section.name { // each btf_var_secinfo contains a map for info in &datasec.entries { - let (map_name, def) = parse_btf_map_def(btf, info)?; + let (map_name, def, inner_def) = parse_btf_map_from_datasec(btf, info)?; let symbol_index = maps.get(&map_name) .ok_or_else(|| ParseError::SymbolNotFound { @@ -783,9 +775,11 @@ impl Object { map_name, Map::Btf(BtfMap { def, + inner_def, section_index: section.index.0, symbol_index: *symbol_index, data: Vec::new(), + initial_slots: BTreeMap::new(), }), ); } @@ -825,7 +819,9 @@ impl Object { section_kind: section.kind, symbol_index: Some(sym.index), def, + inner_def: None, data: Vec::new(), + initial_slots: BTreeMap::new(), }), ); have_symbols = true; @@ -847,7 +843,7 @@ impl Object { self.maps .insert(section.name.to_string(), parse_data_map_section(§ion)?); } - EbpfSectionKind::Text => self.parse_text_section(section)?, + EbpfSectionKind::Text => self.parse_text_section(§ion)?, EbpfSectionKind::Btf => self.parse_btf(§ion)?, EbpfSectionKind::BtfExt => self.parse_btf_ext(§ion)?, EbpfSectionKind::BtfMaps => self.parse_btf_maps(§ion)?, @@ -875,20 +871,19 @@ impl Object { } EbpfSectionKind::Program => { self.parse_programs(§ion)?; - if !section.relocations.is_empty() { - self.relocations.insert( - section.index, - section - .relocations - .into_iter() - .map(|rel| (rel.offset, rel)) - .collect(), - ); - } } EbpfSectionKind::Undefined | EbpfSectionKind::License | EbpfSectionKind::Version => {} } - + if !section.relocations.is_empty() { + self.relocations.insert( + section.index, + section + .relocations + .into_iter() + .map(|rel| (rel.offset, rel)) + .collect(), + ); + } Ok(()) } @@ -1233,7 +1228,9 @@ fn parse_data_map_section(section: &Section) -> Result { // Data maps don't require symbols to be relocated symbol_index: None, def, + inner_def: None, data, + initial_slots: BTreeMap::new(), })) } @@ -1257,7 +1254,10 @@ fn parse_map_def(name: &str, data: &[u8]) -> Result { } } -fn parse_btf_map_def(btf: &Btf, info: &DataSecEntry) -> Result<(String, BtfMapDef), BtfError> { +fn parse_btf_map_from_datasec( + btf: &Btf, + info: &DataSecEntry, +) -> Result<(String, BtfMapDef, Option), BtfError> { let ty = match btf.type_by_id(info.btf_type)? { BtfType::Var(var) => var, other => { @@ -1267,9 +1267,6 @@ fn parse_btf_map_def(btf: &Btf, info: &DataSecEntry) -> Result<(String, BtfMapDe } }; let map_name = btf.string_at(ty.name_offset)?; - let mut map_def = BtfMapDef::default(); - - // Safety: union let root_type = btf.resolve_type(ty.btf_type)?; let s = match btf.type_by_id(root_type)? { BtfType::Struct(s) => s, @@ -1279,7 +1276,17 @@ fn parse_btf_map_def(btf: &Btf, info: &DataSecEntry) -> Result<(String, BtfMapDe }) } }; + let (outer, inner) = parse_btf_map_def(btf, s, false)?; + Ok((map_name.to_string(), outer, inner)) +} +fn parse_btf_map_def( + btf: &Btf, + s: &Struct, + inner: bool, +) -> Result<(BtfMapDef, Option), BtfError> { + let mut map_def = BtfMapDef::default(); + let mut inner_map_def = None; for m in &s.members { match btf.string_at(m.name_offset)?.as_ref() { "type" => { @@ -1311,6 +1318,57 @@ fn parse_btf_map_def(btf: &Btf, info: &DataSecEntry) -> Result<(String, BtfMapDe }); } } + "values" => { + if map_def.map_type != BPF_MAP_TYPE_PROG_ARRAY as u32 + && map_def.map_type != BPF_MAP_TYPE_ARRAY_OF_MAPS as u32 + && map_def.map_type != BPF_MAP_TYPE_HASH_OF_MAPS as u32 + { + return Err(BtfError::BtfError( + "should be map-in-map or prog-array".to_string(), + )); + } + if inner { + return Err(BtfError::BtfError( + "nested map-in-map is not supported".to_string(), + )); + } + if map_def.value_size != 0 && map_def.value_size != 4 { + return Err(BtfError::BtfError(format!( + "conflicting value size. expected 4, got {}", + map_def.value_size + ))); + } + map_def.value_size = 4; + if let BtfType::Array(arr) = btf.type_by_id(m.btf_type)? { + let resolved_t = btf.resolve_type(arr.array.element_type)?; + if let BtfType::Ptr(pty) = btf.type_by_id(resolved_t)? { + let resolved_t = btf.resolve_type(pty.btf_type)?; + if map_def.map_type == BPF_MAP_TYPE_PROG_ARRAY as u32 { + // Just verify that the type is a function proto + if let BtfType::FuncProto(_) = btf.type_by_id(resolved_t)? { + // noop + } else { + return Err(BtfError::BtfError( + "should be a function proto".to_string(), + )); + } + }; + if map_def.map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS as u32 + || map_def.map_type == BPF_MAP_TYPE_HASH_OF_MAPS as u32 + { + if let BtfType::Struct(def) = btf.type_by_id(resolved_t)? { + inner_map_def = Some(parse_btf_map_def(btf, def, true)?.0); + } else { + return Err(BtfError::BtfError( + "map-in-map inner def is not a struct".to_string(), + )); + } + } + } + } else { + return Err(BtfError::BtfError("map values is not an array".to_string())); + } + } "value_size" => { map_def.value_size = get_map_field(btf, m.btf_type)?; } @@ -1333,7 +1391,7 @@ fn parse_btf_map_def(btf: &Btf, info: &DataSecEntry) -> Result<(String, BtfMapDe } } } - Ok((map_name.to_string(), map_def)) + Ok((map_def, inner_map_def)) } /// Parses a [bpf_map_info] into a [Map]. @@ -1350,9 +1408,11 @@ pub fn parse_map_info(info: bpf_map_info, pinned: PinningType) -> Map { btf_key_type_id: info.btf_key_type_id, btf_value_type_id: info.btf_value_type_id, }, + inner_def: None, section_index: 0, symbol_index: 0, data: Vec::new(), + initial_slots: BTreeMap::new(), }) } else { Map::Legacy(LegacyMap { @@ -1365,10 +1425,12 @@ pub fn parse_map_info(info: bpf_map_info, pinned: PinningType) -> Map { pinning: pinned, id: info.id, }, + inner_def: None, section_index: 0, symbol_index: None, section_kind: EbpfSectionKind::Undefined, data: Vec::new(), + initial_slots: BTreeMap::new(), }) } } @@ -1623,7 +1685,9 @@ mod tests { id: 0, pinning: PinningType::None, }, + inner_def: None, data, + initial_slots: _, })) if data == map_data && value_size == map_data.len() as u32 ) } @@ -2601,10 +2665,12 @@ mod tests { id: 1, pinning: PinningType::None, }, + inner_def: None, section_index: 1, section_kind: EbpfSectionKind::Rodata, symbol_index: Some(1), data: vec![0, 0, 0], + initial_slots: BTreeMap::new(), }), ); obj.symbol_table.insert( diff --git a/aya-obj/src/relocation.rs b/aya-obj/src/relocation.rs index b46b277c..d3ce594c 100644 --- a/aya-obj/src/relocation.rs +++ b/aya-obj/src/relocation.rs @@ -1,15 +1,16 @@ //! Program relocation handling. -use alloc::{borrow::ToOwned, collections::BTreeMap, string::String}; +use alloc::{borrow::ToOwned, collections::BTreeMap, format, string::String}; use core::mem; use log::debug; use object::{SectionIndex, SymbolKind}; use crate::{ + btf::{Btf, BtfType}, generated::{ - bpf_insn, BPF_CALL, BPF_JMP, BPF_K, BPF_PSEUDO_CALL, BPF_PSEUDO_FUNC, BPF_PSEUDO_MAP_FD, - BPF_PSEUDO_MAP_VALUE, + bpf_insn, bpf_map_type, BPF_CALL, BPF_JMP, BPF_K, BPF_PSEUDO_CALL, BPF_PSEUDO_FUNC, + BPF_PSEUDO_MAP_FD, BPF_PSEUDO_MAP_VALUE, }, maps::Map, obj::{Function, Object}, @@ -23,8 +24,9 @@ type RawFd = std::os::fd::RawFd; type RawFd = core::ffi::c_int; pub(crate) const INS_SIZE: usize = mem::size_of::(); +pub(crate) const BPF_PTR_SZ: usize = 8; -/// The error type returned by [`Object::relocate_maps`] and [`Object::relocate_calls`] +/// The error type returned by [`Object::relocate_map_references`] and [`Object::relocate_calls`] #[derive(thiserror::Error, Debug)] #[error("error relocating `{function}`")] pub struct EbpfRelocationError { @@ -38,6 +40,10 @@ pub struct EbpfRelocationError { /// Relocation failures #[derive(Debug, thiserror::Error)] pub enum RelocationError { + /// Relocation error + #[error("relocation error: {0}")] + Error(String), + /// Unknown symbol #[error("unknown symbol, index `{index}`")] UnknownSymbol { @@ -106,6 +112,33 @@ pub(crate) struct Symbol { } impl Object { + /// Handles BTF .maps section relocations + pub fn relocate_btf_maps<'a, I: Iterator>( + &mut self, + maps: I, + ) -> Result<(), EbpfRelocationError> { + if let Some((btf_maps_section, _)) = self.section_infos.get(".maps") { + let mut maps_by_name = maps + .filter(|(_, _, map)| map.section_index() == btf_maps_section.0) + .map(|(name, fd, map)| (name, (fd, map))) + .collect(); + + if let Some(relocations) = self.relocations.get(btf_maps_section) { + relocate_btf_maps( + self.btf.as_ref(), + relocations.values(), + &mut maps_by_name, + &self.symbol_table, + ) + .map_err(|error| EbpfRelocationError { + function: String::from(".maps"), + error, + })?; + } + } + Ok(()) + } + /// Relocates the map references pub fn relocate_maps<'a, I: Iterator>( &mut self, @@ -123,7 +156,7 @@ impl Object { for function in self.functions.values_mut() { if let Some(relocations) = self.relocations.get(&function.section_index) { - relocate_maps( + relocate_map_references( function, relocations.values(), &maps_by_section, @@ -179,7 +212,7 @@ impl Object { } } -fn relocate_maps<'a, I: Iterator>( +fn relocate_map_references<'a, I: Iterator>( fun: &mut Function, relocations: I, maps_by_section: &HashMap, @@ -208,7 +241,7 @@ fn relocate_maps<'a, I: Iterator>( } let ins_index = ins_offset / INS_SIZE; - // a map relocation points to the ELF section that contains the map + // a map relocation points to the ELF section that contains the map let sym = symbol_table .get(&rel.symbol_index) .ok_or(RelocationError::UnknownSymbol { @@ -273,6 +306,185 @@ fn relocate_maps<'a, I: Iterator>( Ok(()) } +fn relocate_btf_maps<'a, I: Iterator>( + btf: Option<&Btf>, + relocations: I, + maps_by_name: &mut HashMap<&str, (RawFd, &mut Map)>, + symbol_table: &HashMap, +) -> Result<(), RelocationError> { + let btf = btf.ok_or(RelocationError::Error(String::from( + "BTF relocations require BTF information", + )))?; + for (i, rel) in relocations.enumerate() { + let target = symbol_table + .get(&rel.symbol_index) + .ok_or(RelocationError::UnknownSymbol { + index: rel.symbol_index, + })?; + + let target_name = target.name.as_ref().unwrap(); + + debug!(".maps relocation #{i}: {}", target_name); + let maps_section = btf + .types() + .find(|t| { + if let BtfType::DataSec(ds) = t { + if let Ok(name) = btf.string_at(ds.name_offset) { + name == ".maps" + } else { + false + } + } else { + false + } + }) + .ok_or(RelocationError::Error(String::from("no .maps found")))?; + + let maps_section = if let BtfType::DataSec(ds) = maps_section { + ds + } else { + return Err(RelocationError::Error(String::from("no .maps found"))); + }; + + // find the outer map + let (outer_map_name, (_, map)) = maps_by_name + .iter_mut() + .find(|(map_name, _)| { + if let Some(vi) = maps_section.entries.iter().find(|entry| { + if let Ok(BtfType::Var(v)) = btf.type_by_id(entry.btf_type) { + if let Ok(name) = btf.string_at(v.name_offset) { + name == **map_name + } else { + false + } + } else { + false + } + }) { + vi.offset as u64 <= rel.offset + && (rel.offset + BPF_PTR_SZ as u64 <= (vi.offset as u64 + vi.size as u64)) + } else { + false + } + }) + .ok_or(RelocationError::Error(String::from( + "no outer map found for BTF relocation", + )))?; + + let outer_map_name = *outer_map_name; + debug!( + ".maps relocation #{i}: found outer map {outer_map_name} for inner map {target_name}", + ); + + let is_prog_array = map.map_type() == bpf_map_type::BPF_MAP_TYPE_PROG_ARRAY as u32; + let is_map_in_map = map.map_type() == bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS as u32 + || map.map_type() == bpf_map_type::BPF_MAP_TYPE_HASH_OF_MAPS as u32; + + let targ_fd = if is_map_in_map { + // the target map is the map that matches the name of the symbol + if let Some((fd, _)) = maps_by_name.get(&target_name.as_str()) { + *fd + } else { + return Err(RelocationError::Error(format!( + "can't find map called {}", + target_name + ))); + } + } else if is_prog_array { + todo!("BPF_MAP_TYPE_PROG_ARRAY BTF relocation") + } else { + return Err(RelocationError::Error(format!( + "unsupported map type for BTF relocation: {}", + map.map_type() + ))); + }; + + let (_, map) = maps_by_name.get_mut(&outer_map_name).unwrap(); + + // get array index in the outer map + let outer_map_datasec = maps_section + .entries + .iter() + .find(|entry| { + if let Ok(BtfType::Var(v)) = btf.type_by_id(entry.btf_type) { + if let Ok(name) = btf.string_at(v.name_offset) { + name == *outer_map_name + } else { + false + } + } else { + false + } + }) + .ok_or(RelocationError::Error(format!( + "can't find map datasec for map name {}", + target_name + )))?; + + if let BtfType::Var(var) = btf + .type_by_id(outer_map_datasec.btf_type) + .map_err(|e| RelocationError::Error(format!("btf lookup error: {}", e)))? + { + let resolved_ty = btf + .resolve_type(var.btf_type) + .map_err(|e| RelocationError::Error(format!("btf lookup error: {}", e)))?; + if let BtfType::Struct(s) = btf + .type_by_id(resolved_ty) + .map_err(|e| RelocationError::Error(format!("btf lookup error: {}", e)))? + { + let last_member = s.members.last().ok_or(RelocationError::Error(format!( + "no members in struct for BTF relocation: {}", + target_name + )))?; + + let member_name = btf + .string_at(last_member.name_offset) + .map_err(|e| RelocationError::Error(format!("btf lookup error: {}", e)))?; + + if member_name != "values" { + return Err(RelocationError::Error(format!( + "unexpected member name for BTF relocation: {}", + member_name + ))); + } + + let member_offset = s.member_bit_offset(last_member) / 8; + if rel.offset - (outer_map_datasec.offset as u64) < (member_offset as u64) { + return Err(RelocationError::Error(format!( + "unexpected member offset for BTF relocation: {}", + member_offset + ))); + } + + let member_offset = + rel.offset - (outer_map_datasec.offset as u64) - member_offset as u64; + + if (member_offset % BPF_PTR_SZ as u64) != 0 { + return Err(RelocationError::Error(format!( + "unexpected member offset for BTF relocation: {}", + member_offset + ))); + } + + let member_index = member_offset as usize / BPF_PTR_SZ; + debug!(".maps relocation #{i}: setting {target_name} to index {member_index} of outer map {outer_map_name}"); + if !map.set_initial_map_fd(member_index, targ_fd) { + return Err(RelocationError::Error(format!( + "can't set map fd for BTF relocation: {}", + target_name + ))); + }; + } else { + return Err(RelocationError::Error(format!( + "unexpected BTF type for BTF relocation: {}", + resolved_ty + ))); + } + } + } + Ok(()) +} + struct FunctionLinker<'a> { functions: &'a BTreeMap<(usize, u64), Function>, linked_functions: HashMap, @@ -368,7 +580,7 @@ impl<'a> FunctionLinker<'a> { }) .filter(|(_rel, sym)| { // only consider text relocations, data relocations are - // relocated in relocate_maps() + // relocated in relocate_map_references() sym.kind == SymbolKind::Text || sym .section_index @@ -524,19 +736,23 @@ mod test { fn fake_legacy_map(symbol_index: usize) -> Map { Map::Legacy(LegacyMap { def: Default::default(), + inner_def: None, section_index: 0, section_kind: EbpfSectionKind::Undefined, symbol_index: Some(symbol_index), data: Vec::new(), + initial_slots: BTreeMap::new(), }) } fn fake_btf_map(symbol_index: usize) -> Map { Map::Btf(BtfMap { def: Default::default(), + inner_def: None, section_index: 0, symbol_index, data: Vec::new(), + initial_slots: BTreeMap::new(), }) } @@ -576,7 +792,7 @@ mod test { let map = fake_legacy_map(1); let maps_by_symbol = HashMap::from([(1, ("test_map", 1, &map))]); - relocate_maps( + relocate_map_references( &mut fun, relocations.iter(), &maps_by_section, @@ -632,7 +848,7 @@ mod test { (2, ("test_map_2", 2, &map_2)), ]); - relocate_maps( + relocate_map_references( &mut fun, relocations.iter(), &maps_by_section, @@ -671,7 +887,7 @@ mod test { let map = fake_btf_map(1); let maps_by_symbol = HashMap::from([(1, ("test_map", 1, &map))]); - relocate_maps( + relocate_map_references( &mut fun, relocations.iter(), &maps_by_section, @@ -727,7 +943,7 @@ mod test { (2, ("test_map_2", 2, &map_2)), ]); - relocate_maps( + relocate_map_references( &mut fun, relocations.iter(), &maps_by_section, diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 120b605c..15615735 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -35,13 +35,13 @@ use crate::{ UProbe, Xdp, }, sys::{ - bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, + bpf_load_btf, bpf_map_update_elem, 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, is_prog_type_supported, - retry_with_verifier_logs, + retry_with_verifier_logs, SyscallError, }, util::{bytes_of, bytes_of_slice, nr_cpus, page_size}, }; @@ -157,6 +157,7 @@ pub struct EbpfLoader<'a> { extensions: HashSet<&'a str>, verifier_log_level: VerifierLogLevel, allow_unsupported_maps: bool, + map_in_maps: HashMap<&'a str, (&'a str, Option<&'a [&'a str]>)>, } /// Builder style API for advanced loading of eBPF programs. @@ -195,6 +196,7 @@ impl<'a> EbpfLoader<'a> { extensions: HashSet::new(), verifier_log_level: VerifierLogLevel::default(), allow_unsupported_maps: false, + map_in_maps: HashMap::new(), } } @@ -393,6 +395,28 @@ impl<'a> EbpfLoader<'a> { })?) } + /// Marks the map with the provided name as a map-in-map. + /// + /// This is only required for older C-based eBPF programs that use + /// `bpf_map_def` style map definitions, or when working with aya-ebpf + /// map-in-maps. + /// + /// The eBPF Verifier needs to know the inner type of a map-in-map before + /// it can be used. This method allows you to specify the name of an inner + /// map to be used as a reference type. + /// + /// # Example + pub fn map_in_map( + &mut self, + name: &'a str, + inner: &'a str, + initial_values: Option<&'a [&'a str]>, + ) -> &mut Self { + self.map_in_maps + .insert(name.as_ref(), (inner, initial_values)); + self + } + /// Loads eBPF bytecode from a buffer. /// /// The buffer needs to be 4-bytes aligned. If you are bundling the bytecode statically @@ -418,6 +442,7 @@ impl<'a> EbpfLoader<'a> { extensions, verifier_log_level, allow_unsupported_maps, + map_in_maps: _, } = self; let mut obj = Object::parse(data)?; obj.patch_map_data(globals.clone())?; @@ -484,7 +509,30 @@ impl<'a> EbpfLoader<'a> { obj.relocate_btf(btf)?; } let mut maps = HashMap::new(); - for (name, mut obj) in obj.maps.drain() { + // To support map-in-maps, we need to guarantee that any map that could + // be used as a "template" for the inner dimensions of a map-in-map has + // been processed first. + + let map_in_maps_keys = obj + .maps + .iter() + .filter_map(|(name, obj)| { + if obj.map_type() == BPF_MAP_TYPE_HASH_OF_MAPS as u32 + || obj.map_type() == BPF_MAP_TYPE_ARRAY_OF_MAPS as u32 + { + Some(name.clone()) + } else { + None + } + }) + .collect::>(); + + let map_in_maps = map_in_maps_keys + .iter() + .map(|key| obj.maps.remove_entry(key).unwrap()) + .collect::>(); + + for (name, mut obj) in obj.maps.drain().chain(map_in_maps) { if let (false, EbpfSectionKind::Bss | EbpfSectionKind::Data | EbpfSectionKind::Rodata) = (FEATURES.bpf_global_data(), obj.section_kind()) { @@ -513,6 +561,24 @@ impl<'a> EbpfLoader<'a> { Ok(BPF_MAP_TYPE_DEVMAP | BPF_MAP_TYPE_DEVMAP_HASH) => { obj.set_value_size(if FEATURES.devmap_prog_id() { 8 } else { 4 }) } + Ok(BPF_MAP_TYPE_HASH_OF_MAPS | BPF_MAP_TYPE_ARRAY_OF_MAPS) => { + if obj.inner().is_none() { + let (inner_name, _) = + self.map_in_maps + .get(name.as_str()) + .ok_or(EbpfError::MapError(MapError::Error(format!( + "inner map {name} not found for map-in-map config" + ))))?; + + let inner_map: &MapData = + maps.get(&**inner_name) + .ok_or(EbpfError::MapError(MapError::Error(format!( + "inner map {name} is not a valid map" + ))))?; + + obj.set_legacy_inner(inner_map.obj()); + } + } _ => (), } let btf_fd = btf_fd.as_deref().map(|fd| fd.as_fd()); @@ -538,11 +604,68 @@ impl<'a> EbpfLoader<'a> { .map(|(section_index, _)| *section_index) .collect(); + obj.relocate_btf_maps( + maps.iter_mut() + .map(|(s, data)| (s.as_str(), data.fd().as_fd().as_raw_fd(), data.obj_mut())), + )?; obj.relocate_maps( maps.iter() .map(|(s, data)| (s.as_str(), data.fd().as_fd().as_raw_fd(), data.obj())), &text_sections, )?; + + // Attach Map-in-Maps + for (name, (_, initial_values)) in self.map_in_maps.iter() { + debug!("setting initial map fds for map-in-map {}", name); + if initial_values.is_none() { + continue; + } + let outer_map = maps + .get(&**name) + .ok_or(EbpfError::MapError(MapError::Error( + "map not found for map-in-map".to_string(), + )))?; + + for (i, inner_name) in initial_values.as_ref().unwrap().iter().enumerate() { + debug!("finding inner map {inner_name} for map-in-map {name}"); + let inner_map = + maps.get(&**inner_name) + .ok_or(EbpfError::MapError(MapError::Error(format!( + "inner map {inner_name} not found for map-in-map" + ))))?; + let key = Some(i as u32); + let value = inner_map.fd().as_fd().as_raw_fd(); + bpf_map_update_elem(outer_map.fd().as_fd(), key.as_ref(), &value, 0).map_err( + |(_, io_error)| { + EbpfError::MapError(MapError::SyscallError(SyscallError { + call: "bpf_map_update_elem", + io_error, + })) + }, + )?; + } + } + + for (name, map) in maps.iter_mut() { + if !map.obj().initial_map_fds().is_empty() + && map.obj().map_type() != BPF_MAP_TYPE_HASH_OF_MAPS as u32 + { + debug!("setting initial map fds for map {}", name); + for (i, fd) in map.obj().initial_map_fds().iter() { + debug!("setting initial map value for map {name}: key: #{i} value: {fd}"); + let key = Some(*i as u32); // TODO: What if this is a hashmap? + bpf_map_update_elem(map.fd().as_fd(), key.as_ref(), fd, 0).map_err( + |(_, io_error)| { + EbpfError::MapError(MapError::SyscallError(SyscallError { + call: "bpf_map_update_elem", + io_error, + })) + }, + )?; + } + } + } + obj.relocate_calls(&text_sections)?; obj.sanitize_functions(&FEATURES); @@ -762,6 +885,8 @@ fn parse_map( BPF_MAP_TYPE_DEVMAP => Map::DevMap(map), BPF_MAP_TYPE_DEVMAP_HASH => Map::DevMapHash(map), BPF_MAP_TYPE_XSKMAP => Map::XskMap(map), + BPF_MAP_TYPE_ARRAY_OF_MAPS => Map::ArrayOfMaps(map), + BPF_MAP_TYPE_HASH_OF_MAPS => Map::HashOfMaps(map), m_type => { if allow_unsupported_maps { Map::Unsupported(map) diff --git a/aya/src/maps/mod.rs b/aya/src/maps/mod.rs index 3bae55fa..c55b0299 100644 --- a/aya/src/maps/mod.rs +++ b/aya/src/maps/mod.rs @@ -80,6 +80,7 @@ pub mod bloom_filter; pub mod hash_map; mod info; pub mod lpm_trie; +pub mod of_maps; pub mod perf; pub mod queue; pub mod ring_buf; @@ -93,6 +94,7 @@ pub use bloom_filter::BloomFilter; pub use hash_map::{HashMap, PerCpuHashMap}; pub use info::{loaded_maps, MapInfo, MapType}; pub use lpm_trie::LpmTrie; +pub use of_maps::{Array as ArrayOfMaps, HashMap as HashMapOfMaps}; #[cfg(any(feature = "async_tokio", feature = "async_std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "async_tokio", feature = "async_std"))))] pub use perf::AsyncPerfEventArray; @@ -107,6 +109,10 @@ pub use xdp::{CpuMap, DevMap, DevMapHash, XskMap}; #[derive(Error, Debug)] /// Errors occuring from working with Maps pub enum MapError { + /// A map error + #[error("{0}")] + Error(String), + /// Invalid map type encontered #[error("invalid map type {map_type}")] InvalidMapType { @@ -277,6 +283,8 @@ fn maybe_warn_rlimit() { pub enum Map { /// An [`Array`] map. Array(MapData), + /// An [`ArrayOfMaps`] map. + ArrayOfMaps(MapData), /// A [`BloomFilter`] map. BloomFilter(MapData), /// A [`CpuMap`] map. @@ -287,6 +295,8 @@ pub enum Map { DevMapHash(MapData), /// A [`HashMap`] map. HashMap(MapData), + /// A [`HashOfMaps`] map. + HashOfMaps(MapData), /// A [`LpmTrie`] map. LpmTrie(MapData), /// A [`HashMap`] map that uses a LRU eviction policy. @@ -324,10 +334,12 @@ impl Map { fn map_type(&self) -> u32 { match self { Self::Array(map) => map.obj.map_type(), + Self::ArrayOfMaps(map) => map.obj.map_type(), Self::BloomFilter(map) => map.obj.map_type(), Self::CpuMap(map) => map.obj.map_type(), Self::DevMap(map) => map.obj.map_type(), Self::DevMapHash(map) => map.obj.map_type(), + Self::HashOfMaps(map) => map.obj.map_type(), Self::HashMap(map) => map.obj.map_type(), Self::LpmTrie(map) => map.obj.map_type(), Self::LruHashMap(map) => map.obj.map_type(), @@ -354,10 +366,12 @@ impl Map { pub fn pin>(&self, path: P) -> Result<(), PinError> { match self { Self::Array(map) => map.pin(path), + Self::ArrayOfMaps(map) => map.pin(path), Self::BloomFilter(map) => map.pin(path), Self::CpuMap(map) => map.pin(path), Self::DevMap(map) => map.pin(path), Self::DevMapHash(map) => map.pin(path), + Self::HashOfMaps(map) => map.pin(path), Self::HashMap(map) => map.pin(path), Self::LpmTrie(map) => map.pin(path), Self::LruHashMap(map) => map.pin(path), @@ -412,6 +426,7 @@ impl_map_pin!(() { DevMap, DevMapHash, XskMap, + ArrayOfMaps, }); impl_map_pin!((V) { @@ -421,6 +436,7 @@ impl_map_pin!((V) { BloomFilter, Queue, Stack, + HashMapOfMaps, }); impl_map_pin!((K, V) { @@ -488,6 +504,7 @@ impl_try_from_map!(() { SockMap, StackTraceMap, XskMap, + ArrayOfMaps, }); #[cfg(any(feature = "async_tokio", feature = "async_std"))] @@ -503,6 +520,7 @@ impl_try_from_map!((V) { Queue, SockHash, Stack, + HashMapOfMaps from HashMap, }); impl_try_from_map!((K, V) { @@ -583,6 +601,7 @@ impl MapData { let kernel_version = KernelVersion::current().unwrap(); #[cfg(test)] let kernel_version = KernelVersion::new(0xff, 0xff, 0xff); + let fd = bpf_create_map(&c_name, &obj, btf_fd, kernel_version).map_err(|(code, io_error)| { if kernel_version < KernelVersion::new(5, 11, 0) { @@ -759,6 +778,11 @@ impl MapData { obj } + pub(crate) fn obj_mut(&mut self) -> &mut obj::Map { + let Self { obj, fd: _ } = self; + obj + } + /// Returns the kernel's information about the loaded map. pub fn info(&self) -> Result { MapInfo::new_from_fd(self.fd.as_fd()) @@ -955,6 +979,8 @@ impl Deref for PerCpuValues { #[cfg(test)] mod test_utils { + use std::collections::BTreeMap; + use crate::{ bpf_map_def, generated::{bpf_cmd, bpf_map_type}, @@ -983,10 +1009,12 @@ mod test_utils { max_entries: 1024, ..Default::default() }, + inner_def: None, section_index: 0, section_kind: EbpfSectionKind::Maps, data: Vec::new(), symbol_index: None, + initial_slots: BTreeMap::new(), }) } @@ -1002,10 +1030,12 @@ mod test_utils { max_entries, ..Default::default() }, + inner_def: None, section_index: 0, section_kind: EbpfSectionKind::Maps, data: Vec::new(), symbol_index: None, + initial_slots: BTreeMap::new(), }) } } diff --git a/aya/src/maps/of_maps/array.rs b/aya/src/maps/of_maps/array.rs new file mode 100644 index 00000000..8cd17619 --- /dev/null +++ b/aya/src/maps/of_maps/array.rs @@ -0,0 +1,82 @@ +//! An array of eBPF maps. + +use std::{ + borrow::{Borrow, BorrowMut}, + os::fd::{AsFd as _, AsRawFd}, +}; + +use crate::{ + maps::{check_bounds, check_kv_size, MapData, MapError, MapFd}, + sys::{bpf_map_get_fd_by_id, bpf_map_lookup_elem, bpf_map_update_elem, SyscallError}, +}; + +/// An array of eBPF Maps +/// +/// A `Array` is used to store references to other maps. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 4.14. +#[doc(alias = "BPF_MAP_TYPE_ARRAY_OF_MAPS")] +pub struct Array { + pub(crate) inner: T, +} + +impl> Array { + pub(crate) fn new(map: T) -> Result { + let data = map.borrow(); + check_kv_size::(data)?; + Ok(Self { inner: map }) + } + + /// Returns the number of elements in the array. + /// + /// This corresponds to the value of `bpf_map_def::max_entries` on the eBPF side. + pub fn len(&self) -> u32 { + self.inner.borrow().obj.max_entries() + } + + /// Returns the value stored at the given index. + /// + /// # Errors + /// + /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] + /// if `bpf_map_lookup_elem` fails. + pub fn get(&self, index: &u32, flags: u64) -> Result { + let data = self.inner.borrow(); + check_bounds(data, *index)?; + let fd = data.fd().as_fd(); + + let value: Option = + bpf_map_lookup_elem(fd, index, flags).map_err(|(_, io_error)| SyscallError { + call: "bpf_map_lookup_elem", + io_error, + })?; + if let Some(value) = value { + let fd = bpf_map_get_fd_by_id(value)?; + Ok(MapFd::from_fd(fd)) + } else { + Err(MapError::KeyNotFound) + } + } +} +impl> Array { + /// Sets the value of the element at the given index. + /// + /// # Errors + /// + /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] + /// if `bpf_map_update_elem` fails. + pub fn set(&mut self, index: u32, value: &MapFd, flags: u64) -> Result<(), MapError> { + let data = self.inner.borrow_mut(); + check_bounds(data, index)?; + let fd = data.fd().as_fd(); + bpf_map_update_elem(fd, Some(&index), &value.as_fd().as_raw_fd(), flags).map_err( + |(_, io_error)| SyscallError { + call: "bpf_map_update_elem", + io_error, + }, + )?; + Ok(()) + } +} diff --git a/aya/src/maps/of_maps/hash_map.rs b/aya/src/maps/of_maps/hash_map.rs new file mode 100644 index 00000000..f662fc00 --- /dev/null +++ b/aya/src/maps/of_maps/hash_map.rs @@ -0,0 +1,75 @@ +use std::{ + borrow::{Borrow, BorrowMut}, + marker::PhantomData, + os::fd::{AsFd as _, AsRawFd as _}, +}; + +use crate::{ + maps::{check_kv_size, hash_map, MapData, MapError, MapFd}, + sys::{bpf_map_get_fd_by_id, bpf_map_lookup_elem, SyscallError}, + Pod, +}; + +/// An hashmap of eBPF Maps +/// +/// A `HashMap` is used to store references to other maps. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 4.14. +#[doc(alias = "BPF_MAP_TYPE_HASH")] +#[doc(alias = "BPF_MAP_TYPE_LRU_HASH")] +#[derive(Debug)] +pub struct HashMap { + pub(crate) inner: T, + _k: PhantomData, +} + +impl, K: Pod> HashMap { + pub(crate) fn new(map: T) -> Result { + let data = map.borrow(); + check_kv_size::(data)?; + + Ok(Self { + inner: map, + _k: PhantomData, + }) + } + + /// Returns a copy of the value associated with the key. + pub fn get(&self, key: &K, 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, + })?; + if let Some(value) = value { + let fd = bpf_map_get_fd_by_id(value)?; + Ok(MapFd::from_fd(fd)) + } else { + Err(MapError::KeyNotFound) + } + } +} + +impl, K: Pod> HashMap { + /// Inserts a key-value pair into the map. + pub fn insert( + &mut self, + key: impl Borrow, + value: &MapFd, + flags: u64, + ) -> Result<(), MapError> { + hash_map::insert( + self.inner.borrow_mut(), + key.borrow(), + &value.as_fd().as_raw_fd(), + flags, + ) + } + + /// Removes a key from the map. + pub fn remove(&mut self, key: &K) -> Result<(), MapError> { + hash_map::remove(self.inner.borrow_mut(), key) + } +} diff --git a/aya/src/maps/of_maps/mod.rs b/aya/src/maps/of_maps/mod.rs new file mode 100644 index 00000000..166f0f49 --- /dev/null +++ b/aya/src/maps/of_maps/mod.rs @@ -0,0 +1,6 @@ +//! Maps of maps +mod array; +mod hash_map; + +pub use array::Array; +pub use hash_map::HashMap; diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 08596a45..ab1d83f7 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -1,5 +1,6 @@ use std::{ cmp, + collections::BTreeMap, ffi::{c_char, c_long, CStr, CString}, io, iter, mem::{self, MaybeUninit}, @@ -61,6 +62,24 @@ pub(crate) fn bpf_create_map( u.max_entries = def.max_entries(); u.map_flags = def.map_flags(); + let inner_fd = match def.inner() { + Some(inner_def) => { + let inner_name = &[name.to_bytes(), b".inner\0"].concat(); + let c_inner_name = CStr::from_bytes_with_nul(inner_name) + .map_err(|_| (0, io::Error::from_raw_os_error(ENOSPC)))?; + Some(bpf_create_map( + c_inner_name, + &inner_def, + btf_fd, + kernel_version, + )?) + } + _ => None, + }; + if let Some(fd) = inner_fd.as_ref() { + u.inner_map_fd = fd.as_raw_fd() as u32; + } + if let obj::Map::Btf(m) = def { use bpf_map_type::*; @@ -962,10 +981,12 @@ pub(crate) fn is_bpf_global_data_supported() -> bool { max_entries: 1, ..Default::default() }, + inner_def: None, section_index: 0, section_kind: EbpfSectionKind::Maps, symbol_index: None, data: Vec::new(), + initial_slots: BTreeMap::new(), }), "aya_global", None, diff --git a/ebpf/aya-ebpf/src/maps/array.rs b/ebpf/aya-ebpf/src/maps/array.rs index a062ed5b..beb047ad 100644 --- a/ebpf/aya-ebpf/src/maps/array.rs +++ b/ebpf/aya-ebpf/src/maps/array.rs @@ -5,7 +5,7 @@ use aya_ebpf_cty::c_void; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_ARRAY}, helpers::bpf_map_lookup_elem, - maps::PinningType, + maps::{InnerMap, PinningType}, }; #[repr(transparent)] @@ -15,6 +15,7 @@ pub struct Array { } unsafe impl Sync for Array {} +unsafe impl InnerMap for Array {} impl Array { pub const fn with_max_entries(max_entries: u32, flags: u32) -> Array { @@ -65,10 +66,12 @@ impl Array { #[inline(always)] unsafe fn lookup(&self, index: u32) -> Option> { - let ptr = bpf_map_lookup_elem( - self.def.get() as *mut _, - &index as *const _ as *const c_void, - ); + let ptr = unsafe { + bpf_map_lookup_elem( + self.def.get() as *mut _, + &index as *const _ as *const c_void, + ) + }; NonNull::new(ptr as *mut T) } } diff --git a/ebpf/aya-ebpf/src/maps/array_of_maps.rs b/ebpf/aya-ebpf/src/maps/array_of_maps.rs new file mode 100644 index 00000000..cfa63075 --- /dev/null +++ b/ebpf/aya-ebpf/src/maps/array_of_maps.rs @@ -0,0 +1,66 @@ +use core::{cell::UnsafeCell, marker::PhantomData, mem, ptr::NonNull}; + +use aya_ebpf_cty::c_void; + +use crate::{ + bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_ARRAY_OF_MAPS}, + helpers::bpf_map_lookup_elem, + maps::{InnerMap, PinningType}, +}; + +#[repr(transparent)] +pub struct ArrayOfMaps { + def: UnsafeCell, + _t: PhantomData, +} + +unsafe impl Sync for ArrayOfMaps {} + +impl ArrayOfMaps { + pub const fn with_max_entries(max_entries: u32, flags: u32) -> ArrayOfMaps { + ArrayOfMaps { + def: UnsafeCell::new(bpf_map_def { + type_: BPF_MAP_TYPE_ARRAY_OF_MAPS, + key_size: mem::size_of::() as u32, + value_size: mem::size_of::() as u32, + max_entries, + map_flags: flags, + id: 0, + pinning: PinningType::None as u32, + }), + _t: PhantomData, + } + } + + pub const fn pinned(max_entries: u32, flags: u32) -> ArrayOfMaps { + ArrayOfMaps { + def: UnsafeCell::new(bpf_map_def { + type_: BPF_MAP_TYPE_ARRAY_OF_MAPS, + key_size: mem::size_of::() as u32, + value_size: mem::size_of::() as u32, + max_entries, + map_flags: flags, + id: 0, + pinning: PinningType::ByName as u32, + }), + _t: PhantomData, + } + } + + #[inline(always)] + pub fn get(&self, index: u32) -> Option<&T> { + // FIXME: alignment + unsafe { self.lookup(index).map(|p| p.as_ref()) } + } + + #[inline(always)] + unsafe fn lookup(&self, index: u32) -> Option> { + let ptr = unsafe { + bpf_map_lookup_elem( + self.def.get() as *mut _, + &index as *const _ as *const c_void, + ) + }; + NonNull::new(ptr as *mut T) + } +} diff --git a/ebpf/aya-ebpf/src/maps/bloom_filter.rs b/ebpf/aya-ebpf/src/maps/bloom_filter.rs index 210a9a95..03dcab47 100644 --- a/ebpf/aya-ebpf/src/maps/bloom_filter.rs +++ b/ebpf/aya-ebpf/src/maps/bloom_filter.rs @@ -5,7 +5,7 @@ use aya_ebpf_cty::c_void; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_BLOOM_FILTER}, helpers::{bpf_map_peek_elem, bpf_map_push_elem}, - maps::PinningType, + maps::{InnerMap, PinningType}, }; #[repr(transparent)] @@ -14,6 +14,9 @@ pub struct BloomFilter { _t: PhantomData, } +unsafe impl Sync for BloomFilter {} +unsafe impl InnerMap for BloomFilter {} + impl BloomFilter { pub const fn with_max_entries(max_entries: u32, flags: u32) -> BloomFilter { BloomFilter { diff --git a/ebpf/aya-ebpf/src/maps/hash_map.rs b/ebpf/aya-ebpf/src/maps/hash_map.rs index 765188dc..2432933c 100644 --- a/ebpf/aya-ebpf/src/maps/hash_map.rs +++ b/ebpf/aya-ebpf/src/maps/hash_map.rs @@ -8,7 +8,7 @@ use aya_ebpf_cty::{c_long, c_void}; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_HASH}, helpers::{bpf_map_delete_elem, bpf_map_lookup_elem, bpf_map_update_elem}, - maps::PinningType, + maps::{InnerMap, PinningType}, }; #[repr(transparent)] @@ -19,6 +19,7 @@ pub struct HashMap { } unsafe impl Sync for HashMap {} +unsafe impl InnerMap for HashMap {} impl HashMap { pub const fn with_max_entries(max_entries: u32, flags: u32) -> HashMap { @@ -54,7 +55,7 @@ impl HashMap { /// corruption in case of writes. #[inline] pub unsafe fn get(&self, key: &K) -> Option<&V> { - get(self.def.get(), key) + unsafe { get(self.def.get(), key) } } /// Retrieve the value associate with `key` from the map. @@ -93,6 +94,7 @@ pub struct LruHashMap { } unsafe impl Sync for LruHashMap {} +unsafe impl InnerMap for LruHashMap {} impl LruHashMap { pub const fn with_max_entries(max_entries: u32, flags: u32) -> LruHashMap { @@ -128,7 +130,7 @@ impl LruHashMap { /// corruption in case of writes. #[inline] pub unsafe fn get(&self, key: &K) -> Option<&V> { - get(self.def.get(), key) + unsafe { get(self.def.get(), key) } } /// Retrieve the value associate with `key` from the map. @@ -167,6 +169,7 @@ pub struct PerCpuHashMap { } unsafe impl Sync for PerCpuHashMap {} +unsafe impl InnerMap for PerCpuHashMap {} impl PerCpuHashMap { pub const fn with_max_entries(max_entries: u32, flags: u32) -> PerCpuHashMap { @@ -202,7 +205,7 @@ impl PerCpuHashMap { /// corruption in case of writes. #[inline] pub unsafe fn get(&self, key: &K) -> Option<&V> { - get(self.def.get(), key) + unsafe { get(self.def.get(), key) } } /// Retrieve the value associate with `key` from the map. @@ -241,6 +244,7 @@ pub struct LruPerCpuHashMap { } unsafe impl Sync for LruPerCpuHashMap {} +unsafe impl InnerMap for LruPerCpuHashMap {} impl LruPerCpuHashMap { pub const fn with_max_entries(max_entries: u32, flags: u32) -> LruPerCpuHashMap { @@ -276,7 +280,7 @@ impl LruPerCpuHashMap { /// corruption in case of writes. #[inline] pub unsafe fn get(&self, key: &K) -> Option<&V> { - get(self.def.get(), key) + unsafe { get(self.def.get(), key) } } /// Retrieve the value associate with `key` from the map. @@ -335,7 +339,7 @@ fn get_ptr(def: *mut bpf_map_def, key: &K) -> Option<*const V> { #[inline] unsafe fn get<'a, K, V>(def: *mut bpf_map_def, key: &K) -> Option<&'a V> { - get_ptr(def, key).map(|p| &*p) + get_ptr(def, key).map(|p| unsafe { &*p }) } #[inline] diff --git a/ebpf/aya-ebpf/src/maps/hash_of_maps.rs b/ebpf/aya-ebpf/src/maps/hash_of_maps.rs new file mode 100644 index 00000000..36a41bcc --- /dev/null +++ b/ebpf/aya-ebpf/src/maps/hash_of_maps.rs @@ -0,0 +1,64 @@ +use core::{cell::UnsafeCell, marker::PhantomData, mem, ptr::NonNull}; + +use aya_ebpf_cty::c_void; + +use crate::{ + bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_HASH_OF_MAPS}, + helpers::bpf_map_lookup_elem, + maps::{InnerMap, PinningType}, +}; + +#[repr(transparent)] +pub struct HashOfMaps { + def: UnsafeCell, + _k: PhantomData, + _v: PhantomData, +} + +impl HashOfMaps { + pub const fn with_max_entries(max_entries: u32, flags: u32) -> HashOfMaps { + HashOfMaps { + def: UnsafeCell::new(bpf_map_def { + type_: BPF_MAP_TYPE_HASH_OF_MAPS, + key_size: mem::size_of::() as u32, + value_size: mem::size_of::() as u32, + max_entries, + map_flags: flags, + id: 0, + pinning: PinningType::None as u32, + }), + _k: PhantomData, + _v: PhantomData, + } + } + + pub const fn pinned(max_entries: u32, flags: u32) -> HashOfMaps { + HashOfMaps { + def: UnsafeCell::new(bpf_map_def { + type_: BPF_MAP_TYPE_HASH_OF_MAPS, + key_size: mem::size_of::() as u32, + value_size: mem::size_of::() as u32, + max_entries, + map_flags: flags, + id: 0, + pinning: PinningType::ByName as u32, + }), + _k: PhantomData, + _v: PhantomData, + } + } + + /// Retrieve the value associate with `key` from the map. + /// This function is unsafe. Unless the map flag `BPF_F_NO_PREALLOC` is used, the kernel does not + /// make guarantee on the atomicity of `insert` or `remove`, and any element removed from the + /// map might get aliased by another element in the map, causing garbage to be read, or + /// corruption in case of writes. + #[inline] + pub unsafe fn get(&self, key: &K) -> Option> { + let value = unsafe { + bpf_map_lookup_elem(self.def.get() as *mut _, key as *const _ as *const c_void) + }; + // FIXME: alignment + NonNull::new(value as *mut _) + } +} diff --git a/ebpf/aya-ebpf/src/maps/lpm_trie.rs b/ebpf/aya-ebpf/src/maps/lpm_trie.rs index cbbcd411..4470a015 100644 --- a/ebpf/aya-ebpf/src/maps/lpm_trie.rs +++ b/ebpf/aya-ebpf/src/maps/lpm_trie.rs @@ -6,7 +6,7 @@ use aya_ebpf_cty::{c_long, c_void}; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_LPM_TRIE}, helpers::{bpf_map_delete_elem, bpf_map_lookup_elem, bpf_map_update_elem}, - maps::PinningType, + maps::{InnerMap, PinningType}, }; #[repr(transparent)] @@ -17,6 +17,7 @@ pub struct LpmTrie { } unsafe impl Sync for LpmTrie {} +unsafe impl InnerMap for LpmTrie {} #[repr(C, packed)] pub struct Key { diff --git a/ebpf/aya-ebpf/src/maps/mod.rs b/ebpf/aya-ebpf/src/maps/mod.rs index ead24dc3..28293694 100644 --- a/ebpf/aya-ebpf/src/maps/mod.rs +++ b/ebpf/aya-ebpf/src/maps/mod.rs @@ -6,8 +6,10 @@ pub(crate) enum PinningType { } pub mod array; +pub mod array_of_maps; pub mod bloom_filter; pub mod hash_map; +pub mod hash_of_maps; pub mod lpm_trie; pub mod per_cpu_array; pub mod perf; @@ -21,8 +23,10 @@ pub mod stack_trace; pub mod xdp; pub use array::Array; +pub use array_of_maps::ArrayOfMaps; pub use bloom_filter::BloomFilter; pub use hash_map::{HashMap, LruHashMap, LruPerCpuHashMap, PerCpuHashMap}; +pub use hash_of_maps::HashOfMaps; pub use lpm_trie::LpmTrie; pub use per_cpu_array::PerCpuArray; pub use perf::{PerfEventArray, PerfEventByteArray}; @@ -34,3 +38,6 @@ pub use sock_map::SockMap; pub use stack::Stack; pub use stack_trace::StackTrace; pub use xdp::{CpuMap, DevMap, DevMapHash, XskMap}; + +// Map is a marker trait for all eBPF maps that can be used in a map of maps. +pub unsafe trait InnerMap {} diff --git a/ebpf/aya-ebpf/src/maps/per_cpu_array.rs b/ebpf/aya-ebpf/src/maps/per_cpu_array.rs index f353d50f..a1c1d4d2 100644 --- a/ebpf/aya-ebpf/src/maps/per_cpu_array.rs +++ b/ebpf/aya-ebpf/src/maps/per_cpu_array.rs @@ -5,7 +5,7 @@ use aya_ebpf_cty::c_void; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_PERCPU_ARRAY}, helpers::bpf_map_lookup_elem, - maps::PinningType, + maps::{InnerMap, PinningType}, }; #[repr(transparent)] @@ -15,6 +15,7 @@ pub struct PerCpuArray { } unsafe impl Sync for PerCpuArray {} +unsafe impl InnerMap for PerCpuArray {} impl PerCpuArray { pub const fn with_max_entries(max_entries: u32, flags: u32) -> PerCpuArray { diff --git a/ebpf/aya-ebpf/src/maps/queue.rs b/ebpf/aya-ebpf/src/maps/queue.rs index 8c8f0bb1..11ec9cba 100644 --- a/ebpf/aya-ebpf/src/maps/queue.rs +++ b/ebpf/aya-ebpf/src/maps/queue.rs @@ -3,7 +3,7 @@ use core::{cell::UnsafeCell, marker::PhantomData, mem}; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_QUEUE}, helpers::{bpf_map_pop_elem, bpf_map_push_elem}, - maps::PinningType, + maps::{InnerMap, PinningType}, }; #[repr(transparent)] @@ -13,6 +13,7 @@ pub struct Queue { } unsafe impl Sync for Queue {} +unsafe impl InnerMap for Queue {} impl Queue { pub const fn with_max_entries(max_entries: u32, flags: u32) -> Queue { diff --git a/ebpf/aya-ebpf/src/maps/ring_buf.rs b/ebpf/aya-ebpf/src/maps/ring_buf.rs index 679b65c8..9a88e2b9 100644 --- a/ebpf/aya-ebpf/src/maps/ring_buf.rs +++ b/ebpf/aya-ebpf/src/maps/ring_buf.rs @@ -11,7 +11,7 @@ use crate::{ bpf_ringbuf_discard, bpf_ringbuf_output, bpf_ringbuf_query, bpf_ringbuf_reserve, bpf_ringbuf_submit, }, - maps::PinningType, + maps::{InnerMap, PinningType}, }; #[cfg(unstable)] @@ -31,6 +31,7 @@ pub struct RingBuf { } unsafe impl Sync for RingBuf {} +unsafe impl InnerMap for RingBuf {} /// A ring buffer entry, returned from [`RingBuf::reserve`]. /// diff --git a/ebpf/aya-ebpf/src/maps/sock_hash.rs b/ebpf/aya-ebpf/src/maps/sock_hash.rs index 3ccf52b8..b4df1710 100644 --- a/ebpf/aya-ebpf/src/maps/sock_hash.rs +++ b/ebpf/aya-ebpf/src/maps/sock_hash.rs @@ -8,7 +8,7 @@ use crate::{ bpf_map_lookup_elem, bpf_msg_redirect_hash, bpf_sk_assign, bpf_sk_redirect_hash, bpf_sk_release, bpf_sock_hash_update, }, - maps::PinningType, + maps::{InnerMap, PinningType}, programs::{SkBuffContext, SkLookupContext, SkMsgContext}, EbpfContext, }; @@ -20,6 +20,7 @@ pub struct SockHash { } unsafe impl Sync for SockHash {} +unsafe impl InnerMap for SockHash {} impl SockHash { pub const fn with_max_entries(max_entries: u32, flags: u32) -> SockHash { diff --git a/ebpf/aya-ebpf/src/maps/sock_map.rs b/ebpf/aya-ebpf/src/maps/sock_map.rs index 5d741a9a..c74e0b7e 100644 --- a/ebpf/aya-ebpf/src/maps/sock_map.rs +++ b/ebpf/aya-ebpf/src/maps/sock_map.rs @@ -8,7 +8,7 @@ use crate::{ bpf_map_lookup_elem, bpf_msg_redirect_map, bpf_sk_assign, bpf_sk_redirect_map, bpf_sk_release, bpf_sock_map_update, }, - maps::PinningType, + maps::{InnerMap, PinningType}, programs::{SkBuffContext, SkLookupContext, SkMsgContext}, EbpfContext, }; @@ -19,6 +19,7 @@ pub struct SockMap { } unsafe impl Sync for SockMap {} +unsafe impl InnerMap for SockMap {} impl SockMap { pub const fn with_max_entries(max_entries: u32, flags: u32) -> SockMap { diff --git a/ebpf/aya-ebpf/src/maps/stack.rs b/ebpf/aya-ebpf/src/maps/stack.rs index 6328693d..30bc5eb4 100644 --- a/ebpf/aya-ebpf/src/maps/stack.rs +++ b/ebpf/aya-ebpf/src/maps/stack.rs @@ -3,7 +3,7 @@ use core::{marker::PhantomData, mem}; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_STACK}, helpers::{bpf_map_pop_elem, bpf_map_push_elem}, - maps::PinningType, + maps::{InnerMap, PinningType}, }; #[repr(transparent)] @@ -12,6 +12,9 @@ pub struct Stack { _t: PhantomData, } +unsafe impl Sync for Stack {} +unsafe impl InnerMap for Stack {} + impl Stack { pub const fn with_max_entries(max_entries: u32, flags: u32) -> Stack { Stack { diff --git a/ebpf/aya-ebpf/src/maps/stack_trace.rs b/ebpf/aya-ebpf/src/maps/stack_trace.rs index 6685b4c5..fa3b7541 100644 --- a/ebpf/aya-ebpf/src/maps/stack_trace.rs +++ b/ebpf/aya-ebpf/src/maps/stack_trace.rs @@ -3,7 +3,7 @@ use core::{cell::UnsafeCell, mem}; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_STACK_TRACE}, helpers::bpf_get_stackid, - maps::PinningType, + maps::{InnerMap, PinningType}, EbpfContext, }; @@ -13,6 +13,7 @@ pub struct StackTrace { } unsafe impl Sync for StackTrace {} +unsafe impl InnerMap for StackTrace {} const PERF_MAX_STACK_DEPTH: u32 = 127; diff --git a/ebpf/aya-ebpf/src/maps/xdp/cpu_map.rs b/ebpf/aya-ebpf/src/maps/xdp/cpu_map.rs index 665526ba..aff95b38 100644 --- a/ebpf/aya-ebpf/src/maps/xdp/cpu_map.rs +++ b/ebpf/aya-ebpf/src/maps/xdp/cpu_map.rs @@ -5,7 +5,7 @@ use aya_ebpf_bindings::bindings::bpf_cpumap_val; use super::try_redirect_map; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_CPUMAP}, - maps::PinningType, + maps::{InnerMap, PinningType}, }; /// An array of available CPUs. @@ -36,6 +36,7 @@ pub struct CpuMap { } unsafe impl Sync for CpuMap {} +unsafe impl InnerMap for CpuMap {} impl CpuMap { /// Creates a [`CpuMap`] with a set maximum number of elements. diff --git a/ebpf/aya-ebpf/src/maps/xdp/dev_map.rs b/ebpf/aya-ebpf/src/maps/xdp/dev_map.rs index 209349bb..3e0a9390 100644 --- a/ebpf/aya-ebpf/src/maps/xdp/dev_map.rs +++ b/ebpf/aya-ebpf/src/maps/xdp/dev_map.rs @@ -7,7 +7,7 @@ use super::try_redirect_map; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_DEVMAP}, helpers::bpf_map_lookup_elem, - maps::PinningType, + maps::{InnerMap, PinningType}, }; /// An array of network devices. @@ -37,6 +37,7 @@ pub struct DevMap { } unsafe impl Sync for DevMap {} +unsafe impl InnerMap for DevMap {} impl DevMap { /// Creates a [`DevMap`] with a set maximum number of elements. diff --git a/ebpf/aya-ebpf/src/maps/xdp/dev_map_hash.rs b/ebpf/aya-ebpf/src/maps/xdp/dev_map_hash.rs index 64dfb545..c342fd58 100644 --- a/ebpf/aya-ebpf/src/maps/xdp/dev_map_hash.rs +++ b/ebpf/aya-ebpf/src/maps/xdp/dev_map_hash.rs @@ -7,7 +7,7 @@ use super::{dev_map::DevMapValue, try_redirect_map}; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH}, helpers::bpf_map_lookup_elem, - maps::PinningType, + maps::{InnerMap, PinningType}, }; /// A map of network devices. @@ -39,6 +39,7 @@ pub struct DevMapHash { } unsafe impl Sync for DevMapHash {} +unsafe impl InnerMap for DevMapHash {} impl DevMapHash { /// Creates a [`DevMapHash`] with a set maximum number of elements. diff --git a/ebpf/aya-ebpf/src/maps/xdp/xsk_map.rs b/ebpf/aya-ebpf/src/maps/xdp/xsk_map.rs index 4ce352ec..2a89f956 100644 --- a/ebpf/aya-ebpf/src/maps/xdp/xsk_map.rs +++ b/ebpf/aya-ebpf/src/maps/xdp/xsk_map.rs @@ -7,7 +7,7 @@ use super::try_redirect_map; use crate::{ bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_XSKMAP}, helpers::bpf_map_lookup_elem, - maps::PinningType, + maps::{InnerMap, PinningType}, }; /// An array of AF_XDP sockets. @@ -58,6 +58,7 @@ pub struct XskMap { } unsafe impl Sync for XskMap {} +unsafe impl InnerMap for XskMap {} impl XskMap { /// Creates a [`XskMap`] with a set maximum number of elements. diff --git a/test/integration-ebpf/Cargo.toml b/test/integration-ebpf/Cargo.toml index 24780717..3990eb31 100644 --- a/test/integration-ebpf/Cargo.toml +++ b/test/integration-ebpf/Cargo.toml @@ -85,3 +85,7 @@ path = "src/xdp_sec.rs" [[bin]] name = "uprobe_cookie" path = "src/uprobe_cookie.rs" + +[[bin]] +name = "map_of_maps" +path = "src/map_of_maps.rs" diff --git a/test/integration-ebpf/src/map_of_maps.rs b/test/integration-ebpf/src/map_of_maps.rs new file mode 100644 index 00000000..f3c62f3c --- /dev/null +++ b/test/integration-ebpf/src/map_of_maps.rs @@ -0,0 +1,44 @@ +#![no_std] +#![no_main] + +use aya_ebpf::{ + bindings::xdp_action, + macros::{map, uprobe}, + maps::{Array, ArrayOfMaps}, + programs::ProbeContext, +}; + +#[map] +static OUTER: ArrayOfMaps> = ArrayOfMaps::with_max_entries(10, 0); + +#[map] +static INNER: Array = Array::with_max_entries(10, 0); + +#[map] +static INNER_2: Array = Array::with_max_entries(10, 0); + +#[uprobe] +pub fn mim_test_array(_ctx: ProbeContext) -> u32 { + if let Some(map) = OUTER.get(0) { + if let Some(idx_0) = map.get_ptr_mut(0) { + unsafe { + *idx_0 = 42; + } + } + } + if let Some(map) = OUTER.get(1) { + if let Some(idx_0) = map.get_ptr_mut(0) { + unsafe { + *idx_0 = 24; + } + } + } + + xdp_action::XDP_PASS +} + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/test/integration-test/bpf/ofmaps.bpf.c b/test/integration-test/bpf/ofmaps.bpf.c new file mode 100644 index 00000000..bf013ce2 --- /dev/null +++ b/test/integration-test/bpf/ofmaps.bpf.c @@ -0,0 +1,64 @@ +// clang-format off +#include +#include +// clang-format on + +struct inner_map_type { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, __u32); + __uint(max_entries, 10); // Size is different from the outer map + __uint(map_flags, BPF_F_INNER_MAP); // Flag required due to ^^^ +} inner_map SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS); + __type(key, __u32); // value omitted as should be fixed by loader + __uint(max_entries, 1); + __array(values, struct inner_map_type); +} outer_array_map SEC(".maps") = { + .values = + { + [0] = &inner_map, + }, +}; + +struct { + __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS); + __type(key, __u32); // value omitted as should be fixed by loader + __uint(max_entries, 1); + __array(values, struct inner_map_type); +} outer_hash_map SEC(".maps") = { + .values = + { + [0] = &inner_map, + }, +}; + +static int map_in_map_test(void *outer_map) { + int key = 0; + int value = 42; + void *inner_map; + + inner_map = bpf_map_lookup_elem(outer_map, &key); + if (!inner_map) + return 0; + + bpf_map_update_elem(inner_map, &key, &value, 0); + + return 0; +} + +SEC("xdp") +int mim_test_array(struct xdp_md *ctx) { + map_in_map_test(&outer_array_map); + return XDP_PASS; +} + +SEC("xdp") +int mim_test_hash(struct xdp_md *ctx) { + map_in_map_test(&outer_hash_map); + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; diff --git a/test/integration-test/build.rs b/test/integration-test/build.rs index d7ae6ff0..aec4a2ab 100644 --- a/test/integration-test/build.rs +++ b/test/integration-test/build.rs @@ -66,6 +66,7 @@ fn main() -> Result<()> { ("iter.bpf.c", true), ("main.bpf.c", false), ("multimap-btf.bpf.c", false), + ("ofmaps.bpf.c", false), ("reloc.bpf.c", true), ("text_64_64_reloc.c", false), ("variables_reloc.bpf.c", false), diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index 5dcef22a..49b99300 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -5,6 +5,7 @@ pub const ITER_TASK: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/i pub const MAIN: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/main.bpf.o")); pub const MULTIMAP_BTF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/multimap-btf.bpf.o")); +pub const OFMAPS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ofmaps.bpf.o")); pub const RELOC_BPF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.o")); pub const RELOC_BTF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.target.o")); @@ -32,6 +33,7 @@ pub const TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/test") pub const TWO_PROGS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/two_progs")); pub const XDP_SEC: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/xdp_sec")); pub const UPROBE_COOKIE: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/uprobe_cookie")); +pub const OFMAPS_RUST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/map_of_maps")); #[cfg(test)] mod tests; diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs index 0b4f176e..e5c44d19 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/src/tests/load.rs @@ -7,7 +7,7 @@ use aya::{ loaded_links, loaded_programs, KProbe, TracePoint, UProbe, Xdp, XdpFlags, }, util::KernelVersion, - Ebpf, + Ebpf, EbpfLoader, }; use aya_obj::programs::XdpAttachType; use test_log::test; @@ -575,3 +575,63 @@ fn pin_lifecycle_uprobe() { // Make sure the function isn't optimized out. uprobe_function(); } + +#[test] +fn ofmaps_array() { + let mut bpf = Ebpf::load(crate::OFMAPS).unwrap(); + let prog: &mut Xdp = bpf + .program_mut("mim_test_array") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + prog.attach("lo", XdpFlags::default()).unwrap(); + + assert_loaded("mim_test_array"); +} + +#[test] +fn ofmaps_hash() { + let mut bpf = Ebpf::load(crate::OFMAPS).unwrap(); + let prog: &mut Xdp = bpf + .program_mut("mim_test_hash") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + prog.attach("lo", XdpFlags::default()).unwrap(); + + assert_loaded("mim_test_hash"); +} + +#[test] +fn test_ofmaps_rust() { + let mut bpf = EbpfLoader::new() + .map_in_map("OUTER", "INNER", Some(&["INNER", "INNER_2"])) + .load(crate::OFMAPS_RUST) + .unwrap(); + let prog: &mut UProbe = bpf + .program_mut("mim_test_array") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + prog.attach("trigger_mim_test_program", "/proc/self/exe", None, None) + .unwrap(); + + assert_loaded("mim_test_array"); + + trigger_mim_test_program(); + + let m = aya::maps::Array::<_, u32>::try_from(bpf.map("INNER").unwrap()).unwrap(); + assert_eq!(m.get(&0, 0).unwrap(), 42); + + let m = aya::maps::Array::<_, u32>::try_from(bpf.map("INNER_2").unwrap()).unwrap(); + assert_eq!(m.get(&0, 0).unwrap(), 24); +} + +#[no_mangle] +#[inline(never)] +pub extern "C" fn trigger_mim_test_program() { + core::hint::black_box(trigger_mim_test_program); +}