From 5750c3ef7a611ce2509adb230dc0f1db9efe2362 Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Thu, 8 Sep 2022 19:32:54 +0000 Subject: [PATCH] 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 --- aya/src/bpf.rs | 2 +- aya/src/programs/cgroup_sock.rs | 9 +- aya/src/programs/mod.rs | 307 +++++++++++++++++++++++- test/integration-test/src/tests/load.rs | 87 +++++-- 4 files changed, 375 insertions(+), 30 deletions(-) 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(()) }