Skip to content

Commit

Permalink
Add bindings for querying BPF program information
Browse files Browse the repository at this point in the history
Add low-level system call bindings for querying BPF program information
from the kernel. We need this logic to iterate over available programs
and for retrieving meta-data such as the program's tag. The alternative
of relying on libbpf-rs and higher level primitives was discarded given
the minimal nature of support we require here. There is simply no
justification for all the dependency bloat that libbpf-sys et al pull
in.

Signed-off-by: Daniel Müller <[email protected]>
  • Loading branch information
d-e-s-o committed Oct 16, 2024
1 parent 4bf2a9a commit e595b6b
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/kernel/bpf/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod prog;
mod sys;

pub(super) use prog::BpfProg;
219 changes: 219 additions & 0 deletions src/kernel/bpf/sys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
use std::ffi::c_long;
use std::ffi::c_uint;
use std::io;
use std::os::fd::FromRawFd as _;
use std::os::fd::OwnedFd;
use std::os::fd::RawFd;

use libc::syscall;
use libc::SYS_bpf;

type bpf_cmd = c_uint;

const BPF_PROG_GET_NEXT_ID: bpf_cmd = 11;
const BPF_PROG_GET_FD_BY_ID: bpf_cmd = 13;
const BPF_OBJ_GET_INFO_BY_FD: bpf_cmd = 15;


#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct bpf_prog_info {
pub type_: u32,
pub id: u32,
pub tag: [u8; 8usize],
pub jited_prog_len: u32,
pub xlated_prog_len: u32,
pub jited_prog_insns: u64,
pub xlated_prog_insns: u64,
pub load_time: u64,
pub created_by_uid: u32,
pub nr_map_ids: u32,
pub map_ids: u64,
pub name: [u8; 16usize],
pub ifindex: u32,
pub _bitfield_align_1: [u8; 0],
pub _bitfield_1: [u8; 4usize],
pub netns_dev: u64,
pub netns_ino: u64,
pub nr_jited_ksyms: u32,
pub nr_jited_func_lens: u32,
pub jited_ksyms: u64,
pub jited_func_lens: u64,
pub btf_id: u32,
pub func_info_rec_size: u32,
pub func_info: u64,
pub nr_func_info: u32,
pub nr_line_info: u32,
pub line_info: u64,
pub jited_line_info: u64,
pub nr_jited_line_info: u32,
pub line_info_rec_size: u32,
pub jited_line_info_rec_size: u32,
pub nr_prog_tags: u32,
pub prog_tags: u64,
pub run_time_ns: u64,
pub run_cnt: u64,
pub recursion_misses: u64,
pub verified_insns: u32,
pub attach_btf_obj_id: u32,
pub attach_btf_id: u32,
pub __bindgen_padding_0: [u8; 4usize],
}


/// Defined in `include/uapi/linux/bpf.h`.
#[repr(C)]
#[derive(Copy, Clone)]
union bpf_attr {
pub __bindgen_anon_6: bpf_attr__bindgen_ty_8,
pub info: bpf_attr__bindgen_ty_9,
}

#[repr(C)]
#[derive(Copy, Clone)]
struct bpf_attr__bindgen_ty_8 {
pub __bindgen_anon_1: bpf_attr__bindgen_ty_8__bindgen_ty_1,
pub next_id: u32,
pub open_flags: u32,
}

#[repr(C)]
#[derive(Copy, Clone)]
union bpf_attr__bindgen_ty_8__bindgen_ty_1 {
pub start_id: u32,
pub prog_id: u32,
pub map_id: u32,
pub btf_id: u32,
pub link_id: u32,
}

#[repr(C)]
#[derive(Copy, Clone, Debug)]
struct bpf_attr__bindgen_ty_9 {
pub bpf_fd: u32,
pub info_len: u32,
pub info: u64,
}


fn sys_bpf(cmd: bpf_cmd, attr: *mut bpf_attr, attr_size: usize) -> io::Result<c_long> {
let rc = unsafe { syscall(SYS_bpf, cmd, attr, attr_size) };
if rc < 0 {
// NB: `syscall` is libc provided and takes care of managing
// `errno` for us, which is what `io::Error::last_os_error`
// relies on.
return Err(io::Error::last_os_error())
}
Ok(rc)
}

pub fn bpf_prog_get_next_id(start_id: u32) -> io::Result<u32> {
let mut attr = bpf_attr {
__bindgen_anon_6: bpf_attr__bindgen_ty_8 {
__bindgen_anon_1: bpf_attr__bindgen_ty_8__bindgen_ty_1 { start_id },
next_id: 0,
open_flags: 0,
},
};

let attr_size = unsafe { size_of_val(&attr.__bindgen_anon_6) };
let _rc = sys_bpf(BPF_PROG_GET_NEXT_ID, &mut attr, attr_size)?;
Ok(unsafe { attr.__bindgen_anon_6.next_id })
}

