From f357be7db45b7201be6864e83fb7eb7e78cd984a Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Wed, 19 Jan 2022 00:13:04 +0000 Subject: [PATCH] aya: Support multiple maps in map sections This commit uses the symbol table to discover all maps inside an ELF section. Instead of doing what libbpf does - divide the section data in to equal sized chunks - we read in to section data using the symbol address and offset, thus allowing us to support definitions of varying lengths. Signed-off-by: Dave Tucker --- aya/src/obj/mod.rs | 103 +++++++++++++++++- test/README.md | 2 +- .../010_load/010_multiple_maps/multimap.bpf.c | 47 ++++++++ .../010_load/010_multiple_maps/multimap.rs | 33 ++++++ test/cases/010_load/010_multiple_maps/test.sh | 29 +++++ 5 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 test/cases/010_load/010_multiple_maps/multimap.bpf.c create mode 100755 test/cases/010_load/010_multiple_maps/multimap.rs create mode 100755 test/cases/010_load/010_multiple_maps/test.sh diff --git a/aya/src/obj/mod.rs b/aya/src/obj/mod.rs index 0588fb4f..0aaa3999 100644 --- a/aya/src/obj/mod.rs +++ b/aya/src/obj/mod.rs @@ -519,6 +519,36 @@ impl Object { Ok(()) } + fn parse_map_section( + &mut self, + section: &Section, + symbols: Vec, + ) -> Result<(), ParseError> { + if symbols.is_empty() { + return Err(ParseError::NoSymbolsInMapSection {}); + } + for (i, sym) in symbols.iter().enumerate() { + let start = sym.address as usize; + let end = start + sym.size as usize; + let data = §ion.data[start..end]; + let name = sym + .name + .as_ref() + .ok_or(ParseError::MapSymbolNameNotFound { i })?; + let def = parse_map_def(name, data)?; + self.maps.insert( + name.to_string(), + Map { + section_index: section.index.0, + def, + data: Vec::new(), + kind: MapKind::Other, + }, + ); + } + Ok(()) + } + fn parse_section(&mut self, mut section: Section) -> Result<(), BpfError> { let mut parts = section.name.rsplitn(2, '/').collect::>(); parts.reverse(); @@ -542,9 +572,19 @@ impl Object { BpfSectionKind::Btf => self.parse_btf(§ion)?, BpfSectionKind::BtfExt => self.parse_btf_ext(§ion)?, BpfSectionKind::Maps => { - let name = section.name.splitn(2, '/').last().unwrap(); - self.maps - .insert(name.to_string(), parse_map(§ion, name)?); + let symbols: Vec = self + .symbols_by_index + .values() + .filter(|s| { + if let Some(idx) = s.section_index { + idx == section.index.0 + } else { + false + } + }) + .cloned() + .collect(); + self.parse_map_section(§ion, symbols)? } BpfSectionKind::Program => { let program = self.parse_program(§ion)?; @@ -625,6 +665,12 @@ pub enum ParseError { #[error("map for section with index {index} not found")] MapNotFound { index: usize }, + + #[error("the map number {i} in the `maps` section doesn't have a symbol name")] + MapSymbolNameNotFound { i: usize }, + + #[error("no symbols found for the maps included in the maps section")] + NoSymbolsInMapSection {}, } #[derive(Debug)] @@ -873,6 +919,22 @@ mod tests { } } + fn fake_sym(obj: &mut Object, section_index: usize, address: u64, name: &str, size: u64) { + let idx = obj.symbols_by_index.len(); + obj.symbols_by_index.insert( + idx + 1, + Symbol { + index: idx + 1, + section_index: Some(section_index), + name: Some(name.to_string()), + address, + size, + is_definition: false, + kind: SymbolKind::Data, + }, + ); + } + fn bytes_of(val: &T) -> &[u8] { // Safety: This is for testing only unsafe { crate::util::bytes_of(val) } @@ -1113,7 +1175,7 @@ mod tests { #[test] fn test_parse_section_map() { let mut obj = fake_obj(); - + fake_sym(&mut obj, 0, 0, "foo", mem::size_of::() as u64); assert_matches!( obj.parse_section(fake_section( BpfSectionKind::Maps, @@ -1132,6 +1194,39 @@ mod tests { assert!(obj.maps.get("foo").is_some()); } + #[test] + fn test_parse_section_multiple_maps() { + let mut obj = fake_obj(); + fake_sym(&mut obj, 0, 0, "foo", mem::size_of::() as u64); + fake_sym(&mut obj, 0, 28, "bar", mem::size_of::() as u64); + fake_sym(&mut obj, 0, 60, "baz", mem::size_of::() as u64); + let def = &bpf_map_def { + map_type: 1, + key_size: 2, + value_size: 3, + max_entries: 4, + map_flags: 5, + ..Default::default() + }; + let map_data = bytes_of(def).to_vec(); + let mut buf = vec![]; + buf.extend(&map_data); + buf.extend(&map_data); + // throw in some padding + buf.extend(&[0, 0, 0, 0]); + buf.extend(&map_data); + assert_matches!( + obj.parse_section(fake_section(BpfSectionKind::Maps, "maps", buf.as_slice(),)), + Ok(()) + ); + assert!(obj.maps.get("foo").is_some()); + assert!(obj.maps.get("bar").is_some()); + assert!(obj.maps.get("baz").is_some()); + for (_, m) in &obj.maps { + assert_eq!(&m.def, def); + } + } + #[test] fn test_parse_section_data() { let mut obj = fake_obj(); diff --git a/test/README.md b/test/README.md index 3cc6c0be..164f5b56 100644 --- a/test/README.md +++ b/test/README.md @@ -7,7 +7,7 @@ common usage behaviours work on real Linux distros This assumes you have a working Rust and Go toolchain on the host machine -1. `rustup toolchain add x86_64-unknown-linux-musl` +1. `rustup target add x86_64-unknown-linux-musl` 1. Install [`rtf`](https://github.com/linuxkit/rtf): `go install github.com/linuxkit/rtf@latest` 1. Install rust-script: `cargo install rust-script` 1. Install `qemu` and `cloud-init-utils` package - or any package that provides `cloud-localds` diff --git a/test/cases/010_load/010_multiple_maps/multimap.bpf.c b/test/cases/010_load/010_multiple_maps/multimap.bpf.c new file mode 100644 index 00000000..82ad2832 --- /dev/null +++ b/test/cases/010_load/010_multiple_maps/multimap.bpf.c @@ -0,0 +1,47 @@ +#include +#include + +const int XDP_ACTION_MAX = (XDP_TX + 1); + +struct datarec { + __u64 rx_packets; +}; + +// stats keyed by XDP Action +struct bpf_map_def SEC("maps") xdp_stats_map = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(__u32), + .value_size = sizeof(struct datarec), + .max_entries = XDP_ACTION_MAX, +}; + +// tracks number of times called +struct bpf_map_def SEC("maps") prog_stats_map = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(__u32), + .value_size = sizeof(__u64), + .max_entries = 1, +}; + +SEC("xdp/stats") +int xdp_stats(struct xdp_md *ctx) +{ + __u64 *stats; + struct datarec *rec; + __u32 key = XDP_PASS; + __u32 k1 = 0; + + stats = bpf_map_lookup_elem(&prog_stats_map, &k1); + if (!stats) + return XDP_ABORTED; + __sync_fetch_and_add(stats, 1); + + rec = bpf_map_lookup_elem(&xdp_stats_map, &key); + if (!rec) + return XDP_ABORTED; + __sync_fetch_and_add(&rec->rx_packets, 1); + + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/test/cases/010_load/010_multiple_maps/multimap.rs b/test/cases/010_load/010_multiple_maps/multimap.rs new file mode 100755 index 00000000..48d7548c --- /dev/null +++ b/test/cases/010_load/010_multiple_maps/multimap.rs @@ -0,0 +1,33 @@ +//! ```cargo +//! [dependencies] +//! log = "0.4" +//! simplelog = "0.11" +//! aya = { path = "../../../../aya" } +//! ``` + +use aya::{ + Bpf, + programs::{Xdp, XdpFlags}, +}; +use log::info; +use std::convert::TryInto; + +use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; + +fn main() { + TermLogger::init( + LevelFilter::Debug, + ConfigBuilder::new() + .set_target_level(LevelFilter::Error) + .set_location_level(LevelFilter::Error) + .build(), + TerminalMode::Mixed, + ColorChoice::Auto, + ).unwrap(); + info!("Loading XDP program"); + let mut bpf = Bpf::load_file("multimap.o").unwrap(); + let pass: &mut Xdp = bpf.program_mut("stats").unwrap().try_into().unwrap(); + pass.load().unwrap(); + pass.attach("eth0", XdpFlags::default()).unwrap(); + info!("Success..."); +} diff --git a/test/cases/010_load/010_multiple_maps/test.sh b/test/cases/010_load/010_multiple_maps/test.sh new file mode 100755 index 00000000..75def301 --- /dev/null +++ b/test/cases/010_load/010_multiple_maps/test.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# SUMMARY: Check that a program with multiple maps in the maps section loads +# LABELS: + +set -e + +# Source libraries. Uncomment if needed/defined +#. "${RT_LIB}" +. "${RT_PROJECT_ROOT}/_lib/lib.sh" + +NAME=multimap + +clean_up() { + rm -rf ${NAME}.o ${NAME} + exec_vm rm -f ${NAME}.o ${NAME} +} + +trap clean_up EXIT + +# Test code goes here +compile_c_ebpf "$(pwd)/${NAME}.bpf.c" +compile_user "$(pwd)/${NAME}.rs" + +scp_vm ${NAME}.o +scp_vm ${NAME} + +exec_vm sudo ./${NAME} + +exit 0 \ No newline at end of file