aya: Add PinnedProgram

This commit adds PinnedProgram which allows the creation of a Program
from a path on bpffs. This is useful to be able to call `attach` or
other APIs for programs that are already loaded to the kernel.
Unfortunately figuring out the concrete type of program is hard given
the information in the kernel so this may not work for all program
types.

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
pull/389/head
Dave Tucker 3 years ago
parent e696389837
commit 5750c3ef7a

@ -601,7 +601,7 @@ impl<'a> BpfLoader<'a> {
ProgramSection::CgroupSock { attach_type, .. } => {
Program::CgroupSock(CgroupSock {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
attach_type: *attach_type,
attach_type: Some(*attach_type),
})
}
}

@ -54,13 +54,18 @@ use crate::{
#[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK")]
pub struct CgroupSock {
pub(crate) data: ProgramData<CgroupSockLink>,
pub(crate) attach_type: CgroupSockAttachType,
pub(crate) attach_type: Option<CgroupSockAttachType>,
}
impl CgroupSock {
/// Loads the program inside the kernel.
pub fn load(&mut self) -> Result<(), ProgramError> {
self.data.expected_attach_type = Some(self.attach_type.into());
if self.attach_type.is_none() {
return Err(ProgramError::IncompleteProgramDefinition(
"missing attach_type".to_string(),
));
}
self.data.expected_attach_type = Some(self.attach_type.unwrap().into());
load_program(BPF_PROG_TYPE_CGROUP_SOCK, &mut self.data)
}

@ -66,9 +66,9 @@ pub mod xdp;
use libc::ENOSPC;
use std::{
ffi::CString,
io,
fs, io,
os::unix::io::{AsRawFd, RawFd},
path::Path,
path::{Path, PathBuf},
};
use thiserror::Error;
@ -106,8 +106,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,
};
@ -204,6 +205,10 @@ pub enum ProgramError {
/// program name
name: String,
},
/// The program defintion is incomplete.
#[error("incomplete program defintion. {0}")]
IncompleteProgramDefinition(String),
}
/// A [`Program`] file descriptor.
@ -395,10 +400,209 @@ impl Drop for Program {
}
}
/// A Pinned Program.
pub struct PinnedProgram {
path: PathBuf,
inner: Program,
}
impl PinnedProgram {
/// Loads a program from a pinned entry on a bpffs.
///
/// Not all programs can be loaded since we can't correctly convert them into a [`Program`] since
/// there is missing information in `bpf_prog_info`. Attempting to load an unsupported or
/// unimplemented program type will result in an error. You may use `ProgramInfo::from_pinned` which
/// offers limited interactions with these program types.
///
/// Existing links will not be populated. To work with existing links you should use [`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 path_string = CString::new(path.as_ref().to_str().unwrap()).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());
let p = match info.prog_type()? {
bpf_prog_type::BPF_PROG_TYPE_UNSPEC => {
return Err(ProgramError::IncompleteProgramDefinition(
"unknown program type.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER => Program::SocketFilter(SocketFilter {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_KPROBE => {
return Err(ProgramError::IncompleteProgramDefinition(
"unable to determine probe kind.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS => {
let cname = CString::new(name.clone().unwrap_or_default())
.unwrap()
.into_boxed_c_str();
Program::SchedClassifier(SchedClassifier {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
name: cname,
})
}
bpf_prog_type::BPF_PROG_TYPE_SCHED_ACT => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT => Program::TracePoint(TracePoint {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_XDP => Program::Xdp(Xdp {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_PERF_EVENT => Program::PerfEvent(PerfEvent {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_CGROUP_SKB => Program::CgroupSkb(CgroupSkb {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
expected_attach_type: None,
}),
bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK => Program::CgroupSock(CgroupSock {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
attach_type: None, // pick one because we don't know for sure
}),
bpf_prog_type::BPF_PROG_TYPE_LWT_IN => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_LWT_OUT => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_LWT_XMIT => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_SOCK_OPS => Program::SockOps(SockOps {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_SK_SKB => {
return Err(ProgramError::IncompleteProgramDefinition(
"unable to determine parser or verdict program.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_CGROUP_DEVICE => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_SK_MSG => Program::SkMsg(SkMsg {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT => Program::RawTracePoint(RawTracePoint {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCK_ADDR => {
return Err(ProgramError::IncompleteProgramDefinition(
"unable to determine attach type.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_LWT_SEG6LOCAL => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_LIRC_MODE2 => Program::LircMode2(LircMode2 {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_SK_REUSEPORT => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_FLOW_DISSECTOR => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_CGROUP_SYSCTL => Program::CgroupSysctl(CgroupSysctl {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_CGROUP_SOCKOPT => {
return Err(ProgramError::IncompleteProgramDefinition(
"unable to determine attach type".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_TRACING => {
return Err(ProgramError::IncompleteProgramDefinition(
"unable to distinguish between fentry and fexit.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_STRUCT_OPS => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
bpf_prog_type::BPF_PROG_TYPE_EXT => Program::Extension(Extension {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_LSM => Program::Lsm(Lsm {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_SK_LOOKUP => Program::SkLookup(SkLookup {
data: ProgramData::from_bpf_prog_info(name, fd, info.0)?,
}),
bpf_prog_type::BPF_PROG_TYPE_SYSCALL => {
return Err(ProgramError::IncompleteProgramDefinition(
"not implemented.".to_string(),
))
}
};
Ok(PinnedProgram {
path: PathBuf::from(path.as_ref()),
inner: p,
})
}
/// Removes the pinned program from bpffs.
pub fn unpin(self) -> Result<Program, io::Error> {
fs::remove_file(self.path.clone())?;
Ok(self.inner)
}
}
impl AsRef<Program> for PinnedProgram {
fn as_ref(&self) -> &Program {
&self.inner
}
}
impl AsMut<Program> for PinnedProgram {
fn as_mut(&mut self) -> &mut Program {
&mut self.inner
}
}
#[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>,
@ -418,7 +622,7 @@ impl<T: Link> ProgramData<T> {
) -> ProgramData<T> {
ProgramData {
name,
obj,
obj: Some(obj),
fd: None,
links: LinkMap::new(),
expected_attach_type: None,
@ -429,6 +633,42 @@ impl<T: Link> ProgramData<T> {
verifier_log_level,
}
}
pub(crate) fn from_bpf_prog_info(
name: Option<String>,
fd: RawFd,
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,
})
}
}
impl<T: Link> ProgramData<T> {
@ -481,6 +721,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 {
@ -833,7 +1078,55 @@ impl ProgramInfo {
unsafe {
libc::close(fd);
}
Ok(ProgramInfo(info))
}
/// Returns the program type.
pub fn prog_type(&self) -> Result<bpf_prog_type, ProgramError> {
self.0.type_.try_into()
}
}
impl TryFrom<u32> for bpf_prog_type {
type Error = ProgramError;
fn try_from(v: u32) -> Result<Self, Self::Error> {
use bpf_prog_type::*;
let type_ = match v {
0 => BPF_PROG_TYPE_UNSPEC,
1 => BPF_PROG_TYPE_SOCKET_FILTER,
2 => BPF_PROG_TYPE_KPROBE,
3 => BPF_PROG_TYPE_SCHED_CLS,
4 => BPF_PROG_TYPE_SCHED_ACT,
5 => BPF_PROG_TYPE_TRACEPOINT,
6 => BPF_PROG_TYPE_XDP,
7 => BPF_PROG_TYPE_PERF_EVENT,
8 => BPF_PROG_TYPE_CGROUP_SKB,
9 => BPF_PROG_TYPE_CGROUP_SOCK,
10 => BPF_PROG_TYPE_LWT_IN,
11 => BPF_PROG_TYPE_LWT_OUT,
12 => BPF_PROG_TYPE_LWT_XMIT,
13 => BPF_PROG_TYPE_SOCK_OPS,
14 => BPF_PROG_TYPE_SK_SKB,
15 => BPF_PROG_TYPE_CGROUP_DEVICE,
16 => BPF_PROG_TYPE_SK_MSG,
17 => BPF_PROG_TYPE_RAW_TRACEPOINT,
18 => BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
19 => BPF_PROG_TYPE_LWT_SEG6LOCAL,
20 => BPF_PROG_TYPE_LIRC_MODE2,
21 => BPF_PROG_TYPE_SK_REUSEPORT,
22 => BPF_PROG_TYPE_FLOW_DISSECTOR,
23 => BPF_PROG_TYPE_CGROUP_SYSCTL,
24 => BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE,
25 => BPF_PROG_TYPE_CGROUP_SOCKOPT,
26 => BPF_PROG_TYPE_TRACING,
27 => BPF_PROG_TYPE_STRUCT_OPS,
28 => BPF_PROG_TYPE_EXT,
29 => BPF_PROG_TYPE_LSM,
30 => BPF_PROG_TYPE_SK_LOOKUP,
31 => BPF_PROG_TYPE_SYSCALL,
_ => return Err(ProgramError::UnexpectedProgramType),
};
Ok(type_)
}
}

@ -1,11 +1,11 @@
use std::{process::Command, thread, time};
use std::{convert::TryInto, process::Command, thread, time};
use aya::{
include_bytes_aligned,
maps::{Array, MapRefMut},
programs::{
links::{FdLink, PinnedLink},
TracePoint, Xdp, XdpFlags,
PinnedProgram, TracePoint, Xdp, XdpFlags,
},
Bpf,
};
@ -69,12 +69,22 @@ fn is_loaded(name: &str) -> bool {
stdout.contains(name)
}
fn assert_loaded(name: &str, loaded: bool) {
let state = is_loaded(name);
if state == loaded {
return;
}
panic!("Expected loaded: {} but was loaded: {}", loaded, state);
macro_rules! assert_loaded {
($name:literal) => {
let state = is_loaded($name);
if state != true {
panic!("Expected loaded: {} but was not loaded", $name);
}
};
}
macro_rules! assert_not_loaded {
($name:literal) => {
let state = is_loaded($name);
if state != false {
panic!("Expected not loaded: {} but was loaded", $name);
}
};
}
#[integration_test]
@ -87,19 +97,19 @@ fn unload() -> anyhow::Result<()> {
{
let _link_owned = prog.take_link(link);
prog.unload().unwrap();
assert_loaded("test_unload", true);
assert_loaded!("test_unload");
};
assert_loaded("test_unload", false);
assert_not_loaded!("test_unload");
prog.load().unwrap();
assert_loaded("test_unload", true);
assert_loaded!("test_unload");
prog.attach("lo", XdpFlags::default()).unwrap();
assert_loaded("test_unload", true);
assert_loaded!("test_unload");
prog.unload().unwrap();
assert_loaded("test_unload", false);
assert_not_loaded!("test_unload");
Ok(())
}
@ -111,22 +121,22 @@ fn pin_link() -> anyhow::Result<()> {
prog.load().unwrap();
let link_id = prog.attach("lo", XdpFlags::default()).unwrap();
let link = prog.take_link(link_id)?;
assert_loaded("test_unload", true);
assert_loaded!("test_unload");
let fd_link: FdLink = link.try_into()?;
let pinned = fd_link.pin("/sys/fs/bpf/aya-xdp-test-lo")?;
// because of the pin, the program is still attached
prog.unload()?;
assert_loaded("test_unload", true);
assert_loaded!("test_unload");
// delete the pin, but the program is still attached
let new_link = pinned.unpin()?;
assert_loaded("test_unload", true);
assert_loaded!("test_unload");
// finally when new_link is dropped we're detached
drop(new_link);
assert_loaded("test_unload", false);
assert_not_loaded!("test_unload");
Ok(())
}
@ -147,7 +157,7 @@ fn pin_lifecycle() -> anyhow::Result<()> {
}
// should still be loaded since link was pinned
assert_loaded("pass", true);
assert_loaded!("pass");
// 2. Load a new version of the program, unpin link, and atomically replace old program
{
@ -157,11 +167,48 @@ fn pin_lifecycle() -> anyhow::Result<()> {
let link = PinnedLink::from_pin("/sys/fs/bpf/aya-xdp-test-lo")?.unpin()?;
prog.attach_to_link(link.try_into()?)?;
assert_loaded("pass", true);
assert_loaded!("pass");
}
// program should be unloaded
assert_not_loaded!("pass");
Ok(())
}
#[integration_test]
fn pin_prog_lifecycle() -> anyhow::Result<()> {
assert_not_loaded!("pass");
let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/debug/pass");
// 1. Load Program and Pin
{
let mut bpf = Bpf::load(bytes)?;
let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
prog.load().unwrap();
prog.pin("/sys/fs/bpf/aya-xdp-test-prog")?;
}
// should still be loaded since prog was pinned
assert_loaded!("pass");
// 2. Load program from bpffs
{
let mut pinned = PinnedProgram::from_pin("/sys/fs/bpf/aya-xdp-test-prog")?;
// I can perform an attach operatoin here
let prog: &mut Xdp = pinned.as_mut().try_into()?;
prog.attach("lo", XdpFlags::default()).unwrap();
assert_loaded!("pass");
// Unpin the program. We need to keep this in scope though to avoid it being dropped.
let _unpinned_prog = pinned.unpin().unwrap();
assert_loaded!("pass");
}
// program should be unloaded
assert_loaded("pass", false);
assert_not_loaded!("pass");
Ok(())
}

Loading…
Cancel
Save