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 <dave@dtucker.co.uk>
pull/70/merge^2
Dave Tucker 3 years ago
parent f9edaf9c23
commit 078c6381ec

@ -155,6 +155,10 @@ pub enum BtfError {
/// unable to get symbol name /// unable to get symbol name
#[error("Unable to get symbol name")] #[error("Unable to get symbol name")]
InvalidSymbolName, InvalidSymbolName,
/// an error occurred while parsing a the BTF data
#[error("BTF error: {0}")]
BtfError(String),
} }
/// Available BTF features /// Available BTF features
@ -591,14 +595,6 @@ impl Btf {
for e in entries.iter_mut() { for e in entries.iter_mut() {
if let BtfType::Var(var) = types.type_by_id(e.btf_type)? { if let BtfType::Var(var) = types.type_by_id(e.btf_type)? {
let var_name = self.string_at(var.name_offset)?; 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()) { let offset = match symbol_offsets.get(var_name.as_ref()) {
Some(offset) => offset, Some(offset) => offset,
None => { None => {
@ -612,6 +608,14 @@ impl Btf {
"{} {}: VAR {}: fixup offset {}", "{} {}: VAR {}: fixup offset {}",
kind, name, var_name, offset kind, name, var_name, offset
); );
if var.linkage == VarLinkage::Static {
debug!(
"{} {}: VAR {}: linkage fixup not required",
kind, name, var_name
);
continue;
}
} else { } else {
return Err(BtfError::InvalidDatasec); return Err(BtfError::InvalidDatasec);
} }

@ -42,7 +42,7 @@
//! #[cfg(not(feature = "std"))] //! #[cfg(not(feature = "std"))]
//! let text_sections = hashbrown::HashSet::new(); //! let text_sections = hashbrown::HashSet::new();
//! object.relocate_calls(&text_sections).unwrap(); //! 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 //! // Run with rbpf
//! let function = object.functions.get(&object.programs["prog_name"].function_key()).unwrap(); //! let function = object.functions.get(&object.programs["prog_name"].function_key()).unwrap();

@ -1,6 +1,6 @@
//! Map struct and type bindings. //! Map struct and type bindings.
use alloc::vec::Vec; use alloc::{collections::BTreeMap, vec::Vec};
use core::mem; use core::mem;
use crate::{EbpfSectionKind, InvalidTypeBinding}; use crate::{EbpfSectionKind, InvalidTypeBinding};
@ -248,6 +248,64 @@ impl Map {
Map::Btf(m) => Some(m.symbol_index), 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<Map> {
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<usize, i32> {
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. /// A map declared with legacy BPF map declaration style, most likely from a `maps` section.
@ -258,6 +316,8 @@ impl Map {
pub struct LegacyMap { pub struct LegacyMap {
/// The definition of the map /// The definition of the map
pub def: bpf_map_def, pub def: bpf_map_def,
/// The defintion of the inner map, in case of a map of maps
pub inner_def: Option<bpf_map_def>,
/// The section index /// The section index
pub section_index: usize, pub section_index: usize,
/// The section kind /// The section kind
@ -270,6 +330,8 @@ pub struct LegacyMap {
pub symbol_index: Option<usize>, pub symbol_index: Option<usize>,
/// The map data /// The map data
pub data: Vec<u8>, pub data: Vec<u8>,
/// The initial slots of a map-of-maps
pub initial_slots: BTreeMap<usize, i32>,
} }
/// A BTF-defined map, most likely from a `.maps` section. /// A BTF-defined map, most likely from a `.maps` section.
@ -277,7 +339,11 @@ pub struct LegacyMap {
pub struct BtfMap { pub struct BtfMap {
/// The definition of the map /// The definition of the map
pub def: BtfMapDef, pub def: BtfMapDef,
/// The defintion of the inner map, in case of a map of maps
pub inner_def: Option<BtfMapDef>,
pub(crate) section_index: usize, pub(crate) section_index: usize,
pub(crate) symbol_index: usize, pub(crate) symbol_index: usize,
pub(crate) data: Vec<u8>, pub(crate) data: Vec<u8>,
/// The initial slots of a map-of-maps
pub initial_slots: BTreeMap<usize, i32>,
} }

@ -4,6 +4,7 @@ use alloc::{
borrow::ToOwned, borrow::ToOwned,
collections::BTreeMap, collections::BTreeMap,
ffi::CString, ffi::CString,
format,
string::{String, ToString}, string::{String, ToString},
vec, vec,
vec::Vec, vec::Vec,
@ -20,10 +21,12 @@ use object::{
use crate::{ use crate::{
btf::{ btf::{
Array, Btf, BtfError, BtfExt, BtfFeatures, BtfType, DataSecEntry, FuncSecInfo, LineSecInfo, Array, Btf, BtfError, BtfExt, BtfFeatures, BtfType, DataSecEntry, FuncSecInfo, LineSecInfo,
Struct,
}, },
generated::{ generated::{
bpf_insn, bpf_map_info, bpf_map_type::BPF_MAP_TYPE_ARRAY, BPF_CALL, BPF_F_RDONLY_PROG, bpf_insn, bpf_map_info,
BPF_JMP, BPF_K, 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}, maps::{bpf_map_def, BtfMap, BtfMapDef, LegacyMap, Map, PinningType, MINIMUM_MAP_SIZE},
programs::{ 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(); let mut symbols_by_address = HashMap::new();
for sym in self.symbol_table.values() { 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) = let (func_info, line_info, func_info_rec_size, line_info_rec_size) =
get_func_and_line_info(self.btf_ext.as_ref(), sym, &section, offset, false); get_func_and_line_info(self.btf_ext.as_ref(), sym, section, offset, false);
self.functions.insert( self.functions.insert(
(section.index.0, sym.address), (section.index.0, sym.address),
@ -732,17 +735,6 @@ impl Object {
offset += sym.size as usize; 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(()) Ok(())
} }
@ -773,7 +765,7 @@ impl Object {
if type_name == section.name { if type_name == section.name {
// each btf_var_secinfo contains a map // each btf_var_secinfo contains a map
for info in &datasec.entries { 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 = let symbol_index =
maps.get(&map_name) maps.get(&map_name)
.ok_or_else(|| ParseError::SymbolNotFound { .ok_or_else(|| ParseError::SymbolNotFound {
@ -783,9 +775,11 @@ impl Object {
map_name, map_name,
Map::Btf(BtfMap { Map::Btf(BtfMap {
def, def,
inner_def,
section_index: section.index.0, section_index: section.index.0,
symbol_index: *symbol_index, symbol_index: *symbol_index,
data: Vec::new(), data: Vec::new(),
initial_slots: BTreeMap::new(),
}), }),
); );
} }
@ -825,7 +819,9 @@ impl Object {
section_kind: section.kind, section_kind: section.kind,
symbol_index: Some(sym.index), symbol_index: Some(sym.index),
def, def,
inner_def: None,
data: Vec::new(), data: Vec::new(),
initial_slots: BTreeMap::new(),
}), }),
); );
have_symbols = true; have_symbols = true;
@ -847,7 +843,7 @@ impl Object {
self.maps self.maps
.insert(section.name.to_string(), parse_data_map_section(&section)?); .insert(section.name.to_string(), parse_data_map_section(&section)?);
} }
EbpfSectionKind::Text => self.parse_text_section(section)?, EbpfSectionKind::Text => self.parse_text_section(&section)?,
EbpfSectionKind::Btf => self.parse_btf(&section)?, EbpfSectionKind::Btf => self.parse_btf(&section)?,
EbpfSectionKind::BtfExt => self.parse_btf_ext(&section)?, EbpfSectionKind::BtfExt => self.parse_btf_ext(&section)?,
EbpfSectionKind::BtfMaps => self.parse_btf_maps(&section)?, EbpfSectionKind::BtfMaps => self.parse_btf_maps(&section)?,
@ -875,6 +871,9 @@ impl Object {
} }
EbpfSectionKind::Program => { EbpfSectionKind::Program => {
self.parse_programs(&section)?; self.parse_programs(&section)?;
}
EbpfSectionKind::Undefined | EbpfSectionKind::License | EbpfSectionKind::Version => {}
}
if !section.relocations.is_empty() { if !section.relocations.is_empty() {
self.relocations.insert( self.relocations.insert(
section.index, section.index,
@ -885,10 +884,6 @@ impl Object {
.collect(), .collect(),
); );
} }
}
EbpfSectionKind::Undefined | EbpfSectionKind::License | EbpfSectionKind::Version => {}
}
Ok(()) Ok(())
} }
@ -1233,7 +1228,9 @@ fn parse_data_map_section(section: &Section) -> Result<Map, ParseError> {
// Data maps don't require symbols to be relocated // Data maps don't require symbols to be relocated
symbol_index: None, symbol_index: None,
def, def,
inner_def: None,
data, data,
initial_slots: BTreeMap::new(),
})) }))
} }
@ -1257,7 +1254,10 @@ fn parse_map_def(name: &str, data: &[u8]) -> Result<bpf_map_def, ParseError> {
} }
} }
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<BtfMapDef>), BtfError> {
let ty = match btf.type_by_id(info.btf_type)? { let ty = match btf.type_by_id(info.btf_type)? {
BtfType::Var(var) => var, BtfType::Var(var) => var,
other => { 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 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 root_type = btf.resolve_type(ty.btf_type)?;
let s = match btf.type_by_id(root_type)? { let s = match btf.type_by_id(root_type)? {
BtfType::Struct(s) => s, 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<BtfMapDef>), BtfError> {
let mut map_def = BtfMapDef::default();
let mut inner_map_def = None;
for m in &s.members { for m in &s.members {
match btf.string_at(m.name_offset)?.as_ref() { match btf.string_at(m.name_offset)?.as_ref() {
"type" => { "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" => { "value_size" => {
map_def.value_size = get_map_field(btf, m.btf_type)?; 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]. /// 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_key_type_id: info.btf_key_type_id,
btf_value_type_id: info.btf_value_type_id, btf_value_type_id: info.btf_value_type_id,
}, },
inner_def: None,
section_index: 0, section_index: 0,
symbol_index: 0, symbol_index: 0,
data: Vec::new(), data: Vec::new(),
initial_slots: BTreeMap::new(),
}) })
} else { } else {
Map::Legacy(LegacyMap { Map::Legacy(LegacyMap {
@ -1365,10 +1425,12 @@ pub fn parse_map_info(info: bpf_map_info, pinned: PinningType) -> Map {
pinning: pinned, pinning: pinned,
id: info.id, id: info.id,
}, },
inner_def: None,
section_index: 0, section_index: 0,
symbol_index: None, symbol_index: None,
section_kind: EbpfSectionKind::Undefined, section_kind: EbpfSectionKind::Undefined,
data: Vec::new(), data: Vec::new(),
initial_slots: BTreeMap::new(),
}) })
} }
} }
@ -1623,7 +1685,9 @@ mod tests {
id: 0, id: 0,
pinning: PinningType::None, pinning: PinningType::None,
}, },
inner_def: None,
data, data,
initial_slots: _,
})) if data == map_data && value_size == map_data.len() as u32 })) if data == map_data && value_size == map_data.len() as u32
) )
} }
@ -2601,10 +2665,12 @@ mod tests {
id: 1, id: 1,
pinning: PinningType::None, pinning: PinningType::None,
}, },
inner_def: None,
section_index: 1, section_index: 1,
section_kind: EbpfSectionKind::Rodata, section_kind: EbpfSectionKind::Rodata,
symbol_index: Some(1), symbol_index: Some(1),
data: vec![0, 0, 0], data: vec![0, 0, 0],
initial_slots: BTreeMap::new(),
}), }),
); );
obj.symbol_table.insert( obj.symbol_table.insert(

@ -1,15 +1,16 @@
//! Program relocation handling. //! Program relocation handling.
use alloc::{borrow::ToOwned, collections::BTreeMap, string::String}; use alloc::{borrow::ToOwned, collections::BTreeMap, format, string::String};
use core::mem; use core::mem;
use log::debug; use log::debug;
use object::{SectionIndex, SymbolKind}; use object::{SectionIndex, SymbolKind};
use crate::{ use crate::{
btf::{Btf, BtfType},
generated::{ generated::{
bpf_insn, BPF_CALL, BPF_JMP, BPF_K, BPF_PSEUDO_CALL, BPF_PSEUDO_FUNC, BPF_PSEUDO_MAP_FD, bpf_insn, bpf_map_type, BPF_CALL, BPF_JMP, BPF_K, BPF_PSEUDO_CALL, BPF_PSEUDO_FUNC,
BPF_PSEUDO_MAP_VALUE, BPF_PSEUDO_MAP_FD, BPF_PSEUDO_MAP_VALUE,
}, },
maps::Map, maps::Map,
obj::{Function, Object}, obj::{Function, Object},
@ -23,8 +24,9 @@ type RawFd = std::os::fd::RawFd;
type RawFd = core::ffi::c_int; type RawFd = core::ffi::c_int;
pub(crate) const INS_SIZE: usize = mem::size_of::<bpf_insn>(); pub(crate) const INS_SIZE: usize = mem::size_of::<bpf_insn>();
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)] #[derive(thiserror::Error, Debug)]
#[error("error relocating `{function}`")] #[error("error relocating `{function}`")]
pub struct EbpfRelocationError { pub struct EbpfRelocationError {
@ -38,6 +40,10 @@ pub struct EbpfRelocationError {
/// Relocation failures /// Relocation failures
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum RelocationError { pub enum RelocationError {
/// Relocation error
#[error("relocation error: {0}")]
Error(String),
/// Unknown symbol /// Unknown symbol
#[error("unknown symbol, index `{index}`")] #[error("unknown symbol, index `{index}`")]
UnknownSymbol { UnknownSymbol {
@ -106,6 +112,33 @@ pub(crate) struct Symbol {
} }
impl Object { impl Object {
/// Handles BTF .maps section relocations
pub fn relocate_btf_maps<'a, I: Iterator<Item = (&'a str, RawFd, &'a mut Map)>>(
&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 /// Relocates the map references
pub fn relocate_maps<'a, I: Iterator<Item = (&'a str, RawFd, &'a Map)>>( pub fn relocate_maps<'a, I: Iterator<Item = (&'a str, RawFd, &'a Map)>>(
&mut self, &mut self,
@ -123,7 +156,7 @@ impl Object {
for function in self.functions.values_mut() { for function in self.functions.values_mut() {
if let Some(relocations) = self.relocations.get(&function.section_index) { if let Some(relocations) = self.relocations.get(&function.section_index) {
relocate_maps( relocate_map_references(
function, function,
relocations.values(), relocations.values(),
&maps_by_section, &maps_by_section,
@ -179,7 +212,7 @@ impl Object {
} }
} }
fn relocate_maps<'a, I: Iterator<Item = &'a Relocation>>( fn relocate_map_references<'a, I: Iterator<Item = &'a Relocation>>(
fun: &mut Function, fun: &mut Function,
relocations: I, relocations: I,
maps_by_section: &HashMap<usize, (&str, RawFd, &Map)>, maps_by_section: &HashMap<usize, (&str, RawFd, &Map)>,
@ -273,6 +306,185 @@ fn relocate_maps<'a, I: Iterator<Item = &'a Relocation>>(
Ok(()) Ok(())
} }
fn relocate_btf_maps<'a, I: Iterator<Item = &'a Relocation>>(
btf: Option<&Btf>,
relocations: I,
maps_by_name: &mut HashMap<&str, (RawFd, &mut Map)>,
symbol_table: &HashMap<usize, Symbol>,
) -> 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> { struct FunctionLinker<'a> {
functions: &'a BTreeMap<(usize, u64), Function>, functions: &'a BTreeMap<(usize, u64), Function>,
linked_functions: HashMap<u64, usize>, linked_functions: HashMap<u64, usize>,
@ -368,7 +580,7 @@ impl<'a> FunctionLinker<'a> {
}) })
.filter(|(_rel, sym)| { .filter(|(_rel, sym)| {
// only consider text relocations, data relocations are // only consider text relocations, data relocations are
// relocated in relocate_maps() // relocated in relocate_map_references()
sym.kind == SymbolKind::Text sym.kind == SymbolKind::Text
|| sym || sym
.section_index .section_index
@ -524,19 +736,23 @@ mod test {
fn fake_legacy_map(symbol_index: usize) -> Map { fn fake_legacy_map(symbol_index: usize) -> Map {
Map::Legacy(LegacyMap { Map::Legacy(LegacyMap {
def: Default::default(), def: Default::default(),
inner_def: None,
section_index: 0, section_index: 0,
section_kind: EbpfSectionKind::Undefined, section_kind: EbpfSectionKind::Undefined,
symbol_index: Some(symbol_index), symbol_index: Some(symbol_index),
data: Vec::new(), data: Vec::new(),
initial_slots: BTreeMap::new(),
}) })
} }
fn fake_btf_map(symbol_index: usize) -> Map { fn fake_btf_map(symbol_index: usize) -> Map {
Map::Btf(BtfMap { Map::Btf(BtfMap {
def: Default::default(), def: Default::default(),
inner_def: None,
section_index: 0, section_index: 0,
symbol_index, symbol_index,
data: Vec::new(), data: Vec::new(),
initial_slots: BTreeMap::new(),
}) })
} }
@ -576,7 +792,7 @@ mod test {
let map = fake_legacy_map(1); let map = fake_legacy_map(1);
let maps_by_symbol = HashMap::from([(1, ("test_map", 1, &map))]); let maps_by_symbol = HashMap::from([(1, ("test_map", 1, &map))]);
relocate_maps( relocate_map_references(
&mut fun, &mut fun,
relocations.iter(), relocations.iter(),
&maps_by_section, &maps_by_section,
@ -632,7 +848,7 @@ mod test {
(2, ("test_map_2", 2, &map_2)), (2, ("test_map_2", 2, &map_2)),
]); ]);
relocate_maps( relocate_map_references(
&mut fun, &mut fun,
relocations.iter(), relocations.iter(),
&maps_by_section, &maps_by_section,
@ -671,7 +887,7 @@ mod test {
let map = fake_btf_map(1); let map = fake_btf_map(1);
let maps_by_symbol = HashMap::from([(1, ("test_map", 1, &map))]); let maps_by_symbol = HashMap::from([(1, ("test_map", 1, &map))]);
relocate_maps( relocate_map_references(
&mut fun, &mut fun,
relocations.iter(), relocations.iter(),
&maps_by_section, &maps_by_section,
@ -727,7 +943,7 @@ mod test {
(2, ("test_map_2", 2, &map_2)), (2, ("test_map_2", 2, &map_2)),
]); ]);
relocate_maps( relocate_map_references(
&mut fun, &mut fun,
relocations.iter(), relocations.iter(),
&maps_by_section, &maps_by_section,

@ -35,13 +35,13 @@ use crate::{
UProbe, Xdp, UProbe, Xdp,
}, },
sys::{ 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_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_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_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_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, 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}, util::{bytes_of, bytes_of_slice, nr_cpus, page_size},
}; };
@ -157,6 +157,7 @@ pub struct EbpfLoader<'a> {
extensions: HashSet<&'a str>, extensions: HashSet<&'a str>,
verifier_log_level: VerifierLogLevel, verifier_log_level: VerifierLogLevel,
allow_unsupported_maps: bool, 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. /// Builder style API for advanced loading of eBPF programs.
@ -195,6 +196,7 @@ impl<'a> EbpfLoader<'a> {
extensions: HashSet::new(), extensions: HashSet::new(),
verifier_log_level: VerifierLogLevel::default(), verifier_log_level: VerifierLogLevel::default(),
allow_unsupported_maps: false, 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. /// Loads eBPF bytecode from a buffer.
/// ///
/// The buffer needs to be 4-bytes aligned. If you are bundling the bytecode statically /// The buffer needs to be 4-bytes aligned. If you are bundling the bytecode statically
@ -418,6 +442,7 @@ impl<'a> EbpfLoader<'a> {
extensions, extensions,
verifier_log_level, verifier_log_level,
allow_unsupported_maps, allow_unsupported_maps,
map_in_maps: _,
} = self; } = self;
let mut obj = Object::parse(data)?; let mut obj = Object::parse(data)?;
obj.patch_map_data(globals.clone())?; obj.patch_map_data(globals.clone())?;
@ -484,7 +509,30 @@ impl<'a> EbpfLoader<'a> {
obj.relocate_btf(btf)?; obj.relocate_btf(btf)?;
} }
let mut maps = HashMap::new(); 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::<Vec<_>>();
let map_in_maps = map_in_maps_keys
.iter()
.map(|key| obj.maps.remove_entry(key).unwrap())
.collect::<HashMap<_, _>>();
for (name, mut obj) in obj.maps.drain().chain(map_in_maps) {
if let (false, EbpfSectionKind::Bss | EbpfSectionKind::Data | EbpfSectionKind::Rodata) = if let (false, EbpfSectionKind::Bss | EbpfSectionKind::Data | EbpfSectionKind::Rodata) =
(FEATURES.bpf_global_data(), obj.section_kind()) (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) => { Ok(BPF_MAP_TYPE_DEVMAP | BPF_MAP_TYPE_DEVMAP_HASH) => {
obj.set_value_size(if FEATURES.devmap_prog_id() { 8 } else { 4 }) 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()); 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) .map(|(section_index, _)| *section_index)
.collect(); .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( obj.relocate_maps(
maps.iter() maps.iter()
.map(|(s, data)| (s.as_str(), data.fd().as_fd().as_raw_fd(), data.obj())), .map(|(s, data)| (s.as_str(), data.fd().as_fd().as_raw_fd(), data.obj())),
&text_sections, &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.relocate_calls(&text_sections)?;
obj.sanitize_functions(&FEATURES); obj.sanitize_functions(&FEATURES);
@ -762,6 +885,8 @@ fn parse_map(
BPF_MAP_TYPE_DEVMAP => Map::DevMap(map), BPF_MAP_TYPE_DEVMAP => Map::DevMap(map),
BPF_MAP_TYPE_DEVMAP_HASH => Map::DevMapHash(map), BPF_MAP_TYPE_DEVMAP_HASH => Map::DevMapHash(map),
BPF_MAP_TYPE_XSKMAP => Map::XskMap(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 => { m_type => {
if allow_unsupported_maps { if allow_unsupported_maps {
Map::Unsupported(map) Map::Unsupported(map)

@ -80,6 +80,7 @@ pub mod bloom_filter;
pub mod hash_map; pub mod hash_map;
mod info; mod info;
pub mod lpm_trie; pub mod lpm_trie;
pub mod of_maps;
pub mod perf; pub mod perf;
pub mod queue; pub mod queue;
pub mod ring_buf; pub mod ring_buf;
@ -93,6 +94,7 @@ pub use bloom_filter::BloomFilter;
pub use hash_map::{HashMap, PerCpuHashMap}; pub use hash_map::{HashMap, PerCpuHashMap};
pub use info::{loaded_maps, MapInfo, MapType}; pub use info::{loaded_maps, MapInfo, MapType};
pub use lpm_trie::LpmTrie; pub use lpm_trie::LpmTrie;
pub use of_maps::{Array as ArrayOfMaps, HashMap as HashMapOfMaps};
#[cfg(any(feature = "async_tokio", feature = "async_std"))] #[cfg(any(feature = "async_tokio", feature = "async_std"))]
#[cfg_attr(docsrs, doc(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::AsyncPerfEventArray;
@ -107,6 +109,10 @@ pub use xdp::{CpuMap, DevMap, DevMapHash, XskMap};
#[derive(Error, Debug)] #[derive(Error, Debug)]
/// Errors occuring from working with Maps /// Errors occuring from working with Maps
pub enum MapError { pub enum MapError {
/// A map error
#[error("{0}")]
Error(String),
/// Invalid map type encontered /// Invalid map type encontered
#[error("invalid map type {map_type}")] #[error("invalid map type {map_type}")]
InvalidMapType { InvalidMapType {
@ -277,6 +283,8 @@ fn maybe_warn_rlimit() {
pub enum Map { pub enum Map {
/// An [`Array`] map. /// An [`Array`] map.
Array(MapData), Array(MapData),
/// An [`ArrayOfMaps`] map.
ArrayOfMaps(MapData),
/// A [`BloomFilter`] map. /// A [`BloomFilter`] map.
BloomFilter(MapData), BloomFilter(MapData),
/// A [`CpuMap`] map. /// A [`CpuMap`] map.
@ -287,6 +295,8 @@ pub enum Map {
DevMapHash(MapData), DevMapHash(MapData),
/// A [`HashMap`] map. /// A [`HashMap`] map.
HashMap(MapData), HashMap(MapData),
/// A [`HashOfMaps`] map.
HashOfMaps(MapData),
/// A [`LpmTrie`] map. /// A [`LpmTrie`] map.
LpmTrie(MapData), LpmTrie(MapData),
/// A [`HashMap`] map that uses a LRU eviction policy. /// A [`HashMap`] map that uses a LRU eviction policy.
@ -324,10 +334,12 @@ impl Map {
fn map_type(&self) -> u32 { fn map_type(&self) -> u32 {
match self { match self {
Self::Array(map) => map.obj.map_type(), Self::Array(map) => map.obj.map_type(),
Self::ArrayOfMaps(map) => map.obj.map_type(),
Self::BloomFilter(map) => map.obj.map_type(), Self::BloomFilter(map) => map.obj.map_type(),
Self::CpuMap(map) => map.obj.map_type(), Self::CpuMap(map) => map.obj.map_type(),
Self::DevMap(map) => map.obj.map_type(), Self::DevMap(map) => map.obj.map_type(),
Self::DevMapHash(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::HashMap(map) => map.obj.map_type(),
Self::LpmTrie(map) => map.obj.map_type(), Self::LpmTrie(map) => map.obj.map_type(),
Self::LruHashMap(map) => map.obj.map_type(), Self::LruHashMap(map) => map.obj.map_type(),
@ -354,10 +366,12 @@ impl Map {
pub fn pin<P: AsRef<Path>>(&self, path: P) -> Result<(), PinError> { pub fn pin<P: AsRef<Path>>(&self, path: P) -> Result<(), PinError> {
match self { match self {
Self::Array(map) => map.pin(path), Self::Array(map) => map.pin(path),
Self::ArrayOfMaps(map) => map.pin(path),
Self::BloomFilter(map) => map.pin(path), Self::BloomFilter(map) => map.pin(path),
Self::CpuMap(map) => map.pin(path), Self::CpuMap(map) => map.pin(path),
Self::DevMap(map) => map.pin(path), Self::DevMap(map) => map.pin(path),
Self::DevMapHash(map) => map.pin(path), Self::DevMapHash(map) => map.pin(path),
Self::HashOfMaps(map) => map.pin(path),
Self::HashMap(map) => map.pin(path), Self::HashMap(map) => map.pin(path),
Self::LpmTrie(map) => map.pin(path), Self::LpmTrie(map) => map.pin(path),
Self::LruHashMap(map) => map.pin(path), Self::LruHashMap(map) => map.pin(path),
@ -412,6 +426,7 @@ impl_map_pin!(() {
DevMap, DevMap,
DevMapHash, DevMapHash,
XskMap, XskMap,
ArrayOfMaps,
}); });
impl_map_pin!((V) { impl_map_pin!((V) {
@ -421,6 +436,7 @@ impl_map_pin!((V) {
BloomFilter, BloomFilter,
Queue, Queue,
Stack, Stack,
HashMapOfMaps,
}); });
impl_map_pin!((K, V) { impl_map_pin!((K, V) {
@ -488,6 +504,7 @@ impl_try_from_map!(() {
SockMap, SockMap,
StackTraceMap, StackTraceMap,
XskMap, XskMap,
ArrayOfMaps,
}); });
#[cfg(any(feature = "async_tokio", feature = "async_std"))] #[cfg(any(feature = "async_tokio", feature = "async_std"))]
@ -503,6 +520,7 @@ impl_try_from_map!((V) {
Queue, Queue,
SockHash, SockHash,
Stack, Stack,
HashMapOfMaps from HashMap,
}); });
impl_try_from_map!((K, V) { impl_try_from_map!((K, V) {
@ -583,6 +601,7 @@ impl MapData {
let kernel_version = KernelVersion::current().unwrap(); let kernel_version = KernelVersion::current().unwrap();
#[cfg(test)] #[cfg(test)]
let kernel_version = KernelVersion::new(0xff, 0xff, 0xff); let kernel_version = KernelVersion::new(0xff, 0xff, 0xff);
let fd = let fd =
bpf_create_map(&c_name, &obj, btf_fd, kernel_version).map_err(|(code, io_error)| { bpf_create_map(&c_name, &obj, btf_fd, kernel_version).map_err(|(code, io_error)| {
if kernel_version < KernelVersion::new(5, 11, 0) { if kernel_version < KernelVersion::new(5, 11, 0) {
@ -759,6 +778,11 @@ impl MapData {
obj 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. /// Returns the kernel's information about the loaded map.
pub fn info(&self) -> Result<MapInfo, MapError> { pub fn info(&self) -> Result<MapInfo, MapError> {
MapInfo::new_from_fd(self.fd.as_fd()) MapInfo::new_from_fd(self.fd.as_fd())
@ -955,6 +979,8 @@ impl<T: Pod> Deref for PerCpuValues<T> {
#[cfg(test)] #[cfg(test)]
mod test_utils { mod test_utils {
use std::collections::BTreeMap;
use crate::{ use crate::{
bpf_map_def, bpf_map_def,
generated::{bpf_cmd, bpf_map_type}, generated::{bpf_cmd, bpf_map_type},
@ -983,10 +1009,12 @@ mod test_utils {
max_entries: 1024, max_entries: 1024,
..Default::default() ..Default::default()
}, },
inner_def: None,
section_index: 0, section_index: 0,
section_kind: EbpfSectionKind::Maps, section_kind: EbpfSectionKind::Maps,
data: Vec::new(), data: Vec::new(),
symbol_index: None, symbol_index: None,
initial_slots: BTreeMap::new(),
}) })
} }
@ -1002,10 +1030,12 @@ mod test_utils {
max_entries, max_entries,
..Default::default() ..Default::default()
}, },
inner_def: None,
section_index: 0, section_index: 0,
section_kind: EbpfSectionKind::Maps, section_kind: EbpfSectionKind::Maps,
data: Vec::new(), data: Vec::new(),
symbol_index: None, symbol_index: None,
initial_slots: BTreeMap::new(),
}) })
} }
} }

@ -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<T> {
pub(crate) inner: T,
}
impl<T: Borrow<MapData>> Array<T> {
pub(crate) fn new(map: T) -> Result<Self, MapError> {
let data = map.borrow();
check_kv_size::<u32, u32>(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<MapFd, MapError> {
let data = self.inner.borrow();
check_bounds(data, *index)?;
let fd = data.fd().as_fd();
let value: Option<u32> =
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<T: BorrowMut<MapData>> Array<T> {
/// 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(())
}
}

@ -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<T, K> {
pub(crate) inner: T,
_k: PhantomData<K>,
}
impl<T: Borrow<MapData>, K: Pod> HashMap<T, K> {
pub(crate) fn new(map: T) -> Result<Self, MapError> {
let data = map.borrow();
check_kv_size::<K, u32>(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<MapFd, MapError> {
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<T: BorrowMut<MapData>, K: Pod> HashMap<T, K> {
/// Inserts a key-value pair into the map.
pub fn insert(
&mut self,
key: impl Borrow<K>,
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)
}
}

@ -0,0 +1,6 @@
//! Maps of maps
mod array;
mod hash_map;
pub use array::Array;
pub use hash_map::HashMap;

@ -1,5 +1,6 @@
use std::{ use std::{
cmp, cmp,
collections::BTreeMap,
ffi::{c_char, c_long, CStr, CString}, ffi::{c_char, c_long, CStr, CString},
io, iter, io, iter,
mem::{self, MaybeUninit}, mem::{self, MaybeUninit},
@ -61,6 +62,24 @@ pub(crate) fn bpf_create_map(
u.max_entries = def.max_entries(); u.max_entries = def.max_entries();
u.map_flags = def.map_flags(); 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 { if let obj::Map::Btf(m) = def {
use bpf_map_type::*; use bpf_map_type::*;
@ -962,10 +981,12 @@ pub(crate) fn is_bpf_global_data_supported() -> bool {
max_entries: 1, max_entries: 1,
..Default::default() ..Default::default()
}, },
inner_def: None,
section_index: 0, section_index: 0,
section_kind: EbpfSectionKind::Maps, section_kind: EbpfSectionKind::Maps,
symbol_index: None, symbol_index: None,
data: Vec::new(), data: Vec::new(),
initial_slots: BTreeMap::new(),
}), }),
"aya_global", "aya_global",
None, None,

@ -5,7 +5,7 @@ use aya_ebpf_cty::c_void;
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_ARRAY}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_ARRAY},
helpers::bpf_map_lookup_elem, helpers::bpf_map_lookup_elem,
maps::PinningType, maps::{InnerMap, PinningType},
}; };
#[repr(transparent)] #[repr(transparent)]
@ -15,6 +15,7 @@ pub struct Array<T> {
} }
unsafe impl<T: Sync> Sync for Array<T> {} unsafe impl<T: Sync> Sync for Array<T> {}
unsafe impl<T> InnerMap for Array<T> {}
impl<T> Array<T> { impl<T> Array<T> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> Array<T> { pub const fn with_max_entries(max_entries: u32, flags: u32) -> Array<T> {
@ -65,10 +66,12 @@ impl<T> Array<T> {
#[inline(always)] #[inline(always)]
unsafe fn lookup(&self, index: u32) -> Option<NonNull<T>> { unsafe fn lookup(&self, index: u32) -> Option<NonNull<T>> {
let ptr = bpf_map_lookup_elem( let ptr = unsafe {
bpf_map_lookup_elem(
self.def.get() as *mut _, self.def.get() as *mut _,
&index as *const _ as *const c_void, &index as *const _ as *const c_void,
); )
};
NonNull::new(ptr as *mut T) NonNull::new(ptr as *mut T)
} }
} }

@ -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<T: InnerMap> {
def: UnsafeCell<bpf_map_def>,
_t: PhantomData<T>,
}
unsafe impl<T: InnerMap> Sync for ArrayOfMaps<T> {}
impl<T: InnerMap> ArrayOfMaps<T> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> ArrayOfMaps<T> {
ArrayOfMaps {
def: UnsafeCell::new(bpf_map_def {
type_: BPF_MAP_TYPE_ARRAY_OF_MAPS,
key_size: mem::size_of::<u32>() as u32,
value_size: mem::size_of::<u32>() 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<T> {
ArrayOfMaps {
def: UnsafeCell::new(bpf_map_def {
type_: BPF_MAP_TYPE_ARRAY_OF_MAPS,
key_size: mem::size_of::<u32>() as u32,
value_size: mem::size_of::<u32>() 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<NonNull<T>> {
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)
}
}

@ -5,7 +5,7 @@ use aya_ebpf_cty::c_void;
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_BLOOM_FILTER}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_BLOOM_FILTER},
helpers::{bpf_map_peek_elem, bpf_map_push_elem}, helpers::{bpf_map_peek_elem, bpf_map_push_elem},
maps::PinningType, maps::{InnerMap, PinningType},
}; };
#[repr(transparent)] #[repr(transparent)]
@ -14,6 +14,9 @@ pub struct BloomFilter<T> {
_t: PhantomData<T>, _t: PhantomData<T>,
} }
unsafe impl<T: Sync> Sync for BloomFilter<T> {}
unsafe impl<T> InnerMap for BloomFilter<T> {}
impl<T> BloomFilter<T> { impl<T> BloomFilter<T> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> BloomFilter<T> { pub const fn with_max_entries(max_entries: u32, flags: u32) -> BloomFilter<T> {
BloomFilter { BloomFilter {

@ -8,7 +8,7 @@ use aya_ebpf_cty::{c_long, c_void};
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_HASH}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_HASH},
helpers::{bpf_map_delete_elem, bpf_map_lookup_elem, bpf_map_update_elem}, helpers::{bpf_map_delete_elem, bpf_map_lookup_elem, bpf_map_update_elem},
maps::PinningType, maps::{InnerMap, PinningType},
}; };
#[repr(transparent)] #[repr(transparent)]
@ -19,6 +19,7 @@ pub struct HashMap<K, V> {
} }
unsafe impl<K: Sync, V: Sync> Sync for HashMap<K, V> {} unsafe impl<K: Sync, V: Sync> Sync for HashMap<K, V> {}
unsafe impl<K: Sync, V: Sync> InnerMap for HashMap<K, V> {}
impl<K, V> HashMap<K, V> { impl<K, V> HashMap<K, V> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> HashMap<K, V> { pub const fn with_max_entries(max_entries: u32, flags: u32) -> HashMap<K, V> {
@ -54,7 +55,7 @@ impl<K, V> HashMap<K, V> {
/// corruption in case of writes. /// corruption in case of writes.
#[inline] #[inline]
pub unsafe fn get(&self, key: &K) -> Option<&V> { 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. /// Retrieve the value associate with `key` from the map.
@ -93,6 +94,7 @@ pub struct LruHashMap<K, V> {
} }
unsafe impl<K: Sync, V: Sync> Sync for LruHashMap<K, V> {} unsafe impl<K: Sync, V: Sync> Sync for LruHashMap<K, V> {}
unsafe impl<K: Sync, V: Sync> InnerMap for LruHashMap<K, V> {}
impl<K, V> LruHashMap<K, V> { impl<K, V> LruHashMap<K, V> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> LruHashMap<K, V> { pub const fn with_max_entries(max_entries: u32, flags: u32) -> LruHashMap<K, V> {
@ -128,7 +130,7 @@ impl<K, V> LruHashMap<K, V> {
/// corruption in case of writes. /// corruption in case of writes.
#[inline] #[inline]
pub unsafe fn get(&self, key: &K) -> Option<&V> { 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. /// Retrieve the value associate with `key` from the map.
@ -167,6 +169,7 @@ pub struct PerCpuHashMap<K, V> {
} }
unsafe impl<K, V> Sync for PerCpuHashMap<K, V> {} unsafe impl<K, V> Sync for PerCpuHashMap<K, V> {}
unsafe impl<K: Sync, V: Sync> InnerMap for PerCpuHashMap<K, V> {}
impl<K, V> PerCpuHashMap<K, V> { impl<K, V> PerCpuHashMap<K, V> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> PerCpuHashMap<K, V> { pub const fn with_max_entries(max_entries: u32, flags: u32) -> PerCpuHashMap<K, V> {
@ -202,7 +205,7 @@ impl<K, V> PerCpuHashMap<K, V> {
/// corruption in case of writes. /// corruption in case of writes.
#[inline] #[inline]
pub unsafe fn get(&self, key: &K) -> Option<&V> { 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. /// Retrieve the value associate with `key` from the map.
@ -241,6 +244,7 @@ pub struct LruPerCpuHashMap<K, V> {
} }
unsafe impl<K, V> Sync for LruPerCpuHashMap<K, V> {} unsafe impl<K, V> Sync for LruPerCpuHashMap<K, V> {}
unsafe impl<K: Sync, V: Sync> InnerMap for LruPerCpuHashMap<K, V> {}
impl<K, V> LruPerCpuHashMap<K, V> { impl<K, V> LruPerCpuHashMap<K, V> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> LruPerCpuHashMap<K, V> { pub const fn with_max_entries(max_entries: u32, flags: u32) -> LruPerCpuHashMap<K, V> {
@ -276,7 +280,7 @@ impl<K, V> LruPerCpuHashMap<K, V> {
/// corruption in case of writes. /// corruption in case of writes.
#[inline] #[inline]
pub unsafe fn get(&self, key: &K) -> Option<&V> { 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. /// Retrieve the value associate with `key` from the map.
@ -335,7 +339,7 @@ fn get_ptr<K, V>(def: *mut bpf_map_def, key: &K) -> Option<*const V> {
#[inline] #[inline]
unsafe fn get<'a, K, V>(def: *mut bpf_map_def, key: &K) -> Option<&'a V> { 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] #[inline]

@ -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<K, V> {
def: UnsafeCell<bpf_map_def>,
_k: PhantomData<K>,
_v: PhantomData<V>,
}
impl<K, V: InnerMap> HashOfMaps<K, V> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> HashOfMaps<K, V> {
HashOfMaps {
def: UnsafeCell::new(bpf_map_def {
type_: BPF_MAP_TYPE_HASH_OF_MAPS,
key_size: mem::size_of::<K>() as u32,
value_size: mem::size_of::<u32>() 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<K, V> {
HashOfMaps {
def: UnsafeCell::new(bpf_map_def {
type_: BPF_MAP_TYPE_HASH_OF_MAPS,
key_size: mem::size_of::<K>() as u32,
value_size: mem::size_of::<u32>() 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<NonNull<u32>> {
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 _)
}
}

@ -6,7 +6,7 @@ use aya_ebpf_cty::{c_long, c_void};
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_LPM_TRIE}, 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}, helpers::{bpf_map_delete_elem, bpf_map_lookup_elem, bpf_map_update_elem},
maps::PinningType, maps::{InnerMap, PinningType},
}; };
#[repr(transparent)] #[repr(transparent)]
@ -17,6 +17,7 @@ pub struct LpmTrie<K, V> {
} }
unsafe impl<K: Sync, V: Sync> Sync for LpmTrie<K, V> {} unsafe impl<K: Sync, V: Sync> Sync for LpmTrie<K, V> {}
unsafe impl<K, V> InnerMap for LpmTrie<K, V> {}
#[repr(C, packed)] #[repr(C, packed)]
pub struct Key<K> { pub struct Key<K> {

@ -6,8 +6,10 @@ pub(crate) enum PinningType {
} }
pub mod array; pub mod array;
pub mod array_of_maps;
pub mod bloom_filter; pub mod bloom_filter;
pub mod hash_map; pub mod hash_map;
pub mod hash_of_maps;
pub mod lpm_trie; pub mod lpm_trie;
pub mod per_cpu_array; pub mod per_cpu_array;
pub mod perf; pub mod perf;
@ -21,8 +23,10 @@ pub mod stack_trace;
pub mod xdp; pub mod xdp;
pub use array::Array; pub use array::Array;
pub use array_of_maps::ArrayOfMaps;
pub use bloom_filter::BloomFilter; pub use bloom_filter::BloomFilter;
pub use hash_map::{HashMap, LruHashMap, LruPerCpuHashMap, PerCpuHashMap}; pub use hash_map::{HashMap, LruHashMap, LruPerCpuHashMap, PerCpuHashMap};
pub use hash_of_maps::HashOfMaps;
pub use lpm_trie::LpmTrie; pub use lpm_trie::LpmTrie;
pub use per_cpu_array::PerCpuArray; pub use per_cpu_array::PerCpuArray;
pub use perf::{PerfEventArray, PerfEventByteArray}; pub use perf::{PerfEventArray, PerfEventByteArray};
@ -34,3 +38,6 @@ pub use sock_map::SockMap;
pub use stack::Stack; pub use stack::Stack;
pub use stack_trace::StackTrace; pub use stack_trace::StackTrace;
pub use xdp::{CpuMap, DevMap, DevMapHash, XskMap}; 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 {}

@ -5,7 +5,7 @@ use aya_ebpf_cty::c_void;
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_PERCPU_ARRAY}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_PERCPU_ARRAY},
helpers::bpf_map_lookup_elem, helpers::bpf_map_lookup_elem,
maps::PinningType, maps::{InnerMap, PinningType},
}; };
#[repr(transparent)] #[repr(transparent)]
@ -15,6 +15,7 @@ pub struct PerCpuArray<T> {
} }
unsafe impl<T> Sync for PerCpuArray<T> {} unsafe impl<T> Sync for PerCpuArray<T> {}
unsafe impl<T> InnerMap for PerCpuArray<T> {}
impl<T> PerCpuArray<T> { impl<T> PerCpuArray<T> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> PerCpuArray<T> { pub const fn with_max_entries(max_entries: u32, flags: u32) -> PerCpuArray<T> {

@ -3,7 +3,7 @@ use core::{cell::UnsafeCell, marker::PhantomData, mem};
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_QUEUE}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_QUEUE},
helpers::{bpf_map_pop_elem, bpf_map_push_elem}, helpers::{bpf_map_pop_elem, bpf_map_push_elem},
maps::PinningType, maps::{InnerMap, PinningType},
}; };
#[repr(transparent)] #[repr(transparent)]
@ -13,6 +13,7 @@ pub struct Queue<T> {
} }
unsafe impl<T: Sync> Sync for Queue<T> {} unsafe impl<T: Sync> Sync for Queue<T> {}
unsafe impl<T> InnerMap for Queue<T> {}
impl<T> Queue<T> { impl<T> Queue<T> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> Queue<T> { pub const fn with_max_entries(max_entries: u32, flags: u32) -> Queue<T> {

@ -11,7 +11,7 @@ use crate::{
bpf_ringbuf_discard, bpf_ringbuf_output, bpf_ringbuf_query, bpf_ringbuf_reserve, bpf_ringbuf_discard, bpf_ringbuf_output, bpf_ringbuf_query, bpf_ringbuf_reserve,
bpf_ringbuf_submit, bpf_ringbuf_submit,
}, },
maps::PinningType, maps::{InnerMap, PinningType},
}; };
#[cfg(unstable)] #[cfg(unstable)]
@ -31,6 +31,7 @@ pub struct RingBuf {
} }
unsafe impl Sync for RingBuf {} unsafe impl Sync for RingBuf {}
unsafe impl InnerMap for RingBuf {}
/// A ring buffer entry, returned from [`RingBuf::reserve`]. /// A ring buffer entry, returned from [`RingBuf::reserve`].
/// ///

@ -8,7 +8,7 @@ use crate::{
bpf_map_lookup_elem, bpf_msg_redirect_hash, bpf_sk_assign, bpf_sk_redirect_hash, bpf_map_lookup_elem, bpf_msg_redirect_hash, bpf_sk_assign, bpf_sk_redirect_hash,
bpf_sk_release, bpf_sock_hash_update, bpf_sk_release, bpf_sock_hash_update,
}, },
maps::PinningType, maps::{InnerMap, PinningType},
programs::{SkBuffContext, SkLookupContext, SkMsgContext}, programs::{SkBuffContext, SkLookupContext, SkMsgContext},
EbpfContext, EbpfContext,
}; };
@ -20,6 +20,7 @@ pub struct SockHash<K> {
} }
unsafe impl<K: Sync> Sync for SockHash<K> {} unsafe impl<K: Sync> Sync for SockHash<K> {}
unsafe impl<K> InnerMap for SockHash<K> {}
impl<K> SockHash<K> { impl<K> SockHash<K> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> SockHash<K> { pub const fn with_max_entries(max_entries: u32, flags: u32) -> SockHash<K> {

@ -8,7 +8,7 @@ use crate::{
bpf_map_lookup_elem, bpf_msg_redirect_map, bpf_sk_assign, bpf_sk_redirect_map, bpf_map_lookup_elem, bpf_msg_redirect_map, bpf_sk_assign, bpf_sk_redirect_map,
bpf_sk_release, bpf_sock_map_update, bpf_sk_release, bpf_sock_map_update,
}, },
maps::PinningType, maps::{InnerMap, PinningType},
programs::{SkBuffContext, SkLookupContext, SkMsgContext}, programs::{SkBuffContext, SkLookupContext, SkMsgContext},
EbpfContext, EbpfContext,
}; };
@ -19,6 +19,7 @@ pub struct SockMap {
} }
unsafe impl Sync for SockMap {} unsafe impl Sync for SockMap {}
unsafe impl InnerMap for SockMap {}
impl SockMap { impl SockMap {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> SockMap { pub const fn with_max_entries(max_entries: u32, flags: u32) -> SockMap {

@ -3,7 +3,7 @@ use core::{marker::PhantomData, mem};
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_STACK}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_STACK},
helpers::{bpf_map_pop_elem, bpf_map_push_elem}, helpers::{bpf_map_pop_elem, bpf_map_push_elem},
maps::PinningType, maps::{InnerMap, PinningType},
}; };
#[repr(transparent)] #[repr(transparent)]
@ -12,6 +12,9 @@ pub struct Stack<T> {
_t: PhantomData<T>, _t: PhantomData<T>,
} }
unsafe impl<T: Sync> Sync for Stack<T> {}
unsafe impl<T> InnerMap for Stack<T> {}
impl<T> Stack<T> { impl<T> Stack<T> {
pub const fn with_max_entries(max_entries: u32, flags: u32) -> Stack<T> { pub const fn with_max_entries(max_entries: u32, flags: u32) -> Stack<T> {
Stack { Stack {

@ -3,7 +3,7 @@ use core::{cell::UnsafeCell, mem};
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_STACK_TRACE}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_STACK_TRACE},
helpers::bpf_get_stackid, helpers::bpf_get_stackid,
maps::PinningType, maps::{InnerMap, PinningType},
EbpfContext, EbpfContext,
}; };
@ -13,6 +13,7 @@ pub struct StackTrace {
} }
unsafe impl Sync for StackTrace {} unsafe impl Sync for StackTrace {}
unsafe impl InnerMap for StackTrace {}
const PERF_MAX_STACK_DEPTH: u32 = 127; const PERF_MAX_STACK_DEPTH: u32 = 127;

@ -5,7 +5,7 @@ use aya_ebpf_bindings::bindings::bpf_cpumap_val;
use super::try_redirect_map; use super::try_redirect_map;
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_CPUMAP}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_CPUMAP},
maps::PinningType, maps::{InnerMap, PinningType},
}; };
/// An array of available CPUs. /// An array of available CPUs.
@ -36,6 +36,7 @@ pub struct CpuMap {
} }
unsafe impl Sync for CpuMap {} unsafe impl Sync for CpuMap {}
unsafe impl InnerMap for CpuMap {}
impl CpuMap { impl CpuMap {
/// Creates a [`CpuMap`] with a set maximum number of elements. /// Creates a [`CpuMap`] with a set maximum number of elements.

@ -7,7 +7,7 @@ use super::try_redirect_map;
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_DEVMAP}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_DEVMAP},
helpers::bpf_map_lookup_elem, helpers::bpf_map_lookup_elem,
maps::PinningType, maps::{InnerMap, PinningType},
}; };
/// An array of network devices. /// An array of network devices.
@ -37,6 +37,7 @@ pub struct DevMap {
} }
unsafe impl Sync for DevMap {} unsafe impl Sync for DevMap {}
unsafe impl InnerMap for DevMap {}
impl DevMap { impl DevMap {
/// Creates a [`DevMap`] with a set maximum number of elements. /// Creates a [`DevMap`] with a set maximum number of elements.

@ -7,7 +7,7 @@ use super::{dev_map::DevMapValue, try_redirect_map};
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH},
helpers::bpf_map_lookup_elem, helpers::bpf_map_lookup_elem,
maps::PinningType, maps::{InnerMap, PinningType},
}; };
/// A map of network devices. /// A map of network devices.
@ -39,6 +39,7 @@ pub struct DevMapHash {
} }
unsafe impl Sync for DevMapHash {} unsafe impl Sync for DevMapHash {}
unsafe impl InnerMap for DevMapHash {}
impl DevMapHash { impl DevMapHash {
/// Creates a [`DevMapHash`] with a set maximum number of elements. /// Creates a [`DevMapHash`] with a set maximum number of elements.

@ -7,7 +7,7 @@ use super::try_redirect_map;
use crate::{ use crate::{
bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_XSKMAP}, bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_XSKMAP},
helpers::bpf_map_lookup_elem, helpers::bpf_map_lookup_elem,
maps::PinningType, maps::{InnerMap, PinningType},
}; };
/// An array of AF_XDP sockets. /// An array of AF_XDP sockets.
@ -58,6 +58,7 @@ pub struct XskMap {
} }
unsafe impl Sync for XskMap {} unsafe impl Sync for XskMap {}
unsafe impl InnerMap for XskMap {}
impl XskMap { impl XskMap {
/// Creates a [`XskMap`] with a set maximum number of elements. /// Creates a [`XskMap`] with a set maximum number of elements.

@ -85,3 +85,7 @@ path = "src/xdp_sec.rs"
[[bin]] [[bin]]
name = "uprobe_cookie" name = "uprobe_cookie"
path = "src/uprobe_cookie.rs" path = "src/uprobe_cookie.rs"
[[bin]]
name = "map_of_maps"
path = "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<Array<u32>> = ArrayOfMaps::with_max_entries(10, 0);
#[map]
static INNER: Array<u32> = Array::with_max_entries(10, 0);
#[map]
static INNER_2: Array<u32> = 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 {}
}

@ -0,0 +1,64 @@
// clang-format off
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
// 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";

@ -66,6 +66,7 @@ fn main() -> Result<()> {
("iter.bpf.c", true), ("iter.bpf.c", true),
("main.bpf.c", false), ("main.bpf.c", false),
("multimap-btf.bpf.c", false), ("multimap-btf.bpf.c", false),
("ofmaps.bpf.c", false),
("reloc.bpf.c", true), ("reloc.bpf.c", true),
("text_64_64_reloc.c", false), ("text_64_64_reloc.c", false),
("variables_reloc.bpf.c", false), ("variables_reloc.bpf.c", false),

@ -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 MAIN: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/main.bpf.o"));
pub const MULTIMAP_BTF: &[u8] = pub const MULTIMAP_BTF: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/multimap-btf.bpf.o")); 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_BPF: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.o"));
pub const RELOC_BTF: &[u8] = pub const RELOC_BTF: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/reloc.bpf.target.o")); 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 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 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 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)] #[cfg(test)]
mod tests; mod tests;

@ -7,7 +7,7 @@ use aya::{
loaded_links, loaded_programs, KProbe, TracePoint, UProbe, Xdp, XdpFlags, loaded_links, loaded_programs, KProbe, TracePoint, UProbe, Xdp, XdpFlags,
}, },
util::KernelVersion, util::KernelVersion,
Ebpf, Ebpf, EbpfLoader,
}; };
use aya_obj::programs::XdpAttachType; use aya_obj::programs::XdpAttachType;
use test_log::test; use test_log::test;
@ -575,3 +575,63 @@ fn pin_lifecycle_uprobe() {
// Make sure the function isn't optimized out. // Make sure the function isn't optimized out.
uprobe_function(); 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);
}

Loading…
Cancel
Save