mirror of https://github.com/aya-rs/aya
commit
c5a10f8fbe
@ -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)]
|
#[allow(clippy::module_inception)]
|
||||||
mod btf;
|
mod btf;
|
||||||
|
mod info;
|
||||||
mod relocation;
|
mod relocation;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
pub use btf::*;
|
pub use btf::*;
|
||||||
|
pub(crate) use info::*;
|
||||||
pub use relocation::RelocationError;
|
pub use relocation::RelocationError;
|
||||||
pub(crate) use types::*;
|
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