From 0c3da0c15204c628bff1428dadff1505a8fc3e46 Mon Sep 17 00:00:00 2001 From: Thia Wyrod Date: Sun, 31 Oct 2021 19:46:12 -0700 Subject: [PATCH 001/107] Add a method to detach an XDP program. This allows one to detach an XDP program from an individual interface without dropping the entire XDP struct and detaching from all other interfaces. Signed-off-by: Thia Wyrod --- redbpf/src/lib.rs | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 74e5eacf..2d3889ee 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -879,6 +879,40 @@ impl XDP { } } + /// Detach the XDP program. + /// + /// Detach the XDP program from the given network interface, if attached. + /// + /// # Example + /// ```no_run + /// # use redbpf::{Module, xdp}; + /// # let mut module = Module::parse(&std::fs::read("file.elf").unwrap()).unwrap(); + /// # for uprobe in module.xdps_mut() { + /// uprobe.attach_xdp("eth0", xdp::Flags::default()).unwrap(); + /// uprobe.detach_xdp("eth0").unwrap(); + /// # } + /// ``` + pub fn detach_xdp(&mut self, interface: &str) -> Result<()> { + // The linear search here isn't great, but self.interfaces will almost always be short. + let index = self + .interfaces + .iter() + .enumerate() + .find_map(|(i, v)| (v.as_str() == interface).then(|| i)) + .ok_or(Error::ProgramNotLoaded)?; + if let Err(e) = unsafe { detach_xdp(interface) } { + if let Error::IO(ref oserr) = e { + error!( + "error detaching xdp from interface {}: {}", + interface, oserr + ); + } + return Err(e); + } + self.interfaces.swap_remove(index); + Ok(()) + } + pub fn name(&self) -> String { self.common.name.to_string() } @@ -887,7 +921,7 @@ impl XDP { impl Drop for XDP { fn drop(&mut self) { for interface in self.interfaces.iter() { - let _ = unsafe { attach_xdp(interface, -1, 0) }; + let _ = unsafe { detach_xdp(interface) }; } } } @@ -943,6 +977,10 @@ unsafe fn attach_xdp(dev_name: &str, progfd: libc::c_int, flags: libc::c_uint) - Ok(()) } +unsafe fn detach_xdp(dev_name: &str) -> Result<()> { + attach_xdp(dev_name, -1, 0) +} + impl SocketFilter { /// Attach the socket filter program. /// From b1c496a323a536bca753feeaa319289caef88b6f Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 9 Nov 2021 18:34:33 +0900 Subject: [PATCH 002/107] Add PerCpuHashMap, LruHashMap, LruPerCpuHashMap for BPF programs Signed-off-by: Junyeong Jeong --- redbpf-probes/src/maps.rs | 89 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/redbpf-probes/src/maps.rs b/redbpf-probes/src/maps.rs index 1b35e7dd..b1e02484 100644 --- a/redbpf-probes/src/maps.rs +++ b/redbpf-probes/src/maps.rs @@ -15,7 +15,7 @@ kernel and user-space code. use core::convert::TryInto; use core::default::Default; use core::marker::PhantomData; -use core::mem; +use core::{mem, ptr}; use cty::*; use crate::bindings::*; @@ -47,6 +47,15 @@ macro_rules! define_hashmap { } } /// Returns a reference to the value corresponding to the key. + /// + /// **CUATION** The value that the returned reference refers to is + /// stored at 8 bytes aligned memory. So the reference is not + /// guaranteed to be aligned properly if the alignment of the value + /// exceeds 8 bytes. So this method should not be called if the + /// alignment is greater than 8 bytes. + /// + /// Use `get_val` method instead if the alignment of value is + /// greater than 8 bytes. #[inline] pub fn get(&mut self, key: &K) -> Option<&V> { unsafe { @@ -62,6 +71,17 @@ macro_rules! define_hashmap { } } + /// Returns a mutable reference to the value corresponding to the key. + /// + /// **CUATION** The value that the returned mutable reference + /// refers to is stored at 8 bytes aligned memory. So the mutable + /// reference is not guaranteed to be aligned properly if the + /// alignment of the value exceeds 8 bytes. So this method should + /// not be called if the alignment is greater than 8 bytes. + /// + /// Use `get_val` method instead if the alignment of value is + /// greater than 8 bytes. But you should call `set` method to + /// update the modified value to BPF maps. #[inline] pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { unsafe { @@ -77,6 +97,30 @@ macro_rules! define_hashmap { } } + /// Returns a value corresponding to the key + /// + /// **NOTE** It is better to use more efficient `get_mut` method + /// instead if the alignment of the value is equal to or less than + /// 8 bytes. i.e, alignment is 8, 4, 2 bytes or 1 byte. Rust + /// compiler expects that the value a reference refers to should be + /// aligned properly. But the Linux kernel does not guarantee the + /// alignment of the value the rust compiler assumes but the Linux + /// kernel just stores values at 8 bytes aligned memory. + #[inline] + pub fn get_val(&mut self, key: &K) -> Option { + unsafe { + let value = bpf_map_lookup_elem( + &mut self.def as *mut _ as *mut c_void, + key as *const _ as *const c_void, + ); + if value.is_null() { + None + } else { + Some(ptr::read_unaligned(value as *const V)) + } + } + } + /// Set the `value` in the map for `key` #[inline] pub fn set(&mut self, key: &K, value: &V) { @@ -176,17 +220,48 @@ macro_rules! define_array { }; } define_hashmap!( - /// Hash table map. + /// Hash table map /// - /// High level API for BPF_MAP_TYPE_HASH maps. + /// High level API of BPF_MAP_TYPE_HASH maps for BPF programs. /// - /// For userspace API, see [`redbpf::HashMap`](../../redbpf/struct.HashMap.html) + /// If you are looking for userspace API, see + /// [`redbpf::HashMap`](../../redbpf/struct.HashMap.html) instead. HashMap, bpf_map_type_BPF_MAP_TYPE_HASH ); -// define_hashmap!(PerCpuHashMap, bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH); // userspace part is not implemented yet -// define_hashmap!(LruHashMap, bpf_map_type_BPF_MAP_TYPE_LRU_HASH); // userspace part is not implemented yet -// define_hashmap!(LruPerCpuHashMap, bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH); // userspace part is not implemented yet +define_hashmap!( + /// Per-cpu hash table map + /// + /// High level API of BPF_MAP_TYPE_PERCPU_HASH maps for BPF programs. + /// + /// If you are looking for userspace API, see + /// [`redbpf::PerCpuHashMap`](../../redbpf/struct.PerCpuHashMap.html) + /// instead. + PerCpuHashMap, + bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH +); +define_hashmap!( + /// LRU hash table map + /// + /// High level API of BPF_MAP_TYPE_LRU_HASH maps for BPF programs. + /// + /// If you are looking for userspace API, see + /// [`redbpf::LruHashMap`](../../redbpf/struct.LruHashMap.html) instead. + LruHashMap, + bpf_map_type_BPF_MAP_TYPE_LRU_HASH +); +define_hashmap!( + /// LRU per-cpu hash table map + /// + /// High level API of BPF_MAP_TYPE_LRU_PERCPU_HASH maps for BPF programs. + /// + /// If you are looking for userspace API, see + /// [`redbpf::LruPerCpuHashMap`](../../redbpf/struct.LruPerCpuHashMap.html) + /// instead. + LruPerCpuHashMap, + bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH +); + define_array!( /// BPF array map for BPF programs /// From 2529aa8dbc18bd26311210867ee60dd865cc2d7a Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 9 Nov 2021 18:55:35 +0900 Subject: [PATCH 003/107] Add PerCpuHashMap, LruHashMap, LruPerCpuHashMap for userspace Signed-off-by: Junyeong Jeong --- redbpf/src/lib.rs | 460 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 392 insertions(+), 68 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 2d3889ee..b935b385 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -59,8 +59,10 @@ use bpf_sys::{ bpf_attach_type_BPF_SK_SKB_STREAM_PARSER, bpf_attach_type_BPF_SK_SKB_STREAM_VERDICT, bpf_attach_type_BPF_TRACE_ITER, bpf_create_map_attr, bpf_create_map_xattr, bpf_insn, bpf_iter_create, bpf_link_create, bpf_load_program_xattr, bpf_map_def, bpf_map_info, - bpf_map_type_BPF_MAP_TYPE_ARRAY, bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY, bpf_prog_type, - BPF_ANY, + bpf_map_type_BPF_MAP_TYPE_ARRAY, bpf_map_type_BPF_MAP_TYPE_HASH, + bpf_map_type_BPF_MAP_TYPE_LRU_HASH, bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH, + bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY, bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH, + bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY, bpf_prog_type, BPF_ANY, }; use goblin::elf::{reloc::RelocSection, section_header as hdr, Elf, SectionHeader, Sym}; @@ -236,6 +238,16 @@ pub struct TaskIter { link_fd: Option, } +/// A base BPF map data structure +/// +/// It is a base data structure that contains a map definition and auxiliary +/// data. It just hods data but it does not provide any useful API to users. +/// See [`HashMap`](./struct.HashMap.html), +/// [`LruHashMap`](./struct.LruHashMap.html), +/// [`PerCpuHashMap`](./struct.PerCpuHashMap.html), +/// [`LruPerCpuHashMap`](./struct.LruPerCpuHashMap.html), +/// [`Array`](./struct.Array.html), [`PerCpuArray`](./struct.PerCpuArray.html) +/// that wrap `Map` to provide API of BPF maps to userspace programs. #[derive(Debug)] pub struct Map { pub name: String, @@ -259,12 +271,51 @@ enum MapBuilder<'a> { ExistingMap(Map), } +/// A BPF hash map structure +/// +/// This provides higher level API for BPF maps whose type is +/// `BPF_MAP_TYPE_HASH` pub struct HashMap<'a, K: Clone, V: Clone> { base: &'a Map, _k: PhantomData, _v: PhantomData, } +/// A BPF LRU hash map structure +/// +/// This provides higher level API for BPF maps whose type is +/// `BPF_MAP_TYPE_LRU_HASH` +pub struct LruHashMap<'a, K: Clone, V: Clone> { + base: &'a Map, + _k: PhantomData, + _v: PhantomData, +} + +/// A per-cpu BPF hash map structure +/// +/// This provides higher level API for BPF maps whose type is +/// `BPF_MAP_TYPE_PERCPU_HASH` +pub struct PerCpuHashMap<'a, K: Clone, V: Clone> { + base: &'a Map, + _k: PhantomData, + _v: PhantomData>, +} + +/// An LRU per-cpu BPF hash map structure +/// +/// This provides higher level API for BPF maps whose type is +/// `BPF_MAP_TYPE_LRU_PERCPU_HASH` +pub struct LruPerCpuHashMap<'a, K: Clone, V: Clone> { + base: &'a Map, + _k: PhantomData, + _v: PhantomData>, +} + +/// A stacktrace BPF map structure +/// +/// Stacktrace map provides a feature of getting an array of instruction +/// pointers that are stored in the BPF map whose type is +/// `BPF_MAP_TYPE_STACK_TRACE`. pub struct StackTrace<'a> { base: &'a Map, } @@ -343,6 +394,11 @@ pub struct RelocationInfo { sym_idx: usize, } +trait MapIterable { + fn get(&self, key: K) -> Option; + fn next_key(&self, key: Option) -> Option; +} + impl Program { #[allow(clippy::unnecessary_wraps)] fn new(kind: &str, name: &str, code: &[u8]) -> Result { @@ -1371,7 +1427,11 @@ impl<'a> ModuleBuilder<'a> { if self.programs.contains_key(&rel.target_sec_idx) { if let Err(_) = rel.apply(&mut self.programs, &maps, &symtab) { // means that not normal case, we should rely on symbol value instead of section header index - rel.apply_with_symmap(&mut self.programs, &symval_to_maps, &symtab)?; + rel.apply_with_symmap(&mut self.programs, &symval_to_maps, &symtab) + .map_err(|e| { + error!("can not relocate map"); + e + })?; } } } @@ -1792,9 +1852,11 @@ impl<'base, K: Clone, V: Clone> HashMap<'base, K, V> { pub fn new(base: &Map) -> Result> { if mem::size_of::() != base.config.key_size as usize || mem::size_of::() != base.config.value_size as usize + || (bpf_map_type_BPF_MAP_TYPE_HASH != base.config.type_ + && bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY != base.config.type_) { error!( - "map definitions (sizes of key and value) of base `Map' and + "map definitions (map type and key/value size) of base `Map' and `HashMap' do not match" ); return Err(Error::Map); @@ -1807,46 +1869,211 @@ impl<'base, K: Clone, V: Clone> HashMap<'base, K, V> { }) } - pub fn set(&self, mut key: K, mut value: V) { - unsafe { - bpf_sys::bpf_map_update_elem( - self.base.fd, - &mut key as *mut _ as *mut _, - &mut value as *mut _ as *mut _, - 0, + pub fn set(&self, key: K, value: V) { + let _ = bpf_map_set(self.base.fd, key, value); + } + + pub fn get(&self, key: K) -> Option { + bpf_map_get(self.base.fd, key) + } + + pub fn delete(&self, key: K) { + let _ = bpf_map_delete(self.base.fd, key); + } + + /// Return an iterator over all items in the map + pub fn iter<'a>(&'a self) -> MapIter<'a, K, V> { + MapIter { + iterable: self, + last_key: None, + } + } +} + +impl MapIterable for HashMap<'_, K, V> { + fn get(&self, key: K) -> Option { + HashMap::get(self, key) + } + + fn next_key(&self, key: Option) -> Option { + bpf_map_get_next_key(self.base.fd, key) + } +} + +impl<'base, K: Clone, V: Clone> LruHashMap<'base, K, V> { + pub fn new(base: &Map) -> Result> { + if mem::size_of::() != base.config.key_size as usize + || mem::size_of::() != base.config.value_size as usize + || bpf_map_type_BPF_MAP_TYPE_LRU_HASH != base.config.type_ + { + error!( + "map definitions (map type and key/value sizes) of base `Map' and `LruHashMap' do not match" ); + return Err(Error::Map); } + + Ok(LruHashMap { + base, + _k: PhantomData, + _v: PhantomData, + }) } - pub fn get(&self, mut key: K) -> Option { - let mut value = MaybeUninit::zeroed(); - if unsafe { - bpf_sys::bpf_map_lookup_elem( - self.base.fd, - &mut key as *mut _ as *mut _, - &mut value as *mut _ as *mut _, - ) - } < 0 + pub fn set(&self, key: K, value: V) { + let _ = bpf_map_set(self.base.fd, key, value); + } + + pub fn get(&self, key: K) -> Option { + bpf_map_get(self.base.fd, key) + } + + pub fn delete(&self, key: K) { + let _ = bpf_map_delete(self.base.fd, key); + } + + /// Return an iterator over all items in the map + pub fn iter<'a>(&'a self) -> MapIter<'a, K, V> { + MapIter { + iterable: self, + last_key: None, + } + } +} + +impl MapIterable for LruHashMap<'_, K, V> { + fn get(&self, key: K) -> Option { + LruHashMap::<'_, K, V>::get(self, key) + } + + fn next_key(&self, key: Option) -> Option { + bpf_map_get_next_key(self.base.fd, key) + } +} + +impl<'base, K: Clone, V: Clone> PerCpuHashMap<'base, K, V> { + pub fn new(base: &Map) -> Result> { + if mem::size_of::() != base.config.key_size as usize + || mem::size_of::() != base.config.value_size as usize + || bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH != base.config.type_ { - return None; + error!("map definitions (size of key/value and map type) of base `Map' and `PerCpuHashMap' do not match"); + return Err(Error::Map); } - Some(unsafe { value.assume_init() }) + + Ok(PerCpuHashMap { + base, + _k: PhantomData, + _v: PhantomData, + }) } - pub fn delete(&self, mut key: K) { - unsafe { - bpf_sys::bpf_map_delete_elem(self.base.fd, &mut key as *mut _ as *mut _); + /// Set per-cpu `values` to the BPF map at `key` + /// + /// The number of elements in `values` should be equal to the number of + /// possible CPUs. This requirement is automatically fulfilled when + /// `values` is created by + /// [`PerCpuValues::new`](./struct.PerCpuValues.html#method.new) + /// + /// `Err` can be returned if the number of elements is wrong or underlying + /// bpf_map_update_elem function returns a negative value. + pub fn set(&self, key: K, values: PerCpuValues) -> Result<()> { + bpf_percpu_map_set(self.base.fd, key, values) + } + + /// Get per-cpu values corresponding to the `key` from the BPF map + /// + /// If `key` is found, `Some([PerCpuValues](./struct.PerCpuValues.html))` + /// is returned. + pub fn get(&self, key: K) -> Option> { + bpf_percpu_map_get(self.base.fd, key) + } + + /// Delete `key` from the BPF map + pub fn delete(&self, key: K) { + let _ = bpf_map_delete(self.base.fd, key); + } + + /// Return an iterator over all items in the map + pub fn iter<'a>(&'a self) -> MapIter<'a, K, PerCpuValues> { + MapIter { + iterable: self, + last_key: None, } } +} - pub fn iter<'a>(&'a self) -> MapIter<'a, '_, K, V> { +impl MapIterable> for PerCpuHashMap<'_, K, V> { + fn get(&self, key: K) -> Option> { + PerCpuHashMap::get(self, key) + } + + fn next_key(&self, key: Option) -> Option { + bpf_map_get_next_key(self.base.fd, key) + } +} + +impl<'base, K: Clone, V: Clone> LruPerCpuHashMap<'base, K, V> { + pub fn new(base: &Map) -> Result> { + if mem::size_of::() != base.config.key_size as usize + || mem::size_of::() != base.config.value_size as usize + || bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH != base.config.type_ + { + error!("map definitions (size of key/value and map type) of base `Map' and `LruPerCpuHashMap' do not match"); + return Err(Error::Map); + } + + Ok(LruPerCpuHashMap { + base, + _k: PhantomData, + _v: PhantomData, + }) + } + + /// Set per-cpu `values` to the BPF map at `key` + /// + /// The number of elements in `values` should be equal to the number of + /// possible CPUs. This requirement is automatically fulfilled when + /// `values` is created by + /// [`PerCpuValues::new`](./struct.PerCpuValues.html#method.new) + /// + /// `Err` can be returned if the number of elements is wrong or underlying + /// bpf_map_update_elem function returns a negative value. + pub fn set(&self, key: K, values: PerCpuValues) -> Result<()> { + bpf_percpu_map_set(self.base.fd, key, values) + } + + /// Get per-cpu values corresponding to the `key` from the BPF map + /// + /// If `key` is found, `Some([PerCpuValues](./struct.PerCpuValues.html))` + /// is returned. + pub fn get(&self, key: K) -> Option> { + bpf_percpu_map_get(self.base.fd, key) + } + + /// Delete `key` from the BPF map + pub fn delete(&self, key: K) { + let _ = bpf_map_delete(self.base.fd, key); + } + + /// Return an iterator over all items in the map + pub fn iter<'a>(&'a self) -> MapIter<'a, K, PerCpuValues> { MapIter { - map: self, - key: None, + iterable: self, + last_key: None, } } } +impl MapIterable> for LruPerCpuHashMap<'_, K, V> { + fn get(&self, key: K) -> Option> { + LruPerCpuHashMap::get(self, key) + } + + fn next_key(&self, key: Option) -> Option { + bpf_map_get_next_key(self.base.fd, key) + } +} + impl<'base, T: Clone> Array<'base, T> { /// Create `Array` map from `base` pub fn new(base: &Map) -> Result> { @@ -2129,51 +2356,21 @@ impl<'base> ProgramArray<'base> { } } -pub struct MapIter<'a, 'b, K: Clone, V: Clone> { - map: &'a HashMap<'b, K, V>, - key: Option, +pub struct MapIter<'a, K: Clone, V: Clone> { + iterable: &'a dyn MapIterable, + last_key: Option, } -impl Iterator for MapIter<'_, '_, K, V> { +impl Iterator for MapIter<'_, K, V> { type Item = (K, V); fn next(&mut self) -> Option { - let key = self.key.take(); - self.key = match key { - Some(mut key) => { - let mut next_key = MaybeUninit::::zeroed(); - let ret = unsafe { - bpf_sys::bpf_map_get_next_key( - self.map.base.fd, - &mut key as *mut _ as *mut _, - &mut next_key as *mut _ as *mut _, - ) - }; - if ret < 0 { - None - } else { - Some(unsafe { next_key.assume_init() }) - } - } - None => { - let mut key = MaybeUninit::::zeroed(); - if unsafe { - bpf_sys::bpf_map_get_next_key( - self.map.base.fd, - ptr::null(), - &mut key as *mut _ as *mut _, - ) - } < 0 - { - None - } else { - Some(unsafe { key.assume_init() }) - } - } - }; - - let key = self.key.as_ref()?.clone(); - Some((key.clone(), self.map.get(key).unwrap())) + let key = self.last_key.take(); + self.last_key = self.iterable.next_key(key); + Some(( + self.last_key.as_ref()?.clone(), + self.iterable.get(self.last_key.as_ref()?.clone())?, + )) } } @@ -2434,3 +2631,130 @@ fn data<'d>(bytes: &'d [u8], shdr: &SectionHeader) -> &'d [u8] { &bytes[offset..end] } + +fn bpf_map_set(fd: RawFd, mut key: K, mut value: V) -> Result<()> { + if unsafe { + bpf_sys::bpf_map_update_elem( + fd, + &mut key as *mut _ as *mut _, + &mut value as *mut _ as *mut _, + 0, + ) + } < 0 + { + Err(Error::Map) + } else { + Ok(()) + } +} + +fn bpf_map_get(fd: RawFd, mut key: K) -> Option { + let mut value = MaybeUninit::zeroed(); + if unsafe { + bpf_sys::bpf_map_lookup_elem( + fd, + &mut key as *mut _ as *mut _, + &mut value as *mut _ as *mut _, + ) + } < 0 + { + return None; + } + Some(unsafe { value.assume_init() }) +} + +fn bpf_map_delete(fd: RawFd, mut key: K) -> Result<()> { + if unsafe { bpf_sys::bpf_map_delete_elem(fd, &mut key as *mut _ as *mut _) } < 0 { + Err(Error::Map) + } else { + Ok(()) + } +} + +fn bpf_map_get_next_key(fd: RawFd, key: Option) -> Option { + if let Some(mut key) = key { + let mut next_key = MaybeUninit::::zeroed(); + let ret = unsafe { + bpf_sys::bpf_map_get_next_key( + fd, + &mut key as *mut _ as *mut _, + &mut next_key as *mut _ as *mut _, + ) + }; + if ret < 0 { + None + } else { + Some(unsafe { next_key.assume_init() }) + } + } else { + let mut key = MaybeUninit::::zeroed(); + if unsafe { bpf_sys::bpf_map_get_next_key(fd, ptr::null(), &mut key as *mut _ as *mut _) } + < 0 + { + None + } else { + Some(unsafe { key.assume_init() }) + } + } +} + +fn bpf_percpu_map_set( + fd: RawFd, + mut key: K, + values: PerCpuValues, +) -> Result<()> { + let count = cpus::get_possible_num(); + if values.len() != count { + return Err(Error::Map); + } + + // It is needed to round up the value size to 8*N bytes + // cf., https://elixir.bootlin.com/linux/v5.8/source/kernel/bpf/syscall.c#L1103 + let value_size = round_up::(8); + let alloc_size = value_size * count; + let mut alloc = vec![0u8; alloc_size]; + let mut data = alloc.as_mut_ptr(); + for i in 0..count { + unsafe { + let dst_ptr = data.add(value_size * i) as *const V as *mut V; + ptr::write_unaligned::(dst_ptr, values[i].clone()); + } + } + if unsafe { + bpf_sys::bpf_map_update_elem( + fd, + &mut key as *mut _ as *mut _, + &mut data as *mut _ as *mut _, + 0, + ) + } < 0 + { + Err(Error::Map) + } else { + Ok(()) + } +} + +fn bpf_percpu_map_get(fd: RawFd, mut key: K) -> Option> { + // It is needed to round up the value size to 8*N + // cf., https://elixir.bootlin.com/linux/v5.8/source/kernel/bpf/syscall.c#L1035 + let value_size = round_up::(8); + let count = cpus::get_possible_num(); + let alloc_size = value_size * count; + let mut alloc = vec![0u8; alloc_size]; + let data = alloc.as_mut_ptr(); + if unsafe { bpf_sys::bpf_map_lookup_elem(fd, &mut key as *mut _ as *mut _, data as *mut _) } < 0 + { + return None; + } + + let mut values = Vec::with_capacity(count); + for i in 0..count { + unsafe { + let elem_ptr = data.add(value_size * i) as *const V; + values.push(ptr::read_unaligned(elem_ptr)); + } + } + + Some(values.into()) +} From 14d568983bde58a351cc721dd4a5f502fae1eb3e Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 9 Nov 2021 18:56:58 +0900 Subject: [PATCH 004/107] Implement From for PerCpuValues Signed-off-by: Junyeong Jeong --- redbpf/src/lib.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index b935b385..9cfdd3f5 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -2151,12 +2151,14 @@ fn round_up(unit_size: usize) -> usize { /// the same with [`cpus::get_possible_num`](./cpus/fn.get_possible_num.html). /// It also implements `Deref` and `DerefMut` so it can be used as a normal /// array. +/// /// # Example /// ```no_run /// use redbpf::PerCpuValues; /// let mut values = PerCpuValues::::new(0); /// values[0] = 1; /// ``` +#[derive(Clone, Debug)] pub struct PerCpuValues(Box<[T]>); impl PerCpuValues { @@ -2167,12 +2169,19 @@ impl PerCpuValues { pub fn new(default_value: T) -> Self { let count = cpus::get_possible_num(); let v = vec![default_value; count]; - Self(v.into_boxed_slice()) + Self(v.into()) } +} + +impl From> for PerCpuValues { + fn from(values: Box<[T]>) -> Self { + Self(values) + } +} - // This is called by `get` methods of per-cpu map structures - fn from_boxed_slice(v: Box<[T]>) -> Self { - Self(v) +impl From> for PerCpuValues { + fn from(values: Vec) -> Self { + Self::from(values.into_boxed_slice()) } } @@ -2281,7 +2290,7 @@ impl<'base, T: Clone> PerCpuArray<'base, T> { } } - Some(PerCpuValues::from_boxed_slice(values.into_boxed_slice())) + Some(values.into()) } /// Get length of array map From afb8b8e86b07b074072d0209234deda19b9515f1 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 9 Nov 2021 18:58:40 +0900 Subject: [PATCH 005/107] Add hashmaps example Signed-off-by: Junyeong Jeong --- examples/example-probes/Cargo.toml | 5 ++ examples/example-probes/src/hashmaps/main.rs | 68 ++++++++++++++++ examples/example-probes/src/hashmaps/mod.rs | 15 ++++ examples/example-probes/src/lib.rs | 3 +- .../example-userspace/examples/hashmaps.rs | 77 +++++++++++++++++++ 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 examples/example-probes/src/hashmaps/main.rs create mode 100644 examples/example-probes/src/hashmaps/mod.rs create mode 100644 examples/example-userspace/examples/hashmaps.rs diff --git a/examples/example-probes/Cargo.toml b/examples/example-probes/Cargo.toml index bea65eb4..9bfb7df0 100644 --- a/examples/example-probes/Cargo.toml +++ b/examples/example-probes/Cargo.toml @@ -73,3 +73,8 @@ required-features = ["probes"] name = "tasks" path = "src/tasks/main.rs" required-features = ["probes", "kernel5_8"] + +[[bin]] +name = "hashmaps" +path = "src/hashmaps/main.rs" +required-features = ["probes"] diff --git a/examples/example-probes/src/hashmaps/main.rs b/examples/example-probes/src/hashmaps/main.rs new file mode 100644 index 00000000..97c47615 --- /dev/null +++ b/examples/example-probes/src/hashmaps/main.rs @@ -0,0 +1,68 @@ +//! This is an example of showing difference between `PerCpuHashMap` and +//! `HashMap`. The former is per-cpu data structure and users don't need to +//! worry about race condition. The latter is global data structure so it has +//! race condition problems. +//! +//! `PerCpuArray` can be used instead of bpf stack to hold temporary values +//! that exceeds the maximum size of bpf stack (512 bytes). +#![no_std] +#![no_main] +use example_probes::hashmaps::*; +use redbpf_probes::kprobe::prelude::*; + +program!(0xFFFFFFFE, "GPL"); + +#[map] +static mut ALT_STACK: PerCpuArray = PerCpuArray::with_max_entries(1); + +#[map] +static mut BIG_STRUCT: LruHashMap = LruHashMap::with_max_entries(16); + +#[map] +static mut PCPU_MEM_ALLOC: PerCpuHashMap = PerCpuHashMap::with_max_entries(16); + +#[map] +static mut MEM_ALLOC: HashMap = HashMap::with_max_entries(16); + +#[kprobe] +unsafe fn sched_fork(_regs: Registers) { + let rnd_key = (bpf_get_prandom_u32() & 0xff) as i8; + if let Some(bigstruct) = BIG_STRUCT.get_mut(&rnd_key) { + bigstruct.f2[99] = 99; + BIG_STRUCT.set(&rnd_key, bigstruct); + } else { + // maximum size of bpf stack is 512 bytes. BigStructure struct is 808 + // bytes. So it can not be located in stack. Use percpu array to hold + // temporary BigStructure value. Note that if percpu array is used for + // this purpose, the size of percpu array must be 1. This is checked by + // BPF verifier. + let bigstruct = ALT_STACK.get_mut(0).unwrap(); + for x in 0..=99 { + bigstruct.f2[x] = x; + } + + BIG_STRUCT.set(&rnd_key, bigstruct); + } +} + +#[kprobe] +unsafe fn __kmalloc(regs: Registers) { + let mut size = regs.parm1() as usize; + let mut max: usize = 9999; + for x in 1..=12 { + size >>= 1; + if size == 0 { + max = usize::pow(2, x) - 1; + break; + } + } + if let Some(count) = PCPU_MEM_ALLOC.get_mut(&max) { + *count += 1; + let count = MEM_ALLOC.get_mut(&max).unwrap(); + *count += 1; + } else { + let count = 1; + PCPU_MEM_ALLOC.set(&max, &count); + MEM_ALLOC.set(&max, &count); + } +} diff --git a/examples/example-probes/src/hashmaps/mod.rs b/examples/example-probes/src/hashmaps/mod.rs new file mode 100644 index 00000000..bae5abb4 --- /dev/null +++ b/examples/example-probes/src/hashmaps/mod.rs @@ -0,0 +1,15 @@ +#[repr(C)] +#[derive(Clone, Debug)] +pub struct BigStructure { + pub f1: usize, + pub f2: [usize; 100], +} + +impl Default for BigStructure { + fn default() -> Self { + BigStructure { + f1: 0, + f2: [0; 100], + } + } +} diff --git a/examples/example-probes/src/lib.rs b/examples/example-probes/src/lib.rs index ea9df2e3..83f72982 100644 --- a/examples/example-probes/src/lib.rs +++ b/examples/example-probes/src/lib.rs @@ -12,7 +12,8 @@ pub mod bindings; pub mod echo; +pub mod hashmaps; pub mod mallocstacks; +pub mod tasks; pub mod tcp_lifetime; pub mod vfsreadlat; -pub mod tasks; diff --git a/examples/example-userspace/examples/hashmaps.rs b/examples/example-userspace/examples/hashmaps.rs new file mode 100644 index 00000000..8bc0f796 --- /dev/null +++ b/examples/example-userspace/examples/hashmaps.rs @@ -0,0 +1,77 @@ +//! This example shows usage of HashMap, PerCpuHashMap and LruHashMap. And +//! also it confirms you that hashmap has race condition problems. You should +//! consider PerCpuHashMap if your program needs to store accurate map data. + +use libc; +use std::process; +use std::time::Duration; +use tokio::{signal::ctrl_c, time::sleep}; +use tracing::{error, subscriber, Level}; +use tracing_subscriber::FmtSubscriber; + +use probes::hashmaps::BigStructure; +use redbpf::{load::Loader, HashMap, LruHashMap, PerCpuHashMap}; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + let subscriber = FmtSubscriber::builder() + .with_max_level(Level::TRACE) + .finish(); + subscriber::set_global_default(subscriber).unwrap(); + if unsafe { libc::geteuid() != 0 } { + error!("You must be root to use eBPF!"); + process::exit(1); + } + + let mut loaded = Loader::load(probe_code()).expect("error loading probe"); + for kp in loaded.kprobes_mut() { + kp.attach_kprobe(kp.name().as_str(), 0) + .expect(format!("error on attach_kprobe to {}", kp.name()).as_str()); + } + + let big_struct = + LruHashMap::::new(loaded.map("BIG_STRUCT").expect("map not found")) + .expect("error on LruHashMap::new"); + let pcpu_mem_alloc = + PerCpuHashMap::::new(loaded.map("PCPU_MEM_ALLOC").expect("map not found")) + .expect("error on PerCpuHashMap::new"); + let mem_alloc = HashMap::::new(loaded.map("MEM_ALLOC").expect("map not found")) + .expect("error on HashMap::new"); + println!("Hit Ctrl-C to quit"); + loop { + tokio::select! { + _ = sleep(Duration::from_secs(1)) => {} + _ = ctrl_c() => break + } + + let mut alloc_stats = mem_alloc.iter().collect::>(); + alloc_stats.sort(); + println!("[allocation size upto XXX bytes] => [number of __kmalloc call]"); + + for (size, total_cnt) in alloc_stats { + let pcpu_vals = pcpu_mem_alloc.get(size).unwrap(); + let exact_cnt: usize = pcpu_vals.iter().sum(); + if total_cnt != exact_cnt { + println!( + "{} => {} != {} (hashmap != pcpu hashmap)", + size, total_cnt, exact_cnt + ); + } else { + println!("{} => {}", size, total_cnt); + } + } + } + + println!(""); + println!("iterate over big structures!"); + for (_, bigstruct) in big_struct.iter() { + println!("{:?}", bigstruct); + } +} + +fn probe_code() -> &'static [u8] { + include_bytes!(concat!( + env!("OUT_DIR"), + "/target/bpf/programs/hashmaps/hashmaps.elf" + )) +} From cc407bf04298263653cf5b98677bcf0a84bb633e Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 12 Nov 2021 00:01:13 +0900 Subject: [PATCH 006/107] Update tutorial A few users had struggled with problems originated from RedBPF. And they asked how to solve the tricky problems via github issues. The updated content consists of answers to issues as below: - https://github.com/foniod/redbpf/issues/213 - https://github.com/foniod/redbpf/issues/194 - https://github.com/foniod/redbpf/issues/212 - https://github.com/foniod/redbpf/issues/205 Thanks to users effort and their report, this content can be written. Signed-off-by: Junyeong Jeong --- TUTORIAL.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/TUTORIAL.md b/TUTORIAL.md index a9096340..4fe9c313 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -36,6 +36,59 @@ Small background programs and BPF maps to kernel space and communicate with BPF programs through BPF maps. +### Valid combinations of rust and LLVM versions + +`rustc` uses its own version of LLVM. But RedBPF also requires LLVM installed +in the system. In order to compile BPF programs, RedBPF makes use of `rustc` to +emit bitcode first and then parses and optimizes the bitcode by calling LLVM +API directly. Thus, two versions of LLVM are used while compiling BPF programs. + +- the version of LLVM that `rustc` depends on +- the version of LLVM which is installed in system + +Two versions should match. + +First RedBPF executes `rustc` to emit bitcode and second it calls LLVM API to +handle the resulting bitcode. Normally LLVM is likely to support backward +compatibility for intermediate representation. Thus, it is okay to use `rustc` +that depends on the LLVM version that is equal to or less than system LLVM. + + +| Rust version | LLVM version of the Rust | Valid system LLVM version | +|:-------------|:------------------------:|:--------------------------| +| 1.56 ~ | LLVM 13 | LLVM 13 | +| 1.52 ~ 1.55 | LLVM 12 | LLVM 13, LLVM 12 | +| 1.48 ~ 1.51 | LLVM 11 | LLVM 13, LLVM 12, LLVM 11 | + +* The minimum rust version for compiling `redbpf` is Rust 1.48 + +### Building LLVM from source + +*If you already installed LLVM with a package manager you can skip this this +section. Installing LLVM by a package manager is a simple and preferred way.* + +For some reasons, you may want to build LLVM from source code. + +When you build LLVM, consider building LLVM with `Release` build mode. + +For example, when you build LLVM12 from source code, you can pass +`-DCMAKE_BUILD_TYPE=Release` to the `cmake` command as below: + +```console +$ tar -xaf llvm-12.0.0.src.tar.xz +$ mkdir -p llvm-12.0.0.src/build +$ cd llvm-12.0.0.src/build +$ cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/llvm-12-release -DCMAKE_BUILD_TYPE=Release +$ cmake --build . --target install +``` + +Unless you plan to debug LLVM itself, `Release` or `MinSizeRel` is a good +choice. + +If you try compiling BPF programs with a `Debug` LLVM, the memory consumption +can be increased over 20GB! And also it takes more time to finish. See [this +issue](https://github.com/foniod/redbpf/issues/194#issuecomment-940964881) for +more information. Let's make our first program using RedBPF ---- @@ -57,6 +110,13 @@ $ cargo install cargo-bpf This command is working as a cargo sub-command: `cargo bpf`. +> **NOTE:** If you use LLVM 13, you should specify extra options to compile it +> successfully. +> +> ```console +> $ cargo install cargo-bpf --no-default-features --features=llvm13,command-line +> ``` + Let's create a normal cargo project, `redbpf-tutorial`: ```console $ cargo new redbpf-tutorial @@ -200,6 +260,14 @@ this case, this BPF program prints error message to a file `/sys/kernel/debug/tracing/trace_pipe` by using `bpf_trace_printk`. Note that the **bytes passed to `bpf_trace_printk` should include terminal `NUL` byte**. +> **NOTE:** Your Linux kernel may not provide `bpf_probe_read_user_str` BPF +> helper function. This function is introduced by the Linux v5.5 so if your +> kernel is older than that, the BPF verifier would complain *"invalid func +> unknown#114"*. +> +> In this situation, you can use `bpf_probe_read_str` instead. It is the old +> version of `bpf_probe_read_user_str`. + The **full source code** of `src/openmonitor/main.rs` is here: ```rust From a70f1031a7f54ff97ee0577e680e4107caa5e535 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 16 Nov 2021 00:24:31 +0900 Subject: [PATCH 007/107] Provide struct bpf_timer forward declaration along with vmlinux.h Signed-off-by: Junyeong Jeong --- redbpf-probes/build.rs | 8 ++++++++ redbpf-probes/include/vmlinux_supplement.h | 2 -- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/redbpf-probes/build.rs b/redbpf-probes/build.rs index 27845240..301abfc5 100644 --- a/redbpf-probes/build.rs +++ b/redbpf-probes/build.rs @@ -193,6 +193,11 @@ fn generate_bindings_vmlinux() -> Result<()> { let xdp_vars = ["^IPPROTO_.*"]; let mut builder = bpf_bindgen::get_builder_vmlinux(out_dir.join("vmlinux.h")) .or_else(|e| bail!("error on bpf_bindgen::get_builder_vmlinux: {}", e))? + // Since the Linux v5.15, vmlinux contains `struct bpf_timer`. To + // support older kernel versions, forward declaration of struct + // bpf_timer should be provided. And it is okay to have the forward + // declaration even though the definition exists before it. + .header_contents("bpf_timer.h", "struct bpf_timer;") // // Prevent error E0133: `#[derive]` can't be used on a // // `#[repr(packed)]` struct that does not derive Copy .no_debug("ec_response_motion_sense_fifo_info") @@ -235,6 +240,9 @@ fn generate_bindings_vmlinux() -> Result<()> { bindings.push_str(&accessors); // macro constants and structures of userspace can not be generated by BTF // of vmlinux. So missing parts are generated with the supplement header. + // It is difficult to include the header files along with vmlinux.h to + // generate gen_bindings.rs at once because lots of redefinitions exist in + // the supplement header. So another call to bindgen is conducted here. let supplement = bindgen::builder() .use_core() .ctypes_prefix("::cty") diff --git a/redbpf-probes/include/vmlinux_supplement.h b/redbpf-probes/include/vmlinux_supplement.h index 4e2525f0..aa1940a8 100644 --- a/redbpf-probes/include/vmlinux_supplement.h +++ b/redbpf-probes/include/vmlinux_supplement.h @@ -1,7 +1,5 @@ // supplements that are not generated by BTF of vmlinux -struct bpf_timer; - struct bpf_map_def { unsigned int type; unsigned int key_size; From 34f728d6f2afc09e05b09d6d46266fc08c5a7c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Th=C3=A9riault?= Date: Mon, 8 Nov 2021 23:04:41 -0800 Subject: [PATCH 008/107] sk_lookup support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Thériault --- redbpf/src/error.rs | 1 + redbpf/src/lib.rs | 80 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/redbpf/src/error.rs b/redbpf/src/error.rs index 26de76e6..30e4dbc7 100644 --- a/redbpf/src/error.rs +++ b/redbpf/src/error.rs @@ -20,6 +20,7 @@ pub enum Error { SymbolNotFound(String), ProgramAlreadyLoaded, ProgramNotLoaded, + ProgramAlreadyLinked, ElfError, BTF(String), } diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 9cfdd3f5..08d45ab8 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -56,10 +56,10 @@ pub mod xdp; pub use bpf_sys::uname; use bpf_sys::{ - bpf_attach_type_BPF_SK_SKB_STREAM_PARSER, bpf_attach_type_BPF_SK_SKB_STREAM_VERDICT, - bpf_attach_type_BPF_TRACE_ITER, bpf_create_map_attr, bpf_create_map_xattr, bpf_insn, - bpf_iter_create, bpf_link_create, bpf_load_program_xattr, bpf_map_def, bpf_map_info, - bpf_map_type_BPF_MAP_TYPE_ARRAY, bpf_map_type_BPF_MAP_TYPE_HASH, + bpf_attach_type_BPF_SK_LOOKUP, bpf_attach_type_BPF_SK_SKB_STREAM_PARSER, + bpf_attach_type_BPF_SK_SKB_STREAM_VERDICT, bpf_attach_type_BPF_TRACE_ITER, bpf_create_map_attr, + bpf_create_map_xattr, bpf_insn, bpf_iter_create, bpf_link_create, bpf_load_program_xattr, + bpf_map_def, bpf_map_info, bpf_map_type_BPF_MAP_TYPE_ARRAY, bpf_map_type_BPF_MAP_TYPE_HASH, bpf_map_type_BPF_MAP_TYPE_LRU_HASH, bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH, bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY, bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH, bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY, bpf_prog_type, BPF_ANY, @@ -152,6 +152,7 @@ pub enum Program { StreamParser(StreamParser), StreamVerdict(StreamVerdict), TaskIter(TaskIter), + SkLookup(SkLookup), } struct ProgramData { @@ -238,6 +239,12 @@ pub struct TaskIter { link_fd: Option, } +/// Type to work with `sk_lookup` BPF programs. +pub struct SkLookup { + common: ProgramData, + link: Option<(RawFd, RawFd)>, +} + /// A base BPF map data structure /// /// It is a base data structure that contains a map definition and auxiliary @@ -440,6 +447,7 @@ impl Program { }), "streamparser" => Program::StreamParser(StreamParser { common }), "streamverdict" => Program::StreamVerdict(StreamVerdict { common }), + "sk_lookup" => Program::SkLookup(SkLookup { common, link: None }), _ => return Err(Error::Section(kind.to_string())), }) } @@ -482,6 +490,7 @@ impl Program { TracePoint(_) => bpf_sys::bpf_prog_type_BPF_PROG_TYPE_TRACEPOINT, StreamParser(_) | StreamVerdict(_) => bpf_sys::bpf_prog_type_BPF_PROG_TYPE_SK_SKB, TaskIter(_) => bpf_sys::bpf_prog_type_BPF_PROG_TYPE_TRACING, + SkLookup(_) => bpf_sys::bpf_prog_type_BPF_PROG_TYPE_SK_LOOKUP, } } @@ -497,6 +506,7 @@ impl Program { StreamParser(p) => &p.common, StreamVerdict(p) => &p.common, TaskIter(p) => &p.common, + SkLookup(p) => &p.common, } } @@ -512,6 +522,7 @@ impl Program { StreamParser(p) => &mut p.common, StreamVerdict(p) => &mut p.common, TaskIter(p) => &mut p.common, + SkLookup(p) => &mut p.common, } } @@ -1075,6 +1086,44 @@ impl SocketFilter { } } +impl SkLookup { + pub fn attach_sk_lookup(&mut self, namespace: &str) -> Result<()> { + if self.link.is_some() { + return Err(Error::ProgramAlreadyLinked); + } + + let fd = self.common.fd.ok_or(Error::ProgramNotLoaded)?; + unsafe { + let namespace = CString::new(namespace)?; + let nfd = libc::open(namespace.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC); + if nfd < 0 { + return Err(Error::IO(io::Error::last_os_error())); + } + + let lfd = bpf_link_create(fd, nfd, bpf_attach_type_BPF_SK_LOOKUP, ptr::null()); + if lfd < 0 { + libc::close(nfd); + return Err(Error::IO(io::Error::last_os_error())); + } + + self.link = Some((nfd, lfd)); + } + + Ok(()) + } +} + +impl Drop for SkLookup { + fn drop(&mut self) { + if let Some((nfd, lfd)) = self.link.take() { + unsafe { + libc::close(lfd); + libc::close(nfd); + } + } + } +} + impl Module { pub fn parse(bytes: &[u8]) -> Result { ModuleBuilder::parse(bytes)?.to_module() @@ -1236,6 +1285,26 @@ impl Module { self.stream_verdicts_mut().find(|p| p.common.name == name) } + pub fn sk_lookups(&self) -> impl Iterator { + use Program::*; + self.programs.iter().filter_map(|prog| match prog { + SkLookup(p) => Some(p), + _ => None, + }) + } + + pub fn sk_lookups_mut(&mut self) -> impl Iterator { + use Program::*; + self.programs.iter_mut().filter_map(|prog| match prog { + SkLookup(p) => Some(p), + _ => None, + }) + } + + pub fn sk_lookup_mut(&mut self, name: &str) -> Option<&mut SkLookup> { + self.sk_lookups_mut().find(|p| p.common.name == name) + } + pub fn task_iters(&self) -> impl Iterator { use Program::*; self.programs.iter().filter_map(|prog| match prog { @@ -1359,7 +1428,8 @@ impl<'a> ModuleBuilder<'a> { | (hdr::SHT_PROGBITS, Some(kind @ "xdp"), Some(name)) | (hdr::SHT_PROGBITS, Some(kind @ "socketfilter"), Some(name)) | (hdr::SHT_PROGBITS, Some(kind @ "streamparser"), Some(name)) - | (hdr::SHT_PROGBITS, Some(kind @ "streamverdict"), Some(name)) => { + | (hdr::SHT_PROGBITS, Some(kind @ "streamverdict"), Some(name)) + | (hdr::SHT_PROGBITS, Some(kind @ "sk_lookup"), Some(name)) => { let prog = Program::new(kind, name, &content)?; programs.insert(shndx, prog); } From 4411bc9789d5e000d11a55c271ac4ab60a2e7b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Th=C3=A9riault?= Date: Mon, 8 Nov 2021 23:15:58 -0800 Subject: [PATCH 009/107] sk_lookup support for loader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Thériault --- redbpf/src/load/loader.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/redbpf/src/load/loader.rs b/redbpf/src/load/loader.rs index 754210d6..4bd9d30a 100644 --- a/redbpf/src/load/loader.rs +++ b/redbpf/src/load/loader.rs @@ -15,8 +15,8 @@ use std::path::Path; use crate::load::map_io::PerfMessageStream; use crate::{cpus, Program}; use crate::{ - Error, KProbe, Map, Module, PerfMap, SocketFilter, StreamParser, StreamVerdict, TaskIter, - UProbe, XDP, + Error, KProbe, Map, Module, PerfMap, SkLookup, SocketFilter, StreamParser, StreamVerdict, + TaskIter, UProbe, XDP, }; #[derive(Debug)] @@ -169,6 +169,14 @@ impl Loaded { self.module.stream_verdict_mut(name) } + pub fn sk_lookups_mut(&mut self) -> impl Iterator { + self.module.sk_lookups_mut() + } + + pub fn sk_lookup_mut(&mut self, name: &str) -> Option<&mut SkLookup> { + self.module.sk_lookup_mut(name) + } + pub fn task_iters(&self) -> impl Iterator { self.module.task_iters() } From a8e0efd94a49dc96131dc9482a62fc59ea59aef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Th=C3=A9riault?= Date: Tue, 9 Nov 2021 09:30:22 -0800 Subject: [PATCH 010/107] sk_lookup loading fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Thériault --- redbpf/src/lib.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 08d45ab8..58bdd51e 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -566,12 +566,19 @@ impl Program { attr.license = clicense.as_ptr(); attr.log_level = 0; - if let Program::TaskIter(bpf_iter) = self { - attr.expected_attach_type = bpf_attach_type_BPF_TRACE_ITER; - attr.__bindgen_anon_2.attach_btf_id = bpf_iter.attach_btf_id; - } else { - attr.expected_attach_type = 0; - attr.__bindgen_anon_1.kern_version = kernel_version; + match self { + Program::TaskIter(bpf_iter) => { + attr.expected_attach_type = bpf_attach_type_BPF_TRACE_ITER; + attr.__bindgen_anon_2.attach_btf_id = bpf_iter.attach_btf_id; + } + Program::SkLookup(_) => { + attr.expected_attach_type = bpf_attach_type_BPF_SK_LOOKUP; + attr.__bindgen_anon_1.kern_version = kernel_version; + } + _ => { + attr.expected_attach_type = 0; + attr.__bindgen_anon_1.kern_version = kernel_version; + } } // do not pass log buffer. it is filled with verifier's log but @@ -2556,7 +2563,8 @@ impl<'a> SockMap<'a> { Ok(SockMap { base: map }) } - pub fn set(&mut self, mut idx: u32, mut fd: RawFd) -> Result<()> { + pub fn set(&mut self, mut idx: u32, fd: RawFd) -> Result<()> { + let mut fd = fd as u64; let ret = unsafe { bpf_sys::bpf_map_update_elem( self.base.fd, From f7821dc86ee99245eea15eb60a96e03724e48e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Th=C3=A9riault?= Date: Sun, 14 Nov 2021 21:40:29 -0800 Subject: [PATCH 011/107] Add sk_lookup documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Thériault --- redbpf/src/lib.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 58bdd51e..73773919 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -239,7 +239,56 @@ pub struct TaskIter { link_fd: Option, } -/// Type to work with `sk_lookup` BPF programs. +/// Type to work with [`sk_lookup`] BPF programs. +/// +/// `sk_lookup` programs were introduced with Linux 5.9 and make it possible to +/// programmatically perform socket lookup for new connections. +/// This can be used, for instance, to listen on a large number of addresses and ports +/// with a single socket. +/// +/// In order to take effect, `sk_lookup` programs must be attached to a +/// network namespace, which can be done with the [`attach_sk_lookup`] method. +/// +/// # Example +/// +/// The userland code for listening on a port range could look something like this. +/// +/// ```no_run +/// # static SK_LOOKUP: &[u8] = &[]; +/// use std::net::TcpListener; +/// use std::os::unix::io::AsRawFd +/// +/// use redbpf::{HashMap, SockMap}; +/// use redbpf::load::Loader; +/// +/// let mut listener = TcpListener::bind(("127.0.0.1", 12345)).unwrap(); +/// let mut loaded = Loader::load(SK_LOOKUP).unwrap(); +/// +/// // Pass the listener fd to the BPF program +/// let mut socket = SockMap::new(loaded.map("socket")).unwrap(); +/// socket.set(0, listener.as_raw_fd()); +/// +/// // Pass our port range to the BPF program +/// let mut ports = HashMap::::new(loaded.map("ports")).unwrap(); +/// for port in 80..430 { +/// ports.set(port, 1); +/// } +/// +/// // Attach the BPF program to the current process' network namespace +/// loaded +/// .sk_lookup_mut("range_listener") +/// .unwrap() +/// .attach_sk_lookup("/proc/self/ns/net") +/// .unwrap(); +/// +/// loop { +/// let (client, _) = listener.accept().unwrap(); +/// let addr = client.local_addr().unwrap(); +/// println!("accepted new connection on `{}`", addr); +/// } +/// ``` +/// +/// [`sk_lookup`]: https://github.com/torvalds/linux/blob/master/Documentation/bpf/prog_sk_lookup.rst pub struct SkLookup { common: ProgramData, link: Option<(RawFd, RawFd)>, @@ -1094,6 +1143,9 @@ impl SocketFilter { } impl SkLookup { + /// Attach the `sk_lookup` to the given network namespace. + /// + /// In most cases it should be attached to `/proc/self/ns/net`. pub fn attach_sk_lookup(&mut self, namespace: &str) -> Result<()> { if self.link.is_some() { return Err(Error::ProgramAlreadyLinked); From e09f22c70e6b230b92b51ce8efa839dc564f8f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Th=C3=A9riault?= Date: Sun, 14 Nov 2021 21:49:13 -0800 Subject: [PATCH 012/107] Remove temporary socket map fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Thériault --- redbpf/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 73773919..b5be7738 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -2615,8 +2615,7 @@ impl<'a> SockMap<'a> { Ok(SockMap { base: map }) } - pub fn set(&mut self, mut idx: u32, fd: RawFd) -> Result<()> { - let mut fd = fd as u64; + pub fn set(&mut self, mut idx: u32, mut fd: RawFd) -> Result<()> { let ret = unsafe { bpf_sys::bpf_map_update_elem( self.base.fd, From 827f7637eed3668e1abecb0c4c56f9b459910799 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 16 Nov 2021 02:03:11 +0900 Subject: [PATCH 013/107] Remove redbpf-macros/src/mem.rs Definitions of memcpy, memset, memcmp, memmove, bcmp are removed. Without these definitions all example probes and foniod probes are compiled successfully with llvm11, llvm12 and llvm13. And those probes can pass the BPF verifier. And it is unclear why these definitions should be included by `program!` macro. Signed-off-by: Junyeong Jeong --- redbpf-macros/src/lib.rs | 12 ++------ redbpf-macros/src/mem.rs | 65 ---------------------------------------- 2 files changed, 3 insertions(+), 74 deletions(-) delete mode 100644 redbpf-macros/src/mem.rs diff --git a/redbpf-macros/src/lib.rs b/redbpf-macros/src/lib.rs index e022a5a6..1b3ef5c5 100644 --- a/redbpf-macros/src/lib.rs +++ b/redbpf-macros/src/lib.rs @@ -52,8 +52,8 @@ use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{ - parse_macro_input, parse_quote, parse_str, AttributeArgs, Expr, ExprLit, File, GenericArgument, - ItemFn, ItemStatic, Lit, Meta, NestedMeta, PathArguments, Result, Type, + parse_macro_input, parse_quote, AttributeArgs, Expr, ExprLit, GenericArgument, ItemFn, + ItemStatic, Lit, Meta, NestedMeta, PathArguments, Result, Type, }; use uuid::Uuid; @@ -110,7 +110,7 @@ pub fn program(input: TokenStream) -> TokenStream { let license = args.next().expect("no license"); let (license_ty, license) = inline_string_literal(&license); let (panic_ty, panic_msg) = inline_bytes(b"panic".to_vec()); - let mut tokens = quote! { + let tokens = quote! { #[no_mangle] #[link_section = "license"] pub static _license: #license_ty = #license; @@ -131,12 +131,6 @@ pub fn program(input: TokenStream) -> TokenStream { } }; - let mem = str::from_utf8(include_bytes!("mem.rs")).unwrap(); - let mem: File = parse_str(&mem).unwrap(); - tokens.extend(quote! { - #mem - }); - tokens.into() } diff --git a/redbpf-macros/src/mem.rs b/redbpf-macros/src/mem.rs deleted file mode 100644 index dc2fe385..00000000 --- a/redbpf-macros/src/mem.rs +++ /dev/null @@ -1,65 +0,0 @@ -#[allow(warnings)] -#[cfg(target_pointer_width = "16")] -type c_int = i16; -#[allow(warnings)] -#[cfg(not(target_pointer_width = "16"))] -type c_int = i32; - -#[no_mangle] -pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { - let mut i = 0; - while i < n { - *dest.offset(i as isize) = *src.offset(i as isize); - i += 1; - } - dest -} - -#[no_mangle] -pub unsafe extern "C" fn memmove(dest: *mut u8, src: *const u8, n: usize) -> *mut u8 { - if src < dest as *const u8 { - // copy from end - let mut i = n; - while i != 0 { - i -= 1; - *dest.offset(i as isize) = *src.offset(i as isize); - } - } else { - // copy from beginning - let mut i = 0; - while i < n { - *dest.offset(i as isize) = *src.offset(i as isize); - i += 1; - } - } - dest -} - -#[no_mangle] -pub unsafe extern "C" fn memset(s: *mut u8, c: c_int, n: usize) -> *mut u8 { - let mut i = 0; - while i < n { - *s.offset(i as isize) = c as u8; - i += 1; - } - s -} - -#[no_mangle] -pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { - let mut i = 0; - while i < n { - let a = *s1.offset(i as isize); - let b = *s2.offset(i as isize); - if a != b { - return a as i32 - b as i32; - } - i += 1; - } - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn bcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { - memcmp(s1, s2, n) -} \ No newline at end of file From 2ce88200fdc82315d394ef8cf689b0195d955159 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 16 Nov 2021 02:44:23 +0900 Subject: [PATCH 014/107] Strip .text section The .text section is ignored by redbpf when parsing the elf relocatable file and tc utility fails at loading the elf relocatable file if .text exists. So it is better to remove .text section. Signed-off-by: Junyeong Jeong --- cargo-bpf/src/build.rs | 12 ++++++-- cargo-bpf/src/llvm.rs | 69 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/cargo-bpf/src/build.rs b/cargo-bpf/src/build.rs index cd744eff..a68c64a3 100644 --- a/cargo-bpf/src/build.rs +++ b/cargo-bpf/src/build.rs @@ -173,8 +173,16 @@ fn build_probe( ) })?; - // stripping debug sections is optional process. So don't care its failure. - let _ = llvm::strip_debug(&target); + // stripping .debug sections, .text section and BTF sections is optional + // process. So don't care about its failure. + let contains_tc = unsafe { + llvm::get_function_section_names(&bc_file) + .map_or_else(|_| vec![], |names| names) + .iter() + .find(|name| name.starts_with("tc_action/")) + .is_some() + }; + let _ = llvm::strip_unnecessary(&target, contains_tc); Ok(()) } diff --git a/cargo-bpf/src/llvm.rs b/cargo-bpf/src/llvm.rs index 856582aa..6ff5bfed 100644 --- a/cargo-bpf/src/llvm.rs +++ b/cargo-bpf/src/llvm.rs @@ -137,19 +137,76 @@ pub unsafe fn compile(input: &Path, output: &Path, bc_output: Option<&Path>) -> ret } -/// Strip debug sections from resulting ELF relocatable file +/// Get section names of functions +/// +/// Only functions that do not belong to the default .text section have +/// particular section names. So thd default `.text` won't be included in the +/// result vector. +pub(crate) unsafe fn get_function_section_names(bc: &Path) -> Result> { + let mut section_names = vec![]; + let context = LLVMGetGlobalContext(); + let module = load_module(context, bc)?; + let mut func = LLVMGetFirstFunction(module); + while !func.is_null() { + let secptr = LLVMGetSection(func); + if !secptr.is_null() { + let secname = CStr::from_ptr(secptr).to_string_lossy().into_owned(); + section_names.push(secname); + } + func = LLVMGetNextFunction(func); + } + LLVMDisposeModule(module); + + Ok(section_names) +} + +/// Strip unnecessary sections from resulting ELF relocatable file /// /// This removes sections of which name start with `.debug` and their /// associated relocation sections. But .BTF related sections are not stripped. /// -/// cf) `llvm_sys::debuginfo::LLVMStripModuleDebugInfo` removes BTF sections so do not call it. -pub(crate) fn strip_debug(target: &impl AsRef) -> Result<()> { - Command::new("llvm-strip-12") - .arg("-g") +/// cf) `llvm_sys::debuginfo::LLVMStripModuleDebugInfo` removes BTF sections so +/// do not call it. +/// +/// .text section is also removed. +/// +pub(crate) fn strip_unnecessary(target: &impl AsRef, delete_btf: bool) -> Result<()> { + let cmd = [ + "llvm-strip", + "llvm-strip-13", + "llvm-strip-12", + "llvm-strip-11", + ] + .iter() + .find(|cmd| Command::new(cmd).arg("--version").status().is_ok()) + .ok_or_else(|| anyhow!("llvm-strip command not found"))?; + + Command::new(cmd) + .arg("--strip-debug") + .arg(target.as_ref()) + .status() + .map(|_| ()) + .or_else(|e| Err(anyhow!("llvm-strip --strip-debug failed: {}", e)))?; + + // Even if there does not exist any function in .text section, .text + // section is created with zero size as a result of compilation. So it is + // needed to remove it explictly. + let mut cmd = Command::new(cmd); + if delete_btf { + // The BTF section generated by rustc contains characters that are not + // permitted by BPF verifier of the Linux kernel. So those characters + // should be fixed before loading the BTF sections. But some utils that + // are not aware of rustc generated BTF such as `tc` do not handle + // this. In this case just remove BTF sections to avoid BPF verifier + // problem. + cmd.args("--remove-section .BTF.ext --remove-section .BTF".split(' ')); + } + cmd.args("--remove-section .text".split(' ')) + .arg("--no-strip-all") .arg(target.as_ref()) .status() .map(|_| ()) - .or_else(|e| Err(anyhow!("llvm-strip failed: {}", e))) + .or_else(|e| Err(anyhow!("llvm-strip --remove-section .text failed: {}", e))) } pub unsafe fn process_ir(context: LLVMContextRef, module: LLVMModuleRef) -> Result<()> { From 81399e197cfe0b3e190e13032380866f438014ca Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 16 Nov 2021 02:58:51 +0900 Subject: [PATCH 015/107] Simplify tc-map-share userspace code Now users don't need to remove .text section in person because redbpf handles it under the hood. Signed-off-by: Junyeong Jeong --- .../example-userspace/examples/tc-map-share.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/examples/example-userspace/examples/tc-map-share.rs b/examples/example-userspace/examples/tc-map-share.rs index 9ae4861c..1314fc76 100644 --- a/examples/example-userspace/examples/tc-map-share.rs +++ b/examples/example-userspace/examples/tc-map-share.rs @@ -48,24 +48,6 @@ async fn main() { env!("OUT_DIR"), "/target/bpf/programs/tc-map-share/tc-map-share.elf" ); - // remove .BTF.ext and .eh_frame in order to remove .text - // remove .text section because tc filter does not work if .text exists - // remove .BTF section because it contains invalid names of BTF types - // (currently kernel only allows valid symbol names of C) - for sec in &[".BTF.ext", ".eh_frame", ".text", ".BTF"] { - if !Command::new("llvm-strip-12") - .arg("--no-strip-all") - .arg("--remove-section") - .arg(sec) - .arg(&bpf_elf) - .status() - .expect("error on running command llvm-strip-12") - .success() - { - error!("error on removing section `{}' using llvm-strip-12", sec); - return; - } - } let new_clsact = Command::new("tc") .args("qdisc add dev lo clsact".split(" ")) .status() From 14e9d3458b25592acdb470798fa1cfb2905b6c71 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 16 Nov 2021 21:20:08 +0900 Subject: [PATCH 016/107] Change urls of documentation of packages Signed-off-by: Junyeong Jeong --- bpf-sys/Cargo.toml | 3 ++- cargo-bpf/Cargo.toml | 3 ++- redbpf-macros/Cargo.toml | 3 ++- redbpf-probes/Cargo.toml | 3 ++- redbpf/Cargo.toml | 5 +++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/bpf-sys/Cargo.toml b/bpf-sys/Cargo.toml index 9f1a6aa5..ecccc4f7 100644 --- a/bpf-sys/Cargo.toml +++ b/bpf-sys/Cargo.toml @@ -3,7 +3,8 @@ name = "bpf-sys" version = "2.1.0" description = "Bindings for libbpf" repository = "https://github.com/foniod/redbpf" -documentation = "https://foniod.org/api/bpf_sys/" +homepage = "https://foniod.org" +documentation = "https://docs.rs/bpf-sys/2.1.0/bpf_sys" authors = ["Peter Parkanyi ", "Junyeong Jeong "] links = "bpf" edition = "2018" diff --git a/cargo-bpf/Cargo.toml b/cargo-bpf/Cargo.toml index 126caead..e5ec7932 100644 --- a/cargo-bpf/Cargo.toml +++ b/cargo-bpf/Cargo.toml @@ -3,7 +3,8 @@ name = "cargo-bpf" version = "2.1.0" description = "Cargo plugin to manage eBPF probes using redbpf" repository = "https://github.com/foniod/redbpf" -documentation = "https://foniod.org/api/cargo_bpf/" +homepage = "https://foniod.org" +documentation = "https://docs.rs/cargo-bpf/2.1.0/cargo_bpf_lib/" authors = ["Alessandro Decina ", "Peter Parkanyi ", "Junyeong Jeong "] edition = "2018" keywords = ["cargo", "redbpf", "bpf", "plugin", "subcommand"] diff --git a/redbpf-macros/Cargo.toml b/redbpf-macros/Cargo.toml index 4bb3e0a9..bbbbd41d 100644 --- a/redbpf-macros/Cargo.toml +++ b/redbpf-macros/Cargo.toml @@ -2,7 +2,8 @@ name = "redbpf-macros" description = "Procedural macros for redbpf" repository = "https://github.com/foniod/redbpf" -documentation = "https://foniod.org/api/redbpf_macros/" +homepage = "https://foniod.org" +documentation = "https://docs.rs/redbpf-macros/2.1.0/redbpf_macros/" authors = ["Alessandro Decina ", "Peter Parkanyi ", "Junyeong Jeong "] version = "2.1.0" edition = '2018' diff --git a/redbpf-probes/Cargo.toml b/redbpf-probes/Cargo.toml index 995d8050..1ae14ce1 100644 --- a/redbpf-probes/Cargo.toml +++ b/redbpf-probes/Cargo.toml @@ -2,7 +2,8 @@ name = "redbpf-probes" description = "eBPF probe-related types for redbpf" repository = "https://github.com/foniod/redbpf" -documentation = "https://foniod.org/api/redbpf_probes/" +homepage = "https://foniod.org" +documentation = "https://docs.rs/redbpf-probes/2.1.0/redbpf_probes/" authors = ["Alessandro Decina ", "Peter Parkanyi ", "Junyeong Jeong "] version = "2.1.0" edition = '2018' diff --git a/redbpf/Cargo.toml b/redbpf/Cargo.toml index 06f399c7..81898e73 100644 --- a/redbpf/Cargo.toml +++ b/redbpf/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "redbpf" version = "2.1.0" -description = "eBPF build and runtime library" +description = "eBPF build and userspace runtime library" repository = "https://github.com/foniod/redbpf" -documentation = "https://foniod.org/api/redbpf/" +homepage = "https://foniod.org" +documentation = "https://docs.rs/redbpf/2.1.0/redbpf/" authors = ["Peter Parkanyi ", "Alessandro Decina ", "Junyeong Jeong "] edition = "2018" license = "MIT OR Apache-2.0" From 6628234efff5a64a018d56309f3e2ab11101378a Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 16 Nov 2021 21:27:12 +0900 Subject: [PATCH 017/107] Fix bump-script.sh to change version in documentation url Signed-off-by: Junyeong Jeong --- scripts/bump-version.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 7280af86..23f3919c 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -10,7 +10,9 @@ NEW_VERSION="${1}" echo "Bumping version: ${NEW_VERSION}" -find $PACKAGES -name Cargo.toml -type f -exec sed -i -e "s/^version.*/version = \"$NEW_VERSION\"/" {} \; +find $PACKAGES -name Cargo.toml -type f -exec sed -i -r \ + -e "s/^version.*/version = \"$NEW_VERSION\"/" \ + -e 's@^(documentation *= *"https://docs.rs/[a-z-]+/)[^/]+@\1'$NEW_VERSION'@' {} \; find $PACKAGES -name Cargo.toml -type f -exec sed -i -e "s/^\(bpf-sys.*version = \)\"[^\"]*\"/\\1\"$NEW_VERSION\"/" {} \; find $PACKAGES -name Cargo.toml -type f -exec sed -i -e "s/^\(cargo-bpf.*version = \)\"[^\"]*\"/\\1\"$NEW_VERSION\"/" {} \; find $PACKAGES -name Cargo.toml -type f -exec sed -i -e "s/^\(redbpf.*version = \)\"[^\"]*\"/\\1\"$NEW_VERSION\"/" {} \; From dac7b77adbc92ef124fce08f5b4b688dedf733a1 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Wed, 17 Nov 2021 01:29:47 +0900 Subject: [PATCH 018/107] Fix executing llvm-strip not to print stdout Signed-off-by: Junyeong Jeong --- cargo-bpf/src/llvm.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cargo-bpf/src/llvm.rs b/cargo-bpf/src/llvm.rs index 6ff5bfed..8d425b75 100644 --- a/cargo-bpf/src/llvm.rs +++ b/cargo-bpf/src/llvm.rs @@ -52,7 +52,7 @@ use llvm_sys::{LLVMAttributeFunctionIndex, LLVMInlineAsmDialect::*}; use std::ffi::{CStr, CString}; use std::os::raw::c_char; use std::path::Path; -use std::process::Command; +use std::process::{Command, Stdio}; use std::ptr; pub unsafe fn init() { @@ -178,7 +178,13 @@ pub(crate) fn strip_unnecessary(target: &impl AsRef, delete_btf: bool) -> "llvm-strip-11", ] .iter() - .find(|cmd| Command::new(cmd).arg("--version").status().is_ok()) + .find(|cmd| { + Command::new(cmd) + .arg("--version") + .stdout(Stdio::null()) + .status() + .is_ok() + }) .ok_or_else(|| anyhow!("llvm-strip command not found"))?; Command::new(cmd) From e7808dd839bdb6105c1eca9b9c425ffa539f5721 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Wed, 17 Nov 2021 01:30:57 +0900 Subject: [PATCH 019/107] Remove `rust-version` from Cargo.toml Since `rust-version` is nightly-only feature, the warning message pops up with stable cargo command. So it would rather remove this. Signed-off-by: Junyeong Jeong --- redbpf/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/redbpf/Cargo.toml b/redbpf/Cargo.toml index 06f399c7..57c0f3e2 100644 --- a/redbpf/Cargo.toml +++ b/redbpf/Cargo.toml @@ -9,7 +9,6 @@ edition = "2018" license = "MIT OR Apache-2.0" keywords = ["bpf", "ebpf", "build", "bindgen", "redbpf"] readme = "README.md" -rust-version = "1.48" [badges] maintenance = { status = "actively-developed" } From cd642e0f95481f38c45328aef592de2ee035568b Mon Sep 17 00:00:00 2001 From: rsdy Date: Tue, 16 Nov 2021 23:24:30 +0000 Subject: [PATCH 020/107] Bump version numbers Signed-off-by: rsdy --- bpf-sys/Cargo.toml | 4 ++-- cargo-bpf/Cargo.toml | 8 ++++---- redbpf-macros/Cargo.toml | 6 +++--- redbpf-probes/Cargo.toml | 10 +++++----- redbpf/Cargo.toml | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/bpf-sys/Cargo.toml b/bpf-sys/Cargo.toml index ecccc4f7..0f2d3e04 100644 --- a/bpf-sys/Cargo.toml +++ b/bpf-sys/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "bpf-sys" -version = "2.1.0" +version = "2.2.0" description = "Bindings for libbpf" repository = "https://github.com/foniod/redbpf" homepage = "https://foniod.org" -documentation = "https://docs.rs/bpf-sys/2.1.0/bpf_sys" +documentation = "https://docs.rs/bpf-sys" authors = ["Peter Parkanyi ", "Junyeong Jeong "] links = "bpf" edition = "2018" diff --git a/cargo-bpf/Cargo.toml b/cargo-bpf/Cargo.toml index e5ec7932..b65fc28b 100644 --- a/cargo-bpf/Cargo.toml +++ b/cargo-bpf/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "cargo-bpf" -version = "2.1.0" +version = "2.2.0" description = "Cargo plugin to manage eBPF probes using redbpf" repository = "https://github.com/foniod/redbpf" homepage = "https://foniod.org" -documentation = "https://docs.rs/cargo-bpf/2.1.0/cargo_bpf_lib/" +documentation = "https://docs.rs/cargo-bpf/2.2.0/cargo_bpf_lib/" authors = ["Alessandro Decina ", "Peter Parkanyi ", "Junyeong Jeong "] edition = "2018" keywords = ["cargo", "redbpf", "bpf", "plugin", "subcommand"] @@ -22,8 +22,8 @@ required-features = ["command-line"] clap = { version = "2.33", optional = true } bindgen = {version = "0.59.1", default-features = false, features = ["runtime"], optional = true} toml_edit = { version = "0.2", optional = true } -bpf-sys = { version = "2.1.0", path = "../bpf-sys", optional = true } -redbpf = { version = "2.1.0", path = "../redbpf", default-features = false, optional = true } +bpf-sys = { version = "2.2.0", path = "../bpf-sys", optional = true } +redbpf = { version = "2.2.0", path = "../redbpf", default-features = false, optional = true } futures = { version = "0.3", optional = true } tokio = { version = "^1.0.1", features = ["rt", "macros", "signal"], optional = true } hexdump = { version = "0.1", optional = true } diff --git a/redbpf-macros/Cargo.toml b/redbpf-macros/Cargo.toml index bbbbd41d..94721c14 100644 --- a/redbpf-macros/Cargo.toml +++ b/redbpf-macros/Cargo.toml @@ -3,9 +3,9 @@ name = "redbpf-macros" description = "Procedural macros for redbpf" repository = "https://github.com/foniod/redbpf" homepage = "https://foniod.org" -documentation = "https://docs.rs/redbpf-macros/2.1.0/redbpf_macros/" +documentation = "https://docs.rs/redbpf-macros/" authors = ["Alessandro Decina ", "Peter Parkanyi ", "Junyeong Jeong "] -version = "2.1.0" +version = "2.2.0" edition = '2018' keywords = ["bpf", "ebpf", "redbpf"] license = "MIT OR Apache-2.0" @@ -24,6 +24,6 @@ rustc_version = "0.3.0" [dev-dependencies] # redbpf-probes is needed by doctests -redbpf-probes = { version = "2.1.0", path = "../redbpf-probes" } +redbpf-probes = { version = "2.2.0", path = "../redbpf-probes" } # memoffset is needed by doctests memoffset = "0.6" diff --git a/redbpf-probes/Cargo.toml b/redbpf-probes/Cargo.toml index 1ae14ce1..d7c2649c 100644 --- a/redbpf-probes/Cargo.toml +++ b/redbpf-probes/Cargo.toml @@ -3,21 +3,21 @@ name = "redbpf-probes" description = "eBPF probe-related types for redbpf" repository = "https://github.com/foniod/redbpf" homepage = "https://foniod.org" -documentation = "https://docs.rs/redbpf-probes/2.1.0/redbpf_probes/" +documentation = "https://docs.rs/redbpf-probes/" authors = ["Alessandro Decina ", "Peter Parkanyi ", "Junyeong Jeong "] -version = "2.1.0" +version = "2.2.0" edition = '2018' keywords = ["bpf", "ebpf", "redbpf"] license = "MIT OR Apache-2.0" [dependencies] cty = "0.2" -redbpf-macros = { version = "2.1.0", path = "../redbpf-macros" } +redbpf-macros = { version = "2.2.0", path = "../redbpf-macros" } ufmt = { version = "0.1.0", default-features = false } [build-dependencies] -cargo-bpf = { version = "2.1.0", path = "../cargo-bpf", default-features = false, features = ["bindings"] } -bpf-sys = { version = "2.1.0", path = "../bpf-sys" } +cargo-bpf = { version = "2.2.0", path = "../cargo-bpf", default-features = false, features = ["bindings"] } +bpf-sys = { version = "2.2.0", path = "../bpf-sys" } syn = {version = "1.0", default-features = false, features = ["parsing", "visit"] } quote = "1.0" glob = "0.3.0" diff --git a/redbpf/Cargo.toml b/redbpf/Cargo.toml index 1c2b20cd..76549887 100644 --- a/redbpf/Cargo.toml +++ b/redbpf/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "redbpf" -version = "2.1.0" +version = "2.2.0" description = "eBPF build and userspace runtime library" repository = "https://github.com/foniod/redbpf" homepage = "https://foniod.org" -documentation = "https://docs.rs/redbpf/2.1.0/redbpf/" +documentation = "https://docs.rs/redbpf/" authors = ["Peter Parkanyi ", "Alessandro Decina ", "Junyeong Jeong "] edition = "2018" license = "MIT OR Apache-2.0" @@ -15,7 +15,7 @@ readme = "README.md" maintenance = { status = "actively-developed" } [dependencies] -bpf-sys = { path = "../bpf-sys", version = "2.1.0" } +bpf-sys = { path = "../bpf-sys", version = "2.2.0" } goblin = "0.4" zero = "0.1" libc = "0.2" From e94916751d37024a0822ac5a10bda102b2527c28 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Mon, 22 Nov 2021 21:07:16 +0900 Subject: [PATCH 021/107] Raise compile error when the alignment of value > 8 bytes This is the basis why the item whose alignment exceeds 8 bytes are banned. 1. In official Rust documents, it is stated that misaligned pointers or references lead to undefined behavior. 2. In BPF programs, the pointer returned by bpf_map_lookup_elem is misaligned because the value the pointer points to is merely 8 bytes aligned data. 3. Hence, creating reference of the value, or dereferencing the pointer incurs undefined behavior. Signed-off-by: Junyeong Jeong --- cargo-bpf/src/llvm.rs | 33 +++++++++++++++++++++++++++++++++ redbpf-macros/src/lib.rs | 10 +++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/cargo-bpf/src/llvm.rs b/cargo-bpf/src/llvm.rs index 8d425b75..583ed5ca 100644 --- a/cargo-bpf/src/llvm.rs +++ b/cargo-bpf/src/llvm.rs @@ -54,6 +54,7 @@ use std::os::raw::c_char; use std::path::Path; use std::process::{Command, Stdio}; use std::ptr; +use std::slice; pub unsafe fn init() { LLVM_InitializeAllTargets(); @@ -127,9 +128,41 @@ unsafe fn inject_exit_call(context: LLVMContextRef, func: LLVMValueRef, builder: LLVMBuildCall(builder, exit, ptr::null_mut(), 0, c_str.as_ptr()); } +unsafe fn check_map_value_alignment(context: LLVMContextRef, module: LLVMModuleRef) -> Result<()> { + const PROBES_ALIGNMENT_MAX: usize = 8; + let mut global = LLVMGetFirstGlobal(module); + while !global.is_null() { + let mut size: libc::size_t = 0; + let c_name = LLVMGetValueName2(global, &mut size as *mut _); + if !c_name.is_null() { + let name = String::from_utf8_lossy(slice::from_raw_parts(c_name as *const u8, size)); + let align_prefix = "MAP_VALUE_ALIGN_"; + if name.starts_with(align_prefix) { + let align = LLVMGetAlignment(global) as usize; + if align > PROBES_ALIGNMENT_MAX { + let map_name = &name[align_prefix.len()..]; + let msg = format!( + "error: Illegal alignment the value of the map! map={} value-alignment={}. + In kernel context, the Linux kernel does not guarantee alignment of the value stored in the + BPF map when the alignment of the value is greater than 8 bytes. + Since it is undefined behavior to create references or dereference pointers of unaligned + data in Rust, BPF programs should avoid using types whose alignment is greater than 8 bytes.", + map_name, align + ); + return Err(anyhow!(msg)); + } + } + } + global = LLVMGetNextGlobal(global); + } + + Ok(()) +} + pub unsafe fn compile(input: &Path, output: &Path, bc_output: Option<&Path>) -> Result<()> { let context = LLVMGetGlobalContext(); let module = load_module(context, input)?; + check_map_value_alignment(context, module)?; process_ir(context, module)?; let ret = compile_module(module, output, bc_output); LLVMDisposeModule(module); diff --git a/redbpf-macros/src/lib.rs b/redbpf-macros/src/lib.rs index 1b3ef5c5..ee54c44e 100644 --- a/redbpf-macros/src/lib.rs +++ b/redbpf-macros/src/lib.rs @@ -258,13 +258,21 @@ pub fn map(attrs: TokenStream, item: TokenStream) -> TokenStream { let mod_ident = syn::Ident::new(&mod_name, static_item.ident.span()); let ktype = key_type.unwrap(); let vtype = value_type.unwrap(); + // CAUTION: When you change the names (MAP_BTF_XXXX and + // MAP_VALUE_ALIGN_XXXX) you should consider changing corresponding + // parts that use them. let map_btf_name = format!("MAP_BTF_{}", static_item.ident.to_string()); let map_btf_ident = syn::Ident::new(&map_btf_name, static_item.ident.span()); + let value_align_name = format!("MAP_VALUE_ALIGN_{}", static_item.ident.to_string()); + let value_align_ident = syn::Ident::new(&value_align_name, static_item.ident.span()); tokens.extend(quote! { mod #mod_ident { #[allow(unused_imports)] use super::*; - use core::mem; + use core::mem::{self, MaybeUninit}; + + #[no_mangle] + static #value_align_ident: MaybeUninit<#vtype> = MaybeUninit::uninit(); #[repr(C)] struct MapBtf { From 613884f5a2f6aad03b7a887c144346a1ff906bef Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 25 Nov 2021 20:54:49 +0900 Subject: [PATCH 022/107] Fix compile warning of unused variable Signed-off-by: Junyeong Jeong --- cargo-bpf/src/llvm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cargo-bpf/src/llvm.rs b/cargo-bpf/src/llvm.rs index 583ed5ca..12f1c290 100644 --- a/cargo-bpf/src/llvm.rs +++ b/cargo-bpf/src/llvm.rs @@ -128,7 +128,7 @@ unsafe fn inject_exit_call(context: LLVMContextRef, func: LLVMValueRef, builder: LLVMBuildCall(builder, exit, ptr::null_mut(), 0, c_str.as_ptr()); } -unsafe fn check_map_value_alignment(context: LLVMContextRef, module: LLVMModuleRef) -> Result<()> { +unsafe fn check_map_value_alignment(_context: LLVMContextRef, module: LLVMModuleRef) -> Result<()> { const PROBES_ALIGNMENT_MAX: usize = 8; let mut global = LLVMGetFirstGlobal(module); while !global.is_null() { From 3b0388a409acaeccce52ef4a33dd91882cf39e88 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 25 Nov 2021 20:55:03 +0900 Subject: [PATCH 023/107] Fix probes template Signed-off-by: Junyeong Jeong --- cargo-bpf/src/new_program.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cargo-bpf/src/new_program.rs b/cargo-bpf/src/new_program.rs index f6199b03..9e45bd39 100644 --- a/cargo-bpf/src/new_program.rs +++ b/cargo-bpf/src/new_program.rs @@ -8,6 +8,7 @@ use std::fs::{self, File, OpenOptions}; use std::io::{Seek, SeekFrom, Write}; use std::path::Path; + use crate::CommandError; impl From for CommandError { @@ -96,8 +97,7 @@ use cty::*; let mut main_rs = File::create(main_rs)?; write!( &mut main_rs, - r#" -#![no_std] + r#"#![no_std] #![no_main] use cty::*; @@ -105,6 +105,10 @@ use cty::*; // use redbpf_probes::kprobe::prelude::*; // use redbpf_probes::xdp::prelude::*; // use redbpf_probes::socket_filter::prelude::*; +// use redbpf_probes::tc::prelude::*; +// use redbpf_probes::uprobe::prelude::*; +// use redbpf_probes::sockmap::prelude::*; +// use redbpf_probes::bpf_iter::prelude::*; // Use the types you're going to share with userspace, eg: // use {lib}::{name}::SomeEvent; @@ -113,7 +117,7 @@ program!(0xFFFFFFFE, "GPL"); // The maps and probe functions go here, eg: // -// #[map("syscall_events")] +// #[map] // static mut syscall_events: PerfMap = PerfMap::with_max_entries(1024); // // #[kprobe("__x64_sys_open")] @@ -128,8 +132,8 @@ program!(0xFFFFFFFE, "GPL"); // unsafe {{ syscall_events.insert(regs.ctx, &event) }}; // }} "#, - lib = crate_name, - name = name + lib = name_to_ident(crate_name.as_str()), + name = name_to_ident(name), )?; Ok(()) From 86a1f94b70d7b293f96f5d2742de7b096ad7a7f0 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 26 Nov 2021 22:04:02 +0900 Subject: [PATCH 024/107] Add safe wrapper of bpf helpers that always succeed Signed-off-by: Junyeong Jeong --- .../example-probes/src/vfsreadlat/main.rs | 16 ++--- redbpf-probes/src/helpers.rs | 66 +++++++++++++++++++ redbpf-probes/src/socket.rs | 2 + 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/examples/example-probes/src/vfsreadlat/main.rs b/examples/example-probes/src/vfsreadlat/main.rs index c930416d..8593c55e 100644 --- a/examples/example-probes/src/vfsreadlat/main.rs +++ b/examples/example-probes/src/vfsreadlat/main.rs @@ -14,27 +14,27 @@ static mut PID: PerfMap = PerfMap::with_max_entries(10240); #[kprobe("vfs_read")] fn vfs_read_enter(_regs: Registers) { let pid_tgid = bpf_get_current_pid_tgid(); - let p = pid_tgid >> 32; - let t = pid_tgid & 0xFFFFFFFF; + let pid = pid_tgid & 0xFFFFFFFF; // task->pid + let tgid = pid_tgid >> 32; // task->tgid let event = VFSEvent { - pid: p, - tgid: t, + pid, + tgid, timestamp: bpf_ktime_get_ns(), latency: 0, }; unsafe { - TIMESTAMP.set(&t, &event); + TIMESTAMP.set(&tgid, &event); }; } #[kretprobe("vfs_read")] fn vfs_read_exit(regs: Registers) { - let t = bpf_get_current_pid_tgid() & 0xFFFFFFFF; + let tgid = bpf_get_current_pid_tgid() >> 32; unsafe { - match TIMESTAMP.get_mut(&t) { + match TIMESTAMP.get_mut(&tgid) { Some(event) => { - TIMESTAMP.delete(&t); + TIMESTAMP.delete(&tgid); event.latency = bpf_ktime_get_ns() - event.timestamp; PID.insert(regs.ctx, &event); } diff --git a/redbpf-probes/src/helpers.rs b/redbpf-probes/src/helpers.rs index 42a6cfd6..7a232350 100644 --- a/redbpf-probes/src/helpers.rs +++ b/redbpf-probes/src/helpers.rs @@ -88,11 +88,27 @@ pub fn bpf_get_current_comm() -> [c_char; 16] { } /// Returns the time elapsed since system boot, in nanoseconds. +/// +/// The time during the system was suspended is **NOT** included. #[inline] pub fn bpf_ktime_get_ns() -> u64 { unsafe { gen::bpf_ktime_get_ns() } } +/// Return the time elapsed since system boot, in nanoseconds +/// +/// The time during the system was suspended is included. +pub fn bpf_ktime_get_boot_ns() -> u64 { + unsafe { gen::bpf_ktime_get_boot_ns() } +} + +/// Return a coarse-grained version of the time elapsed since system boot, in nanoseconds +/// +/// The time during the system was suspended is **NOT** included. +pub fn bpf_ktime_get_coarse_ns() -> u64 { + unsafe { gen::bpf_ktime_get_coarse_ns() } +} + // For tracing programs, safely attempt to read `mem::size_of::()` bytes from // address src. #[inline] @@ -110,6 +126,20 @@ pub unsafe fn bpf_probe_read(src: *const T) -> Result { Ok(v.assume_init()) } +/// Print a message to `/sys/kernel/debug/tracing/trace_pipe` +/// +/// `message` should end with NUL byte. Otherwise, it is rejected by the Linux +/// kernel so it won't be shown at the `trace_pipe` +/// +/// # Example +/// ```no_run +/// # use redbpf_probes::kprobe::prelude::*; +/// +/// # #[kprobe] +/// # fn print_example(_regs: Registers) { +/// bpf_trace_printk(b"Hello world\0"); +/// # } +/// ``` #[inline] pub fn bpf_trace_printk(message: &[u8]) -> ::cty::c_int { unsafe { @@ -122,6 +152,42 @@ pub fn bpf_trace_printk(message: &[u8]) -> ::cty::c_int { } } +/// Get a pseudo-random number +#[inline] +pub fn bpf_get_prandom_u32() -> u32 { + unsafe { gen::bpf_get_prandom_u32() } +} + +/// Get the SMP (symmetric multiprocessing) processor id +#[inline] +pub fn bpf_get_smp_processor_id() -> u32 { + unsafe { gen::bpf_get_smp_processor_id() } +} + +/// A pointer to the current task struct +#[inline] +pub fn bpf_get_current_task() -> u64 { + unsafe { gen::bpf_get_current_task() } +} + +/// The id of current NUMA node +#[inline] +pub fn bpf_get_numa_node_id() -> i64 { + unsafe { gen::bpf_get_numa_node_id() } +} + +/// Current cgroup id within which the current task is running +#[inline] +pub fn bpf_get_current_cgroup_id() -> u64 { + unsafe { gen::bpf_get_current_cgroup_id() } +} + +/// Obtain the 64bit jiffies +#[inline] +pub fn bpf_jiffies64() -> u64 { + unsafe { gen::bpf_jiffies64() } +} + #[inline] pub fn bpf_perf_event_output( ctx: *mut c_void, diff --git a/redbpf-probes/src/socket.rs b/redbpf-probes/src/socket.rs index 18ee3a84..4ae9a78c 100644 --- a/redbpf-probes/src/socket.rs +++ b/redbpf-probes/src/socket.rs @@ -47,6 +47,8 @@ impl SkBuff { #[inline] /// Loads data from the socket buffer. /// + /// Provide an easy way to load data from a packet. + /// /// # Example /// ```no_run /// use core::mem; From 86adbc7120e68fc55f998500819da677659488e5 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 3 Dec 2021 01:42:16 +0900 Subject: [PATCH 025/107] Bump up bindgen to allow using "try" as a variable name Signed-off-by: Junyeong Jeong --- bpf-sys/Cargo.toml | 2 +- cargo-bpf/Cargo.toml | 2 +- redbpf-probes/Cargo.toml | 2 +- redbpf/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bpf-sys/Cargo.toml b/bpf-sys/Cargo.toml index 0f2d3e04..cd4852a4 100644 --- a/bpf-sys/Cargo.toml +++ b/bpf-sys/Cargo.toml @@ -19,7 +19,7 @@ glob = "0.3.0" [build-dependencies] cc = "1.0" -bindgen = {version = "0.59.1", default-features = false, features = ["runtime"]} +bindgen = {version = "0.59.2", default-features = false, features = ["runtime"]} libc = "0.2" glob = "0.3.0" diff --git a/cargo-bpf/Cargo.toml b/cargo-bpf/Cargo.toml index b65fc28b..6ec4e5ab 100644 --- a/cargo-bpf/Cargo.toml +++ b/cargo-bpf/Cargo.toml @@ -20,7 +20,7 @@ required-features = ["command-line"] [dependencies] clap = { version = "2.33", optional = true } -bindgen = {version = "0.59.1", default-features = false, features = ["runtime"], optional = true} +bindgen = {version = "0.59.2", default-features = false, features = ["runtime"], optional = true} toml_edit = { version = "0.2", optional = true } bpf-sys = { version = "2.2.0", path = "../bpf-sys", optional = true } redbpf = { version = "2.2.0", path = "../redbpf", default-features = false, optional = true } diff --git a/redbpf-probes/Cargo.toml b/redbpf-probes/Cargo.toml index d7c2649c..f9648b7e 100644 --- a/redbpf-probes/Cargo.toml +++ b/redbpf-probes/Cargo.toml @@ -21,7 +21,7 @@ bpf-sys = { version = "2.2.0", path = "../bpf-sys" } syn = {version = "1.0", default-features = false, features = ["parsing", "visit"] } quote = "1.0" glob = "0.3.0" -bindgen = { version = "0.59.1", default-features = false, features = ["runtime"] } +bindgen = { version = "0.59.2", default-features = false, features = ["runtime"] } anyhow = "1.0" tracing = "0.1.26" tracing-subscriber = "0.2.18" diff --git a/redbpf/Cargo.toml b/redbpf/Cargo.toml index 76549887..bcfbffca 100644 --- a/redbpf/Cargo.toml +++ b/redbpf/Cargo.toml @@ -19,7 +19,7 @@ bpf-sys = { path = "../bpf-sys", version = "2.2.0" } goblin = "0.4" zero = "0.1" libc = "0.2" -bindgen = {version = "0.59.1", default-features = false, features = ["runtime"]} +bindgen = {version = "0.59.2", default-features = false, features = ["runtime"]} regex = "1.0" lazy_static = "1.0" byteorder = "1" From e44890eeba5d397e1585a5ed9955e4221e82b051 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 3 Dec 2021 01:44:28 +0900 Subject: [PATCH 026/107] Update README Signed-off-by: Junyeong Jeong --- README.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fd150cca..53125025 100644 --- a/README.md +++ b/README.md @@ -11,19 +11,51 @@ A Rust eBPF toolchain. The redbpf project is a collection of tools and libraries to build eBPF programs using Rust. It includes: -- [redbpf](https://foniod.org/api/redbpf/) - a user space library that can be +- [redbpf](https://docs.rs/redbpf/latest/redbpf/) - a user space library that can be used to load eBPF programs or access eBPF maps. -- [redbpf-probes](https://foniod.org/api/redbpf_probes/) - an idiomatic Rust +- [redbpf-probes](https://docs.rs/redbpf-probes/latest/redbpf_probes/) - an idiomatic Rust API to write eBPF programs that can be loaded by the linux kernel -- [redbpf-macros](https://foniod.org/api/redbpf_macros/) - companion crate to +- [redbpf-macros](https://docs.rs/redbpf-macros/latest/redbpf_macros/) - companion crate to `redbpf-probes` which provides convenient procedural macros useful when writing eBPF programs. For example, `#[map]` for defining a map, `#[kprobe]` for defining a BPF program that can be attached to kernel functions. -- [cargo-bpf](https://foniod.org/api/cargo_bpf/) - a cargo subcommand for - creating, building and debugging eBPF programs +- [cargo-bpf](./cargo-bpf/src/main.rs) - a cargo subcommand for creating, + building and debugging eBPF programs + +# Features + +- Allows users to write both BPF programs and userspace programs in Rust +- Offers many BPF map types + 1. `HashMap`, `PerCpuHashMap`, `LruHashMap`, `LruPerCpuHashMap`, `Array`, + `PerCpuArray`, `PerfMap`, `TcHashMap`, `StackTrace`, `ProgramArray`, + `SockMap` +- Offers several BPF program types + 1. `KProbe`, `KRetProbe`, `UProbe`, `URetProbe`, `SocketFilter`, `XDP`, + `StreamParser`, `StreamVerdict`, `TaskIter`, `SkLookup` +- Provides attribute macros that define various kind of BPF programs and BPF + maps in a declarative way. + 1. `#[kprobe]`, `#[kretprobe]`, `#[uprobe]`, `#[uretprobe]`, `#[xdp]`, + `#[tc_action]`, `#[socket_filter]`, `#[stream_parser]`, + `#[stream_verdict]`, `#[task_iter]` + 2. `#[map]` +- Can generate Rust bindings from the Linux kernel headers or from the BTF of + `vmlinux` +- Provides API for both BPF programs and userspace programs to help users write + Rust idiomatic code +- Supports BTF for maps +- Supports pinning maps and loading maps from pins +- Supports BPF iterator for `task` +- Enables users to write BPF programs for `tc` action and RedBPF compiles the + programs into the ELF object file that is compatible with `tc` command +- Provides wrappers of BPF helper functions +- Offers asynchronous stream of `perf events` for userspace programs +- Supports multiple versions of LLVM +- Shows BPF verifier logs when loading BPF programs, BPF maps or BTF fails +- Has several example programs that are separated into two parts: BPF programs + and userspace programs # Requirements @@ -36,6 +68,11 @@ you can specify other LLVM versions as follows: - `cargo build --no-default-features --features llvm13` - `cargo build --no-default-features --features llvm11` +If you want to install `cargo-bpf` with other LLVM versions then you can try +this command: +- `cargo install cargo-bpf --no-default-features --features=llvm13,command-line` +- `cargo install cargo-bpf --no-default-features --features=llvm1,command-line` + ## Valid combinations of rust and LLVM versions `rustc` uses its own version of LLVM. But RedBPF also requires LLVM installed @@ -97,8 +134,11 @@ On Debian, Ubuntu and derivatives you can install the dependencies running: llvm-12-dev libclang-12-dev linux-headers-$(uname -r) \ libelf-dev -If your distribution doesn't have LLVM 12, you can add the [official LLVM -APT repository](https://apt.llvm.org) to your `sources.list`. +If your distribution doesn't have LLVM 12, you can add the [official LLVM APT +repository](https://apt.llvm.org) to your `sources.list`. Or simply run the +script that you can download at the +[llvm.sh](https://apt.llvm.org/llvm.sh). Note that this script is only for +Debian or Ubuntu. ## Installing dependencies on RPM based distributions @@ -128,8 +168,8 @@ programs are splitted into two parts: `example-probes` and kernel context. `example-userspace` includes userspace programs that load BPF programs into kernel space and communicate with BPF programs through BPF maps. -Also see [documentation](https://foniod.org/api/cargo_bpf/) of `cargo-bpf`. It -provides a CLI tool for compiling BPF programs easily. +Also see [documentation](./cargo-bpf/src/main.rs) of `cargo-bpf`. It provides a +CLI tool for compiling BPF programs easily. [redbpf-tools](https://github.com/foniod/redbpf/tree/master/redbpf-tools) is a `cargo-bpf` generated crate that includes simple examples you can use to From f9c89dd708f175ed4647ec13282949b24490a1dc Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 30 Nov 2021 01:59:34 +0900 Subject: [PATCH 027/107] Refactoring BTF handling code Do not modify raw BTF types in place. Instead dump all BTF types including modification to the data container afterwards. Signed-off-by: Junyeong Jeong --- redbpf/src/btf.rs | 251 +++++++++++++++++++++++++++++----------------- 1 file changed, 158 insertions(+), 93 deletions(-) diff --git a/redbpf/src/btf.rs b/redbpf/src/btf.rs index d9fb346d..2dbb4c9c 100644 --- a/redbpf/src/btf.rs +++ b/redbpf/src/btf.rs @@ -32,8 +32,9 @@ use crate::error::{Error, Result}; const BTF_SECTION_NAME: &str = ".BTF"; pub(crate) struct BTF { - types: Vec<(u32, BtfType, *mut u8)>, - raw_bytes: Vec, + types: Vec<(u32, BtfType)>, + btf_hdr: btf_header, + raw_str_enc: Vec, fd: Option, } @@ -119,14 +120,15 @@ impl BTF { return Err(Error::BTF("BTF already loaded".to_string())); } + let raw_bytes = self.dump()?; let mut v = vec![0i8; 64 * 1024]; let log_buf = v.as_mut_ptr(); let log_buf_size = v.capacity() * mem::size_of_val(&v[0]); let fd; unsafe { fd = bpf_sys::bpf_load_btf( - self.raw_bytes.as_ptr() as *const _, - self.raw_bytes.len() as u32, + raw_bytes.as_ptr() as *const _, + raw_bytes.len() as u32, log_buf, log_buf_size as u32, false, @@ -141,6 +143,62 @@ impl BTF { Ok(()) } + fn dump(&self) -> Result> { + let mut raw_bytes: Vec = vec![]; + raw_bytes.extend(unsafe { + slice::from_raw_parts( + &self.btf_hdr as *const _ as *const u8, + mem::size_of::(), + ) + }); + + for _ in 0..self.btf_hdr.type_off { + raw_bytes.push(0); + } + + for (_, type_) in self.types.iter() { + let offset = raw_bytes.len(); + let sz = type_.byte_len(); + raw_bytes.reserve(sz); + unsafe { + let out = raw_bytes.as_mut_ptr().add(offset); + type_.dump(out)?; + raw_bytes.set_len(offset + sz); + } + } + if raw_bytes.len() + != mem::size_of::() + + (self.btf_hdr.type_off + self.btf_hdr.type_len) as usize + { + error!("The length of dumped BTF types differs from the size recorded in btf_header"); + return Err(Error::BTF( + "failed to dump BTF types. length mismatch".to_string(), + )); + } + + if mem::size_of::() + (self.btf_hdr.str_off as usize) < raw_bytes.len() { + error!("BTF type data exceeds BTF string section offset"); + return Err(Error::BTF("invalid BTF string section offset".to_string())); + } + + for _ in 0..(mem::size_of::() + self.btf_hdr.str_off as usize - raw_bytes.len()) + { + raw_bytes.push(0); + } + raw_bytes.extend(&self.raw_str_enc); + + if mem::size_of::() + (self.btf_hdr.str_off + self.btf_hdr.str_len) as usize + != raw_bytes.len() + { + error!("The length of dumped BTF string section differs from the size recorded in btf_header"); + return Err(Error::BTF( + "failed to dump BTF string section. length mismatch".to_string(), + )); + } + + Ok(raw_bytes) + } + fn parse_raw(bytes: &[u8]) -> Result { if mem::size_of::() > bytes.len() { return Err(Error::BTF("BTF section data size is too small".to_string())); @@ -161,11 +219,21 @@ impl BTF { return Err(Error::BTF("invalid binary data length".to_string())); } - let mut clone_bytes = bytes.to_vec(); - let btf_types = Self::parse_types(&btf_hdr, &mut clone_bytes)?; + let raw_type_enc = { + let start = (btf_hdr.hdr_len + btf_hdr.type_off) as usize; + let end = start + btf_hdr.type_len as usize; + &bytes[start..end] + }; + let mut raw_str_enc = { + let start = (btf_hdr.hdr_len + btf_hdr.str_off) as usize; + let end = start + btf_hdr.str_len as usize; + (&bytes[start..end]).to_vec() + }; + let types = Self::parse_types(&raw_type_enc, &mut raw_str_enc)?; Ok(BTF { - types: btf_types, - raw_bytes: clone_bytes, + types, + btf_hdr, + raw_str_enc, fd: None, }) } @@ -181,7 +249,7 @@ impl BTF { let btf_bytes = &bytes[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize]; let mut btf = Self::parse_raw(&btf_bytes)?; btf.fix_datasection(object)?; - for (type_id, type_, _) in btf.types.iter() { + for (type_id, type_) in btf.types.iter() { debug!("[{}] {:?}", type_id, type_); } Ok(btf) @@ -194,14 +262,13 @@ impl BTF { fn fix_datasection(&mut self, object: &Elf) -> Result<()> { // fix size of datasection let mut var_type_ids = vec![]; - for (_, type_, data) in self.types.iter_mut() { + for (_, type_) in self.types.iter_mut() { if let BtfType::DataSection(comm, vsis) = type_ { let shdr = get_section_header_by_name(object, &comm.name_raw).ok_or_else(|| { Error::Section(format!("DataSection not found: {}", &comm.name_raw)) })?; comm.set_size(shdr.sh_size as u32); var_type_ids.extend(vsis.iter().map(|vsi| vsi.type_)); - type_.dump(*data)?; } } // fix offset of var section info @@ -210,7 +277,7 @@ impl BTF { let var = self .types .iter() - .find_map(|(type_id, type_, _)| { + .find_map(|(type_id, type_)| { if let BtfType::Variable(..) = type_ { if &vti == type_id { return Some(type_); @@ -247,46 +314,31 @@ impl BTF { var_offsets.insert(vti, sym.st_value); } } - for (_, type_, data) in self.types.iter_mut() { + for (_, type_) in self.types.iter_mut() { if let BtfType::DataSection(_, vsis) = type_ { - let mut modified = false; for vsi in vsis.iter_mut() { // offsets of variables whose linkage is static are intact if let Some(offset) = var_offsets.get(&vsi.type_) { vsi.offset = *offset as u32; - modified = true; } } - if modified { - type_.dump(*data)?; - } } } Ok(()) } /// Helper function for parsing BPF type encoding binary data. - fn parse_types( - btf_hdr: &btf_header, - btf_bytes: &mut [u8], - ) -> Result> { + fn parse_types(btf_type_enc: &[u8], btf_str_enc: &mut [u8]) -> Result> { let mut btf_types = vec![]; - let type_start = unsafe { - btf_bytes - .as_ptr() - .offset((btf_hdr.hdr_len + btf_hdr.type_off) as isize) - }; - let type_end = unsafe { type_start.offset(btf_hdr.type_len as isize) }; - let mut type_ptr = type_start; // type id 0 is reserved for void type. so type id is starting from 1. let mut type_id: u32 = 1; - let str_bytes = &mut btf_bytes[(btf_hdr.hdr_len + btf_hdr.str_off) as usize..]; - while type_ptr < type_end { - let type_ = BtfType::parse(type_ptr as *mut _, str_bytes)?; + let mut remain = &btf_type_enc[..]; + while remain.len() > 0 { + let type_ = BtfType::parse(remain, btf_str_enc)?; let sz = type_.byte_len(); - btf_types.push((type_id, type_, type_ptr as *mut _)); - type_ptr = unsafe { type_ptr.offset(sz as isize) }; + btf_types.push((type_id, type_)); type_id += 1; + remain = &remain[sz..]; } Ok(btf_types) } @@ -294,7 +346,7 @@ impl BTF { fn get_type_by_id(&self, type_id: u32) -> Option<&BtfType> { self.types .iter() - .find_map(|(tid, type_, _)| if &type_id == tid { Some(type_) } else { None }) + .find_map(|(tid, type_)| if &type_id == tid { Some(type_) } else { None }) } /// Get BTF type ids of a map of which symbol name is `map_sym_name` @@ -310,7 +362,7 @@ impl BTF { let map_btf_type = self .types .iter() - .find_map(|(_, type_, _)| { + .find_map(|(_, type_)| { if let Variable(common, _) = type_ { if common.name_raw == map_btf_sym_name { return Some(type_); @@ -381,33 +433,31 @@ impl BTF { pub(crate) fn find_type_id(&self, type_name: &str, kind: BtfKind) -> Option { use BtfType::*; - self.types - .iter() - .find_map(|(type_id, type_, _)| match type_ { - Integer(common, _) - | Pointer(common) - | Array(common, _) - | Structure(common, _) - | Union(common, _) - | Enumeration(common, _) - | Forward(common) - | TypeDef(common) - | Volatile(common) - | Constant(common) - | Restrict(common) - | Function(common) - | FunctionProtocol(common, _) - | Variable(common, _) - | DataSection(common, _) - | FloatingPoint(common) => { - if common.kind() == kind { - if common.name_raw == type_name { - return Some(*type_id); - } + self.types.iter().find_map(|(type_id, type_)| match type_ { + Integer(common, _) + | Pointer(common) + | Array(common, _) + | Structure(common, _) + | Union(common, _) + | Enumeration(common, _) + | Forward(common) + | TypeDef(common) + | Volatile(common) + | Constant(common) + | Restrict(common) + | Function(common) + | FunctionProtocol(common, _) + | Variable(common, _) + | DataSection(common, _) + | FloatingPoint(common) => { + if common.kind() == kind { + if common.name_raw == type_name { + return Some(*type_id); } - None } - }) + None + } + }) } } @@ -450,8 +500,8 @@ impl From for BtfKind { } impl BtfTypeCommon { - fn parse(bytes: *const u8, str_bytes: &[u8]) -> Result { - let type_ = unsafe { ptr::read_unaligned(bytes as *const btf_type) }; + fn parse(bytes: &[u8], str_bytes: &[u8]) -> Result { + let type_ = unsafe { ptr::read_unaligned(bytes.as_ptr() as *const btf_type) }; let name = get_type_name(str_bytes, type_.name_off)?; Ok(Self { type_, @@ -486,7 +536,7 @@ impl BtfTypeCommon { } impl BtfType { - fn parse(bytes: *mut u8, str_bytes: &mut [u8]) -> Result { + fn parse(bytes: &[u8], str_bytes: &mut [u8]) -> Result { let comm = BtfTypeCommon::parse(bytes, str_bytes)?; let vlen = comm.vlen(); use BtfType::*; @@ -595,7 +645,6 @@ impl BtfType { ); comm.name_fixed = None; comm.type_.name_off = 0; - type_.dump(bytes)?; } } Structure(comm, _) @@ -706,40 +755,56 @@ impl BtfType { fn dump(&self, out: *mut u8) -> Result<()> { use BtfType::*; match self { - DataSection(comm, vsis) => unsafe { - let mut dst = out; - ptr::write_unaligned(dst as *mut btf_type, comm.type_); - dst = dst.offset(mem::size_of::() as isize); - for vsi in vsis.iter() { - ptr::write_unaligned(dst as *mut btf_var_secinfo, *vsi); - dst = dst.offset(mem::size_of::() as isize); - } - Ok(()) - }, - Pointer(comm) | Volatile(comm) | Constant(comm) | Restrict(comm) => unsafe { + Pointer(comm) | Volatile(comm) | Constant(comm) | Restrict(comm) | Forward(comm) + | TypeDef(comm) | Function(comm) | FloatingPoint(comm) => unsafe { ptr::write_unaligned(out as *mut btf_type, comm.type_); - Ok(()) }, Array(comm, arr) => unsafe { ptr::write_unaligned(out as *mut btf_type, comm.type_); - ptr::write_unaligned( - out.offset(mem::size_of::() as isize) as *mut btf_array, - *arr, - ); - Ok(()) + ptr::write_unaligned(out.add(mem::size_of::()) as *mut btf_array, *arr); + }, + Integer(comm, int) => unsafe { + ptr::write_unaligned(out as *mut btf_type, comm.type_); + ptr::write_unaligned(out.add(mem::size_of::()) as *mut u32, *int); + }, + Variable(comm, var) => unsafe { + ptr::write_unaligned(out as *mut btf_type, comm.type_); + ptr::write_unaligned(out.add(mem::size_of::()) as *mut btf_var, *var); }, FunctionProtocol(comm, params) => unsafe { - let mut dst = out; - ptr::write_unaligned(dst as *mut btf_type, comm.type_); - dst = dst.offset(mem::size_of::() as isize); + ptr::write_unaligned(out as *mut btf_type, comm.type_); + let mut dst = out.add(mem::size_of::()); for param in params.iter() { ptr::write_unaligned(dst as *mut btf_param, *param); - dst = dst.offset(mem::size_of::() as isize); + dst = dst.add(mem::size_of::()); + } + }, + Structure(comm, membs) | Union(comm, membs) => unsafe { + ptr::write_unaligned(out as *mut btf_type, comm.type_); + let mut dst = out.add(mem::size_of::()); + for memb in membs.iter() { + ptr::write_unaligned(dst as *mut btf_member, memb.member); + dst = dst.add(mem::size_of::()); + } + }, + Enumeration(comm, enus) => unsafe { + ptr::write_unaligned(out as *mut btf_type, comm.type_); + let mut dst = out.add(mem::size_of::()); + for enu in enus.iter() { + ptr::write_unaligned(dst as *mut btf_enum, *enu); + dst = dst.add(mem::size_of::()); + } + }, + DataSection(comm, vsis) => unsafe { + ptr::write_unaligned(out as *mut btf_type, comm.type_); + let mut dst = out.add(mem::size_of::()); + for vsi in vsis.iter() { + ptr::write_unaligned(dst as *mut btf_var_secinfo, *vsi); + dst = dst.add(mem::size_of::()); } - Ok(()) }, - _ => Err(Error::BTF(format!("dump is not supported for {:?}", self))), } + Ok(()) } /// overwrite fixed name binary into string section @@ -751,16 +816,16 @@ impl BtfType { } /// `data` is a pointer to binary data of `btf_type` - fn read_multiple_extra(data: *const u8, vlen: u32) -> Vec { - let head_ptr = unsafe { data.offset(mem::size_of::() as isize) as *const T }; + fn read_multiple_extra(data: &[u8], vlen: u32) -> Vec { + let head_ptr = unsafe { data.as_ptr().add(mem::size_of::()) as *const T }; (0..vlen) - .map(|i| unsafe { ptr::read_unaligned(head_ptr.offset(i as isize)) }) + .map(|i| unsafe { ptr::read_unaligned(head_ptr.add(i as usize)) }) .collect() } /// `data` is a pointer to binary data of `btf_type` - fn read_extra(data: *const u8) -> T { - unsafe { ptr::read_unaligned(data.offset(mem::size_of::() as isize) as *const T) } + fn read_extra(data: &[u8]) -> T { + unsafe { ptr::read_unaligned(data.as_ptr().add(mem::size_of::()) as *const T) } } } From 16aae97a0ea5cdef10e7eae60cc84458dc6b49cc Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 30 Nov 2021 02:21:12 +0900 Subject: [PATCH 028/107] Store tc compatible key/value BTF annotation tc requires struct ___btf_map_ to get to know the BTF types of key and value. Hence, store that data if map type is TcHashMap. Signed-off-by: Junyeong Jeong --- redbpf-macros/src/lib.rs | 95 +++++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/redbpf-macros/src/lib.rs b/redbpf-macros/src/lib.rs index ee54c44e..32cf1a9b 100644 --- a/redbpf-macros/src/lib.rs +++ b/redbpf-macros/src/lib.rs @@ -228,27 +228,45 @@ pub fn map(attrs: TokenStream, item: TokenStream) -> TokenStream { #item } }; + + let mut tc_compatible = false; let mut key_type: Option = None; let mut value_type: Option = None; if let Type::Path(path) = *static_item.ty { if let Some(seg) = path.path.segments.last() { + let map_type_name = seg.ident.to_string(); if let PathArguments::AngleBracketed(bracket) = &seg.arguments { // or - let map_type_name = seg.ident.to_string(); - match map_type_name.as_ref() { + match map_type_name.as_str() { "Array" | "PerCpuArray" => { if bracket.args.len() == 1 { key_type = Some(parse_quote!(u32)); value_type = Some(bracket.args.first().unwrap().clone()); } } - "HashMap" => { + "HashMap" | "PerCpuHashMap" | "LruHashMap" | "LruPerCpuHashMap" + | "TcHashMap" => { if bracket.args.len() == 2 { key_type = Some(bracket.args.first().unwrap().clone()); value_type = Some(bracket.args.last().unwrap().clone()); } } - _ => {} + "PerfMap" => {} + _ => { + panic!("unknown map type name: {}", map_type_name); + } + } + + if map_type_name == "TcHashMap" { + tc_compatible = true; + } + } else { + // without generic types + match map_type_name.as_str() { + "StackTrace" | "SockMap" | "ProgramArray" => {} + _ => { + panic!("unknown map type name: {}", map_type_name); + } } } } @@ -265,28 +283,55 @@ pub fn map(attrs: TokenStream, item: TokenStream) -> TokenStream { let map_btf_ident = syn::Ident::new(&map_btf_name, static_item.ident.span()); let value_align_name = format!("MAP_VALUE_ALIGN_{}", static_item.ident.to_string()); let value_align_ident = syn::Ident::new(&value_align_name, static_item.ident.span()); - tokens.extend(quote! { - mod #mod_ident { - #[allow(unused_imports)] - use super::*; - use core::mem::{self, MaybeUninit}; - - #[no_mangle] - static #value_align_ident: MaybeUninit<#vtype> = MaybeUninit::uninit(); - - #[repr(C)] - struct MapBtf { - key_type: #ktype, - value_type: #vtype, + if tc_compatible { + let btf_type_name = format!("____btf_map_{}", static_item.ident.to_string()); + let btf_map_type = syn::Ident::new(&btf_type_name, static_item.ident.span()); + tokens.extend(quote! { + mod #mod_ident { + #[allow(unused_imports)] + use super::*; + use core::mem::{self, MaybeUninit}; + + #[no_mangle] + static #value_align_ident: MaybeUninit<#vtype> = MaybeUninit::uninit(); + + #[repr(C)] + struct #btf_map_type { + key: #ktype, + value: #vtype, + } + // `impl Sync` is needed to allow pointer types of keys and values + unsafe impl Sync for #btf_map_type {} + const N: usize = mem::size_of::<#btf_map_type>(); + #[no_mangle] + #[link_section = "maps.ext"] + static #map_btf_ident: #btf_map_type = unsafe { mem::transmute::<[u8; N], #btf_map_type>([0u8; N]) }; } - // `impl Sync` is needed to allow pointer types of keys and values - unsafe impl Sync for MapBtf {} - const N: usize = mem::size_of::(); - #[no_mangle] - #[link_section = "maps.ext"] - static #map_btf_ident: MapBtf = unsafe { mem::transmute::<[u8; N], MapBtf>([0u8; N]) }; - } - }); + }); + } else { + tokens.extend(quote! { + mod #mod_ident { + #[allow(unused_imports)] + use super::*; + use core::mem::{self, MaybeUninit}; + + #[no_mangle] + static #value_align_ident: MaybeUninit<#vtype> = MaybeUninit::uninit(); + + #[repr(C)] + struct MapBtf { + key_type: #ktype, + value_type: #vtype, + } + // `impl Sync` is needed to allow pointer types of keys and values + unsafe impl Sync for MapBtf {} + const N: usize = mem::size_of::(); + #[no_mangle] + #[link_section = "maps.ext"] + static #map_btf_ident: MapBtf = unsafe { mem::transmute::<[u8; N], MapBtf>([0u8; N]) }; + } + }); + } } tokens.into() } From 7437b6c895b9736eb4b6c7e374ca09ec66cd7af6 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 2 Dec 2021 19:03:36 +0900 Subject: [PATCH 029/107] Add BTF::filter method Filter particular BTF types that are chosen by a predicate closure. This method is intended to be used by cargo-bpf to remove some BTF types that are now supported by tc command. If a BTF record is chosen to be deleted then all relevant BTF types that depend on it are also eliminated first. So BTF consistency can be upheld. Signed-off-by: Junyeong Jeong --- redbpf/src/btf.rs | 239 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 231 insertions(+), 8 deletions(-) diff --git a/redbpf/src/btf.rs b/redbpf/src/btf.rs index 2dbb4c9c..d83aa68f 100644 --- a/redbpf/src/btf.rs +++ b/redbpf/src/btf.rs @@ -38,6 +38,7 @@ pub(crate) struct BTF { fd: Option, } +#[derive(Clone)] struct BtfTypeCommon { type_: btf_type, #[allow(unused)] @@ -66,6 +67,7 @@ pub(crate) enum BtfKind { FloatingPoint, } +#[derive(Clone)] enum BtfType { Integer(BtfTypeCommon, u32), Pointer(BtfTypeCommon), @@ -85,6 +87,7 @@ enum BtfType { FloatingPoint(BtfTypeCommon), } +#[derive(Clone)] struct BtfMember { member: btf_member, name: String, @@ -390,7 +393,7 @@ impl BTF { .iter() .find_map(|memb| { if &memb.name == "key_type" { - Some(memb.member.type_) + Some(memb.type_id()) } else { None } @@ -405,7 +408,7 @@ impl BTF { .iter() .find_map(|memb| { if &memb.name == "value_type" { - Some(memb.member.type_) + Some(memb.type_id()) } else { None } @@ -459,6 +462,188 @@ impl BTF { } }) } + + fn filter(&mut self, mut f: F) -> Result<()> + where + F: FnMut(&BtfType) -> bool, + { + use BtfType::*; + // key: type_id / value: [type_id, type_id, ...]. + // + // Value contains the types refering to the key type. Since the types + // in a value depend on the key type, if the key type is removed then + // the types the value desginates have to be eliminated first. + let mut in_link = RSHashMap::>::with_capacity(self.types.len()); + for (id, ty) in self.types.iter_mut() { + in_link.entry(*id).or_default(); + match ty { + Integer(..) | Enumeration(..) | FloatingPoint(..) => {} + Pointer(comm) + | TypeDef(comm) + | Volatile(comm) + | Constant(comm) + | Restrict(comm) + | Function(comm) + | Variable(comm, ..) + | Forward(comm) => { + in_link.entry(comm.type_id()).or_default().push(*id); + } + Structure(_, membs) | Union(_, membs) => { + for memb in membs.iter() { + in_link.entry(memb.type_id()).or_default().push(*id); + } + } + FunctionProtocol(comm, params) => { + let ret_type_id = comm.type_id(); + in_link.entry(ret_type_id).or_default().push(*id); + for param in params.iter() { + in_link.entry(param.type_).or_default().push(*id); + } + } + DataSection(_, vsis) => { + for vsi in vsis.iter() { + in_link.entry(vsi.type_).or_default().push(*id); + } + } + Array(_, arr) => { + in_link.entry(arr.type_).or_default().push(*id); + in_link.entry(arr.index_type).or_default().push(*id); + } + } + } + + for (id, ty) in self.types.iter() { + if !f(ty) { + let mut stack = vec![]; + let mut rem_id: u32 = *id; + // remove this type and all types that refer to it + 'recur: loop { + if let Some(mut in_ids) = in_link.remove(&rem_id) { + if let Some(in_id) = in_ids.pop() { + rem_id = in_id; + stack.extend(in_ids); + continue; + } + } + + if let Some(in_id) = stack.pop() { + rem_id = in_id; + } else { + break 'recur; + } + } + } + } + + let mut old_ids = in_link.into_keys().collect::>(); + old_ids.sort(); + + let mut new_ids = RSHashMap::::with_capacity(old_ids.len()); + for (i, old_id) in old_ids.iter().enumerate() { + new_ids.insert(*old_id, i as u32); + } + + assert_eq!(new_ids.get(&0), Some(&0)); + let mut new_types = self + .types + .iter() + .filter_map(|(old_id, ty)| { + if let Some(new_id) = new_ids.get(old_id) { + Some((*new_id, (*ty).clone())) + } else { + None + } + }) + .collect::>(); + + let mut type_len = 0; + for (_, ty) in new_types.iter_mut() { + let tydbg = format!("{:?}", ty); + match ty { + Integer(..) | Enumeration(..) | FloatingPoint(..) => {} + Pointer(comm) + | TypeDef(comm) + | Volatile(comm) + | Constant(comm) + | Restrict(comm) + | Function(comm) + | Variable(comm, ..) + | Forward(comm) => { + let new_id = *new_ids.get(&comm.type_id()).ok_or_else(|| { + error!("Failed to get new type id of {}", tydbg); + Error::BTF("inconsistency detected in BTF".to_string()) + })?; + comm.set_type_id(new_id); + } + Structure(_, membs) | Union(_, membs) => { + for memb in membs.iter_mut() { + let new_id = *new_ids.get(&memb.type_id()).ok_or_else(|| { + error!( + "Failed to get new type id of member {} of {}", + memb.name, tydbg + ); + Error::BTF("inconsistency detected in BTF".to_string()) + })?; + memb.set_type_id(new_id); + } + } + FunctionProtocol(comm, params) => { + let new_id = *new_ids.get(&comm.type_id()).ok_or_else(|| { + error!("Failed to get new ret type id of {}", tydbg); + Error::BTF("inconsistency detected in BTF".to_string()) + })?; + comm.set_type_id(new_id); + for param in params.iter_mut() { + let new_id = *new_ids.get(¶m.type_).ok_or_else(|| { + error!("Failed to get new type id of param of {}", tydbg); + Error::BTF("inconsistency detected in BTF".to_string()) + })?; + param.type_ = new_id; + } + } + DataSection(_, vsis) => { + for vsi in vsis.iter_mut() { + let new_id = *new_ids.get(&vsi.type_).ok_or_else(|| { + error!( + "Failed to get new type id of variable section info of {}", + tydbg + ); + Error::BTF("inconsistency detected in BTF".to_string()) + })?; + vsi.type_ = new_id; + } + } + Array(_, arr) => { + let new_id = *new_ids.get(&arr.type_).ok_or_else(|| { + error!("Failed to get new type id of {}", tydbg); + Error::BTF("inconsistency detected in BTF".to_string()) + })?; + arr.type_ = new_id; + let new_id = *new_ids.get(&arr.index_type).ok_or_else(|| { + error!("Failed to get new index type id of {}", tydbg); + Error::BTF("inconsistency detected in BTF".to_string()) + })?; + arr.index_type = new_id; + } + } + + type_len += ty.byte_len(); + } + + let decreased = self.btf_hdr.type_len - type_len as u32; + self.btf_hdr.type_len = type_len as u32; + // It is not allowed to have gap between type and str + // https://elixir.bootlin.com/linux/v5.13/source/kernel/bpf/btf.c#L4164 + self.btf_hdr.str_off -= decreased; + // Increase str data size with zero-filled padding + self.btf_hdr.str_len += decreased; + for _ in 0..decreased { + self.raw_str_enc.push(0); + } + self.types = new_types; + + Ok(()) + } } impl Drop for BTF { @@ -533,6 +718,10 @@ impl BtfTypeCommon { fn type_id(&self) -> u32 { unsafe { self.type_.__bindgen_anon_1.type_ } } + + fn set_type_id(&mut self, type_: u32) { + self.type_.__bindgen_anon_1.type_ = type_; + } } impl BtfType { @@ -906,11 +1095,24 @@ impl fmt::Debug for BtfType { )?; for bm in btf_membs.iter() { - write!( - f, - "\n\t'{}' type_id={} bits_offset={}", - bm.name, bm.member.type_, bm.member.offset - )?; + if comm.kind_flag() { + write!( + f, + "\n\t'{}' type_id={} bits_offset={} bitfield_size={}", + bm.name, + bm.type_id(), + bm.bit_offset(), + bm.bitfield_size(), + )?; + } else { + write!( + f, + "\n\t'{}' type_id={} bits_offset={}", + bm.name, + bm.type_id(), + bm.bit_offset() + )?; + } } write!(f, "") @@ -953,7 +1155,7 @@ impl fmt::Debug for BtfType { FunctionProtocol(comm, ..) => { write!( f, - "FunctionProtocol '{}' type_id={} vlen={}", + "FunctionProtocol '{}' ret_type_id={} vlen={}", if comm.name_raw.is_empty() { &anon } else { @@ -1000,6 +1202,27 @@ impl fmt::Debug for BtfType { } } +impl BtfMember { + /// If `BtfTypeCommand::kind_flag` returns true, `self.member.offset` + /// contains both bit offset and bitfield size. Otherwise, + /// `self.member.offset` only contains bit offset. + fn bit_offset(&self) -> u32 { + self.member.offset & 0xffffff + } + + fn bitfield_size(&self) -> u32 { + self.member.offset >> 24 + } + + fn type_id(&self) -> u32 { + self.member.type_ + } + + fn set_type_id(&mut self, type_: u32) { + self.member.type_ = type_; + } +} + fn fix_btf_identifier(btf_id: &str) -> String { btf_id .chars() From 32b48f22352a1ad083966e9f304e2c52c435ee1d Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 2 Dec 2021 19:10:17 +0900 Subject: [PATCH 030/107] Add a function for fixing BTF section for tc command Signed-off-by: Junyeong Jeong --- redbpf/src/btf.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++ redbpf/src/lib.rs | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/redbpf/src/btf.rs b/redbpf/src/btf.rs index d83aa68f..03303a7e 100644 --- a/redbpf/src/btf.rs +++ b/redbpf/src/btf.rs @@ -1284,3 +1284,57 @@ fn get_type_name(str_bytes: &[u8], name_off: u32) -> Result { .into_owned() }) } + +/// Fix .BTF section for `tc` command that depends on legacy BPF +/// +/// BTF types generated by rustc contain invalid characters from the point of +/// view of BPF verifier. Since BPF verifier only allows type identifiers and +/// variable names compatible with C language. Since the names of BTF types +/// that are generated by rustc also contain generic types, but they are not +/// compatible with C language thus it is failed to load them into the kernel. +/// +/// This function is intended to be used by `cargo-bpf` to make `tc` utility +/// load `.BTF` section successfully. To achieve this goal, `.BTF` section is +/// fixed during compiling the ELF object file and some BTF types that are not +/// supported by legacy BPF of `tc` command. +/// +/// **NOTE** Currently while compiling `tc` command, the `libbpf` can be opted +/// in so that the `tc` command can recognize new BTF types. But most distros +/// do not provide tc with libbpf by default but so filtering. Perhaps it is +/// possible that `tc` compiled with `libbpf` will be installed system wide by +/// default in the future, but it will take time. Until then it's better to fix +/// BTF types for most users. +pub fn tc_legacy_fix_btf_section(elf_bytes: &[u8]) -> Result> { + let object = Elf::parse(elf_bytes)?; + // let btf = BTF::parse_elf(&object, elf_bytes)?; + + let shdr = get_section_header_by_name(&object, BTF_SECTION_NAME) + .ok_or_else(|| Error::BTF("section not found".to_string()))?; + let start = shdr.sh_offset as usize; + let end = start + shdr.sh_size as usize; + let raw_btf = &elf_bytes[start..end]; + let mut btf = BTF::parse_raw(raw_btf)?; + btf.filter(|type_| { + use BtfType::*; + match type_ { + Integer(..) | Pointer(..) | Array(..) | Structure(..) | Union(..) | Enumeration(..) + | Forward(..) | TypeDef(..) | Volatile(..) | Constant(..) | Restrict(..) + | Function(..) | FunctionProtocol(..) => true, + _ => false, + } + })?; + + let mut fixed: Vec = vec![]; + fixed.extend(&elf_bytes[..start]); + fixed.extend(btf.dump()?); + if end < fixed.len() { + error!("BTF data size increased after some BTF records are filtered"); + return Err(Error::BTF("BTF section is bloated".to_string())); + } + if end > fixed.len() { + error!("BTF section size decreased after some BTF records are filtered"); + return Err(Error::BTF("BTF section is shrunk".to_string())); + } + fixed.extend(&elf_bytes[end..]); + Ok(fixed) +} diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index b5be7738..2988d61f 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -44,7 +44,7 @@ for kprobe in loader.kprobes_mut() { #[macro_use] extern crate lazy_static; -mod btf; +pub mod btf; pub mod cpus; mod error; #[cfg(feature = "load")] From 1626b105c60528ec6ec1b67e1c93c3dfde5e0d4d Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 2 Dec 2021 19:12:02 +0900 Subject: [PATCH 031/107] Fix BTF section only if tc action probes exis in ELF relocatable file Signed-off-by: Junyeong Jeong --- cargo-bpf/src/build.rs | 10 ++++++++++ cargo-bpf/src/llvm.rs | 43 ++++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/cargo-bpf/src/build.rs b/cargo-bpf/src/build.rs index a68c64a3..e34a2d55 100644 --- a/cargo-bpf/src/build.rs +++ b/cargo-bpf/src/build.rs @@ -16,6 +16,8 @@ use std::process::Command; use std::str; use toml_edit::{Document, Item}; +use redbpf::btf; + use crate::llvm; use crate::CommandError; @@ -30,6 +32,7 @@ pub enum Error { Link(String), IOError(io::Error), PatternError(PatternError), + BTF, } impl std::error::Error for Error { @@ -61,6 +64,7 @@ impl Display for Error { NoLLC => write!(f, "no usable llc executable found, expecting version 9"), IOError(e) => write!(f, "{}", e), PatternError(e) => write!(f, "couldn't list probe files: {}", e), + BTF => write!(f, "failed to fix BTF section"), } } } @@ -182,6 +186,12 @@ fn build_probe( .find(|name| name.starts_with("tc_action/")) .is_some() }; + if contains_tc { + let elf_bytes = fs::read(&target).or_else(|e| Err(Error::IOError(e)))?; + let fixed = + btf::tc_legacy_fix_btf_section(elf_bytes.as_slice()).or_else(|_| Err(Error::BTF))?; + fs::write(&target, fixed).or_else(|e| Err(Error::IOError(e)))?; + } let _ = llvm::strip_unnecessary(&target, contains_tc); Ok(()) diff --git a/cargo-bpf/src/llvm.rs b/cargo-bpf/src/llvm.rs index 12f1c290..2b6cb6ce 100644 --- a/cargo-bpf/src/llvm.rs +++ b/cargo-bpf/src/llvm.rs @@ -193,6 +193,22 @@ pub(crate) unsafe fn get_function_section_names(bc: &Path) -> Result Ok(section_names) } +fn find_available_command<'a>(candidates: &[&'a str]) -> Option<&'a str> { + candidates.iter().find_map(|cmd| { + if Command::new(*cmd) + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .is_ok() + { + Some(*cmd) + } else { + None + } + }) +} + /// Strip unnecessary sections from resulting ELF relocatable file /// /// This removes sections of which name start with `.debug` and their @@ -204,20 +220,12 @@ pub(crate) unsafe fn get_function_section_names(bc: &Path) -> Result /// .text section is also removed. /// pub(crate) fn strip_unnecessary(target: &impl AsRef, delete_btf: bool) -> Result<()> { - let cmd = [ + let cmd = find_available_command(&[ "llvm-strip", "llvm-strip-13", "llvm-strip-12", "llvm-strip-11", - ] - .iter() - .find(|cmd| { - Command::new(cmd) - .arg("--version") - .stdout(Stdio::null()) - .status() - .is_ok() - }) + ]) .ok_or_else(|| anyhow!("llvm-strip command not found"))?; Command::new(cmd) @@ -227,19 +235,14 @@ pub(crate) fn strip_unnecessary(target: &impl AsRef, delete_btf: bool) -> .map(|_| ()) .or_else(|e| Err(anyhow!("llvm-strip --strip-debug failed: {}", e)))?; - // Even if there does not exist any function in .text section, .text - // section is created with zero size as a result of compilation. So it is - // needed to remove it explictly. let mut cmd = Command::new(cmd); if delete_btf { - // The BTF section generated by rustc contains characters that are not - // permitted by BPF verifier of the Linux kernel. So those characters - // should be fixed before loading the BTF sections. But some utils that - // are not aware of rustc generated BTF such as `tc` do not handle - // this. In this case just remove BTF sections to avoid BPF verifier - // problem. - cmd.args("--remove-section .BTF.ext --remove-section .BTF".split(' ')); + cmd.args("--remove-section .BTF.ext".split(' ')); } + // Even if there does not exist any function in .text section, .text + // section is created with zero size as a result of compilation. So it is + // needed to remove it explictly. The .text section can cause a problem if + // the resulting ELF relocatable file is passed to tc command. cmd.args("--remove-section .text".split(' ')) .arg("--no-strip-all") .arg(target.as_ref()) From 32a749f6042cc716dd078eff29f1111a8943b335 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 2 Dec 2021 19:15:18 +0900 Subject: [PATCH 032/107] Add error message that sets users at ease Signed-off-by: Junyeong Jeong --- redbpf/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 2988d61f..c6543e1d 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -1413,6 +1413,10 @@ impl<'a> ModuleBuilder<'a> { // BTF is optional let btf: Option = BTF::parse_elf(&object, bytes) .and_then(|mut btf| btf.load().map(|_| btf)) + .or_else(|e| { + warn!("Failed to load BTF but BTF is optional. Ignore it"); + Err(e) + }) .ok(); let mut vmlinux_btf = None; for (shndx, shdr) in object.section_headers.iter().enumerate() { @@ -1806,8 +1810,9 @@ impl Map { }) } else { error!( - "error on bpf_create_map_xattr. failed to load map `{}`", - name + "error on bpf_create_map_xattr. failed to load map `{}`: {}", + name, + io::Error::last_os_error() ); Err(Error::Map) } From 408b8030bda935513925e7a289117224bf989387 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 2 Dec 2021 19:16:41 +0900 Subject: [PATCH 033/107] Update tc example to demonstrate bpf_spin_lock helper function Signed-off-by: Junyeong Jeong --- .../example-probes/src/tc_map_share/main.rs | 25 +++++++++++++++++++ examples/example-userspace/Cargo.toml | 4 ++- examples/example-userspace/build.rs | 7 ++++++ .../examples/tc-map-share.rs | 1 + 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/examples/example-probes/src/tc_map_share/main.rs b/examples/example-probes/src/tc_map_share/main.rs index 32d0881e..e2553396 100644 --- a/examples/example-probes/src/tc_map_share/main.rs +++ b/examples/example-probes/src/tc_map_share/main.rs @@ -20,9 +20,34 @@ program!(0xFFFFFFFE, "GPL"); static mut blocked_packets: TcHashMap = TcHashMap::::with_max_entries(1024, TcMapPinning::GlobalNamespace); +#[repr(C)] +struct SpinLock { + lock: bpf_spin_lock, +} + +#[map(link_section = "maps")] +static mut spinlocks: TcHashMap = TcHashMap::with_max_entries(1, TcMapPinning::None); + /// BPF program type is BPF_PROG_TYPE_SCHED_CLS #[tc_action] fn block_ports(skb: SkBuff) -> TcActionResult { + let lock = unsafe { + let key = 0; + if let Some(lock) = spinlocks.get_mut(&key) { + lock + } else { + let val = MaybeUninit::::zeroed().assume_init(); + spinlocks.set(&key, &val); + spinlocks.get_mut(&key).unwrap() + } + }; + + // just show how to use spin lock + unsafe { + bpf_spin_lock(&mut lock.lock as *mut _); + bpf_spin_unlock(&mut lock.lock as *mut _); + } + let tcp_hdr_offset = match u32::from_be(unsafe { *skb.skb }.protocol << 16) { ETH_P_IP => { let mut uninit = MaybeUninit::::uninit(); diff --git a/examples/example-userspace/Cargo.toml b/examples/example-userspace/Cargo.toml index 19676782..a948af0e 100644 --- a/examples/example-userspace/Cargo.toml +++ b/examples/example-userspace/Cargo.toml @@ -11,6 +11,8 @@ publish = false [build-dependencies] cargo-bpf = { path = "../../cargo-bpf", default-features = false, features = ["build"] } +tracing = "0.1.26" +tracing-subscriber = "0.3.3" [dependencies] probes = { path = "../example-probes", package = "example-probes" } @@ -19,7 +21,7 @@ tokio = { version = "^1.0.1", features = ["rt", "signal", "time", "io-util", "ne redbpf = { path = "../../redbpf", features = ["load"] } futures = "0.3" tracing = "0.1.26" -tracing-subscriber = "0.2.18" +tracing-subscriber = "0.3.3" [[example]] name = "tasks" diff --git a/examples/example-userspace/build.rs b/examples/example-userspace/build.rs index fa9d3b19..285861c8 100644 --- a/examples/example-userspace/build.rs +++ b/examples/example-userspace/build.rs @@ -1,9 +1,16 @@ use std::env; use std::path::{Path, PathBuf}; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; use cargo_bpf_lib as cargo_bpf; fn main() { + let subscriber = FmtSubscriber::builder() + .with_max_level(Level::TRACE) + .finish(); + tracing::subscriber::set_global_default(subscriber).unwrap(); + let cargo = PathBuf::from(env::var("CARGO").unwrap()); let target = PathBuf::from(env::var("OUT_DIR").unwrap()); let probes = Path::new("../example-probes"); diff --git a/examples/example-userspace/examples/tc-map-share.rs b/examples/example-userspace/examples/tc-map-share.rs index 1314fc76..fccc9bfe 100644 --- a/examples/example-userspace/examples/tc-map-share.rs +++ b/examples/example-userspace/examples/tc-map-share.rs @@ -60,6 +60,7 @@ async fn main() { .arg("object-file") .arg(bpf_elf) .args("section tc_action/block_ports".split(" ")) + .arg("verbose") .status() .expect("error on tc filter add"); From 1835ebb616e136ba7451709979b422a69f262829 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 2 Dec 2021 22:50:46 +0900 Subject: [PATCH 034/107] Fix new type id generation method Signed-off-by: Junyeong Jeong --- redbpf/src/btf.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/redbpf/src/btf.rs b/redbpf/src/btf.rs index 03303a7e..ff526e81 100644 --- a/redbpf/src/btf.rs +++ b/redbpf/src/btf.rs @@ -534,16 +534,16 @@ impl BTF { } } } + // type_id 0 may exist or may not exist in the in_link + let new_id_correc = if let Some(_) = in_link.get(&0) { 0 } else { 1 }; let mut old_ids = in_link.into_keys().collect::>(); old_ids.sort(); let mut new_ids = RSHashMap::::with_capacity(old_ids.len()); for (i, old_id) in old_ids.iter().enumerate() { - new_ids.insert(*old_id, i as u32); + new_ids.insert(*old_id, i as u32 + new_id_correc); } - - assert_eq!(new_ids.get(&0), Some(&0)); let mut new_types = self .types .iter() From 59de5e65c8d3270f1df2d0dcc1b8879e0545c619 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 2 Dec 2021 23:17:31 +0900 Subject: [PATCH 035/107] Fix alpine build test Signed-off-by: Junyeong Jeong --- .github/workflows/build-test.yml | 2 +- cargo-bpf/Cargo.toml | 2 +- redbpf/src/btf.rs | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 1e82cde9..01d488f5 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -109,7 +109,7 @@ jobs: /bin/bash -c 'cargo clean && /lib/modules/*/build/scripts/extract-vmlinux /lib/modules/*/vmlinuz > /boot/vmlinux && export REDBPF_VMLINUX=/boot/vmlinux && cargo build && cargo build --examples' alpine-314-build-test: - name: Alpine 3.14 (Disabled until LLVM12 support) + name: Alpine 3.14 runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/cargo-bpf/Cargo.toml b/cargo-bpf/Cargo.toml index 6ec4e5ab..4eb95cdd 100644 --- a/cargo-bpf/Cargo.toml +++ b/cargo-bpf/Cargo.toml @@ -45,7 +45,7 @@ rustversion = "1.0" [features] default = ["command-line", "llvm12"] bindings = ["bpf-sys", "bindgen", "syn", "quote", "proc-macro2", "tempfile"] -build = ["bindings", "libc", "toml_edit"] +build = ["bindings", "libc", "toml_edit", "redbpf"] llvm10 = ["llvm-sys-100"] llvm11 = ["llvm-sys-110"] llvm12 = ["llvm-sys-120"] diff --git a/redbpf/src/btf.rs b/redbpf/src/btf.rs index ff526e81..73936b4c 100644 --- a/redbpf/src/btf.rs +++ b/redbpf/src/btf.rs @@ -537,7 +537,10 @@ impl BTF { // type_id 0 may exist or may not exist in the in_link let new_id_correc = if let Some(_) = in_link.get(&0) { 0 } else { 1 }; - let mut old_ids = in_link.into_keys().collect::>(); + let mut old_ids = in_link + .drain() + .map(|(old_id, _)| old_id) + .collect::>(); old_ids.sort(); let mut new_ids = RSHashMap::::with_capacity(old_ids.len()); From b7e53621184144da4a52659ef2e4c7f4bebd30bb Mon Sep 17 00:00:00 2001 From: ihciah Date: Sat, 11 Dec 2021 15:58:46 +0800 Subject: [PATCH 036/107] fix: change symbol __va_list_tag to __gnuc_va_list for aarch64 Signed-off-by: ihciah --- bpf-sys/src/type_gen.rs | 18 +++++++++++++++--- redbpf/src/btf.rs | 6 +++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/bpf-sys/src/type_gen.rs b/bpf-sys/src/type_gen.rs index 5baccd1c..16e1310b 100644 --- a/bpf-sys/src/type_gen.rs +++ b/bpf-sys/src/type_gen.rs @@ -19,8 +19,8 @@ system. */ use super::{ - __va_list_tag, btf, btf__free, btf__get_nr_types, btf__name_by_offset, btf__parse_elf, - btf__type_by_id, btf_dump, btf_dump__dump_type, btf_dump__free, btf_dump__new, btf_dump_opts, + btf, btf__free, btf__get_nr_types, btf__name_by_offset, btf__parse_elf, btf__type_by_id, + btf_dump, btf_dump__dump_type, btf_dump__free, btf_dump__new, btf_dump_opts, libbpf_find_kernel_btf, vdprintf, }; use libc::{c_char, c_void}; @@ -226,10 +226,22 @@ impl Drop for VmlinuxBtfDump { } // wrapping vdprintf to get rid of return type +#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] unsafe extern "C" fn vdprintf_wrapper( ctx: *mut c_void, format: *const c_char, - va_list: *mut __va_list_tag, + va_list: *mut super::__va_list_tag, +) { + let rawfd_wrapper = &*(ctx as *mut RawFdWrapper); + vdprintf(rawfd_wrapper.0, format, va_list); +} + +// wrapping vdprintf to get rid of return type +#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] +unsafe extern "C" fn vdprintf_wrapper( + ctx: *mut c_void, + format: *const c_char, + va_list: super::__gnuc_va_list, ) { let rawfd_wrapper = &*(ctx as *mut RawFdWrapper); vdprintf(rawfd_wrapper.0, format, va_list); diff --git a/redbpf/src/btf.rs b/redbpf/src/btf.rs index 73936b4c..12816ce8 100644 --- a/redbpf/src/btf.rs +++ b/redbpf/src/btf.rs @@ -132,12 +132,12 @@ impl BTF { fd = bpf_sys::bpf_load_btf( raw_bytes.as_ptr() as *const _, raw_bytes.len() as u32, - log_buf, + log_buf as _, log_buf_size as u32, false, ); if fd < 0 { - let cstr = CStr::from_ptr(log_buf); + let cstr = CStr::from_ptr(log_buf as _); error!("error on bpf_load_btf: {}", cstr.to_str().unwrap()); return Err(Error::IO(io::Error::last_os_error())); } @@ -1282,7 +1282,7 @@ fn get_type_name(str_bytes: &[u8], name_off: u32) -> Result { return Err(Error::BTF("name offset is out of string data".to_string())); } Ok(unsafe { - CStr::from_ptr(str_bytes.as_ptr().offset(name_off as isize) as *const i8) + CStr::from_ptr(str_bytes.as_ptr().offset(name_off as isize) as _) .to_string_lossy() .into_owned() }) From de2d876af12a0c1617c84c8cfa0ff83a38c11bec Mon Sep 17 00:00:00 2001 From: Mikhail Trishchenkov Date: Thu, 16 Dec 2021 21:12:02 +0700 Subject: [PATCH 037/107] Add support for devmap map type Signed-off-by: Mikhail Trishchenkov --- redbpf-macros/src/lib.rs | 2 +- redbpf-probes/src/helpers.rs | 12 +++++++ redbpf-probes/src/xdp/devmap.rs | 48 +++++++++++++++++++++++++ redbpf-probes/src/xdp/mod.rs | 3 ++ redbpf/src/xdp.rs | 62 +++++++++++++++++++++++++++++++-- 5 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 redbpf-probes/src/xdp/devmap.rs diff --git a/redbpf-macros/src/lib.rs b/redbpf-macros/src/lib.rs index 32cf1a9b..b3b392bd 100644 --- a/redbpf-macros/src/lib.rs +++ b/redbpf-macros/src/lib.rs @@ -263,7 +263,7 @@ pub fn map(attrs: TokenStream, item: TokenStream) -> TokenStream { } else { // without generic types match map_type_name.as_str() { - "StackTrace" | "SockMap" | "ProgramArray" => {} + "StackTrace" | "SockMap" | "ProgramArray" | "DevMap" => {} _ => { panic!("unknown map type name: {}", map_type_name); } diff --git a/redbpf-probes/src/helpers.rs b/redbpf-probes/src/helpers.rs index 7a232350..e734f405 100644 --- a/redbpf-probes/src/helpers.rs +++ b/redbpf-probes/src/helpers.rs @@ -207,3 +207,15 @@ pub fn bpf_perf_event_output( f(ctx, map, flags, data, size) } } + +#[inline] +pub fn bpf_redirect_map(map: *mut c_void, key: u32, flags: u64) -> i64 { + unsafe { + let f: unsafe extern "C" fn( + map: *mut c_void, + key: u32, + flags: u64, + ) -> i64 = ::core::mem::transmute(51usize); + f(map, key, flags) + } +} diff --git a/redbpf-probes/src/xdp/devmap.rs b/redbpf-probes/src/xdp/devmap.rs new file mode 100644 index 00000000..cd2f6a82 --- /dev/null +++ b/redbpf-probes/src/xdp/devmap.rs @@ -0,0 +1,48 @@ +use core::mem; +use cty::c_void; +use crate::xdp::{ + bpf_map_def, bpf_map_type_BPF_MAP_TYPE_DEVMAP, XdpAction, + prelude::bpf_redirect_map, +}; + +/// Device map. +/// +/// DevMap is array-like map which may be used to redirect packet to another +/// network interface. It's values are interface indices. +/// This is a wrapper for `BPF_MAP_TYPE_DEVMAP`. +#[repr(transparent)] +pub struct DevMap { + def: bpf_map_def, +} + +impl DevMap { + /// Creates a device map with the specified maximum number of elements. + pub const fn with_max_entries(max_entries: u32) -> Self { + Self { + def: bpf_map_def { + type_: bpf_map_type_BPF_MAP_TYPE_DEVMAP, + key_size: mem::size_of::() as u32, + value_size: mem::size_of::() as u32, + max_entries, + map_flags: 0, + }, + } + } + + /// Redirects the packet to the endpoint referenced at key `key`. + /// Returns Ok if device was found for specified key. XDP probe + /// must return XdpAction::Redirect to actually redirect packet. + /// If key is not found, Err is returned. + #[inline] + pub fn redirect(&mut self, key: u32) -> Result<(), ()> { + let res = bpf_redirect_map( + &mut self.def as *mut _ as *mut c_void, + key, XdpAction::Aborted as u64 + ); + if res == XdpAction::Redirect as i64 { + Ok(()) + } else { + Err(()) + } + } +} diff --git a/redbpf-probes/src/xdp/mod.rs b/redbpf-probes/src/xdp/mod.rs index 2bc8793e..5965ae8e 100644 --- a/redbpf-probes/src/xdp/mod.rs +++ b/redbpf-probes/src/xdp/mod.rs @@ -35,6 +35,9 @@ fn block_port_80(ctx: XdpContext) -> XdpResult { ``` */ pub mod prelude; +mod devmap; + +pub use devmap::DevMap; use crate::bindings::*; use crate::maps::{PerfMap as PerfMapBase, PerfMapFlags}; diff --git a/redbpf/src/xdp.rs b/redbpf/src/xdp.rs index d2f7b21b..f6b0e420 100644 --- a/redbpf/src/xdp.rs +++ b/redbpf/src/xdp.rs @@ -1,12 +1,16 @@ use std::default::Default; use std::slice; +use std::mem; -use crate::Sample; +use crate::error::{Error, Result}; +use crate::{Map, Sample}; use bpf_sys::{ XDP_FLAGS_DRV_MODE, XDP_FLAGS_HW_MODE, XDP_FLAGS_MASK, XDP_FLAGS_MODES, XDP_FLAGS_SKB_MODE, - XDP_FLAGS_UPDATE_IF_NOEXIST, + XDP_FLAGS_UPDATE_IF_NOEXIST, BPF_ANY, bpf_map_type_BPF_MAP_TYPE_DEVMAP, }; +use tracing::error; + #[derive(Debug, Clone, Copy)] #[repr(u32)] pub enum Flags { @@ -58,3 +62,57 @@ impl MapData { } } } + +/// DevMap structure for storing interface indices to redirect packets to. +/// +/// A devmap is a BPF map type that holds network interface indices. BPF XDP +/// program can use the devmap to redirect raw packets to another interface. +/// +/// The counterpart which is used by BPF program is: +/// [`redbpf_probes::maps::DevMap`](../redbpf_probes/maps/struct.DevMap.html). +pub struct DevMap<'a> { + base: &'a Map, +} + +impl<'a> DevMap<'a> { + pub fn new(base: &'a Map) -> Result> { + if mem::size_of::() != base.config.key_size as usize + || mem::size_of::() != base.config.value_size as usize + || (bpf_map_type_BPF_MAP_TYPE_DEVMAP != base.config.type_) + { + error!( + "map definitions (map type and key/value size) of base `Map' and + `DevMap' do not match" + ); + return Err(Error::Map); + } + + Ok(DevMap { base }) + } + + pub fn set(&mut self, mut idx: u32, mut interface_index: u32) -> Result<()> { + let ret = unsafe { + bpf_sys::bpf_map_update_elem( + self.base.fd, + &mut idx as *mut _ as *mut _ as *mut _, + &mut interface_index as *mut _ as *mut _, + BPF_ANY.into(), // No condition on the existence of the entry for `idx`. + ) + }; + if ret < 0 { + Err(Error::Map) + } else { + Ok(()) + } + } + + pub fn delete(&mut self, mut idx: u32) -> Result<()> { + let ret = + unsafe { bpf_sys::bpf_map_delete_elem(self.base.fd, &mut idx as *mut _ as *mut _) }; + if ret < 0 { + Err(Error::Map) + } else { + Ok(()) + } + } +} From fabb81ad9cd4d85f033259aee6bfb8dd0ad4c953 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 23 Dec 2021 00:45:26 +0900 Subject: [PATCH 038/107] Try parsing raw BTF data before parsing ELF file to get vmlinux BTF Signed-off-by: Junyeong Jeong --- bpf-sys/src/type_gen.rs | 28 ++++++++++++++++++++++++++-- redbpf-probes/src/lib.rs | 4 +++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/bpf-sys/src/type_gen.rs b/bpf-sys/src/type_gen.rs index 16e1310b..bf71c3d5 100644 --- a/bpf-sys/src/type_gen.rs +++ b/bpf-sys/src/type_gen.rs @@ -110,7 +110,7 @@ impl VmlinuxBtfDump { }) } - /// Try to load BTF data out of the given ELF file + /// Read the ELF file and parse BTF data out of the given ELF file pub fn with_elf_file(elf_file: impl AsRef) -> Result { if !elf_file.as_ref().exists() { return Err(TypeGenError::VmlinuxNotFound); @@ -133,6 +133,26 @@ impl VmlinuxBtfDump { }) } + /// Parse BTF data from a file containing raw BTF data + pub fn with_raw_file(raw: impl AsRef) -> Result { + if !raw.as_ref().exists() { + return Err(TypeGenError::VmlinuxNotFound); + } + + let raw_str = raw.as_ref().to_str().ok_or(TypeGenError::InvalidPath)?; + + let cpath = CString::new(raw_str).unwrap(); + let btfptr = unsafe { btf__parse_raw(cpath.as_ptr()) }; + if (btfptr as isize) < 0 { + return Err(TypeGenError::VmlinuxParsingError); + } + + Ok(VmlinuxBtfDump { + allowlist: None, + btfptr, + }) + } + /// Add regex `pattern` into allowlist of BTF types pub fn allowlist(mut self, pattern: &str) -> Self { let allowlist: &mut Vec = if let Some(allowlist) = &mut self.allowlist { @@ -251,12 +271,16 @@ pub fn get_custom_vmlinux_path() -> Option { Some(PathBuf::from(env::var(ENV_VMLINUX_PATH).ok()?)) } +/// Find a source of vmlinux BTF and parse it +/// +/// Using the returned `VmlinuxBtfDump`, BTF of the Linux kernel can be dumped +/// into `vmlinux.h`. pub fn vmlinux_btf_dump() -> Result { if let Some(path) = get_custom_vmlinux_path() { if path.to_str().unwrap() == "system" { VmlinuxBtfDump::with_system_default() } else { - VmlinuxBtfDump::with_elf_file(&path) + VmlinuxBtfDump::with_raw_file(&path).or_else(|_| VmlinuxBtfDump::with_elf_file(&path)) } } else { VmlinuxBtfDump::with_system_default() diff --git a/redbpf-probes/src/lib.rs b/redbpf-probes/src/lib.rs index 8cbb02ad..c7181c36 100644 --- a/redbpf-probes/src/lib.rs +++ b/redbpf-probes/src/lib.rs @@ -92,7 +92,9 @@ vmlinux is not required at all. # Possible `REDBPF_VMLINUX` values -1. A path to the custom vmlinux. e.g) `REDBPF_VMLINUX=/boot/my-vmlinux-5.11.0` +1. A path to the custom vmlinux file or raw BTF data file +- For example, `REDBPF_VMLINUX=/boot/my-vmlinux-5.11.0` specifies a path to vmlinux image. +- For example, `REDBPF_VMLINUX=/sys/kernel/btf/vmlinux` sets a path to raw BTF data file. 2. Special treatment for `REDBPF_VMLINUX=system`. If `system` is given, redBPF tries to probe vmlinux from the well-known system paths and uses it From bc737f6b6d1fc943a9af4d90fb7dde4cd12209f4 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 23 Dec 2021 00:46:19 +0900 Subject: [PATCH 039/107] Fix compile error on arm64+musl platforms Signed-off-by: Junyeong Jeong --- bpf-sys/src/type_gen.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bpf-sys/src/type_gen.rs b/bpf-sys/src/type_gen.rs index bf71c3d5..33fb29d7 100644 --- a/bpf-sys/src/type_gen.rs +++ b/bpf-sys/src/type_gen.rs @@ -19,8 +19,8 @@ system. */ use super::{ - btf, btf__free, btf__get_nr_types, btf__name_by_offset, btf__parse_elf, btf__type_by_id, - btf_dump, btf_dump__dump_type, btf_dump__free, btf_dump__new, btf_dump_opts, + btf, btf__free, btf__get_nr_types, btf__name_by_offset, btf__parse_elf, btf__parse_raw, + btf__type_by_id, btf_dump, btf_dump__dump_type, btf_dump__free, btf_dump__new, btf_dump_opts, libbpf_find_kernel_btf, vdprintf, }; use libc::{c_char, c_void}; @@ -261,7 +261,8 @@ unsafe extern "C" fn vdprintf_wrapper( unsafe extern "C" fn vdprintf_wrapper( ctx: *mut c_void, format: *const c_char, - va_list: super::__gnuc_va_list, + #[cfg(target_env = "musl")] va_list: super::__isoc_va_list, + #[cfg(not(target_env = "musl"))] va_list: super::__gnuc_va_list, ) { let rawfd_wrapper = &*(ctx as *mut RawFdWrapper); vdprintf(rawfd_wrapper.0, format, va_list); From 11f301260c0c9812da926865e8c7678df10867f2 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sat, 11 Dec 2021 21:46:08 +0900 Subject: [PATCH 040/107] Build RedBPF on aarch64 as well as x86_64 docker containers Signed-off-by: Junyeong Jeong --- .github/workflows/build-test.yml | 392 ++++++++++++++++++++++++++++++- README.md | 8 +- 2 files changed, 387 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 01d488f5..f1131ea4 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -14,33 +14,202 @@ on: env: BASE_IMAGE: ghcr.io/${{ github.repository_owner }}/foniod-build + REDBPF_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/redbpf-build jobs: - ubuntu-2104-build-test: + build-on-x86_64-ubuntu-2104: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Build host info + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info run: | uname -a cat /etc/os-release - - name: Initialize git submodules + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on x86_64 Ubuntu 21.04 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-x86_64-ubuntu21.04 \ + bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' + - + name: Build RedBPF with vmlinux on x86_64 Ubuntu 21.04 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-x86_64-ubuntu21.04 \ + /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' + + build-on-x86_64-debian11: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on x86_64 Debian 11 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-x86_64-debian11 \ + /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' + - + name: Build RedBPF with vmlinux on x86_64 Debian 11 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-x86_64-debian11 \ + /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' + + build-on-x86_64-fedora35: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on x86_64 Fedora 35 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-x86_64-fedora35 \ + /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + - + name: Build RedBPF with vmlinux on x86_64 Fedora 35 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-x86_64-fedora35 \ + /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux \ + && cargo clean \ + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + + build-on-x86_64-alpine315: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on x86_64 Alpine 3.15 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-x86_64-alpine3.15 \ + /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && export RUSTFLAGS=-Ctarget-feature=-crt-static \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' + + build-on-x86_64-archlinux: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Initialize git submodules run: | git submodule update --init --recursive - - name: Run RedBPF build with kernel headers on ubuntu-21.04 container + - + name: Build RedBPF with the kernel headers on x86_64 Arch Linux run: | docker run --privileged \ -v $PWD:/build \ -w /build \ - $BASE_IMAGE:latest-ubuntu-21.04 \ - /bin/bash -c 'cargo clean && export KERNEL_VERSION=$(ls --indicator-style=none /lib/modules/) && echo KERNEL_VERSION=$KERNEL_VERSION && cargo build && cargo build --examples --features=kernel5_8' - - name: Run RedBPF build with vmlinux on ubuntu-21.04 container + $REDBPF_IMAGE_NAME:latest-x86_64-archlinux \ + /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + - + name: Build RedBPF with vmlinux on x86_64 Arch Linux run: | docker run --privileged \ -v $PWD:/build \ -w /build \ - $BASE_IMAGE:latest-ubuntu-21.04 \ - /bin/bash -c 'cargo clean && /lib/modules/*/build/scripts/extract-vmlinux /boot/vmlinuz > /boot/vmlinux && export REDBPF_VMLINUX=/boot/vmlinux && cargo build && cargo build --examples --features=kernel5_8' + $REDBPF_IMAGE_NAME:latest-x86_64-archlinux \ + /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux \ + && cargo clean \ + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' ubuntu-2004-build-test: runs-on: ubuntu-latest @@ -128,15 +297,216 @@ jobs: $BASE_IMAGE:latest-alpine \ sh -c 'cargo clean && export KERNEL_VERSION=$(ls --indicator-style=none /lib/modules/) RUSTFLAGS=-Ctarget-feature=-crt-static && echo KERNEL_VERSION=$KERNEL_VERSION && cargo +1.51 build --no-default-features --features llvm11 && cargo +1.51 build --no-default-features --features llvm11 --examples' + build-on-aarch64-ubuntu-2104: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + with: + image: tonistiigi/binfmt:latest + platforms: linux/arm64 + - + name: Available platforms + run: echo ${{ steps.qemu.outputs.platforms }} + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on aarch64 Ubuntu 21.04 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-aarch64-ubuntu21.04 \ + /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' + - + name: Build RedBPF with vmlinux on aarch64 Ubuntu 21.04 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-aarch64-ubuntu21.04 \ + /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux-btf \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' + + build-on-aarch64-debian11: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + with: + image: tonistiigi/binfmt:latest + platforms: linux/arm64 + - + name: Available platforms + run: echo ${{ steps.qemu.outputs.platforms }} + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on aarch64 Debian 11 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-aarch64-debian11 \ + /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' + - + name: Build RedBPF with vmlinux on aarch64 Debian 11 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-aarch64-debian11 \ + /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux-btf \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' + + build-on-aarch64-fedora35: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + with: + image: tonistiigi/binfmt:latest + platforms: linux/arm64 + - + name: Available platforms + run: echo ${{ steps.qemu.outputs.platforms }} + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on aarch64 Fedora 35 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-aarch64-fedora35 \ + /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + - + name: Build RedBPF with vmlinux on aarch64 Fedora 35 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-aarch64-fedora35 \ + /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux-btf \ + && cargo clean \ + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + + build-on-aarch64-alpine315: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + with: + image: tonistiigi/binfmt:latest + platforms: linux/arm64 + - + name: Available platforms + run: echo ${{ steps.qemu.outputs.platforms }} + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on aarch64 Alpine 3.15 + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-aarch64-alpine3.15 \ + /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && export RUSTFLAGS=-Ctarget-feature=-crt-static \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' + publish: runs-on: ubuntu-latest if: startsWith( github.ref, 'refs/tags/v') needs: + - build-on-x86_64-ubuntu-2104 + - build-on-x86_64-debian11 + - build-on-x86_64-fedora35 + - build-on-x86_64-alpine315 + - build-on-x86_64-archlinux - fedora-34-build-test - - ubuntu-2104-build-test - ubuntu-2004-build-test - ubuntu-1804-build-test - alpine-314-build-test + - build-on-aarch64-ubuntu-2104 + - build-on-aarch64-debian11 + - build-on-aarch64-fedora35 + - build-on-aarch64-alpine315 steps: - uses: actions/checkout@v2 diff --git a/README.md b/README.md index 53125025..0fda50f4 100644 --- a/README.md +++ b/README.md @@ -155,8 +155,12 @@ Then install the dependencies running: ## Build images -You can refer to `Dockerfile`s that are ready for building redBPF and foniod: -[build-images](https://github.com/foniod/build-images) +You can refer to various `Dockerfile`s that contain minimal necessary packages +to build `RedBPF` properly: [Dockerfiles for +RedBPF](https://github.com/foniod/build-images/redbpf) + +If you want docker images that are prepared to build `foniod` then refer to +this: [Dockerfiles for foniod](https://github.com/foniod/build-images) # Getting started From a1e30a1b15246406424c0a4d3241c450e3750c40 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sun, 12 Dec 2021 22:05:31 +0900 Subject: [PATCH 041/107] Fix build failure of fedora 35 Signed-off-by: Junyeong Jeong --- redbpf-probes/build.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/redbpf-probes/build.rs b/redbpf-probes/build.rs index 301abfc5..bf73ef4a 100644 --- a/redbpf-probes/build.rs +++ b/redbpf-probes/build.rs @@ -253,7 +253,6 @@ fn generate_bindings_vmlinux() -> Result<()> { .allowlist_var("^IPPROTO_.*") // for additional IPPROTO_* .allowlist_var("^SOCK_.*") .allowlist_type("^bpf_map_def$") - .allowlist_type("^bpf_timer$") .blocklist_type("_bindgen_ty.*") // avoid unncessary collision .parse_callbacks(Box::new(HideEnum)) // hide enums because they are included before .generate() From 9bdd7daaa3803eed385dd015ae96905e3af2b03a Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 21 Dec 2021 23:09:37 +0900 Subject: [PATCH 042/107] In build test, use current stable rust Signed-off-by: Junyeong Jeong --- .github/workflows/build-test.yml | 242 +++++++++++++++++++------------ 1 file changed, 146 insertions(+), 96 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f1131ea4..b3c8aec7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -42,9 +42,9 @@ jobs: bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build \ - && cargo build --bin cargo-bpf \ - && cargo build --features=kernel5_8 --examples' + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' - name: Build RedBPF with vmlinux on x86_64 Ubuntu 21.04 run: | @@ -54,9 +54,9 @@ jobs: $REDBPF_IMAGE_NAME:latest-x86_64-ubuntu21.04 \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux \ && cargo clean \ - && cargo build \ - && cargo build --bin cargo-bpf \ - && cargo build --features=kernel5_8 --examples' + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' build-on-x86_64-debian11: runs-on: ubuntu-latest @@ -83,9 +83,9 @@ jobs: /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build \ - && cargo build --bin cargo-bpf \ - && cargo build --features=kernel5_8 --examples' + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' - name: Build RedBPF with vmlinux on x86_64 Debian 11 run: | @@ -95,9 +95,9 @@ jobs: $REDBPF_IMAGE_NAME:latest-x86_64-debian11 \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux \ && cargo clean \ - && cargo build \ - && cargo build --bin cargo-bpf \ - && cargo build --features=kernel5_8 --examples' + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' build-on-x86_64-fedora35: runs-on: ubuntu-latest @@ -166,9 +166,9 @@ jobs: && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && export RUSTFLAGS=-Ctarget-feature=-crt-static \ && cargo clean \ - && cargo build \ - && cargo build --bin cargo-bpf \ - && cargo build --features=kernel5_8 --examples' + && cargo build --no-default-features --features=llvm12 \ + && cargo build --no-default-features --features=llvm12,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm12,kernel5_8 --examples' build-on-x86_64-archlinux: runs-on: ubuntu-latest @@ -211,91 +211,63 @@ jobs: && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' - ubuntu-2004-build-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Build host info - run: | - uname -a - cat /etc/os-release - - name: Initialize git submodules - run: | - git submodule update --init --recursive - - name: Run RedBPF build on ubuntu-20.04 container - run: | - docker run --privileged \ - -v $PWD:/build \ - -w /build \ - $BASE_IMAGE:latest-ubuntu-20.04 \ - /bin/bash -c 'cargo clean && export KERNEL_VERSION=$(ls --indicator-style=none /lib/modules/) && echo KERNEL_VERSION=$KERNEL_VERSION && cargo build && cargo build --examples' - - ubuntu-1804-build-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Build host info - run: | - uname -a - cat /etc/os-release - - name: Initialize git submodules - run: | - git submodule update --init --recursive - - name: Run RedBPF build with kernel v4.19 on ubuntu-18.04 container - run: | - docker run --privileged \ - -v $PWD:/build \ - -w /build \ - $BASE_IMAGE:latest-ubuntu-18.04 \ - /bin/bash -c 'cargo clean && export KERNEL_VERSION=$(ls --indicator-style=none /lib/modules/) && echo KERNEL_VERSION=$KERNEL_VERSION && cargo build && cargo build --examples' - - fedora-34-build-test: + build-on-x86_64-ubuntu-2004: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Build host info + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info run: | uname -a cat /etc/os-release - - name: Initialize git submodules + - + name: Initialize git submodules run: | git submodule update --init --recursive - - name: Run RedBPF build with kernel headers on fedora-34 container - # Run 'make prepare' to prevent generated/autoconf.h from being removed - # when 'make -qp' is executed by bpf_sys::headers::build_kernel_version - run: | - docker run --privileged \ - -v $PWD:/build \ - -w /build \ - $BASE_IMAGE:latest-fedora-34 \ - /bin/bash -c 'make -C /lib/modules/*/build prepare; cargo clean && export KERNEL_VERSION=$(ls --indicator-style=none /lib/modules/) && echo KERNEL_VERSION=$KERNEL_VERSION && cargo build && cargo build --examples' - - name: Run RedBPF build with vmlinux on fedora-34 container + - + name: Build RedBPF with the kernel headers on x86_64 Ubuntu 20.04 LTS run: | docker run --privileged \ -v $PWD:/build \ -w /build \ - $BASE_IMAGE:latest-fedora-34 \ - /bin/bash -c 'cargo clean && /lib/modules/*/build/scripts/extract-vmlinux /lib/modules/*/vmlinuz > /boot/vmlinux && export REDBPF_VMLINUX=/boot/vmlinux && cargo build && cargo build --examples' + $REDBPF_IMAGE_NAME:latest-x86_64-ubuntu20.04 \ + bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13 --examples' - alpine-314-build-test: - name: Alpine 3.14 + build-on-x86_64-ubuntu-1804: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Build host info + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info run: | uname -a cat /etc/os-release - - name: Initialize git submodules + - + name: Initialize git submodules run: | git submodule update --init --recursive - - name: Run RedBPF build on alpine-3.14 container + - + name: Build RedBPF with the kernel headers on x86_64 Ubuntu 18.04 LTS run: | docker run --privileged \ -v $PWD:/build \ -w /build \ - $BASE_IMAGE:latest-alpine \ - sh -c 'cargo clean && export KERNEL_VERSION=$(ls --indicator-style=none /lib/modules/) RUSTFLAGS=-Ctarget-feature=-crt-static && echo KERNEL_VERSION=$KERNEL_VERSION && cargo +1.51 build --no-default-features --features llvm11 && cargo +1.51 build --no-default-features --features llvm11 --examples' + $REDBPF_IMAGE_NAME:latest-x86_64-ubuntu18.04 \ + bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13 --examples' build-on-aarch64-ubuntu-2104: runs-on: ubuntu-latest @@ -332,9 +304,9 @@ jobs: /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build \ - && cargo build --bin cargo-bpf \ - && cargo build --features=kernel5_8 --examples' + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' - name: Build RedBPF with vmlinux on aarch64 Ubuntu 21.04 run: | @@ -344,9 +316,9 @@ jobs: $REDBPF_IMAGE_NAME:latest-aarch64-ubuntu21.04 \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux-btf \ && cargo clean \ - && cargo build \ - && cargo build --bin cargo-bpf \ - && cargo build --features=kernel5_8 --examples' + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' build-on-aarch64-debian11: runs-on: ubuntu-latest @@ -383,9 +355,9 @@ jobs: /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build \ - && cargo build --bin cargo-bpf \ - && cargo build --features=kernel5_8 --examples' + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' - name: Build RedBPF with vmlinux on aarch64 Debian 11 run: | @@ -395,9 +367,9 @@ jobs: $REDBPF_IMAGE_NAME:latest-aarch64-debian11 \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux-btf \ && cargo clean \ - && cargo build \ - && cargo build --bin cargo-bpf \ - && cargo build --features=kernel5_8 --examples' + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' build-on-aarch64-fedora35: runs-on: ubuntu-latest @@ -486,9 +458,87 @@ jobs: && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && export RUSTFLAGS=-Ctarget-feature=-crt-static \ && cargo clean \ - && cargo build \ - && cargo build --bin cargo-bpf \ - && cargo build --features=kernel5_8 --examples' + && cargo build --no-default-features --features=llvm12 \ + && cargo build --no-default-features --features=llvm12,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm12,kernel5_8 --examples' + + build-on-aarch64-ubuntu-2004: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + with: + image: tonistiigi/binfmt:latest + platforms: linux/arm64 + - + name: Available platforms + run: echo ${{ steps.qemu.outputs.platforms }} + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on aarch64 Ubuntu 20.04 LTS + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-aarch64-ubuntu20.04 \ + /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13 --examples' + + build-on-aarch64-ubuntu-1804: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + with: + image: tonistiigi/binfmt:latest + platforms: linux/arm64 + - + name: Available platforms + run: echo ${{ steps.qemu.outputs.platforms }} + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on aarch64 Ubuntu 18.04 LTS + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-aarch64-ubuntu18.04 \ + /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build --no-default-features --features=llvm13 \ + && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ + && cargo build --no-default-features --features=llvm13 --examples' publish: runs-on: ubuntu-latest @@ -499,14 +549,14 @@ jobs: - build-on-x86_64-fedora35 - build-on-x86_64-alpine315 - build-on-x86_64-archlinux - - fedora-34-build-test - - ubuntu-2004-build-test - - ubuntu-1804-build-test - - alpine-314-build-test + - build-on-x86_64-ubuntu-2004 + - build-on-x86_64-ubuntu-1804 - build-on-aarch64-ubuntu-2104 - build-on-aarch64-debian11 - build-on-aarch64-fedora35 - build-on-aarch64-alpine315 + - build-on-aarch64-ubuntu-2004 + - build-on-aarch64-ubuntu-1804 steps: - uses: actions/checkout@v2 From 0c959b3bab2ac9791322555240c3c117fb85e919 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Thu, 23 Dec 2021 14:22:26 +0900 Subject: [PATCH 043/107] Split aarch64-fedora35 into two parts to reduce build test time Signed-off-by: Junyeong Jeong --- .github/workflows/build-test.yml | 33 ++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b3c8aec7..40192e3c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -371,7 +371,9 @@ jobs: && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' - build-on-aarch64-fedora35: + # Split aarch64-fedora35 into two parts because its running time exceeds 6 + # hours, the maximum time for build test. + build-on-aarch64-fedora35-header: runs-on: ubuntu-latest steps: - @@ -409,6 +411,32 @@ jobs: && cargo build --no-default-features --features=llvm13 \ && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + + build-on-aarch64-fedora35-vmlinux: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + with: + image: tonistiigi/binfmt:latest + platforms: linux/arm64 + - + name: Available platforms + run: echo ${{ steps.qemu.outputs.platforms }} + - + name: Initialize git submodules + run: | + git submodule update --init --recursive - name: Build RedBPF with vmlinux on aarch64 Fedora 35 run: | @@ -553,7 +581,8 @@ jobs: - build-on-x86_64-ubuntu-1804 - build-on-aarch64-ubuntu-2104 - build-on-aarch64-debian11 - - build-on-aarch64-fedora35 + - build-on-aarch64-fedora35-header + - build-on-aarch64-fedora35-vmlinux - build-on-aarch64-alpine315 - build-on-aarch64-ubuntu-2004 - build-on-aarch64-ubuntu-1804 From f4af39854e49838cc5e76b3129f443879ac9765f Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 21 Dec 2021 01:02:02 +0900 Subject: [PATCH 044/107] Find if get or get_mut method of RedBPF maps are called Previously cargo-bpf raises an error if the alignment of value is greater than 8 bytes. But this is inappropriate to some BPF program that never calls get/get_mut. Since calling these methods creates references of possibly misaligned data, it is okay to permit values that have alignment greater than 8 bytes if get/get_mut is not called anywhere in BPF programs. Find if get/get_mut method is called by inspecting LLVM bitcode and raise an error. Signed-off-by: Junyeong Jeong --- cargo-bpf/src/llvm.rs | 259 ++++++++++++++++++++++++++-- examples/example-userspace/build.rs | 8 +- 2 files changed, 245 insertions(+), 22 deletions(-) diff --git a/cargo-bpf/src/llvm.rs b/cargo-bpf/src/llvm.rs index 2b6cb6ce..8de69eda 100644 --- a/cargo-bpf/src/llvm.rs +++ b/cargo-bpf/src/llvm.rs @@ -41,6 +41,7 @@ cfg_if::cfg_if! { use anyhow::{anyhow, Result}; use llvm_sys::bit_writer::LLVMWriteBitcodeToFile; use llvm_sys::core::*; +use llvm_sys::debuginfo::*; use llvm_sys::ir_reader::LLVMParseIRInContext; use llvm_sys::prelude::*; use llvm_sys::support::LLVMParseCommandLineOptions; @@ -49,6 +50,7 @@ use llvm_sys::target_machine::*; use llvm_sys::transforms::ipo::LLVMAddAlwaysInlinerPass; use llvm_sys::transforms::pass_manager_builder::*; use llvm_sys::{LLVMAttributeFunctionIndex, LLVMInlineAsmDialect::*}; +use llvm_sys::{LLVMOpcode, LLVMTypeKind, LLVMValueKind}; use std::ffi::{CStr, CString}; use std::os::raw::c_char; use std::path::Path; @@ -128,32 +130,251 @@ unsafe fn inject_exit_call(context: LLVMContextRef, func: LLVMValueRef, builder: LLVMBuildCall(builder, exit, ptr::null_mut(), 0, c_str.as_ptr()); } +/// Find debugger intrinsics handling methods of RedBPF maps. The the type +/// string of methods such as `get`, `get_mut`, `get_val`, `set`, `delete` are +/// returned. +unsafe fn find_redbpf_map_method_type_str(dbg_var_inst: LLVMValueRef) -> Option { + let called_val = LLVMGetCalledValue(dbg_var_inst); + if called_val.is_null() { + return None; + } + let mut len: usize = 0; + let cname = LLVMGetValueName2(called_val, &mut len as *mut _); + let cv_name = CStr::from_ptr(cname).to_str().unwrap(); + if cv_name != "llvm.dbg.value" { + return None; + } + // llvm.dbg.value requires three arguments + if LLVMGetNumArgOperands(dbg_var_inst) != 3 { + return None; + } + + // The first argument of llvm.dbg.value is new value + let new_value = LLVMGetArgOperand(dbg_var_inst, 0); + if LLVMGetNumOperands(new_value) == 0 { + return None; + } + let undef = LLVMGetOperand(new_value, 0); + match LLVMGetValueKind(undef) { + LLVMValueKind::LLVMUndefValueValueKind => {} + _ => { + return None; + } + } + let undef_type = LLVMTypeOf(undef); + match LLVMGetTypeKind(undef_type) { + LLVMTypeKind::LLVMPointerTypeKind => {} + _ => { + return None; + } + } + let cname = LLVMPrintTypeToString(undef_type); + let undef_type_str = CStr::from_ptr(cname).to_str().unwrap(); + if !(undef_type_str.starts_with("%\"redbpf_probes::maps::") && undef_type_str.ends_with("\"*")) + { + return None; + } + // e.g., `redbpf_probes::maps::HashMap` + let map_type_str = &undef_type_str[2..undef_type_str.len() - 2]; + + // The second argument of llvm.dbg.value is DI local variable. + let di_local_var = LLVMGetArgOperand(dbg_var_inst, 1); + let dilocvar_meta = LLVMValueAsMetadata(di_local_var); + match LLVMGetMetadataKind(dilocvar_meta) { + LLVMMetadataKind::LLVMDILocalVariableMetadataKind => {} + _ => { + return None; + } + } + let method_scope = LLVMDIVariableGetScope(dilocvar_meta); + match LLVMGetMetadataKind(method_scope) { + LLVMMetadataKind::LLVMDISubprogramMetadataKind => {} + _ => { + return None; + } + } + // e.g., `get_mut` + let mut len: usize = 0; + let cname = LLVMDITypeGetName(method_scope, &mut len as *mut _); + + // e.g., `redbpf_probes::maps::HashMap::get_mut` + Some(format!( + "{}::{}", + map_type_str, + String::from_utf8_lossy(slice::from_raw_parts(cname as *const _, len)) + )) +} + +unsafe fn find_map_calling_bpf_map_lookup_elem(call_inst: LLVMValueRef) -> Option { + let called_val = LLVMGetCalledValue(call_inst); + if called_val.is_null() { + return None; + } + match LLVMGetValueKind(called_val) { + LLVMValueKind::LLVMConstantExprValueKind => {} + _ => { + return None; + } + } + match LLVMGetTypeKind(LLVMTypeOf(called_val)) { + LLVMTypeKind::LLVMPointerTypeKind => {} + _ => { + return None; + } + } + match LLVMGetConstOpcode(called_val) { + LLVMOpcode::LLVMIntToPtr => {} + _ => { + return None; + } + } + let const_int = LLVMGetOperand(called_val, 0); + match LLVMGetValueKind(const_int) { + LLVMValueKind::LLVMConstantIntValueKind => {} + _ => { + return None; + } + } + // `bpf_map_lookup_elem` BPF helper is 1 + if LLVMConstIntGetZExtValue(const_int) != 1 { + return None; + } + // `bpf_map_lookup_elem` requires two arguments + if LLVMGetNumArgOperands(call_inst) != 2 { + return None; + } + // The first argument of `bpf_map_lookup_elem` is a pointer to map def + let map_def = LLVMGetArgOperand(call_inst, 0); + match LLVMGetValueKind(map_def) { + LLVMValueKind::LLVMConstantExprValueKind => {} + _ => { + return None; + } + } + match LLVMGetConstOpcode(map_def) { + LLVMOpcode::LLVMGetElementPtr => {} + _ => { + return None; + } + } + // Get a map that is the container of the map definition + let map = LLVMGetOperand(map_def, 0); + match LLVMGetValueKind(map) { + LLVMValueKind::LLVMGlobalVariableValueKind => {} + _ => { + return None; + } + } + // Get name of the map + let mut len: usize = 0; + let cname = LLVMGetValueName2(map, &mut len as *mut _); + let map_variable_name = + String::from_utf8_lossy(slice::from_raw_parts(cname as *const _, len)).to_string(); + Some(map_variable_name) +} + +/// Check if the alignment of value of map exceeds 8 bytes and `get` or +/// `get_mut` method is called to create a reference of the possibly misaligned +/// value data. unsafe fn check_map_value_alignment(_context: LLVMContextRef, module: LLVMModuleRef) -> Result<()> { + let mut get_called_maps = Vec::<(String, String, String)>::new(); // (map_variable_name, calling_function_name, get_method_name) + let mut func = LLVMGetFirstFunction(module); + while !func.is_null() { + let mut block = LLVMGetFirstBasicBlock(func); + let mut map_method_type_str = None; + while !block.is_null() { + let mut inst = LLVMGetFirstInstruction(block); + while !inst.is_null() { + // inspect debugger intrinsics + if !LLVMIsADbgVariableIntrinsic(inst).is_null() { + // find debugger intrinsics of `get` or `get_mut` methods + // of RedBPF maps such as + // `redbpf_probes::maps::HashMap::get_mut` + if let Some(name) = find_redbpf_map_method_type_str(inst) { + map_method_type_str = Some(name); + } + // inspect normal function call + } else if !LLVMIsACallInst(inst).is_null() && LLVMIsAIntrinsicInst(inst).is_null() { + // find the calling instruction to the `bpf_map_lookup_elem` BPF helper function. + if let Some(map_var_name) = find_map_calling_bpf_map_lookup_elem(inst) { + if let Some(method_name) = map_method_type_str.take() { + // Don't care the `get_val` method because it does + // not create any reference of misaligned value + // data. + if method_name.contains("::get<") || method_name.contains("::get_mut<") + { + let mut len: usize = 0; + let cname = LLVMGetValueName2(func, &mut len as *mut _); + let calling_func_name = String::from_utf8_lossy( + slice::from_raw_parts(cname as *const _, len), + ) + .to_string(); + + get_called_maps.push(( + map_var_name, + calling_func_name, + method_name, + )); + } + } + } + } + inst = LLVMGetNextInstruction(inst); + } + block = LLVMGetNextBasicBlock(block); + } + func = LLVMGetNextFunction(func); + } + const PROBES_ALIGNMENT_MAX: usize = 8; + const ALIGN_PREFIX: &str = "MAP_VALUE_ALIGN_"; let mut global = LLVMGetFirstGlobal(module); - while !global.is_null() { + 'next_global: while !global.is_null() { let mut size: libc::size_t = 0; let c_name = LLVMGetValueName2(global, &mut size as *mut _); - if !c_name.is_null() { - let name = String::from_utf8_lossy(slice::from_raw_parts(c_name as *const u8, size)); - let align_prefix = "MAP_VALUE_ALIGN_"; - if name.starts_with(align_prefix) { - let align = LLVMGetAlignment(global) as usize; - if align > PROBES_ALIGNMENT_MAX { - let map_name = &name[align_prefix.len()..]; - let msg = format!( - "error: Illegal alignment the value of the map! map={} value-alignment={}. - In kernel context, the Linux kernel does not guarantee alignment of the value stored in the - BPF map when the alignment of the value is greater than 8 bytes. - Since it is undefined behavior to create references or dereference pointers of unaligned - data in Rust, BPF programs should avoid using types whose alignment is greater than 8 bytes.", - map_name, align - ); - return Err(anyhow!(msg)); + if c_name.is_null() { + global = LLVMGetNextGlobal(global); + continue 'next_global; + } + let name = String::from_utf8_lossy(slice::from_raw_parts(c_name as *const _, size)); + if !name.starts_with(ALIGN_PREFIX) { + global = LLVMGetNextGlobal(global); + continue 'next_global; + } + let align = LLVMGetAlignment(global) as usize; + if align <= PROBES_ALIGNMENT_MAX { + global = LLVMGetNextGlobal(global); + continue 'next_global; + } + let map_name = &name[ALIGN_PREFIX.len()..]; + let get_callers = get_called_maps + .iter() + .filter_map(|(map_var_name, calling_func_name, get_method_name)| { + if map_var_name == map_name { + Some((calling_func_name, get_method_name)) + } else { + None } - } + }) + .collect::>(); + if get_callers.len() == 0 { + global = LLVMGetNextGlobal(global); + continue 'next_global; + } + let mut emsg = "In BPF programs, it is prohibited to call `get` or `get_mut` methods of maps of which value has the alignment greater than 8 bytes.\n".to_string(); + emsg.push_str("Because in kernel context, it is not guaranteed for the values to be stored at the correct alignment if the alignment is greater than 8 bytes.\n"); + emsg.push_str("Since it is undefined behavior in Rust to create references or to dereference pointers of unaligned data, BPF programs should not call `get` or `get_mut` methods that create references of the possibly misaligned data.\n"); + emsg.push_str(&format!( + "\tmap variable name: {} alignment of value: {} bytes\n", + map_name, align + )); + for (calling_func_name, get_method_name) in get_callers.iter() { + emsg.push_str(&format!( + "\tcalling function name: {} called get method type: {}\n", + calling_func_name, get_method_name + )); } - global = LLVMGetNextGlobal(global); + return Err(anyhow!(emsg)); } Ok(()) diff --git a/examples/example-userspace/build.rs b/examples/example-userspace/build.rs index 285861c8..3af3c31b 100644 --- a/examples/example-userspace/build.rs +++ b/examples/example-userspace/build.rs @@ -19,14 +19,16 @@ fn main() { if env::var("CARGO_FEATURE_KERNEL5_8").is_ok() { features.push(String::from("kernel5_8")); } - cargo_bpf::build_with_features( + if let Err(e) = cargo_bpf::build_with_features( &cargo, &probes, &target.join("target"), &mut Vec::new(), &features, - ) - .expect("couldn't compile probes"); + ) { + eprintln!("{}", e); + panic!("probes build failed"); + } cargo_bpf::probe_files(&probes) .expect("couldn't list probe files") From 845fffa242d2b31a75fc61d251b43805ddf87c6f Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sun, 9 Jan 2022 16:40:38 +0900 Subject: [PATCH 045/107] Bump version 2.3.0 Signed-off-by: Junyeong Jeong --- TUTORIAL.md | 2 +- bpf-sys/Cargo.toml | 2 +- cargo-bpf/Cargo.toml | 8 ++++---- cargo-bpf/src/new.rs | 6 +++--- redbpf-macros/Cargo.toml | 4 ++-- redbpf-probes/Cargo.toml | 8 ++++---- redbpf/Cargo.toml | 4 ++-- scripts/bump-version.sh | 3 +-- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index 4fe9c313..deefc936 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -367,7 +367,7 @@ Cargo.toml probes/ src/ target/ Open `Cargo.toml` with your favorite editor and add dependencies: ```toml -redbpf = { version = "2.1.0", features = ["load"] } +redbpf = { version = "2.3.0", features = ["load"] } tokio = { version = "1.0", features = ["rt", "signal", "time", "io-util", "net", "sync"] } tracing-subscriber = "0.2" tracing = "0.1" diff --git a/bpf-sys/Cargo.toml b/bpf-sys/Cargo.toml index cd4852a4..21a53da1 100644 --- a/bpf-sys/Cargo.toml +++ b/bpf-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bpf-sys" -version = "2.2.0" +version = "2.3.0" description = "Bindings for libbpf" repository = "https://github.com/foniod/redbpf" homepage = "https://foniod.org" diff --git a/cargo-bpf/Cargo.toml b/cargo-bpf/Cargo.toml index 4eb95cdd..585b7111 100644 --- a/cargo-bpf/Cargo.toml +++ b/cargo-bpf/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "cargo-bpf" -version = "2.2.0" +version = "2.3.0" description = "Cargo plugin to manage eBPF probes using redbpf" repository = "https://github.com/foniod/redbpf" homepage = "https://foniod.org" -documentation = "https://docs.rs/cargo-bpf/2.2.0/cargo_bpf_lib/" +documentation = "https://docs.rs/cargo-bpf/latest/cargo_bpf_lib/" authors = ["Alessandro Decina ", "Peter Parkanyi ", "Junyeong Jeong "] edition = "2018" keywords = ["cargo", "redbpf", "bpf", "plugin", "subcommand"] @@ -22,8 +22,8 @@ required-features = ["command-line"] clap = { version = "2.33", optional = true } bindgen = {version = "0.59.2", default-features = false, features = ["runtime"], optional = true} toml_edit = { version = "0.2", optional = true } -bpf-sys = { version = "2.2.0", path = "../bpf-sys", optional = true } -redbpf = { version = "2.2.0", path = "../redbpf", default-features = false, optional = true } +bpf-sys = { version = "2.3.0", path = "../bpf-sys", optional = true } +redbpf = { version = "2.3.0", path = "../redbpf", default-features = false, optional = true } futures = { version = "0.3", optional = true } tokio = { version = "^1.0.1", features = ["rt", "macros", "signal"], optional = true } hexdump = { version = "0.1", optional = true } diff --git a/cargo-bpf/src/new.rs b/cargo-bpf/src/new.rs index f196a25d..16c2a99c 100644 --- a/cargo-bpf/src/new.rs +++ b/cargo-bpf/src/new.rs @@ -31,11 +31,11 @@ edition = '2018' [dependencies] cty = "0.2" -redbpf-macros = "2.1.0" -redbpf-probes = "2.1.0" +redbpf-macros = "2.3.0" +redbpf-probes = "2.3.0" [build-dependencies] -cargo-bpf = {{ version = "2.1.0", default-features = false }} +cargo-bpf = {{ version = "2.3.0", default-features = false }} [features] default = [] diff --git a/redbpf-macros/Cargo.toml b/redbpf-macros/Cargo.toml index 94721c14..b1f4474f 100644 --- a/redbpf-macros/Cargo.toml +++ b/redbpf-macros/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/foniod/redbpf" homepage = "https://foniod.org" documentation = "https://docs.rs/redbpf-macros/" authors = ["Alessandro Decina ", "Peter Parkanyi ", "Junyeong Jeong "] -version = "2.2.0" +version = "2.3.0" edition = '2018' keywords = ["bpf", "ebpf", "redbpf"] license = "MIT OR Apache-2.0" @@ -24,6 +24,6 @@ rustc_version = "0.3.0" [dev-dependencies] # redbpf-probes is needed by doctests -redbpf-probes = { version = "2.2.0", path = "../redbpf-probes" } +redbpf-probes = { version = "2.3.0", path = "../redbpf-probes" } # memoffset is needed by doctests memoffset = "0.6" diff --git a/redbpf-probes/Cargo.toml b/redbpf-probes/Cargo.toml index f9648b7e..be2d9823 100644 --- a/redbpf-probes/Cargo.toml +++ b/redbpf-probes/Cargo.toml @@ -5,19 +5,19 @@ repository = "https://github.com/foniod/redbpf" homepage = "https://foniod.org" documentation = "https://docs.rs/redbpf-probes/" authors = ["Alessandro Decina ", "Peter Parkanyi ", "Junyeong Jeong "] -version = "2.2.0" +version = "2.3.0" edition = '2018' keywords = ["bpf", "ebpf", "redbpf"] license = "MIT OR Apache-2.0" [dependencies] cty = "0.2" -redbpf-macros = { version = "2.2.0", path = "../redbpf-macros" } +redbpf-macros = { version = "2.3.0", path = "../redbpf-macros" } ufmt = { version = "0.1.0", default-features = false } [build-dependencies] -cargo-bpf = { version = "2.2.0", path = "../cargo-bpf", default-features = false, features = ["bindings"] } -bpf-sys = { version = "2.2.0", path = "../bpf-sys" } +cargo-bpf = { version = "2.3.0", path = "../cargo-bpf", default-features = false, features = ["bindings"] } +bpf-sys = { version = "2.3.0", path = "../bpf-sys" } syn = {version = "1.0", default-features = false, features = ["parsing", "visit"] } quote = "1.0" glob = "0.3.0" diff --git a/redbpf/Cargo.toml b/redbpf/Cargo.toml index bcfbffca..89ea0bae 100644 --- a/redbpf/Cargo.toml +++ b/redbpf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "redbpf" -version = "2.2.0" +version = "2.3.0" description = "eBPF build and userspace runtime library" repository = "https://github.com/foniod/redbpf" homepage = "https://foniod.org" @@ -15,7 +15,7 @@ readme = "README.md" maintenance = { status = "actively-developed" } [dependencies] -bpf-sys = { path = "../bpf-sys", version = "2.2.0" } +bpf-sys = { path = "../bpf-sys", version = "2.3.0" } goblin = "0.4" zero = "0.1" libc = "0.2" diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 23f3919c..b0a184e1 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -11,8 +11,7 @@ NEW_VERSION="${1}" echo "Bumping version: ${NEW_VERSION}" find $PACKAGES -name Cargo.toml -type f -exec sed -i -r \ - -e "s/^version.*/version = \"$NEW_VERSION\"/" \ - -e 's@^(documentation *= *"https://docs.rs/[a-z-]+/)[^/]+@\1'$NEW_VERSION'@' {} \; + -e "s/^version.*/version = \"$NEW_VERSION\"/" {} \; find $PACKAGES -name Cargo.toml -type f -exec sed -i -e "s/^\(bpf-sys.*version = \)\"[^\"]*\"/\\1\"$NEW_VERSION\"/" {} \; find $PACKAGES -name Cargo.toml -type f -exec sed -i -e "s/^\(cargo-bpf.*version = \)\"[^\"]*\"/\\1\"$NEW_VERSION\"/" {} \; find $PACKAGES -name Cargo.toml -type f -exec sed -i -e "s/^\(redbpf.*version = \)\"[^\"]*\"/\\1\"$NEW_VERSION\"/" {} \; From 095fbe158ee4e44817252ad63089204f26caad9e Mon Sep 17 00:00:00 2001 From: sebastiaoamaro Date: Mon, 17 Jan 2022 14:04:45 +0000 Subject: [PATCH 046/107] replaced -fPIE with -fPIC Signed-off-by: sebastiaoamaro --- bpf-sys/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpf-sys/build.rs b/bpf-sys/build.rs index 4d580a00..d6e31261 100644 --- a/bpf-sys/build.rs +++ b/bpf-sys/build.rs @@ -48,7 +48,7 @@ fn main() { // 21.04, Alpine 3.14 also works fine with it if !Command::new("make") .args(format!("-C libbpf/src BUILD_STATIC_ONLY=1 OBJDIR={out_dir}/libbpf DESTDIR={out_dir} INCLUDEDIR= LIBDIR= UAPIDIR=", out_dir=env::var("OUT_DIR").unwrap()).split(" ")) - .arg("CFLAGS=-g -O2 -Werror -Wall -fPIE") + .arg("CFLAGS=-g -O2 -Werror -Wall -fPIC") .arg("install") .status() .expect("error on executing `make` command for building `libbpf` static library") From a8ac1df77fb350a3de9e49b2e352f6ef4a080b41 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Wed, 19 Jan 2022 00:56:19 +0900 Subject: [PATCH 047/107] Change default LLVM to 13 Save LLVM version that is linked to cargo-bpf at compile-time and compare the LLVM version with the LLVM of rustc at run-time. Make rustdoc for cargo-bpf succeed with llvm-sys-130. Signed-off-by: Junyeong Jeong --- cargo-bpf/Cargo.toml | 22 +++++----- cargo-bpf/build.rs | 60 +++++++++++++++++++++++++++ cargo-bpf/src/build.rs | 40 ++++++++++++++++++ cargo-bpf/src/llvm.rs | 39 +++++------------ cargo-bpf/src/main.rs | 2 +- examples/example-userspace/Cargo.toml | 4 -- redbpf-tools/Cargo.toml | 6 --- 7 files changed, 124 insertions(+), 49 deletions(-) create mode 100644 cargo-bpf/build.rs diff --git a/cargo-bpf/Cargo.toml b/cargo-bpf/Cargo.toml index 585b7111..06a3fa86 100644 --- a/cargo-bpf/Cargo.toml +++ b/cargo-bpf/Cargo.toml @@ -35,26 +35,28 @@ tempfile = { version = "3.1", optional = true} lazy_static = "1.0" glob = "0.3" anyhow = "1.0" -llvm-sys-100 = { version = "100", optional = true, package = "llvm-sys" } -llvm-sys-110 = { version = "110", optional = true, package = "llvm-sys" } -llvm-sys-120 = { version = "120", optional = true, package = "llvm-sys" } +# if `strict-versioning` feature is off, llvm-sys-130 can be linked to +# libLLVM13 and newer libraries. llvm-sys-130 = { version = "130", optional = true, package = "llvm-sys" } cfg-if = "1.0" rustversion = "1.0" +rustc_version = "0.4.0" +semver = "1.0.0" + +[build-dependencies] +regex = "1.0.0" +cfg-if = "1.0.0" [features] -default = ["command-line", "llvm12"] +default = ["command-line"] bindings = ["bpf-sys", "bindgen", "syn", "quote", "proc-macro2", "tempfile"] -build = ["bindings", "libc", "toml_edit", "redbpf"] -llvm10 = ["llvm-sys-100"] -llvm11 = ["llvm-sys-110"] -llvm12 = ["llvm-sys-120"] -llvm13 = ["llvm-sys-130"] +build = ["bindings", "libc", "toml_edit", "redbpf", "llvm-sys-130"] +docsrs-llvm = ["llvm-sys-130/no-llvm-linking", "llvm-sys-130/disable-alltargets-init"] build-c = [] command-line = ["build", "clap", "redbpf/load", "futures", "tokio", "hexdump"] [package.metadata.docs.rs] all-features = false no-default-features = true -features = ["command-line", "llvm10"] +features = ["command-line", "docsrs-llvm"] rustdoc-args = ["--cfg", "docsrs"] diff --git a/cargo-bpf/build.rs b/cargo-bpf/build.rs new file mode 100644 index 00000000..09718ff8 --- /dev/null +++ b/cargo-bpf/build.rs @@ -0,0 +1,60 @@ +use regex::Regex; +use std::env; +use std::process::Command; + +fn print_cargo_bpf_llvm_version() { + let config_path_re = Regex::new(r"DEP_LLVM_(\d+)_CONFIG_PATH").unwrap(); + let config_paths = env::vars() + .filter_map(|(key, value)| { + if config_path_re.is_match(&key) { + Some(value) + } else { + None + } + }) + .collect::>(); + if config_paths.is_empty() { + panic!("llvm-config not found"); + } else if config_paths.len() > 1 { + panic!("Multiple LLVMs are specified. Choose one LLVM version"); + } + + let llvm_config = &config_paths[0]; + let version_str = Command::new(llvm_config) + .arg("--version") + .output() + .map(|output| { + String::from_utf8(output.stdout).expect("Output from llvm-config was not valid UTF-8") + }) + .unwrap(); + + // LLVM isn't really semver and uses version suffixes to build + // version strings like '3.8.0svn', so limit what we try to parse + // to only the numeric bits. + let semver_re = Regex::new(r"^(?P\d+)\.(?P\d+)(?:\.(?P\d+))??").unwrap(); + let captures = semver_re + .captures(&version_str) + .expect("Could not determine LLVM version from llvm-config."); + + // some systems don't have a patch number but Version wants it so we just append .0 if it isn't + // there + let norm_version_str = match captures.name("patch") { + None => format!("{}.0", &captures[0]), + Some(_) => captures[0].to_string(), + }; + + println!( + "cargo:rustc-env=CARGO_BPF_LLVM_VERSION={}", + norm_version_str + ); +} + +fn main() { + cfg_if::cfg_if! { + if #[cfg(all(feature = "llvm-sys-130", not(feature = "docsrs-llvm")))] { + print_cargo_bpf_llvm_version(); + } else { + println!("cargo:rustc-env=CARGO_BPF_LLVM_VERSION=0.0.0"); + } + } +} diff --git a/cargo-bpf/src/build.rs b/cargo-bpf/src/build.rs index e34a2d55..b2e891fb 100644 --- a/cargo-bpf/src/build.rs +++ b/cargo-bpf/src/build.rs @@ -7,6 +7,7 @@ use bpf_sys::headers::build_kernel_version; use glob::{glob, PatternError}; +use semver::Version; use std::convert::From; use std::fmt::{self, Display}; use std::fs; @@ -33,6 +34,7 @@ pub enum Error { IOError(io::Error), PatternError(PatternError), BTF, + InvalidLLVMVersion(String), } impl std::error::Error for Error { @@ -65,6 +67,7 @@ impl Display for Error { IOError(e) => write!(f, "{}", e), PatternError(e) => write!(f, "couldn't list probe files: {}", e), BTF => write!(f, "failed to fix BTF section"), + InvalidLLVMVersion(p) => write!(f, "Invalid LLVMVersion: {}", p), } } } @@ -127,6 +130,43 @@ fn build_probe( .map(|v| format!(r#"kernel_version="{}.{}""#, v.version, v.patchlevel)) .unwrap_or_else(|_| r#"kernel_version="unknown""#.to_string()); + // Compare the LLVM version[1] that rustc depends on currently and the LLVM + // version[2] `cargo-bpf` had been linked into. + // + // [2] must be greater than or equal to [1]. + // + // Because the bitcode generated by [1] should be processed by [2]. + // + // If [1] is newer than [2], [2] will fail to process the newer bitcode + // format. + let linked_llvm_version = Version::parse(env!("CARGO_BPF_LLVM_VERSION")).map_err(|_| { + Error::InvalidLLVMVersion("Unknown LLVM version that cargo-bpf linked to".to_string()) + })?; + let llvm_version = rustc_version::version_meta() + .map_err(|e| { + Error::InvalidLLVMVersion(format!("Failed to get LLVM version of rustc: {}", e)) + })? + .llvm_version + .ok_or_else(|| { + Error::InvalidLLVMVersion("Failed to get LLVM version of rustc".to_string()) + })?; + if linked_llvm_version.major < llvm_version.major + || (linked_llvm_version.major == llvm_version.major + && linked_llvm_version.minor < llvm_version.minor) + { + return Err(Error::InvalidLLVMVersion(format!( + "LLVM version that cargo-bpf linked to ({}.{}) < LLVM version that rustc depends on ({}.{}). You should re-build cargo-bpf with LLVM version ({}.{}), or downgrade rustc that uses LLVM version ({}.{})", + linked_llvm_version.major, + linked_llvm_version.minor, + llvm_version.major, + llvm_version.minor, + llvm_version.major, + llvm_version.minor, + linked_llvm_version.major, + linked_llvm_version.minor, + ))); + } + if !Command::new(cargo) .current_dir(package) .env(env_name, env_value) diff --git a/cargo-bpf/src/llvm.rs b/cargo-bpf/src/llvm.rs index 8de69eda..1fc38dcf 100644 --- a/cargo-bpf/src/llvm.rs +++ b/cargo-bpf/src/llvm.rs @@ -1,5 +1,6 @@ -/*! The version of LLVM that rust depends on should be equal to or less than -the version of system LLVM. For example, this combination is invalid. +/*! The version of LLVM that rustc depends on should be equal to or less than +the version of system LLVM that cargo-bpf is linked to. For example, this +combination is invalid. - `rustc v1.56` that relies on `LLVM 13` - `LLVM 12` installed in the system @@ -9,34 +10,17 @@ But this combination is valid. - `rustc v1.55` depending on `LLVM 12` or `rustc v1.51` relying on `LLVM 11` - `LLVM 12` installed in the system -If invalid combination is used, compiling BPF programs results in corrupted ELF -files or compiling process abnormally exits with SIGSEGV, SIGABRT or LLVM +If invalid combination is used, cargo-bpf stops compiling BPF programs and +prints error. If it just proceeds, compiling BPF programs results in corrupted +ELF files or compiling process abnormally exits with SIGSEGV, SIGABRT or LLVM related indigestible errors. -This restriction exists because `cargo-bpf` executes `rustc` to emit bitcode -first and then calls LLVM API directly to parse and optimize the emitted -bitcode second. So LLVM version mismatch incurs problems. +This problem occurs because `cargo-bpf` executes `rustc` to emit bitcode first +and then calls LLVM API directly to parse and optimize the emitted bitcode +second. */ -cfg_if::cfg_if! { - if #[cfg(feature = "llvm-sys-130")] { - use llvm_sys_130 as llvm_sys; - } else if #[cfg(feature = "llvm-sys-120")] { - use llvm_sys_120 as llvm_sys; - #[rustversion::since(1.56)] - compile_error!("Can not use LLVM12 with Rust >= 1.56"); - } else if #[cfg(feature = "llvm-sys-110")] { - use llvm_sys_110 as llvm_sys; - #[rustversion::since(1.52)] - compile_error!("Can not use LLVM11 with Rust >= 1.52"); - } else if #[cfg(feature = "llvm-sys-100")] { - use llvm_sys_100 as llvm_sys; - #[cfg(not(docsrs))] - #[rustversion::since(1.47)] - compile_error!("Can not use LLVM10 with Rust >= 1.47"); - } else { - compile_error!("At least one of `llvm13`, `llvm12`, `llvm11` and `llvm10` features should be specified"); - } -} + +use llvm_sys_130 as llvm_sys; use anyhow::{anyhow, Result}; use llvm_sys::bit_writer::LLVMWriteBitcodeToFile; @@ -119,7 +103,6 @@ unsafe fn inject_exit_call(context: LLVMContextRef, func: LLVMValueRef, builder: 0, 0, LLVMInlineAsmDialectATT, - #[cfg(feature = "llvm-sys-130")] 0, ); diff --git a/cargo-bpf/src/main.rs b/cargo-bpf/src/main.rs index 1d80c7ff..57c18e46 100644 --- a/cargo-bpf/src/main.rs +++ b/cargo-bpf/src/main.rs @@ -148,7 +148,7 @@ fn main() { ]) .subcommand( SubCommand::with_name("bpf") - .version(crate_version!()) + .version(format!("{} (with LLVM {})", crate_version!(), env!("CARGO_BPF_LLVM_VERSION")).as_str()) .author(crate_authors!("\n")) .about("A cargo subcommand for developing eBPF programs") .settings(&[ diff --git a/examples/example-userspace/Cargo.toml b/examples/example-userspace/Cargo.toml index a948af0e..135303e3 100644 --- a/examples/example-userspace/Cargo.toml +++ b/examples/example-userspace/Cargo.toml @@ -30,7 +30,3 @@ required-features = ["kernel5_8"] # bpf iterator has been supported since kerne [features] kernel5_8 = [] -default = ["llvm12"] -llvm13 = ["cargo-bpf/llvm13"] -llvm12 = ["cargo-bpf/llvm12"] -llvm11 = ["cargo-bpf/llvm11"] diff --git a/redbpf-tools/Cargo.toml b/redbpf-tools/Cargo.toml index ea40bb39..d6fcc2a4 100644 --- a/redbpf-tools/Cargo.toml +++ b/redbpf-tools/Cargo.toml @@ -16,9 +16,3 @@ tokio = { version = "^1.0.1", features = ["rt", "macros", "signal", "time"] } futures = "0.3" getopts = "0.2" libc = "0.2" - -[features] -default = ["llvm12"] -llvm13 = ["cargo-bpf/llvm13"] -llvm12 = ["cargo-bpf/llvm12"] -llvm11 = ["cargo-bpf/llvm11"] From 32fa7eb70b2a8471615a0637ff36eac2c9c6ec77 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Wed, 19 Jan 2022 01:04:16 +0900 Subject: [PATCH 048/107] Update README - Update LLVM and clang installation commands. - Update how to install cargo-bpf and how to build redbpf from source. - Improve description of the LLVM version issue - Introduce build test docker images Signed-off-by: Junyeong Jeong --- README.md | 246 ++++++++++++++++++++++++++++--------------- TUTORIAL.md | 43 +------- cargo-bpf/src/lib.rs | 12 ++- 3 files changed, 176 insertions(+), 125 deletions(-) diff --git a/README.md b/README.md index 0fda50f4..50fc9ea0 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ programs using Rust. It includes: - Offers many BPF map types 1. `HashMap`, `PerCpuHashMap`, `LruHashMap`, `LruPerCpuHashMap`, `Array`, `PerCpuArray`, `PerfMap`, `TcHashMap`, `StackTrace`, `ProgramArray`, - `SockMap` + `SockMap`, `DevMap` - Offers several BPF program types 1. `KProbe`, `KRetProbe`, `UProbe`, `URetProbe`, `SocketFilter`, `XDP`, `StreamParser`, `StreamVerdict`, `TaskIter`, `SkLookup` @@ -57,110 +57,104 @@ programs using Rust. It includes: - Has several example programs that are separated into two parts: BPF programs and userspace programs -# Requirements +# Install -In order to use redBPF, you need -- one of LLVM 13, LLVM 12 or LLVM 11 installed in the system -- either the Linux kernel headers or `vmlinux`, you want to target +## Requirements -Currently LLVM 12 is used as a default version when compiling BPF programs, but -you can specify other LLVM versions as follows: -- `cargo build --no-default-features --features llvm13` -- `cargo build --no-default-features --features llvm11` +`LLVM` is required in your build system to compile BPF bytecode using RedBPF. -If you want to install `cargo-bpf` with other LLVM versions then you can try -this command: -- `cargo install cargo-bpf --no-default-features --features=llvm13,command-line` -- `cargo install cargo-bpf --no-default-features --features=llvm1,command-line` +- **LLVM 13** + It is needed to compile BPF bytecode. -## Valid combinations of rust and LLVM versions +- One of the followings: + 1. The Linux kernel headers + 2. `vmlinux`, the Linux kernel image that contains `.BTF` section + 3. Raw BTF data i.e. `/sys/kernel/btf/vmlinux` + These are needed to generate Rust bindings of the data structures of the Linux kernel. -`rustc` uses its own version of LLVM. But RedBPF also requires LLVM installed -in the system. In order to compile BPF programs, RedBPF makes use of `rustc` to -emit bitcode first and then parses and optimizes the bitcode by calling LLVM -API directly. Thus, two versions of LLVM are used while compiling BPF programs. - -- the version of LLVM that `rustc` depends on -- the version of LLVM which is installed in system - -Two versions should match. - -First RedBPF executes `rustc` to emit bitcode and second it calls LLVM API to -handle the resulting bitcode. Normally LLVM is likely to support backward -compatibility for intermediate representation. Thus, it is okay to use `rustc` -that depends on the LLVM version that is equal to or less than system LLVM. - - -| Rust version | LLVM version of the Rust | Valid system LLVM version | -|:-------------|:------------------------:|:--------------------------| -| 1.56 ~ | LLVM 13 | LLVM 13 | -| 1.52 ~ 1.55 | LLVM 12 | LLVM 13, LLVM 12 | -| 1.48 ~ 1.51 | LLVM 11 | LLVM 13, LLVM 12, LLVM 11 | - -* The minimum rust version for compiling `redbpf` is Rust 1.48 - -## Linux kernel - -The **minimum kernel version supported is 4.19**. Kernel headers are discovered -automatically, or you can use the `KERNEL_SOURCE` environment variable to point -to a specific location. Building against a linux source tree is supported as -long as you run `make prepare` first. - -**NOTE** for compiling BPF programs **inside containers**. -You need to specify `KERNEL_SOURCE` or `KERNEL_VERSION` environment variables -that indicate kernel headers. The headers should be found inside the -container. For example, inside the Ubuntu 21.04 container that contains the -Linux `5.11.0-25-generic` kernel headers, you should specify `KERNEL_VERSION` -environment variable as follows: +### On Ubuntu 21.04 LTS +Install LLVM 13 and the Linux kernel headers ```console -# KERNEL_VERSION=5.11.0-25-generic cargo build --examples +# apt-get update \ + && apt-get -y install \ + wget \ + build-essential \ + software-properties-common \ + lsb-release \ + libelf-dev \ + linux-headers-generic \ + && wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 13 && rm -f ./llvm.sh +# llvm-config-13 --version | grep 13 ``` -If your container has `vmlinux`, you can specify it instead of the Linux kernel -headers. +### On Fedora 35 +Install LLVM 13 and the Linux kernel headers ```console -# REDBPF_VMLINUX=/boot/vmlinux cargo build --examples +# dnf install -y \ + clang-13.0.0 \ + llvm-13.0.0 \ + llvm-libs-13.0.0 \ + llvm-devel-13.0.0 \ + llvm-static-13.0.0 \ + kernel \ + kernel-devel \ + elfutils-libelf-devel \ + make \ + zstd +# llvm-config --version | grep 13 ``` -See, [build-test.sh](./scripts/build-test.sh) for more information. +### Building LLVM from source -## Installing dependencies on Debian based distributions +If your Linux distro does not support the latest LLVM as pre-built packages +yet, you may build LLVM from the LLVM source code. -On Debian, Ubuntu and derivatives you can install the dependencies running: +```console +$ tar -xaf llvm-13.0.0.src.tar.xz +$ mkdir -p llvm-13.0.0.src/build +$ cd llvm-13.0.0.src/build +$ cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/llvm-13-release -DCMAKE_BUILD_TYPE=Release +$ cmake --build . --target install +``` - sudo apt-get -y install build-essential zlib1g-dev \ - llvm-12-dev libclang-12-dev linux-headers-$(uname -r) \ - libelf-dev +Then you can use your LLVM by specifying the custom installation path when +installing `cargo-bpf` or building RedBPF like this: -If your distribution doesn't have LLVM 12, you can add the [official LLVM APT -repository](https://apt.llvm.org) to your `sources.list`. Or simply run the -script that you can download at the -[llvm.sh](https://apt.llvm.org/llvm.sh). Note that this script is only for -Debian or Ubuntu. +```console +$ LLVM_SYS_130_PREFIX=$HOME/llvm-13-release/ cargo install cargo-bpf +$ LLVM_SYS_130_PREFIX=$HOME/llvm-13-release/ cargo build +``` -## Installing dependencies on RPM based distributions +Make sure correct `-DCMAKE_BUILD_TYPE` is specified. Typically `Debug` type is +not recommended if you are not going to debug LLVM itself. -First ensure that your distro includes LLVM 12: - yum info llvm-devel | grep Version - Version : 12.0.0 +## Installing `cargo-bpf` -If you don't have vesion 12, you can get it from the Fedora 34 repository. +`cargo-bpf` is a command line tool for compiling BPF program written in Rust +into BPF bytecode. -Then install the dependencies running: +```console +$ cargo install cargo-bpf +$ cargo bpf --version +``` - yum install clang llvm-devel zlib-devel kernel-devel +You can learn how to use this from [tutorial](TUTORIAL.md). -## Build images +## Building RedBPF from source -You can refer to various `Dockerfile`s that contain minimal necessary packages -to build `RedBPF` properly: [Dockerfiles for -RedBPF](https://github.com/foniod/build-images/redbpf) +If you want to build RedBPF from source to fix something, you can do as follows: -If you want docker images that are prepared to build `foniod` then refer to -this: [Dockerfiles for foniod](https://github.com/foniod/build-images) +```console +$ git clone https://github.com/foniod/redbpf.git +$ cd redbpf +$ git submodule sync +$ git submodule update --init +$ cargo build +$ cargo build --examples +``` # Getting started @@ -172,7 +166,7 @@ programs are splitted into two parts: `example-probes` and kernel context. `example-userspace` includes userspace programs that load BPF programs into kernel space and communicate with BPF programs through BPF maps. -Also see [documentation](./cargo-bpf/src/main.rs) of `cargo-bpf`. It provides a +See also [documentation](./cargo-bpf/src/main.rs) of `cargo-bpf`. It provides a CLI tool for compiling BPF programs easily. [redbpf-tools](https://github.com/foniod/redbpf/tree/master/redbpf-tools) is a @@ -182,14 +176,96 @@ understand how to structure your programs. Finally, check the [foniod project](https://github.com/foniod/foniod) that includes more advanced, concrete production ready examples of redbpf programs. -# Building from source +## Valid combinations of Rust and LLVM versions + +`rustc` is linked to its own bundled version of LLVM. And `cargo-bpf` also uses +its own version of LLVM that is statically linked into `cargo-bpf` itself. But +note that users can control the LLVM version of `cargo-bpf` by providing other +versions of LLVM in their system when building `cargo-bpf`. + +Why do we care about two LLVM versions? +Because both two versions of LLVMs are all participating in the process of +compiling BPF programs. + +1. RedBPF executes `rustc` to compile BPF programs. And `rustc` calls LLVM + functions to emit LLVM bitcode. +2. And then RedBPF parses the emitted LLVM bitcode to convert it into BPF + bytecode. To do so, it calls LLVM functions that are statically linked into + `cargo-bpf`. + +What happens if LLVM of `rustc` is newer than the LLVM of `cargo-bpf`? You +already feel it. BAM! Typically older version of LLVM can not properly handle +the bitcode that is generated by newer version of LLVM. i.e., `cargo-bpf` with +older LLVM can not properly handle what `rustc` with newer LLVM emits. -After cloning the repository run: +What happens if LLVM of `rustc` is older than the LLVM of `cargo-bpf`? Normally +LLVM is likely to support backward compatibility for intermediate +representation. + +Let's put things together. + +There are two LLVM versions involved in compiling BPF programs: + +1. the version of LLVM**(1)** that `cargo-bpf` is statically linked to when + `cargo-bpf` is built. +2. the version of LLVM**(2)** that `rustc` is linked to. + +*And*, **(1)** should be greater than or equal to **(2)**. +*It is the best case if `(1) == (2)` but `(1) > (2)` is also okay.* + +| Rust version | LLVM version of the rustc | Valid LLVM version of system | +|:-------------|:-------------------------:|:-----------------------------| +| 1.56 ~ | LLVM 13 | LLVM 13 and newer | + +## Docker images for RedBPF build test + +You can refer to various `Dockerfile`s that contain minimal necessary packages +to build `RedBPF` properly: [Dockerfiles for +RedBPF](https://github.com/foniod/build-images/redbpf) + +These docker images are pushed to ghcr.io: + +x86_64 +- `ghcr.io/foniod/redbpf-build:latest-x86_64-ubuntu21.04` +- `ghcr.io/foniod/redbpf-build:latest-x86_64-fedora35` +- `ghcr.io/foniod/redbpf-build:latest-x86_64-alpine3.15` +- `ghcr.io/foniod/redbpf-build:latest-x86_64-debian11` +- `ghcr.io/foniod/redbpf-build:latest-x86_64-archlinux` + +ARM64 +- `ghcr.io/foniod/redbpf-build:latest-aarch64-ubuntu21.04` +- `ghcr.io/foniod/redbpf-build:latest-aarch64-fedora35` +- `ghcr.io/foniod/redbpf-build:latest-aarch64-alpine3.15` +- `ghcr.io/foniod/redbpf-build:latest-aarch64-debian11` + +See [build-test.yml](.github/workflows/build-test.yml) for more information. +It describes build tests of RedBPF that run inside docker containers. + +If you want docker images that are prepared to build `foniod` then refer to +this: [Dockerfiles for foniod](https://github.com/foniod/build-images) + +## Note for building RedBPF inside docker containers + +You need to specify `KERNEL_SOURCE` or `KERNEL_VERSION` environment variables +that indicate kernel headers. The headers should be found inside the +container. For example, inside the Ubuntu 21.04 container that contains the +Linux `5.11.0-25-generic` kernel headers, you should specify `KERNEL_VERSION` +environment variable as follows: + +```console +# KERNEL_VERSION=5.11.0-25-generic cargo build --examples +``` + +If your container has `vmlinux`, the Linux kernel image that contains `.BTF` +section in it, you can specify it instead of the Linux kernel headers. + +```console +# REDBPF_VMLINUX=/boot/vmlinux cargo build --examples +``` - git submodule sync - git submodule update --init +See [build-test.yml](.github/workflows/build-test.yml) for more information. +It describes build tests of RedBPF that run inside docker containers. -Install the dependencies as documented above, then run `cargo build` as usual. # License diff --git a/TUTORIAL.md b/TUTORIAL.md index deefc936..f9118978 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -36,32 +36,6 @@ Small background programs and BPF maps to kernel space and communicate with BPF programs through BPF maps. -### Valid combinations of rust and LLVM versions - -`rustc` uses its own version of LLVM. But RedBPF also requires LLVM installed -in the system. In order to compile BPF programs, RedBPF makes use of `rustc` to -emit bitcode first and then parses and optimizes the bitcode by calling LLVM -API directly. Thus, two versions of LLVM are used while compiling BPF programs. - -- the version of LLVM that `rustc` depends on -- the version of LLVM which is installed in system - -Two versions should match. - -First RedBPF executes `rustc` to emit bitcode and second it calls LLVM API to -handle the resulting bitcode. Normally LLVM is likely to support backward -compatibility for intermediate representation. Thus, it is okay to use `rustc` -that depends on the LLVM version that is equal to or less than system LLVM. - - -| Rust version | LLVM version of the Rust | Valid system LLVM version | -|:-------------|:------------------------:|:--------------------------| -| 1.56 ~ | LLVM 13 | LLVM 13 | -| 1.52 ~ 1.55 | LLVM 12 | LLVM 13, LLVM 12 | -| 1.48 ~ 1.51 | LLVM 11 | LLVM 13, LLVM 12, LLVM 11 | - -* The minimum rust version for compiling `redbpf` is Rust 1.48 - ### Building LLVM from source *If you already installed LLVM with a package manager you can skip this this @@ -71,14 +45,14 @@ For some reasons, you may want to build LLVM from source code. When you build LLVM, consider building LLVM with `Release` build mode. -For example, when you build LLVM12 from source code, you can pass +For example, when you build LLVM13 from source code, you can pass `-DCMAKE_BUILD_TYPE=Release` to the `cmake` command as below: ```console -$ tar -xaf llvm-12.0.0.src.tar.xz -$ mkdir -p llvm-12.0.0.src/build -$ cd llvm-12.0.0.src/build -$ cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/llvm-12-release -DCMAKE_BUILD_TYPE=Release +$ tar -xaf llvm-13.0.0.src.tar.xz +$ mkdir -p llvm-13.0.0.src/build +$ cd llvm-13.0.0.src/build +$ cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/llvm-13-release -DCMAKE_BUILD_TYPE=Release $ cmake --build . --target install ``` @@ -110,13 +84,6 @@ $ cargo install cargo-bpf This command is working as a cargo sub-command: `cargo bpf`. -> **NOTE:** If you use LLVM 13, you should specify extra options to compile it -> successfully. -> -> ```console -> $ cargo install cargo-bpf --no-default-features --features=llvm13,command-line -> ``` - Let's create a normal cargo project, `redbpf-tutorial`: ```console $ cargo new redbpf-tutorial diff --git a/cargo-bpf/src/lib.rs b/cargo-bpf/src/lib.rs index 976b0fee..0f0f238f 100644 --- a/cargo-bpf/src/lib.rs +++ b/cargo-bpf/src/lib.rs @@ -4,6 +4,14 @@ // http://apache.org/licenses/LICENSE-2.0> or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. +/*! +This supports API that is behind `cargo-bpf` command. + +It would be better using `cargo-bpf` instead of this backend API. Please see +[RedBPF Tutorial](https://github.com/foniod/redbpf/blob/main/TUTORIAL.md) to +learn how to use `cargo-bpf`. + +*/ mod build_constants; #[cfg(feature = "bindings")] @@ -13,10 +21,10 @@ pub mod bindgen; #[cfg(feature = "build")] mod build; -#[cfg(feature = "build")] -mod llvm; #[cfg(feature = "build-c")] mod build_c; +#[cfg(feature = "build")] +mod llvm; #[cfg(feature = "command-line")] mod load; From 15ac8def9a97b908c7a48a59af31a5d2b1ad1b2f Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Wed, 19 Jan 2022 22:24:07 +0900 Subject: [PATCH 049/107] Add `llvm13` feature Signed-off-by: Junyeong Jeong --- cargo-bpf/Cargo.toml | 6 ++++-- cargo-bpf/src/llvm.rs | 10 +++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cargo-bpf/Cargo.toml b/cargo-bpf/Cargo.toml index 06a3fa86..bbb268f7 100644 --- a/cargo-bpf/Cargo.toml +++ b/cargo-bpf/Cargo.toml @@ -48,12 +48,14 @@ regex = "1.0.0" cfg-if = "1.0.0" [features] -default = ["command-line"] +default = ["command-line", "llvm-sys"] bindings = ["bpf-sys", "bindgen", "syn", "quote", "proc-macro2", "tempfile"] -build = ["bindings", "libc", "toml_edit", "redbpf", "llvm-sys-130"] +build = ["bindings", "libc", "toml_edit", "redbpf"] docsrs-llvm = ["llvm-sys-130/no-llvm-linking", "llvm-sys-130/disable-alltargets-init"] build-c = [] command-line = ["build", "clap", "redbpf/load", "futures", "tokio", "hexdump"] +llvm-sys = ["llvm-sys-130"] # Use current default LLVM version or higher +llvm13 = ["llvm-sys-130"] [package.metadata.docs.rs] all-features = false diff --git a/cargo-bpf/src/llvm.rs b/cargo-bpf/src/llvm.rs index 1fc38dcf..196b74b7 100644 --- a/cargo-bpf/src/llvm.rs +++ b/cargo-bpf/src/llvm.rs @@ -19,9 +19,13 @@ This problem occurs because `cargo-bpf` executes `rustc` to emit bitcode first and then calls LLVM API directly to parse and optimize the emitted bitcode second. */ - -use llvm_sys_130 as llvm_sys; - +cfg_if::cfg_if! { + if #[cfg(feature = "llvm-sys-130")] { + use llvm_sys_130 as llvm_sys; + } else { + compile_error!("Specify --features llvm13"); + } +} use anyhow::{anyhow, Result}; use llvm_sys::bit_writer::LLVMWriteBitcodeToFile; use llvm_sys::core::*; From 85a46a0a48ed8f8e96830e7a6e652b21997c9504 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Wed, 19 Jan 2022 22:27:34 +0900 Subject: [PATCH 050/107] Fix build test Use default LLVM version. Disable Alpine 3.15 until it supports LLVM13. Signed-off-by: Junyeong Jeong --- .github/workflows/build-test.yml | 186 ++++++++++--------------------- 1 file changed, 60 insertions(+), 126 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 40192e3c..fb247909 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -42,9 +42,9 @@ jobs: bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' - name: Build RedBPF with vmlinux on x86_64 Ubuntu 21.04 run: | @@ -54,9 +54,9 @@ jobs: $REDBPF_IMAGE_NAME:latest-x86_64-ubuntu21.04 \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' build-on-x86_64-debian11: runs-on: ubuntu-latest @@ -83,9 +83,9 @@ jobs: /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' - name: Build RedBPF with vmlinux on x86_64 Debian 11 run: | @@ -95,9 +95,9 @@ jobs: $REDBPF_IMAGE_NAME:latest-x86_64-debian11 \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' build-on-x86_64-fedora35: runs-on: ubuntu-latest @@ -124,9 +124,9 @@ jobs: /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' - name: Build RedBPF with vmlinux on x86_64 Fedora 35 run: | @@ -136,11 +136,13 @@ jobs: $REDBPF_IMAGE_NAME:latest-x86_64-fedora35 \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' build-on-x86_64-alpine315: + name: Alpine 3.15 is disabled temporarily (LLVM13 unsupported) + if: false runs-on: ubuntu-latest steps: - @@ -166,9 +168,9 @@ jobs: && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && export RUSTFLAGS=-Ctarget-feature=-crt-static \ && cargo clean \ - && cargo build --no-default-features --features=llvm12 \ - && cargo build --no-default-features --features=llvm12,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm12,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' build-on-x86_64-archlinux: runs-on: ubuntu-latest @@ -195,9 +197,9 @@ jobs: /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' - name: Build RedBPF with vmlinux on x86_64 Arch Linux run: | @@ -207,9 +209,9 @@ jobs: $REDBPF_IMAGE_NAME:latest-x86_64-archlinux \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' build-on-x86_64-ubuntu-2004: runs-on: ubuntu-latest @@ -236,38 +238,9 @@ jobs: bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13 --examples' - - build-on-x86_64-ubuntu-1804: - runs-on: ubuntu-latest - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Build host info - run: | - uname -a - cat /etc/os-release - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - - - name: Build RedBPF with the kernel headers on x86_64 Ubuntu 18.04 LTS - run: | - docker run --privileged \ - -v $PWD:/build \ - -w /build \ - $REDBPF_IMAGE_NAME:latest-x86_64-ubuntu18.04 \ - bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ - && echo KERNEL_SOURCE=$KERNEL_SOURCE \ - && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --examples' build-on-aarch64-ubuntu-2104: runs-on: ubuntu-latest @@ -304,9 +277,9 @@ jobs: /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' - name: Build RedBPF with vmlinux on aarch64 Ubuntu 21.04 run: | @@ -316,9 +289,9 @@ jobs: $REDBPF_IMAGE_NAME:latest-aarch64-ubuntu21.04 \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux-btf \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' build-on-aarch64-debian11: runs-on: ubuntu-latest @@ -355,9 +328,9 @@ jobs: /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' - name: Build RedBPF with vmlinux on aarch64 Debian 11 run: | @@ -367,9 +340,9 @@ jobs: $REDBPF_IMAGE_NAME:latest-aarch64-debian11 \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux-btf \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' # Split aarch64-fedora35 into two parts because its running time exceeds 6 # hours, the maximum time for build test. @@ -408,9 +381,9 @@ jobs: /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' build-on-aarch64-fedora35-vmlinux: runs-on: ubuntu-latest @@ -446,11 +419,13 @@ jobs: $REDBPF_IMAGE_NAME:latest-aarch64-fedora35 \ /bin/bash -c 'export REDBPF_VMLINUX=/boot/vmlinux-btf \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' build-on-aarch64-alpine315: + name: Alpine 3.15 is disabled temporarily (LLVM13 unsupported) + if: false runs-on: ubuntu-latest steps: - @@ -486,9 +461,9 @@ jobs: && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && export RUSTFLAGS=-Ctarget-feature=-crt-static \ && cargo clean \ - && cargo build --no-default-features --features=llvm12 \ - && cargo build --no-default-features --features=llvm12,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm12,kernel5_8 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --features=kernel5_8 --examples' build-on-aarch64-ubuntu-2004: runs-on: ubuntu-latest @@ -525,48 +500,9 @@ jobs: /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ && echo KERNEL_SOURCE=$KERNEL_SOURCE \ && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13 --examples' - - build-on-aarch64-ubuntu-1804: - runs-on: ubuntu-latest - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Build host info - run: | - uname -a - cat /etc/os-release - - - name: Set up QEMU - id: qemu - uses: docker/setup-qemu-action@v1 - with: - image: tonistiigi/binfmt:latest - platforms: linux/arm64 - - - name: Available platforms - run: echo ${{ steps.qemu.outputs.platforms }} - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - - - name: Build RedBPF with the kernel headers on aarch64 Ubuntu 18.04 LTS - run: | - docker run --privileged \ - -v $PWD:/build \ - -w /build \ - $REDBPF_IMAGE_NAME:latest-aarch64-ubuntu18.04 \ - /bin/bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ - && echo KERNEL_SOURCE=$KERNEL_SOURCE \ - && cargo clean \ - && cargo build --no-default-features --features=llvm13 \ - && cargo build --no-default-features --features=llvm13,command-line --bin cargo-bpf \ - && cargo build --no-default-features --features=llvm13 --examples' + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --examples' publish: runs-on: ubuntu-latest @@ -575,17 +511,15 @@ jobs: - build-on-x86_64-ubuntu-2104 - build-on-x86_64-debian11 - build-on-x86_64-fedora35 - - build-on-x86_64-alpine315 + # - build-on-x86_64-alpine315 - build-on-x86_64-archlinux - build-on-x86_64-ubuntu-2004 - - build-on-x86_64-ubuntu-1804 - build-on-aarch64-ubuntu-2104 - build-on-aarch64-debian11 - build-on-aarch64-fedora35-header - build-on-aarch64-fedora35-vmlinux - - build-on-aarch64-alpine315 + # - build-on-aarch64-alpine315 - build-on-aarch64-ubuntu-2004 - - build-on-aarch64-ubuntu-1804 steps: - uses: actions/checkout@v2 From 4e75dce720e6ba9fb3f796b3e15678d439f5f9d4 Mon Sep 17 00:00:00 2001 From: wm775825 Date: Sun, 23 Jan 2022 02:53:25 +0800 Subject: [PATCH 051/107] update do_sys_openat2 example to do_sys_open Signed-off-by: wm775825 --- TUTORIAL.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index f9118978..619b0198 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -68,7 +68,7 @@ Let's make our first program using RedBPF ---- We are going to make our first BPF program and its corresponding userspace -program. The BPF program will be attached to a `do_sys_openat2` kernel function +program. The BPF program will be attached to a `do_sys_open` kernel function and it will generate a perf event delivering an open filename to userspace whenever the kernel function is invoked. And its corresponding userspace program will listen to the perf events and print the filename to stdout @@ -107,8 +107,8 @@ the latter directory is for BPF programs. ### Step 2. Add a new BPF program In this tutorial, you are going to write a simple BPF program that will be -attached to the `do_sys_openat2` kernel function. And that program generates -perf events whenever `do_sys_openat2` is called. +attached to the `do_sys_open` kernel function. And that program generates +perf events whenever `do_sys_open` is called. Create a template of a new BPF program by executing this command: ```console @@ -185,7 +185,7 @@ handles a `OpenPath` structure. And `#[map]` macro attribute is applied to the ```rust #[kprobe] -fn do_sys_openat2(regs: Registers) { +fn do_sys_open(regs: Registers) { let mut path = OpenPath::default(); unsafe { let filename = regs.parm2() as *const u8; @@ -206,16 +206,16 @@ fn do_sys_openat2(regs: Registers) { ↑ This is the main logic of the BPF program. `#[kprobe]` macro attribute indicates that this item is a BPF program, and this can be attached to entry points of kernel functions using kprobe. The name of a function is merely a -hint. The function name, `do_sys_openat2`, implies that this function is -intended to be attached to do_sys_openat2 kernel function. Determining where -`do_sys_openat2` will be attached to is up to userspace program. We will make +hint. The function name, `do_sys_open`, implies that this function is +intended to be attached to do_sys_open kernel function. Determining where +`do_sys_open` will be attached to is up to userspace program. We will make userspace part soon. When you define a function that will be attached to kernel functions using kprobe, a parameter of the function is always `Registers`. And parameters of the kernel function can be accessed through it. The signature of the Linux -kernel function do_sys_openat2 is `static long do_sys_openat2(int dfd, const -char __user *filename, struct open_how *how);` so we can get the `filename` by +kernel function do_sys_open is `long do_sys_open(int dfd, const char __user +*filename, int flags, umode_t mode)` so we can get the `filename` by calling `Registers::parm2()`. `bpf_probe_read_user_str` BPF helper function copies a string to a buffer and @@ -250,7 +250,7 @@ program!(0xFFFFFFFE, "GPL"); static mut OPEN_PATHS: PerfMap = PerfMap::with_max_entries(1024); #[kprobe] -fn do_sys_openat2(regs: Registers) { +fn do_sys_open(regs: Registers) { let mut path = OpenPath::default(); unsafe { let filename = regs.parm2() as *const u8; @@ -412,9 +412,9 @@ use redbpf::load::Loader; let mut loaded = Loader::load(probe_code()).expect("error on Loader::load"); loaded - .kprobe_mut("do_sys_openat2") + .kprobe_mut("do_sys_open") .expect("error on Loaded::kprobe_mut") - .attach_kprobe("do_sys_openat2", 0) + .attach_kprobe("do_sys_open", 0) .expect("error on KProbe::attach_kprobe"); ``` @@ -423,17 +423,17 @@ programs into the Linux kernel automatically. The remainder of the work is to attach the BPF programs to instrumentation points that you want. In case of `openmonitor`, we wrote the BPF program that is designed to attached -to do_sys_openat2 kernel function. `Loaded::kprobe_mut` gets a BPF program -whose name is `do_sys_openat2`. Do you remember that you defined a function of -which name is `do_sys_openat2` in the previous step? `#[kprobe]` attribute can +to do_sys_open kernel function. `Loaded::kprobe_mut` gets a BPF program +whose name is `do_sys_open`. Do you remember that you defined a function of +which name is `do_sys_open` in the previous step? `#[kprobe]` attribute can assign a name of a BPF program like this: `#[kprobe("CUSTOM_NAME_HERE")]`. If no custom name is specified explicitly, the function's name is used as a kprobe BPF program's name instead. So you can get the BPF program by calling -`loaded.kprobe_mut("do_sys_openat2")`. +`loaded.kprobe_mut("do_sys_open")`. `KProbe::attach_kprobe` attaches a kprobe BPF program to a specified kernel -function. So `attach_kprobe("do_sys_openat2", 0)` attaches the kprobe BPF -program to the `do_sys_openat2` kernel function entry at the offset 0 byte. +function. So `attach_kprobe("do_sys_open", 0)` attaches the kprobe BPF +program to the `do_sys_open` kernel function entry at the offset 0 byte. ```rust use futures::stream::StreamExt; @@ -510,9 +510,9 @@ async fn main() { let mut loaded = Loader::load(probe_code()).expect("error on Loader::load"); loaded - .kprobe_mut("do_sys_openat2") + .kprobe_mut("do_sys_open") .expect("error on Loaded::kprobe_mut") - .attach_kprobe("do_sys_openat2", 0) + .attach_kprobe("do_sys_open", 0) .expect("error on KProbe::attach_kprobe"); while let Some((map_name, events)) = loaded.events.next().await { @@ -579,4 +579,4 @@ Most features of BPF require **root privileges**. So run the program by root. system wide. Your output will be totally different from mine. Yes! You just completed the first BPF program and its userspace program using -RedBPF. +RedBPF. \ No newline at end of file From b4444693eaeeaa92e33469eded2ab9328c7bc22c Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 28 Jan 2022 00:35:19 +0900 Subject: [PATCH 052/107] Update README Fix examples to call geteuid instead of getuid. Signed-off-by: Junyeong Jeong --- README.md | 23 ++++++++++++++++++- examples/example-userspace/examples/tasks.rs | 2 +- .../examples/tc-map-share.rs | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 50fc9ea0..746bd31c 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ programs using Rust. It includes: 3. Raw BTF data i.e. `/sys/kernel/btf/vmlinux` These are needed to generate Rust bindings of the data structures of the Linux kernel. -### On Ubuntu 21.04 LTS +### On Ubuntu 20.04 LTS Install LLVM 13 and the Linux kernel headers ```console @@ -106,6 +106,23 @@ Install LLVM 13 and the Linux kernel headers # llvm-config --version | grep 13 ``` +### On Arch Linux + +Install LLVM 13 and the Linux kernel headers + +```console +# pacman --noconfirm -Syu \ + && pacman -S --noconfirm \ + llvm \ + llvm-libs \ + libffi \ + clang \ + make \ + linux-headers \ + linux +# llvm-config --version | grep -q '^13' +``` + ### Building LLVM from source If your Linux distro does not support the latest LLVM as pre-built packages @@ -266,6 +283,10 @@ section in it, you can specify it instead of the Linux kernel headers. See [build-test.yml](.github/workflows/build-test.yml) for more information. It describes build tests of RedBPF that run inside docker containers. +## Supported Architectures + +Currently, `x86-64` and `aarch64` architectures are supported. + # License diff --git a/examples/example-userspace/examples/tasks.rs b/examples/example-userspace/examples/tasks.rs index 4f5a23a0..31454265 100644 --- a/examples/example-userspace/examples/tasks.rs +++ b/examples/example-userspace/examples/tasks.rs @@ -16,7 +16,7 @@ async fn main() { .with_max_level(Level::TRACE) .finish(); tracing::subscriber::set_global_default(subscriber).unwrap(); - if unsafe { libc::getuid() != 0 } { + if unsafe { libc::geteuid() != 0 } { error!("You must be root to use eBPF!"); process::exit(1); } diff --git a/examples/example-userspace/examples/tc-map-share.rs b/examples/example-userspace/examples/tc-map-share.rs index fccc9bfe..53169609 100644 --- a/examples/example-userspace/examples/tc-map-share.rs +++ b/examples/example-userspace/examples/tc-map-share.rs @@ -27,7 +27,7 @@ async fn main() { .with_max_level(Level::TRACE) .finish(); tracing::subscriber::set_global_default(subscriber).unwrap(); - if unsafe { libc::getuid() != 0 } { + if unsafe { libc::geteuid() != 0 } { error!("You must be root to use eBPF!"); process::exit(1); } From df46b25b6f6b14d3e02322206809c89be3a0dcde Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 28 Jan 2022 00:55:16 +0900 Subject: [PATCH 053/107] Make loop unrolling optional Since the Linux kernel v5.3, bounded loop is permitted by BPF verifier. It has been supported long enough, so make loop unrolling optional and allow bounded loop in resulting ELF file that contains BPF bytecode. Add cargo bpf build --force-loop-unroll option that forces full loop unrolling like before. Signed-off-by: Junyeong Jeong --- cargo-bpf/src/build.rs | 40 ++++++++++++++++++++++++++++++---------- cargo-bpf/src/llvm.rs | 9 ++++++++- cargo-bpf/src/main.rs | 16 ++++++++++------ 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/cargo-bpf/src/build.rs b/cargo-bpf/src/build.rs index b2e891fb..f87053f7 100644 --- a/cargo-bpf/src/build.rs +++ b/cargo-bpf/src/build.rs @@ -9,6 +9,7 @@ use bpf_sys::headers::build_kernel_version; use glob::{glob, PatternError}; use semver::Version; use std::convert::From; +use std::env; use std::fmt::{self, Display}; use std::fs; use std::io; @@ -22,6 +23,20 @@ use redbpf::btf; use crate::llvm; use crate::CommandError; +pub struct BuildOptions { + pub target_dir: PathBuf, + pub force_loop_unroll: bool, +} + +impl Default for BuildOptions { + fn default() -> Self { + BuildOptions { + target_dir: env::current_dir().unwrap().join("target"), + force_loop_unroll: false, + } + } +} + #[derive(Debug)] pub enum Error { MissingManifest(PathBuf), @@ -240,14 +255,14 @@ fn build_probe( pub fn build( cargo: &Path, package: &Path, - target_dir: &Path, - mut probes: Vec, + probes: &mut Vec, + buildopt: &BuildOptions, ) -> Result<(), Error> { build_with_features( cargo, package, - target_dir, - &mut probes, + probes, + buildopt, &vec![String::from("probes")], ) } @@ -255,8 +270,8 @@ pub fn build( pub fn build_with_features( cargo: &Path, package: &Path, - target_dir: &Path, probes: &mut Vec, + buildopt: &BuildOptions, features: &Vec, ) -> Result<(), Error> { let path = package.join("Cargo.toml"); @@ -269,22 +284,27 @@ pub fn build_with_features( probes.extend(probe_names(&doc, &features)?); } - unsafe { llvm::init() }; + unsafe { + llvm::init(); + if buildopt.force_loop_unroll { + llvm::force_loop_unroll(); + } + } for probe in probes { - build_probe(cargo, package, &target_dir, &probe, &features)?; + build_probe(cargo, package, &buildopt.target_dir, &probe, &features)?; } Ok(()) } -pub fn cmd_build(programs: Vec, target_dir: PathBuf) -> Result<(), CommandError> { +pub fn cmd_build(mut programs: Vec, buildopt: &BuildOptions) -> Result<(), CommandError> { let current_dir = std::env::current_dir().unwrap(); Ok(build( Path::new("cargo"), ¤t_dir, - &target_dir, - programs, + &mut programs, + buildopt, )?) } diff --git a/cargo-bpf/src/llvm.rs b/cargo-bpf/src/llvm.rs index 196b74b7..0e95ae2e 100644 --- a/cargo-bpf/src/llvm.rs +++ b/cargo-bpf/src/llvm.rs @@ -46,13 +46,20 @@ use std::process::{Command, Stdio}; use std::ptr; use std::slice; -pub unsafe fn init() { +pub(crate) unsafe fn init() { LLVM_InitializeAllTargets(); LLVM_InitializeAllTargetInfos(); LLVM_InitializeAllTargetMCs(); LLVM_InitializeAllAsmPrinters(); LLVM_InitializeAllAsmParsers(); +} +/// Force loop unroll +/// +/// Normally if loop iteration count is big, loop is intact. Loops with small +/// iteration count are unrolled. But if this function is called, every loop is +/// unrolled. +pub(crate) unsafe fn force_loop_unroll() { let mut args = Vec::new(); args.push(CString::new("cargo-bpf").unwrap()); args.push(CString::new(format!("-unroll-threshold={}", std::u32::MAX)).unwrap()); diff --git a/cargo-bpf/src/main.rs b/cargo-bpf/src/main.rs index 57c18e46..08795eb5 100644 --- a/cargo-bpf/src/main.rs +++ b/cargo-bpf/src/main.rs @@ -134,6 +134,7 @@ $ sudo cargo bpf load -i eth0 target/bpf/programs/block_http.elf use clap::{self, crate_authors, crate_version, App, AppSettings, Arg, SubCommand}; use std::path::PathBuf; +use cargo_bpf::BuildOptions; use cargo_bpf_lib as cargo_bpf; fn main() { @@ -185,6 +186,9 @@ fn main() { .arg(Arg::with_name("TARGET_DIR").value_name("DIRECTORY").long("target-dir").help( "Directory for all generated artifacts" )) + .arg(Arg::with_name("FORCE_LOOP_UNROLL").long("force-loop-unroll").help( + "Ensure every loop is unrolled" + )) .arg(Arg::with_name("NAME").required(false).multiple(true).help( "The names of the programs to compile. When no names are specified, all the programs are built", )) @@ -231,16 +235,16 @@ fn main() { } } if let Some(m) = matches.subcommand_matches("build") { - let current_dir = std::env::current_dir().unwrap(); - let target_dir = m - .value_of("TARGET_DIR") - .map(PathBuf::from) - .unwrap_or_else(|| current_dir.join("target")); + let mut buildopt = BuildOptions::default(); + if let Some(v) = m.value_of("TARGET_DIR") { + buildopt.target_dir = PathBuf::from(v); + } + buildopt.force_loop_unroll = m.is_present("FORCE_LOOP_UNROLL"); let programs = m .values_of("NAME") .map(|i| i.map(String::from).collect()) .unwrap_or_else(Vec::new); - if let Err(e) = cargo_bpf::cmd_build(programs, target_dir) { + if let Err(e) = cargo_bpf::cmd_build(programs, &buildopt) { clap::Error::with_description(&e.0, clap::ErrorKind::InvalidValue).exit() } } From 826d118e4bad611a90eb695f173f1be2c99fcf10 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 28 Jan 2022 00:56:03 +0900 Subject: [PATCH 054/107] Fix build scripts of examples and tools Signed-off-by: Junyeong Jeong --- examples/example-userspace/Cargo.toml | 1 + examples/example-userspace/build.rs | 21 ++++++++++++--------- redbpf-tools/build.rs | 17 ++++++++--------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/examples/example-userspace/Cargo.toml b/examples/example-userspace/Cargo.toml index 135303e3..e373a115 100644 --- a/examples/example-userspace/Cargo.toml +++ b/examples/example-userspace/Cargo.toml @@ -30,3 +30,4 @@ required-features = ["kernel5_8"] # bpf iterator has been supported since kerne [features] kernel5_8 = [] +force-loop-unroll = [] diff --git a/examples/example-userspace/build.rs b/examples/example-userspace/build.rs index 3af3c31b..6461bc50 100644 --- a/examples/example-userspace/build.rs +++ b/examples/example-userspace/build.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use tracing::Level; use tracing_subscriber::FmtSubscriber; +use cargo_bpf::BuildOptions; use cargo_bpf_lib as cargo_bpf; fn main() { @@ -13,24 +14,26 @@ fn main() { let cargo = PathBuf::from(env::var("CARGO").unwrap()); let target = PathBuf::from(env::var("OUT_DIR").unwrap()); - let probes = Path::new("../example-probes"); + let package = Path::new("../example-probes"); let mut features = vec![String::from("probes")]; if env::var("CARGO_FEATURE_KERNEL5_8").is_ok() { features.push(String::from("kernel5_8")); } - if let Err(e) = cargo_bpf::build_with_features( - &cargo, - &probes, - &target.join("target"), - &mut Vec::new(), - &features, - ) { + + let mut buildopt = BuildOptions::default(); + buildopt.target_dir = target.join("target"); + if env::var("CARGO_FEATURE_FORCE_LOOP_UNROLL").is_ok() { + buildopt.force_loop_unroll = true; + } + if let Err(e) = + cargo_bpf::build_with_features(&cargo, &package, &mut Vec::new(), &buildopt, &features) + { eprintln!("{}", e); panic!("probes build failed"); } - cargo_bpf::probe_files(&probes) + cargo_bpf::probe_files(&package) .expect("couldn't list probe files") .iter() .for_each(|file| { diff --git a/redbpf-tools/build.rs b/redbpf-tools/build.rs index 59c4b66d..1546205f 100644 --- a/redbpf-tools/build.rs +++ b/redbpf-tools/build.rs @@ -1,22 +1,21 @@ use std::env; use std::path::{Path, PathBuf}; +use cargo_bpf::BuildOptions; use cargo_bpf_lib as cargo_bpf; fn main() { let cargo = PathBuf::from(env::var("CARGO").unwrap()); let target = PathBuf::from(env::var("OUT_DIR").unwrap()); - let probes = Path::new("probes"); + let package = Path::new("probes"); - cargo_bpf::build( - &cargo, - &probes, - &target.join("target"), - Vec::new(), - ) - .expect("couldn't compile probes"); + let mut buildopt = BuildOptions::default(); + buildopt.target_dir = target.join("target"); - cargo_bpf::probe_files(&probes) + cargo_bpf::build(&cargo, &package, &mut Vec::new(), &buildopt) + .expect("couldn't compile probes"); + + cargo_bpf::probe_files(&package) .expect("couldn't list probe files") .iter() .for_each(|file| { From 61a603358c9c31450e5fb0045d8bf55eda22c650 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 28 Jan 2022 00:57:12 +0900 Subject: [PATCH 055/107] Add bounded-loop example Signed-off-by: Junyeong Jeong --- examples/example-probes/Cargo.toml | 5 +++ .../example-probes/src/bounded_loop/main.rs | 20 ++++++++++++ .../examples/bounded-loop.rs | 31 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 examples/example-probes/src/bounded_loop/main.rs create mode 100644 examples/example-userspace/examples/bounded-loop.rs diff --git a/examples/example-probes/Cargo.toml b/examples/example-probes/Cargo.toml index 9bfb7df0..33415841 100644 --- a/examples/example-probes/Cargo.toml +++ b/examples/example-probes/Cargo.toml @@ -78,3 +78,8 @@ required-features = ["probes", "kernel5_8"] name = "hashmaps" path = "src/hashmaps/main.rs" required-features = ["probes"] + +[[bin]] +name = "bounded_loop" +path = "src/bounded_loop/main.rs" +required-features = ["probes"] diff --git a/examples/example-probes/src/bounded_loop/main.rs b/examples/example-probes/src/bounded_loop/main.rs new file mode 100644 index 00000000..a767db8b --- /dev/null +++ b/examples/example-probes/src/bounded_loop/main.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] +use redbpf_probes::kprobe::prelude::*; +program!(0xFFFFFFFE, "GPL"); + +#[map] +static mut ARRAY: Array = Array::with_max_entries(1000); + +#[kprobe] +pub fn prog(_: Registers) { + unsafe { + let sum = ARRAY.get_mut(0).unwrap(); + // This is bounded loop. If `cargo bpf build` command is executed + // without --force-loop-unroll flag, this loop is intact. + for idx in 1..1000 { + let val = ARRAY.get(idx).unwrap(); + *sum += val; + } + } +} diff --git a/examples/example-userspace/examples/bounded-loop.rs b/examples/example-userspace/examples/bounded-loop.rs new file mode 100644 index 00000000..36405824 --- /dev/null +++ b/examples/example-userspace/examples/bounded-loop.rs @@ -0,0 +1,31 @@ +/// This example shows disassemble of bounded_loop probe file. +/// +/// If `cargo bpf build` is executed, the resulting BPF bytecode is relatively +/// small. And you can see that the disassembled code is short and you can find +/// bounded loop code in there. Since the Linux kernel v5.3, the bounded loop +/// is permitted by the BPF verifier. So the default behavior of `cargo bpf +/// build` is to preserve the bounded loop if the loop iteration count is big +/// enough. +/// +/// If `cargo bpf build --force-loop-unroll` is executed, the resulting BPF +/// bytecode does not contain loop code. Instead, it contains fully unrolled +/// loop. So you can see the disassembled code is pretty long. +use std::path::PathBuf; +use std::process::Command; + +fn main() { + println!("Run llvm-objdump --disassemble"); + println!("========"); + let _ = Command::new("llvm-objdump") + .arg("--disassemble") + .arg(probe_path().into_os_string().into_string().unwrap()) + .status(); +} + +fn probe_path() -> PathBuf { + let path = concat!( + env!("OUT_DIR"), + "/target/bpf/programs/bounded_loop/bounded_loop.elf" + ); + PathBuf::from(path) +} From 7b6ce8de2ac2a8e7bdf9dbfdaa7a6334bbf8d9f3 Mon Sep 17 00:00:00 2001 From: Kyle Simpson Date: Tue, 1 Feb 2022 13:41:05 +0000 Subject: [PATCH 056/107] Add support for XSKMAP eBPF Maps This commit adds support for XskMap datastructures (BPF_MAP_TYPE_XSKMAP), allowing the use of redbpf-generated binaries in AF_XDP programs. This mostly repurposes the existing `DevMap` code as the redirect logic is mostly identical. Signed-off-by: Kyle Simpson --- redbpf-macros/src/lib.rs | 2 +- redbpf-probes/src/xdp/mod.rs | 2 ++ redbpf-probes/src/xdp/xskmap.rs | 49 +++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 redbpf-probes/src/xdp/xskmap.rs diff --git a/redbpf-macros/src/lib.rs b/redbpf-macros/src/lib.rs index b3b392bd..47d0a885 100644 --- a/redbpf-macros/src/lib.rs +++ b/redbpf-macros/src/lib.rs @@ -263,7 +263,7 @@ pub fn map(attrs: TokenStream, item: TokenStream) -> TokenStream { } else { // without generic types match map_type_name.as_str() { - "StackTrace" | "SockMap" | "ProgramArray" | "DevMap" => {} + "StackTrace" | "SockMap" | "ProgramArray" | "DevMap" | "XskMap" => {} _ => { panic!("unknown map type name: {}", map_type_name); } diff --git a/redbpf-probes/src/xdp/mod.rs b/redbpf-probes/src/xdp/mod.rs index 5965ae8e..b230a3d4 100644 --- a/redbpf-probes/src/xdp/mod.rs +++ b/redbpf-probes/src/xdp/mod.rs @@ -36,8 +36,10 @@ fn block_port_80(ctx: XdpContext) -> XdpResult { */ pub mod prelude; mod devmap; +mod xskmap; pub use devmap::DevMap; +pub use xskmap::XskMap; use crate::bindings::*; use crate::maps::{PerfMap as PerfMapBase, PerfMapFlags}; diff --git a/redbpf-probes/src/xdp/xskmap.rs b/redbpf-probes/src/xdp/xskmap.rs new file mode 100644 index 00000000..99e44fd8 --- /dev/null +++ b/redbpf-probes/src/xdp/xskmap.rs @@ -0,0 +1,49 @@ +use core::mem; +use cty::c_void; +use crate::xdp::{ + bpf_map_def, bpf_map_type_BPF_MAP_TYPE_XSKMAP, XdpAction, + prelude::bpf_redirect_map, +}; + +/// AF_XDP socket map. +/// +/// XskMap is an array-like map which may be used to redirect a packet to a target +/// AF_XDP socket in user-level code. Its values are socket file descriptors. +/// This is a wrapper for `BPF_MAP_TYPE_XSKMAP`. +#[repr(transparent)] +pub struct XskMap { + def: bpf_map_def, +} + +impl XskMap { + /// Creates an AF_XDP socket map with the specified maximum number of elements. + pub const fn with_max_entries(max_entries: u32) -> Self { + Self { + def: bpf_map_def { + type_: bpf_map_type_BPF_MAP_TYPE_XSKMAP, + key_size: mem::size_of::() as u32, + value_size: mem::size_of::() as u32, + max_entries, + map_flags: 0, + }, + } + } + + /// Redirects the packet to the AF_XDP socket referenced at key `key`. + /// Returns Ok if socket was found for specified key. XDP probe + /// must return XdpAction::Redirect to actually redirect packet. + /// If key is not found, Err is returned. + #[inline] + pub fn redirect(&mut self, key: u32) -> Result<(), ()> { + let res = bpf_redirect_map( + &mut self.def as *mut _ as *mut c_void, + key, XdpAction::Aborted as u64 + ); + if res == XdpAction::Redirect as i64 { + Ok(()) + } else { + Err(()) + } + } +} + From b35fd07254b128008ac3b10be860d0a28cc41425 Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Thu, 20 Jan 2022 06:09:07 +0000 Subject: [PATCH 057/107] DWN-541 add p0f as example Signed-off-by: Kevin Sun --- examples/example-probes/Cargo.toml | 5 + examples/example-probes/src/lib.rs | 1 + examples/example-probes/src/p0f/main.rs | 312 +++++++++++++++++++++ examples/example-probes/src/p0f/mod.rs | 151 ++++++++++ examples/example-userspace/examples/p0f.rs | 49 ++++ 5 files changed, 518 insertions(+) create mode 100644 examples/example-probes/src/p0f/main.rs create mode 100644 examples/example-probes/src/p0f/mod.rs create mode 100644 examples/example-userspace/examples/p0f.rs diff --git a/examples/example-probes/Cargo.toml b/examples/example-probes/Cargo.toml index 9bfb7df0..caecc02f 100644 --- a/examples/example-probes/Cargo.toml +++ b/examples/example-probes/Cargo.toml @@ -78,3 +78,8 @@ required-features = ["probes", "kernel5_8"] name = "hashmaps" path = "src/hashmaps/main.rs" required-features = ["probes"] + +[[bin]] +name = "p0f" +path = "src/p0f/main.rs" +required-features = ["probes"] diff --git a/examples/example-probes/src/lib.rs b/examples/example-probes/src/lib.rs index 83f72982..aa7df1ed 100644 --- a/examples/example-probes/src/lib.rs +++ b/examples/example-probes/src/lib.rs @@ -17,3 +17,4 @@ pub mod mallocstacks; pub mod tasks; pub mod tcp_lifetime; pub mod vfsreadlat; +pub mod p0f; diff --git a/examples/example-probes/src/p0f/main.rs b/examples/example-probes/src/p0f/main.rs new file mode 100644 index 00000000..2e45d49c --- /dev/null +++ b/examples/example-probes/src/p0f/main.rs @@ -0,0 +1,312 @@ +#![no_std] +#![no_main] + +use redbpf_probes::{bindings::iphdr, bindings::tcphdr, net::Transport, xdp::prelude::*}; + +use example_probes::p0f::{ + self, Quirks, TcpOptionMss, TcpOptionSackok, TcpOptionU32, TcpOptionWscale, TcpSignature, +}; + +program!(0xFFFFFFFE, "GPL"); + +#[map(link_section = "maps/log_events")] +static mut log_events: PerfMap = PerfMap::with_max_entries(512); + +#[map(link_section = "maps/tcp_signatures")] +static mut tcp_signatures: PerfMap = PerfMap::with_max_entries(512); + +// #[inline(always)] +#[inline(never)] +fn parse_tcp_signature(ctx: &XdpContext, tcp_header: &tcphdr, tcp_sig: &mut TcpSignature) -> bool { + tcp_sig.set_quirk(Quirks::ZERO_SEQ, tcp_header.seq == 0); + + if tcp_header.ack() != 0 { + tcp_sig.set_quirk(Quirks::ZERO_ACK, tcp_header.ack_seq == 0); + } else { + tcp_sig.set_quirk(Quirks::NZ_ACK, tcp_header.ack_seq != 0); + } + + if tcp_header.urg() != 0 { + tcp_sig.set_quirk_true(Quirks::URG); + } else { + tcp_sig.set_quirk(Quirks::NZ_URG, tcp_header.urg_ptr != 0); + } + + tcp_sig.set_quirk(Quirks::PUSH, tcp_header.psh() != 0); + tcp_sig.win = tcp_header.window; + + // let's do tcp options + let mut tcp_option_pos = match unsafe { ctx.ptr_after(tcp_header as *const tcphdr) } { + Ok(pos) => pos as *const u8 as usize, + Err(_) => return false, + }; + + let tcp_option_end = tcp_header as *const tcphdr as usize + (tcp_header.doff() * 4) as usize; + if tcp_option_end > ctx.data_end() { + return false; + } + + tcp_sig.pay_class = (ctx.data_end() != tcp_option_end) as i8; + + // unsafe { + // log_events.insert(&ctx, &MapData::new(tcp_option_pos as usize)); + // } + + for _ in 0..p0f::MAX_TCP_OPT - 6 { + if tcp_option_pos >= tcp_option_end { + return true; + } + + // return true means finished + if parse_tcp_option(ctx, &mut tcp_option_pos, tcp_option_end, tcp_sig) { + break; + } + } + + if tcp_option_pos != tcp_option_end { + tcp_sig.set_quirk(Quirks::OPT_BAD, true); + } + + true +} + +#[inline(never)] +// #[inline(always)] +fn parse_tcp_option( + ctx: &XdpContext, + tcp_option_pos: &mut usize, + tcp_option_end: usize, + tcp_sig: &mut TcpSignature, +) -> bool { + let data_end = ctx.data_end(); + + let tcp_opt = unsafe { *(*tcp_option_pos as *const u8) }; + tcp_sig.add_opt(tcp_opt); + + // skip the opt kind byte + if *tcp_option_pos + 2 > data_end { + return true; + } + *tcp_option_pos += 1; + + match tcp_opt { + // EOL is a single-byte option that aborts further option parsing. + p0f::TCPOPT_EOL => unsafe { + // EOL is a single-byte option that aborts further option parsing. + // Take note of how many bytes of option data are left, and if any of them are non-zero + if *tcp_option_pos < tcp_option_end { + tcp_sig.opt_eol_pad = (tcp_option_end - *tcp_option_pos) as u8; + + // the padding is used to make sure tcp header is 32bit aligned, so it should be 4 bytes at most + for _ in 0..4 { + if *(*tcp_option_pos as *const u8) != 0 as u8 { + tcp_sig.set_quirk_true(Quirks::OPT_EOL_NZ); + return true; + } + + if *tcp_option_pos + 2 > data_end || *tcp_option_pos + 2 > tcp_option_end { + return true; + } + *tcp_option_pos += 1; + } + } + return true; + }, + + // MSS is a four-byte option with specified size: type, len, mss (u16) + p0f::TCPOPT_MSS => unsafe { + if *tcp_option_pos + core::mem::size_of::() > data_end { + return true; + } + let tcp_opt_mss: *const TcpOptionMss = *tcp_option_pos as *const TcpOptionMss; + + if (*tcp_opt_mss).len != 4 as u8 { + tcp_sig.set_quirk_true(Quirks::OPT_BAD); + } + tcp_sig.mss = i16::from_be((*tcp_opt_mss).mss) as i32; + + if *tcp_option_pos + 4 > data_end { + return true; + } + *tcp_option_pos += 3; + }, + + // WS is a three-byte option with specified size: type, len, shift + p0f::TCPOPT_WSCALE => unsafe { + if *tcp_option_pos + core::mem::size_of::() > data_end { + return true; + } + let tcp_opt_wscale: *const TcpOptionWscale = *tcp_option_pos as *const TcpOptionWscale; + + if (*tcp_opt_wscale).len != 3 as u8 { + tcp_sig.set_quirk_true(Quirks::OPT_BAD); + } + + tcp_sig.wscale = (*tcp_opt_wscale).wscale as i16; + if tcp_sig.wscale > 14 { + tcp_sig.set_quirk_true(Quirks::OPT_EXWS); + } + + if *tcp_option_pos + 3 > data_end { + return true; + } + *tcp_option_pos += 2; + }, + + // SACKOK is a two-byte option with specified size + p0f::TCPOPT_SACKOK => unsafe { + if *tcp_option_pos + core::mem::size_of::() > data_end { + return true; + } + let tcp_opt_sackok: *const TcpOptionSackok = *tcp_option_pos as *const TcpOptionSackok; + + if (*tcp_opt_sackok).len != 2 as u8 { + tcp_sig.set_quirk_true(Quirks::OPT_BAD); + } + + if *tcp_option_pos + 2 > data_end { + return true; + } + *tcp_option_pos += 1; + }, + + // SACK is a variable-length option of 10 to 34 bytes + p0f::TCPOPT_SACK => unsafe { + if *tcp_option_pos + core::mem::size_of::() > data_end { + return true; + } + let tcp_opt_sack: *const TcpOptionU32 = *tcp_option_pos as *const TcpOptionU32; + + let len = (*tcp_opt_sack).len; + if len < 10 as u8 || len > 34 as u8 { + tcp_sig.set_quirk_true(Quirks::OPT_BAD); + return true; + } + + if len == 10 { + if *tcp_option_pos + 10 > data_end { + return true; + } + *tcp_option_pos += 9; + } else if len == 18 { + if *tcp_option_pos + 18 > data_end { + return true; + } + *tcp_option_pos += 17; + } else if len == 26 { + if *tcp_option_pos + 26 > data_end { + return true; + } + *tcp_option_pos += 25; + } else if len == 34 { + if *tcp_option_pos + 34 > data_end { + return true; + } + *tcp_option_pos += 33; + } else { + tcp_sig.set_quirk_true(Quirks::OPT_BAD); + return true; + }; + }, + + // Timestamp is a ten-byte option with specified size + p0f::TCPOPT_TSTAMP => unsafe { + if *tcp_option_pos + core::mem::size_of::() > data_end { + return true; + } + let tcp_opt_tstamp: *const TcpOptionU32 = *tcp_option_pos as *const TcpOptionU32; + + if (*tcp_opt_tstamp).len != 10 as u8 { + tcp_sig.set_quirk_true(Quirks::OPT_BAD); + } + + tcp_sig.ts1 = u32::from_be((*tcp_opt_tstamp).u32_1); + tcp_sig.set_quirk(Quirks::OPT_ZERO_TS1, tcp_sig.ts1 == 0); + tcp_sig.set_quirk(Quirks::OPT_NZ_TS2, (*tcp_opt_tstamp).u32_2 != 0); + + if *tcp_option_pos + 10 > data_end { + return true; + } + *tcp_option_pos += 9; + }, + + // NOP is a single-byte option that does nothing + // others just keep move forward + _other => {} + }; + + false +} + +// #[inline(always)] +#[inline(never)] +fn parse_ipv4_tcp_signatures( + ctx: &XdpContext, + ip_header: &iphdr, + tcp_sig: &mut TcpSignature, +) -> bool { + let header_len = ip_header.ihl() as usize * 4; + if header_len < core::mem::size_of::() { + return false; + } + + let tcp_header: *const tcphdr = { + let addr = ip_header as *const iphdr as usize + header_len; + if ip_header.protocol as u32 != IPPROTO_TCP + || addr + core::mem::size_of::() > ctx.data_end() + { + return false; + } else { + addr as *const tcphdr + } + }; + + let tcp_header = unsafe { tcp_header.as_ref() }.unwrap(); + let dest_port = u16::from_be(tcp_header.dest); + if dest_port != p0f::HTTP_PORT && dest_port != p0f::HTTPS_PORT + || tcp_header.syn() == 0 + || tcp_header.ack() != 0 + { + return false; + } + + // set IPv4 header signature + tcp_sig.ip_ver = 0x04; + tcp_sig.ttl = ip_header.ttl; + tcp_sig.ip_opt_len = header_len.saturating_sub(20) as u8; + tcp_sig.tot_hdr = header_len as u16; + + // ECN is the last two bits of ToS + tcp_sig.set_quirk(Quirks::ECN, (ip_header.tos & (p0f::IP_TOS_ECN)) != 0); + + let ip_flags = u16::from_be(ip_header.frag_off); + tcp_sig.set_quirk(Quirks::NZ_MBZ, (ip_flags & p0f::IP4_MBZ) != 0); + if (ip_flags & p0f::IP4_DF) != 0 { + tcp_sig.set_quirk_true(Quirks::DF); + tcp_sig.set_quirk(Quirks::NZ_ID, ip_header.id != 0); + } else { + tcp_sig.set_quirk(Quirks::ZERO_ID, ip_header.id == 0); + } + + parse_tcp_signature(ctx, &tcp_header, tcp_sig) +} + +#[xdp("p0f_extractor")] +pub fn p0f_extractor(ctx: XdpContext) -> XdpResult { + let mut tcp_sig = TcpSignature::default(); + let eth = ctx.eth()?; + unsafe { + if (*eth).h_proto == u16::from_be(ETH_P_IP as u16) { + let ip_header: *const iphdr = ctx.ptr_after(eth)?; + + if parse_ipv4_tcp_signatures(&ctx, &*ip_header, &mut tcp_sig) { + tcp_signatures.insert(&ctx, &MapData::new(tcp_sig)); + } + } else { + // might other process, e.g. IPv6 + return Ok(XdpAction::Pass); + }; + } + + Ok(XdpAction::Pass) +} diff --git a/examples/example-probes/src/p0f/mod.rs b/examples/example-probes/src/p0f/mod.rs new file mode 100644 index 00000000..2ff4994b --- /dev/null +++ b/examples/example-probes/src/p0f/mod.rs @@ -0,0 +1,151 @@ +use core::mem; +use redbpf_probes::{bindings::iphdr, bindings::tcphdr, xdp::prelude::*}; + +#[repr(C)] +#[derive(Debug)] +pub struct TcpSignature { + pub quirks: u32, /* Quirks */ + pub opt_eol_pad: u8, /* Amount of padding past EOL */ + pub ip_opt_len: u8, /* Length of IP options */ + pub ip_ver: i8, /* -1 = any, IP_VER4, IP_VER6 */ + pub ttl: u8, /* Actual TTL */ + + pub mss: i32, /* Maximum segment size (-1 = any) */ + pub win: u16, /* Window size */ + + pub win_type: u8, /* WIN_TYPE_* */ + pub pay_class: i8, /* -1 = any, 0 = zero, 1 = non-zero */ + + pub wscale: i16, /* Window scale (-1 = any) */ + pub tot_hdr: u16, /* Total header length */ + pub ts1: u32, /* Own timestamp */ + + pub opt_cnt: u8, + pub options: [u8; MAX_TCP_OPT], +} + +// names from p0f +#[allow(non_camel_case_types)] +pub enum Quirks { + /* IP-level quirks: */ + ECN = 0x00000001, /* ECN supported */ + DF = 0x00000002, /* DF used (probably PMTUD) */ + NZ_ID = 0x00000004, /* Non-zero IDs when DF set */ + ZERO_ID = 0x00000008, /* Zero IDs when DF not set */ + NZ_MBZ = 0x00000010, /* IP "must be zero" field isn't */ + FLOW = 0x00000020, /* IPv6 flows used */ + + /* Core TCP quirks: */ + ZERO_SEQ = 0x00001000, /* SEQ is zero */ + NZ_ACK = 0x00002000, /* ACK non-zero when ACK flag not set */ + ZERO_ACK = 0x00004000, /* ACK is zero when ACK flag set */ + NZ_URG = 0x00008000, /* URG non-zero when URG flag not set */ + URG = 0x00010000, /* URG flag set */ + PUSH = 0x00020000, /* PUSH flag on a control packet */ + + /* TCP option quirks: */ + OPT_ZERO_TS1 = 0x01000000, /* Own timestamp set to zero */ + OPT_NZ_TS2 = 0x02000000, /* Peer timestamp non-zero on SYN */ + OPT_EOL_NZ = 0x04000000, /* Non-zero padding past EOL */ + OPT_EXWS = 0x08000000, /* Excessive window scaling */ + OPT_BAD = 0x10000000, /* Problem parsing TCP options */ +} + +// IP-level ECN, last two bits +pub const IP_TOS_ECN: u8 = 0x03; + +/* IP flags: */ +pub const IP4_MBZ: u16 = 0x8000; /* "Must be zero" */ +pub const IP4_DF: u16 = 0x4000; /* Don't fragment (usually PMTUD) */ +pub const IP4_MF: u16 = 0x2000; /* More fragments coming */ + +pub const MIN_TCP4: usize = mem::size_of::() + mem::size_of::(); +pub const MIN_TCP6: usize = mem::size_of::() + mem::size_of::(); + +pub const HTTP_PORT: u16 = 80; +pub const HTTPS_PORT: u16 = 443; + +// tcp_sig_match uses 10, here use 11 to make TcpSignature 4 bytes aligned +pub const MAX_TCP_OPT: usize = 11; +pub const TCP_HDR_LEN: usize = mem::size_of::(); + +/* Notable options, aligned with p0f */ +pub const TCPOPT_EOL: u8 = 0; // End of options (1) +pub const TCPOPT_NOP: u8 = 1; // No-op (1) +pub const TCPOPT_MSS: u8 = 2; // Maximum segment size (4) +pub const TCPOPT_WSCALE: u8 = 3; // Window scaling (3) +pub const TCPOPT_SACKOK: u8 = 4; // Selective ACK permitted (2) +pub const TCPOPT_SACK: u8 = 5; // Actual selective ACK (10-34) +pub const TCPOPT_TSTAMP: u8 = 8; // Timestamp (10) + +/* Methods for matching window size in tcp_sig: */ +pub const WIN_TYPE_NORMAL: u8 = 0x00; /* Literal value */ +pub const WIN_TYPE_ANY: u8 = 0x01; /* Wildcard (p0f.fp sigs only) */ +pub const WIN_TYPE_MOD: u8 = 0x02; /* Modulo check (p0f.fp sigs only) */ +pub const WIN_TYPE_MSS: u8 = 0x03; /* Window size MSS multiplier */ +pub const WIN_TYPE_MTU: u8 = 0x04; /* Window size MTU multiplier */ + +impl TcpSignature { + pub fn default() -> Self { + TcpSignature { + quirks: 0, + opt_eol_pad: 0, + ip_opt_len: 0, + ip_ver: 0, + ttl: 0, + mss: 0, + win: 0, + win_type: WIN_TYPE_NORMAL, + pay_class: -1, + wscale: 0, + tot_hdr: 0, + ts1: 0, + opt_cnt: 0, + options: [0; MAX_TCP_OPT], + } + } + + #[inline] + pub fn set_quirk(&mut self, quirk: Quirks, cond: bool) { + // only set quirk when cond is true + if cond { + self.quirks |= quirk as u32; + } + } + + #[inline] + pub fn set_quirk_true(&mut self, quirk: Quirks) { + self.quirks |= quirk as u32; + } + + #[inline] + pub fn add_opt(&mut self, opt: u8) { + // it is always safe because the tcp option loop count <= MAX_TCP_OPT + self.options[self.opt_cnt as usize] = opt; + self.opt_cnt += 1; + } +} + +#[repr(C, packed)] +pub struct TcpOptionMss { + pub len: u8, + pub mss: i16, +} + +#[repr(C, packed)] +pub struct TcpOptionWscale { + pub len: u8, + pub wscale: u8, +} + +#[repr(C, packed)] +pub struct TcpOptionSackok { + pub len: u8, +} + +#[repr(C, packed)] +pub struct TcpOptionU32 { + pub len: u8, + pub u32_1: u32, + pub u32_2: u32, +} diff --git a/examples/example-userspace/examples/p0f.rs b/examples/example-userspace/examples/p0f.rs new file mode 100644 index 00000000..81865414 --- /dev/null +++ b/examples/example-userspace/examples/p0f.rs @@ -0,0 +1,49 @@ +// for details about the p0f, please refer to https://github.com/p0f/p0f + +use futures::stream::StreamExt; +use probes::p0f::TcpSignature; +use redbpf::{load::Loader, xdp}; + +fn probe_code() -> &'static [u8] { + include_bytes!(concat!(env!("OUT_DIR"), "/target/bpf/programs/p0f/p0f.elf")) +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> std::result::Result<(), String> { + let xdp_mode = xdp::Flags::DrvMode; + let interfaces: Vec = vec!["eth0".to_string()]; + + let mut loaded = Loader::load(probe_code()).map_err(|err| format!("{:?}", err))?; + + for interface in &interfaces { + println!("Attach p0f on interface: {} with mode {:?}", interface, xdp_mode); + for prog in loaded.xdps_mut() { + prog.attach_xdp(interface, xdp_mode) + .map_err(|err| format!("{:?}", err))?; + } + } + + let _ = tokio::spawn(async move { + while let Some((name, events)) = loaded.events.next().await { + for event in events { + match name.as_str() { + "tcp_signatures" => { + let tcp_sig = + unsafe { std::ptr::read(event.as_ptr() as *const TcpSignature) }; + println!("tcp_signature = {:?}", tcp_sig); + } + + "log_events" => { + let log_value = unsafe { std::ptr::read(event.as_ptr() as *const usize) }; + println!("read log_value = {}", log_value); + } + + _ => panic!("unexpected event"), + } + } + } + }) + .await; + + Ok(()) +} From 003b4756838ddcb24099674f1fbd307e2592972e Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Fri, 21 Jan 2022 00:10:39 +0000 Subject: [PATCH 058/107] add p0f as example Signed-off-by: Kevin Sun --- examples/example-probes/src/p0f/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/example-probes/src/p0f/main.rs b/examples/example-probes/src/p0f/main.rs index 2e45d49c..f7abe1fc 100644 --- a/examples/example-probes/src/p0f/main.rs +++ b/examples/example-probes/src/p0f/main.rs @@ -70,6 +70,8 @@ fn parse_tcp_signature(ctx: &XdpContext, tcp_header: &tcphdr, tcp_sig: &mut TcpS true } +// this function is tedious, especially the way handle tcp_option_pos, it would be nice to find +// a way to simplify it and pass the Linux BPF verifier #[inline(never)] // #[inline(always)] fn parse_tcp_option( From 1cf8286de5e1213c8b45541e766876314c95165a Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Wed, 2 Feb 2022 00:26:20 +0000 Subject: [PATCH 059/107] add p0f as example Signed-off-by: Kevin Sun --- examples/example-userspace/examples/p0f.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example-userspace/examples/p0f.rs b/examples/example-userspace/examples/p0f.rs index 81865414..442ba39c 100644 --- a/examples/example-userspace/examples/p0f.rs +++ b/examples/example-userspace/examples/p0f.rs @@ -29,7 +29,7 @@ async fn main() -> std::result::Result<(), String> { match name.as_str() { "tcp_signatures" => { let tcp_sig = - unsafe { std::ptr::read(event.as_ptr() as *const TcpSignature) }; + unsafe { std::ptr::read_unaligned(event.as_ptr() as *const TcpSignature) }; println!("tcp_signature = {:?}", tcp_sig); } From 96ad58d681ed377b9438b8e36c30b49a195c86dc Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 25 Jan 2022 01:56:13 +0900 Subject: [PATCH 060/107] Fix bug of set methods of percpu in userspace API Reported-by: Tuetuopay Signed-off-by: Junyeong Jeong --- redbpf/src/lib.rs | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index c6543e1d..652f8083 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -2363,28 +2363,28 @@ impl<'base, T: Clone> PerCpuArray<'base, T> { if values.len() != count { return Err(Error::Map); } + // It is needed to round up the value size to 8*N bytes // cf., https://elixir.bootlin.com/linux/v5.8/source/kernel/bpf/syscall.c#L1103 let value_size = round_up::(8); let alloc_size = value_size * count; let mut alloc = vec![0u8; alloc_size]; - let mut ptr = alloc.as_mut_ptr(); + let mut data = alloc.as_mut_ptr(); for i in 0..count { unsafe { - let dst_ptr = ptr.offset((value_size * i) as isize) as *const T as *mut T; - ptr::write_unaligned::(dst_ptr, values[i].clone()); + let dst_ptr = data.add(value_size * i) as *mut T; + dst_ptr.write_unaligned(values[i].clone()); } } - let rv = unsafe { + if unsafe { bpf_sys::bpf_map_update_elem( self.base.fd, &mut index as *mut _ as *mut _, - &mut ptr as *mut _ as *mut _, + data as *mut _, 0, ) - }; - - if rv < 0 { + } < 0 + { Err(Error::Map) } else { Ok(()) @@ -2859,18 +2859,12 @@ fn bpf_percpu_map_set( let mut data = alloc.as_mut_ptr(); for i in 0..count { unsafe { - let dst_ptr = data.add(value_size * i) as *const V as *mut V; - ptr::write_unaligned::(dst_ptr, values[i].clone()); + let dst_ptr = data.add(value_size * i) as *mut V; + dst_ptr.write_unaligned(values[i].clone()); } } - if unsafe { - bpf_sys::bpf_map_update_elem( - fd, - &mut key as *mut _ as *mut _, - &mut data as *mut _ as *mut _, - 0, - ) - } < 0 + if unsafe { bpf_sys::bpf_map_update_elem(fd, &mut key as *mut _ as *mut _, data as *mut _, 0) } + < 0 { Err(Error::Map) } else { From b5cb8e626f705da056379445fe59e43801cc0cd9 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 4 Feb 2022 01:44:11 +0900 Subject: [PATCH 061/107] Check if tc_action program contains relocation for another section Signed-off-by: Junyeong Jeong --- cargo-bpf/Cargo.toml | 1 + cargo-bpf/src/build.rs | 74 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/cargo-bpf/Cargo.toml b/cargo-bpf/Cargo.toml index bbb268f7..7287d117 100644 --- a/cargo-bpf/Cargo.toml +++ b/cargo-bpf/Cargo.toml @@ -42,6 +42,7 @@ cfg-if = "1.0" rustversion = "1.0" rustc_version = "0.4.0" semver = "1.0.0" +goblin = "0.4.3" [build-dependencies] regex = "1.0.0" diff --git a/cargo-bpf/src/build.rs b/cargo-bpf/src/build.rs index b2e891fb..8cf4bacb 100644 --- a/cargo-bpf/src/build.rs +++ b/cargo-bpf/src/build.rs @@ -7,6 +7,7 @@ use bpf_sys::headers::build_kernel_version; use glob::{glob, PatternError}; +use goblin::elf::{sym::STT_SECTION, Elf}; use semver::Version; use std::convert::From; use std::fmt::{self, Display}; @@ -35,6 +36,7 @@ pub enum Error { PatternError(PatternError), BTF, InvalidLLVMVersion(String), + IllegalProgram(String), } impl std::error::Error for Error { @@ -68,6 +70,7 @@ impl Display for Error { PatternError(e) => write!(f, "couldn't list probe files: {}", e), BTF => write!(f, "failed to fix BTF section"), InvalidLLVMVersion(p) => write!(f, "Invalid LLVMVersion: {}", p), + IllegalProgram(p) => write!(f, "Illegal Program: {}", p), } } } @@ -209,8 +212,8 @@ fn build_probe( let bc_file = bc_files.drain(..).next().unwrap(); let opt_bc_file = bc_file.with_extension("bc.opt"); - let target = artifacts_dir.join(format!("{}.elf", probe)); - unsafe { llvm::compile(&bc_file, &target, Some(&opt_bc_file)) }.map_err(|msg| { + let target_tmp = artifacts_dir.join(format!("{}.elf.tmp", probe)); + unsafe { llvm::compile(&bc_file, &target_tmp, Some(&opt_bc_file)) }.map_err(|msg| { Error::Compile( probe.into(), Some(format!("couldn't process IR file: {}", msg)), @@ -227,13 +230,70 @@ fn build_probe( .is_some() }; if contains_tc { - let elf_bytes = fs::read(&target).or_else(|e| Err(Error::IOError(e)))?; - let fixed = - btf::tc_legacy_fix_btf_section(elf_bytes.as_slice()).or_else(|_| Err(Error::BTF))?; - fs::write(&target, fixed).or_else(|e| Err(Error::IOError(e)))?; + let elf_bytes = fs::read(&target_tmp).map_err(|e| Error::IOError(e))?; + let binary = Elf::parse(&elf_bytes).map_err(|_| -> Error { + Error::IllegalProgram(format!("{}: failed to parse ELF", probe)) + })?; + check_tc_action_relocs(&binary)?; + let fixed = btf::tc_legacy_fix_btf_section(elf_bytes.as_slice()).map_err(|_| Error::BTF)?; + fs::write(&target_tmp, fixed).map_err(|e| Error::IOError(e))?; } - let _ = llvm::strip_unnecessary(&target, contains_tc); + let _ = llvm::strip_unnecessary(&target_tmp, contains_tc); + let target = artifacts_dir.join(format!("{}.elf", probe)); + fs::rename(&target_tmp, &target).map_err(|e| Error::IOError(e))?; + Ok(()) +} + +/// Check if the sections of tc_action program have relocation of data other +/// than maps. +/// +/// For example, `map.get(&42)` can not be supported by tc utility as of now. +/// Since 42 is stored at .rodata section, the tc_action program needs to be +/// relocated properly before it is loaded into the Linux kernel. But tc +/// utility does not support relocation other than maps. +fn check_tc_action_relocs(binary: &Elf) -> Result<(), Error> { + for (shidx, relsec) in binary.shdr_relocs.iter() { + let shdr = if let Some(shdr) = binary.section_headers.get(*shidx) { + shdr + } else { + continue; + }; + let hdr_name_off = shdr.sh_name; + let hdr_name = if let Some(hdr_name) = binary.shdr_strtab.get_at(hdr_name_off) { + hdr_name + } else { + continue; + }; + if !hdr_name.starts_with(".reltc_action/") { + continue; + } + for reloc in relsec.iter() { + let symidx = reloc.r_sym; + let sym = if let Some(sym) = binary.syms.get(symidx) { + sym + } else { + continue; + }; + + let sym_shdr = if let Some(shdr) = binary.section_headers.get(sym.st_shndx) { + shdr + } else { + continue; + }; + let sym_hdr_name_off = sym_shdr.sh_name; + let sym_hdr_name = if let Some(hdr_name) = binary.shdr_strtab.get_at(sym_hdr_name_off) { + hdr_name + } else { + continue; + }; + // If `map.get(&42)` is written in the code, the 42 is stored at + // `.rodata` section and st_type is `STT_SECTION` + if sym.st_type() == STT_SECTION { + return Err(Error::IllegalProgram(format!("`{}` section has a relocation for `{}` section. But tc utility does not support relocation except for maps. Did you use `map.get(&42)` instead of `map.get(&key)`?", &hdr_name[4..], sym_hdr_name))); + } + } + } Ok(()) } From 900c504f41e12833987a92545f4f49605886a723 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sat, 5 Feb 2022 17:03:39 +0900 Subject: [PATCH 062/107] Export all maps through redbpf_probes::xdp::prelude Signed-off-by: Junyeong Jeong --- redbpf-probes/src/xdp/prelude.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/redbpf-probes/src/xdp/prelude.rs b/redbpf-probes/src/xdp/prelude.rs index 3efde9af..3227590e 100644 --- a/redbpf-probes/src/xdp/prelude.rs +++ b/redbpf-probes/src/xdp/prelude.rs @@ -14,8 +14,11 @@ //! ``` pub use crate::bindings::*; pub use crate::helpers::*; -pub use crate::maps::{HashMap, PerfMapFlags}; +pub use crate::maps::*; pub use crate::net::*; + +pub use crate::xdp::PerfMap; pub use crate::xdp::*; + pub use cty::*; pub use redbpf_macros::{map, program, xdp}; From 929c067362b12826f324bcff525e59717f6bd349 Mon Sep 17 00:00:00 2001 From: Mikhail Trishchenkov Date: Mon, 7 Feb 2022 22:25:20 +0700 Subject: [PATCH 063/107] Use libbpf-sys Signed-off-by: Mikhail Trishchenkov --- README.md | 7 -- bpf-sys/Cargo.toml | 1 + bpf-sys/build.rs | 85 ------------------------ bpf-sys/src/lib.rs | 12 ---- bpf-sys/src/perf_reader.rs | 13 ---- bpf-sys/src/type_gen.rs | 24 ++++--- cargo-bpf/Cargo.toml | 3 +- redbpf-probes/Cargo.toml | 1 + redbpf/Cargo.toml | 1 + redbpf/src/btf.rs | 4 +- redbpf/src/lib.rs | 128 ++++++++++++++++++------------------- redbpf/src/xdp.rs | 10 +-- 12 files changed, 92 insertions(+), 197 deletions(-) delete mode 100644 bpf-sys/src/perf_reader.rs diff --git a/README.md b/README.md index 50fc9ea0..d9cb6a1d 100644 --- a/README.md +++ b/README.md @@ -269,13 +269,6 @@ It describes build tests of RedBPF that run inside docker containers. # License -This repository contains code from other software in the following -directories, licensed under their own particular licenses: - - * `bpf-sys/libbpf`: LGPL2 + BSD-2 - -Where '+' means they are dual licensed. - RedBPF and its components, unless otherwise stated, are licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or diff --git a/bpf-sys/Cargo.toml b/bpf-sys/Cargo.toml index 21a53da1..8526b05e 100644 --- a/bpf-sys/Cargo.toml +++ b/bpf-sys/Cargo.toml @@ -16,6 +16,7 @@ zero = "0.1" libc = "0.2" regex = { version = "1.5" } glob = "0.3.0" +libbpf-sys = "0.6.1-2" [build-dependencies] cc = "1.0" diff --git a/bpf-sys/build.rs b/bpf-sys/build.rs index d6e31261..9e8705a0 100644 --- a/bpf-sys/build.rs +++ b/bpf-sys/build.rs @@ -5,90 +5,5 @@ // http://opensource.org/licenses/MIT>, at your option. This file may not be // copied, modified, or distributed except according to those terms. -#![deny(clippy::all)] -use std::env; -use std::path::PathBuf; -use std::process::Command; - -pub mod uname { - include!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/uname.rs")); -} - -pub mod headers { - include!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/headers.rs")); -} - -fn rerun_if_changed_dir(dir: &str) { - println!("cargo:rerun-if-changed={}/", dir); - for ext in &["c", "h", "bash", "map", "md", "rst", "sh", "template"] { - glob::glob(&format!("./{}/**/*.{}", dir, ext)) - .expect("Failed to glob for source files from build.rs") - .filter_map(|e| e.ok()) - .for_each(|path| println!("cargo:rerun-if-changed={}", path.to_string_lossy())); - } -} - fn main() { - println!( - "cargo:rustc-link-search=native={}", - env::var("OUT_DIR").unwrap() - ); - println!("cargo:rustc-link-lib=static=bpf"); - println!("cargo:rustc-link-lib=elf"); - println!("cargo:rustc-link-lib=z"); - - rerun_if_changed_dir("libbpf"); - println!("cargo:rerun-if-changed=bpfsys-musl.h"); - println!("cargo:rerun-if-changed=libbpf_xdp.h"); - - let out_dir = env::var("OUT_DIR").unwrap(); - let out_path = PathBuf::from(out_dir); - - // -fPIE is passed because Fedora 35 requires it. Other distros like Ubuntu - // 21.04, Alpine 3.14 also works fine with it - if !Command::new("make") - .args(format!("-C libbpf/src BUILD_STATIC_ONLY=1 OBJDIR={out_dir}/libbpf DESTDIR={out_dir} INCLUDEDIR= LIBDIR= UAPIDIR=", out_dir=env::var("OUT_DIR").unwrap()).split(" ")) - .arg("CFLAGS=-g -O2 -Werror -Wall -fPIC") - .arg("install") - .status() - .expect("error on executing `make` command for building `libbpf` static library") - .success() { - panic!("failed to build `libbpf` static library"); - } - let bindings = bindgen::Builder::default() - .header("libbpf_xdp.h") - .header("libbpf/src/bpf.h") - .header("libbpf/src/libbpf.h") - .header("libbpf/include/uapi/linux/btf.h") - .header("libbpf/src/btf.h") - .clang_arg("-Ilibbpf/src") - .clang_arg("-Ilibbpf/include/uapi") - .clang_arg("-Ilibbpf/include") - // blacklist `bpf_map_def` to avoid conflict with libbpf_map_def.rs - .blocklist_type("bpf_map_def") - .generate() - .expect("Unable to generate bindings"); - bindings - .write_to_file(out_path.join("libbpf_bindings.rs")) - .expect("Couldn't write bindings!"); - let bindings = bindgen::Builder::default() - .header("libbpf/src/libbpf.h") - .clang_arg("-Ilibbpf/include/uapi") - .clang_arg("-Ilibbpf/include") - .allowlist_type("bpf_map_def") - .generate() - .expect("Unable to generate bindings"); - bindings - .write_to_file(out_path.join("libbpf_map_def.rs")) - .expect("Couldn't write bindings!"); - let bindings = bindgen::Builder::default() - .header("libbpf/src/bpf.h") - .clang_arg("-Ilibbpf/src") - .clang_arg("-Ilibbpf/include/uapi") - .clang_arg("-Ilibbpf/include") - .generate() - .expect("Unable to generate bindings"); - bindings - .write_to_file(out_path.join("perf_reader_bindings.rs")) - .expect("Couldn't write bindings!"); } diff --git a/bpf-sys/src/lib.rs b/bpf-sys/src/lib.rs index c2de7ea9..c01a0348 100644 --- a/bpf-sys/src/lib.rs +++ b/bpf-sys/src/lib.rs @@ -5,19 +5,7 @@ // http://opensource.org/licenses/MIT>, at your option. This file may not be // copied, modified, or distributed except according to those terms. -#![allow(non_camel_case_types)] -#![allow(non_upper_case_globals)] -#![allow(non_snake_case)] -#![allow(clippy::all)] - -extern crate zero; pub mod headers; -pub mod perf_reader; pub mod uname; -include!(concat!(env!("OUT_DIR"), "/libbpf_bindings.rs")); -include!(concat!(env!("OUT_DIR"), "/libbpf_map_def.rs")); -unsafe impl ::zero::Pod for bpf_map_def {} -unsafe impl ::zero::Pod for bpf_insn {} - pub mod type_gen; diff --git a/bpf-sys/src/perf_reader.rs b/bpf-sys/src/perf_reader.rs deleted file mode 100644 index 6c702cda..00000000 --- a/bpf-sys/src/perf_reader.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2019 Authors of Red Sift -// -// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -// copied, modified, or distributed except according to those terms. - -#![allow(non_camel_case_types)] -#![allow(non_upper_case_globals)] -#![allow(non_snake_case)] -#![allow(clippy::all)] - -include!(concat!(env!("OUT_DIR"), "/perf_reader_bindings.rs")); diff --git a/bpf-sys/src/type_gen.rs b/bpf-sys/src/type_gen.rs index 33fb29d7..096c7daa 100644 --- a/bpf-sys/src/type_gen.rs +++ b/bpf-sys/src/type_gen.rs @@ -18,10 +18,10 @@ syntax. So macro constants can not be generated from vmlinux image. But system. */ -use super::{ +use libbpf_sys::{ btf, btf__free, btf__get_nr_types, btf__name_by_offset, btf__parse_elf, btf__parse_raw, btf__type_by_id, btf_dump, btf_dump__dump_type, btf_dump__free, btf_dump__new, btf_dump_opts, - libbpf_find_kernel_btf, vdprintf, + libbpf_find_kernel_btf, __va_list_tag, }; use libc::{c_char, c_void}; use regex::RegexSet; @@ -36,6 +36,14 @@ use std::path::PathBuf; use std::ptr; pub const ENV_VMLINUX_PATH: &'static str = "REDBPF_VMLINUX"; +extern "C" { + fn vdprintf( + __fd: libc::c_int, + __fmt: *const c_char, + __arg: *mut __va_list_tag + ) -> libc::c_int; +} + // only used for RAII struct RawFdWrapper(RawFd); impl Drop for RawFdWrapper { @@ -194,16 +202,16 @@ impl VmlinuxBtfDump { None }; unsafe { - let dump_opts = { + let mut dump_opts = { let mut uninit = MaybeUninit::::zeroed(); - (*uninit.as_mut_ptr()).ctx = &mut rawfd as *mut _ as *mut _; + (*uninit.as_mut_ptr()).__bindgen_anon_1.ctx = &mut rawfd as *mut _ as *mut _; uninit.assume_init() }; let dumpptr = btf_dump__new( self.btfptr, - ptr::null(), - &dump_opts as *const _, - Some(vdprintf_wrapper), + None, + &mut dump_opts as *const _ as *mut _, + vdprintf_wrapper as *const _, ); if (dumpptr as isize) < 0 { return Err(TypeGenError::DumpError); @@ -250,7 +258,7 @@ impl Drop for VmlinuxBtfDump { unsafe extern "C" fn vdprintf_wrapper( ctx: *mut c_void, format: *const c_char, - va_list: *mut super::__va_list_tag, + va_list: *mut __va_list_tag, ) { let rawfd_wrapper = &*(ctx as *mut RawFdWrapper); vdprintf(rawfd_wrapper.0, format, va_list); diff --git a/cargo-bpf/Cargo.toml b/cargo-bpf/Cargo.toml index bbb268f7..8196d51a 100644 --- a/cargo-bpf/Cargo.toml +++ b/cargo-bpf/Cargo.toml @@ -22,6 +22,7 @@ required-features = ["command-line"] clap = { version = "2.33", optional = true } bindgen = {version = "0.59.2", default-features = false, features = ["runtime"], optional = true} toml_edit = { version = "0.2", optional = true } +libbpf-sys = { version = "0.6.1-2", optional = true } bpf-sys = { version = "2.3.0", path = "../bpf-sys", optional = true } redbpf = { version = "2.3.0", path = "../redbpf", default-features = false, optional = true } futures = { version = "0.3", optional = true } @@ -49,7 +50,7 @@ cfg-if = "1.0.0" [features] default = ["command-line", "llvm-sys"] -bindings = ["bpf-sys", "bindgen", "syn", "quote", "proc-macro2", "tempfile"] +bindings = ["libbpf-sys", "bpf-sys", "bindgen", "syn", "quote", "proc-macro2", "tempfile"] build = ["bindings", "libc", "toml_edit", "redbpf"] docsrs-llvm = ["llvm-sys-130/no-llvm-linking", "llvm-sys-130/disable-alltargets-init"] build-c = [] diff --git a/redbpf-probes/Cargo.toml b/redbpf-probes/Cargo.toml index be2d9823..89bdf42c 100644 --- a/redbpf-probes/Cargo.toml +++ b/redbpf-probes/Cargo.toml @@ -18,6 +18,7 @@ ufmt = { version = "0.1.0", default-features = false } [build-dependencies] cargo-bpf = { version = "2.3.0", path = "../cargo-bpf", default-features = false, features = ["bindings"] } bpf-sys = { version = "2.3.0", path = "../bpf-sys" } +libbpf-sys = "0.6.1-2" syn = {version = "1.0", default-features = false, features = ["parsing", "visit"] } quote = "1.0" glob = "0.3.0" diff --git a/redbpf/Cargo.toml b/redbpf/Cargo.toml index 89ea0bae..0c0a755f 100644 --- a/redbpf/Cargo.toml +++ b/redbpf/Cargo.toml @@ -16,6 +16,7 @@ maintenance = { status = "actively-developed" } [dependencies] bpf-sys = { path = "../bpf-sys", version = "2.3.0" } +libbpf-sys = "0.6.1-2" goblin = "0.4" zero = "0.1" libc = "0.2" diff --git a/redbpf/src/btf.rs b/redbpf/src/btf.rs index 12816ce8..5581a523 100644 --- a/redbpf/src/btf.rs +++ b/redbpf/src/btf.rs @@ -19,7 +19,7 @@ use std::ptr; use std::slice; use tracing::{debug, error, warn}; -use bpf_sys::{ +use libbpf_sys::{ btf_array, btf_enum, btf_header, btf_member, btf_param, btf_type, btf_var, btf_var_secinfo, BTF_INT_BOOL, BTF_INT_CHAR, BTF_INT_SIGNED, BTF_KIND_ARRAY, BTF_KIND_CONST, BTF_KIND_DATASEC, BTF_KIND_ENUM, BTF_KIND_FLOAT, BTF_KIND_FUNC, BTF_KIND_FUNC_PROTO, BTF_KIND_FWD, BTF_KIND_INT, @@ -129,7 +129,7 @@ impl BTF { let log_buf_size = v.capacity() * mem::size_of_val(&v[0]); let fd; unsafe { - fd = bpf_sys::bpf_load_btf( + fd = libbpf_sys::bpf_load_btf( raw_bytes.as_ptr() as *const _, raw_bytes.len() as u32, log_buf as _, diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index c6543e1d..356529a3 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -55,14 +55,14 @@ pub mod sys; pub mod xdp; pub use bpf_sys::uname; -use bpf_sys::{ - bpf_attach_type_BPF_SK_LOOKUP, bpf_attach_type_BPF_SK_SKB_STREAM_PARSER, - bpf_attach_type_BPF_SK_SKB_STREAM_VERDICT, bpf_attach_type_BPF_TRACE_ITER, bpf_create_map_attr, +use libbpf_sys::{ + BPF_SK_LOOKUP, BPF_SK_SKB_STREAM_PARSER, + BPF_SK_SKB_STREAM_VERDICT, BPF_TRACE_ITER, bpf_create_map_attr, bpf_create_map_xattr, bpf_insn, bpf_iter_create, bpf_link_create, bpf_load_program_xattr, - bpf_map_def, bpf_map_info, bpf_map_type_BPF_MAP_TYPE_ARRAY, bpf_map_type_BPF_MAP_TYPE_HASH, - bpf_map_type_BPF_MAP_TYPE_LRU_HASH, bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH, - bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY, bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH, - bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY, bpf_prog_type, BPF_ANY, + bpf_map_def, bpf_map_info, BPF_MAP_TYPE_ARRAY, BPF_MAP_TYPE_HASH, + BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH, + BPF_MAP_TYPE_PERCPU_ARRAY, BPF_MAP_TYPE_PERCPU_HASH, + BPF_MAP_TYPE_PERF_EVENT_ARRAY, bpf_prog_type, BPF_ANY, }; use goblin::elf::{reloc::RelocSection, section_header as hdr, Elf, SectionHeader, Sym}; @@ -256,7 +256,7 @@ pub struct TaskIter { /// ```no_run /// # static SK_LOOKUP: &[u8] = &[]; /// use std::net::TcpListener; -/// use std::os::unix::io::AsRawFd +/// use std::os::unix::io::AsRawFd; /// /// use redbpf::{HashMap, SockMap}; /// use redbpf::load::Loader; @@ -265,11 +265,11 @@ pub struct TaskIter { /// let mut loaded = Loader::load(SK_LOOKUP).unwrap(); /// /// // Pass the listener fd to the BPF program -/// let mut socket = SockMap::new(loaded.map("socket")).unwrap(); +/// let mut socket = SockMap::new(loaded.map("socket").unwrap()).unwrap(); /// socket.set(0, listener.as_raw_fd()); /// /// // Pass our port range to the BPF program -/// let mut ports = HashMap::::new(loaded.map("ports")).unwrap(); +/// let mut ports = HashMap::::new(loaded.map("ports").unwrap()).unwrap(); /// for port in 80..430 { /// ports.set(port, 1); /// } @@ -458,7 +458,7 @@ trait MapIterable { impl Program { #[allow(clippy::unnecessary_wraps)] fn new(kind: &str, name: &str, code: &[u8]) -> Result { - let code = zero::read_array(code).to_vec(); + let code = unsafe { zero::read_array_unsafe(code) }.to_vec(); let name = name.to_string(); let common = ProgramData { @@ -502,7 +502,7 @@ impl Program { } fn with_btf(kind: &str, name: &str, code: &[u8], btf: &BTF) -> Result { - let code = zero::read_array(code).to_vec(); + let code = unsafe { zero::read_array_unsafe(code) }.to_vec(); let name = name.to_string(); let common = ProgramData { @@ -532,14 +532,14 @@ impl Program { match self { KProbe(_) | KRetProbe(_) | UProbe(_) | URetProbe(_) => { - bpf_sys::bpf_prog_type_BPF_PROG_TYPE_KPROBE + libbpf_sys::BPF_PROG_TYPE_KPROBE } - XDP(_) => bpf_sys::bpf_prog_type_BPF_PROG_TYPE_XDP, - SocketFilter(_) => bpf_sys::bpf_prog_type_BPF_PROG_TYPE_SOCKET_FILTER, - TracePoint(_) => bpf_sys::bpf_prog_type_BPF_PROG_TYPE_TRACEPOINT, - StreamParser(_) | StreamVerdict(_) => bpf_sys::bpf_prog_type_BPF_PROG_TYPE_SK_SKB, - TaskIter(_) => bpf_sys::bpf_prog_type_BPF_PROG_TYPE_TRACING, - SkLookup(_) => bpf_sys::bpf_prog_type_BPF_PROG_TYPE_SK_LOOKUP, + XDP(_) => libbpf_sys::BPF_PROG_TYPE_XDP, + SocketFilter(_) => libbpf_sys::BPF_PROG_TYPE_SOCKET_FILTER, + TracePoint(_) => libbpf_sys::BPF_PROG_TYPE_TRACEPOINT, + StreamParser(_) | StreamVerdict(_) => libbpf_sys::BPF_PROG_TYPE_SK_SKB, + TaskIter(_) => libbpf_sys::BPF_PROG_TYPE_TRACING, + SkLookup(_) => libbpf_sys::BPF_PROG_TYPE_SK_LOOKUP, } } @@ -606,7 +606,7 @@ impl Program { let cname = CString::new(self.name().clone())?; let clicense = CString::new(license)?; - let mut attr = unsafe { mem::zeroed::() }; + let mut attr = unsafe { mem::zeroed::() }; attr.prog_type = self.to_prog_type(); attr.name = cname.as_ptr(); @@ -617,11 +617,11 @@ impl Program { match self { Program::TaskIter(bpf_iter) => { - attr.expected_attach_type = bpf_attach_type_BPF_TRACE_ITER; + attr.expected_attach_type = BPF_TRACE_ITER; attr.__bindgen_anon_2.attach_btf_id = bpf_iter.attach_btf_id; } Program::SkLookup(_) => { - attr.expected_attach_type = bpf_attach_type_BPF_SK_LOOKUP; + attr.expected_attach_type = BPF_SK_LOOKUP; attr.__bindgen_anon_1.kern_version = kernel_version; } _ => { @@ -670,7 +670,7 @@ impl Program { let mut buf_vec = vec![0; vec_len]; let log_buffer: MutDataPtr = buf_vec.as_mut_ptr(); let buf_size = buf_vec.capacity() * mem::size_of_val(unsafe { &*log_buffer }); - let fd = unsafe { bpf_sys::bpf_load_program_xattr(&attr, log_buffer, buf_size as u64) }; + let fd = unsafe { libbpf_sys::bpf_load_program_xattr(&attr, log_buffer, buf_size as u64) }; if fd >= 0 { warn!( "bpf_load_program_xattr had failed but it unexpectedly succeeded while reproducing the error" @@ -746,7 +746,7 @@ fn pin_bpf_obj(fd: RawFd, file: impl AsRef) -> Result<()> { fs::create_dir_all(dir)?; unsafe { let cpathname = CString::new(file.to_str().unwrap())?; - if bpf_sys::bpf_obj_pin(fd, cpathname.as_ptr()) != 0 { + if libbpf_sys::bpf_obj_pin(fd, cpathname.as_ptr()) != 0 { error!("error on bpf_obj_pin: {}", io::Error::last_os_error()); Err(Error::IO(io::Error::last_os_error())) } else { @@ -1094,7 +1094,7 @@ unsafe fn attach_xdp(dev_name: &str, progfd: libc::c_int, flags: libc::c_uint) - return Err(Error::IO(io::Error::last_os_error())); } - if bpf_sys::bpf_set_link_xdp_fd(ifindex, progfd, flags) != 0 { + if libbpf_sys::bpf_set_link_xdp_fd(ifindex, progfd, flags) != 0 { return Err(Error::IO(io::Error::last_os_error())); } Ok(()) @@ -1159,7 +1159,7 @@ impl SkLookup { return Err(Error::IO(io::Error::last_os_error())); } - let lfd = bpf_link_create(fd, nfd, bpf_attach_type_BPF_SK_LOOKUP, ptr::null()); + let lfd = bpf_link_create(fd, nfd, BPF_SK_LOOKUP, ptr::null()); if lfd < 0 { libc::close(nfd); return Err(Error::IO(io::Error::last_os_error())); @@ -1693,10 +1693,10 @@ impl RelocationInfo { // the index of the instruction we need to patch if map.section_data { - code[insn_idx].set_src_reg(bpf_sys::BPF_PSEUDO_MAP_VALUE as u8); + code[insn_idx].set_src_reg(libbpf_sys::BPF_PSEUDO_MAP_VALUE as u8); code[insn_idx + 1].imm = code[insn_idx].imm + sym.st_value as i32; } else { - code[insn_idx].set_src_reg(bpf_sys::BPF_PSEUDO_MAP_FD as u8); + code[insn_idx].set_src_reg(libbpf_sys::BPF_PSEUDO_MAP_FD as u8); } code[insn_idx].imm = map.fd; Ok(()) @@ -1714,7 +1714,7 @@ impl RelocationInfo { let insn_idx = (self.offset / std::mem::size_of::() as u64) as usize; let code = &mut prog.data_mut().code; let map = symval_to_maps.get(&sym.st_value).ok_or(Error::Reloc)?; - code[insn_idx].set_src_reg(bpf_sys::BPF_PSEUDO_MAP_FD as u8); + code[insn_idx].set_src_reg(libbpf_sys::BPF_PSEUDO_MAP_FD as u8); code[insn_idx].imm = map.fd; Ok(()) } @@ -1722,7 +1722,7 @@ impl RelocationInfo { impl Map { pub fn load(name: &str, code: &[u8]) -> Result { - let config: bpf_map_def = *zero::read(code); + let config: bpf_map_def = *unsafe { zero::read_unsafe(code) }; Map::with_map_def(name, config, None) } @@ -1730,7 +1730,7 @@ impl Map { let mut map = Map::with_map_def( name, bpf_map_def { - type_: bpf_sys::bpf_map_type_BPF_MAP_TYPE_ARRAY, + type_: libbpf_sys::BPF_MAP_TYPE_ARRAY, key_size: mem::size_of::() as u32, value_size: data.len() as u32, max_entries: 1, @@ -1742,7 +1742,7 @@ impl Map { // for BSS we don't need to copy the data, it's already 0-initialized if name != ".bss" { unsafe { - let ret = bpf_sys::bpf_map_update_elem( + let ret = libbpf_sys::bpf_map_update_elem( map.fd, &mut 0 as *mut _ as *mut _, data.as_ptr() as *mut u8 as *mut _, @@ -1830,7 +1830,7 @@ impl Map { let file = file.as_ref(); let fd = unsafe { let cpathname = CString::new(file.to_str().unwrap())?; - bpf_sys::bpf_obj_get(cpathname.as_ptr()) + libbpf_sys::bpf_obj_get(cpathname.as_ptr()) }; if fd < 0 { error!("error on bpf_obj_get: {}", io::Error::last_os_error()); @@ -1839,7 +1839,7 @@ impl Map { let map_info = unsafe { let mut info = mem::zeroed::(); let mut info_len = mem::size_of_val(&info) as u32; - if bpf_sys::bpf_obj_get_info_by_fd(fd, &mut info as *mut _ as *mut _, &mut info_len) + if libbpf_sys::bpf_obj_get_info_by_fd(fd, &mut info as *mut _ as *mut _, &mut info_len) != 0 { error!( @@ -1972,7 +1972,7 @@ impl<'a> MapBuilder<'a> { name.as_ref(), bytes, if name.starts_with(".rodata") { - bpf_sys::BPF_F_RDONLY_PROG + libbpf_sys::BPF_F_RDONLY_PROG } else { 0 }, @@ -1986,8 +1986,8 @@ impl<'base, K: Clone, V: Clone> HashMap<'base, K, V> { pub fn new(base: &Map) -> Result> { if mem::size_of::() != base.config.key_size as usize || mem::size_of::() != base.config.value_size as usize - || (bpf_map_type_BPF_MAP_TYPE_HASH != base.config.type_ - && bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY != base.config.type_) + || (BPF_MAP_TYPE_HASH != base.config.type_ + && BPF_MAP_TYPE_PERF_EVENT_ARRAY != base.config.type_) { error!( "map definitions (map type and key/value size) of base `Map' and @@ -2038,7 +2038,7 @@ impl<'base, K: Clone, V: Clone> LruHashMap<'base, K, V> { pub fn new(base: &Map) -> Result> { if mem::size_of::() != base.config.key_size as usize || mem::size_of::() != base.config.value_size as usize - || bpf_map_type_BPF_MAP_TYPE_LRU_HASH != base.config.type_ + || BPF_MAP_TYPE_LRU_HASH != base.config.type_ { error!( "map definitions (map type and key/value sizes) of base `Map' and `LruHashMap' do not match" @@ -2088,7 +2088,7 @@ impl<'base, K: Clone, V: Clone> PerCpuHashMap<'base, K, V> { pub fn new(base: &Map) -> Result> { if mem::size_of::() != base.config.key_size as usize || mem::size_of::() != base.config.value_size as usize - || bpf_map_type_BPF_MAP_TYPE_PERCPU_HASH != base.config.type_ + || BPF_MAP_TYPE_PERCPU_HASH != base.config.type_ { error!("map definitions (size of key/value and map type) of base `Map' and `PerCpuHashMap' do not match"); return Err(Error::Map); @@ -2150,7 +2150,7 @@ impl<'base, K: Clone, V: Clone> LruPerCpuHashMap<'base, K, V> { pub fn new(base: &Map) -> Result> { if mem::size_of::() != base.config.key_size as usize || mem::size_of::() != base.config.value_size as usize - || bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH != base.config.type_ + || BPF_MAP_TYPE_LRU_PERCPU_HASH != base.config.type_ { error!("map definitions (size of key/value and map type) of base `Map' and `LruPerCpuHashMap' do not match"); return Err(Error::Map); @@ -2212,7 +2212,7 @@ impl<'base, T: Clone> Array<'base, T> { /// Create `Array` map from `base` pub fn new(base: &Map) -> Result> { if mem::size_of::() != base.config.value_size as usize - || bpf_map_type_BPF_MAP_TYPE_ARRAY != base.config.type_ + || BPF_MAP_TYPE_ARRAY != base.config.type_ { error!( "map definitions (size of value, map type) of base `Map' and @@ -2232,7 +2232,7 @@ impl<'base, T: Clone> Array<'base, T> { /// This method can fail if `index` is out of bound pub fn set(&self, mut index: u32, mut value: T) -> Result<()> { let rv = unsafe { - bpf_sys::bpf_map_update_elem( + libbpf_sys::bpf_map_update_elem( self.base.fd, &mut index as *mut _ as *mut _, &mut value as *mut _ as *mut _, @@ -2253,7 +2253,7 @@ impl<'base, T: Clone> Array<'base, T> { pub fn get(&self, mut index: u32) -> Option { let mut value = MaybeUninit::zeroed(); if unsafe { - bpf_sys::bpf_map_lookup_elem( + libbpf_sys::bpf_map_lookup_elem( self.base.fd, &mut index as *mut _ as *mut _, &mut value as *mut _ as *mut _, @@ -2336,7 +2336,7 @@ impl DerefMut for PerCpuValues { impl<'base, T: Clone> PerCpuArray<'base, T> { pub fn new(base: &Map) -> Result> { if mem::size_of::() != base.config.value_size as usize - || bpf_map_type_BPF_MAP_TYPE_PERCPU_ARRAY != base.config.type_ + || BPF_MAP_TYPE_PERCPU_ARRAY != base.config.type_ { error!( "map definitions (size of value, map type) of base `Map' and @@ -2376,7 +2376,7 @@ impl<'base, T: Clone> PerCpuArray<'base, T> { } } let rv = unsafe { - bpf_sys::bpf_map_update_elem( + libbpf_sys::bpf_map_update_elem( self.base.fd, &mut index as *mut _ as *mut _, &mut ptr as *mut _ as *mut _, @@ -2406,7 +2406,7 @@ impl<'base, T: Clone> PerCpuArray<'base, T> { let mut alloc = vec![0u8; alloc_size]; let ptr = alloc.as_mut_ptr(); if unsafe { - bpf_sys::bpf_map_lookup_elem( + libbpf_sys::bpf_map_lookup_elem( self.base.fd, &mut index as *mut _ as *mut _, ptr as *mut _, @@ -2452,7 +2452,7 @@ impl<'base> ProgramArray<'base> { pub fn get(&self, mut index: u32) -> Result { let mut fd: RawFd = 0; if unsafe { - bpf_sys::bpf_map_lookup_elem( + libbpf_sys::bpf_map_lookup_elem( self.base.fd, &mut index as *mut _ as *mut _, &mut fd as *mut _ as *mut _, @@ -2484,7 +2484,7 @@ impl<'base> ProgramArray<'base> { /// ``` pub fn set(&mut self, mut index: u32, mut fd: RawFd) -> Result<()> { let ret = unsafe { - bpf_sys::bpf_map_update_elem( + libbpf_sys::bpf_map_update_elem( self.base.fd, &mut index as *mut _ as *mut _, &mut fd as *mut _ as *mut _, @@ -2526,7 +2526,7 @@ impl StackTrace<'_> { unsafe { let mut value = MaybeUninit::uninit(); - let ret = bpf_sys::bpf_map_lookup_elem( + let ret = libbpf_sys::bpf_map_lookup_elem( self.base.fd, &mut id as *const _ as *mut _, value.as_mut_ptr() as *mut _, @@ -2542,7 +2542,7 @@ impl StackTrace<'_> { pub fn delete(&mut self, id: i64) -> Result<()> { unsafe { - let ret = bpf_sys::bpf_map_delete_elem(self.base.fd, &id as *const _ as *mut _); + let ret = libbpf_sys::bpf_map_delete_elem(self.base.fd, &id as *const _ as *mut _); if ret == 0 { Ok(()) @@ -2569,10 +2569,10 @@ impl StreamParser { let prog_fd = self.common.fd.unwrap(); let ret = unsafe { - bpf_sys::bpf_prog_attach( + libbpf_sys::bpf_prog_attach( prog_fd, attach_fd, - bpf_attach_type_BPF_SK_SKB_STREAM_PARSER, + BPF_SK_SKB_STREAM_PARSER, 0, ) }; @@ -2600,10 +2600,10 @@ impl StreamVerdict { let prog_fd = self.common.fd.unwrap(); let ret = unsafe { - bpf_sys::bpf_prog_attach( + libbpf_sys::bpf_prog_attach( prog_fd, attach_fd, - bpf_attach_type_BPF_SK_SKB_STREAM_VERDICT, + BPF_SK_SKB_STREAM_VERDICT, 0, ) }; @@ -2622,7 +2622,7 @@ impl<'a> SockMap<'a> { pub fn set(&mut self, mut idx: u32, mut fd: RawFd) -> Result<()> { let ret = unsafe { - bpf_sys::bpf_map_update_elem( + libbpf_sys::bpf_map_update_elem( self.base.fd, &mut idx as *mut _ as *mut _, &mut fd as *mut _ as *mut _, @@ -2638,7 +2638,7 @@ impl<'a> SockMap<'a> { pub fn delete(&mut self, mut idx: u32) -> Result<()> { let ret = - unsafe { bpf_sys::bpf_map_delete_elem(self.base.fd, &mut idx as *mut _ as *mut _) }; + unsafe { libbpf_sys::bpf_map_delete_elem(self.base.fd, &mut idx as *mut _ as *mut _) }; if ret < 0 { Err(Error::Map) } else { @@ -2697,7 +2697,7 @@ impl TaskIter { bpf_link_create( self.common.fd.unwrap(), 0, - bpf_attach_type_BPF_TRACE_ITER, + BPF_TRACE_ITER, ptr::null(), ) }; @@ -2777,7 +2777,7 @@ fn data<'d>(bytes: &'d [u8], shdr: &SectionHeader) -> &'d [u8] { fn bpf_map_set(fd: RawFd, mut key: K, mut value: V) -> Result<()> { if unsafe { - bpf_sys::bpf_map_update_elem( + libbpf_sys::bpf_map_update_elem( fd, &mut key as *mut _ as *mut _, &mut value as *mut _ as *mut _, @@ -2794,7 +2794,7 @@ fn bpf_map_set(fd: RawFd, mut key: K, mut value: V) -> Resul fn bpf_map_get(fd: RawFd, mut key: K) -> Option { let mut value = MaybeUninit::zeroed(); if unsafe { - bpf_sys::bpf_map_lookup_elem( + libbpf_sys::bpf_map_lookup_elem( fd, &mut key as *mut _ as *mut _, &mut value as *mut _ as *mut _, @@ -2807,7 +2807,7 @@ fn bpf_map_get(fd: RawFd, mut key: K) -> Option { } fn bpf_map_delete(fd: RawFd, mut key: K) -> Result<()> { - if unsafe { bpf_sys::bpf_map_delete_elem(fd, &mut key as *mut _ as *mut _) } < 0 { + if unsafe { libbpf_sys::bpf_map_delete_elem(fd, &mut key as *mut _ as *mut _) } < 0 { Err(Error::Map) } else { Ok(()) @@ -2818,7 +2818,7 @@ fn bpf_map_get_next_key(fd: RawFd, key: Option) -> Option { if let Some(mut key) = key { let mut next_key = MaybeUninit::::zeroed(); let ret = unsafe { - bpf_sys::bpf_map_get_next_key( + libbpf_sys::bpf_map_get_next_key( fd, &mut key as *mut _ as *mut _, &mut next_key as *mut _ as *mut _, @@ -2831,7 +2831,7 @@ fn bpf_map_get_next_key(fd: RawFd, key: Option) -> Option { } } else { let mut key = MaybeUninit::::zeroed(); - if unsafe { bpf_sys::bpf_map_get_next_key(fd, ptr::null(), &mut key as *mut _ as *mut _) } + if unsafe { libbpf_sys::bpf_map_get_next_key(fd, ptr::null(), &mut key as *mut _ as *mut _) } < 0 { None @@ -2864,7 +2864,7 @@ fn bpf_percpu_map_set( } } if unsafe { - bpf_sys::bpf_map_update_elem( + libbpf_sys::bpf_map_update_elem( fd, &mut key as *mut _ as *mut _, &mut data as *mut _ as *mut _, @@ -2886,7 +2886,7 @@ fn bpf_percpu_map_get(fd: RawFd, mut key: K) -> Option DevMap<'a> { pub fn new(base: &'a Map) -> Result> { if mem::size_of::() != base.config.key_size as usize || mem::size_of::() != base.config.value_size as usize - || (bpf_map_type_BPF_MAP_TYPE_DEVMAP != base.config.type_) + || (BPF_MAP_TYPE_DEVMAP != base.config.type_) { error!( "map definitions (map type and key/value size) of base `Map' and @@ -92,7 +92,7 @@ impl<'a> DevMap<'a> { pub fn set(&mut self, mut idx: u32, mut interface_index: u32) -> Result<()> { let ret = unsafe { - bpf_sys::bpf_map_update_elem( + libbpf_sys::bpf_map_update_elem( self.base.fd, &mut idx as *mut _ as *mut _ as *mut _, &mut interface_index as *mut _ as *mut _, @@ -108,7 +108,7 @@ impl<'a> DevMap<'a> { pub fn delete(&mut self, mut idx: u32) -> Result<()> { let ret = - unsafe { bpf_sys::bpf_map_delete_elem(self.base.fd, &mut idx as *mut _ as *mut _) }; + unsafe { libbpf_sys::bpf_map_delete_elem(self.base.fd, &mut idx as *mut _ as *mut _) }; if ret < 0 { Err(Error::Map) } else { From 35d153230568c1278121a34f5a21beb9022b6dfd Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 11 Feb 2022 01:20:57 +0900 Subject: [PATCH 064/107] Fix to keep supporting struct request of the newer kernel Signed-off-by: Junyeong Jeong --- examples/example-probes/include/bindings.h | 1 + redbpf-tools/Cargo.toml | 2 +- redbpf-tools/probes/include/bindings.h | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/example-probes/include/bindings.h b/examples/example-probes/include/bindings.h index d3edfd4a..5d55fcb9 100644 --- a/examples/example-probes/include/bindings.h +++ b/examples/example-probes/include/bindings.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #endif diff --git a/redbpf-tools/Cargo.toml b/redbpf-tools/Cargo.toml index d6fcc2a4..72b3bb55 100644 --- a/redbpf-tools/Cargo.toml +++ b/redbpf-tools/Cargo.toml @@ -7,7 +7,7 @@ repository = "https://github.com/foniod/redbpf" publish = false [build-dependencies] -cargo-bpf = { path = "../cargo-bpf", default-features = false, features = ["build"] } +cargo-bpf = { path = "../cargo-bpf", default-features = false, features = ["build", "llvm-sys"] } [dependencies] probes = { path = "./probes" } diff --git a/redbpf-tools/probes/include/bindings.h b/redbpf-tools/probes/include/bindings.h index a6003fc4..f1ed7b6a 100644 --- a/redbpf-tools/probes/include/bindings.h +++ b/redbpf-tools/probes/include/bindings.h @@ -13,4 +13,5 @@ #define asm_inline asm #endif #include +#include #endif From 2e33ffc003e81d1e7704949db357ff3fef566c44 Mon Sep 17 00:00:00 2001 From: cr0ax Date: Thu, 10 Feb 2022 08:28:27 -0800 Subject: [PATCH 065/107] Add advisory to tutorial about different syscall It appears on some systems, `do_sys_open` is called rarely. This changes the tutorial to acknowledge that, and instructs the reader to attach the kprobe to two syscalls. Some copy was added to the tutorial explaining the reasoning for this. Signed-off-by: Christian Burke --- TUTORIAL.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/TUTORIAL.md b/TUTORIAL.md index 619b0198..05778126 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -411,11 +411,15 @@ use redbpf::load::Loader; let mut loaded = Loader::load(probe_code()).expect("error on Loader::load"); - loaded + let probe = loaded .kprobe_mut("do_sys_open") - .expect("error on Loaded::kprobe_mut") + .expect("error on Loaded::kprobe_mut"); + probe .attach_kprobe("do_sys_open", 0) .expect("error on KProbe::attach_kprobe"); + probe + .attach_kprobe("do_sys_openat2", 0) + .expect("error on KProbe::attach_kprobe"); ``` ↑ `Loader::load` parses an ELF relocatable file and loads all BPF maps and BPF @@ -429,7 +433,10 @@ which name is `do_sys_open` in the previous step? `#[kprobe]` attribute can assign a name of a BPF program like this: `#[kprobe("CUSTOM_NAME_HERE")]`. If no custom name is specified explicitly, the function's name is used as a kprobe BPF program's name instead. So you can get the BPF program by calling -`loaded.kprobe_mut("do_sys_open")`. +`loaded.kprobe_mut("do_sys_open")`. On some systems, attaching to `do_sys_open` +may not result in any output. Instead, you can attach to do_sys_openat2. +You can also attach to both kernel functions, because the second param for +do_sys_openat2 is the same. `KProbe::attach_kprobe` attaches a kprobe BPF program to a specified kernel function. So `attach_kprobe("do_sys_open", 0)` attaches the kprobe BPF @@ -509,11 +516,15 @@ async fn main() { let mut loaded = Loader::load(probe_code()).expect("error on Loader::load"); - loaded + let probe = loaded .kprobe_mut("do_sys_open") - .expect("error on Loaded::kprobe_mut") + .expect("error on Loaded::kprobe_mut"); + probe .attach_kprobe("do_sys_open", 0) .expect("error on KProbe::attach_kprobe"); + probe + .attach_kprobe("do_sys_openat2", 0) + .expect("error on KProbe::attach_kprobe"); while let Some((map_name, events)) = loaded.events.next().await { if map_name == "OPEN_PATHS" { @@ -579,4 +590,4 @@ Most features of BPF require **root privileges**. So run the program by root. system wide. Your output will be totally different from mine. Yes! You just completed the first BPF program and its userspace program using -RedBPF. \ No newline at end of file +RedBPF. From 67d93975605d0fd28f1ae9aab5e6ebea760be8ed Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Mon, 14 Feb 2022 00:52:39 +0900 Subject: [PATCH 066/107] Print cargo:rustc-link-lib=LLVM-13 in build.rs Support compiling cargo-bpf in gentoo linux. Other Linux distros can compile cargo-bpf correctly with or without this directive. Signed-off-by: Junyeong Jeong --- cargo-bpf/build.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cargo-bpf/build.rs b/cargo-bpf/build.rs index 09718ff8..5918380d 100644 --- a/cargo-bpf/build.rs +++ b/cargo-bpf/build.rs @@ -43,6 +43,11 @@ fn print_cargo_bpf_llvm_version() { Some(_) => captures[0].to_string(), }; + // Support compiling cargo-bpf in gentoo. + if let Some(major) = captures.name("major") { + println!("cargo:rustc-link-lib=LLVM-{}", major.as_str()); + } + println!( "cargo:rustc-env=CARGO_BPF_LLVM_VERSION={}", norm_version_str From 9ccb6e3d7cc9af83e125a42d88e07dbd5c587091 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Tue, 15 Feb 2022 19:15:49 +0900 Subject: [PATCH 067/107] Rebuild example-probes if redbpf-probes got modified Signed-off-by: Junyeong Jeong --- examples/example-userspace/build.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/example-userspace/build.rs b/examples/example-userspace/build.rs index 6461bc50..4500afc5 100644 --- a/examples/example-userspace/build.rs +++ b/examples/example-userspace/build.rs @@ -33,6 +33,8 @@ fn main() { panic!("probes build failed"); } + println!("cargo:rerun-if-changed=../../redbpf-probes"); + println!("cargo:rerun-if-changed=../../redbpf-macros"); cargo_bpf::probe_files(&package) .expect("couldn't list probe files") .iter() From 6b47c284fabc7b92c468c488d6c5dc67091cbb4d Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 18 Feb 2022 00:08:11 +0900 Subject: [PATCH 068/107] Use libbpf binding for btf_dump__new btf_dump__new is passed btf_dump_printf_fn_t that requires va_list. The implementation of va_list differs depending on architectures but libbpf-sys only supports amd64 bindings. The actual type of va_list in x86-64 is struct __va_list_tag. And va_list in aarch64 is [u64; 4usize]. For now It is uncertain that using __va_list_tag in aarch64 is okay. So keep using libbpf bindings only for functions relevant with va_list. Bump up libbpf to v0.6.1 since libbpf-sys uses it. Signed-off-by: Junyeong Jeong --- bpf-sys/bindings.h | 2 ++ bpf-sys/build.rs | 18 +++++++++++++ bpf-sys/libbpf | 2 +- bpf-sys/src/lib.rs | 7 ++++++ bpf-sys/src/type_gen.rs | 56 ++++++++++++++--------------------------- 5 files changed, 47 insertions(+), 38 deletions(-) create mode 100644 bpf-sys/bindings.h diff --git a/bpf-sys/bindings.h b/bpf-sys/bindings.h new file mode 100644 index 00000000..0ec64d04 --- /dev/null +++ b/bpf-sys/bindings.h @@ -0,0 +1,2 @@ +#include +#include diff --git a/bpf-sys/build.rs b/bpf-sys/build.rs index 9e8705a0..e3df01fb 100644 --- a/bpf-sys/build.rs +++ b/bpf-sys/build.rs @@ -4,6 +4,24 @@ // http://apache.org/licenses/LICENSE-2.0> or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. +use bindgen; +use std::env; +use std::path::PathBuf; fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + let out_path = PathBuf::from(out_dir); + + let bindings = bindgen::Builder::default() + .header("bindings.h") + .clang_arg("-Ilibbpf/src") + .clang_arg("-Ilibbpf/include/uapi") + .clang_arg("-Ilibbpf/include") + .allowlist_function("btf_dump__new") + .allowlist_function("vdprintf") + .generate() + .expect("Unable to generate bindings"); + bindings + .write_to_file(out_path.join("libbpf_bindings.rs")) + .expect("Couldn't write bindings!"); } diff --git a/bpf-sys/libbpf b/bpf-sys/libbpf index 6e6f18ac..56794b31 160000 --- a/bpf-sys/libbpf +++ b/bpf-sys/libbpf @@ -1 +1 @@ -Subproject commit 6e6f18ac5d811706b6fa5416c07b22f22d972b32 +Subproject commit 56794b31eea0a6245f194b5915e3ed867be144fe diff --git a/bpf-sys/src/lib.rs b/bpf-sys/src/lib.rs index c01a0348..3723b4c4 100644 --- a/bpf-sys/src/lib.rs +++ b/bpf-sys/src/lib.rs @@ -9,3 +9,10 @@ pub mod headers; pub mod uname; pub mod type_gen; + +// FIXME: Remove libbpf_bindings in favor of libbpf-sys +mod libbpf_bindings { + #![allow(non_camel_case_types)] + #![allow(dead_code)] + include!(concat!(env!("OUT_DIR"), "/libbpf_bindings.rs")); +} diff --git a/bpf-sys/src/type_gen.rs b/bpf-sys/src/type_gen.rs index 096c7daa..8a018b1e 100644 --- a/bpf-sys/src/type_gen.rs +++ b/bpf-sys/src/type_gen.rs @@ -18,10 +18,10 @@ syntax. So macro constants can not be generated from vmlinux image. But system. */ +use super::libbpf_bindings; use libbpf_sys::{ btf, btf__free, btf__get_nr_types, btf__name_by_offset, btf__parse_elf, btf__parse_raw, - btf__type_by_id, btf_dump, btf_dump__dump_type, btf_dump__free, btf_dump__new, btf_dump_opts, - libbpf_find_kernel_btf, __va_list_tag, + btf__type_by_id, btf_dump, btf_dump__dump_type, btf_dump__free, libbpf_find_kernel_btf, }; use libc::{c_char, c_void}; use regex::RegexSet; @@ -29,21 +29,13 @@ use std::env; use std::ffi::{CStr, CString}; use std::fs::File; use std::io::{self, Write}; -use std::mem::{self, MaybeUninit}; +use std::mem; use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd}; use std::path::Path; use std::path::PathBuf; use std::ptr; pub const ENV_VMLINUX_PATH: &'static str = "REDBPF_VMLINUX"; -extern "C" { - fn vdprintf( - __fd: libc::c_int, - __fmt: *const c_char, - __arg: *mut __va_list_tag - ) -> libc::c_int; -} - // only used for RAII struct RawFdWrapper(RawFd); impl Drop for RawFdWrapper { @@ -202,21 +194,16 @@ impl VmlinuxBtfDump { None }; unsafe { - let mut dump_opts = { - let mut uninit = MaybeUninit::::zeroed(); - (*uninit.as_mut_ptr()).__bindgen_anon_1.ctx = &mut rawfd as *mut _ as *mut _; - uninit.assume_init() - }; - let dumpptr = btf_dump__new( - self.btfptr, - None, - &mut dump_opts as *const _ as *mut _, - vdprintf_wrapper as *const _, + let dumpptr = libbpf_bindings::btf_dump__new( + self.btfptr as _, + Some(vdprintf_wrapper), + &mut rawfd as *mut _ as *mut _, + ptr::null(), ); if (dumpptr as isize) < 0 { return Err(TypeGenError::DumpError); } - let dumpptr = BtfDumpWrapper(dumpptr); + let dumpptr = BtfDumpWrapper(dumpptr as _); for type_id in 1..=btf__get_nr_types(self.btfptr) { let btftypeptr = btf__type_by_id(self.btfptr, type_id); let nameptr = btf__name_by_offset(self.btfptr, (*btftypeptr).name_off); @@ -253,27 +240,22 @@ impl Drop for VmlinuxBtfDump { } } +// FIXME: remove libbpf_bindings in favor of libbpf-sys // wrapping vdprintf to get rid of return type -#[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] -unsafe extern "C" fn vdprintf_wrapper( - ctx: *mut c_void, - format: *const c_char, - va_list: *mut __va_list_tag, -) { - let rawfd_wrapper = &*(ctx as *mut RawFdWrapper); - vdprintf(rawfd_wrapper.0, format, va_list); -} - -// wrapping vdprintf to get rid of return type -#[cfg(any(target_arch = "arm", target_arch = "aarch64"))] unsafe extern "C" fn vdprintf_wrapper( ctx: *mut c_void, format: *const c_char, - #[cfg(target_env = "musl")] va_list: super::__isoc_va_list, - #[cfg(not(target_env = "musl"))] va_list: super::__gnuc_va_list, + #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] + #[cfg(target_env = "musl")] + va_list: libbpf_bindings::__isoc_va_list, + #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] + #[cfg(not(target_env = "musl"))] + va_list: libbpf_bindings::__gnuc_va_list, + #[cfg(not(any(target_arch = "arm", target_arch = "aarch64")))] + va_list: *mut libbpf_bindings::__va_list_tag, ) { let rawfd_wrapper = &*(ctx as *mut RawFdWrapper); - vdprintf(rawfd_wrapper.0, format, va_list); + libbpf_bindings::vdprintf(rawfd_wrapper.0, format, va_list); } pub fn get_custom_vmlinux_path() -> Option { From f41b29c1741ddb81f9af304e7364dd7b257296c0 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 18 Feb 2022 01:25:41 +0900 Subject: [PATCH 069/107] Generate binding for struct unix_sock for probes Signed-off-by: Junyeong Jeong --- redbpf-probes/build.rs | 2 ++ redbpf-probes/include/redbpf_helpers.h | 1 + 2 files changed, 3 insertions(+) diff --git a/redbpf-probes/build.rs b/redbpf-probes/build.rs index bf73ef4a..6504a575 100644 --- a/redbpf-probes/build.rs +++ b/redbpf-probes/build.rs @@ -83,6 +83,7 @@ fn generate_bindings_kernel_headers() -> Result<()> { "__sk_.*", "sk_.*", "inet_sock", + "unix_sock", "sockaddr", "sockaddr_in", "in_addr", @@ -181,6 +182,7 @@ fn generate_bindings_vmlinux() -> Result<()> { "^__sk_.*", "^sk_.*", "^inet_sock$", + "^unix_sock$", "^sockaddr$", "^sockaddr_in$", "^in_addr$", diff --git a/redbpf-probes/include/redbpf_helpers.h b/redbpf-probes/include/redbpf_helpers.h index c0fd9509..c7e3c13c 100644 --- a/redbpf-probes/include/redbpf_helpers.h +++ b/redbpf-probes/include/redbpf_helpers.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "xdp.h" #include "bpf_iter.h" From 87fae7c32f9d7a534a8f5732b3f3ca3f91642ffe Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 18 Feb 2022 23:14:22 +0900 Subject: [PATCH 070/107] libbpf is still used to generate bpf helpers by redbpf-probes Signed-off-by: Junyeong Jeong --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index d9cb6a1d..50fc9ea0 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,13 @@ It describes build tests of RedBPF that run inside docker containers. # License +This repository contains code from other software in the following +directories, licensed under their own particular licenses: + + * `bpf-sys/libbpf`: LGPL2 + BSD-2 + +Where '+' means they are dual licensed. + RedBPF and its components, unless otherwise stated, are licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or From c2b4dcb31dfd55ea1fe95573126ae840085f1bc5 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sat, 19 Feb 2022 01:24:42 +0900 Subject: [PATCH 071/107] Fix bug Signed-off-by: Junyeong Jeong --- redbpf/src/lib.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 38967c9d..b6c481ad 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -2369,7 +2369,7 @@ impl<'base, T: Clone> PerCpuArray<'base, T> { let value_size = round_up::(8); let alloc_size = value_size * count; let mut alloc = vec![0u8; alloc_size]; - let mut data = alloc.as_mut_ptr(); + let data = alloc.as_mut_ptr(); for i in 0..count { unsafe { let dst_ptr = data.add(value_size * i) as *mut T; @@ -2857,7 +2857,7 @@ fn bpf_percpu_map_set( let value_size = round_up::(8); let alloc_size = value_size * count; let mut alloc = vec![0u8; alloc_size]; - let mut data = alloc.as_mut_ptr(); + let data = alloc.as_mut_ptr(); for i in 0..count { unsafe { let dst_ptr = data.add(value_size * i) as *mut V; @@ -2866,12 +2866,7 @@ fn bpf_percpu_map_set( } if unsafe { - libbpf_sys::bpf_map_update_elem( - fd, - &mut key as *mut _ as *mut _, - &mut data as *mut _ as *mut _, - 0, - ) + libbpf_sys::bpf_map_update_elem(fd, &mut key as *mut _ as *mut _, data as *mut _, 0) } < 0 { Err(Error::Map) From 092a0b2c5d3e6332b54c2519d652190715e27330 Mon Sep 17 00:00:00 2001 From: Mikhail Trishchenkov Date: Sat, 1 Jan 2022 16:58:26 +0700 Subject: [PATCH 072/107] Add type-safe printk! macro (wrapper for bpf_trace_printk) Signed-off-by: Mikhail Trishchenkov --- redbpf-macros/src/lib.rs | 125 +++++++++++++++++++++++++++++++++++ redbpf-probes/src/helpers.rs | 18 +++++ 2 files changed, 143 insertions(+) diff --git a/redbpf-macros/src/lib.rs b/redbpf-macros/src/lib.rs index 47d0a885..5965db0a 100644 --- a/redbpf-macros/src/lib.rs +++ b/redbpf-macros/src/lib.rs @@ -770,3 +770,128 @@ pub fn task_iter(attrs: TokenStream, item: TokenStream) -> TokenStream { probe_impl("task_iter", attrs, wrapper, name) } + +/// Safe wrapper for bpf_trace_printk helper. +/// +/// Maximum three arguments are accepted, only one of +/// them may be string. +/// +/// Supported formats: +/// * %d - i32 +/// * %u - u32 +/// * %x - u32 (hex) +/// * %s - &std::ffi::CStr +/// * %zd - isize +/// * %zu - usize +/// * %zx - usize (hex) +/// * %ld - ::cty::c_long +/// * %lu - ::cty::c_ulong +/// * %lx - ::cty::c_ulong (hex) +/// * %lld - i64 +/// * %llu - u64 +/// * %llx - u64 (hex) +/// * %p - ::cty::c_void +/// * %% - literal '%' +/// +/// # Example +/// +/// ```no_run +/// #![no_std] +/// #![no_main] +/// use redbpf_macros::printk; +/// # fn main() { +/// printk!("found %d things: %s", num, msg); +/// # } +/// ``` +/// +#[proc_macro] +pub fn printk(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as Args); + let mut macro_args = input.0.iter(); + + // Parse and validate format string. + let fmt_arg = macro_args.next().expect("no format string"); + let fmt_str = match fmt_arg { + Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }) => s.value(), + _ => panic!("expected string literal"), + }; + + let placeholders = parse_format_string(&fmt_str); + if placeholders.len() > 3 { + panic!("maximum 3 arguments to printk! are supported"); + } + if placeholders.iter().filter(|p| matches!(p, FmtPlaceholder::String)).count() > 1 { + panic!("maximum 1 string argument to printk! is supported"); + } + + let args = macro_args.collect::>(); + if args.len() != placeholders.len() { + panic!("number of arguments doesn't match number of placeholders"); + } + + let (_fmt_ty, fmt) = inline_string_literal(&fmt_arg); + let mut tok_args = args.iter().zip(placeholders).map(|(arg, placeholder)| { + match placeholder { + FmtPlaceholder::Number(typ) => quote! { ::core::convert::Into::<#typ>::into(#arg) as u64 }, + FmtPlaceholder::String => quote! { AsRef::<::core::ffi::CStr>::as_ref(#arg).as_ptr() }, + } + }).collect::>(); + + // bpf_trace_printk_raw accepts 3 parameters, pass 0 for left ones. + while tok_args.len() < 3 { + tok_args.push(quote! { 0u64 }); + } + + let tokens = quote! { + ::redbpf_probes::helpers::bpf_trace_printk_raw(&#fmt, #(#tok_args),*) + }; + + tokens.into() +} + +enum FmtPlaceholder { + Number(/* type */ TokenStream2), + String, +} + +fn parse_format_string(fmt: &str) -> Vec { + let mut res = Vec::new(); + let mut iter = fmt.bytes(); + while let Some(ch) = iter.next() { + if ch != b'%' { + continue + } + + match iter.next() { + Some(b'%') => continue, + + Some(b'd') => res.push(FmtPlaceholder::Number(quote!{i32})), + Some(b'u' | b'x') => res.push(FmtPlaceholder::Number(quote!{u32})), + Some(b's') => res.push(FmtPlaceholder::String), + Some(b'z') => match iter.next() { + Some(b'd') => res.push(FmtPlaceholder::Number(quote!{isize})), + Some(b'u' | b'x') => res.push(FmtPlaceholder::Number(quote!{usize})), + Some(c) => panic!("unsupported format placeholder %z{}, expected %zd, %zu or %zx", c), + None => panic!("unfinished format string placeholder %z, expected %zd, %zu or %zx"), + }, + Some(b'l') => match iter.next() { + Some(b'd') => res.push(FmtPlaceholder::Number(quote!{::cty::c_long})), + Some(b'u' | b'x') => res.push(FmtPlaceholder::Number(quote!{::cty::c_ulong})), + Some(b'l') => match iter.next() { + Some(b'd') => res.push(FmtPlaceholder::Number(quote!{i64})), + Some(b'u' | b'x') => res.push(FmtPlaceholder::Number(quote!{u64})), + Some(c) => panic!("unsupported format placeholder %ll{}, expected %lld, %llu or %llx", c), + None => panic!("unfinished format string placeholder %ll, expected %lld, %llu or %llx"), + }, + Some(c) => panic!("unsupported format placeholder %l{}, expected %ld, %lu, %lx, %lld, %llu or %llx", c), + None => panic!("unfinished format string placeholder %l, expected %ld, %lu, %lx, %lld, %llu or %llx"), + }, + Some(b'p') => res.push(FmtPlaceholder::Number(quote!{::cty::c_void})), + Some(c) => panic!("unsupported format placeholder %{}, expected %%, %d, %u, %x, %zd, %zu, %zx, %ld, %lu, %lx, %lld, %llu, %llx or %p", c), + None => panic!("unfinished format string placeholder %, expected %%, %d, %u, %x, %zd, %zu, %zx, %ld, %lu, %lx, %lld, %llu, %llx or %p"), + } + } + res +} diff --git a/redbpf-probes/src/helpers.rs b/redbpf-probes/src/helpers.rs index e734f405..804aa685 100644 --- a/redbpf-probes/src/helpers.rs +++ b/redbpf-probes/src/helpers.rs @@ -152,6 +152,24 @@ pub fn bpf_trace_printk(message: &[u8]) -> ::cty::c_int { } } +#[inline] +pub fn bpf_trace_printk_raw(message: &[u8], arg1: u64, arg2: u64, arg3: u64) -> Result { + let res = unsafe { + let f: unsafe extern "C" fn(fmt: *const ::cty::c_char, fmt_size: __u32, arg1: u64, arg2: u64, arg3: u64) -> ::cty::c_int = + ::core::mem::transmute(6usize); + f( + message.as_ptr() as *const ::cty::c_char, + message.len() as u32, + arg1, arg2, arg3, + ) + }; + return if res >= 0 { + Ok(res) + } else { + Err(()) + } +} + /// Get a pseudo-random number #[inline] pub fn bpf_get_prandom_u32() -> u32 { From 4167e103fd6d9eb3a10f2d9bac95a24cf499cb5e Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 25 Feb 2022 01:03:34 +0900 Subject: [PATCH 073/107] Add printk macro to preludes Signed-off-by: Junyeong Jeong --- redbpf-probes/src/bpf_iter/mod.rs | 2 +- redbpf-probes/src/kprobe/prelude.rs | 2 +- redbpf-probes/src/socket_filter/prelude.rs | 2 +- redbpf-probes/src/sockmap/prelude.rs | 2 +- redbpf-probes/src/uprobe/prelude.rs | 2 +- redbpf-probes/src/xdp/prelude.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/redbpf-probes/src/bpf_iter/mod.rs b/redbpf-probes/src/bpf_iter/mod.rs index c5a48518..4723f586 100644 --- a/redbpf-probes/src/bpf_iter/mod.rs +++ b/redbpf-probes/src/bpf_iter/mod.rs @@ -24,5 +24,5 @@ pub mod prelude { pub use crate::bindings::*; pub use crate::helpers::*; pub use crate::maps::*; - pub use redbpf_macros::{map, program, task_iter}; + pub use redbpf_macros::{map, printk, program, task_iter}; } diff --git a/redbpf-probes/src/kprobe/prelude.rs b/redbpf-probes/src/kprobe/prelude.rs index 46d08c25..6ae0482d 100644 --- a/redbpf-probes/src/kprobe/prelude.rs +++ b/redbpf-probes/src/kprobe/prelude.rs @@ -18,4 +18,4 @@ pub use crate::helpers::*; pub use crate::maps::*; pub use crate::registers::*; pub use cty::*; -pub use redbpf_macros::{kprobe, kretprobe, map, program}; +pub use redbpf_macros::{kprobe, kretprobe, map, printk, program}; diff --git a/redbpf-probes/src/socket_filter/prelude.rs b/redbpf-probes/src/socket_filter/prelude.rs index 48a72418..60cbe064 100644 --- a/redbpf-probes/src/socket_filter/prelude.rs +++ b/redbpf-probes/src/socket_filter/prelude.rs @@ -18,4 +18,4 @@ pub use crate::maps::*; pub use crate::socket::*; pub use crate::socket_filter::*; pub use cty::*; -pub use redbpf_macros::{map, program, socket_filter}; +pub use redbpf_macros::{map, printk, program, socket_filter}; diff --git a/redbpf-probes/src/sockmap/prelude.rs b/redbpf-probes/src/sockmap/prelude.rs index ad0e6986..edece177 100644 --- a/redbpf-probes/src/sockmap/prelude.rs +++ b/redbpf-probes/src/sockmap/prelude.rs @@ -3,4 +3,4 @@ pub use crate::helpers::*; pub use crate::maps::*; pub use crate::socket::{SkAction, SkBuff}; pub use crate::sockmap::*; -pub use redbpf_macros::{map, program, stream_parser, stream_verdict}; +pub use redbpf_macros::{map, printk, program, stream_parser, stream_verdict}; diff --git a/redbpf-probes/src/uprobe/prelude.rs b/redbpf-probes/src/uprobe/prelude.rs index b313c7d9..6d2cab42 100644 --- a/redbpf-probes/src/uprobe/prelude.rs +++ b/redbpf-probes/src/uprobe/prelude.rs @@ -17,4 +17,4 @@ pub use crate::helpers::*; pub use crate::maps::*; pub use crate::registers::*; pub use cty::*; -pub use redbpf_macros::{map, program, uprobe, uretprobe}; +pub use redbpf_macros::{map, printk, program, uprobe, uretprobe}; diff --git a/redbpf-probes/src/xdp/prelude.rs b/redbpf-probes/src/xdp/prelude.rs index 3227590e..2d0144da 100644 --- a/redbpf-probes/src/xdp/prelude.rs +++ b/redbpf-probes/src/xdp/prelude.rs @@ -21,4 +21,4 @@ pub use crate::xdp::PerfMap; pub use crate::xdp::*; pub use cty::*; -pub use redbpf_macros::{map, program, xdp}; +pub use redbpf_macros::{map, printk, program, xdp}; From 9fdaaa2c1f329bc536c288ca9835783d4ea0fcd6 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 25 Feb 2022 01:04:10 +0900 Subject: [PATCH 074/107] Improve example code using printk! macro Signed-off-by: Junyeong Jeong --- examples/example-probes/src/echo/main.rs | 48 ++---------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/examples/example-probes/src/echo/main.rs b/examples/example-probes/src/echo/main.rs index 1a354f48..5cc57e18 100644 --- a/examples/example-probes/src/echo/main.rs +++ b/examples/example-probes/src/echo/main.rs @@ -21,7 +21,7 @@ fn parse_message_boundary(skb: SkBuff) -> StreamParserResult { let addr = (skb.skb as usize + offset_of!(__sk_buff, len)) as *const u32; ptr::read(addr) }; - trace_print(b"length: ", len); + printk!("length: %u", len); Ok(StreamParserAction::MessageLength(len)) } @@ -33,8 +33,8 @@ fn verdict(skb: SkBuff) -> SkAction { (ptr::read(ip_addr), ptr::read(port_addr)) }; - trace_print(b"ip as BE: ", ip); - trace_print(b"port as BE: ", port); + printk!("ip: %x", u32::from_be(ip)); + printk!("port: %u", u32::from_be(port)); let mut idx = 0; match unsafe { @@ -50,45 +50,3 @@ fn verdict(skb: SkBuff) -> SkAction { Err(_) => SkAction::Drop, } } - -fn hex_u8(v: u8, buf: &mut [u8]) { - let w = v / 0x10; - buf[0] = if w < 0xa { w + b'0' } else { w - 0xa + b'a' }; - let u = v % 0x10; - buf[1] = if u < 0xa { u + b'0' } else { u - 0xa + b'a' }; -} - -fn hex_bytes(arr: &[u8], buf: &mut [u8]) -> usize { - let mut pos = 0; - for (i, b) in arr.iter().enumerate() { - if i != 0 { - buf[pos] = b' '; - pos += 1; - } - hex_u8(*b, &mut buf[pos..pos + 2]); - pos += 2; - } - pos -} - -fn trace_print(msg: &[u8], x: T) { - let mut buf = [0u8; 128]; - let mut pos = 0; - for c in msg { - buf[pos] = *c; - pos += 1; - } - - let ptr = &x as *const T as *const usize as usize; - let sz = mem::size_of::(); - let mut arr = [0u8; 64]; - for i in 0..sz { - arr[i] = unsafe { ptr::read((ptr + i) as *const usize as *const u8) }; - } - - pos += hex_bytes(&arr[..sz], &mut buf[pos..]); - buf[pos] = b'\n'; - pos += 2; - - bpf_trace_printk(&buf[..pos]); -} From d09b0db72efbc9f206328a3e4dbbfadac075faca Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sat, 19 Feb 2022 22:07:52 +0900 Subject: [PATCH 075/107] Add gentoo build test Signed-off-by: Junyeong Jeong --- .github/workflows/build-test.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index fb247909..f9f1be82 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -242,6 +242,35 @@ jobs: && cargo build --bin cargo-bpf \ && cargo build --examples' + build-on-x86_64-gentoo: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Build host info + run: | + uname -a + cat /etc/os-release + - + name: Initialize git submodules + run: | + git submodule update --init --recursive + - + name: Build RedBPF with the kernel headers on x86_64 Gentoo + run: | + docker run --privileged \ + -v $PWD:/build \ + -w /build \ + $REDBPF_IMAGE_NAME:latest-x86_64-gentoo \ + bash -c 'export KERNEL_SOURCE=$(echo /lib/modules/*) \ + && echo KERNEL_SOURCE=$KERNEL_SOURCE \ + && cargo clean \ + && cargo build \ + && cargo build --bin cargo-bpf \ + && cargo build --examples' + build-on-aarch64-ubuntu-2104: runs-on: ubuntu-latest steps: @@ -514,6 +543,7 @@ jobs: # - build-on-x86_64-alpine315 - build-on-x86_64-archlinux - build-on-x86_64-ubuntu-2004 + - build-on-x86_64-gentoo - build-on-aarch64-ubuntu-2104 - build-on-aarch64-debian11 - build-on-aarch64-fedora35-header From 960490367c7a3b8ef59003e1c61b2b32c26c1aa8 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sat, 26 Feb 2022 00:44:58 +0900 Subject: [PATCH 076/107] Avoid linking of BPF programs Linking does not create any useful stuff. ELF object file that contains BPF programs are already generated before the linking step so it is okay to ignore the linking procedure. Instead of executing ld to link BPF programs, run the `true` command to do nothing successfully. Signed-off-by: Junyeong Jeong --- cargo-bpf/src/build.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cargo-bpf/src/build.rs b/cargo-bpf/src/build.rs index 5cc1071e..ae25d4cc 100644 --- a/cargo-bpf/src/build.rs +++ b/cargo-bpf/src/build.rs @@ -197,10 +197,7 @@ fn build_probe( .arg("--") .arg("--cfg") .arg(version) - .args( - "--emit=llvm-bc -C panic=abort -C lto -C link-arg=-nostartfiles -C opt-level=3" - .split(' '), - ) + .args("--emit=llvm-bc -C panic=abort -C lto -C opt-level=3 -C linker=true".split(' ')) // /usr/bin/true or /bin/true .arg("-g") // To generate .BTF section .arg("-o") .arg(artifacts_dir.join(probe).to_str().unwrap()) From ae411faa7c53745ed64a9b0d4fb48e70908f5dd9 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sat, 26 Feb 2022 00:52:53 +0900 Subject: [PATCH 077/107] Mention pkg-config as installation requirement Signed-off-by: Junyeong Jeong --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 746bd31c..12c7a6ca 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ Install LLVM 13 and the Linux kernel headers lsb-release \ libelf-dev \ linux-headers-generic \ + pkg-config \ && wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 13 && rm -f ./llvm.sh # llvm-config-13 --version | grep 13 ``` @@ -102,6 +103,7 @@ Install LLVM 13 and the Linux kernel headers kernel-devel \ elfutils-libelf-devel \ make \ + pkg-config \ zstd # llvm-config --version | grep 13 ``` @@ -118,6 +120,7 @@ Install LLVM 13 and the Linux kernel headers libffi \ clang \ make \ + pkg-config \ linux-headers \ linux # llvm-config --version | grep -q '^13' From 7d1db50c585d5b40fa907d033dddb8ecdf579584 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sat, 26 Feb 2022 00:58:05 +0900 Subject: [PATCH 078/107] Fix .bss section Signed-off-by: Junyeong Jeong --- redbpf/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index b6c481ad..dc75b8a7 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -1430,10 +1430,12 @@ impl<'a> ModuleBuilder<'a> { (hdr::SHT_PROGBITS, Some("license"), _) => { license = zero::read_str(content).to_string() } + (hdr::SHT_NOBITS, Some(name @ ".bss"), None) => { + let map_builder = MapBuilder::with_section_data(name, &content)?; + map_builders.insert(shndx, map_builder); + } (hdr::SHT_PROGBITS, Some(name), None) - if name == ".bss" - || name.starts_with(".data") - || name.starts_with(".rodata") => + if name.starts_with(".data") || name.starts_with(".rodata") => { let map_builder = MapBuilder::with_section_data(name, &content)?; map_builders.insert(shndx, map_builder); From 4ddccaef5198cee1a657cb80c5539e0ba64ebac0 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Sun, 27 Feb 2022 18:08:32 +0900 Subject: [PATCH 079/107] Improve the echo example program that uses SockMap Update sockmap with a new socket descriptor before any data is read from the descriptor or before some epoll event about the descriptor occurs. Because setting sockmap fails if those events happens before. Read all remaining data from the socket and write it to the client to serve the echo task. This happens when setting sockmap fails because the connection is already half closed. Signed-off-by: Junyeong Jeong --- examples/example-probes/src/echo/main.rs | 45 +++--- examples/example-userspace/examples/echo.rs | 153 ++++++++++---------- redbpf/src/lib.rs | 4 + 3 files changed, 102 insertions(+), 100 deletions(-) diff --git a/examples/example-probes/src/echo/main.rs b/examples/example-probes/src/echo/main.rs index 5cc57e18..c13d1c36 100644 --- a/examples/example-probes/src/echo/main.rs +++ b/examples/example-probes/src/echo/main.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +/// This example program shows how to redirect packet using sockmap. use core::mem; use core::ptr; use memoffset::offset_of; @@ -16,37 +17,35 @@ static mut ECHO_SOCKMAP: SockMap = SockMap::with_max_entries(10240); static mut IDX_MAP: HashMap = HashMap::with_max_entries(1024); #[stream_parser] -fn parse_message_boundary(skb: SkBuff) -> StreamParserResult { - let len: u32 = unsafe { - let addr = (skb.skb as usize + offset_of!(__sk_buff, len)) as *const u32; - ptr::read(addr) - }; - printk!("length: %u", len); +unsafe fn parse_message_boundary(skb: SkBuff) -> StreamParserResult { + let len = (*skb.skb).len; + printk!("message length: %u", len); Ok(StreamParserAction::MessageLength(len)) } #[stream_verdict] -fn verdict(skb: SkBuff) -> SkAction { - let (ip, port) = unsafe { - let ip_addr = (skb.skb as usize + offset_of!(__sk_buff, remote_ip4)) as *const u32; - let port_addr = (skb.skb as usize + offset_of!(__sk_buff, remote_port)) as *const u32; - (ptr::read(ip_addr), ptr::read(port_addr)) - }; +unsafe fn verdict(skb: SkBuff) -> SkAction { + let ip = (*skb.skb).remote_ip4; + let port = (*skb.skb).remote_port; printk!("ip: %x", u32::from_be(ip)); printk!("port: %u", u32::from_be(port)); - let mut idx = 0; - match unsafe { - let key = IdxMapKey { addr: ip, port }; - IDX_MAP.get(&key) - } { - None => return SkAction::Drop, - Some(index) => idx = *index, - } + let idx = if let Some(index) = IDX_MAP.get(&IdxMapKey { addr: ip, port }) { + *index + } else { + printk!("drop packet since addr not found in idx map"); + return SkAction::Drop; + }; - match unsafe { ECHO_SOCKMAP.redirect(skb.skb as *mut _, idx) } { - Ok(_) => SkAction::Pass, - Err(_) => SkAction::Drop, + match ECHO_SOCKMAP.redirect(skb.skb as *mut _, idx) { + Ok(_) => { + printk!("redirect success"); + SkAction::Pass + } + Err(_) => { + printk!("drop packet since redirect failed"); + SkAction::Drop + } } } diff --git a/examples/example-userspace/examples/echo.rs b/examples/example-userspace/examples/echo.rs index 7b626ace..0882204c 100644 --- a/examples/example-userspace/examples/echo.rs +++ b/examples/example-userspace/examples/echo.rs @@ -3,13 +3,13 @@ use std::env; use std::net::{IpAddr, SocketAddr}; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::AsRawFd; use std::process; -use tokio::io::AsyncReadExt; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpListener; use tokio::sync::mpsc; use tokio::task; -use tracing::{error, Level}; +use tracing::{debug, error, Level}; use tracing_subscriber::FmtSubscriber; use probes::echo::IdxMapKey; @@ -17,7 +17,6 @@ use redbpf::load::Loader; use redbpf::{HashMap, SockMap}; #[derive(Debug)] enum Command { - Set { fd: RawFd, key: IdxMapKey }, Delete { key: IdxMapKey }, } @@ -40,91 +39,91 @@ async fn main() { .parse() .expect("invalid port number"); + let loaded = Loader::load(include_bytes!(concat!( + env!("OUT_DIR"), + "/target/bpf/programs/echo/echo.elf" + ))) + .expect("error loading BPF program"); + let mut echo_sockmap = + SockMap::new(loaded.map("echo_sockmap").expect("sockmap not found")).unwrap(); + loaded + .stream_parsers() + .next() + .unwrap() + .attach_sockmap(&echo_sockmap) + .expect("Attaching sockmap failed"); + loaded + .stream_verdicts() + .next() + .unwrap() + .attach_sockmap(&echo_sockmap) + .expect("Attaching sockmap failed"); + let idx_map = + HashMap::::new(loaded.map("idx_map").expect("idx map not found")).unwrap(); + + let mut counter: u32 = 0; + let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], port))) + .await + .unwrap(); let (tx, mut rx) = mpsc::channel(128); - let local = task::LocalSet::new(); - local.spawn_local(async move { - let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], port))) - .await - .unwrap(); - loop { - let (mut tcp_stream, client_addr) = listener.accept().await.unwrap(); - let fd = tcp_stream.as_raw_fd(); - if let IpAddr::V4(ipaddr) = client_addr.ip() { - println!("new client: {:?}, fd: {}", client_addr, fd); + loop { + tokio::select! { + Ok((mut tcp_stream, client_addr)) = listener.accept() => { + let fd = tcp_stream.as_raw_fd(); + let ipaddr = if let IpAddr::V4(ipaddr) = client_addr.ip() { + ipaddr + } else { + error!("not an IPv4 address: {:?}", client_addr); + continue; + }; + debug!("new client: {:?}, fd: {}", client_addr, fd); let key = IdxMapKey { // use big endian because __sk_buff.remote_ip4 and // __sk_buff.remote_port are big endian - addr: u32::to_be(u32::from(ipaddr)), - port: u32::to_be(client_addr.port().into()), + addr: u32::from(ipaddr).to_be(), + port: (client_addr.port() as u32).to_be(), }; - tx.send(Command::Set { fd, key }).await.unwrap(); + idx_map.set(key, counter); + // NOTE: Sockmap should be set before any data is read from the + // socket descriptor or before some epoll event of the socket + // descriptor occurs. Otherwise setting sockmap results in + // EOPNOTSUPP error. So setting sockmap here asap. + let _ = echo_sockmap + .set(counter, fd) + .map_err(|_| error!("SockMap::set failed. Perhaps the socket is already half closed")); + counter += 1; + + // Keep tcp_stream not to be dropped. And notify connection + // close to delete itself from the sockmap let tx = tx.clone(); - task::spawn_local(async move { - let mut buf = [0; 0]; + task::spawn(async move { + let mut buf = Vec::new(); + tcp_stream.write(&buf).await.unwrap(); // Even though it awaits for something to read, it only - // ends after the connection is hung up. Because all data - // is echo-ed by BPF program, the user level socket does - // not receive anything. - tcp_stream.read(&mut buf[..]).await.unwrap(); - println!("delete client: {:?} fd: {}", client_addr, fd); + // ends after the connection is half closed by the peer. + // Normally the read call reads nothing but some data can + // be read if setting sockmap had failed. So write all + // buffer to echo it. + tcp_stream.read_to_end(&mut buf).await.unwrap(); + tcp_stream.write_all(&buf).await.unwrap(); + debug!("delete client: {:?} fd: {}", client_addr, fd); tx.send(Command::Delete { key }).await.unwrap(); }); - } else { - error!("error: not an IPv4 address: {:?}", client_addr); } - } - }); - - local.spawn_local(async move { - let loaded = Loader::load(include_bytes!(concat!( - env!("OUT_DIR"), - "/target/bpf/programs/echo/echo.elf" - ))) - .expect("error loading BPF program"); - let mut echo_sockmap = - SockMap::new(loaded.map("echo_sockmap").expect("sockmap not found")).unwrap(); - loaded - .stream_parsers() - .next() - .unwrap() - .attach_sockmap(&echo_sockmap) - .expect("Attaching sockmap failed"); - loaded - .stream_verdicts() - .next() - .unwrap() - .attach_sockmap(&echo_sockmap) - .expect("Attaching sockmap failed"); - let idx_map = - HashMap::::new(loaded.map("idx_map").expect("idx map not found")) - .unwrap(); - let mut counter: u32 = 0; - while let Some(cmd) = rx.recv().await { - match cmd { - Command::Set { fd, key } => { - unsafe { - let addr: [u8; 4] = std::mem::transmute(key.addr); - let port: [u8; 4] = std::mem::transmute(key.port); - println!( - "ipv4: {:x} {:x} {:x} {:x} port: {:x} {:x} {:x} {:x}", - addr[0], addr[1], addr[2], addr[3], port[0], port[1], port[2], port[3] - ); - } - - idx_map.set(key, counter); - echo_sockmap.set(counter, fd).unwrap(); - counter += 1; - } - Command::Delete { key } => { - if let Some(idx) = idx_map.get(key) { - idx_map.delete(key); - // This can be failed when the fd had been closed - let _ = echo_sockmap.delete(idx); + Some(cmd) = rx.recv() => { + match cmd { + Command::Delete { key } => { + if let Some(idx) = idx_map.get(key) { + // This can be failed when the fd had been closed + let _ = echo_sockmap.delete(idx); + idx_map.delete(key); + } } } } + _ = tokio::signal::ctrl_c() => { + break; + } } - }); - - let _ = local.run_until(tokio::signal::ctrl_c()).await; + } } diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index b6c481ad..b6b94aa0 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -2631,6 +2631,10 @@ impl<'a> SockMap<'a> { ) }; if ret < 0 { + error!( + "error on updating sockmap: {:?}", + io::Error::last_os_error() + ); Err(Error::Map) } else { Ok(()) From c47255c0bcb1035a1b4312162b293c4974e2e9e9 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Fri, 4 Mar 2022 00:28:42 +0900 Subject: [PATCH 080/107] Run rustfmt to fix all source files Signed-off-by: Junyeong Jeong --- bpf-sys/src/uname.rs | 25 ++++++---- examples/example-probes/src/lib.rs | 2 +- examples/example-userspace/examples/p0f.rs | 10 ++-- redbpf-macros/src/lib.rs | 25 ++++++---- redbpf-probes/src/helpers.rs | 26 +++++----- redbpf-probes/src/xdp/devmap.rs | 14 +++--- redbpf-probes/src/xdp/mod.rs | 2 +- redbpf-probes/src/xdp/xskmap.rs | 15 +++--- redbpf-tools/probes/src/iotop/mod.rs | 6 +-- redbpf/src/lib.rs | 56 ++++++++-------------- redbpf/src/xdp.rs | 6 +-- 11 files changed, 94 insertions(+), 93 deletions(-) diff --git a/bpf-sys/src/uname.rs b/bpf-sys/src/uname.rs index 44cfe6c3..fb803e9f 100644 --- a/bpf-sys/src/uname.rs +++ b/bpf-sys/src/uname.rs @@ -5,12 +5,12 @@ // http://opensource.org/licenses/MIT>, at your option. This file may not be // copied, modified, or distributed except according to those terms. -use std::os::raw::c_char; use std::ffi::CStr; +use std::fs; use std::mem; +use std::os::raw::c_char; use std::str::from_utf8_unchecked; use std::str::FromStr; -use std::fs; #[allow(clippy::result_unit_err)] pub fn uname() -> Result<::libc::utsname, ()> { @@ -31,9 +31,7 @@ pub fn get_kernel_internal_version() -> Option { to_str(&uname().ok()?.release).into() }; - parse_version(&version).map(|(major, minor, patch)| { - major << 16 | minor << 8 | patch - }) + parse_version(&version).map(|(major, minor, patch)| major << 16 | minor << 8 | patch) } #[allow(clippy::result_unit_err)] @@ -67,11 +65,14 @@ fn parse_version_signature(signature: &str) -> Option { fn parse_version(version: &str) -> Option<(u32, u32, u32)> { if let Some(version) = version.splitn(2, '-').next() { if let Some(version) = version.splitn(2, '+').next() { - let parts: Vec<_> = version.splitn(3, '.').filter_map(|v| u32::from_str(v).ok()).collect(); + let parts: Vec<_> = version + .splitn(3, '.') + .filter_map(|v| u32::from_str(v).ok()) + .collect(); if parts.len() != 3 { return None; } - return Some((parts[0], parts[1], parts[2])) + return Some((parts[0], parts[1], parts[2])); } } @@ -98,8 +99,14 @@ mod test { #[test] fn test_parse_version_signature() { - assert_eq!(parse_version_signature("Ubuntu 4.15.0-55.60-generic 4.15.18"), Some("4.15.18".into())); - assert_eq!(parse_version_signature("Ubuntu 4.15.0-55.60-generic 4.15.18 foo"), None); + assert_eq!( + parse_version_signature("Ubuntu 4.15.0-55.60-generic 4.15.18"), + Some("4.15.18".into()) + ); + assert_eq!( + parse_version_signature("Ubuntu 4.15.0-55.60-generic 4.15.18 foo"), + None + ); assert_eq!(parse_version_signature("Ubuntu 4.15.0-55.60-generic"), None); } } diff --git a/examples/example-probes/src/lib.rs b/examples/example-probes/src/lib.rs index aa7df1ed..743880c8 100644 --- a/examples/example-probes/src/lib.rs +++ b/examples/example-probes/src/lib.rs @@ -14,7 +14,7 @@ pub mod bindings; pub mod echo; pub mod hashmaps; pub mod mallocstacks; +pub mod p0f; pub mod tasks; pub mod tcp_lifetime; pub mod vfsreadlat; -pub mod p0f; diff --git a/examples/example-userspace/examples/p0f.rs b/examples/example-userspace/examples/p0f.rs index 442ba39c..a8fd7706 100644 --- a/examples/example-userspace/examples/p0f.rs +++ b/examples/example-userspace/examples/p0f.rs @@ -16,7 +16,10 @@ async fn main() -> std::result::Result<(), String> { let mut loaded = Loader::load(probe_code()).map_err(|err| format!("{:?}", err))?; for interface in &interfaces { - println!("Attach p0f on interface: {} with mode {:?}", interface, xdp_mode); + println!( + "Attach p0f on interface: {} with mode {:?}", + interface, xdp_mode + ); for prog in loaded.xdps_mut() { prog.attach_xdp(interface, xdp_mode) .map_err(|err| format!("{:?}", err))?; @@ -28,8 +31,9 @@ async fn main() -> std::result::Result<(), String> { for event in events { match name.as_str() { "tcp_signatures" => { - let tcp_sig = - unsafe { std::ptr::read_unaligned(event.as_ptr() as *const TcpSignature) }; + let tcp_sig = unsafe { + std::ptr::read_unaligned(event.as_ptr() as *const TcpSignature) + }; println!("tcp_signature = {:?}", tcp_sig); } diff --git a/redbpf-macros/src/lib.rs b/redbpf-macros/src/lib.rs index 5965db0a..e0a33e72 100644 --- a/redbpf-macros/src/lib.rs +++ b/redbpf-macros/src/lib.rs @@ -775,7 +775,7 @@ pub fn task_iter(attrs: TokenStream, item: TokenStream) -> TokenStream { /// /// Maximum three arguments are accepted, only one of /// them may be string. -/// +/// /// Supported formats: /// * %d - i32 /// * %u - u32 @@ -822,7 +822,12 @@ pub fn printk(input: TokenStream) -> TokenStream { if placeholders.len() > 3 { panic!("maximum 3 arguments to printk! are supported"); } - if placeholders.iter().filter(|p| matches!(p, FmtPlaceholder::String)).count() > 1 { + if placeholders + .iter() + .filter(|p| matches!(p, FmtPlaceholder::String)) + .count() + > 1 + { panic!("maximum 1 string argument to printk! is supported"); } @@ -832,12 +837,16 @@ pub fn printk(input: TokenStream) -> TokenStream { } let (_fmt_ty, fmt) = inline_string_literal(&fmt_arg); - let mut tok_args = args.iter().zip(placeholders).map(|(arg, placeholder)| { - match placeholder { - FmtPlaceholder::Number(typ) => quote! { ::core::convert::Into::<#typ>::into(#arg) as u64 }, + let mut tok_args = args + .iter() + .zip(placeholders) + .map(|(arg, placeholder)| match placeholder { + FmtPlaceholder::Number(typ) => { + quote! { ::core::convert::Into::<#typ>::into(#arg) as u64 } + } FmtPlaceholder::String => quote! { AsRef::<::core::ffi::CStr>::as_ref(#arg).as_ptr() }, - } - }).collect::>(); + }) + .collect::>(); // bpf_trace_printk_raw accepts 3 parameters, pass 0 for left ones. while tok_args.len() < 3 { @@ -861,7 +870,7 @@ fn parse_format_string(fmt: &str) -> Vec { let mut iter = fmt.bytes(); while let Some(ch) = iter.next() { if ch != b'%' { - continue + continue; } match iter.next() { diff --git a/redbpf-probes/src/helpers.rs b/redbpf-probes/src/helpers.rs index 804aa685..47aa28e4 100644 --- a/redbpf-probes/src/helpers.rs +++ b/redbpf-probes/src/helpers.rs @@ -155,19 +155,22 @@ pub fn bpf_trace_printk(message: &[u8]) -> ::cty::c_int { #[inline] pub fn bpf_trace_printk_raw(message: &[u8], arg1: u64, arg2: u64, arg3: u64) -> Result { let res = unsafe { - let f: unsafe extern "C" fn(fmt: *const ::cty::c_char, fmt_size: __u32, arg1: u64, arg2: u64, arg3: u64) -> ::cty::c_int = - ::core::mem::transmute(6usize); + let f: unsafe extern "C" fn( + fmt: *const ::cty::c_char, + fmt_size: __u32, + arg1: u64, + arg2: u64, + arg3: u64, + ) -> ::cty::c_int = ::core::mem::transmute(6usize); f( message.as_ptr() as *const ::cty::c_char, message.len() as u32, - arg1, arg2, arg3, + arg1, + arg2, + arg3, ) }; - return if res >= 0 { - Ok(res) - } else { - Err(()) - } + return if res >= 0 { Ok(res) } else { Err(()) }; } /// Get a pseudo-random number @@ -229,11 +232,8 @@ pub fn bpf_perf_event_output( #[inline] pub fn bpf_redirect_map(map: *mut c_void, key: u32, flags: u64) -> i64 { unsafe { - let f: unsafe extern "C" fn( - map: *mut c_void, - key: u32, - flags: u64, - ) -> i64 = ::core::mem::transmute(51usize); + let f: unsafe extern "C" fn(map: *mut c_void, key: u32, flags: u64) -> i64 = + ::core::mem::transmute(51usize); f(map, key, flags) } } diff --git a/redbpf-probes/src/xdp/devmap.rs b/redbpf-probes/src/xdp/devmap.rs index cd2f6a82..4c0dfda5 100644 --- a/redbpf-probes/src/xdp/devmap.rs +++ b/redbpf-probes/src/xdp/devmap.rs @@ -1,9 +1,8 @@ -use core::mem; -use cty::c_void; use crate::xdp::{ - bpf_map_def, bpf_map_type_BPF_MAP_TYPE_DEVMAP, XdpAction, - prelude::bpf_redirect_map, + bpf_map_def, bpf_map_type_BPF_MAP_TYPE_DEVMAP, prelude::bpf_redirect_map, XdpAction, }; +use core::mem; +use cty::c_void; /// Device map. /// @@ -37,12 +36,13 @@ impl DevMap { pub fn redirect(&mut self, key: u32) -> Result<(), ()> { let res = bpf_redirect_map( &mut self.def as *mut _ as *mut c_void, - key, XdpAction::Aborted as u64 + key, + XdpAction::Aborted as u64, ); if res == XdpAction::Redirect as i64 { - Ok(()) + Ok(()) } else { - Err(()) + Err(()) } } } diff --git a/redbpf-probes/src/xdp/mod.rs b/redbpf-probes/src/xdp/mod.rs index b230a3d4..e6e6efb8 100644 --- a/redbpf-probes/src/xdp/mod.rs +++ b/redbpf-probes/src/xdp/mod.rs @@ -34,8 +34,8 @@ fn block_port_80(ctx: XdpContext) -> XdpResult { } ``` */ -pub mod prelude; mod devmap; +pub mod prelude; mod xskmap; pub use devmap::DevMap; diff --git a/redbpf-probes/src/xdp/xskmap.rs b/redbpf-probes/src/xdp/xskmap.rs index 99e44fd8..dbe85ffe 100644 --- a/redbpf-probes/src/xdp/xskmap.rs +++ b/redbpf-probes/src/xdp/xskmap.rs @@ -1,9 +1,8 @@ -use core::mem; -use cty::c_void; use crate::xdp::{ - bpf_map_def, bpf_map_type_BPF_MAP_TYPE_XSKMAP, XdpAction, - prelude::bpf_redirect_map, + bpf_map_def, bpf_map_type_BPF_MAP_TYPE_XSKMAP, prelude::bpf_redirect_map, XdpAction, }; +use core::mem; +use cty::c_void; /// AF_XDP socket map. /// @@ -37,13 +36,13 @@ impl XskMap { pub fn redirect(&mut self, key: u32) -> Result<(), ()> { let res = bpf_redirect_map( &mut self.def as *mut _ as *mut c_void, - key, XdpAction::Aborted as u64 + key, + XdpAction::Aborted as u64, ); if res == XdpAction::Redirect as i64 { - Ok(()) + Ok(()) } else { - Err(()) + Err(()) } } } - diff --git a/redbpf-tools/probes/src/iotop/mod.rs b/redbpf-tools/probes/src/iotop/mod.rs index 065cdeea..db6f4ba0 100644 --- a/redbpf-tools/probes/src/iotop/mod.rs +++ b/redbpf-tools/probes/src/iotop/mod.rs @@ -4,7 +4,7 @@ use cty::*; #[repr(C)] pub struct Process { pub pid: u64, - pub comm: [c_char; 16] + pub comm: [c_char; 16], } #[derive(Clone, Debug)] @@ -21,5 +21,5 @@ pub struct CounterKey { pub struct Counter { pub bytes: u64, pub us: u64, - pub io: u64 -} \ No newline at end of file + pub io: u64, +} diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 1fd76d05..3e4afc8d 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -55,16 +55,14 @@ pub mod sys; pub mod xdp; pub use bpf_sys::uname; +use goblin::elf::{reloc::RelocSection, section_header as hdr, Elf, SectionHeader, Sym}; use libbpf_sys::{ - BPF_SK_LOOKUP, BPF_SK_SKB_STREAM_PARSER, - BPF_SK_SKB_STREAM_VERDICT, BPF_TRACE_ITER, bpf_create_map_attr, - bpf_create_map_xattr, bpf_insn, bpf_iter_create, bpf_link_create, bpf_load_program_xattr, - bpf_map_def, bpf_map_info, BPF_MAP_TYPE_ARRAY, BPF_MAP_TYPE_HASH, - BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH, - BPF_MAP_TYPE_PERCPU_ARRAY, BPF_MAP_TYPE_PERCPU_HASH, - BPF_MAP_TYPE_PERF_EVENT_ARRAY, bpf_prog_type, BPF_ANY, + bpf_create_map_attr, bpf_create_map_xattr, bpf_insn, bpf_iter_create, bpf_link_create, + bpf_load_program_xattr, bpf_map_def, bpf_map_info, bpf_prog_type, BPF_ANY, BPF_MAP_TYPE_ARRAY, + BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH, + BPF_MAP_TYPE_PERCPU_ARRAY, BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_PERF_EVENT_ARRAY, + BPF_SK_LOOKUP, BPF_SK_SKB_STREAM_PARSER, BPF_SK_SKB_STREAM_VERDICT, BPF_TRACE_ITER, }; -use goblin::elf::{reloc::RelocSection, section_header as hdr, Elf, SectionHeader, Sym}; use libc::{self, pid_t}; use std::collections::HashMap as RSHashMap; @@ -531,9 +529,7 @@ impl Program { use Program::*; match self { - KProbe(_) | KRetProbe(_) | UProbe(_) | URetProbe(_) => { - libbpf_sys::BPF_PROG_TYPE_KPROBE - } + KProbe(_) | KRetProbe(_) | UProbe(_) | URetProbe(_) => libbpf_sys::BPF_PROG_TYPE_KPROBE, XDP(_) => libbpf_sys::BPF_PROG_TYPE_XDP, SocketFilter(_) => libbpf_sys::BPF_PROG_TYPE_SOCKET_FILTER, TracePoint(_) => libbpf_sys::BPF_PROG_TYPE_TRACEPOINT, @@ -670,7 +666,8 @@ impl Program { let mut buf_vec = vec![0; vec_len]; let log_buffer: MutDataPtr = buf_vec.as_mut_ptr(); let buf_size = buf_vec.capacity() * mem::size_of_val(unsafe { &*log_buffer }); - let fd = unsafe { libbpf_sys::bpf_load_program_xattr(&attr, log_buffer, buf_size as u64) }; + let fd = + unsafe { libbpf_sys::bpf_load_program_xattr(&attr, log_buffer, buf_size as u64) }; if fd >= 0 { warn!( "bpf_load_program_xattr had failed but it unexpectedly succeeded while reproducing the error" @@ -2571,14 +2568,8 @@ impl StreamParser { let attach_fd = sock_map.base.fd; let prog_fd = self.common.fd.unwrap(); - let ret = unsafe { - libbpf_sys::bpf_prog_attach( - prog_fd, - attach_fd, - BPF_SK_SKB_STREAM_PARSER, - 0, - ) - }; + let ret = + unsafe { libbpf_sys::bpf_prog_attach(prog_fd, attach_fd, BPF_SK_SKB_STREAM_PARSER, 0) }; if ret < 0 { Err(Error::BPF) } else { @@ -2603,12 +2594,7 @@ impl StreamVerdict { let prog_fd = self.common.fd.unwrap(); let ret = unsafe { - libbpf_sys::bpf_prog_attach( - prog_fd, - attach_fd, - BPF_SK_SKB_STREAM_VERDICT, - 0, - ) + libbpf_sys::bpf_prog_attach(prog_fd, attach_fd, BPF_SK_SKB_STREAM_VERDICT, 0) }; if ret < 0 { Err(Error::BPF) @@ -2700,14 +2686,8 @@ impl Iterator for BPFIter { impl TaskIter { fn create_link(&mut self) -> Result<()> { - let link_fd = unsafe { - bpf_link_create( - self.common.fd.unwrap(), - 0, - BPF_TRACE_ITER, - ptr::null(), - ) - }; + let link_fd = + unsafe { bpf_link_create(self.common.fd.unwrap(), 0, BPF_TRACE_ITER, ptr::null()) }; if link_fd < 0 { error!("Error on bpf_link_create"); return Err(Error::BPF); @@ -2838,8 +2818,9 @@ fn bpf_map_get_next_key(fd: RawFd, key: Option) -> Option { } } else { let mut key = MaybeUninit::::zeroed(); - if unsafe { libbpf_sys::bpf_map_get_next_key(fd, ptr::null(), &mut key as *mut _ as *mut _) } - < 0 + if unsafe { + libbpf_sys::bpf_map_get_next_key(fd, ptr::null(), &mut key as *mut _ as *mut _) + } < 0 { None } else { @@ -2889,7 +2870,8 @@ fn bpf_percpu_map_get(fd: RawFd, mut key: K) -> Option Date: Fri, 4 Mar 2022 00:32:39 +0900 Subject: [PATCH 081/107] Fix echo example Trigger stream parser to process packets that were already received by tcp receive queue before echo sockmap was set. Setting SO_RCVLOWAT calls tcp_set_rcvlowat in linux/net/ipv4/tcp.c of the Linux kernel and then the function calls tcp_data_ready. Signed-off-by: Junyeong Jeong --- examples/example-userspace/examples/echo.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/example-userspace/examples/echo.rs b/examples/example-userspace/examples/echo.rs index 0882204c..7fcb36e1 100644 --- a/examples/example-userspace/examples/echo.rs +++ b/examples/example-userspace/examples/echo.rs @@ -93,18 +93,29 @@ async fn main() { .map_err(|_| error!("SockMap::set failed. Perhaps the socket is already half closed")); counter += 1; + // NOTE: Call setsockopt to trigger stream parser manually. If + // this workaround is not involved, the packets received before + // setting sockmap won't be handled until the next packet + // arrives. + let optval: u32 = 1; + if unsafe {libc::setsockopt(fd, libc::SOL_SOCKET, libc::SO_RCVLOWAT, &optval as *const _ as *const _, 4)} < 0 { + error!("setsockopt error: {:?}", std::io::Error::last_os_error()); + } + // Keep tcp_stream not to be dropped. And notify connection // close to delete itself from the sockmap let tx = tx.clone(); task::spawn(async move { let mut buf = Vec::new(); - tcp_stream.write(&buf).await.unwrap(); // Even though it awaits for something to read, it only // ends after the connection is half closed by the peer. // Normally the read call reads nothing but some data can // be read if setting sockmap had failed. So write all // buffer to echo it. tcp_stream.read_to_end(&mut buf).await.unwrap(); + if !buf.is_empty() { + debug!("some data is read by userspace: {:x?}", &buf); + } tcp_stream.write_all(&buf).await.unwrap(); debug!("delete client: {:?} fd: {}", client_addr, fd); tx.send(Command::Delete { key }).await.unwrap(); From 506ce5e4e419fb36c7d8525c347b1d9ae089d92c Mon Sep 17 00:00:00 2001 From: Mikhail Trishchenkov Date: Sat, 1 Jan 2022 17:02:15 +0700 Subject: [PATCH 082/107] Allow users to define own BPF maps compatible with map! macro Signed-off-by: Mikhail Trishchenkov --- redbpf-macros/src/lib.rs | 136 ++++++++------------------------ redbpf-probes/src/maps.rs | 35 ++++++++ redbpf-probes/src/tc/maps.rs | 6 ++ redbpf-probes/src/xdp/devmap.rs | 6 ++ redbpf-probes/src/xdp/mod.rs | 7 +- redbpf-probes/src/xdp/xskmap.rs | 6 ++ 6 files changed, 92 insertions(+), 104 deletions(-) diff --git a/redbpf-macros/src/lib.rs b/redbpf-macros/src/lib.rs index e0a33e72..f9df99a5 100644 --- a/redbpf-macros/src/lib.rs +++ b/redbpf-macros/src/lib.rs @@ -52,8 +52,8 @@ use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{ - parse_macro_input, parse_quote, AttributeArgs, Expr, ExprLit, GenericArgument, ItemFn, - ItemStatic, Lit, Meta, NestedMeta, PathArguments, Result, Type, + parse_macro_input, parse_quote, AttributeArgs, Expr, ExprLit, ItemFn, + ItemStatic, Lit, Meta, NestedMeta, Result, }; use uuid::Uuid; @@ -229,110 +229,40 @@ pub fn map(attrs: TokenStream, item: TokenStream) -> TokenStream { } }; - let mut tc_compatible = false; - let mut key_type: Option = None; - let mut value_type: Option = None; - if let Type::Path(path) = *static_item.ty { - if let Some(seg) = path.path.segments.last() { - let map_type_name = seg.ident.to_string(); - if let PathArguments::AngleBracketed(bracket) = &seg.arguments { - // or - match map_type_name.as_str() { - "Array" | "PerCpuArray" => { - if bracket.args.len() == 1 { - key_type = Some(parse_quote!(u32)); - value_type = Some(bracket.args.first().unwrap().clone()); - } - } - "HashMap" | "PerCpuHashMap" | "LruHashMap" | "LruPerCpuHashMap" - | "TcHashMap" => { - if bracket.args.len() == 2 { - key_type = Some(bracket.args.first().unwrap().clone()); - value_type = Some(bracket.args.last().unwrap().clone()); - } - } - "PerfMap" => {} - _ => { - panic!("unknown map type name: {}", map_type_name); - } - } + let map_type = static_item.ty; + let mod_name = format!("_{}", Uuid::new_v4().to_simple().to_string()); + let mod_ident = syn::Ident::new(&mod_name, static_item.ident.span()); + // CAUTION: When you change the names (MAP_BTF_XXXX and + // MAP_VALUE_ALIGN_XXXX) you should consider changing corresponding + // parts that use them. + let map_btf_name = format!("MAP_BTF_{}", static_item.ident.to_string()); + let map_btf_ident = syn::Ident::new(&map_btf_name, static_item.ident.span()); + let value_align_name = format!("MAP_VALUE_ALIGN_{}", static_item.ident.to_string()); + let value_align_ident = syn::Ident::new(&value_align_name, static_item.ident.span()); + let btf_type_name = format!("____btf_map_{}", static_item.ident.to_string()); + let btf_map_type = syn::Ident::new(&btf_type_name, static_item.ident.span()); + tokens.extend(quote! { + mod #mod_ident { + #[allow(unused_imports)] + use super::*; + use core::mem::{self, MaybeUninit}; - if map_type_name == "TcHashMap" { - tc_compatible = true; - } - } else { - // without generic types - match map_type_name.as_str() { - "StackTrace" | "SockMap" | "ProgramArray" | "DevMap" | "XskMap" => {} - _ => { - panic!("unknown map type name: {}", map_type_name); - } - } + #[no_mangle] + static #value_align_ident: MaybeUninit<<#map_type as ::redbpf_probes::maps::BpfMap>::Value> = MaybeUninit::uninit(); + + #[repr(C)] + struct #btf_map_type { + key: <#map_type as ::redbpf_probes::maps::BpfMap>::Key, + value: <#map_type as ::redbpf_probes::maps::BpfMap>::Value, } + // `impl Sync` is needed to allow pointer types of keys and values + unsafe impl Sync for #btf_map_type {} + const N: usize = mem::size_of::<#btf_map_type>(); + #[no_mangle] + #[link_section = "maps.ext"] + static #map_btf_ident: #btf_map_type = unsafe { mem::transmute::<[u8; N], #btf_map_type>([0u8; N]) }; } - } - if key_type.is_some() && value_type.is_some() { - let mod_name = format!("_{}", Uuid::new_v4().to_simple().to_string()); - let mod_ident = syn::Ident::new(&mod_name, static_item.ident.span()); - let ktype = key_type.unwrap(); - let vtype = value_type.unwrap(); - // CAUTION: When you change the names (MAP_BTF_XXXX and - // MAP_VALUE_ALIGN_XXXX) you should consider changing corresponding - // parts that use them. - let map_btf_name = format!("MAP_BTF_{}", static_item.ident.to_string()); - let map_btf_ident = syn::Ident::new(&map_btf_name, static_item.ident.span()); - let value_align_name = format!("MAP_VALUE_ALIGN_{}", static_item.ident.to_string()); - let value_align_ident = syn::Ident::new(&value_align_name, static_item.ident.span()); - if tc_compatible { - let btf_type_name = format!("____btf_map_{}", static_item.ident.to_string()); - let btf_map_type = syn::Ident::new(&btf_type_name, static_item.ident.span()); - tokens.extend(quote! { - mod #mod_ident { - #[allow(unused_imports)] - use super::*; - use core::mem::{self, MaybeUninit}; - - #[no_mangle] - static #value_align_ident: MaybeUninit<#vtype> = MaybeUninit::uninit(); - - #[repr(C)] - struct #btf_map_type { - key: #ktype, - value: #vtype, - } - // `impl Sync` is needed to allow pointer types of keys and values - unsafe impl Sync for #btf_map_type {} - const N: usize = mem::size_of::<#btf_map_type>(); - #[no_mangle] - #[link_section = "maps.ext"] - static #map_btf_ident: #btf_map_type = unsafe { mem::transmute::<[u8; N], #btf_map_type>([0u8; N]) }; - } - }); - } else { - tokens.extend(quote! { - mod #mod_ident { - #[allow(unused_imports)] - use super::*; - use core::mem::{self, MaybeUninit}; - - #[no_mangle] - static #value_align_ident: MaybeUninit<#vtype> = MaybeUninit::uninit(); - - #[repr(C)] - struct MapBtf { - key_type: #ktype, - value_type: #vtype, - } - // `impl Sync` is needed to allow pointer types of keys and values - unsafe impl Sync for MapBtf {} - const N: usize = mem::size_of::(); - #[no_mangle] - #[link_section = "maps.ext"] - static #map_btf_ident: MapBtf = unsafe { mem::transmute::<[u8; N], MapBtf>([0u8; N]) }; - } - }); - } - } + }); tokens.into() } diff --git a/redbpf-probes/src/maps.rs b/redbpf-probes/src/maps.rs index b1e02484..e33b91ca 100644 --- a/redbpf-probes/src/maps.rs +++ b/redbpf-probes/src/maps.rs @@ -21,6 +21,11 @@ use cty::*; use crate::bindings::*; use crate::helpers::*; +pub trait BpfMap { + type Key; + type Value; +} + macro_rules! define_hashmap { ($(#[$attr:meta])* $name:ident, $map_type:expr) => { $(#[$attr])* @@ -145,6 +150,11 @@ macro_rules! define_hashmap { } } } + + impl BpfMap for $name { + type Key = K; + type Value = V; + } }; } @@ -217,6 +227,11 @@ macro_rules! define_array { } } } + + impl BpfMap for $name { + type Key = u32; + type Value = T; + } }; } define_hashmap!( @@ -392,6 +407,11 @@ impl PerfMap { } } +impl BpfMap for PerfMap { + type Key = u32; + type Value = u32; +} + // TODO Use PERF_MAX_STACK_DEPTH const BPF_MAX_STACK_DEPTH: usize = 127; @@ -428,6 +448,11 @@ impl StackTrace { } } +impl BpfMap for StackTrace { + type Key = u32; + type Value = [u64; BPF_MAX_STACK_DEPTH]; +} + /// Program array map. /// /// An array of eBPF programs that can be used as a jump table. @@ -486,6 +511,11 @@ impl ProgramArray { } } +impl BpfMap for ProgramArray { + type Key = u32; + type Value = u32; +} + /// SockMap. /// /// A sockmap is a BPF map type that holds references to sock structs. BPF @@ -546,3 +576,8 @@ impl SockMap { } } } + +impl BpfMap for SockMap { + type Key = i32; + type Value = i32; +} diff --git a/redbpf-probes/src/tc/maps.rs b/redbpf-probes/src/tc/maps.rs index 49d3b040..94e4c744 100644 --- a/redbpf-probes/src/tc/maps.rs +++ b/redbpf-probes/src/tc/maps.rs @@ -24,6 +24,7 @@ use core::mem; use crate::bindings::*; use crate::helpers::*; +use crate::maps::BpfMap; /// `bpf_elf_map` struct is defined by tc. It is not required to use the same /// name, but it is better to do so. @@ -68,6 +69,11 @@ pub struct TcHashMap { _v: PhantomData, } +impl BpfMap for TcHashMap { + type Key = K; + type Value = V; +} + /// A structure representing types of map pinning /// /// Pinning the map is conducted by `tc` utility when an ELF object file is diff --git a/redbpf-probes/src/xdp/devmap.rs b/redbpf-probes/src/xdp/devmap.rs index 4c0dfda5..b3455762 100644 --- a/redbpf-probes/src/xdp/devmap.rs +++ b/redbpf-probes/src/xdp/devmap.rs @@ -1,6 +1,7 @@ use crate::xdp::{ bpf_map_def, bpf_map_type_BPF_MAP_TYPE_DEVMAP, prelude::bpf_redirect_map, XdpAction, }; +use crate::maps::BpfMap; use core::mem; use cty::c_void; @@ -46,3 +47,8 @@ impl DevMap { } } } + +impl BpfMap for DevMap { + type Key = u32; + type Value = u32; +} diff --git a/redbpf-probes/src/xdp/mod.rs b/redbpf-probes/src/xdp/mod.rs index e6e6efb8..c653b8c8 100644 --- a/redbpf-probes/src/xdp/mod.rs +++ b/redbpf-probes/src/xdp/mod.rs @@ -42,7 +42,7 @@ pub use devmap::DevMap; pub use xskmap::XskMap; use crate::bindings::*; -use crate::maps::{PerfMap as PerfMapBase, PerfMapFlags}; +use crate::maps::{BpfMap, PerfMap as PerfMapBase, PerfMapFlags}; use crate::net::{NetworkBuffer, NetworkResult}; /// The result type for XDP programs. @@ -169,3 +169,8 @@ impl PerfMap { self.0.insert_with_flags(ctx.inner(), data, flags) } } + +impl BpfMap for PerfMap { + type Key = as BpfMap>::Key; + type Value = as BpfMap>::Value; +} diff --git a/redbpf-probes/src/xdp/xskmap.rs b/redbpf-probes/src/xdp/xskmap.rs index dbe85ffe..a8d9398b 100644 --- a/redbpf-probes/src/xdp/xskmap.rs +++ b/redbpf-probes/src/xdp/xskmap.rs @@ -1,6 +1,7 @@ use crate::xdp::{ bpf_map_def, bpf_map_type_BPF_MAP_TYPE_XSKMAP, prelude::bpf_redirect_map, XdpAction, }; +use crate::maps::BpfMap; use core::mem; use cty::c_void; @@ -46,3 +47,8 @@ impl XskMap { } } } + +impl BpfMap for XskMap { + type Key = u32; + type Value = u32; +} From 02d5067a01bd41f14388f7b15dd7b9268850fc7c Mon Sep 17 00:00:00 2001 From: "boyang.lai" Date: Fri, 11 Mar 2022 11:28:59 +0800 Subject: [PATCH 083/107] Add `--features` to `cargo bpf build` command Signed-off-by: boyang.lai --- cargo-bpf/src/build.rs | 5 +++-- cargo-bpf/src/main.rs | 13 ++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/cargo-bpf/src/build.rs b/cargo-bpf/src/build.rs index ae25d4cc..9b2b0996 100644 --- a/cargo-bpf/src/build.rs +++ b/cargo-bpf/src/build.rs @@ -355,13 +355,14 @@ pub fn build_with_features( Ok(()) } -pub fn cmd_build(mut programs: Vec, buildopt: &BuildOptions) -> Result<(), CommandError> { +pub fn cmd_build(mut programs: Vec, buildopt: &BuildOptions, features: &Vec) -> Result<(), CommandError> { let current_dir = std::env::current_dir().unwrap(); - Ok(build( + Ok(build_with_features( Path::new("cargo"), ¤t_dir, &mut programs, buildopt, + features, )?) } diff --git a/cargo-bpf/src/main.rs b/cargo-bpf/src/main.rs index 08795eb5..0e8cfe24 100644 --- a/cargo-bpf/src/main.rs +++ b/cargo-bpf/src/main.rs @@ -192,6 +192,9 @@ fn main() { .arg(Arg::with_name("NAME").required(false).multiple(true).help( "The names of the programs to compile. When no names are specified, all the programs are built", )) + .arg(Arg::with_name("FEATURES").value_name("FEATURES").short("f").long("features").required(false).multiple(true).help( + "The features of the programs to compile. `probes` features are added by default.", + )) ) .subcommand( SubCommand::with_name("load") @@ -244,7 +247,15 @@ fn main() { .values_of("NAME") .map(|i| i.map(String::from).collect()) .unwrap_or_else(Vec::new); - if let Err(e) = cargo_bpf::cmd_build(programs, &buildopt) { + let mut features = m + .values_of("FEATURES") + .map(|i| i.map(String::from).collect()) + .unwrap_or_else(Vec::new); + let probes_feature = "probes".to_owned(); + if !features.contains(&probes_feature) { + features.push(probes_feature) + } + if let Err(e) = cargo_bpf::cmd_build(programs, &buildopt, &features) { clap::Error::with_description(&e.0, clap::ErrorKind::InvalidValue).exit() } } From cfa9aa3b4c1f8cf00ad071f18bf447d85bfd476b Mon Sep 17 00:00:00 2001 From: Mikhail Trishchenkov Date: Sun, 6 Mar 2022 23:10:57 +0700 Subject: [PATCH 084/107] Add support for LPM trie map Signed-off-by: Mikhail Trishchenkov --- redbpf-probes/src/maps.rs | 138 ++++++++++++++++++++++++++++++++++++++ redbpf/src/lib.rs | 69 +++++++++++++++++++ 2 files changed, 207 insertions(+) diff --git a/redbpf-probes/src/maps.rs b/redbpf-probes/src/maps.rs index e33b91ca..adba5856 100644 --- a/redbpf-probes/src/maps.rs +++ b/redbpf-probes/src/maps.rs @@ -581,3 +581,141 @@ impl BpfMap for SockMap { type Key = i32; type Value = i32; } + +/// LPM trie map. +/// +/// An LPM (longest prefix match) trie map is a BPF map type that can be +/// used to find entry having longest prefix match with provided one. +#[repr(transparent)] +pub struct LpmTrieMap { + def: bpf_map_def, + _k: PhantomData, + _v: PhantomData, +} + +#[repr(C)] +pub struct LpmTrieMapKey { + pub prefix_len: u32, + pub data: T, +} + +impl LpmTrieMap { + pub const fn with_max_entries(max_entries: u32) -> Self { + Self { + def: bpf_map_def { + type_: bpf_map_type_BPF_MAP_TYPE_LPM_TRIE, + key_size: mem::size_of::>() as u32, + value_size: mem::size_of::() as u32, + max_entries, + map_flags: BPF_F_NO_PREALLOC, + }, + _k: PhantomData, + _v: PhantomData, + } + } + + /// Returns a reference to the value corresponding to the key. + /// + /// **CUATION** The value that the returned reference refers to is + /// stored at 8 bytes aligned memory. So the reference is not + /// guaranteed to be aligned properly if the alignment of the value + /// exceeds 8 bytes. So this method should not be called if the + /// alignment is greater than 8 bytes. + /// + /// Use `get_val` method instead if the alignment of value is + /// greater than 8 bytes. + #[inline] + pub fn get(&mut self, key: &LpmTrieMapKey) -> Option<&V> { + unsafe { + let value = bpf_map_lookup_elem( + &mut self.def as *mut _ as *mut c_void, + key as *const _ as *const c_void, + ); + if value.is_null() { + None + } else { + Some(&*(value as *const V)) + } + } + } + + /// Returns a mutable reference to the value corresponding to the key. + /// + /// **CUATION** The value that the returned mutable reference + /// refers to is stored at 8 bytes aligned memory. So the mutable + /// reference is not guaranteed to be aligned properly if the + /// alignment of the value exceeds 8 bytes. So this method should + /// not be called if the alignment is greater than 8 bytes. + /// + /// Use `get_val` method instead if the alignment of value is + /// greater than 8 bytes. But you should call `set` method to + /// update the modified value to BPF maps. + #[inline] + pub fn get_mut(&mut self, key: &LpmTrieMapKey) -> Option<&mut V> { + unsafe { + let value = bpf_map_lookup_elem( + &mut self.def as *mut _ as *mut c_void, + key as *const _ as *const c_void, + ); + if value.is_null() { + None + } else { + Some(&mut *(value as *mut V)) + } + } + } + + /// Returns a value corresponding to the key + /// + /// **NOTE** It is better to use more efficient `get_mut` method + /// instead if the alignment of the value is equal to or less than + /// 8 bytes. i.e, alignment is 8, 4, 2 bytes or 1 byte. Rust + /// compiler expects that the value a reference refers to should be + /// aligned properly. But the Linux kernel does not guarantee the + /// alignment of the value the rust compiler assumes but the Linux + /// kernel just stores values at 8 bytes aligned memory. + #[inline] + pub fn get_val(&mut self, key: &LpmTrieMapKey) -> Option { + unsafe { + let value = bpf_map_lookup_elem( + &mut self.def as *mut _ as *mut c_void, + key as *const _ as *const c_void, + ); + if value.is_null() { + None + } else { + Some(ptr::read_unaligned(value as *const V)) + } + } + } + + /// Set the `value` in the map for `key` + #[inline] + pub fn set(&mut self, key: &LpmTrieMapKey, value: &V) { + unsafe { + bpf_map_update_elem( + &mut self.def as *mut _ as *mut c_void, + key as *const _ as *const c_void, + value as *const _ as *const c_void, + BPF_ANY.into(), + ); + } + } + + /// Delete the entry indexed by `key` + #[inline] + pub fn delete(&mut self, key: &LpmTrieMapKey) { + unsafe { + bpf_map_delete_elem( + &mut self.def as *mut _ as *mut c_void, + key as *const _ as *const c_void, + ); + } + } +} + +impl BpfMap for LpmTrieMap { + type Key = LpmTrieMapKey; + type Value = V; +} + diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 3e4afc8d..591af929 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -61,6 +61,7 @@ use libbpf_sys::{ bpf_load_program_xattr, bpf_map_def, bpf_map_info, bpf_prog_type, BPF_ANY, BPF_MAP_TYPE_ARRAY, BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH, BPF_MAP_TYPE_PERCPU_ARRAY, BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_PERF_EVENT_ARRAY, + BPF_MAP_TYPE_LPM_TRIE, BPF_SK_LOOKUP, BPF_SK_SKB_STREAM_PARSER, BPF_SK_SKB_STREAM_VERDICT, BPF_TRACE_ITER, }; @@ -422,6 +423,23 @@ pub struct PerCpuArray<'a, T: Clone> { _element: PhantomData, } +#[repr(C)] +#[derive(Clone)] +pub struct LpmTrieMapKey { + pub prefix_len: u32, + pub data: T, +} + +/// A BPF LPM trie map structure +/// +/// This provides higher level API for BPF maps whose type is +/// `BPF_MAP_TYPE_LPM_TRIE` +pub struct LpmTrieMap<'a, K: Clone, V: Clone> { + base: &'a Map, + _k: PhantomData, + _v: PhantomData, +} + // TODO Use PERF_MAX_STACK_DEPTH const BPF_MAX_STACK_DEPTH: usize = 127; const BPF_FS_MAGIC: i64 = 0xcafe4a11; @@ -2640,6 +2658,57 @@ impl<'a> SockMap<'a> { } } +impl<'base, K: Clone, V: Clone> LpmTrieMap<'base, K, V> { + pub fn new(base: &'base Map) -> Result { + if mem::size_of::() + mem::size_of::() != base.config.key_size as usize + || mem::size_of::() != base.config.value_size as usize + || BPF_MAP_TYPE_LPM_TRIE != base.config.type_ + { + error!( + "map definitions (map type and key/value size) of base `Map' and + `LpmTrieMap' do not match" + ); + return Err(Error::Map); + } + + Ok(Self { + base, + _k: PhantomData, + _v: PhantomData, + }) + } + + pub fn set(&self, key: LpmTrieMapKey, value: V) { + let _ = bpf_map_set(self.base.fd, key, value); + } + + pub fn get(&self, key: LpmTrieMapKey) -> Option { + bpf_map_get(self.base.fd, key) + } + + pub fn delete(&self, key: LpmTrieMapKey) { + let _ = bpf_map_delete(self.base.fd, key); + } + + /// Return an iterator over all items in the map + pub fn iter<'a>(&'a self) -> MapIter<'a, LpmTrieMapKey, V> { + MapIter { + iterable: self, + last_key: None, + } + } +} + +impl MapIterable, V> for LpmTrieMap<'_, K, V> { + fn get(&self, key: LpmTrieMapKey) -> Option { + LpmTrieMap::get(self, key) + } + + fn next_key(&self, key: Option>) -> Option> { + bpf_map_get_next_key(self.base.fd, key) + } +} + /// A structure for reading data from BPF iterators /// /// The data read by this structure is written by BPF iterators from the kernel From d0015e07d62921196aa411a213617d55c56e5cca Mon Sep 17 00:00:00 2001 From: Mikhail Trishchenkov Date: Wed, 23 Mar 2022 13:38:15 +0700 Subject: [PATCH 085/107] Add ability to use interface index for attaching/detaching In some cases there is already interface index and user is forced to convert it to interface name just for RedBPF to immediately resolve it back to interface index. Moreover, interface indices are stable, while name of interface may change on the fly. Signed-off-by: Mikhail Trishchenkov --- redbpf/src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 3e4afc8d..9e369f3f 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -198,7 +198,7 @@ pub struct TracePoint { /// Type to work with `XDP` programs. pub struct XDP { common: ProgramData, - interfaces: Vec, + interfaces: Vec, } /// Type to work with `stream_parser` BPF programs. @@ -987,11 +987,27 @@ impl XDP { /// # } /// ``` pub fn attach_xdp(&mut self, interface: &str, flags: xdp::Flags) -> Result<()> { + self.attach_xdp_by_index(if_nametoindex(interface)?, flags) + } + + /// Attach the XDP program to interface by it's index. + /// + /// Attach the XDP program to the given network interface. + /// + /// # Example + /// ```no_run + /// # use redbpf::{Module, xdp}; + /// # let mut module = Module::parse(&std::fs::read("file.elf").unwrap()).unwrap(); + /// # for uprobe in module.xdps_mut() { + /// uprobe.attach_xdp_by_index(2, xdp::Flags::default()).unwrap(); + /// # } + /// ``` + pub fn attach_xdp_by_index(&mut self, ifindex: u32, flags: xdp::Flags) -> Result<()> { let fd = self.common.fd.ok_or(Error::ProgramNotLoaded)?; - self.interfaces.push(interface.to_string()); - if let Err(e) = unsafe { attach_xdp(interface, fd, flags as u32) } { + self.interfaces.push(ifindex); + if let Err(e) = unsafe { attach_xdp(ifindex, fd, flags as u32) } { if let Error::IO(oserr) = e { - error!("error attaching xdp to interface {}: {}", interface, oserr); + error!("error attaching xdp to interface #{}: {}", ifindex, oserr); } Err(Error::BPF) } else { @@ -1013,18 +1029,35 @@ impl XDP { /// # } /// ``` pub fn detach_xdp(&mut self, interface: &str) -> Result<()> { + self.detach_xdp_by_index(if_nametoindex(interface)?) + } + + /// Detach the XDP program from interface by it's index. + /// + /// Detach the XDP program from the given network interface, if attached. + /// + /// # Example + /// ```no_run + /// # use redbpf::{Module, xdp}; + /// # let mut module = Module::parse(&std::fs::read("file.elf").unwrap()).unwrap(); + /// # for uprobe in module.xdps_mut() { + /// uprobe.attach_xdp_by_index(23, xdp::Flags::default()).unwrap(); + /// uprobe.detach_xdp_by_index(23).unwrap(); + /// # } + /// ``` + pub fn detach_xdp_by_index(&mut self, ifindex: u32) -> Result<()> { // The linear search here isn't great, but self.interfaces will almost always be short. let index = self .interfaces .iter() .enumerate() - .find_map(|(i, v)| (v.as_str() == interface).then(|| i)) + .find_map(|(i, v)| (*v == ifindex).then(|| i)) .ok_or(Error::ProgramNotLoaded)?; - if let Err(e) = unsafe { detach_xdp(interface) } { + if let Err(e) = unsafe { detach_xdp(ifindex) } { if let Error::IO(ref oserr) = e { error!( - "error detaching xdp from interface {}: {}", - interface, oserr + "error detaching xdp from interface #{}: {}", + ifindex, oserr ); } return Err(e); @@ -1040,8 +1073,8 @@ impl XDP { impl Drop for XDP { fn drop(&mut self) { - for interface in self.interfaces.iter() { - let _ = unsafe { detach_xdp(interface) }; + for ifindex in self.interfaces.iter() { + let _ = unsafe { detach_xdp(*ifindex) }; } } } @@ -1084,21 +1117,24 @@ unsafe fn open_raw_sock(name: &str) -> Result { Ok(sock) } -unsafe fn attach_xdp(dev_name: &str, progfd: libc::c_int, flags: libc::c_uint) -> Result<()> { +fn if_nametoindex(dev_name: &str) -> Result { let ciface = CString::new(dev_name).unwrap(); - let ifindex = libc::if_nametoindex(ciface.as_ptr()) as i32; + let ifindex = unsafe { libc::if_nametoindex(ciface.as_ptr()) }; if ifindex == 0 { return Err(Error::IO(io::Error::last_os_error())); } + Ok(ifindex) +} - if libbpf_sys::bpf_set_link_xdp_fd(ifindex, progfd, flags) != 0 { +unsafe fn attach_xdp(ifindex: u32, progfd: libc::c_int, flags: libc::c_uint) -> Result<()> { + if libbpf_sys::bpf_set_link_xdp_fd(ifindex as i32, progfd, flags) != 0 { return Err(Error::IO(io::Error::last_os_error())); } Ok(()) } -unsafe fn detach_xdp(dev_name: &str) -> Result<()> { - attach_xdp(dev_name, -1, 0) +unsafe fn detach_xdp(ifindex: u32) -> Result<()> { + attach_xdp(ifindex, -1, 0) } impl SocketFilter { From 9a27817d85ba8a177168695f2b290549b3e15856 Mon Sep 17 00:00:00 2001 From: Michael Torres Date: Fri, 25 Mar 2022 17:52:45 -0700 Subject: [PATCH 086/107] Parse WSL kernel versions Parse WSL kernel versions, which have 4 parts versus the standard 3. Fixes #284 Signed-off-by: Michael Torres --- bpf-sys/src/uname.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bpf-sys/src/uname.rs b/bpf-sys/src/uname.rs index fb803e9f..cd6b2340 100644 --- a/bpf-sys/src/uname.rs +++ b/bpf-sys/src/uname.rs @@ -66,10 +66,10 @@ fn parse_version(version: &str) -> Option<(u32, u32, u32)> { if let Some(version) = version.splitn(2, '-').next() { if let Some(version) = version.splitn(2, '+').next() { let parts: Vec<_> = version - .splitn(3, '.') + .splitn(4, '.') .filter_map(|v| u32::from_str(v).ok()) .collect(); - if parts.len() != 3 { + if parts.len() < 3 { return None; } return Some((parts[0], parts[1], parts[2])); @@ -85,6 +85,7 @@ mod test { #[test] fn test_parse_version() { + assert_eq!(parse_version("5.10.93.2-microsoft-standard-WSL2"), Some((5, 10, 93))); assert_eq!(parse_version("4.15.18"), Some((4, 15, 18))); assert_eq!(parse_version("4.15.1-generic"), Some((4, 15, 1))); assert_eq!(parse_version("4.15.1-generic-foo"), Some((4, 15, 1))); From 340505d06965655f4589a4825006c2ecc47748b9 Mon Sep 17 00:00:00 2001 From: Nikita Baksalyar Date: Sun, 3 Apr 2022 17:14:47 +0100 Subject: [PATCH 087/107] Implement Clone for Map This allows to share maps across threads/async processes. Signed-off-by: Nikita Baksalyar --- redbpf/src/lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 01758d00..bb1d3926 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -313,6 +313,19 @@ pub struct Map { pin_file: Option>, } +impl Clone for Map { + fn clone(&self) -> Self { + Map { + name: self.name.clone(), + kind: self.kind, + fd: unsafe { libc::dup(self.fd) }, + config: self.config.clone(), + section_data: self.section_data, + pin_file: self.pin_file.clone(), + } + } +} + enum MapBuilder<'a> { Normal { name: String, From 05b511c60eafce541de0019035698df95d842082 Mon Sep 17 00:00:00 2001 From: Nikita Baksalyar Date: Sun, 3 Apr 2022 17:52:09 +0100 Subject: [PATCH 088/107] Fix symbols resolving for position-independent executables This commit fixes the symbols resolver for position-independent executables which have `ET_DYN` or `ET_EXEC` flags in the ELF header. In that case, virtual addresses need to be translated into physical addresses using the offsets information from ELF headers. Signed-off-by: Nikita Baksalyar --- redbpf/src/symbols.rs | 60 +++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/redbpf/src/symbols.rs b/redbpf/src/symbols.rs index 26c979a8..bf6a3e7f 100644 --- a/redbpf/src/symbols.rs +++ b/redbpf/src/symbols.rs @@ -34,23 +34,55 @@ impl<'a> ElfSymbols<'a> { } fn resolve_dyn_syms(&self, sym_name: &str) -> Option { - self.elf.dynsyms.iter().find(|sym| { - self.elf - .dynstrtab - .get_at(sym.st_name) - .map(|n| n == sym_name) - .unwrap_or(false) - }) + self.elf + .dynsyms + .iter() + .find(|sym| { + self.elf + .dynstrtab + .get_at(sym.st_name) + .map(|n| n == sym_name) + .unwrap_or(false) + }) + .map(|s| self.translate_sym_addr(s)) + } + + fn needs_addr_translation(&self) -> bool { + self.elf.header.e_type == goblin::elf::header::ET_DYN + || self.elf.header.e_type == goblin::elf::header::ET_EXEC + } + + fn translate_sym_addr(&self, sym: Sym) -> Sym { + if !self.needs_addr_translation() { + return sym; + } + if let Some(shdr) = self + .elf + .section_headers + .iter() + .find(|shdr| shdr.vm_range().contains(&(sym.st_value as usize))) + { + Sym { + st_value: sym.st_value - shdr.sh_addr + shdr.sh_offset, + ..sym + } + } else { + sym + } } fn resolve_syms(&self, sym_name: &str) -> Option { - self.elf.syms.iter().find(|sym| { - self.elf - .strtab - .get_at(sym.st_name) - .map(|n| n == sym_name) - .unwrap_or(false) - }) + self.elf + .syms + .iter() + .find(|sym| { + self.elf + .strtab + .get_at(sym.st_name) + .map(|n| n == sym_name) + .unwrap_or(false) + }) + .map(|s| self.translate_sym_addr(s)) } pub fn resolve(&self, sym_name: &str) -> Option { From f4077a2411dd70d0574f2aec24e3e658e548f8cf Mon Sep 17 00:00:00 2001 From: Jonathan Gravel Date: Tue, 21 Dec 2021 20:48:13 +0000 Subject: [PATCH 089/107] Add BPF_MAP_TYPE_RINGBUF support with RingBufMap Signed-off-by: Jonathan Gravel --- redbpf-probes/Cargo.toml | 1 + redbpf-probes/src/kprobe/prelude.rs | 2 + redbpf-probes/src/lib.rs | 2 + redbpf-probes/src/ringbuf.rs | 205 ++++++++++++++++++++++++++++ redbpf/src/lib.rs | 2 + redbpf/src/load/loader.rs | 20 ++- redbpf/src/load/map_io.rs | 37 ++++- redbpf/src/ringbuf.rs | 117 ++++++++++++++++ 8 files changed, 382 insertions(+), 4 deletions(-) create mode 100644 redbpf-probes/src/ringbuf.rs create mode 100644 redbpf/src/ringbuf.rs diff --git a/redbpf-probes/Cargo.toml b/redbpf-probes/Cargo.toml index 89bdf42c..7f2d6889 100644 --- a/redbpf-probes/Cargo.toml +++ b/redbpf-probes/Cargo.toml @@ -33,6 +33,7 @@ memoffset = "0.6" [features] default = [] probes = [] +ringbuf = [] [package.metadata.docs.rs] all-features = true diff --git a/redbpf-probes/src/kprobe/prelude.rs b/redbpf-probes/src/kprobe/prelude.rs index 6ae0482d..afd537dc 100644 --- a/redbpf-probes/src/kprobe/prelude.rs +++ b/redbpf-probes/src/kprobe/prelude.rs @@ -17,5 +17,7 @@ pub use crate::bindings::*; pub use crate::helpers::*; pub use crate::maps::*; pub use crate::registers::*; +#[cfg(feature = "ringbuf")] +pub use crate::ringbuf::*; pub use cty::*; pub use redbpf_macros::{kprobe, kretprobe, map, printk, program}; diff --git a/redbpf-probes/src/lib.rs b/redbpf-probes/src/lib.rs index c7181c36..35846981 100644 --- a/redbpf-probes/src/lib.rs +++ b/redbpf-probes/src/lib.rs @@ -111,6 +111,8 @@ pub mod kprobe; pub mod maps; pub mod net; pub mod registers; +#[cfg(feature = "ringbuf")] +pub mod ringbuf; pub mod socket; pub mod socket_filter; pub mod sockmap; diff --git a/redbpf-probes/src/ringbuf.rs b/redbpf-probes/src/ringbuf.rs new file mode 100644 index 00000000..88ca7a27 --- /dev/null +++ b/redbpf-probes/src/ringbuf.rs @@ -0,0 +1,205 @@ +// Copyright 2022 Authors of Red Sift +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +/*! +eBPF ringbuf. + */ +use core::marker::PhantomData; +use core::mem; +use cty::*; + +use crate::bindings::*; +use crate::helpers::*; +use crate::maps::BpfMap; + +/// Flags that be passed to `RingBuf::output_with_flags`. +#[derive(Debug, Copy, Clone)] +pub struct RingBufMapFlags(u64); + +impl Default for RingBufMapFlags { + #[inline] + fn default() -> Self { + RingBufMapFlags(0) + } +} + +impl RingBufMapFlags { + /// Create new default flags. + #[inline] + pub fn new() -> Self { + Default::default() + } + + /// Set BPF_RB_NO_WAKEUP flag. + #[inline] + pub fn no_wakeup(&mut self) -> &mut RingBufMapFlags { + self.0 |= BPF_RB_NO_WAKEUP as u64; + self + } + + /// Set BPF_RB_FORCE_WAKEUP flag + #[inline] + pub fn force_wakeup(&mut self) -> &mut RingBufMapFlags { + self.0 |= BPF_RB_FORCE_WAKEUP as u64; + self + } +} + +impl From for u64 { + #[inline] + fn from(flags: RingBufMapFlags) -> u64 { + flags.0 + } +} + +/// Ring buffer map. +/// +/// This is a wrapper for `BPF_MAP_TYPE_RINGBUF`. +pub struct RingBufMap { + def: bpf_map_def, + _event: PhantomData, +} + +impl RingBufMap { + /// Creates a ring buffer map with the specied maximum number of elements. + /// + /// `buffer_size` must be a power of 2 value and must be page aligned. + pub const fn with_buffer_size(buffer_size: u32) -> Self { + Self { + def: bpf_map_def { + type_: bpf_map_type_BPF_MAP_TYPE_RINGBUF, + key_size: 0, + value_size: 0, + max_entries: buffer_size, + map_flags: 0, + }, + _event: PhantomData, + } + } + + /// Copy data to ring buffer. + /// + /// The ring buffer can hold up to `buffer_size` bytes, see `with_buffer_size`. + /// No loss reporting occurs in user space. Monitoring must be done by the BPF + /// program. + /// + /// This method will wake up the polling thread if it is currently waiting on the + /// ring buffer. If this isn't desired, see `output_with_flags`. + #[inline] + pub fn output(&mut self, data: &T) -> Result<(), i64> { + self.output_with_flags(data, RingBufMapFlags::default()) + } + + /// Copy data to ring buffer specifying wake options in the given + /// `RingBufMapFlags`. + #[inline] + pub fn output_with_flags(&mut self, data: &T, flags: RingBufMapFlags) -> Result<(), i64> { + unsafe { + let ret = bpf_ringbuf_output( + &mut self.def as *mut _ as *mut c_void, + data as *const _ as *mut c_void, + mem::size_of::() as u64, + flags.into(), + ); + if ret == 0 { + Ok(()) + } else { + Err(ret) + } + } + } + + /// Reserve sample in ring buffer. + #[inline] + pub fn reserve(&mut self) -> Option> { + unsafe { + let ptr = bpf_ringbuf_reserve( + &mut self.def as *mut _ as *mut c_void, + mem::size_of::() as u64, + 0, + ) as *mut T; + + if ptr.is_null() { + None + } else { + Some(RingBufPayload { + reserved: true, + ptr, + }) + } + } + } +} + +impl BpfMap for RingBufMap { + type Key = u32; + type Value = T; +} + +/// Ring buffer map entry. +pub struct RingBufPayload { + reserved: bool, + ptr: *mut T, +} + +impl RingBufPayload { + /// Retrieve payload as mutable reference. + /// + /// # Safety + /// + /// Safety of this funciton is the same as ptr::as_mut: + /// + /// * The pointer must be properly aligned. + /// + /// * It must be "dereferencable" in the sense defined in [the module documentation]. + /// + /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is + /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. + /// In particular, for the duration of this lifetime, the memory the pointer points to must + /// not get accessed (read or written) through any other pointer. + /// + /// This pointer is already assumed to be non-null since it was checked in RingBufMap::reserve. + pub unsafe fn as_mut(&self) -> &mut T { + self.ptr.as_mut().unwrap() + } + + /// Submit reserved sample. + #[inline] + pub fn submit(&mut self) { + self.submit_with_flags(RingBufMapFlags::default()) + } + + /// Submit reserved sample specifying wake options in the given `RingBufMapFlags`. + #[inline] + pub fn submit_with_flags(&mut self, flags: RingBufMapFlags) { + unsafe { bpf_ringbuf_submit(self.ptr as *mut c_void, flags.into()) } + self.reserved = false; + } + + /// Discard reserved sample. + #[inline] + pub fn discard(&mut self) { + self.discard_with_flags(RingBufMapFlags::default()); + } + + /// Discard reserved sample specifying wake options in the given `RingBufMapFlags`. + #[inline] + pub fn discard_with_flags(&mut self, flags: RingBufMapFlags) { + unsafe { + bpf_ringbuf_discard(self.ptr as *mut c_void, flags.into()); + } + self.reserved = false; + } +} + +impl Drop for RingBufPayload { + fn drop(&mut self) { + if self.reserved { + self.discard(); + } + } +} diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index bb1d3926..cc001891 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -50,6 +50,7 @@ mod error; #[cfg(feature = "load")] pub mod load; mod perf; +mod ringbuf; mod symbols; pub mod sys; pub mod xdp; @@ -80,6 +81,7 @@ use std::ptr; use crate::btf::{BtfKind, MapBtfTypeId, BTF}; pub use crate::error::{Error, Result}; pub use crate::perf::*; +pub use crate::ringbuf::*; use crate::symbols::*; use crate::uname::get_kernel_internal_version; diff --git a/redbpf/src/load/loader.rs b/redbpf/src/load/loader.rs index 4bd9d30a..c49f90c3 100644 --- a/redbpf/src/load/loader.rs +++ b/redbpf/src/load/loader.rs @@ -12,11 +12,11 @@ use std::fs; use std::io; use std::path::Path; -use crate::load::map_io::PerfMessageStream; +use crate::load::map_io::{PerfMessageStream, RingBufMessageStream}; use crate::{cpus, Program}; use crate::{ - Error, KProbe, Map, Module, PerfMap, SkLookup, SocketFilter, StreamParser, StreamVerdict, - TaskIter, UProbe, XDP, + Error, KProbe, Map, Module, PerfMap, RingBufMap, SkLookup, SocketFilter, StreamParser, + StreamVerdict, TaskIter, UProbe, XDP, }; #[derive(Debug)] @@ -44,6 +44,7 @@ impl Loader { let online_cpus = cpus::get_online().unwrap(); let (sender, receiver) = mpsc::unbounded(); + // bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4 for m in module.maps.iter_mut().filter(|m| m.kind == 4) { for cpuid in online_cpus.iter() { @@ -59,6 +60,19 @@ impl Loader { } } + // bpf_map_type_BPF_MAP_TYPE_RINGBUF + for m in module.maps.iter_mut().filter(|m| m.kind == 27) { + let name = m.name.clone(); + let map = RingBufMap::bind(m).unwrap(); + let stream = RingBufMessageStream::new(name.clone(), map); + let mut s = sender.clone(); + let fut = stream.for_each(move |events| { + s.start_send((name.clone(), events)).unwrap(); + future::ready(()) + }); + tokio::spawn(fut); + } + Ok(Loaded { module, events: receiver, diff --git a/redbpf/src/load/map_io.rs b/redbpf/src/load/map_io.rs index 20e76360..3ceb76cf 100644 --- a/redbpf/src/load/map_io.rs +++ b/redbpf/src/load/map_io.rs @@ -14,7 +14,7 @@ use tokio::io::unix::AsyncFd; use tokio::io::Interest; use tracing::error; -use crate::{Event, PerfMap}; +use crate::{Event, PerfMap, RingBufMap}; pub struct PerfMessageStream { poll: AsyncFd, @@ -69,3 +69,38 @@ impl Stream for PerfMessageStream { Some(self.read_messages()).into() } } + +pub struct RingBufMessageStream { + poll: AsyncFd, + map: RingBufMap, + name: String, +} + +impl RingBufMessageStream { + pub fn new(name: String, map: RingBufMap) -> Self { + let poll = AsyncFd::with_interest(map.fd, Interest::READABLE).unwrap(); + RingBufMessageStream { poll, map, name } + } + + fn read_messages(&mut self) -> Vec> { + self.map.read_messages() + } +} + +impl Stream for RingBufMessageStream { + type Item = Vec>; + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.poll.poll_read_ready(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(e)) => { + // it should never happen + error!("RingBufMessageStream error for {}: {:?}", &self.name, e); + return Poll::Ready(None); + } + Poll::Ready(Ok(mut rg)) => rg.clear_ready(), + }; + // Must read all messages because AsyncFdReadyGuard::clear_ready is + // already called. + Some(self.read_messages()).into() + } +} diff --git a/redbpf/src/ringbuf.rs b/redbpf/src/ringbuf.rs new file mode 100644 index 00000000..2ac8ca7b --- /dev/null +++ b/redbpf/src/ringbuf.rs @@ -0,0 +1,117 @@ +// Copyright 2019 Authors of Red Sift +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! # Ring Buffer Event handling +//! +//! The ringbuf module makes it easier to hook up and consume from ring buffers. +//! and provdes a safe interface for accesing the ring buffer +//! +//! The consumed data is a raw pointer and will require unsafe code to transform +//! into a data structure. + +use crate::{Error, Map, Result}; +use std::cell::RefCell; +use std::io; +use std::os::unix::io::RawFd; +use std::slice; + +use libbpf_sys::{ + ring_buffer, ring_buffer__consume, ring_buffer__epoll_fd, ring_buffer__free, ring_buffer__new, +}; +use libc::c_void; + +#[derive(Debug)] +struct RingBufMapContext { + messages: RefCell>>, +} + +impl RingBufMapContext { + fn new() -> RingBufMapContext { + RingBufMapContext { + messages: RefCell::new(Vec::new()), + } + } + + pub fn read_message(&self, data: *mut c_void, size: u64) { + unsafe { + let mut messages = self.messages.borrow_mut(); + let message = slice::from_raw_parts(data as *const u8, size as usize).to_vec(); + messages.push(message.into_boxed_slice()); + } + } + + pub fn take_messages(&self) -> Vec> { + let mut messages = self.messages.borrow_mut(); + let new_vec = messages.clone(); + messages.clear(); + new_vec + } +} + +#[derive(Debug)] +pub struct RingBufInner(*mut ring_buffer); +unsafe impl Send for RingBufInner {} + +#[derive(Debug)] +pub struct RingBufMap { + ctx: Box, + ring_buffer: RingBufInner, + pub fd: RawFd, +} + +unsafe extern "C" fn ring_buffer_sample_cb(ctx: *mut c_void, data: *mut c_void, size: u64) -> i32 { + let rb = &mut *(ctx as *mut RingBufMapContext); + rb.read_message(data, size); + 0 +} + +impl RingBufMap { + pub fn bind(map: &mut Map) -> Result { + unsafe { + if map.kind != 27 { + return Err(Error::IO(io::Error::new( + io::ErrorKind::InvalidData, + format!("Invalid map type {}", map.config.type_), + ))); + } + + let mut ctx = Box::new(RingBufMapContext::new()); + let ring_buffer = ring_buffer__new( + map.fd, + Some(ring_buffer_sample_cb), + &mut *ctx as *mut _ as *mut c_void, + std::ptr::null(), + ); + + if ring_buffer.is_null() { + Err(Error::IO(io::Error::last_os_error())) + } else { + let fd = ring_buffer__epoll_fd(ring_buffer); + Ok(RingBufMap { + ctx, + ring_buffer: RingBufInner(ring_buffer), + fd, + }) + } + } + } + + pub fn read_messages(&self) -> Vec> { + unsafe { + ring_buffer__consume(self.ring_buffer.0); + self.ctx.take_messages() + } + } +} + +impl Drop for RingBufMap { + fn drop(&mut self) { + unsafe { + ring_buffer__free(self.ring_buffer.0); + } + } +} From e3d6dab7d5b48117e660bf49acc32515695a6bb3 Mon Sep 17 00:00:00 2001 From: yobiscus <65927223+yobiscus@users.noreply.github.com> Date: Wed, 6 Apr 2022 00:06:46 -0400 Subject: [PATCH 090/107] Building LLVM from source requires LLVM_BUILD_LLVM_DYLIB Otherwise libLLVM.so will not be included in the build output. Signed-off-by: Jonathan Gravel --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12c7a6ca..54494d7b 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ yet, you may build LLVM from the LLVM source code. $ tar -xaf llvm-13.0.0.src.tar.xz $ mkdir -p llvm-13.0.0.src/build $ cd llvm-13.0.0.src/build -$ cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/llvm-13-release -DCMAKE_BUILD_TYPE=Release +$ cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/llvm-13-release -DCMAKE_BUILD_TYPE=Release -DLLVM_BUILD_LLVM_DYLIB=1 $ cmake --build . --target install ``` From 6ec85611aa2911831be44e3de6353fdf7cfbf318 Mon Sep 17 00:00:00 2001 From: rsdy Date: Wed, 6 Apr 2022 14:34:21 +0100 Subject: [PATCH 091/107] Add nixos flake Signed-off-by: rsdy --- .envrc | 1 + .github/workflows/build-test.yml | 96 +-- .gitignore | 2 + Cargo.lock | 1317 ++++++++++++++++++++++++++++++ Cargo.toml | 2 +- flake.lock | 89 ++ flake.nix | 77 ++ 7 files changed, 1535 insertions(+), 49 deletions(-) create mode 100644 .envrc create mode 100644 Cargo.lock create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f9f1be82..6ccbac0c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -23,15 +23,13 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | uname -a cat /etc/os-release - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on x86_64 Ubuntu 21.04 run: | @@ -64,15 +62,13 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | uname -a cat /etc/os-release - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on x86_64 Debian 11 run: | @@ -105,15 +101,13 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | uname -a cat /etc/os-release - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on x86_64 Fedora 35 run: | @@ -148,15 +142,13 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | uname -a cat /etc/os-release - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on x86_64 Alpine 3.15 run: | @@ -178,15 +170,13 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | uname -a cat /etc/os-release - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on x86_64 Arch Linux run: | @@ -219,15 +209,13 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | uname -a cat /etc/os-release - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on x86_64 Ubuntu 20.04 LTS run: | @@ -248,15 +236,13 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | uname -a cat /etc/os-release - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on x86_64 Gentoo run: | @@ -277,6 +263,8 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | @@ -292,10 +280,6 @@ jobs: - name: Available platforms run: echo ${{ steps.qemu.outputs.platforms }} - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on aarch64 Ubuntu 21.04 run: | @@ -328,6 +312,8 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | @@ -343,10 +329,6 @@ jobs: - name: Available platforms run: echo ${{ steps.qemu.outputs.platforms }} - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on aarch64 Debian 11 run: | @@ -381,6 +363,8 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | @@ -396,10 +380,6 @@ jobs: - name: Available platforms run: echo ${{ steps.qemu.outputs.platforms }} - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on aarch64 Fedora 35 run: | @@ -420,6 +400,8 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | @@ -435,10 +417,6 @@ jobs: - name: Available platforms run: echo ${{ steps.qemu.outputs.platforms }} - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with vmlinux on aarch64 Fedora 35 run: | @@ -460,6 +438,8 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + submodules: recursive - name: Build host info run: | @@ -475,10 +455,6 @@ jobs: - name: Available platforms run: echo ${{ steps.qemu.outputs.platforms }} - - - name: Initialize git submodules - run: | - git submodule update --init --recursive - name: Build RedBPF with the kernel headers on aarch64 Alpine 3.15 run: | @@ -533,6 +509,28 @@ jobs: && cargo build --bin cargo-bpf \ && cargo build --examples' + build-on-x86_64-nix-stable: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.4.0 + with: + submodules: recursive + - uses: cachix/install-nix-action@v17 + with: + nix_path: nixpkgs=channel:nixos-21.11 + - run: nix build '.?submodules=1' + + build-on-x86_64-nix-unstable: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.4.0 + with: + submodules: recursive + - uses: cachix/install-nix-action@v17 + with: + nix_path: nixpkgs=channel:nixos-unstable + - run: nix build '.?submodules=1' + publish: runs-on: ubuntu-latest if: startsWith( github.ref, 'refs/tags/v') @@ -544,6 +542,8 @@ jobs: - build-on-x86_64-archlinux - build-on-x86_64-ubuntu-2004 - build-on-x86_64-gentoo + - build-on-x86_64-nix-stable + - build-on-x86_64-nix-unstable - build-on-aarch64-ubuntu-2104 - build-on-aarch64-debian11 - build-on-aarch64-fedora35-header diff --git a/.gitignore b/.gitignore index 47aaa684..30dd2e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ GPATH *TAGS* cscope* lsp-target +.direnv +/result diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..b1cbbf36 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1317 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bindgen" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bpf-sys" +version = "2.3.0" +dependencies = [ + "bindgen", + "cc", + "glob", + "libbpf-sys", + "libc", + "regex", + "zero", +] + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cargo-bpf" +version = "2.3.0" +dependencies = [ + "anyhow", + "bindgen", + "bpf-sys", + "cfg-if", + "clap", + "futures", + "glob", + "goblin", + "hexdump", + "lazy_static", + "libbpf-sys", + "libc", + "llvm-sys", + "proc-macro2", + "quote", + "redbpf", + "regex", + "rustc_version 0.4.0", + "rustversion", + "semver 1.0.7", + "syn", + "tempfile", + "tokio", + "toml_edit", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "clang-sys" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "combine" +version = "4.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "example-probes" +version = "0.1.0" +dependencies = [ + "bpf-sys", + "cargo-bpf", + "cty", + "memoffset", + "redbpf-macros", + "redbpf-probes", + "tracing", + "tracing-subscriber 0.2.25", +] + +[[package]] +name = "example-userspace" +version = "0.1.0" +dependencies = [ + "cargo-bpf", + "example-probes", + "futures", + "libc", + "redbpf", + "tokio", + "tracing", + "tracing-subscriber 0.3.10", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "goblin" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32401e89c6446dcd28185931a01b1093726d0356820ac744023e6850689bf926" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hexdump" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40283dadb02f3af778878be1d717b17b4e4ab92e1d935ab03a730b0542905f2" +dependencies = [ + "arrayvec", + "itertools", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a9b56eb56058f43dc66e58f40a214b2ccbc9f3df51861b63d51dec7b65bc3f" + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libbpf-sys" +version = "0.6.1-2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df746ec055dfd68b3d0e8ea8dd12882157529132652d0df2e001305ba4803f0c" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "libc" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "llvm-sys" +version = "130.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95eb03b4f7ae21f48ef7c565a3e3aa22c50616aea64645fb1fd7f6f56b51c274" +dependencies = [ + "cc", + "lazy_static", + "libc", + "regex", + "semver 0.11.0", +] + +[[package]] +name = "log" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "probes" +version = "0.1.0" +dependencies = [ + "bpf-sys", + "cargo-bpf", + "cty", + "glob", + "redbpf-macros", + "redbpf-probes", + "tracing", + "tracing-subscriber 0.2.25", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redbpf" +version = "2.3.0" +dependencies = [ + "bindgen", + "bpf-sys", + "byteorder", + "futures", + "goblin", + "lazy_static", + "libbpf-sys", + "libc", + "regex", + "ring", + "serde_derive", + "serde_json", + "tokio", + "tracing", + "zero", +] + +[[package]] +name = "redbpf-macros" +version = "2.3.0" +dependencies = [ + "memoffset", + "proc-macro2", + "quote", + "redbpf-probes", + "rustc_version 0.3.3", + "syn", + "uuid", +] + +[[package]] +name = "redbpf-probes" +version = "2.3.0" +dependencies = [ + "anyhow", + "bindgen", + "bpf-sys", + "cargo-bpf", + "cty", + "glob", + "libbpf-sys", + "memoffset", + "quote", + "redbpf-macros", + "syn", + "tracing", + "tracing-subscriber 0.2.25", + "ufmt", +] + +[[package]] +name = "redbpf-tools" +version = "0.1.0" +dependencies = [ + "cargo-bpf", + "futures", + "getopts", + "libc", + "probes", + "redbpf", + "tokio", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.7", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "scroll" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaaae8f38bb311444cfb7f1979af0bc9240d95795f75f9ceddf6a59b79ceffa0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "tokio" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "once_cell", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml_edit" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbbdcf4f749dd33b1f1ea19b547bf789d87442ec40767d6015e5e2d39158d69a" +dependencies = [ + "chrono", + "combine", + "linked-hash-map", +] + +[[package]] +name = "tracing" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90442985ee2f57c9e1b548ee72ae842f4a9a20e3f417cc38dbc5dc684d9bb4ee" +dependencies = [ + "lazy_static", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9df98b037d039d03400d9dd06b0f8ce05486b5f25e9a2d7d36196e142ebbc52" +dependencies = [ + "ansi_term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "ufmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7ecea7ef79d3f8f878eee614afdf5256475c63ad76139d4da6125617c784a0" +dependencies = [ + "proc-macro-hack", + "ufmt-macros", + "ufmt-write", +] + +[[package]] +name = "ufmt-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed813e34a2bfa9dc58ee2ed8c8314d25e6d70c911486d64b8085cb695cfac069" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ufmt-write" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zero" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" diff --git a/Cargo.toml b/Cargo.toml index 594fa1ab..8c446608 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["bpf-sys", "redbpf", "redbpf-probes", "redbpf-macros", "cargo-bpf", "redbpf-tools", "examples/example-probes", "examples/example-userspace"] +members = ["bpf-sys", "redbpf", "redbpf-probes", "redbpf-macros", "cargo-bpf", "redbpf-tools","redbpf-tools/probes", "examples/example-probes", "examples/example-userspace"] diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..5c01d54d --- /dev/null +++ b/flake.lock @@ -0,0 +1,89 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1648199409, + "narHash": "sha256-JwPKdC2PoVBkG6E+eWw3j6BMR6sL3COpYWfif7RVb8Y=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "64a525ee38886ab9028e6f61790de0832aa3ef03", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1649096192, + "narHash": "sha256-7O8e+eZEYeU+ET98u/zW5epuoN/xYx9G+CIh4DjZVzY=", + "owner": "nix-community", + "repo": "naersk", + "rev": "d626f73332a8f587b613b0afe7293dd0777be07d", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1648219316, + "narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=", + "path": "/nix/store/m2vv0bxfchzrjngx8wi0i7dhzb9q2g50-source", + "rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1648219316, + "narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=", + "path": "/nix/store/m2vv0bxfchzrjngx8wi0i7dhzb9q2g50-source", + "rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "naersk": "naersk", + "nixpkgs": "nixpkgs_2", + "utils": "utils" + } + }, + "utils": { + "locked": { + "lastModified": 1648297722, + "narHash": "sha256-W+qlPsiZd8F3XkzXOzAoR+mpFqzm3ekQkJNa+PIh1BQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "0f8662f1319ad6abf89b3380dd2722369fc51ade", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..8426c2a1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,77 @@ +{ + inputs = { + nixpkgs.url = "nixpkgs"; + utils.url = "github:numtide/flake-utils"; + naersk.url = "github:nix-community/naersk"; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + }; + + outputs = { self, nixpkgs, utils, naersk, ... }: + utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + naersk-lib = naersk.lib."${system}"; + in + rec { + # `nix build` + packages.cargo-bpf = naersk-lib.buildPackage { + meta = with pkgs.lib; { + description = "Rust eBPF tooling"; + homepage = "https://foniod.org"; + license = licenses.mit; + platforms = [ "x86_64-linux" "aarch64-linux" ]; + }; + + name = "cargo-bpf"; + version = "3.0.0"; + + src = ./.; + root = ./.; + gitSubmodules = true; + + LIBCLANG_PATH = "${pkgs.llvmPackages_13.libclang.lib}/lib"; + KERNEL_SOURCE = "${pkgs.linuxPackages.kernel.dev}/lib/modules/${pkgs.linuxPackages.kernel.version}"; + + nativeBuildInputs = with pkgs; [ + pkgconfig + llvm_13 + clang_13 + ]; + buildInputs = with pkgs; [ + openssl + zlib + libxml2 + libelf + llvm_13.dev + clang_13 + linuxPackages.kernel.dev + linuxHeaders + glibc.dev + ]; + }; + defaultPackage = packages.cargo-bpf; + + defaultApp = apps.cargo-bpf; + apps.cargo-bpf = { + type = "app"; + program = "${self.defaultPackage."${system}"}/bin/cargo-bpf"; + }; + + # `nix develop` + devShell = pkgs.mkShell { + inputsFrom = [ self.packages.${system}.cargo-bpf ]; + nativeBuildInputs = with pkgs; [ + cargo + rustc + rust-analyzer + rustfmt + clippy + ]; + + LIBCLANG_PATH = "${pkgs.llvmPackages_13.libclang.lib}/lib"; + }; + }); +} From ab36bfaf3249482a0a0e84bb5bbbd7c1fb28379e Mon Sep 17 00:00:00 2001 From: Nikita Baksalyar Date: Sun, 3 Apr 2022 17:22:10 +0100 Subject: [PATCH 092/107] Add support for USDT semaphores USDT probes can be used to define static trace points which can provide helpful structured debug info. Due to performance considerations, these probes can be disabled by default through a mechanism called _semaphores_ which are basically pre-defined memory locations which need to be rewritten to enable probes. This can be done on a per-process basis by poking a given memory address but you need to know a PID beforehand to do so. The Linux kernel provides a mechanism to automatically override semaphore addresses when you setup a uprobe. This commit implements an optional argument for uprobes which can be used to set a semaphore address to pass on to the kernel. Signed-off-by: Nikita Baksalyar --- redbpf/src/lib.rs | 44 ++++++++++++++++++++++++++++++++++++-------- redbpf/src/perf.rs | 10 ++++++++-- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index cc001891..89bd5531 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -60,9 +60,8 @@ use goblin::elf::{reloc::RelocSection, section_header as hdr, Elf, SectionHeader use libbpf_sys::{ bpf_create_map_attr, bpf_create_map_xattr, bpf_insn, bpf_iter_create, bpf_link_create, bpf_load_program_xattr, bpf_map_def, bpf_map_info, bpf_prog_type, BPF_ANY, BPF_MAP_TYPE_ARRAY, - BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH, + BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LPM_TRIE, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH, BPF_MAP_TYPE_PERCPU_ARRAY, BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_PERF_EVENT_ARRAY, - BPF_MAP_TYPE_LPM_TRIE, BPF_SK_LOOKUP, BPF_SK_SKB_STREAM_PARSER, BPF_SK_SKB_STREAM_VERDICT, BPF_TRACE_ITER, }; @@ -906,6 +905,38 @@ impl UProbe { offset: u64, target: &str, pid: Option, + ) -> Result<()> { + self.attach_uprobe_with_semaphore(fn_name, offset, 0, target, pid) + } + + /// Attach the `uprobe` or `uretprobe`. + /// + /// Attach the probe to the function `fn_name` defined in the library or + /// binary at `path`. If an `offset` is given, the probe will be attached at + /// that byte offset inside the function. If `fn_name` is `None`, then + /// `offset` is treated as an absolute address. + /// + /// Use `semaphore` to set an address for a reference counter that will be + /// automatically updated by the kernel to enable a USDT probe. This feature is + /// supported by Linux kernels starting from 4.20. + /// + /// If a `pid` is passed, only the corresponding process is traced. + /// + /// # Example + /// ```no_run + /// use redbpf::Module; + /// let mut module = Module::parse(&std::fs::read("file.elf").unwrap()).unwrap(); + /// for uprobe in module.uprobes_mut() { + /// uprobe.attach_uprobe_semaphore(Some(&uprobe.name()), 0, 0, "/lib/x86_64-linux-gnu/libc-2.30.so", None).unwrap(); + /// } + /// ``` + pub fn attach_uprobe_with_semaphore( + &mut self, + fn_name: Option<&str>, + offset: u64, + semaphore: u32, + target: &str, + pid: Option, ) -> Result<()> { let fd = self.common.fd.ok_or(Error::ProgramNotLoaded)?; @@ -930,10 +961,10 @@ impl UProbe { unsafe { let pfd = match self.attach_type { ProbeAttachType::Entry => { - perf::open_uprobe_perf_event(&path, offset + sym_offset, pid)? + perf::open_uprobe_perf_event(&path, offset + sym_offset, semaphore, pid)? } ProbeAttachType::Return => { - perf::open_uretprobe_perf_event(&path, offset + sym_offset, pid)? + perf::open_uretprobe_perf_event(&path, offset + sym_offset, semaphore, pid)? } }; let ret = perf::attach_perf_event(fd, pfd); @@ -1088,10 +1119,7 @@ impl XDP { .ok_or(Error::ProgramNotLoaded)?; if let Err(e) = unsafe { detach_xdp(ifindex) } { if let Error::IO(ref oserr) = e { - error!( - "error detaching xdp from interface #{}: {}", - ifindex, oserr - ); + error!("error detaching xdp from interface #{}: {}", ifindex, oserr); } return Err(e); } diff --git a/redbpf/src/perf.rs b/redbpf/src/perf.rs index b7d3eea6..516a12f5 100644 --- a/redbpf/src/perf.rs +++ b/redbpf/src/perf.rs @@ -68,6 +68,8 @@ use libc::{ use crate::sys::perf::*; +const PERF_UPROBE_REF_CTR_OFFSET_SHIFT: u64 = 32; + unsafe fn open_perf_buffer(pid: i32, cpu: i32, group: RawFd, flags: u32) -> Result { let mut attr = mem::zeroed::(); @@ -159,6 +161,7 @@ pub(crate) unsafe fn open_kretprobe_perf_event(name: &str, offset: u64) -> Resul unsafe fn perf_event_open_uprobe( name: &str, offset: u64, + ref_ctr_offset: u32, pid: Option, retprobe: bool, ) -> Result { @@ -176,6 +179,7 @@ unsafe fn perf_event_open_uprobe( let bit = v[1].parse::().unwrap(); attr.config |= 1 << bit; } + attr.config |= (ref_ctr_offset as u64) << PERF_UPROBE_REF_CTR_OFFSET_SHIFT; attr.size = mem::size_of_val(&attr) as u32; attr.type_ = type_; let cname = CString::new(name)?; @@ -200,17 +204,19 @@ unsafe fn perf_event_open_uprobe( pub(crate) unsafe fn open_uprobe_perf_event( name: &str, offset: u64, + semaphore: u32, pid: Option, ) -> Result { - perf_event_open_uprobe(name, offset, pid, false) + perf_event_open_uprobe(name, offset, semaphore, pid, false) } pub(crate) unsafe fn open_uretprobe_perf_event( name: &str, offset: u64, + semaphore: u32, pid: Option, ) -> Result { - perf_event_open_uprobe(name, offset, pid, true) + perf_event_open_uprobe(name, offset, semaphore, pid, true) } pub(crate) unsafe fn open_tracepoint_perf_event(category: &str, name: &str) -> Result { From f78e007f41f76fbdf4402c04409176d44b46ab4b Mon Sep 17 00:00:00 2001 From: Mikhail Trishchenkov Date: Wed, 13 Apr 2022 16:12:32 +0700 Subject: [PATCH 093/107] Use libbpf-sys with semver versioning scheme Signed-off-by: Mikhail Trishchenkov --- Cargo.lock | 4 ++-- bpf-sys/Cargo.toml | 2 +- cargo-bpf/Cargo.toml | 2 +- redbpf-probes/Cargo.toml | 2 +- redbpf/Cargo.toml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1cbbf36..7caa41a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,9 +437,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libbpf-sys" -version = "0.6.1-2" +version = "0.6.2+v0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df746ec055dfd68b3d0e8ea8dd12882157529132652d0df2e001305ba4803f0c" +checksum = "1c2c15bbeae2b87e3a63feea85a579272ed082f0f4d8f0e7968cc9a17e1b4d69" dependencies = [ "cc", "pkg-config", diff --git a/bpf-sys/Cargo.toml b/bpf-sys/Cargo.toml index 8526b05e..5e4f3633 100644 --- a/bpf-sys/Cargo.toml +++ b/bpf-sys/Cargo.toml @@ -16,7 +16,7 @@ zero = "0.1" libc = "0.2" regex = { version = "1.5" } glob = "0.3.0" -libbpf-sys = "0.6.1-2" +libbpf-sys = "0.6.2" [build-dependencies] cc = "1.0" diff --git a/cargo-bpf/Cargo.toml b/cargo-bpf/Cargo.toml index f3418a0c..937bae3d 100644 --- a/cargo-bpf/Cargo.toml +++ b/cargo-bpf/Cargo.toml @@ -22,7 +22,7 @@ required-features = ["command-line"] clap = { version = "2.33", optional = true } bindgen = {version = "0.59.2", default-features = false, features = ["runtime"], optional = true} toml_edit = { version = "0.2", optional = true } -libbpf-sys = { version = "0.6.1-2", optional = true } +libbpf-sys = { version = "0.6.2", optional = true } bpf-sys = { version = "2.3.0", path = "../bpf-sys", optional = true } redbpf = { version = "2.3.0", path = "../redbpf", default-features = false, optional = true } futures = { version = "0.3", optional = true } diff --git a/redbpf-probes/Cargo.toml b/redbpf-probes/Cargo.toml index 7f2d6889..87c4debd 100644 --- a/redbpf-probes/Cargo.toml +++ b/redbpf-probes/Cargo.toml @@ -18,7 +18,7 @@ ufmt = { version = "0.1.0", default-features = false } [build-dependencies] cargo-bpf = { version = "2.3.0", path = "../cargo-bpf", default-features = false, features = ["bindings"] } bpf-sys = { version = "2.3.0", path = "../bpf-sys" } -libbpf-sys = "0.6.1-2" +libbpf-sys = "0.6.2" syn = {version = "1.0", default-features = false, features = ["parsing", "visit"] } quote = "1.0" glob = "0.3.0" diff --git a/redbpf/Cargo.toml b/redbpf/Cargo.toml index 0c0a755f..09c2de01 100644 --- a/redbpf/Cargo.toml +++ b/redbpf/Cargo.toml @@ -16,7 +16,7 @@ maintenance = { status = "actively-developed" } [dependencies] bpf-sys = { path = "../bpf-sys", version = "2.3.0" } -libbpf-sys = "0.6.1-2" +libbpf-sys = "0.6.2" goblin = "0.4" zero = "0.1" libc = "0.2" From bddfd30350702f57fabc5f9a771af35f605b1d5f Mon Sep 17 00:00:00 2001 From: Tom Lienard Date: Sun, 24 Apr 2022 21:14:02 +0200 Subject: [PATCH 094/107] fix(bpf-sys): use uname when /proc/version_signature is empty Signed-off-by: Tom Lienard Signed-off-by: QuiiBz --- bpf-sys/src/uname.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bpf-sys/src/uname.rs b/bpf-sys/src/uname.rs index cd6b2340..20fba4a6 100644 --- a/bpf-sys/src/uname.rs +++ b/bpf-sys/src/uname.rs @@ -25,13 +25,18 @@ pub fn uname() -> Result<::libc::utsname, ()> { #[inline] pub fn get_kernel_internal_version() -> Option { - let version = if let Ok(version) = fs::read_to_string("/proc/version_signature") { - parse_version_signature(&version.trim())? - } else { - to_str(&uname().ok()?.release).into() + let mut version = None; + + if let Ok(version_signature) = fs::read_to_string("/proc/version_signature") { + version = parse_version_signature(&version_signature.trim()); + } + + let final_version = match version { + Some(version) => version, + None => to_str(&uname().ok()?.release).to_string(), }; - parse_version(&version).map(|(major, minor, patch)| major << 16 | minor << 8 | patch) + parse_version(&final_version).map(|(major, minor, patch)| major << 16 | minor << 8 | patch) } #[allow(clippy::result_unit_err)] From 9c71df38d7907d842f215759e1669ae0a926e113 Mon Sep 17 00:00:00 2001 From: QuiiBz Date: Mon, 25 Apr 2022 22:16:44 +0200 Subject: [PATCH 095/107] refactor: remove mut from version Signed-off-by: QuiiBz --- bpf-sys/src/uname.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bpf-sys/src/uname.rs b/bpf-sys/src/uname.rs index 20fba4a6..652edff2 100644 --- a/bpf-sys/src/uname.rs +++ b/bpf-sys/src/uname.rs @@ -25,11 +25,11 @@ pub fn uname() -> Result<::libc::utsname, ()> { #[inline] pub fn get_kernel_internal_version() -> Option { - let mut version = None; - - if let Ok(version_signature) = fs::read_to_string("/proc/version_signature") { - version = parse_version_signature(&version_signature.trim()); - } + let version = if let Ok(version_signature) = fs::read_to_string("/proc/version_signature") { + parse_version_signature(&version_signature.trim()) + } else { + None + }; let final_version = match version { Some(version) => version, From 8b6ba4e34794c108e5faed1be308fa860ed383e9 Mon Sep 17 00:00:00 2001 From: John Soo Date: Sun, 24 Apr 2022 09:55:14 -0700 Subject: [PATCH 096/107] flake.lock: Swap path type nixpkgs to github. To fix flake setups in settings where the nixpkgs revision has not been fetched yet. Signed-off-by: John Soo --- flake.lock | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/flake.lock b/flake.lock index 5c01d54d..4afa3b4e 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1648199409, - "narHash": "sha256-JwPKdC2PoVBkG6E+eWw3j6BMR6sL3COpYWfif7RVb8Y=", + "lastModified": 1650374568, + "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", "owner": "edolstra", "repo": "flake-compat", - "rev": "64a525ee38886ab9028e6f61790de0832aa3ef03", + "rev": "b4a34015c698c7793d592d66adbab377907a2be8", "type": "github" }, "original": { @@ -21,11 +21,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1649096192, - "narHash": "sha256-7O8e+eZEYeU+ET98u/zW5epuoN/xYx9G+CIh4DjZVzY=", + "lastModified": 1650265945, + "narHash": "sha256-SO8+1db4jTOjnwP++29vVgImLIfETSXyoz0FuLkiikE=", "owner": "nix-community", "repo": "naersk", - "rev": "d626f73332a8f587b613b0afe7293dd0777be07d", + "rev": "e8f9f8d037774becd82fce2781e1abdb7836d7df", "type": "github" }, "original": { @@ -36,11 +36,12 @@ }, "nixpkgs": { "locked": { - "lastModified": 1648219316, - "narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=", - "path": "/nix/store/m2vv0bxfchzrjngx8wi0i7dhzb9q2g50-source", - "rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634", - "type": "path" + "lastModified": 1650726686, + "narHash": "sha256-hE5PCqQlsdgWH3AUTwesvjZWs5ZUZ8SjMS5cnFB6W54=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3c0f57e36ed0cf9947281e3b31f1bebb7ce5d4a1", + "type": "github" }, "original": { "id": "nixpkgs", @@ -49,11 +50,12 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1648219316, - "narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=", - "path": "/nix/store/m2vv0bxfchzrjngx8wi0i7dhzb9q2g50-source", - "rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634", - "type": "path" + "lastModified": 1650726686, + "narHash": "sha256-hE5PCqQlsdgWH3AUTwesvjZWs5ZUZ8SjMS5cnFB6W54=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3c0f57e36ed0cf9947281e3b31f1bebb7ce5d4a1", + "type": "github" }, "original": { "id": "nixpkgs", @@ -70,11 +72,11 @@ }, "utils": { "locked": { - "lastModified": 1648297722, - "narHash": "sha256-W+qlPsiZd8F3XkzXOzAoR+mpFqzm3ekQkJNa+PIh1BQ=", + "lastModified": 1649676176, + "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", "owner": "numtide", "repo": "flake-utils", - "rev": "0f8662f1319ad6abf89b3380dd2722369fc51ade", + "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", "type": "github" }, "original": { From 5021e1794d46779df7eeb1c4acf6642c744a0832 Mon Sep 17 00:00:00 2001 From: John Soo Date: Sun, 1 May 2022 10:54:48 -0700 Subject: [PATCH 097/107] flake: Correct clang/llvm inputs, add pkg-config to shell. Signed-off-by: John Soo --- flake.nix | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 8426c2a1..91bf2fd2 100644 --- a/flake.nix +++ b/flake.nix @@ -32,13 +32,13 @@ root = ./.; gitSubmodules = true; - LIBCLANG_PATH = "${pkgs.llvmPackages_13.libclang.lib}/lib"; + LIBCLANG_PATH = "${pkgs.llvmPackages_14.libclang.lib}/lib"; KERNEL_SOURCE = "${pkgs.linuxPackages.kernel.dev}/lib/modules/${pkgs.linuxPackages.kernel.version}"; nativeBuildInputs = with pkgs; [ pkgconfig - llvm_13 - clang_13 + llvm_14 + clang_14 ]; buildInputs = with pkgs; [ openssl @@ -65,13 +65,14 @@ inputsFrom = [ self.packages.${system}.cargo-bpf ]; nativeBuildInputs = with pkgs; [ cargo + pkg-config rustc rust-analyzer rustfmt clippy ]; - LIBCLANG_PATH = "${pkgs.llvmPackages_13.libclang.lib}/lib"; + LIBCLANG_PATH = "${pkgs.llvmPackages_14.libclang.lib}/lib"; }; }); } From 51e118df6514188220afabe9128081e49ea0b543 Mon Sep 17 00:00:00 2001 From: Junyeong Jeong Date: Wed, 4 May 2022 00:44:37 +0900 Subject: [PATCH 098/107] Parse ___btf_map_ structure to get key/value type id Signed-off-by: Junyeong Jeong --- redbpf/src/btf.rs | 16 ++++++----- redbpf/src/lib.rs | 71 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/redbpf/src/btf.rs b/redbpf/src/btf.rs index 5581a523..048aca80 100644 --- a/redbpf/src/btf.rs +++ b/redbpf/src/btf.rs @@ -354,8 +354,9 @@ impl BTF { /// Get BTF type ids of a map of which symbol name is `map_sym_name` /// - /// A variable of which name is `MAP_BTF_` holds `MapBtf` - /// structure that in turn holds key and value types of the map. + /// A variable of which name is `MAP_BTF_` holds + /// `____btf_map_` structure type that in turn holds `key` + /// and `value` members. pub(crate) fn get_map_type_ids(&self, map_sym_name: &str) -> Result { if !self.is_loaded() { return Err(Error::BTF("BTF is not loaded yet".to_string())); @@ -383,7 +384,8 @@ impl BTF { match map_btf_type { Structure(struct_comm, members) => { - if &struct_comm.name_raw != "MapBtf" { + let btf_type_name = format!("____btf_map_{}", map_sym_name); + if struct_comm.name_raw != btf_type_name { let msg = format!("illegal structure name: {}", struct_comm.name_raw); error!("{}", msg); return Err(Error::BTF(msg)); @@ -392,14 +394,14 @@ impl BTF { let key_type_id = members .iter() .find_map(|memb| { - if &memb.name == "key_type" { + if &memb.name == "key" { Some(memb.type_id()) } else { None } }) .ok_or_else(|| { - let msg = format!("MapBtf::key_type field not found"); + let msg = format!("{}::key_type field not found", btf_type_name); error!("{}", msg); Error::BTF(msg) })?; @@ -407,14 +409,14 @@ impl BTF { let value_type_id = members .iter() .find_map(|memb| { - if &memb.name == "value_type" { + if &memb.name == "value" { Some(memb.type_id()) } else { None } }) .ok_or_else(|| { - let msg = format!("MapBtf::value_type field not found"); + let msg = format!("{}::value field not found", btf_type_name); error!("{}", msg); Error::BTF(msg) })?; diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 89bd5531..6bb62118 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -60,9 +60,17 @@ use goblin::elf::{reloc::RelocSection, section_header as hdr, Elf, SectionHeader use libbpf_sys::{ bpf_create_map_attr, bpf_create_map_xattr, bpf_insn, bpf_iter_create, bpf_link_create, bpf_load_program_xattr, bpf_map_def, bpf_map_info, bpf_prog_type, BPF_ANY, BPF_MAP_TYPE_ARRAY, - BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_LPM_TRIE, BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH, - BPF_MAP_TYPE_PERCPU_ARRAY, BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_PERF_EVENT_ARRAY, - BPF_SK_LOOKUP, BPF_SK_SKB_STREAM_PARSER, BPF_SK_SKB_STREAM_VERDICT, BPF_TRACE_ITER, + BPF_MAP_TYPE_ARRAY_OF_MAPS, BPF_MAP_TYPE_BLOOM_FILTER, BPF_MAP_TYPE_CGROUP_ARRAY, + BPF_MAP_TYPE_CGROUP_STORAGE, BPF_MAP_TYPE_CPUMAP, BPF_MAP_TYPE_DEVMAP, + BPF_MAP_TYPE_DEVMAP_HASH, BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_HASH_OF_MAPS, + BPF_MAP_TYPE_INODE_STORAGE, BPF_MAP_TYPE_LPM_TRIE, BPF_MAP_TYPE_LRU_HASH, + BPF_MAP_TYPE_LRU_PERCPU_HASH, BPF_MAP_TYPE_PERCPU_ARRAY, BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE, + BPF_MAP_TYPE_PERCPU_HASH, BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_MAP_TYPE_PROG_ARRAY, + BPF_MAP_TYPE_QUEUE, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY, BPF_MAP_TYPE_RINGBUF, + BPF_MAP_TYPE_SK_STORAGE, BPF_MAP_TYPE_SOCKHASH, BPF_MAP_TYPE_SOCKMAP, BPF_MAP_TYPE_STACK, + BPF_MAP_TYPE_STACK_TRACE, BPF_MAP_TYPE_STRUCT_OPS, BPF_MAP_TYPE_TASK_STORAGE, + BPF_MAP_TYPE_XSKMAP, BPF_SK_LOOKUP, BPF_SK_SKB_STREAM_PARSER, BPF_SK_SKB_STREAM_VERDICT, + BPF_TRACE_ITER, }; use libc::{self, pid_t}; @@ -1858,7 +1866,7 @@ impl Map { btf_type_id: Option, ) -> Result { let cname = CString::new(name)?; - let attr = unsafe { + let mut attr = unsafe { let mut attr_uninit = MaybeUninit::::zeroed(); let attr_ptr = attr_uninit.as_mut_ptr(); (*attr_ptr).name = cname.as_ptr(); @@ -1867,7 +1875,7 @@ impl Map { (*attr_ptr).key_size = config.key_size; (*attr_ptr).value_size = config.value_size; (*attr_ptr).max_entries = config.max_entries; - if let Some(type_id) = btf_type_id { + if let Some(type_id) = btf_type_id.as_ref() { (*attr_ptr).btf_fd = type_id.btf_fd as u32; (*attr_ptr).btf_key_type_id = type_id.key_type_id; (*attr_ptr).btf_value_type_id = type_id.value_type_id; @@ -1880,6 +1888,7 @@ impl Map { // used for the memory accounting and bpf() syscall returned -EPERM on // exceeding the limit. if fd < 0 { + const ENOTSUPP: i32 = 524; // ENOTSUPP of the Linux kernel if let Some(libc::EPERM) = io::Error::last_os_error().raw_os_error() { let mut uninit = MaybeUninit::::zeroed(); let p = uninit.as_mut_ptr(); @@ -1893,6 +1902,21 @@ impl Map { } } } + } else if let Some(ENOTSUPP) = io::Error::last_os_error().raw_os_error() { + if btf_type_id.is_some() { + debug!( + "The kernel does not support BTF for {}. Ignoring BTF for map {}.", + map_type_name(config.type_), + name + ); + attr.btf_fd = 0; + attr.btf_key_type_id = 0; + attr.btf_value_type_id = 0; + + unsafe { + fd = bpf_create_map_xattr(&attr); + } + } } } if fd >= 0 { @@ -3034,3 +3058,40 @@ fn bpf_percpu_map_get(fd: RawFd, mut key: K) -> Option String { + match map_type { + BPF_MAP_TYPE_HASH => "HashMap", + BPF_MAP_TYPE_ARRAY => "Array", + BPF_MAP_TYPE_PROG_ARRAY => "ProgramArray", + BPF_MAP_TYPE_PERF_EVENT_ARRAY => "PerfMap", + BPF_MAP_TYPE_PERCPU_HASH => "PerCpuHashMap", + BPF_MAP_TYPE_PERCPU_ARRAY => "PerCpuArray", + BPF_MAP_TYPE_STACK_TRACE => "StackTrace", + BPF_MAP_TYPE_CGROUP_ARRAY => "CgroupArray", + BPF_MAP_TYPE_LRU_HASH => "LruHashMap", + BPF_MAP_TYPE_LRU_PERCPU_HASH => "LruPerCpuHashMap", + BPF_MAP_TYPE_LPM_TRIE => "LpmTrieMap", + BPF_MAP_TYPE_ARRAY_OF_MAPS => "ArrayOfMaps", + BPF_MAP_TYPE_HASH_OF_MAPS => "HashOfMaps", + BPF_MAP_TYPE_DEVMAP => "DevMap", + BPF_MAP_TYPE_SOCKMAP => "SockMap", + BPF_MAP_TYPE_CPUMAP => "CpuMap", + BPF_MAP_TYPE_XSKMAP => "XskMap", + BPF_MAP_TYPE_SOCKHASH => "SockHashMap", + BPF_MAP_TYPE_CGROUP_STORAGE => "CgroupStorage", + BPF_MAP_TYPE_REUSEPORT_SOCKARRAY => "ReusePortSockArray", + BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE => "PerCpuCgroupStorage", + BPF_MAP_TYPE_QUEUE => "Queue", + BPF_MAP_TYPE_STACK => "Stack", + BPF_MAP_TYPE_SK_STORAGE => "SkStorage", + BPF_MAP_TYPE_DEVMAP_HASH => "DevMapHashMap", + BPF_MAP_TYPE_STRUCT_OPS => "StructOps", + BPF_MAP_TYPE_RINGBUF => "RingBuf", + BPF_MAP_TYPE_INODE_STORAGE => "InodeStorage", + BPF_MAP_TYPE_TASK_STORAGE => "TaskStorage", + BPF_MAP_TYPE_BLOOM_FILTER => "BloomFilter", + _ => "(unknown)", + } + .to_string() +} From fc2932ca514dfebeb5e00aae9ecbe4c7e59b208d Mon Sep 17 00:00:00 2001 From: GermanCoding Date: Tue, 10 May 2022 14:27:02 +0200 Subject: [PATCH 099/107] Only set BTF key/value id if size is nonzero (fixes #327) Signed-off-by: GermanCoding --- redbpf/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index 6bb62118..aabfa076 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -1877,8 +1877,12 @@ impl Map { (*attr_ptr).max_entries = config.max_entries; if let Some(type_id) = btf_type_id.as_ref() { (*attr_ptr).btf_fd = type_id.btf_fd as u32; - (*attr_ptr).btf_key_type_id = type_id.key_type_id; - (*attr_ptr).btf_value_type_id = type_id.value_type_id; + if config.key_size != 0 { + (*attr_ptr).btf_key_type_id = type_id.key_type_id; + } + if config.value_size != 0 { + (*attr_ptr).btf_value_type_id = type_id.value_type_id; + } } attr_uninit.assume_init() }; From 52e7909feb0ce11bfb9cf1f28ae52881bc07ca3e Mon Sep 17 00:00:00 2001 From: GermanCoding Date: Mon, 30 May 2022 15:07:02 +0200 Subject: [PATCH 100/107] Add tracepoint macro This improves support for tracepoints by adding a macro and prelude to create them. It also adds some basic documentation for usage and fixes a loading-bug which prevented tracepoints from being attached previously. Signed-off-by: GermanCoding --- redbpf-macros/src/lib.rs | 26 ++++++++++++++++++ redbpf-probes/src/lib.rs | 1 + redbpf-probes/src/tracepoint/mod.rs | 36 +++++++++++++++++++++++++ redbpf-probes/src/tracepoint/prelude.rs | 23 ++++++++++++++++ redbpf/src/lib.rs | 2 +- redbpf/src/load/loader.rs | 10 ++++++- redbpf/src/perf.rs | 1 + 7 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 redbpf-probes/src/tracepoint/mod.rs create mode 100644 redbpf-probes/src/tracepoint/prelude.rs diff --git a/redbpf-macros/src/lib.rs b/redbpf-macros/src/lib.rs index f9df99a5..64f861a1 100644 --- a/redbpf-macros/src/lib.rs +++ b/redbpf-macros/src/lib.rs @@ -498,6 +498,32 @@ pub fn uretprobe(attrs: TokenStream, item: TokenStream) -> TokenStream { probe_impl("uretprobe", attrs, wrapper, name) } +/// Attribute macro that must be used to define [`tracepoint probes`](https://www.kernel.org/doc/Documentation/trace/tracepoint-analysis.txt). +/// +/// # Example +/// +/// ```no_run +/// use redbpf_probes::tracepoint::prelude::*; +/// +/// #[tracepoint("syscalls:sys_enter_ioctl")] +/// pub fn sys_enter_ioctl(args: *const c_void) { +/// // do something here +/// } +/// ``` +/// +/// # Function parameters +/// +/// The input parameter is a raw pointer to a struct containing tracepoint-specific members. +/// Members and their offsets for your kernel can be found in (if debugfs is available) +/// +/// `sudo cat /sys/kernel/debug/tracing/events///format` +#[proc_macro_attribute] +pub fn tracepoint(attrs: TokenStream, item: TokenStream) -> TokenStream { + let item = parse_macro_input!(item as ItemFn); + let name = item.sig.ident.to_string(); + probe_impl("tracepoint", attrs, item, name) +} + /// Attribute macro that must be used to define [`XDP` probes](https://www.iovisor.org/technology/xdp). /// /// See also the [`XDP` API provided by diff --git a/redbpf-probes/src/lib.rs b/redbpf-probes/src/lib.rs index 35846981..2adf3fdb 100644 --- a/redbpf-probes/src/lib.rs +++ b/redbpf-probes/src/lib.rs @@ -117,5 +117,6 @@ pub mod socket; pub mod socket_filter; pub mod sockmap; pub mod tc; +pub mod tracepoint; pub mod uprobe; pub mod xdp; diff --git a/redbpf-probes/src/tracepoint/mod.rs b/redbpf-probes/src/tracepoint/mod.rs new file mode 100644 index 00000000..c2e1f380 --- /dev/null +++ b/redbpf-probes/src/tracepoint/mod.rs @@ -0,0 +1,36 @@ +// Copyright 2022 Authors of Red Sift +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +/*! +Tracepoint probes. + +[Tracepoints](https://www.kernel.org/doc/Documentation/trace/tracepoints.txt) are similar to [kprobes](super::kprobe), but without the ability +to read CPU registers. Instead a tracepoint receives a fixed set of input arguments +depending on the tracepoint. This makes tracepoints more stable across kernel versions +and is less architecture-specific. However tracepoint probes can only be attached +to a predetermined set of kernel functions (unlike kprobes that can be attached almost anywhere). + +There is no explicit return type for tracepoints (i.e. no kretprobe equivalent), but many kernel functions +have tracepoints defined at function entry and exit. + +# Example +Do something when ioctl system call is triggered. See the examples directory for a more +complete scenario. + +```no_run +#![no_std] +#![no_main] +use redbpf_probes::tracepoint::prelude::*; +program!(0xFFFFFFFE, "GPL"); +#[tracepoint("syscalls:sys_enter_ioctl")] +pub fn sys_enter_ioctl(args: *const c_void) { + // do something here + // ... +} +``` + */ +pub mod prelude; diff --git a/redbpf-probes/src/tracepoint/prelude.rs b/redbpf-probes/src/tracepoint/prelude.rs new file mode 100644 index 00000000..84cd90b8 --- /dev/null +++ b/redbpf-probes/src/tracepoint/prelude.rs @@ -0,0 +1,23 @@ +// Copyright 2022 Authors of Red Sift +// +// Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +//! The Tracepoint Prelude +//! +//! The purpose of this module is to alleviate imports of the common tracepoint types +//! by adding a glob import to the top of tracepoint programs: +//! +//! ``` +//! use redbpf_probes::tracepoint::prelude::*; +//! ``` +pub use crate::bindings::*; +pub use crate::helpers::*; +pub use crate::maps::*; +pub use crate::registers::*; +#[cfg(feature = "ringbuf")] +pub use crate::ringbuf::*; +pub use cty::*; +pub use redbpf_macros::{tracepoint, map, printk, program}; diff --git a/redbpf/src/lib.rs b/redbpf/src/lib.rs index aabfa076..5dccfc7a 100644 --- a/redbpf/src/lib.rs +++ b/redbpf/src/lib.rs @@ -1033,7 +1033,6 @@ impl UProbe { impl TracePoint { pub fn attach_trace_point(&mut self, category: &str, name: &str) -> Result<()> { let fd = self.common.fd.ok_or(Error::ProgramNotLoaded)?; - // TODO Check this works correctly unsafe { let pfd = perf::open_tracepoint_perf_event(category, name)?; perf::attach_perf_event(fd, pfd) @@ -1592,6 +1591,7 @@ impl<'a> ModuleBuilder<'a> { | (hdr::SHT_PROGBITS, Some(kind @ "kretprobe"), Some(name)) | (hdr::SHT_PROGBITS, Some(kind @ "uprobe"), Some(name)) | (hdr::SHT_PROGBITS, Some(kind @ "uretprobe"), Some(name)) + | (hdr::SHT_PROGBITS, Some(kind @ "tracepoint"), Some(name)) | (hdr::SHT_PROGBITS, Some(kind @ "xdp"), Some(name)) | (hdr::SHT_PROGBITS, Some(kind @ "socketfilter"), Some(name)) | (hdr::SHT_PROGBITS, Some(kind @ "streamparser"), Some(name)) diff --git a/redbpf/src/load/loader.rs b/redbpf/src/load/loader.rs index c49f90c3..924c8816 100644 --- a/redbpf/src/load/loader.rs +++ b/redbpf/src/load/loader.rs @@ -13,7 +13,7 @@ use std::io; use std::path::Path; use crate::load::map_io::{PerfMessageStream, RingBufMessageStream}; -use crate::{cpus, Program}; +use crate::{cpus, Program, TracePoint}; use crate::{ Error, KProbe, Map, Module, PerfMap, RingBufMap, SkLookup, SocketFilter, StreamParser, StreamVerdict, TaskIter, UProbe, XDP, @@ -202,4 +202,12 @@ impl Loaded { pub fn task_iter_mut(&mut self, name: &str) -> Option<&mut TaskIter> { self.module.task_iter_mut(name) } + + pub fn tracepoints_mut(&mut self) -> impl Iterator { + self.module.trace_points_mut() + } + + pub fn tracepoint_mut(&mut self, name: &str) -> Option<&mut TracePoint> { + self.module.trace_point_mut(name) + } } diff --git a/redbpf/src/perf.rs b/redbpf/src/perf.rs index 516a12f5..a48b3792 100644 --- a/redbpf/src/perf.rs +++ b/redbpf/src/perf.rs @@ -223,6 +223,7 @@ pub(crate) unsafe fn open_tracepoint_perf_event(category: &str, name: &str) -> R let file = format!("/sys/kernel/debug/tracing/events/{}/{}/id", category, name); let tp_id = fs::read_to_string(&file) .expect(&format!("Cannot read {}", &file)) + .trim() .parse::() .unwrap(); if tp_id < 0 { From a0ea23e4286a1b6cf0756cc44e9b075abe0225b6 Mon Sep 17 00:00:00 2001 From: GermanCoding Date: Tue, 31 May 2022 16:32:16 +0200 Subject: [PATCH 101/107] Add example to demonstrate tracepoint probes Signed-off-by: GermanCoding --- examples/example-probes/Cargo.toml | 5 +++ examples/example-probes/build.rs | 4 +-- examples/example-probes/include/bindings.h | 1 + .../src/connection_tracer/main.rs | 32 +++++++++++++++++ .../src/connection_tracer/mod.rs | 25 ++++++++++++++ examples/example-probes/src/lib.rs | 1 + .../examples/connection-tracer.rs | 34 +++++++++++++++++++ 7 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 examples/example-probes/src/connection_tracer/main.rs create mode 100644 examples/example-probes/src/connection_tracer/mod.rs create mode 100644 examples/example-userspace/examples/connection-tracer.rs diff --git a/examples/example-probes/Cargo.toml b/examples/example-probes/Cargo.toml index eaf202dc..849201ea 100644 --- a/examples/example-probes/Cargo.toml +++ b/examples/example-probes/Cargo.toml @@ -88,3 +88,8 @@ required-features = ["probes"] name = "bounded_loop" path = "src/bounded_loop/main.rs" required-features = ["probes"] + +[[bin]] +name = "connection_tracer" +path = "src/connection_tracer/main.rs" +required-features = ["probes"] diff --git a/examples/example-probes/build.rs b/examples/example-probes/build.rs index 6cf97202..518ac89f 100644 --- a/examples/example-probes/build.rs +++ b/examples/example-probes/build.rs @@ -81,9 +81,9 @@ fn main() { // specify kernel headers in include/bindings.h, not here. builder = builder.header("include/bindings.h"); // designate whitelist types - let types = ["request"]; + let types = ["request", "sockaddr_in"]; // allowing variables - let variables = ["NSEC_PER_MSEC", "NSEC_PER_USEC"]; + let variables = ["NSEC_PER_MSEC", "NSEC_PER_USEC", "AF_INET"]; for &ty in types.iter() { builder = builder.allowlist_type(ty); } diff --git a/examples/example-probes/include/bindings.h b/examples/example-probes/include/bindings.h index 5d55fcb9..eb901e8a 100644 --- a/examples/example-probes/include/bindings.h +++ b/examples/example-probes/include/bindings.h @@ -24,5 +24,6 @@ #include #include #include +#include #endif diff --git a/examples/example-probes/src/connection_tracer/main.rs b/examples/example-probes/src/connection_tracer/main.rs new file mode 100644 index 00000000..76549619 --- /dev/null +++ b/examples/example-probes/src/connection_tracer/main.rs @@ -0,0 +1,32 @@ +//! This example demonstrates how to use a tracepoint to trace the connect() system call +//! +//! See also the definition of the structs in `mod.rs` +#![no_std] +#![no_main] + +use core::mem::size_of; +use example_probes::connection_tracer::SysEnterConnectArgs; +use redbpf_probes::tracepoint::prelude::*; + +program!(0xFFFFFFFE, "GPL"); + +#[tracepoint] +unsafe fn sys_enter_connect(args: *const SysEnterConnectArgs) { + let args = bpf_probe_read(args).expect("Failed to read arguments"); + let addrlen = args.addrlen; + if addrlen < size_of::() as u64 { + return; + } + + let addr = args.useraddr; + let family = bpf_probe_read(addr as *const sa_family_t).unwrap_or(u16::MAX) as u32; + match family { + AF_INET => { + let sockaddr_struct = bpf_probe_read(addr as *const sockaddr_in).unwrap(); + let ipv4 = &(sockaddr_struct.sin_addr.s_addr as u64) as *const u64; + bpf_trace_printk_raw(b"Connected to IPv4 address %pI4\0", ipv4 as u64, 0, 0) + .expect("printk failed"); + } + _ => {} + }; +} diff --git a/examples/example-probes/src/connection_tracer/mod.rs b/examples/example-probes/src/connection_tracer/mod.rs new file mode 100644 index 00000000..3b15132f --- /dev/null +++ b/examples/example-probes/src/connection_tracer/mod.rs @@ -0,0 +1,25 @@ +//! To read the input parameters for a tracepoint conveniently, define a struct +//! to hold the input arguments. Refer to to +//! `/sys/kernel/debug/tracing/events///format` +//! for information about a specific tracepoint. + +#[repr(C, packed(1))] +pub struct TracepointCommonArgs { + pub ctype: u16, + pub flags: u8, + pub preempt_count: u8, + pub pid: i32, +} + +/// Members defined in `cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_connect/format +/// Note that offset addresses are important here, so ensure the compiler does not add padding. +/// Any required padding will be set explicitly here. +#[repr(C, packed(1))] +pub struct SysEnterConnectArgs { + pub common: TracepointCommonArgs, + pub sys_nr: i32, + pad: u32, + pub fd: u64, + pub useraddr: u64, + pub addrlen: u64, +} \ No newline at end of file diff --git a/examples/example-probes/src/lib.rs b/examples/example-probes/src/lib.rs index 743880c8..c6470f5a 100644 --- a/examples/example-probes/src/lib.rs +++ b/examples/example-probes/src/lib.rs @@ -11,6 +11,7 @@ #[cfg(feature = "probes")] pub mod bindings; +pub mod connection_tracer; pub mod echo; pub mod hashmaps; pub mod mallocstacks; diff --git a/examples/example-userspace/examples/connection-tracer.rs b/examples/example-userspace/examples/connection-tracer.rs new file mode 100644 index 00000000..8d2f916a --- /dev/null +++ b/examples/example-userspace/examples/connection-tracer.rs @@ -0,0 +1,34 @@ +use libc; +use std::process; +use tokio::signal::ctrl_c; +use tracing::{error, subscriber, Level}; +use tracing_subscriber::FmtSubscriber; +use redbpf::load::Loader; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + let subscriber = FmtSubscriber::builder() + .with_max_level(Level::TRACE) + .finish(); + subscriber::set_global_default(subscriber).unwrap(); + if unsafe { libc::geteuid() != 0 } { + error!("You must be root to use eBPF!"); + process::exit(1); + } + + let mut loaded = Loader::load(probe_code()).expect("error loading probe"); + for tracepoint in loaded.tracepoints_mut() { + tracepoint.attach_trace_point("syscalls", "sys_enter_connect") + .expect(format!("error on attach_trace_point to {}", tracepoint.name()).as_str()); + } + + println!("Hit Ctrl-C to quit"); + ctrl_c().await.expect("Error awaiting CTRL-C"); +} + +fn probe_code() -> &'static [u8] { + include_bytes!(concat!( + env!("OUT_DIR"), + "/target/bpf/programs/connection_tracer/connection_tracer.elf" + )) +} From 92522c3c4778cef120eb91de12539276fdc9b4b4 Mon Sep 17 00:00:00 2001 From: GermanCoding Date: Tue, 31 May 2022 16:39:24 +0200 Subject: [PATCH 102/107] Update README, add new types Signed-off-by: GermanCoding --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 54494d7b..a8d4edd7 100644 --- a/README.md +++ b/README.md @@ -31,15 +31,15 @@ programs using Rust. It includes: - Offers many BPF map types 1. `HashMap`, `PerCpuHashMap`, `LruHashMap`, `LruPerCpuHashMap`, `Array`, `PerCpuArray`, `PerfMap`, `TcHashMap`, `StackTrace`, `ProgramArray`, - `SockMap`, `DevMap` + `SockMap`, `DevMap`, `RingBuf` - Offers several BPF program types 1. `KProbe`, `KRetProbe`, `UProbe`, `URetProbe`, `SocketFilter`, `XDP`, - `StreamParser`, `StreamVerdict`, `TaskIter`, `SkLookup` + `StreamParser`, `StreamVerdict`, `TaskIter`, `SkLookup`, `Tracepoint` - Provides attribute macros that define various kind of BPF programs and BPF maps in a declarative way. 1. `#[kprobe]`, `#[kretprobe]`, `#[uprobe]`, `#[uretprobe]`, `#[xdp]`, `#[tc_action]`, `#[socket_filter]`, `#[stream_parser]`, - `#[stream_verdict]`, `#[task_iter]` + `#[stream_verdict]`, `#[task_iter]`, `#[tracepoint]` 2. `#[map]` - Can generate Rust bindings from the Linux kernel headers or from the BTF of `vmlinux` From 69a06e11f9e190fa9069db9d265bcaacd7635a9f Mon Sep 17 00:00:00 2001 From: Robert Schauklies Date: Wed, 10 Aug 2022 22:59:44 +0200 Subject: [PATCH 103/107] Add IPv6 support to XDP Added basic IPv6-support via parsing the IP-Header. We are currently not parsing any other IPv6 extensions. Signed-off-by: Robert Schauklies --- redbpf-probes/src/net.rs | 47 +++++++++++++++++++-------- redbpf-tools/probes/src/knock/main.rs | 17 ++++++---- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/redbpf-probes/src/net.rs b/redbpf-probes/src/net.rs index e58badd0..7e3ccb5d 100644 --- a/redbpf-probes/src/net.rs +++ b/redbpf-probes/src/net.rs @@ -25,7 +25,10 @@ pub enum Transport { TCP(*const tcphdr), UDP(*const udphdr), } - +pub enum Ip { + IPv4(*const iphdr), + IPv6(*const ipv6hdr), +} impl Transport { /// Returns the source port. #[inline] @@ -126,32 +129,50 @@ where /// Returns the packet's `IP` header if present. #[inline] - fn ip(&self) -> NetworkResult<*const iphdr> { + fn ip(&self) -> NetworkResult { let eth = self.eth()?; unsafe { - if (*eth).h_proto != u16::from_be(ETH_P_IP as u16) { - return Err(NetworkError::NoIPHeader); - } + let ip = match u16::from_be((*eth).h_proto as u16) as u32 { + ETH_P_IP => (Ip::IPv4(self.ptr_after(eth)?)), + ETH_P_IPV6 => (Ip::IPv6(self.ptr_after(eth)?)), + _t => return Err(NetworkError::NoIPHeader), + }; + Ok(ip) + } + } + fn transport_ipv6(&self, ipv6header: *const ipv6hdr) -> NetworkResult { + //the next header after the ipv6-header is always 40 bytes away + let addr = ipv6header as usize + 40; - self.ptr_after(eth) + unsafe { + match (*ipv6header).nexthdr as u32 { + IPPROTO_TCP => Ok(Transport::TCP(self.ptr_at(addr)?)), + IPPROTO_UDP => Ok(Transport::UDP(self.ptr_at(addr)?)), + t => Err(NetworkError::UnsupportedTransport(t)), + } } } - /// Returns the packet's transport header if present. - #[inline] - fn transport(&self) -> NetworkResult { + fn transport_ipv4(&self, ipv4header: *const iphdr) -> NetworkResult { + let addr = unsafe { ipv4header as usize + ((*ipv4header).ihl() * 4) as usize }; unsafe { - let ip = self.ip()?; - let addr = ip as usize + ((*ip).ihl() * 4) as usize; - let transport = match (*ip).protocol as u32 { + let transport = match (*ipv4header).protocol as u32 { IPPROTO_TCP => (Transport::TCP(self.ptr_at(addr)?)), IPPROTO_UDP => (Transport::UDP(self.ptr_at(addr)?)), t => return Err(NetworkError::UnsupportedTransport(t)), }; - Ok(transport) } } + /// Returns the packet's transport header if present. + #[inline] + fn transport(&self) -> NetworkResult { + use Ip::*; + match self.ip()? { + IPv6(ipaddr) => self.transport_ipv6(ipaddr), + IPv4(ipaddr) => self.transport_ipv4(ipaddr), + } + } /// Returns the packet's data starting after the transport headers. #[inline] diff --git a/redbpf-tools/probes/src/knock/main.rs b/redbpf-tools/probes/src/knock/main.rs index 23ed78e1..522cb504 100644 --- a/redbpf-tools/probes/src/knock/main.rs +++ b/redbpf-tools/probes/src/knock/main.rs @@ -32,8 +32,13 @@ pub fn probe(ctx: XdpContext) -> XdpResult { t @ Transport::TCP(_) => t, _ => return Ok(XdpAction::Pass), }; - let ip = unsafe { *ctx.ip()? }; - + //for now ipv4 + let ip = match ctx.ip()? { + Ip::IPv4(ip) => ip, + _ => panic!("NO IPv4") + }; + //let ip = unsafe { *ctx.ip()? }; + let source_addr = unsafe{u32::from_be((*ip).saddr)}; // get the knock sequence as configured by user space let target_seq = unsafe { let seq_id = 0u8; @@ -47,7 +52,7 @@ pub fn probe(ctx: XdpContext) -> XdpResult { // get the knock data for the source IP address let mut knock = unsafe { - let key = ip.saddr; + let key = source_addr; match knocks.get_mut(&key) { Some(k) => k, None => { @@ -69,7 +74,7 @@ pub fn probe(ctx: XdpContext) -> XdpResult { if !target_seq.is_complete(&knock.sequence) { // notify user space that we're blocking the connection let conn = Connection { - source_ip: u32::from_be(ip.saddr), + source_ip: source_addr, allowed: 0, }; unsafe { connections.insert(&ctx, &MapData::new(conn)) } @@ -83,7 +88,7 @@ pub fn probe(ctx: XdpContext) -> XdpResult { // notify user space that we're allowing the connection let conn = Connection { - source_ip: u32::from_be(ip.saddr), + source_ip: source_addr, allowed: 1, }; unsafe { connections.insert(&ctx, &MapData::new(conn)) } @@ -105,7 +110,7 @@ pub fn probe(ctx: XdpContext) -> XdpResult { // notify user space that ip.saddr knocked on tcp.dest() let attempt = KnockAttempt { - source_ip: u32::from_be(ip.saddr), + source_ip: source_addr, padding: 0, sequence: knock.sequence.clone(), }; From d73fb72a868be2b4220821c7ed2c53e3e0013301 Mon Sep 17 00:00:00 2001 From: zhaofeng0019 Date: Wed, 12 Oct 2022 11:30:57 +0800 Subject: [PATCH 104/107] chore(loader): abort spawned streams when Loaded destroyed Signed-off-by: zhaofeng0019 --- redbpf/src/load/loader.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/redbpf/src/load/loader.rs b/redbpf/src/load/loader.rs index 924c8816..9abecab8 100644 --- a/redbpf/src/load/loader.rs +++ b/redbpf/src/load/loader.rs @@ -29,6 +29,19 @@ pub enum LoaderError { /// High level API to load bpf programs. pub struct Loader {} +// save for tasks of PerfMessageStream and RingBufMessageStream to abort when Loaded destoryed to avoid fd leak +struct JoinHandles { + inner: Vec>, +} + +impl Drop for JoinHandles { + fn drop(&mut self) { + for handle in self.inner.iter() { + handle.abort(); + } + } +} + impl Loader { /// Loads the programs included in `data`. /// @@ -45,6 +58,7 @@ impl Loader { let online_cpus = cpus::get_online().unwrap(); let (sender, receiver) = mpsc::unbounded(); + let mut _join_handles = JoinHandles { inner: Vec::new() }; // bpf_map_type_BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4 for m in module.maps.iter_mut().filter(|m| m.kind == 4) { for cpuid in online_cpus.iter() { @@ -56,7 +70,7 @@ impl Loader { s.start_send((name.clone(), events)).unwrap(); future::ready(()) }); - tokio::spawn(fut); + _join_handles.inner.push(tokio::spawn(fut)); } } @@ -70,12 +84,13 @@ impl Loader { s.start_send((name.clone(), events)).unwrap(); future::ready(()) }); - tokio::spawn(fut); + _join_handles.inner.push(tokio::spawn(fut)); } Ok(Loaded { module, events: receiver, + _join_handles, }) } @@ -108,6 +123,7 @@ pub struct Loaded { /// # }; /// ``` pub events: mpsc::UnboundedReceiver<(String, ::Item)>, + _join_handles: JoinHandles, } impl Loaded { From fe8f53844b74c353e6cd81e708c8a663b8b6a2dd Mon Sep 17 00:00:00 2001 From: Quentin JEROME Date: Tue, 22 Nov 2022 15:02:53 +0100 Subject: [PATCH 105/107] Exposes fs_struct in bindgen.rs + generate accessors for task_struct and fs_struct Signed-off-by: Quentin JEROME --- redbpf-probes/build.rs | 12 ++++++++++-- redbpf-probes/include/kernel_supplement.h | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 redbpf-probes/include/kernel_supplement.h diff --git a/redbpf-probes/build.rs b/redbpf-probes/build.rs index 6504a575..7cf9abfe 100644 --- a/redbpf-probes/build.rs +++ b/redbpf-probes/build.rs @@ -99,7 +99,8 @@ fn generate_bindings_kernel_headers() -> Result<()> { let mut builder = bpf_bindgen::get_builder_kernel_headers() .or_else(|e| bail!("error on Builder::get_builder_kernel_headers: {}", e))? .header("include/redbpf_helpers.h") - .header("include/bpf_helpers.h"); + .header("include/bpf_helpers.h") + .header("include/kernel_supplement.h"); for ty in types.iter().chain(xdp_types.iter()) { builder = builder.allowlist_type(ty); @@ -114,6 +115,8 @@ fn generate_bindings_kernel_headers() -> Result<()> { .generate() .or_else(|e| bail!("error on Builder::generate: {:?}", e))? .to_string(); + + // Generate BPF helper function to access struct fields let accessors = bpf_bindgen::generate_read_accessors( &bindings, &[ @@ -126,6 +129,8 @@ fn generate_bindings_kernel_headers() -> Result<()> { "path", "dentry", "qstr", + "task_struct", + "fs_struct", ], ); bindings.push_str("use crate::helpers::bpf_probe_read;"); @@ -224,6 +229,8 @@ fn generate_bindings_vmlinux() -> Result<()> { .generate() .or_else(|e| bail!("error on Builder::generate: {:?}", e))? .to_string(); + + // Generate BPF helper function to access struct fields let accessors = bpf_bindgen::generate_read_accessors( &bindings, &[ @@ -236,6 +243,8 @@ fn generate_bindings_vmlinux() -> Result<()> { "path", "dentry", "qstr", + "task_struct", + "fs_struct", ], ); bindings.push_str("use crate::helpers::bpf_probe_read;"); @@ -281,7 +290,6 @@ fn main() { .with_max_level(Level::TRACE) .finish(); tracing::subscriber::set_global_default(subscriber).unwrap(); - if let Ok(_) = env::var("DOCS_RS") { let mut paths = available_kernel_header_paths(); paths.sort(); diff --git a/redbpf-probes/include/kernel_supplement.h b/redbpf-probes/include/kernel_supplement.h new file mode 100644 index 00000000..77bc2cc8 --- /dev/null +++ b/redbpf-probes/include/kernel_supplement.h @@ -0,0 +1,12 @@ +#ifndef KERNEL_SUPPLEMENT_H +#define KERNEL_SUPPLEMENT_H + +/* +This file is parsed only when building from Kernel source. +Include here headers containing structures for which you'd +like to generate binding for, not included in the other header files. +*/ + +#include // expose fs_struct + +#endif From 4770a8b2ca692c2583eadcf69271fa2ef77f0763 Mon Sep 17 00:00:00 2001 From: Quentin JEROME Date: Wed, 7 Dec 2022 13:49:32 +0100 Subject: [PATCH 106/107] - Added read accessors for structures: - vfsmount, mount -> for path parsing - cred -> to access task_struct credentials - mm_struct -> to access task_struct memory information - Moved read accessors to a global const slice ref - Moved xdp types to a global const slice ref Signed-off-by: Quentin JEROME --- redbpf-probes/build.rs | 132 +++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 77 deletions(-) diff --git a/redbpf-probes/build.rs b/redbpf-probes/build.rs index 7cf9abfe..fcf0cf3c 100644 --- a/redbpf-probes/build.rs +++ b/redbpf-probes/build.rs @@ -31,6 +31,53 @@ use syn::{ use tracing::{debug, warn, Level}; use tracing_subscriber::FmtSubscriber; +const XDP_TYPES: &[&'static str] = &[ + "^xdp_md$", + "^ethhdr$", + "^iphdr$", + "^ipv6hdr$", + "^tcphdr$", + "^udphdr$", + "^xdp_action$", + "^__sk_.*", + "^sk_.*", + "^inet_sock$", + "^unix_sock$", + "^sockaddr$", + "^sockaddr_in$", + "^in_addr$", + "^tcp.*_sock$", + "^udp.*_sock$", + "^btf_ptr$", + "^sock_type$", // for enum of SOCK_* + "^sock_flags$", // for enum of SOCK_* + "^linux_binprm$", +]; + +const READ_ACCESSORS: &[&'static str] = &[ + // network + "sock", + "sockaddr", + "sockaddr_in", + "in_addr", + // file-system + "file", + "inode", + "path", + "dentry", + "qstr", + "fs_struct", + "vfsmount", + // this structure is not available in kernel headers but it is + // in .BTF section of vmlinux so if you want to generate it make + // sure to build with one of the vmlinux variants + "mount", + // task + "task_struct", + "mm_struct", + "cred", +]; + fn create_module(path: PathBuf, name: &str, bindings: &str) -> io::Result<()> { { let mut file = File::create(&path)?; @@ -72,28 +119,7 @@ fn generate_bindings_kernel_headers() -> Result<()> { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let types = ["pt_regs", "s32", "bpf_.*"]; let vars = ["BPF_.*"]; - let xdp_types = [ - "xdp_md", - "ethhdr", - "iphdr", - "ipv6hdr", - "tcphdr", - "udphdr", - "xdp_action", - "__sk_.*", - "sk_.*", - "inet_sock", - "unix_sock", - "sockaddr", - "sockaddr_in", - "in_addr", - "tcp.*_sock", - "udp.*_sock", - "btf_ptr", - "linux_binprm", - "^sock_type$", // for enum of SOCK_* - "^sock_flags$", // for enum of SOCK_* - ]; + let xdp_vars = ["ETH_.*", "IPPROTO_.*", "SOCK_.*", "SK_FL_.*", "AF_.*"]; let mut builder = bpf_bindgen::get_builder_kernel_headers() @@ -102,7 +128,7 @@ fn generate_bindings_kernel_headers() -> Result<()> { .header("include/bpf_helpers.h") .header("include/kernel_supplement.h"); - for ty in types.iter().chain(xdp_types.iter()) { + for ty in types.iter().chain(XDP_TYPES.iter()) { builder = builder.allowlist_type(ty); } @@ -117,22 +143,8 @@ fn generate_bindings_kernel_headers() -> Result<()> { .to_string(); // Generate BPF helper function to access struct fields - let accessors = bpf_bindgen::generate_read_accessors( - &bindings, - &[ - "sock", - "sockaddr", - "sockaddr_in", - "in_addr", - "file", - "inode", - "path", - "dentry", - "qstr", - "task_struct", - "fs_struct", - ], - ); + let accessors = bpf_bindgen::generate_read_accessors(&bindings, READ_ACCESSORS); + bindings.push_str("use crate::helpers::bpf_probe_read;"); bindings.push_str(&accessors); create_module(out_dir.join("gen_bindings.rs"), "gen_bindings", &bindings)?; @@ -176,27 +188,7 @@ fn generate_bindings_vmlinux() -> Result<()> { // match the exact name. let types = ["^pt_regs$", "^s32$", "^bpf_.*"]; let vars = ["^BPF_.*"]; - let xdp_types = [ - "^xdp_md$", - "^ethhdr$", - "^iphdr$", - "^ipv6hdr$", - "^tcphdr$", - "^udphdr$", - "^xdp_action$", - "^__sk_.*", - "^sk_.*", - "^inet_sock$", - "^unix_sock$", - "^sockaddr$", - "^sockaddr_in$", - "^in_addr$", - "^tcp.*_sock$", - "^udp.*_sock$", - "^btf_ptr$", - "^sock_type$", // for enum of SOCK_* - "^sock_flags$", // for enum of SOCK_* - ]; + let xdp_vars = ["^IPPROTO_.*"]; let mut builder = bpf_bindgen::get_builder_vmlinux(out_dir.join("vmlinux.h")) .or_else(|e| bail!("error on bpf_bindgen::get_builder_vmlinux: {}", e))? @@ -217,7 +209,7 @@ fn generate_bindings_vmlinux() -> Result<()> { // kernel. And the generated bindings can be used to compile BPF // programs. But if all types are generated, compiling BPF programs takes a // long time. So keep whitelist types. - for ty in types.iter().chain(xdp_types.iter()) { + for ty in types.iter().chain(XDP_TYPES.iter()) { builder = builder.allowlist_type(ty); } @@ -231,22 +223,8 @@ fn generate_bindings_vmlinux() -> Result<()> { .to_string(); // Generate BPF helper function to access struct fields - let accessors = bpf_bindgen::generate_read_accessors( - &bindings, - &[ - "sock", - "sockaddr", - "sockaddr_in", - "in_addr", - "file", - "inode", - "path", - "dentry", - "qstr", - "task_struct", - "fs_struct", - ], - ); + let accessors = bpf_bindgen::generate_read_accessors(&bindings, READ_ACCESSORS); + bindings.push_str("use crate::helpers::bpf_probe_read;"); bindings.push_str(&accessors); // macro constants and structures of userspace can not be generated by BTF From 94c7f688aff592924b047e6dc8ede634b33c10f9 Mon Sep 17 00:00:00 2001 From: Quentin JEROME Date: Thu, 8 Dec 2022 14:09:45 +0100 Subject: [PATCH 107/107] - bindgen for sockaddr_in6 structure and read accessors Signed-off-by: Quentin JEROME --- redbpf-probes/build.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/redbpf-probes/build.rs b/redbpf-probes/build.rs index fcf0cf3c..5a6cf1fb 100644 --- a/redbpf-probes/build.rs +++ b/redbpf-probes/build.rs @@ -45,6 +45,7 @@ const XDP_TYPES: &[&'static str] = &[ "^unix_sock$", "^sockaddr$", "^sockaddr_in$", + "^sockaddr_in6$", "^in_addr$", "^tcp.*_sock$", "^udp.*_sock$", @@ -59,6 +60,7 @@ const READ_ACCESSORS: &[&'static str] = &[ "sock", "sockaddr", "sockaddr_in", + "sockaddr_in6", "in_addr", // file-system "file",