Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aya: Support multiple maps in map sections #181

Merged
merged 1 commit into from Apr 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
103 changes: 99 additions & 4 deletions aya/src/obj/mod.rs
Expand Up @@ -519,6 +519,36 @@ impl Object {
Ok(())
}

fn parse_map_section(
&mut self,
section: &Section,
symbols: Vec<Symbol>,
) -> 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 = &section.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::<Vec<_>>();
parts.reverse();
Expand All @@ -542,9 +572,19 @@ impl Object {
BpfSectionKind::Btf => self.parse_btf(&section)?,
BpfSectionKind::BtfExt => self.parse_btf_ext(&section)?,
BpfSectionKind::Maps => {
let name = section.name.splitn(2, '/').last().unwrap();
self.maps
.insert(name.to_string(), parse_map(&section, name)?);
let symbols: Vec<Symbol> = self
.symbols_by_index
.values()
.filter(|s| {
if let Some(idx) = s.section_index {
idx == section.index.0
} else {
false
}
})
.cloned()
.collect();
dave-tucker marked this conversation as resolved.
Show resolved Hide resolved
self.parse_map_section(&section, symbols)?
}
BpfSectionKind::Program => {
let program = self.parse_program(&section)?;
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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<T>(val: &T) -> &[u8] {
// Safety: This is for testing only
unsafe { crate::util::bytes_of(val) }
Expand Down Expand Up @@ -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::<bpf_map_def>() as u64);
assert_matches!(
obj.parse_section(fake_section(
BpfSectionKind::Maps,
Expand All @@ -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::<bpf_map_def>() as u64);
fake_sym(&mut obj, 0, 28, "bar", mem::size_of::<bpf_map_def>() as u64);
fake_sym(&mut obj, 0, 60, "baz", mem::size_of::<bpf_map_def>() 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();
Expand Down
2 changes: 1 addition & 1 deletion test/README.md
Expand Up @@ -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`
Expand Down
47 changes: 47 additions & 0 deletions test/cases/010_load/010_multiple_maps/multimap.bpf.c
@@ -0,0 +1,47 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

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";
33 changes: 33 additions & 0 deletions 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...");
}
29 changes: 29 additions & 0 deletions 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