diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 3bd01c8b..48ad81c5 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -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), }) } } diff --git a/aya/src/programs/cgroup_sock.rs b/aya/src/programs/cgroup_sock.rs index 17bc68ee..0e62c2bd 100644 --- a/aya/src/programs/cgroup_sock.rs +++ b/aya/src/programs/cgroup_sock.rs @@ -54,13 +54,18 @@ use crate::{ #[doc(alias = "BPF_PROG_TYPE_CGROUP_SOCK")] pub struct CgroupSock { pub(crate) data: ProgramData, - pub(crate) attach_type: CgroupSockAttachType, + pub(crate) attach_type: Option, } 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) } diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 7f392f7a..df218f69 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -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>(path: P) -> Result { + 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 { + fs::remove_file(self.path.clone())?; + Ok(self.inner) + } +} + +impl AsRef for PinnedProgram { + fn as_ref(&self) -> &Program { + &self.inner + } +} + +impl AsMut for PinnedProgram { + fn as_mut(&mut self) -> &mut Program { + &mut self.inner + } +} + #[derive(Debug)] pub(crate) struct ProgramData { pub(crate) name: Option, - pub(crate) obj: obj::Program, + pub(crate) obj: Option, pub(crate) fd: Option, pub(crate) links: LinkMap, pub(crate) expected_attach_type: Option, @@ -418,7 +622,7 @@ impl ProgramData { ) -> ProgramData { ProgramData { name, - obj, + obj: Some(obj), fd: None, links: LinkMap::new(), expected_attach_type: None, @@ -429,6 +633,42 @@ impl ProgramData { verifier_log_level, } } + + pub(crate) fn from_bpf_prog_info( + name: Option, + fd: RawFd, + info: bpf_prog_info, + ) -> Result, 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 ProgramData { @@ -481,6 +721,11 @@ fn load_program( 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 { + self.0.type_.try_into() + } +} + +impl TryFrom for bpf_prog_type { + type Error = ProgramError; + + fn try_from(v: u32) -> Result { + 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_) + } } diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs index 37ff0e8f..f4b22c43 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/src/tests/load.rs @@ -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(()) }