Merge branch 'aya-rs:main' into interface

pull/478/head
Ayrton Sparling 2 years ago committed by GitHub
commit 207076a2d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,7 +9,7 @@
[crates-badge]: https://img.shields.io/crates/v/aya.svg?style=for-the-badge&logo=rust
[crates-url]: https://crates.io/crates/aya
[license-badge]: https://img.shields.io/badge/license-MIT%2FApache--2.0-blue?style=for-the-badge
[build-badge]: https://img.shields.io/github/workflow/status/aya-rs/aya/build-aya?style=for-the-badge&logo=github
[build-badge]: https://img.shields.io/github/actions/workflow/status/aya-rs/aya/build-aya.yml?branch=main&style=for-the-badge
[book-badge]: https://img.shields.io/badge/read%20the-book-9cf.svg?style=for-the-badge&logo=mdbook
[book-url]: https://aya-rs.dev/book

@ -200,20 +200,33 @@ impl SkMsg {
pub struct Xdp {
item: ItemFn,
name: Option<String>,
frags: bool,
}
impl Xdp {
pub fn from_syn(mut args: Args, item: ItemFn) -> Result<Xdp> {
let name = name_arg(&mut args)?;
Ok(Xdp { item, name })
let name = pop_arg(&mut args, "name");
let mut frags = false;
if let Some(s) = pop_arg(&mut args, "frags") {
if let Ok(m) = s.parse() {
frags = m
} else {
return Err(Error::new_spanned(
"mutlibuffer",
"invalid value. should be 'true' or 'false'",
));
}
}
err_on_unknown_args(&args)?;
Ok(Xdp { item, name, frags })
}
pub fn expand(&self) -> Result<TokenStream> {
let section_prefix = if self.frags { "xdp.frags" } else { "xdp" };
let section_name = if let Some(name) = &self.name {
format!("xdp/{name}")
format!("{section_prefix}/{name}")
} else {
"xdp".to_owned()
section_prefix.to_string()
};
let fn_vis = &self.item.vis;
let fn_name = &self.item.sig.ident;

@ -160,7 +160,6 @@ pub struct Function {
/// - `iter+`, `iter.s+`
/// - `xdp.frags/cpumap`, `xdp/cpumap`
/// - `xdp.frags/devmap`, `xdp/devmap`
/// - `xdp.frags`
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub enum ProgramSection {
@ -184,6 +183,7 @@ pub enum ProgramSection {
},
Xdp {
name: String,
frags_supported: bool,
},
SkMsg {
name: String,
@ -266,7 +266,7 @@ impl ProgramSection {
ProgramSection::URetProbe { name } => name,
ProgramSection::TracePoint { name } => name,
ProgramSection::SocketFilter { name } => name,
ProgramSection::Xdp { name } => name,
ProgramSection::Xdp { name, .. } => name,
ProgramSection::SkMsg { name } => name,
ProgramSection::SkSkbStreamParser { name } => name,
ProgramSection::SkSkbStreamVerdict { name } => name,
@ -313,7 +313,14 @@ impl FromStr for ProgramSection {
"kretprobe" => KRetProbe { name },
"uprobe" => UProbe { name },
"uretprobe" => URetProbe { name },
"xdp" => Xdp { name },
"xdp" => Xdp {
name,
frags_supported: false,
},
"xdp.frags" => Xdp {
name,
frags_supported: true,
},
"tp_btf" => BtfTracePoint { name },
_ if kind.starts_with("tracepoint") || kind.starts_with("tp") => {
// tracepoint sections are named `tracepoint/category/event_name`,
@ -1837,6 +1844,30 @@ mod tests {
);
}
#[test]
fn test_parse_section_xdp_frags() {
let mut obj = fake_obj();
assert_matches!(
obj.parse_section(fake_section(
BpfSectionKind::Program,
"xdp.frags/foo",
bytes_of(&fake_ins())
)),
Ok(())
);
assert_matches!(
obj.programs.get("foo"),
Some(Program {
section: ProgramSection::Xdp {
frags_supported: true,
..
},
..
})
);
}
#[test]
fn test_parse_section_raw_tp() {
let mut obj = fake_obj();

@ -5,7 +5,7 @@ authors = ["Alessandro Decina <alessandro.d@gmail.com>"]
edition = "2021"
[dependencies]
bindgen = "0.63"
bindgen = "0.64"
clap = { version = "4", features = ["derive"] }
anyhow = "1"
thiserror = "1"

@ -20,11 +20,4 @@ pub fn bpf_builder() -> Builder {
.clang_arg("-Wno-unknown-attributes")
.default_enum_style(EnumVariation::ModuleConsts)
.prepend_enum_name(false)
// NOTE(vadorovsky): It's a workaround for the upstream bindgen issue:
// https://github.com/rust-lang/rust-bindgen/issues/2083
// tl;dr: Rust nightly complains about #[repr(packed)] structs deriving
// Debug without Copy.
// It needs to be fixed properly upstream, but for now we have to
// disable Debug derive here.
.derive_debug(false)
}

@ -9,6 +9,7 @@ use std::{
use aya_obj::{
btf::{BtfFeatures, BtfRelocationError},
generated::BPF_F_XDP_HAS_FRAGS,
relocation::BpfRelocationError,
};
use log::debug;
@ -34,8 +35,8 @@ use crate::{
sys::{
bpf_load_btf, bpf_map_freeze, bpf_map_update_elem_ptr, is_btf_datasec_supported,
is_btf_decl_tag_supported, is_btf_float_supported, is_btf_func_global_supported,
is_btf_func_supported, is_btf_supported, is_btf_type_tag_supported, is_prog_name_supported,
retry_with_verifier_logs,
is_btf_func_supported, is_btf_supported, is_btf_type_tag_supported, is_perf_link_supported,
is_prog_name_supported, retry_with_verifier_logs,
},
util::{bytes_of, possible_cpus, VerifierLog, POSSIBLE_CPUS},
};
@ -64,50 +65,39 @@ unsafe impl<T: Pod, const N: usize> Pod for [T; N] {}
pub use aya_obj::maps::{bpf_map_def, PinningType};
lazy_static! {
pub(crate) static ref FEATURES: Features = Features::new();
}
// Features implements BPF and BTF feature detection
#[derive(Default, Debug)]
pub(crate) struct Features {
pub bpf_name: bool,
pub bpf_perf_link: bool,
pub btf: Option<BtfFeatures>,
}
impl Features {
fn probe_features(&mut self) {
self.bpf_name = is_prog_name_supported();
debug!("[FEAT PROBE] BPF program name support: {}", self.bpf_name);
self.btf = if is_btf_supported() {
Some(BtfFeatures::default())
fn new() -> Self {
let btf = if is_btf_supported() {
Some(BtfFeatures {
btf_func: is_btf_func_supported(),
btf_func_global: is_btf_func_global_supported(),
btf_datasec: is_btf_datasec_supported(),
btf_float: is_btf_float_supported(),
btf_decl_tag: is_btf_decl_tag_supported(),
btf_type_tag: is_btf_type_tag_supported(),
})
} else {
None
};
debug!("[FEAT PROBE] BTF support: {}", self.btf.is_some());
if let Some(ref mut btf) = self.btf {
btf.btf_func = is_btf_func_supported();
debug!("[FEAT PROBE] BTF func support: {}", btf.btf_func);
btf.btf_func_global = is_btf_func_global_supported();
debug!(
"[FEAT PROBE] BTF global func support: {}",
btf.btf_func_global
);
btf.btf_datasec = is_btf_datasec_supported();
debug!(
"[FEAT PROBE] BTF var and datasec support: {}",
btf.btf_datasec
);
btf.btf_float = is_btf_float_supported();
debug!("[FEAT PROBE] BTF float support: {}", btf.btf_float);
btf.btf_decl_tag = is_btf_decl_tag_supported();
debug!("[FEAT PROBE] BTF decl_tag support: {}", btf.btf_decl_tag);
btf.btf_type_tag = is_btf_type_tag_supported();
debug!("[FEAT PROBE] BTF type_tag support: {}", btf.btf_type_tag);
}
let f = Features {
bpf_name: is_prog_name_supported(),
bpf_perf_link: is_perf_link_supported(),
btf,
};
debug!("BPF Feature Detection: {:#?}", f);
f
}
}
@ -138,7 +128,6 @@ pub struct BpfLoader<'a> {
map_pin_path: Option<PathBuf>,
globals: HashMap<&'a str, &'a [u8]>,
max_entries: HashMap<&'a str, u32>,
features: Features,
extensions: HashSet<&'a str>,
verifier_log_level: VerifierLogLevel,
}
@ -168,14 +157,11 @@ impl Default for VerifierLogLevel {
impl<'a> BpfLoader<'a> {
/// Creates a new loader instance.
pub fn new() -> BpfLoader<'a> {
let mut features = Features::default();
features.probe_features();
BpfLoader {
btf: Btf::from_sys_fs().ok().map(Cow::Owned),
map_pin_path: None,
globals: HashMap::new(),
max_entries: HashMap::new(),
features,
extensions: HashSet::new(),
verifier_log_level: VerifierLogLevel::default(),
}
@ -359,8 +345,8 @@ impl<'a> BpfLoader<'a> {
let mut obj = Object::parse(data)?;
obj.patch_map_data(self.globals.clone())?;
let btf_fd = if let Some(ref btf) = self.features.btf {
if let Some(btf) = obj.fixup_and_sanitize_btf(btf)? {
let btf_fd = if let Some(features) = &FEATURES.btf {
if let Some(btf) = obj.fixup_and_sanitize_btf(features)? {
// load btf to the kernel
Some(load_btf(btf.to_bytes())?)
} else {
@ -448,7 +434,7 @@ impl<'a> BpfLoader<'a> {
.programs
.drain()
.map(|(name, obj)| {
let prog_name = if self.features.bpf_name {
let prog_name = if FEATURES.bpf_name {
Some(name.clone())
} else {
None
@ -485,9 +471,16 @@ impl<'a> BpfLoader<'a> {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
})
}
ProgramSection::Xdp { .. } => Program::Xdp(Xdp {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
}),
ProgramSection::Xdp {
frags_supported, ..
} => {
let mut data =
ProgramData::new(prog_name, obj, btf_fd, verifier_log_level);
if *frags_supported {
data.flags = BPF_F_XDP_HAS_FRAGS;
}
Program::Xdp(Xdp { data })
}
ProgramSection::SkMsg { .. } => Program::SkMsg(SkMsg {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
}),
@ -608,10 +601,10 @@ fn parse_map(data: (String, MapData)) -> Result<(String, Map), BpfError> {
BPF_MAP_TYPE_PERCPU_ARRAY => Ok(Map::PerCpuArray(map)),
BPF_MAP_TYPE_PROG_ARRAY => Ok(Map::ProgramArray(map)),
BPF_MAP_TYPE_HASH => Ok(Map::HashMap(map)),
BPF_MAP_TYPE_LRU_HASH => Ok(Map::LruHashMap(map)),
BPF_MAP_TYPE_PERCPU_HASH => Ok(Map::PerCpuHashMap(map)),
BPF_MAP_TYPE_PERF_EVENT_ARRAY | BPF_MAP_TYPE_LRU_PERCPU_HASH => {
Ok(Map::PerfEventArray(map))
}
BPF_MAP_TYPE_LRU_PERCPU_HASH => Ok(Map::PerCpuLruHashMap(map)),
BPF_MAP_TYPE_PERF_EVENT_ARRAY => Ok(Map::PerfEventArray(map)),
BPF_MAP_TYPE_SOCKHASH => Ok(Map::SockHash(map)),
BPF_MAP_TYPE_SOCKMAP => Ok(Map::SockMap(map)),
BPF_MAP_TYPE_BLOOM_FILTER => Ok(Map::BloomFilter(map)),

@ -242,6 +242,10 @@ pub enum Map {
HashMap(MapData),
/// A [`PerCpuHashMap`] map
PerCpuHashMap(MapData),
/// A [`HashMap`] map that uses a LRU eviction policy.
LruHashMap(MapData),
/// A [`PerCpuHashMap`] map that uses a LRU eviction policy.
PerCpuLruHashMap(MapData),
/// A [`PerfEventArray`] map
PerfEventArray(MapData),
/// A [`SockMap`] map
@ -268,7 +272,9 @@ impl Map {
Map::PerCpuArray(map) => map.obj.map_type(),
Map::ProgramArray(map) => map.obj.map_type(),
Map::HashMap(map) => map.obj.map_type(),
Map::LruHashMap(map) => map.obj.map_type(),
Map::PerCpuHashMap(map) => map.obj.map_type(),
Map::PerCpuLruHashMap(map) => map.obj.map_type(),
Map::PerfEventArray(map) => map.obj.map_type(),
Map::SockHash(map) => map.obj.map_type(),
Map::SockMap(map) => map.obj.map_type(),

@ -2,6 +2,7 @@
use std::{
hash::Hash,
os::unix::prelude::{AsRawFd, RawFd},
path::Path,
};
use crate::{
@ -138,6 +139,23 @@ impl CgroupSkb {
pub fn detach(&mut self, link_id: CgroupSkbLinkId) -> Result<(), ProgramError> {
self.data.links.remove(link_id)
}
/// Creates a program from a pinned entry on a bpffs.
///
/// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
///
/// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(
path: P,
expected_attach_type: CgroupSkbAttachType,
) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?;
Ok(Self {
data,
expected_attach_type: Some(expected_attach_type),
})
}
}
#[derive(Debug, Hash, Eq, PartialEq)]

@ -4,6 +4,7 @@ pub use aya_obj::programs::CgroupSockAttachType;
use std::{
hash::Hash,
os::unix::prelude::{AsRawFd, RawFd},
path::Path,
};
use crate::{
@ -113,6 +114,20 @@ impl CgroupSock {
pub fn detach(&mut self, link_id: CgroupSockLinkId) -> Result<(), ProgramError> {
self.data.links.remove(link_id)
}
/// Creates a program from a pinned entry on a bpffs.
///
/// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
///
/// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(
path: P,
attach_type: CgroupSockAttachType,
) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?;
Ok(Self { data, attach_type })
}
}
#[derive(Debug, Hash, Eq, PartialEq)]

@ -4,6 +4,7 @@ pub use aya_obj::programs::CgroupSockAddrAttachType;
use std::{
hash::Hash,
os::unix::prelude::{AsRawFd, RawFd},
path::Path,
};
use crate::{
@ -119,6 +120,20 @@ impl CgroupSockAddr {
pub fn detach(&mut self, link_id: CgroupSockAddrLinkId) -> Result<(), ProgramError> {
self.data.links.remove(link_id)
}
/// Creates a program from a pinned entry on a bpffs.
///
/// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
///
/// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(
path: P,
attach_type: CgroupSockAddrAttachType,
) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?;
Ok(Self { data, attach_type })
}
}
#[derive(Debug, Hash, Eq, PartialEq)]

@ -4,6 +4,7 @@ pub use aya_obj::programs::CgroupSockoptAttachType;
use std::{
hash::Hash,
os::unix::prelude::{AsRawFd, RawFd},
path::Path,
};
use crate::{
@ -114,6 +115,20 @@ impl CgroupSockopt {
pub fn detach(&mut self, link_id: CgroupSockoptLinkId) -> Result<(), ProgramError> {
self.data.links.remove(link_id)
}
/// Creates a program from a pinned entry on a bpffs.
///
/// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
///
/// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(
path: P,
attach_type: CgroupSockoptAttachType,
) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?;
Ok(Self { data, attach_type })
}
}
#[derive(Debug, Hash, Eq, PartialEq)]

@ -1,12 +1,12 @@
//! Kernel space probes.
use std::io;
use std::{io, path::Path};
use thiserror::Error;
use crate::{
generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE,
programs::{
define_link_wrapper, load_program,
perf_attach::{PerfLink, PerfLinkId},
perf_attach::{PerfLinkIdInner, PerfLinkInner},
probe::{attach, ProbeKind},
ProgramData, ProgramError,
},
@ -83,6 +83,17 @@ impl KProbe {
pub fn take_link(&mut self, link_id: KProbeLinkId) -> Result<KProbeLink, ProgramError> {
self.data.take_link(link_id)
}
/// Creates a program from a pinned entry on a bpffs.
///
/// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
///
/// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P, kind: ProbeKind) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?;
Ok(Self { data, kind })
}
}
define_link_wrapper!(
@ -90,8 +101,8 @@ define_link_wrapper!(
KProbeLink,
/// The type returned by [KProbe::attach]. Can be passed to [KProbe::detach].
KProbeLinkId,
PerfLink,
PerfLinkId
PerfLinkInner,
PerfLinkIdInner
);
/// The type returned when attaching a [`KProbe`] fails.

@ -185,7 +185,7 @@ impl PinnedLink {
PinnedLink { inner: link, path }
}
/// Creates a [`PinnedLink`] from a valid path on bpffs.
/// Creates a [`crate::programs::links::PinnedLink`] from a valid path on bpffs.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
let path_string = CString::new(path.as_ref().to_string_lossy().to_string()).unwrap();
let fd =

@ -1,4 +1,5 @@
//! LSM probes.
use crate::{
generated::{bpf_attach_type::BPF_LSM_MAC, bpf_prog_type::BPF_PROG_TYPE_LSM},
obj::btf::{Btf, BtfKind},

@ -69,7 +69,7 @@ use std::{
ffi::CString,
io,
os::unix::io::{AsRawFd, RawFd},
path::Path,
path::{Path, PathBuf},
};
use thiserror::Error;
@ -112,8 +112,9 @@ use crate::{
obj::{self, btf::BtfError, Function, KernelVersion},
pin::PinError,
sys::{
bpf_get_object, bpf_load_program, bpf_pin_object, bpf_prog_get_fd_by_id,
bpf_prog_get_info_by_fd, bpf_prog_query, retry_with_verifier_logs, BpfLoadProgramAttrs,
bpf_btf_get_fd_by_id, bpf_get_object, bpf_load_program, bpf_pin_object,
bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, bpf_prog_query, retry_with_verifier_logs,
BpfLoadProgramAttrs,
},
util::VerifierLog,
};
@ -341,33 +342,33 @@ impl Program {
}
}
/// Unload the program
fn unload(&mut self) -> Result<(), ProgramError> {
/// Unloads the program from the kernel.
pub fn unload(self) -> Result<(), ProgramError> {
match self {
Program::KProbe(p) => p.unload(),
Program::UProbe(p) => p.unload(),
Program::TracePoint(p) => p.unload(),
Program::SocketFilter(p) => p.unload(),
Program::Xdp(p) => p.unload(),
Program::SkMsg(p) => p.unload(),
Program::SkSkb(p) => p.unload(),
Program::SockOps(p) => p.unload(),
Program::SchedClassifier(p) => p.unload(),
Program::CgroupSkb(p) => p.unload(),
Program::CgroupSysctl(p) => p.unload(),
Program::CgroupSockopt(p) => p.unload(),
Program::LircMode2(p) => p.unload(),
Program::PerfEvent(p) => p.unload(),
Program::RawTracePoint(p) => p.unload(),
Program::Lsm(p) => p.unload(),
Program::BtfTracePoint(p) => p.unload(),
Program::FEntry(p) => p.unload(),
Program::FExit(p) => p.unload(),
Program::Extension(p) => p.unload(),
Program::CgroupSockAddr(p) => p.unload(),
Program::SkLookup(p) => p.unload(),
Program::CgroupSock(p) => p.unload(),
Program::CgroupDevice(p) => p.unload(),
Program::KProbe(mut p) => p.unload(),
Program::UProbe(mut p) => p.unload(),
Program::TracePoint(mut p) => p.unload(),
Program::SocketFilter(mut p) => p.unload(),
Program::Xdp(mut p) => p.unload(),
Program::SkMsg(mut p) => p.unload(),
Program::SkSkb(mut p) => p.unload(),
Program::SockOps(mut p) => p.unload(),
Program::SchedClassifier(mut p) => p.unload(),
Program::CgroupSkb(mut p) => p.unload(),
Program::CgroupSysctl(mut p) => p.unload(),
Program::CgroupSockopt(mut p) => p.unload(),
Program::LircMode2(mut p) => p.unload(),
Program::PerfEvent(mut p) => p.unload(),
Program::RawTracePoint(mut p) => p.unload(),
Program::Lsm(mut p) => p.unload(),
Program::BtfTracePoint(mut p) => p.unload(),
Program::FEntry(mut p) => p.unload(),
Program::FExit(mut p) => p.unload(),
Program::Extension(mut p) => p.unload(),
Program::CgroupSockAddr(mut p) => p.unload(),
Program::SkLookup(mut p) => p.unload(),
Program::CgroupSock(mut p) => p.unload(),
Program::CgroupDevice(mut p) => p.unload(),
}
}
@ -405,16 +406,10 @@ impl Program {
}
}
impl Drop for Program {
fn drop(&mut self) {
let _ = self.unload();
}
}
#[derive(Debug)]
pub(crate) struct ProgramData<T: Link> {
pub(crate) name: Option<String>,
pub(crate) obj: obj::Program,
pub(crate) obj: Option<obj::Program>,
pub(crate) fd: Option<RawFd>,
pub(crate) links: LinkMap<T>,
pub(crate) expected_attach_type: Option<bpf_attach_type>,
@ -423,6 +418,8 @@ pub(crate) struct ProgramData<T: Link> {
pub(crate) attach_prog_fd: Option<RawFd>,
pub(crate) btf_fd: Option<RawFd>,
pub(crate) verifier_log_level: u32,
pub(crate) path: Option<PathBuf>,
pub(crate) flags: u32,
}
impl<T: Link> ProgramData<T> {
@ -434,7 +431,7 @@ impl<T: Link> ProgramData<T> {
) -> ProgramData<T> {
ProgramData {
name,
obj,
obj: Some(obj),
fd: None,
links: LinkMap::new(),
expected_attach_type: None,
@ -443,8 +440,70 @@ impl<T: Link> ProgramData<T> {
attach_prog_fd: None,
btf_fd,
verifier_log_level,
path: None,
flags: 0,
}
}
pub(crate) fn from_bpf_prog_info(
name: Option<String>,
fd: RawFd,
path: &Path,
info: bpf_prog_info,
) -> Result<ProgramData<T>, ProgramError> {
let attach_btf_id = if info.attach_btf_id > 0 {
Some(info.attach_btf_id)
} else {
None
};
let attach_btf_obj_fd = if info.attach_btf_obj_id > 0 {
let fd = bpf_btf_get_fd_by_id(info.attach_btf_obj_id).map_err(|io_error| {
ProgramError::SyscallError {
call: "bpf_btf_get_fd_by_id".to_string(),
io_error,
}
})?;
Some(fd as u32)
} else {
None
};
Ok(ProgramData {
name,
obj: None,
fd: Some(fd),
links: LinkMap::new(),
expected_attach_type: None,
attach_btf_obj_fd,
attach_btf_id,
attach_prog_fd: None,
btf_fd: None,
verifier_log_level: 0,
path: Some(path.to_path_buf()),
flags: 0,
})
}
pub(crate) fn from_pinned_path<P: AsRef<Path>>(
path: P,
) -> Result<ProgramData<T>, ProgramError> {
let path_string =
CString::new(path.as_ref().as_os_str().to_string_lossy().as_bytes()).unwrap();
let fd =
bpf_get_object(&path_string).map_err(|(_, io_error)| ProgramError::SyscallError {
call: "bpf_obj_get".to_owned(),
io_error,
})? as RawFd;
let info = bpf_prog_get_info_by_fd(fd).map_err(|io_error| ProgramError::SyscallError {
call: "bpf_prog_get_info_by_fd".to_owned(),
io_error,
})?;
let info = ProgramInfo(info);
let name = info.name_as_str().map(|s| s.to_string());
ProgramData::from_bpf_prog_info(name, fd, path.as_ref(), info.0)
}
}
impl<T: Link> ProgramData<T> {
@ -497,6 +556,11 @@ fn load_program<T: Link>(
if fd.is_some() {
return Err(ProgramError::AlreadyLoaded);
}
if obj.is_none() {
// This program was loaded from a pin in bpffs
return Err(ProgramError::AlreadyLoaded);
}
let obj = obj.as_ref().unwrap();
let crate::obj::Program {
function:
Function {
@ -549,6 +613,7 @@ fn load_program<T: Link>(
func_info: func_info.clone(),
line_info_rec_size: *line_info_rec_size,
line_info: line_info.clone(),
flags: data.flags,
};
let verifier_log_level = data.verifier_log_level;
@ -625,6 +690,12 @@ macro_rules! impl_program_unload {
unload_program(&mut self.data)
}
}
impl Drop for $struct_name {
fn drop(&mut self) {
let _ = self.unload();
}
}
)+
}
}
@ -749,8 +820,17 @@ macro_rules! impl_program_pin{
/// To remove the program, the file on the BPF filesystem must be removed.
/// Any directories in the the path provided should have been created by the caller.
pub fn pin<P: AsRef<Path>>(&mut self, path: P) -> Result<(), PinError> {
self.data.path = Some(path.as_ref().to_path_buf());
pin_program(&mut self.data, path)
}
/// Removes the pinned link from the filesystem.
pub fn unpin(mut self) -> Result<(), io::Error> {
if let Some(path) = self.data.path.take() {
std::fs::remove_file(path)?;
}
Ok(())
}
}
)+
}
@ -783,6 +863,45 @@ impl_program_pin!(
CgroupDevice,
);
macro_rules! impl_from_pin {
($($struct_name:ident),+ $(,)?) => {
$(
impl $struct_name {
/// Creates a program from a pinned entry on a bpffs.
///
/// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
///
/// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?;
Ok(Self { data })
}
}
)+
}
}
// Use impl_from_pin if the program doesn't require additional data
impl_from_pin!(
TracePoint,
SocketFilter,
Xdp,
SkMsg,
CgroupSysctl,
LircMode2,
PerfEvent,
Lsm,
RawTracePoint,
BtfTracePoint,
FEntry,
FExit,
Extension,
SkLookup,
SockOps,
CgroupDevice,
);
macro_rules! impl_try_from_program {
($($ty:ident),+ $(,)?) => {
$(
@ -895,7 +1014,6 @@ impl ProgramInfo {
unsafe {
libc::close(fd);
}
Ok(ProgramInfo(info))
}
}

@ -3,11 +3,42 @@ use libc::close;
use std::os::unix::io::RawFd;
use crate::{
programs::{probe::detach_debug_fs, Link, ProbeKind, ProgramData, ProgramError},
sys::perf_event_ioctl,
PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, PERF_EVENT_IOC_SET_BPF,
generated::bpf_attach_type::BPF_PERF_EVENT,
programs::{probe::detach_debug_fs, FdLink, Link, ProbeKind, ProgramError},
sys::{bpf_link_create, perf_event_ioctl},
FEATURES, PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, PERF_EVENT_IOC_SET_BPF,
};
#[derive(Debug, Hash, Eq, PartialEq)]
pub(crate) enum PerfLinkIdInner {
FdLinkId(<FdLink as Link>::Id),
PerfLinkId(<PerfLink as Link>::Id),
}
#[derive(Debug)]
pub(crate) enum PerfLinkInner {
FdLink(FdLink),
PerfLink(PerfLink),
}
impl Link for PerfLinkInner {
type Id = PerfLinkIdInner;
fn id(&self) -> Self::Id {
match self {
PerfLinkInner::FdLink(link) => PerfLinkIdInner::FdLinkId(link.id()),
PerfLinkInner::PerfLink(link) => PerfLinkIdInner::PerfLinkId(link.id()),
}
}
fn detach(self) -> Result<(), ProgramError> {
match self {
PerfLinkInner::FdLink(link) => link.detach(),
PerfLinkInner::PerfLink(link) => link.detach(),
}
}
}
/// The identifer of a PerfLink.
#[derive(Debug, Hash, Eq, PartialEq)]
pub struct PerfLinkId(RawFd);
@ -41,29 +72,36 @@ impl Link for PerfLink {
}
}
pub(crate) fn perf_attach<T: Link + From<PerfLink>>(
data: &mut ProgramData<T>,
fd: RawFd,
) -> Result<T::Id, ProgramError> {
perf_attach_either(data, fd, None, None)
pub(crate) fn perf_attach(prog_fd: RawFd, fd: RawFd) -> Result<PerfLinkInner, ProgramError> {
if FEATURES.bpf_perf_link {
let link_fd =
bpf_link_create(prog_fd, fd, BPF_PERF_EVENT, None, 0).map_err(|(_, io_error)| {
ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,
}
})? as RawFd;
Ok(PerfLinkInner::FdLink(FdLink::new(link_fd)))
} else {
perf_attach_either(prog_fd, fd, None, None)
}
}
pub(crate) fn perf_attach_debugfs<T: Link + From<PerfLink>>(
data: &mut ProgramData<T>,
pub(crate) fn perf_attach_debugfs(
prog_fd: RawFd,
fd: RawFd,
probe_kind: ProbeKind,
event_alias: String,
) -> Result<T::Id, ProgramError> {
perf_attach_either(data, fd, Some(probe_kind), Some(event_alias))
) -> Result<PerfLinkInner, ProgramError> {
perf_attach_either(prog_fd, fd, Some(probe_kind), Some(event_alias))
}
fn perf_attach_either<T: Link + From<PerfLink>>(
data: &mut ProgramData<T>,
fn perf_attach_either(
prog_fd: RawFd,
fd: RawFd,
probe_kind: Option<ProbeKind>,
event_alias: Option<String>,
) -> Result<T::Id, ProgramError> {
let prog_fd = data.fd_or_err()?;
) -> Result<PerfLinkInner, ProgramError> {
perf_event_ioctl(fd, PERF_EVENT_IOC_SET_BPF, prog_fd).map_err(|(_, io_error)| {
ProgramError::SyscallError {
call: "PERF_EVENT_IOC_SET_BPF".to_owned(),
@ -77,12 +115,9 @@ fn perf_attach_either<T: Link + From<PerfLink>>(
}
})?;
data.links.insert(
PerfLink {
perf_fd: fd,
probe_kind,
event_alias,
}
.into(),
)
Ok(PerfLinkInner::PerfLink(PerfLink {
perf_fd: fd,
probe_kind,
event_alias,
}))
}

@ -1,4 +1,5 @@
//! Perf event programs.
pub use crate::generated::{
perf_hw_cache_id, perf_hw_cache_op_id, perf_hw_cache_op_result_id, perf_hw_id, perf_sw_ids,
};
@ -12,8 +13,9 @@ use crate::{
},
},
programs::{
links::define_link_wrapper,
load_program, perf_attach,
perf_attach::{PerfLink, PerfLinkId},
perf_attach::{PerfLinkIdInner, PerfLinkInner},
ProgramData, ProgramError,
},
sys::perf_event_open,
@ -118,7 +120,7 @@ pub enum PerfEventScope {
#[derive(Debug)]
#[doc(alias = "BPF_PROG_TYPE_PERF_EVENT")]
pub struct PerfEvent {
pub(crate) data: ProgramData<PerfLink>,
pub(crate) data: ProgramData<PerfEventLink>,
}
impl PerfEvent {
@ -140,7 +142,7 @@ impl PerfEvent {
config: u64,
scope: PerfEventScope,
sample_policy: SamplePolicy,
) -> Result<PerfLinkId, ProgramError> {
) -> Result<PerfEventLinkId, ProgramError> {
let (sample_period, sample_frequency) = match sample_policy {
SamplePolicy::Period(period) => (period, None),
SamplePolicy::Frequency(frequency) => (0, Some(frequency)),
@ -167,13 +169,14 @@ impl PerfEvent {
io_error,
})? as i32;
perf_attach(&mut self.data, fd)
let link = perf_attach(self.data.fd_or_err()?, fd)?;
self.data.links.insert(PerfEventLink::new(link))
}
/// Detaches the program.
///
/// See [PerfEvent::attach].
pub fn detach(&mut self, link_id: PerfLinkId) -> Result<(), ProgramError> {
pub fn detach(&mut self, link_id: PerfEventLinkId) -> Result<(), ProgramError> {
self.data.links.remove(link_id)
}
@ -181,7 +184,16 @@ impl PerfEvent {
///
/// The link will be detached on `Drop` and the caller is now responsible
/// for managing its lifetime.
pub fn take_link(&mut self, link_id: PerfLinkId) -> Result<PerfLink, ProgramError> {
pub fn take_link(&mut self, link_id: PerfEventLinkId) -> Result<PerfEventLink, ProgramError> {
self.data.take_link(link_id)
}
}
define_link_wrapper!(
/// The link used by [PerfEvent] programs.
PerfEventLink,
/// The type returned by [PerfEvent::attach]. Can be passed to [PerfEvent::detach].
PerfEventLinkId,
PerfLinkInner,
PerfLinkIdInner
);

@ -8,7 +8,7 @@ use std::{
use crate::{
programs::{
kprobe::KProbeError, perf_attach, perf_attach::PerfLink, perf_attach_debugfs,
kprobe::KProbeError, perf_attach, perf_attach::PerfLinkInner, perf_attach_debugfs,
trace_point::read_sys_fs_trace_point_id, uprobe::UProbeError, utils::find_tracefs_path,
Link, ProgramData, ProgramError,
},
@ -37,7 +37,7 @@ impl ProbeKind {
}
}
pub(crate) fn attach<T: Link + From<PerfLink>>(
pub(crate) fn attach<T: Link + From<PerfLinkInner>>(
program_data: &mut ProgramData<T>,
kind: ProbeKind,
fn_name: &str,
@ -49,13 +49,18 @@ pub(crate) fn attach<T: Link + From<PerfLink>>(
let k_ver = kernel_version().unwrap();
if k_ver < (4, 17, 0) {
let (fd, event_alias) = create_as_trace_point(kind, fn_name, offset, pid)?;
return perf_attach_debugfs(program_data, fd, kind, event_alias);
let link = T::from(perf_attach_debugfs(
program_data.fd_or_err()?,
fd,
kind,
event_alias,
)?);
return program_data.links.insert(link);
};
let fd = create_as_probe(kind, fn_name, offset, pid)?;
perf_attach(program_data, fd)
let link = T::from(perf_attach(program_data.fd_or_err()?, fd)?);
program_data.links.insert(link)
}
pub(crate) fn detach_debug_fs(kind: ProbeKind, event_alias: &str) -> Result<(), ProgramError> {

@ -1,6 +1,6 @@
//! Skskb programs.
use std::os::unix::io::AsRawFd;
use std::{os::unix::io::AsRawFd, path::Path};
use crate::{
generated::{
@ -105,6 +105,17 @@ impl SkSkb {
pub fn take_link(&mut self, link_id: SkSkbLinkId) -> Result<SkSkbLink, ProgramError> {
self.data.take_link(link_id)
}
/// Creates a program from a pinned entry on a bpffs.
///
/// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
///
/// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P, kind: SkSkbKind) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?;
Ok(Self { data, kind })
}
}
define_link_wrapper!(

@ -4,6 +4,7 @@ use thiserror::Error;
use std::{
ffi::{CStr, CString},
io,
path::Path,
};
use crate::{
@ -190,6 +191,20 @@ impl SchedClassifier {
) -> Result<SchedClassifierLink, ProgramError> {
self.data.take_link(link_id)
}
/// Creates a program from a pinned entry on a bpffs.
///
/// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
///
/// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?;
let cname = CString::new(data.name.clone().unwrap_or_default())
.unwrap()
.into_boxed_c_str();
Ok(Self { data, name: cname })
}
}
impl SchedClassifierLink {

@ -1,4 +1,5 @@
//! BTF-enabled raw tracepoints.
use crate::{
generated::{bpf_attach_type::BPF_TRACE_RAW_TP, bpf_prog_type::BPF_PROG_TYPE_TRACING},
obj::btf::{Btf, BtfKind},

@ -6,7 +6,7 @@ use crate::{
generated::bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT,
programs::{
define_link_wrapper, load_program,
perf_attach::{perf_attach, PerfLink, PerfLinkId},
perf_attach::{perf_attach, PerfLinkIdInner, PerfLinkInner},
utils::find_tracefs_path,
ProgramData, ProgramError,
},
@ -87,7 +87,8 @@ impl TracePoint {
}
})? as i32;
perf_attach(&mut self.data, fd)
let link = perf_attach(self.data.fd_or_err()?, fd)?;
self.data.links.insert(TracePointLink::new(link))
}
/// Detaches from a trace point.
@ -111,8 +112,8 @@ define_link_wrapper!(
TracePointLink,
/// The type returned by [TracePoint::attach]. Can be passed to [TracePoint::detach].
TracePointLinkId,
PerfLink,
PerfLinkId
PerfLinkInner,
PerfLinkIdInner
);
pub(crate) fn read_sys_fs_trace_point_id(

@ -17,7 +17,7 @@ use crate::{
generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE,
programs::{
define_link_wrapper, load_program,
perf_attach::{PerfLink, PerfLinkId},
perf_attach::{PerfLinkIdInner, PerfLinkInner},
probe::{attach, ProbeKind},
ProgramData, ProgramError,
},
@ -136,6 +136,17 @@ impl UProbe {
pub fn take_link(&mut self, link_id: UProbeLinkId) -> Result<UProbeLink, ProgramError> {
self.data.take_link(link_id)
}
/// Creates a program from a pinned entry on a bpffs.
///
/// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`].
///
/// On drop, any managed links are detached and the program is unloaded. This will not result in
/// the program being unloaded from the kernel if it is still pinned.
pub fn from_pin<P: AsRef<Path>>(path: P, kind: ProbeKind) -> Result<Self, ProgramError> {
let data = ProgramData::from_pinned_path(path)?;
Ok(Self { data, kind })
}
}
define_link_wrapper!(
@ -143,8 +154,8 @@ define_link_wrapper!(
UProbeLink,
/// The type returned by [UProbe::attach]. Can be passed to [UProbe::detach].
UProbeLinkId,
PerfLink,
PerfLinkId
PerfLinkInner,
PerfLinkIdInner
);
/// The type returned when attaching an [`UProbe`] fails.

@ -115,6 +115,7 @@ pub(crate) struct BpfLoadProgramAttrs<'a> {
pub(crate) func_info: FuncSecInfo,
pub(crate) line_info_rec_size: usize,
pub(crate) line_info: LineSecInfo,
pub(crate) flags: u32,
}
pub(crate) fn bpf_load_program(
@ -136,6 +137,7 @@ pub(crate) fn bpf_load_program(
u.prog_name = name;
}
u.prog_flags = aya_attr.flags;
u.prog_type = aya_attr.ty as u32;
if let Some(v) = aya_attr.expected_attach_type {
u.expected_attach_type = v as u32;
@ -179,7 +181,6 @@ pub(crate) fn bpf_load_program(
if let Some(v) = aya_attr.attach_btf_id {
u.attach_btf_id = v;
}
sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr)
}
@ -599,6 +600,37 @@ pub(crate) fn is_prog_name_supported() -> bool {
}
}
pub(crate) fn is_perf_link_supported() -> bool {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
let u = unsafe { &mut attr.__bindgen_anon_3 };
let prog: &[u8] = &[
0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov64 r0 = 0
0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit
];
let gpl = b"GPL\0";
u.license = gpl.as_ptr() as u64;
let insns = copy_instructions(prog).unwrap();
u.insn_cnt = insns.len() as u32;
u.insns = insns.as_ptr() as u64;
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32;
if let Ok(fd) = sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr) {
if let Err((_, e)) =
// Uses an invalid target FD so we get EBADF if supported.
bpf_link_create(fd as i32, -1, bpf_attach_type::BPF_PERF_EVENT, None, 0)
{
// Returns EINVAL if unsupported. EBADF if supported.
let res = e.raw_os_error() == Some(libc::EBADF);
unsafe { libc::close(fd as i32) };
return res;
}
}
false
}
pub(crate) fn is_btf_supported() -> bool {
let mut btf = Btf::new();
let name_offset = btf.add_string("int".to_string());
@ -861,3 +893,33 @@ where
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sys::override_syscall;
use libc::{EBADF, EINVAL};
#[test]
fn test_perf_link_supported() {
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_LINK_CREATE,
..
} => Err((-1, io::Error::from_raw_os_error(EBADF))),
_ => Ok(42),
});
let supported = is_perf_link_supported();
assert!(supported);
override_syscall(|call| match call {
Syscall::Bpf {
cmd: bpf_cmd::BPF_LINK_CREATE,
..
} => Err((-1, io::Error::from_raw_os_error(EINVAL))),
_ => Ok(42),
});
let supported = is_perf_link_supported();
assert!(!supported);
}
}

@ -14,7 +14,7 @@ static FOO: Array<u32> = Array::<u32>::with_max_entries(10, 0);
#[map(name = "BAR")]
static BAZ: Array<u32> = Array::<u32>::with_max_entries(10, 0);
#[xdp]
#[xdp(frags = "true")]
pub fn pass(ctx: XdpContext) -> u32 {
match unsafe { try_pass(ctx) } {
Ok(ret) => ret,

@ -3,7 +3,7 @@
use aya_bpf::{bindings::xdp_action, macros::xdp, programs::XdpContext};
#[xdp(name = "pass")]
#[xdp(name = "pass", frags = "true")]
pub fn pass(ctx: XdpContext) -> u32 {
match unsafe { try_pass(ctx) } {
Ok(ret) => ret,

@ -1,4 +1,4 @@
use std::{process::Command, thread, time};
use std::{convert::TryInto, process::Command, thread, time};
use aya::{
include_bytes_aligned,
@ -192,16 +192,36 @@ fn pin_lifecycle() {
let mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
prog.load().unwrap();
prog.pin("/sys/fs/bpf/aya-xdp-test-prog").unwrap();
}
// should still be loaded since prog was pinned
assert_loaded!("pass", true);
// 2. Load program from bpffs but don't attach it
{
let _ = Xdp::from_pin("/sys/fs/bpf/aya-xdp-test-prog").unwrap();
}
// should still be loaded since prog was pinned
assert_loaded!("pass", true);
// 3. Load program from bpffs and attach
{
let mut prog = Xdp::from_pin("/sys/fs/bpf/aya-xdp-test-prog").unwrap();
let link_id = prog.attach("lo", XdpFlags::default()).unwrap();
let link = prog.take_link(link_id).unwrap();
let fd_link: FdLink = link.try_into().unwrap();
fd_link.pin("/sys/fs/bpf/aya-xdp-test-lo").unwrap();
// Unpin the program. It will stay attached since its links were pinned.
prog.unpin().unwrap();
}
// should still be loaded since link was pinned
assert_loaded!("pass", true);
// 2. Load a new version of the program, unpin link, and atomically replace old program
// 4. Load a new version of the program, unpin link, and atomically replace old program
{
let mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();

Loading…
Cancel
Save