From 162847458d82d00f9841cff6d20a93f272316a1d 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 | 19 +- 4 files changed, 324 insertions(+), 13 deletions(-) diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index f5307f83..01092d25 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -630,7 +630,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 b88867f9..83df4c5c 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, programs::{ links::{FdLink, PinnedLink}, - TracePoint, Xdp, XdpFlags, + PinnedProgram, TracePoint, Xdp, XdpFlags, }, Bpf, }; @@ -140,16 +140,29 @@ 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 + { + 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 = 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. + pinned.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 + // 3. 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();