Browse Source

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 <[email protected]>
Dave Tucker 3 years ago
parent
commit
f357be7db4

+ 99 - 4
aya/src/obj/mod.rs

@@ -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();
@@ -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();
+                self.parse_map_section(&section, symbols)?
             }
             BpfSectionKind::Program => {
                 let program = self.parse_program(&section)?;
@@ -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<T>(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::<bpf_map_def>() 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::<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();

+ 1 - 1
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`

+ 47 - 0
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 - 0
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 - 0
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