|
|
|
@ -1,13 +1,13 @@
|
|
|
|
|
//! Program links.
|
|
|
|
|
use libc::{close, dup};
|
|
|
|
|
use thiserror::Error;
|
|
|
|
|
|
|
|
|
|
use std::{
|
|
|
|
|
borrow::Borrow,
|
|
|
|
|
collections::{hash_map::Entry, HashMap},
|
|
|
|
|
ffi::CString,
|
|
|
|
|
ops::Deref,
|
|
|
|
|
io,
|
|
|
|
|
os::unix::prelude::RawFd,
|
|
|
|
|
path::Path,
|
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
@ -25,37 +25,10 @@ pub trait Link: std::fmt::Debug + 'static {
|
|
|
|
|
/// Returns the link id
|
|
|
|
|
fn id(&self) -> Self::Id;
|
|
|
|
|
|
|
|
|
|
/// Detaches the Link
|
|
|
|
|
/// Detaches the LinkOwnedLink is gone... but this doesn't work :(
|
|
|
|
|
fn detach(self) -> Result<(), ProgramError>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// An owned link that automatically detaches the inner link when dropped.
|
|
|
|
|
pub struct OwnedLink<T: Link> {
|
|
|
|
|
inner: Option<T>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: Link> OwnedLink<T> {
|
|
|
|
|
pub(crate) fn new(inner: T) -> Self {
|
|
|
|
|
Self { inner: Some(inner) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: Link> Deref for OwnedLink<T> {
|
|
|
|
|
type Target = T;
|
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
self.inner.borrow().as_ref().unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: Link> Drop for OwnedLink<T> {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
if let Some(link) = self.inner.take() {
|
|
|
|
|
link.detach().unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub(crate) struct LinkMap<T: Link> {
|
|
|
|
|
links: HashMap<T::Id, T>,
|
|
|
|
@ -106,37 +79,63 @@ impl<T: Link> Drop for LinkMap<T> {
|
|
|
|
|
|
|
|
|
|
/// The identifier of an `FdLink`.
|
|
|
|
|
#[derive(Debug, Hash, Eq, PartialEq)]
|
|
|
|
|
pub struct FdLinkId(pub(crate) RawFd);
|
|
|
|
|
pub struct FdLinkId(pub(crate) Option<RawFd>);
|
|
|
|
|
|
|
|
|
|
/// A file descriptor link.
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct FdLink {
|
|
|
|
|
pub(crate) fd: RawFd,
|
|
|
|
|
pub(crate) fd: Option<RawFd>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FdLink {
|
|
|
|
|
pub(crate) fn new(fd: RawFd) -> FdLink {
|
|
|
|
|
FdLink { fd }
|
|
|
|
|
FdLink { fd: Some(fd) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pins the FdLink to a BPF filesystem.
|
|
|
|
|
/// Pins the link to a BPF file system.
|
|
|
|
|
///
|
|
|
|
|
/// When a BPF object is pinned to a BPF filesystem it will remain attached after
|
|
|
|
|
/// Aya has detached the link.
|
|
|
|
|
/// To remove the attachment, the file on the BPF filesystem must be removed.
|
|
|
|
|
/// Any directories in the the path provided should have been created by the caller.
|
|
|
|
|
pub fn pin<P: AsRef<Path>>(&self, path: P) -> Result<(), PinError> {
|
|
|
|
|
/// When a link is pinned it will remain attached even after the link instance is dropped,
|
|
|
|
|
/// and will only be detached once the pinned file is removed. To unpin, see [PinnedFd::unpin].
|
|
|
|
|
///
|
|
|
|
|
/// The parent directories in the provided path must already exist before calling this method,
|
|
|
|
|
/// and must be on a BPF file system (bpffs).
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # use aya::programs::{links::FdLink, Extension};
|
|
|
|
|
/// # use std::convert::TryInto;
|
|
|
|
|
/// # #[derive(thiserror::Error, Debug)]
|
|
|
|
|
/// # enum Error {
|
|
|
|
|
/// # #[error(transparent)]
|
|
|
|
|
/// # Bpf(#[from] aya::BpfError),
|
|
|
|
|
/// # #[error(transparent)]
|
|
|
|
|
/// # Pin(#[from] aya::pin::PinError),
|
|
|
|
|
/// # #[error(transparent)]
|
|
|
|
|
/// # Program(#[from] aya::programs::ProgramError)
|
|
|
|
|
/// # }
|
|
|
|
|
/// # let mut bpf = aya::Bpf::load(&[])?;
|
|
|
|
|
/// # let prog: &mut Extension = bpf.program_mut("example").unwrap().try_into()?;
|
|
|
|
|
/// let link_id = prog.attach()?;
|
|
|
|
|
/// let owned_link = prog.take_link(link_id)?;
|
|
|
|
|
/// let fd_link: FdLink = owned_link.into();
|
|
|
|
|
/// let pinned_link = fd_link.pin("/sys/fs/bpf/example")?;
|
|
|
|
|
/// # Ok::<(), Error>(())
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn pin<P: AsRef<Path>>(mut self, path: P) -> Result<PinnedLink, PinError> {
|
|
|
|
|
let fd = self.fd.take().ok_or_else(|| PinError::NoFd {
|
|
|
|
|
name: "link".to_string(),
|
|
|
|
|
})?;
|
|
|
|
|
let path_string =
|
|
|
|
|
CString::new(path.as_ref().to_string_lossy().into_owned()).map_err(|e| {
|
|
|
|
|
PinError::InvalidPinPath {
|
|
|
|
|
error: e.to_string(),
|
|
|
|
|
}
|
|
|
|
|
})?;
|
|
|
|
|
bpf_pin_object(self.fd, &path_string).map_err(|(_, io_error)| PinError::SyscallError {
|
|
|
|
|
bpf_pin_object(fd, &path_string).map_err(|(_, io_error)| PinError::SyscallError {
|
|
|
|
|
name: "BPF_OBJ_PIN".to_string(),
|
|
|
|
|
io_error,
|
|
|
|
|
})?;
|
|
|
|
|
Ok(())
|
|
|
|
|
Ok(PinnedLink::new(PathBuf::from(path.as_ref()), fd))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -148,11 +147,47 @@ impl Link for FdLink {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn detach(self) -> Result<(), ProgramError> {
|
|
|
|
|
unsafe { close(self.fd) };
|
|
|
|
|
// detach is a noop since it consumes self. once self is consumed,
|
|
|
|
|
// drop will be triggered and the link will be detached.
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for FdLink {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
if let Some(fd) = self.fd.take() {
|
|
|
|
|
// Safety: libc
|
|
|
|
|
unsafe { close(fd) };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A pinned file descriptor link.
|
|
|
|
|
///
|
|
|
|
|
/// This link has been pinned to the BPF filesystem. On drop, the file descriptor that backs
|
|
|
|
|
/// this link will be closed. Whether or not the program remains attached is dependent
|
|
|
|
|
/// on the presence of the file in BPFFS.
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct PinnedLink {
|
|
|
|
|
inner: FdLink,
|
|
|
|
|
path: PathBuf,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PinnedLink {
|
|
|
|
|
fn new(path: PathBuf, fd: RawFd) -> Self {
|
|
|
|
|
PinnedLink {
|
|
|
|
|
inner: FdLink::new(fd),
|
|
|
|
|
path,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Removes the pinned link from the filesystem and returns an [`FdLink`].
|
|
|
|
|
pub fn unpin(self) -> Result<FdLink, io::Error> {
|
|
|
|
|
std::fs::remove_file(self.path)?;
|
|
|
|
|
Ok(self.inner)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The identifier of a `ProgAttachLink`.
|
|
|
|
|
#[derive(Debug, Hash, Eq, PartialEq)]
|
|
|
|
|
pub struct ProgAttachLinkId(RawFd, RawFd, bpf_attach_type);
|
|
|
|
@ -220,18 +255,32 @@ macro_rules! define_link_wrapper {
|
|
|
|
|
$wrapper(b)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<$wrapper> for $base {
|
|
|
|
|
fn from(w: $wrapper) -> $base {
|
|
|
|
|
w.into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) use define_link_wrapper;
|
|
|
|
|
|
|
|
|
|
#[derive(Error, Debug)]
|
|
|
|
|
/// Errors from operations on links.
|
|
|
|
|
pub enum LinkError {
|
|
|
|
|
/// Invalid link.
|
|
|
|
|
#[error("Invalid link")]
|
|
|
|
|
InvalidLink,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use std::{cell::RefCell, rc::Rc};
|
|
|
|
|
use std::{cell::RefCell, env, fs::File, mem, os::unix::io::AsRawFd, rc::Rc};
|
|
|
|
|
|
|
|
|
|
use crate::programs::{OwnedLink, ProgramError};
|
|
|
|
|
use crate::{programs::ProgramError, sys::override_syscall};
|
|
|
|
|
|
|
|
|
|
use super::{Link, LinkMap};
|
|
|
|
|
use super::{FdLink, Link, LinkMap};
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Hash, Eq, PartialEq)]
|
|
|
|
|
struct TestLinkId(u8, u8);
|
|
|
|
@ -363,28 +412,23 @@ mod tests {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_owned_drop() {
|
|
|
|
|
let l1 = TestLink::new(1, 2);
|
|
|
|
|
let l1_detached = Rc::clone(&l1.detached);
|
|
|
|
|
let l2 = TestLink::new(1, 3);
|
|
|
|
|
let l2_detached = Rc::clone(&l2.detached);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let mut links = LinkMap::new();
|
|
|
|
|
let id1 = links.insert(l1).unwrap();
|
|
|
|
|
links.insert(l2).unwrap();
|
|
|
|
|
|
|
|
|
|
// manually forget one link and wrap in OwnedLink
|
|
|
|
|
let _ = OwnedLink {
|
|
|
|
|
inner: Some(links.forget(id1).unwrap()),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// OwnedLink was dropped in the statement above
|
|
|
|
|
assert!(*l1_detached.borrow() == 1);
|
|
|
|
|
assert!(*l2_detached.borrow() == 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
assert!(*l1_detached.borrow() == 1);
|
|
|
|
|
assert!(*l2_detached.borrow() == 1);
|
|
|
|
|
#[cfg_attr(miri, ignore)]
|
|
|
|
|
fn test_pin() {
|
|
|
|
|
let dir = env::temp_dir();
|
|
|
|
|
let f1 = File::create(dir.join("f1")).expect("unable to create file in tmpdir");
|
|
|
|
|
let fd_link = FdLink::new(f1.as_raw_fd());
|
|
|
|
|
|
|
|
|
|
// leak the fd, it will get closed when our pinned link is dropped
|
|
|
|
|
mem::forget(f1);
|
|
|
|
|
|
|
|
|
|
// override syscall to allow for pin to happen in our tmpdir
|
|
|
|
|
override_syscall(|_| Ok(0));
|
|
|
|
|
// create the file that would have happened as a side-effect of a real pin operation
|
|
|
|
|
File::create(dir.join("f1-pin")).expect("unable to create file in tmpdir");
|
|
|
|
|
assert!(dir.join("f1-pin").exists());
|
|
|
|
|
|
|
|
|
|
let pinned_link = fd_link.pin(dir.join("f1-pin")).expect("pin failed");
|
|
|
|
|
pinned_link.unpin().expect("unpin failed");
|
|
|
|
|
assert!(!dir.join("f1-pin").exists());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|