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/444/head
Dave Tucker 3 years ago
parent 88d7777553
commit 162847458d

@ -630,7 +630,7 @@ impl<'a> BpfLoader<'a> {
ProgramSection::CgroupSock { attach_type, .. } => { ProgramSection::CgroupSock { attach_type, .. } => {
Program::CgroupSock(CgroupSock { Program::CgroupSock(CgroupSock {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level), 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")] #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK")]
pub struct CgroupSock { pub struct CgroupSock {
pub(crate) data: ProgramData<CgroupSockLink>, pub(crate) data: ProgramData<CgroupSockLink>,
pub(crate) attach_type: CgroupSockAttachType, pub(crate) attach_type: Option<CgroupSockAttachType>,
} }
impl CgroupSock { impl CgroupSock {
/// Loads the program inside the kernel. /// Loads the program inside the kernel.
pub fn load(&mut self) -> Result<(), ProgramError> { 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) load_program(BPF_PROG_TYPE_CGROUP_SOCK, &mut self.data)
} }

@ -66,9 +66,9 @@ pub mod xdp;
use libc::ENOSPC; use libc::ENOSPC;
use std::{ use std::{
ffi::CString, ffi::CString,
io, fs, io,
os::unix::io::{AsRawFd, RawFd}, os::unix::io::{AsRawFd, RawFd},
path::Path, path::{Path, PathBuf},
}; };
use thiserror::Error; use thiserror::Error;
@ -106,8 +106,9 @@ use crate::{
obj::{self, btf::BtfError, Function, KernelVersion}, obj::{self, btf::BtfError, Function, KernelVersion},
pin::PinError, pin::PinError,
sys::{ sys::{
bpf_get_object, bpf_load_program, bpf_pin_object, bpf_prog_get_fd_by_id, bpf_btf_get_fd_by_id, bpf_get_object, bpf_load_program, bpf_pin_object,
bpf_prog_get_info_by_fd, bpf_prog_query, retry_with_verifier_logs, BpfLoadProgramAttrs, bpf_prog_get_fd_by_id, bpf_prog_get_info_by_fd, bpf_prog_query, retry_with_verifier_logs,
BpfLoadProgramAttrs,
}, },
util::VerifierLog, util::VerifierLog,
}; };
@ -204,6 +205,10 @@ pub enum ProgramError {
/// program name /// program name
name: String, name: String,
}, },
/// The program defintion is incomplete.
#[error("incomplete program defintion. {0}")]
IncompleteProgramDefinition(String),
} }
/// A [`Program`] file descriptor. /// 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)] #[derive(Debug)]
pub(crate) struct ProgramData<T: Link> { pub(crate) struct ProgramData<T: Link> {
pub(crate) name: Option<String>, pub(crate) name: Option<String>,
pub(crate) obj: obj::Program, pub(crate) obj: Option<obj::Program>,
pub(crate) fd: Option<RawFd>, pub(crate) fd: Option<RawFd>,
pub(crate) links: LinkMap<T>, pub(crate) links: LinkMap<T>,
pub(crate) expected_attach_type: Option<bpf_attach_type>, pub(crate) expected_attach_type: Option<bpf_attach_type>,
@ -418,7 +622,7 @@ impl<T: Link> ProgramData<T> {
) -> ProgramData<T> { ) -> ProgramData<T> {
ProgramData { ProgramData {
name, name,
obj, obj: Some(obj),
fd: None, fd: None,
links: LinkMap::new(), links: LinkMap::new(),
expected_attach_type: None, expected_attach_type: None,
@ -429,6 +633,42 @@ impl<T: Link> ProgramData<T> {
verifier_log_level, 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> { impl<T: Link> ProgramData<T> {
@ -481,6 +721,11 @@ fn load_program<T: Link>(
if fd.is_some() { if fd.is_some() {
return Err(ProgramError::AlreadyLoaded); 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 { let crate::obj::Program {
function: function:
Function { Function {
@ -833,7 +1078,55 @@ impl ProgramInfo {
unsafe { unsafe {
libc::close(fd); libc::close(fd);
} }
Ok(ProgramInfo(info)) 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::{ use aya::{
include_bytes_aligned, include_bytes_aligned,
maps::Array, maps::Array,
programs::{ programs::{
links::{FdLink, PinnedLink}, links::{FdLink, PinnedLink},
TracePoint, Xdp, XdpFlags, PinnedProgram, TracePoint, Xdp, XdpFlags,
}, },
Bpf, Bpf,
}; };
@ -140,16 +140,29 @@ fn pin_lifecycle() {
let mut bpf = Bpf::load(bytes).unwrap(); let mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
prog.load().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
{
let mut pinned = PinnedProgram::from_pin("/sys/fs/bpf/aya-xdp-test-prog").unwrap();
let prog: &mut Xdp = pinned.as_mut().try_into().unwrap();
let link_id = prog.attach("lo", XdpFlags::default()).unwrap(); let link_id = prog.attach("lo", XdpFlags::default()).unwrap();
let link = prog.take_link(link_id).unwrap(); let link = prog.take_link(link_id).unwrap();
let fd_link: FdLink = link.try_into().unwrap(); let fd_link: FdLink = link.try_into().unwrap();
fd_link.pin("/sys/fs/bpf/aya-xdp-test-lo").unwrap(); fd_link.pin("/sys/fs/bpf/aya-xdp-test-lo").unwrap();
// Unpin the program. It will stay attached since its links were pinned.
pinned.unpin().unwrap();
} }
// should still be loaded since link was pinned // should still be loaded since link was pinned
assert_loaded!("pass", true); assert_loaded!("pass", true);
// 2. Load a new version of the program, unpin link, and atomically replace old program // 3. Load a new version of the program, unpin link, and atomically replace old program
{ {
let mut bpf = Bpf::load(bytes).unwrap(); let mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); let prog: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();

Loading…
Cancel
Save