mirror of https://github.com/aya-rs/aya
Add BPF_PROG_TYPE_EXT
This requires loading the BTF to kernel when loading all programs as well as implementing Extension program type Signed-off-by: Dave Tucker <dave@dtucker.co.uk>pull/127/head
parent
379bb313b1
commit
5c6131afba
@ -0,0 +1,189 @@
|
||||
use std::{collections::HashMap, convert::TryInto};
|
||||
|
||||
use bytes::BufMut;
|
||||
use object::Endianness;
|
||||
|
||||
use crate::{
|
||||
generated::{bpf_func_info, bpf_line_info},
|
||||
obj::relocation::INS_SIZE,
|
||||
util::bytes_of,
|
||||
};
|
||||
|
||||
/* The func_info subsection layout:
|
||||
* record size for struct bpf_func_info in the func_info subsection
|
||||
* struct btf_sec_func_info for section #1
|
||||
* a list of bpf_func_info records for section #1
|
||||
* where struct bpf_func_info mimics one in include/uapi/linux/bpf.h
|
||||
* but may not be identical
|
||||
* struct btf_sec_func_info for section #2
|
||||
* a list of bpf_func_info records for section #2
|
||||
* ......
|
||||
*/
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct FuncSecInfo {
|
||||
pub _sec_name_offset: u32,
|
||||
pub num_info: u32,
|
||||
pub func_info: Vec<bpf_func_info>,
|
||||
}
|
||||
|
||||
impl FuncSecInfo {
|
||||
pub(crate) fn parse(
|
||||
sec_name_offset: u32,
|
||||
num_info: u32,
|
||||
rec_size: usize,
|
||||
func_info_data: &[u8],
|
||||
endianness: Endianness,
|
||||
) -> FuncSecInfo {
|
||||
let func_info = func_info_data
|
||||
.chunks(rec_size)
|
||||
.map(|data| {
|
||||
let read_u32 = if endianness == Endianness::Little {
|
||||
u32::from_le_bytes
|
||||
} else {
|
||||
u32::from_be_bytes
|
||||
};
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
// ELF instruction offsets are in bytes
|
||||
// Kernel instruction offsets are in instructions units
|
||||
// We can convert by dividing the length in bytes by INS_SIZE
|
||||
let insn_off =
|
||||
read_u32(data[offset..offset + 4].try_into().unwrap()) / INS_SIZE as u32;
|
||||
offset += 4;
|
||||
let type_id = read_u32(data[offset..offset + 4].try_into().unwrap());
|
||||
|
||||
bpf_func_info { insn_off, type_id }
|
||||
})
|
||||
.collect();
|
||||
|
||||
FuncSecInfo {
|
||||
_sec_name_offset: sec_name_offset,
|
||||
num_info,
|
||||
func_info,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn func_info_bytes(&self) -> Vec<u8> {
|
||||
let mut buf = vec![];
|
||||
for l in &self.func_info {
|
||||
// Safety: bpf_func_info is POD
|
||||
buf.put(unsafe { bytes_of::<bpf_func_info>(l) })
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.func_info.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct FuncInfo {
|
||||
pub data: HashMap<String, FuncSecInfo>,
|
||||
}
|
||||
|
||||
impl FuncInfo {
|
||||
pub(crate) fn new() -> FuncInfo {
|
||||
FuncInfo {
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, name: &str) -> FuncSecInfo {
|
||||
match self.data.get(name) {
|
||||
Some(d) => d.clone(),
|
||||
None => FuncSecInfo::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct LineSecInfo {
|
||||
// each line info section has a header
|
||||
pub _sec_name_offset: u32,
|
||||
pub num_info: u32,
|
||||
// followed by one or more bpf_line_info structs
|
||||
pub line_info: Vec<bpf_line_info>,
|
||||
}
|
||||
|
||||
impl LineSecInfo {
|
||||
pub(crate) fn parse(
|
||||
sec_name_offset: u32,
|
||||
num_info: u32,
|
||||
rec_size: usize,
|
||||
func_info_data: &[u8],
|
||||
endianness: Endianness,
|
||||
) -> LineSecInfo {
|
||||
let line_info = func_info_data
|
||||
.chunks(rec_size)
|
||||
.map(|data| {
|
||||
let read_u32 = if endianness == Endianness::Little {
|
||||
u32::from_le_bytes
|
||||
} else {
|
||||
u32::from_be_bytes
|
||||
};
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
// ELF instruction offsets are in bytes
|
||||
// Kernel instruction offsets are in instructions units
|
||||
// We can convert by dividing the length in bytes by INS_SIZE
|
||||
let insn_off =
|
||||
read_u32(data[offset..offset + 4].try_into().unwrap()) / INS_SIZE as u32;
|
||||
offset += 4;
|
||||
let file_name_off = read_u32(data[offset..offset + 4].try_into().unwrap());
|
||||
offset += 4;
|
||||
let line_off = read_u32(data[offset..offset + 4].try_into().unwrap());
|
||||
offset += 4;
|
||||
let line_col = read_u32(data[offset..offset + 4].try_into().unwrap());
|
||||
|
||||
bpf_line_info {
|
||||
insn_off,
|
||||
file_name_off,
|
||||
line_off,
|
||||
line_col,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
LineSecInfo {
|
||||
_sec_name_offset: sec_name_offset,
|
||||
num_info,
|
||||
line_info,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn line_info_bytes(&self) -> Vec<u8> {
|
||||
let mut buf = vec![];
|
||||
for l in &self.line_info {
|
||||
// Safety: bpf_func_info is POD
|
||||
buf.put(unsafe { bytes_of::<bpf_line_info>(l) })
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.line_info.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct LineInfo {
|
||||
pub data: HashMap<String, LineSecInfo>,
|
||||
}
|
||||
|
||||
impl LineInfo {
|
||||
pub(crate) fn new() -> LineInfo {
|
||||
LineInfo {
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, name: &str) -> LineSecInfo {
|
||||
match self.data.get(name) {
|
||||
Some(d) => d.clone(),
|
||||
None => LineSecInfo::default(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
#[allow(clippy::module_inception)]
|
||||
mod btf;
|
||||
mod info;
|
||||
mod relocation;
|
||||
mod types;
|
||||
|
||||
pub use btf::*;
|
||||
pub(crate) use info::*;
|
||||
pub use relocation::RelocationError;
|
||||
pub(crate) use types::*;
|
||||
|
@ -0,0 +1,140 @@
|
||||
use std::os::unix::prelude::{AsRawFd, RawFd};
|
||||
use thiserror::Error;
|
||||
|
||||
use object::Endianness;
|
||||
|
||||
use crate::{
|
||||
generated::{bpf_attach_type::BPF_CGROUP_INET_INGRESS, bpf_prog_type::BPF_PROG_TYPE_EXT},
|
||||
obj::btf::BtfKind,
|
||||
programs::{load_program, FdLink, LinkRef, ProgramData, ProgramError},
|
||||
sys::{self, bpf_link_create},
|
||||
Btf,
|
||||
};
|
||||
|
||||
/// The type returned when loading or attaching an [`Extension`] fails
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ExtensionError {
|
||||
#[error("target BPF program does not have BTF loaded to the kernel")]
|
||||
NoBTF,
|
||||
}
|
||||
|
||||
/// A program used to extend existing BPF programs
|
||||
///
|
||||
/// [`Extension`] programs can be loaded to replace a global
|
||||
/// function in a program that has already been loaded.
|
||||
///
|
||||
/// # Minimum kernel version
|
||||
///
|
||||
/// The minimum kernel version required to use this feature is 5.9
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use aya::{BpfLoader, programs::{Xdp, XdpFlags, Extension, ProgramFd}};
|
||||
/// use std::convert::TryInto;
|
||||
///
|
||||
/// let mut bpf = BpfLoader::new().extension("extension").load_file("app.o")?;
|
||||
/// let prog: &mut Xdp = bpf.program_mut("main").unwrap().try_into()?;
|
||||
/// prog.load()?;
|
||||
/// prog.attach("eth0", XdpFlags::default())?;
|
||||
///
|
||||
/// let prog_fd = prog.fd().unwrap();
|
||||
/// let ext: &mut Extension = bpf.program_mut("extension").unwrap().try_into()?;
|
||||
/// ext.load(prog_fd, "function_to_replace")?;
|
||||
/// ext.attach()?;
|
||||
/// Ok::<(), aya::BpfError>(())
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[doc(alias = "BPF_PROG_TYPE_EXT")]
|
||||
pub struct Extension {
|
||||
pub(crate) data: ProgramData,
|
||||
}
|
||||
|
||||
impl Extension {
|
||||
/// Loads the extension inside the kernel.
|
||||
///
|
||||
/// Prepares the code included in the extension to replace the code of the function
|
||||
/// `func_name` within the eBPF program represented by the `program` file descriptor.
|
||||
/// This requires that both the [`Extension`] and `program` have had their BTF
|
||||
/// loaded into the kernel as the verifier must check that the function signatures
|
||||
/// match.
|
||||
///
|
||||
/// The extension code will be loaded but inactive until it's attached.
|
||||
/// There are no restrictions on what functions may be replaced, so you could replace
|
||||
/// the main entry point of your program with an extension.
|
||||
///
|
||||
/// See also [`Program::load`](crate::programs::Program::load).
|
||||
pub fn load<T: AsRawFd>(&mut self, program: T, func_name: &str) -> Result<(), ProgramError> {
|
||||
let target_prog_fd = program.as_raw_fd();
|
||||
|
||||
let info = sys::bpf_obj_get_info_by_fd(target_prog_fd).map_err(|io_error| {
|
||||
ProgramError::SyscallError {
|
||||
call: "bpf_obj_get_info_by_fd".to_owned(),
|
||||
io_error,
|
||||
}
|
||||
})?;
|
||||
|
||||
if info.btf_id == 0 {
|
||||
return Err(ProgramError::ExtensionError(ExtensionError::NoBTF));
|
||||
}
|
||||
|
||||
let btf_fd = sys::bpf_btf_get_fd_by_id(info.btf_id).map_err(|io_error| {
|
||||
ProgramError::SyscallError {
|
||||
call: "bpf_btf_get_fd_by_id".to_owned(),
|
||||
io_error,
|
||||
}
|
||||
})?;
|
||||
|
||||
let mut buf = vec![0u8; 4096];
|
||||
let btf_info = match sys::btf_obj_get_info_by_fd(btf_fd, &mut buf) {
|
||||
Ok(info) => {
|
||||
if info.btf_size > buf.len() as u32 {
|
||||
buf.resize(info.btf_size as usize, 0u8);
|
||||
let btf_info =
|
||||
sys::btf_obj_get_info_by_fd(btf_fd, &mut buf).map_err(|io_error| {
|
||||
ProgramError::SyscallError {
|
||||
call: "bpf_obj_get_info_by_fd".to_owned(),
|
||||
io_error,
|
||||
}
|
||||
})?;
|
||||
Ok(btf_info)
|
||||
} else {
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
Err(io_error) => Err(ProgramError::SyscallError {
|
||||
call: "bpf_obj_get_info_by_fd".to_owned(),
|
||||
io_error,
|
||||
}),
|
||||
}?;
|
||||
|
||||
let btf = Btf::parse(&buf[0..btf_info.btf_size as usize], Endianness::default())
|
||||
.map_err(ProgramError::Btf)?;
|
||||
|
||||
let btf_id = btf
|
||||
.id_by_type_name_kind(func_name, BtfKind::Func)
|
||||
.map_err(ProgramError::Btf)?;
|
||||
|
||||
self.data.attach_btf_obj_fd = Some(btf_fd as u32);
|
||||
self.data.attach_prog_fd = Some(target_prog_fd);
|
||||
self.data.attach_btf_id = Some(btf_id);
|
||||
load_program(BPF_PROG_TYPE_EXT, &mut self.data)
|
||||
}
|
||||
|
||||
/// Attaches the extension
|
||||
///
|
||||
/// Attaches the extension effectively replacing the original target function.
|
||||
/// Detaching the returned link restores the original function.
|
||||
pub fn attach(&mut self) -> Result<LinkRef, ProgramError> {
|
||||
let prog_fd = self.data.fd_or_err()?;
|
||||
let target_fd = self.data.attach_prog_fd.ok_or(ProgramError::NotLoaded)?;
|
||||
let btf_id = self.data.attach_btf_id.ok_or(ProgramError::NotLoaded)?;
|
||||
// the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS
|
||||
let link_fd = bpf_link_create(prog_fd, target_fd, BPF_CGROUP_INET_INGRESS, Some(btf_id), 0)
|
||||
.map_err(|(_, io_error)| ProgramError::SyscallError {
|
||||
call: "bpf_link_create".to_owned(),
|
||||
io_error,
|
||||
})? as RawFd;
|
||||
Ok(self.data.link(FdLink { fd: Some(link_fd) }))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue