From f32db4014afbf9f4b13cc2fa2c8fc3ce06639475 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Mon, 11 Oct 2021 11:09:13 +0100 Subject: [PATCH] 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 | 236 +++++++++++++++++++-- aya/src/bpf.rs | 131 +++++++++++- aya/src/maps/mod.rs | 33 ++- 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, 1107 insertions(+), 85 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 51f9594b1..fd35de59a 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 f1054707c..bb84845c1 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 902c5b179..90bf2b7a1 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 cfa3f613a..ac4175289 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 b46b277cd..49bead5ac 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,181 @@ 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, (std::os::fd::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}"); + //TODO: Handle error + let _ = map.set_initial_map_fd(member_index, targ_fd); + } 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 +576,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 +732,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 +788,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 +844,7 @@ mod test { (2, ("test_map_2", 2, &map_2)), ]); - relocate_maps( + relocate_map_references( &mut fun, relocations.iter(), &maps_by_section, @@ -671,7 +883,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 +939,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 120b605c5..15615735c 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 3bae55fa3..75b59be40 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; @@ -95,8 +96,8 @@ pub use info::{loaded_maps, MapInfo, MapType}; pub use lpm_trie::LpmTrie; #[cfg(any(feature = "async_tokio", feature = "async_std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "async_tokio", feature = "async_std"))))] -pub use perf::AsyncPerfEventArray; -pub use perf::PerfEventArray; +pub use of_maps::{Array as ArrayOfMaps, HashMap as HashMapOfMaps}; +pub use perf::{AsyncPerfEventArray, PerfEventArray}; pub use queue::Queue; pub use ring_buf::RingBuf; pub use sock::{SockHash, SockMap}; @@ -107,6 +108,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 +282,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 +294,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 +333,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 +365,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 +425,7 @@ impl_map_pin!(() { DevMap, DevMapHash, XskMap, + ArrayOfMaps, }); impl_map_pin!((V) { @@ -421,6 +435,7 @@ impl_map_pin!((V) { BloomFilter, Queue, Stack, + HashMapOfMaps, }); impl_map_pin!((K, V) { @@ -488,6 +503,7 @@ impl_try_from_map!(() { SockMap, StackTraceMap, XskMap, + ArrayOfMaps, }); #[cfg(any(feature = "async_tokio", feature = "async_std"))] @@ -503,6 +519,7 @@ impl_try_from_map!((V) { Queue, SockHash, Stack, + HashMapOfMaps from HashMap, }); impl_try_from_map!((K, V) { @@ -583,6 +600,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 +777,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 +978,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 +1008,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 +1029,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 000000000..8cd176196 --- /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 000000000..f662fc00d --- /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 000000000..166f0f492 --- /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 08596a453..ab1d83f7d 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 a062ed5bc..beb047ad1 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 000000000..cfa630757 --- /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 210a9a95c..03dcab470 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 765188dce..2432933c3 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 000000000..36a41bcc9 --- /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 cbbcd411e..4470a0159 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 ead24dc38..28293694f 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 f353d50fc..a1c1d4d2a 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 8c8f0bb14..11ec9cbaa 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 679b65c82..9a88e2b97 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 3ccf52b8b..b4df17104 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 5d741a9a3..c74e0b7e1 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 6328693d8..30bc5eb4b 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 6685b4c5a..fa3b7541b 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 665526ba5..aff95b38c 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 209349bb4..3e0a9390b 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 64dfb5458..c342fd584 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 4ce352ecd..2a89f956c 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 247807171..3990eb31c 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 000000000..f3c62f3c9 --- /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 000000000..bf013ce21 --- /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 d7ae6ff0a..aec4a2ab3 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 5dcef22ad..49b99300a 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 0b4f176e5..e5c44d19e 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); +}