From e2685c98d8ff976610efea019d23d2f584f577c2 Mon Sep 17 00:00:00 2001 From: Davide Bertola Date: Fri, 3 Jun 2022 09:42:33 +0200 Subject: [PATCH] Program unload API (#264) aya: add `program.unload()` API --- aya/src/programs/links.rs | 11 ++- aya/src/programs/mod.rs | 86 +++++++++++++++++++++ test/cases/010_load/030_unload/test.ebpf.rs | 26 +++++++ test/cases/010_load/030_unload/test.rs | 57 ++++++++++++++ test/cases/010_load/030_unload/test.sh | 27 +++++++ 5 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 test/cases/010_load/030_unload/test.ebpf.rs create mode 100755 test/cases/010_load/030_unload/test.rs create mode 100755 test/cases/010_load/030_unload/test.sh diff --git a/aya/src/programs/links.rs b/aya/src/programs/links.rs index d98b1a7e..b3ee0e06 100644 --- a/aya/src/programs/links.rs +++ b/aya/src/programs/links.rs @@ -79,6 +79,13 @@ impl LinkMap { .detach() } + pub(crate) fn remove_all(&mut self) -> Result<(), ProgramError> { + for (_, link) in self.links.drain() { + link.detach()?; + } + Ok(()) + } + pub(crate) fn forget(&mut self, link_id: T::Id) -> Result { self.links.remove(&link_id).ok_or(ProgramError::NotAttached) } @@ -86,9 +93,7 @@ impl LinkMap { impl Drop for LinkMap { fn drop(&mut self) { - for (_, link) in self.links.drain() { - let _ = link.detach(); - } + let _ = self.remove_all(); } } diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index a148a8e2..5cbd1565 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -386,6 +386,15 @@ impl ProgramData { } } +fn unload_program(data: &mut ProgramData) -> Result<(), ProgramError> { + data.links.remove_all()?; + let fd = data.fd.take().ok_or(ProgramError::NotLoaded)?; + unsafe { + libc::close(fd); + } + Ok(()) +} + fn load_program( prog_type: bpf_prog_type, data: &mut ProgramData, @@ -540,6 +549,83 @@ impl<'a, P: ProgramFd> ProgramFd for &'a P { } } +macro_rules! impl_program_unload { + ($($struct_name:ident),+ $(,)?) => { + $( + impl $struct_name { + /// Unloads the program from the kernel. + /// + /// Links will be detached before unloading the program. Note + /// that owned links obtained using `forget_link()` will not be + /// detached. + pub fn unload(&mut self) -> Result<(), ProgramError> { + unload_program(&mut self.data) + } + } + )+ + } +} + +impl_program_unload!( + KProbe, + UProbe, + TracePoint, + SocketFilter, + Xdp, + SkMsg, + SkSkb, + SchedClassifier, + CgroupSkb, + CgroupSysctl, + CgroupSockopt, + LircMode2, + PerfEvent, + Lsm, + RawTracePoint, + BtfTracePoint, + FEntry, + FExit, + Extension, + CgroupSockAddr, + SkLookup, + SockOps +); + +#[cfg(test)] +mod tests { + use super::Program; + + #[allow(dead_code)] + // When a new program is added, this fn will break as a reminder: consider adding unload() + // See [impl_program_unload!()] + fn program_implements_unload(a: Program) { + let _ = match a { + Program::KProbe(mut p) => p.unload(), + Program::UProbe(mut p) => p.unload(), + Program::TracePoint(mut p) => p.unload(), + Program::SocketFilter(mut p) => p.unload(), + Program::Xdp(mut p) => p.unload(), + Program::SkMsg(mut p) => p.unload(), + Program::SkSkb(mut p) => p.unload(), + Program::SockOps(mut p) => p.unload(), + Program::SchedClassifier(mut p) => p.unload(), + Program::CgroupSkb(mut p) => p.unload(), + Program::CgroupSysctl(mut p) => p.unload(), + Program::CgroupSockopt(mut p) => p.unload(), + Program::LircMode2(mut p) => p.unload(), + Program::PerfEvent(mut p) => p.unload(), + Program::RawTracePoint(mut p) => p.unload(), + Program::Lsm(mut p) => p.unload(), + Program::BtfTracePoint(mut p) => p.unload(), + Program::FEntry(mut p) => p.unload(), + Program::FExit(mut p) => p.unload(), + Program::Extension(mut p) => p.unload(), + Program::CgroupSockAddr(mut p) => p.unload(), + Program::SkLookup(mut p) => p.unload(), + }; + } +} + macro_rules! impl_program_fd { ($($struct_name:ident),+ $(,)?) => { $( diff --git a/test/cases/010_load/030_unload/test.ebpf.rs b/test/cases/010_load/030_unload/test.ebpf.rs new file mode 100644 index 00000000..a6ed58dd --- /dev/null +++ b/test/cases/010_load/030_unload/test.ebpf.rs @@ -0,0 +1,26 @@ +//! ```cargo +//! [dependencies] +//! aya-bpf = { path = "../../../../bpf/aya-bpf" } +//! ``` + +#![no_std] +#![no_main] + +use aya_bpf::{bindings::xdp_action, macros::xdp, programs::XdpContext}; + +#[xdp(name = "test_unload")] +pub fn pass(ctx: XdpContext) -> u32 { + match unsafe { try_pass(ctx) } { + Ok(ret) => ret, + Err(_) => xdp_action::XDP_ABORTED, + } +} + +unsafe fn try_pass(_ctx: XdpContext) -> Result { + Ok(xdp_action::XDP_PASS) +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { core::hint::unreachable_unchecked() } +} diff --git a/test/cases/010_load/030_unload/test.rs b/test/cases/010_load/030_unload/test.rs new file mode 100755 index 00000000..eb88ad5c --- /dev/null +++ b/test/cases/010_load/030_unload/test.rs @@ -0,0 +1,57 @@ +//! ```cargo +//! [dependencies] +//! aya = { path = "../../../../aya" } +//! ``` + +use aya::{ + programs::{Xdp, XdpFlags}, + Bpf, +}; +use std::convert::TryInto; +use std::process::Command; + +fn is_loaded() -> bool { + let output = Command::new("bpftool").args(&["prog"]).output().unwrap(); + let stdout = String::from_utf8(output.stdout).unwrap(); + stdout.contains("test_unload") +} + +fn assert_loaded(loaded: bool) { + let state = is_loaded(); + if state == loaded { + return; + } + panic!("Expected loaded: {} but was loaded: {}", loaded, state); +} + +fn main() { + println!("Loading XDP program"); + let mut bpf = Bpf::load_file("test.o").unwrap(); + let dispatcher: &mut Xdp = bpf.program_mut("test_unload").unwrap().try_into().unwrap(); + + dispatcher.load().unwrap(); + + let link = dispatcher.attach("eth0", XdpFlags::default()).unwrap(); + + { + let link_owned = dispatcher.forget_link(link); + + dispatcher.unload().unwrap(); + + assert_loaded(true); + }; + + assert_loaded(false); + + dispatcher.load().unwrap(); + + assert_loaded(true); + + dispatcher.attach("eth0", XdpFlags::default()).unwrap(); + + assert_loaded(true); + + dispatcher.unload().unwrap(); + + assert_loaded(false); +} diff --git a/test/cases/010_load/030_unload/test.sh b/test/cases/010_load/030_unload/test.sh new file mode 100755 index 00000000..efe20416 --- /dev/null +++ b/test/cases/010_load/030_unload/test.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# SUMMARY: Check that the program can be unloaded +# LABELS: + +set -ex + +# Source libraries. Uncomment if needed/defined +#. "${RT_LIB}" +. "${RT_PROJECT_ROOT}/_lib/lib.sh" + +NAME=test + +clean_up() { + rm -rf ebpf user ${NAME}.o ${NAME} + exec_vm rm ${NAME} ${NAME}.o +} + +trap clean_up EXIT + +# Test code goes here +compile_ebpf ${NAME}.ebpf.rs +compile_user ${NAME}.rs + +scp_vm ${NAME}.o +scp_vm ${NAME} + +exec_vm sudo ./${NAME} \ No newline at end of file