pub fn bpf_prog_get_fd_from_id(prog_id: u32) -> io::Result<OwnedFd> {
let mut attr = bpf_attr {
__bindgen_anon_6: bpf_attr__bindgen_ty_8 {
__bindgen_anon_1: bpf_attr__bindgen_ty_8__bindgen_ty_1 { prog_id },
next_id: 0,
open_flags: 0,
},
};

let attr_size = unsafe { size_of_val(&attr.__bindgen_anon_6) };
let fd = sys_bpf(BPF_PROG_GET_FD_BY_ID, &mut attr, attr_size)?;
// SAFETY: The system call was checked for success and on success a
// valid owned file descriptor is returned.
let fd = unsafe { OwnedFd::from_raw_fd(fd.try_into().unwrap()) };
Ok(fd)
}

pub fn bpf_prog_get_info_from_fd(bpf_fd: RawFd, info: &mut bpf_prog_info) -> io::Result<()> {
let mut attr = bpf_attr {
info: bpf_attr__bindgen_ty_9 {
bpf_fd: bpf_fd as _,
info_len: size_of::<bpf_prog_info>() as _,
// NB: Evidently `info` is not just used as output argument
// but also as input.
info: info as *mut _ as usize as _,
},
};

let attr_size = unsafe { size_of_val(&attr.info) };
let _rc = sys_bpf(BPF_OBJ_GET_INFO_BY_FD, &mut attr, attr_size)?;
// TODO: May need to double check `attr.info.info_len`?
Ok(())
}


#[cfg(test)]
mod tests {
use super::*;

use std::ffi::CStr;
use std::os::fd::AsRawFd as _;

use crate::test_helper::prog_mut;
use crate::test_helper::test_object;

use tempfile::tempfile;


/// Make sure that we fail `bpf_prog_get_info_by_fd` as expected
/// when an unsupported file descriptor type is presented.
#[test]
fn invalid_prog_info_retrieval() {
let file = tempfile().unwrap();
let mut info = bpf_prog_info::default();
let err = bpf_prog_get_info_from_fd(file.as_raw_fd(), &mut info).unwrap_err();
// We invoked the function with a regular file, not a BPF
// program file descriptor.
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}

/// Check that we can retrieve a BPF program's ID.
#[test]
fn prog_discovery() {
let mut obj = test_object("getpid.bpf.o");
let prog = prog_mut(&mut obj, "handle__getpid");
let _link = prog
.attach_tracepoint("syscalls", "sys_enter_getpid")
.expect("failed to attach prog");

let prog_id = 0;
// Given that we just loaded a program, we should be able to find
// at least one.
let prog_id = bpf_prog_get_next_id(prog_id).unwrap();
assert_ne!(prog_id, 0);
let fd = bpf_prog_get_fd_from_id(prog_id).unwrap();
assert_ne!(fd.as_raw_fd(), 0);
}

/// Check that we can iterate over all active BPF programs.
#[test]
fn prog_iteration() {
let mut next_prog_id = 0;
while let Ok(prog_id) = bpf_prog_get_next_id(next_prog_id) {
let fd = bpf_prog_get_fd_from_id(prog_id).unwrap();
let mut info = bpf_prog_info::default();
let () = bpf_prog_get_info_from_fd(fd.as_raw_fd(), &mut info).unwrap();
println!(
"found BPF program: {}",
CStr::from_bytes_until_nul(info.name.as_slice())
.unwrap()
.to_string_lossy()
);
next_prog_id = prog_id;
}
}
}
6 changes: 3 additions & 3 deletions src/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ fn open_test_object(object: &str) -> OpenObject {
}

#[track_caller]
fn test_object(filename: &str) -> Object {
pub(crate) fn test_object(filename: &str) -> Object {
open_test_object(filename)
.load()
.expect("failed to load object")
Expand All @@ -100,7 +100,7 @@ fn map_mut<'obj>(object: &'obj mut Object, name: &str) -> MapMut<'obj> {

/// Find the BPF program with the given name, panic if it does not exist.
#[track_caller]
fn prog_mut<'obj>(object: &'obj mut Object, name: &str) -> ProgramMut<'obj> {
pub(crate) fn prog_mut<'obj>(object: &'obj mut Object, name: &str) -> ProgramMut<'obj> {
object
.progs_mut()
.find(|map| map.name() == name)
Expand Down Expand Up @@ -135,7 +135,7 @@ where

/// Retrieve the address of the `symbolization_target` function in the
/// `getpid.bpf.o` BPF program.
pub fn bpf_symbolization_target_addr() -> Addr {
pub(crate) fn bpf_symbolization_target_addr() -> Addr {
let mut obj = test_object("getpid.bpf.o");
let prog = prog_mut(&mut obj, "handle__getpid");
let _link = prog
Expand Down

0 comments on commit e595b6b

Please sign in to comment.