1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
use core::ffi::c_void;
use std::alloc::alloc_zeroed;
use std::alloc::dealloc;
use std::alloc::Layout;
use std::ffi::CString;
use std::mem::size_of;
use std::os::raw::c_char;
use std::os::raw::c_ulong;
use std::ptr;
use std::ptr::NonNull;
use libbpf_sys::bpf_link;
use libbpf_sys::bpf_map;
use libbpf_sys::bpf_map_skeleton;
use libbpf_sys::bpf_object;
use libbpf_sys::bpf_object_skeleton;
use libbpf_sys::bpf_prog_skeleton;
use libbpf_sys::bpf_program;
use crate::error::IntoError as _;
use crate::util;
use crate::Error;
use crate::Object;
use crate::ObjectBuilder;
use crate::OpenObject;
use crate::Result;
#[derive(Debug)]
struct MapSkelConfig {
name: String,
p: Box<*mut bpf_map>,
mmaped: Option<Box<*mut c_void>>,
}
#[derive(Debug)]
struct ProgSkelConfig {
name: String,
p: Box<*mut bpf_program>,
link: Box<*mut bpf_link>,
}
#[allow(missing_docs)]
#[derive(Debug)]
pub struct ObjectSkeletonConfigBuilder<'a> {
data: &'a [u8],
p: Box<*mut bpf_object>,
name: Option<String>,
maps: Vec<MapSkelConfig>,
progs: Vec<ProgSkelConfig>,
}
fn str_to_cstring_and_pool(s: &str, pool: &mut Vec<CString>) -> Result<*const c_char> {
let cname = util::str_to_cstring(s)?;
let p = cname.as_ptr();
pool.push(cname);
Ok(p)
}
impl<'a> ObjectSkeletonConfigBuilder<'a> {
/// Construct a new instance
///
/// `object_data` is the contents of the `.o` from clang
///
/// `p` is a reference to the pointer where `libbpf_sys::bpf_object` should be
/// stored/retrieved
pub fn new(object_data: &'a [u8]) -> Self {
Self {
data: object_data,
p: Box::new(ptr::null_mut()),
name: None,
maps: Vec::new(),
progs: Vec::new(),
}
}
#[allow(missing_docs)]
pub fn name<T: AsRef<str>>(&mut self, name: T) -> &mut Self {
self.name = Some(name.as_ref().to_string());
self
}
/// Adds a map to the config
///
/// Set `mmaped` to `true` if the map is mmap'able to userspace
pub fn map<T: AsRef<str>>(&mut self, name: T, mmaped: bool) -> &mut Self {
let m = if mmaped {
Some(Box::new(ptr::null_mut()))
} else {
None
};
self.maps.push(MapSkelConfig {
name: name.as_ref().to_string(),
p: Box::new(ptr::null_mut()),
mmaped: m,
});
self
}
/// Adds a prog to the config
pub fn prog<T: AsRef<str>>(&mut self, name: T) -> &mut Self {
self.progs.push(ProgSkelConfig {
name: name.as_ref().to_string(),
p: Box::new(ptr::null_mut()),
link: Box::new(ptr::null_mut()),
});
self
}
fn build_maps(
maps: &mut [MapSkelConfig],
s: &mut bpf_object_skeleton,
string_pool: &mut Vec<CString>,
) -> Option<Layout> {
if maps.is_empty() {
return None;
}
s.map_cnt = maps.len() as i32;
s.map_skel_sz = size_of::<bpf_map_skeleton>() as i32;
let layout = Layout::array::<bpf_map_skeleton>(maps.len())
.expect("Failed to allocate memory for maps skeleton");
unsafe {
s.maps = alloc_zeroed(layout) as *mut bpf_map_skeleton;
for (i, map) in maps.iter_mut().enumerate() {
let current_map = s.maps.add(i);
// Opt to panic on error here. We've already allocated memory and we'd rather not
// leak. Extremely unlikely to have invalid unicode anyways.
(*current_map).name = str_to_cstring_and_pool(&map.name, string_pool)
.expect("Invalid unicode in map name");
(*current_map).map = &mut *map.p;
(*current_map).mmaped = if let Some(ref mut mmaped) = map.mmaped {
&mut **mmaped
} else {
ptr::null_mut()
};
}
}
Some(layout)
}
fn build_progs(
progs: &mut [ProgSkelConfig],
s: &mut bpf_object_skeleton,
string_pool: &mut Vec<CString>,
) -> Option<Layout> {
if progs.is_empty() {
return None;
}
s.prog_cnt = progs.len() as i32;
s.prog_skel_sz = size_of::<bpf_prog_skeleton>() as i32;
let layout = Layout::array::<bpf_prog_skeleton>(progs.len())
.expect("Failed to allocate memory for progs skeleton");
unsafe {
s.progs = alloc_zeroed(layout) as *mut bpf_prog_skeleton;
for (i, prog) in progs.iter_mut().enumerate() {
let current_prog = s.progs.add(i);
// See above for `expect()` rationale
(*current_prog).name = str_to_cstring_and_pool(&prog.name, string_pool)
.expect("Invalid unicode in prog name");
(*current_prog).prog = &mut *prog.p;
(*current_prog).link = &mut *prog.link;
}
}
Some(layout)
}
#[allow(missing_docs)]
pub fn build(mut self) -> Result<ObjectSkeletonConfig<'a>> {
// Holds `CString`s alive so pointers to them stay valid
let mut string_pool = Vec::new();
let mut s = libbpf_sys::bpf_object_skeleton {
sz: size_of::<bpf_object_skeleton>() as c_ulong,
..Default::default()
};
if let Some(ref n) = self.name {
s.name = str_to_cstring_and_pool(n, &mut string_pool)?;
}
// libbpf_sys will use it as const despite the signature
s.data = self.data.as_ptr() as *mut c_void;
s.data_sz = self.data.len() as c_ulong;
// Give s ownership over the box
s.obj = Box::into_raw(self.p);
let maps_layout = Self::build_maps(&mut self.maps, &mut s, &mut string_pool);
let progs_layout = Self::build_progs(&mut self.progs, &mut s, &mut string_pool);
Ok(ObjectSkeletonConfig {
inner: s,
maps: self.maps,
progs: self.progs,
maps_layout,
progs_layout,
_data: self.data,
_string_pool: string_pool,
})
}
}
/// Helper struct that wraps a `libbpf_sys::bpf_object_skeleton`.
///
/// This struct will:
/// * ensure lifetimes are valid for dependencies (pointers, data buffer)
/// * free any allocated memory on drop
///
/// This struct can be moved around at will. Upon drop, all allocated resources will be freed
#[derive(Debug)]
pub struct ObjectSkeletonConfig<'a> {
inner: bpf_object_skeleton,
maps: Vec<MapSkelConfig>,
progs: Vec<ProgSkelConfig>,
/// Layout necessary to `dealloc` memory
maps_layout: Option<Layout>,
/// Same as above
progs_layout: Option<Layout>,
/// Hold this reference so that compiler guarantees buffer lives as long as us
_data: &'a [u8],
/// Hold strings alive so pointers to them stay valid
_string_pool: Vec<CString>,
}
impl ObjectSkeletonConfig<'_> {
#[allow(missing_docs)]
pub fn get(&mut self) -> &mut bpf_object_skeleton {
&mut self.inner
}
/// Warning: the returned pointer is only valid while the
/// `ObjectSkeletonConfig` is alive.
///
/// # Panic
/// This method panics if the inner [`bpf_object_skeleton`] has not been
/// initialized.
///
/// To initialize it, first call [`Self::get`] and initialize the skeleton.
pub fn object_ptr(&mut self) -> NonNull<bpf_object> {
NonNull::new(unsafe { *self.inner.obj }).expect(
r#"
The generated code failed to initialize bpf_object_skeleton.obj pointer through the use
of `bpf_object__open_skeleton(skel_config.get(), &open_opts)`
"#,
)
}
/// Returns the `mmaped` pointer for a map at the specified `index`.
///
/// The index is determined by the order in which the map was passed to
/// `ObjectSkeletonConfigBuilder::map`. Index starts at 0.
///
/// Warning: the returned pointer is only valid while the `ObjectSkeletonConfig` is alive.
pub fn map_mmap_ptr(&self, index: usize) -> Result<*const c_void> {
if index >= self.maps.len() {
return Err(Error::with_invalid_data(format!(
"Invalid map index: {index}"
)));
}
let p = self.maps[index]
.mmaped
.as_ref()
.ok_or_invalid_data(|| "Map does not have mmaped ptr")?;
Ok(**p)
}
/// Returns the `mmaped` pointer for a map at the specified `index`.
///
/// The index is determined by the order in which the map was passed to
/// `ObjectSkeletonConfigBuilder::map`. Index starts at 0.
///
/// Warning: the returned pointer is only valid while the `ObjectSkeletonConfig` is alive.
pub fn map_mmap_ptr_mut(&mut self, index: usize) -> Result<*mut c_void> {
self.map_mmap_ptr(index).map(|p| p.cast_mut())
}
/// Returns the link pointer for a prog at the specified `index`.
///
/// The index is determined by the order in which the prog was passed to
/// `ObjectSkeletonConfigBuilder::prog`. Index starts at 0.
///
/// Warning: the returned pointer is only valid while the `ObjectSkeletonConfig` is alive.
pub fn prog_link_ptr(&mut self, index: usize) -> Result<*mut bpf_link> {
if index >= self.progs.len() {
return Err(Error::with_invalid_data(format!(
"Invalid prog index: {index}"
)));
}
Ok(*self.progs[index].link)
}
}
impl Drop for ObjectSkeletonConfig<'_> {
// Note we do *not* run `libbpf_sys::bpf_object__destroy_skeleton` here.
//
// Couple reasons:
//
// 1) We did not allocate `libbpf_sys::bpf_object_skeleton` on the heap and
// `libbpf_sys::bpf_object__destroy_skeleton` will try to free from heap
//
// 2) `libbpf_object_skeleton` assumes it "owns" the object and everything inside it.
// libbpf-cargo's generated skeleton instead gives ownership of the object to
// libbpf-rs::*Object. The destructors in libbpf-rs::*Object will know when and how to do
// cleanup.
fn drop(&mut self) {
assert_eq!(self.maps_layout.is_none(), self.inner.maps.is_null());
assert_eq!(self.progs_layout.is_none(), self.inner.progs.is_null());
if let Some(layout) = self.maps_layout {
unsafe {
dealloc(self.inner.maps as _, layout);
}
}
if let Some(layout) = self.progs_layout {
unsafe {
dealloc(self.inner.progs as _, layout);
}
}
let _ = unsafe { Box::from_raw(self.inner.obj) };
}
}
/// A trait for skeleton builder.
pub trait SkelBuilder<'a> {
/// Define that when BPF object is opened, the returned type should implement the [`OpenSkel`] trait
type Output: OpenSkel;
/// Open eBPF object and return [`OpenSkel`]
fn open(self) -> Result<Self::Output>;
/// Open eBPF object with [`libbpf_sys::bpf_object_open_opts`] and return [`OpenSkel`]
fn open_opts(self, open_opts: libbpf_sys::bpf_object_open_opts) -> Result<Self::Output>;
/// Get a reference to [`ObjectBuilder`]
fn object_builder(&self) -> &ObjectBuilder;
/// Get a mutable reference to [`ObjectBuilder`]
fn object_builder_mut(&mut self) -> &mut ObjectBuilder;
}
/// A trait for opened skeleton.
///
/// In addition to the methods defined in this trait, skeletons that implement this trait will also
/// have bespoke implementations of a few additional methods to facilitate access to global
/// variables of the BPF program. These methods will be named `bss()`, `data()`, and `rodata()`.
/// Each corresponds to the variables stored in the BPF ELF program section of the same name.
/// However if your BPF program lacks one of these sections the corresponding rust method will not
/// be generated.
///
/// The type of the value returned by each of these methods will be specific to your BPF program.
/// A common convention is to define a single global variable in the BPF program with a struct type
/// containing a field for each configuration parameter <sup>\[[source]\]</sup>. libbpf-rs
/// auto-generates this pattern for you without you having to define such a struct type in your BPF
/// program. It does this by examining each of the global variables in your BPF program's `.bss`,
/// `.data`, and `.rodata` sections and then creating Rust struct types. Since these struct types
/// are specific to the layout of your BPF program, they are not documented in this crate. However
/// you can see documentation for them by running `cargo doc` in your own project and looking at
/// the `imp` module. You can also view their implementation by looking at the generated skeleton
/// rust source file. The use of these methods can also be seen in the examples 'capable',
/// 'runqslower', and 'tproxy'.
///
/// If you ever doubt whether libbpf-rs has placed a particular variable in the correct struct
/// type, you can see which section each global variable is stored in by examining the output of
/// the following command (after a successful build):
///
/// ```sh
/// bpf-objdump --syms ./target/bpf/*.bpf.o
/// ```
///
/// [source]: https://nakryiko.com/posts/bcc-to-libbpf-howto-guide/#application-configuration
pub trait OpenSkel {
/// Define that when BPF object is loaded, the returned type should implement the [`Skel`] trait
type Output: Skel;
/// Load BPF object and return [`Skel`].
fn load(self) -> Result<Self::Output>;
/// Get a reference to [`OpenObject`].
fn open_object(&self) -> &OpenObject;
/// Get a mutable reference to [`OpenObject`].
fn open_object_mut(&mut self) -> &mut OpenObject;
}
/// A trait for loaded skeleton.
pub trait Skel {
/// Attach BPF object.
fn attach(&mut self) -> Result<()> {
unimplemented!()
}
/// Get a reference to [`Object`].
fn object(&self) -> &Object;
/// Get a mutable reference to [`Object`].
fn object_mut(&mut self) -> &mut Object;
}