Skip to main content

profile_bee_aya/programs/
links.rs

1//! Program links.
2use std::{
3    ffi::CString,
4    io,
5    os::fd::{AsFd as _, AsRawFd as _, BorrowedFd, RawFd},
6    path::{Path, PathBuf},
7};
8
9use aya_obj::{
10    InvalidTypeBinding,
11    generated::{
12        BPF_F_AFTER, BPF_F_ALLOW_MULTI, BPF_F_ALLOW_OVERRIDE, BPF_F_BEFORE, BPF_F_ID, BPF_F_LINK,
13        BPF_F_REPLACE, bpf_attach_type, bpf_link_info, bpf_link_type,
14    },
15};
16use hashbrown::hash_set::{Entry, HashSet};
17use thiserror::Error;
18
19use crate::{
20    pin::PinError,
21    programs::{MultiProgLink, MultiProgram, ProgramError, ProgramFd, ProgramId},
22    sys::{
23        SyscallError, bpf_get_object, bpf_link_get_info_by_fd, bpf_pin_object, bpf_prog_attach,
24        bpf_prog_detach,
25    },
26};
27
28/// A Link.
29pub trait Link: std::fmt::Debug + Eq + std::hash::Hash + 'static {
30    /// Unique Id
31    type Id: std::fmt::Debug + Eq + std::hash::Hash + hashbrown::Equivalent<Self>;
32
33    /// Returns the link id
34    fn id(&self) -> Self::Id;
35
36    /// Detaches the `LinkOwnedLink` is gone... but this doesn't work :(
37    fn detach(self) -> Result<(), ProgramError>;
38}
39
40/// Program attachment mode.
41#[derive(Clone, Copy, Debug, Default)]
42pub enum CgroupAttachMode {
43    /// Allows only one BPF program in the cgroup subtree.
44    #[default]
45    Single,
46
47    /// Allows the program to be overridden by one in a sub-cgroup.
48    AllowOverride,
49
50    /// Allows multiple programs to be run in the cgroup subtree.
51    AllowMultiple,
52}
53
54impl From<CgroupAttachMode> for u32 {
55    fn from(mode: CgroupAttachMode) -> Self {
56        match mode {
57            CgroupAttachMode::Single => 0,
58            CgroupAttachMode::AllowOverride => BPF_F_ALLOW_OVERRIDE,
59            CgroupAttachMode::AllowMultiple => BPF_F_ALLOW_MULTI,
60        }
61    }
62}
63
64#[derive(Debug)]
65pub(crate) struct Links<T: Link> {
66    links: HashSet<T>,
67}
68
69impl<T> Links<T>
70where
71    T: Eq + std::hash::Hash + Link,
72    T::Id: hashbrown::Equivalent<T> + Eq + std::hash::Hash,
73{
74    pub(crate) fn new() -> Self {
75        Self {
76            links: Default::default(),
77        }
78    }
79
80    pub(crate) fn insert(&mut self, link: T) -> Result<T::Id, ProgramError> {
81        match self.links.entry(link) {
82            Entry::Occupied(_entry) => Err(ProgramError::AlreadyAttached),
83            Entry::Vacant(entry) => Ok(entry.insert().get().id()),
84        }
85    }
86
87    pub(crate) fn remove(&mut self, link_id: T::Id) -> Result<(), ProgramError> {
88        self.links
89            .take(&link_id)
90            .ok_or(ProgramError::NotAttached)?
91            .detach()
92    }
93
94    pub(crate) fn forget(&mut self, link_id: T::Id) -> Result<T, ProgramError> {
95        self.links.take(&link_id).ok_or(ProgramError::NotAttached)
96    }
97}
98
99impl<T: Link> Links<T> {
100    pub(crate) fn remove_all(&mut self) -> Result<(), ProgramError> {
101        for link in self.links.drain() {
102            link.detach()?;
103        }
104        Ok(())
105    }
106}
107
108impl<T: Link> Drop for Links<T> {
109    fn drop(&mut self) {
110        let _unused: Result<(), ProgramError> = self.remove_all();
111    }
112}
113
114/// Provides metadata information about an eBPF attachment.
115#[doc(alias = "bpf_link_info")]
116pub struct LinkInfo(bpf_link_info);
117
118impl LinkInfo {
119    pub(crate) fn new_from_fd(fd: BorrowedFd<'_>) -> Result<Self, LinkError> {
120        let info = bpf_link_get_info_by_fd(fd)?;
121        Ok(Self(info))
122    }
123
124    /// Returns the link ID.
125    pub const fn id(&self) -> u32 {
126        self.0.id
127    }
128
129    /// Returns the program ID.
130    pub const fn program_id(&self) -> u32 {
131        self.0.prog_id
132    }
133
134    /// Returns the type of the link.
135    pub fn link_type(&self) -> Result<LinkType, LinkError> {
136        bpf_link_type::try_from(self.0.type_)
137            .map_err(|InvalidTypeBinding { value }| LinkError::UnknownLinkType(value))
138            .and_then(LinkType::try_from)
139    }
140}
141
142/// The type of eBPF link.
143#[non_exhaustive]
144#[doc(alias = "bpf_link_type")]
145#[derive(Copy, Clone, Debug, PartialEq)]
146pub enum LinkType {
147    /// An unspecified link type.
148    #[doc(alias = "BPF_LINK_TYPE_UNSPEC")]
149    Unspecified = bpf_link_type::BPF_LINK_TYPE_UNSPEC as isize,
150    /// A Raw Tracepoint link type.
151    #[doc(alias = "BPF_LINK_TYPE_RAW_TRACEPOINT")]
152    RawTracePoint = bpf_link_type::BPF_LINK_TYPE_RAW_TRACEPOINT as isize,
153    /// A Tracing link type.
154    #[doc(alias = "BPF_LINK_TYPE_TRACING")]
155    Tracing = bpf_link_type::BPF_LINK_TYPE_TRACING as isize,
156    /// A Cgroup link type.
157    #[doc(alias = "BPF_LINK_TYPE_CGROUP")]
158    Cgroup = bpf_link_type::BPF_LINK_TYPE_CGROUP as isize,
159    /// An Iterator link type.
160    #[doc(alias = "BPF_LINK_TYPE_ITER")]
161    Iter = bpf_link_type::BPF_LINK_TYPE_ITER as isize,
162    /// A Network Namespace link type.
163    #[doc(alias = "BPF_LINK_TYPE_NETNS")]
164    Netns = bpf_link_type::BPF_LINK_TYPE_NETNS as isize,
165    /// An XDP link type.
166    #[doc(alias = "BPF_LINK_TYPE_XDP")]
167    Xdp = bpf_link_type::BPF_LINK_TYPE_XDP as isize,
168    /// A Perf Event link type.
169    #[doc(alias = "BPF_LINK_TYPE_PERF_EVENT")]
170    PerfEvent = bpf_link_type::BPF_LINK_TYPE_PERF_EVENT as isize,
171    /// A `KProbe` Multi link type.
172    #[doc(alias = "BPF_LINK_TYPE_KPROBE_MULTI")]
173    KProbeMulti = bpf_link_type::BPF_LINK_TYPE_KPROBE_MULTI as isize,
174    /// A `StructOps` link type.
175    #[doc(alias = "BPF_LINK_TYPE_STRUCT_OPS")]
176    StructOps = bpf_link_type::BPF_LINK_TYPE_STRUCT_OPS as isize,
177    /// A Netfilter link type.
178    #[doc(alias = "BPF_LINK_TYPE_NETFILTER")]
179    Netfilter = bpf_link_type::BPF_LINK_TYPE_NETFILTER as isize,
180    /// A Tcx link type.
181    #[doc(alias = "BPF_LINK_TYPE_TCX")]
182    Tcx = bpf_link_type::BPF_LINK_TYPE_TCX as isize,
183    /// A Uprobe Multi link type.
184    #[doc(alias = "BPF_LINK_TYPE_UPROBE_MULTI")]
185    UProbeMulti = bpf_link_type::BPF_LINK_TYPE_UPROBE_MULTI as isize,
186    /// A Netkit link type.
187    #[doc(alias = "BPF_LINK_TYPE_NETKIT")]
188    Netkit = bpf_link_type::BPF_LINK_TYPE_NETKIT as isize,
189}
190
191impl TryFrom<bpf_link_type> for LinkType {
192    type Error = LinkError;
193
194    fn try_from(link_type: bpf_link_type) -> Result<Self, Self::Error> {
195        match link_type {
196            bpf_link_type::BPF_LINK_TYPE_UNSPEC => Ok(Self::Unspecified),
197            bpf_link_type::BPF_LINK_TYPE_RAW_TRACEPOINT => Ok(Self::RawTracePoint),
198            bpf_link_type::BPF_LINK_TYPE_TRACING => Ok(Self::Tracing),
199            bpf_link_type::BPF_LINK_TYPE_CGROUP => Ok(Self::Cgroup),
200            bpf_link_type::BPF_LINK_TYPE_ITER => Ok(Self::Iter),
201            bpf_link_type::BPF_LINK_TYPE_NETNS => Ok(Self::Netns),
202            bpf_link_type::BPF_LINK_TYPE_XDP => Ok(Self::Xdp),
203            bpf_link_type::BPF_LINK_TYPE_PERF_EVENT => Ok(Self::PerfEvent),
204            bpf_link_type::BPF_LINK_TYPE_KPROBE_MULTI => Ok(Self::KProbeMulti),
205            bpf_link_type::BPF_LINK_TYPE_STRUCT_OPS => Ok(Self::StructOps),
206            bpf_link_type::BPF_LINK_TYPE_NETFILTER => Ok(Self::Netfilter),
207            bpf_link_type::BPF_LINK_TYPE_TCX => Ok(Self::Tcx),
208            bpf_link_type::BPF_LINK_TYPE_UPROBE_MULTI => Ok(Self::UProbeMulti),
209            bpf_link_type::BPF_LINK_TYPE_NETKIT => Ok(Self::Netkit),
210            bpf_link_type::__MAX_BPF_LINK_TYPE => Err(LinkError::UnknownLinkType(link_type as u32)),
211        }
212    }
213}
214
215/// The identifier of an `FdLink`.
216#[derive(Debug, Hash, Eq, PartialEq)]
217pub struct FdLinkId(pub(crate) RawFd);
218
219/// A file descriptor link.
220///
221/// Fd links are returned directly when attaching some program types (for
222/// instance [`crate::programs::cgroup_skb::CgroupSkb`]), or can be obtained by
223/// converting other link types (see the `TryFrom` implementations).
224///
225/// An important property of fd links is that they can be pinned. Pinning
226/// can be used keep a link attached "in background" even after the program
227/// that has created the link terminates.
228///
229/// # Example
230///
231/// ```no_run
232/// # let mut bpf = Ebpf::load_file("ebpf_programs.o")?;
233/// use aya::{Ebpf, programs::{links::FdLink, KProbe}};
234///
235/// let program: &mut KProbe = bpf.program_mut("intercept_wakeups").unwrap().try_into()?;
236/// program.load()?;
237/// let link_id = program.attach("try_to_wake_up", 0)?;
238/// let link = program.take_link(link_id).unwrap();
239/// let fd_link: FdLink = link.try_into().unwrap();
240/// fd_link.pin("/sys/fs/bpf/intercept_wakeups_link").unwrap();
241///
242/// # Ok::<(), aya::EbpfError>(())
243/// ```
244#[derive(Debug)]
245pub struct FdLink {
246    pub(crate) fd: crate::MockableFd,
247}
248
249impl FdLink {
250    pub(crate) const fn new(fd: crate::MockableFd) -> Self {
251        Self { fd }
252    }
253
254    /// Pins the link to a BPF file system.
255    ///
256    /// When a link is pinned it will remain attached even after the link instance is dropped,
257    /// and will only be detached once the pinned file is removed. To unpin, see [`PinnedLink::unpin()`].
258    ///
259    /// The parent directories in the provided path must already exist before calling this method,
260    /// and must be on a BPF file system (bpffs).
261    ///
262    /// # Example
263    /// ```no_run
264    /// # use aya::programs::{links::FdLink, Extension};
265    /// # use std::convert::TryInto;
266    /// # #[derive(thiserror::Error, Debug)]
267    /// # enum Error {
268    /// #     #[error(transparent)]
269    /// #     Ebpf(#[from] aya::EbpfError),
270    /// #     #[error(transparent)]
271    /// #     Pin(#[from] aya::pin::PinError),
272    /// #     #[error(transparent)]
273    /// #     Program(#[from] aya::programs::ProgramError)
274    /// # }
275    /// # let mut bpf = aya::Ebpf::load(&[])?;
276    /// # let prog: &mut Extension = bpf.program_mut("example").unwrap().try_into()?;
277    /// let link_id = prog.attach()?;
278    /// let owned_link = prog.take_link(link_id)?;
279    /// let fd_link: FdLink = owned_link.into();
280    /// let pinned_link = fd_link.pin("/sys/fs/bpf/example")?;
281    /// # Ok::<(), Error>(())
282    /// ```
283    pub fn pin<P: AsRef<Path>>(self, path: P) -> Result<PinnedLink, PinError> {
284        use std::os::unix::ffi::OsStrExt as _;
285
286        let path = path.as_ref();
287        let path_string = CString::new(path.as_os_str().as_bytes()).map_err(|error| {
288            PinError::InvalidPinPath {
289                path: path.into(),
290                error,
291            }
292        })?;
293        bpf_pin_object(self.fd.as_fd(), &path_string).map_err(|io_error| SyscallError {
294            call: "BPF_OBJ_PIN",
295            io_error,
296        })?;
297        Ok(PinnedLink::new(path.into(), self))
298    }
299
300    /// Returns the kernel information about this link.
301    pub fn info(&self) -> Result<LinkInfo, LinkError> {
302        LinkInfo::new_from_fd(self.fd.as_fd())
303    }
304}
305
306impl Link for FdLink {
307    type Id = FdLinkId;
308
309    fn id(&self) -> Self::Id {
310        FdLinkId(self.fd.as_raw_fd())
311    }
312
313    fn detach(self) -> Result<(), ProgramError> {
314        // detach is a noop since it consumes self. once self is consumed, drop will be triggered
315        // and the link will be detached.
316        //
317        // Other links don't need to do this since they use define_link_wrapper!, but FdLink is a
318        // bit special in that it defines a custom ::new() so it can't use the macro.
319        Ok(())
320    }
321}
322
323id_as_key!(FdLink, FdLinkId);
324
325impl From<PinnedLink> for FdLink {
326    fn from(p: PinnedLink) -> Self {
327        p.inner
328    }
329}
330
331/// A pinned file descriptor link.
332///
333/// This link has been pinned to the BPF filesystem. On drop, the file descriptor that backs
334/// this link will be closed. Whether or not the program remains attached is dependent
335/// on the presence of the file in BPFFS.
336#[derive(Debug)]
337pub struct PinnedLink {
338    inner: FdLink,
339    path: PathBuf,
340}
341
342impl PinnedLink {
343    const fn new(path: PathBuf, link: FdLink) -> Self {
344        Self { inner: link, path }
345    }
346
347    /// Creates a [`crate::programs::links::PinnedLink`] from a valid path on bpffs.
348    pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, LinkError> {
349        use std::os::unix::ffi::OsStrExt as _;
350
351        // TODO: avoid this unwrap by adding a new error variant.
352        let path_string = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap();
353        let fd = bpf_get_object(&path_string).map_err(|io_error| {
354            LinkError::SyscallError(SyscallError {
355                call: "BPF_OBJ_GET",
356                io_error,
357            })
358        })?;
359        Ok(Self::new(path.as_ref().to_path_buf(), FdLink::new(fd)))
360    }
361
362    /// Removes the pinned link from the filesystem and returns an [`FdLink`].
363    pub fn unpin(self) -> Result<FdLink, io::Error> {
364        std::fs::remove_file(self.path)?;
365        Ok(self.inner)
366    }
367}
368
369/// The identifier of a `ProgAttachLink`.
370#[derive(Debug, Hash, Eq, PartialEq)]
371pub struct ProgAttachLinkId(RawFd, RawFd, bpf_attach_type);
372
373/// The Link type used by programs that are attached with `bpf_prog_attach`.
374#[derive(Debug)]
375pub struct ProgAttachLink {
376    prog_fd: ProgramFd,
377    target_fd: crate::MockableFd,
378    attach_type: bpf_attach_type,
379}
380
381impl ProgAttachLink {
382    pub(crate) const fn new(
383        prog_fd: ProgramFd,
384        target_fd: crate::MockableFd,
385        attach_type: bpf_attach_type,
386    ) -> Self {
387        Self {
388            prog_fd,
389            target_fd,
390            attach_type,
391        }
392    }
393
394    pub(crate) fn attach(
395        prog_fd: BorrowedFd<'_>,
396        target_fd: BorrowedFd<'_>,
397        attach_type: bpf_attach_type,
398        mode: CgroupAttachMode,
399    ) -> Result<Self, ProgramError> {
400        // The link is going to own this new file descriptor so we are
401        // going to need a duplicate whose lifetime we manage. Let's
402        // duplicate it prior to attaching it so the new file
403        // descriptor is closed at drop in case it fails to attach.
404        let prog_fd = prog_fd.try_clone_to_owned()?;
405        let prog_fd = crate::MockableFd::from_fd(prog_fd);
406        let target_fd = target_fd.try_clone_to_owned()?;
407        let target_fd = crate::MockableFd::from_fd(target_fd);
408        bpf_prog_attach(prog_fd.as_fd(), target_fd.as_fd(), attach_type, mode.into())?;
409
410        let prog_fd = ProgramFd(prog_fd);
411        Ok(Self {
412            prog_fd,
413            target_fd,
414            attach_type,
415        })
416    }
417}
418
419impl Link for ProgAttachLink {
420    type Id = ProgAttachLinkId;
421
422    fn id(&self) -> Self::Id {
423        ProgAttachLinkId(
424            self.prog_fd.as_fd().as_raw_fd(),
425            self.target_fd.as_raw_fd(),
426            self.attach_type,
427        )
428    }
429
430    fn detach(self) -> Result<(), ProgramError> {
431        bpf_prog_detach(
432            self.prog_fd.as_fd(),
433            self.target_fd.as_fd(),
434            self.attach_type,
435        )
436        .map_err(Into::into)
437    }
438}
439
440id_as_key!(ProgAttachLink, ProgAttachLinkId);
441
442macro_rules! id_as_key {
443    ($wrapper:ident, $wrapper_id:ident) => {
444        impl PartialEq for $wrapper {
445            fn eq(&self, other: &Self) -> bool {
446                use $crate::programs::links::Link as _;
447
448                self.id() == other.id()
449            }
450        }
451
452        impl Eq for $wrapper {}
453
454        impl std::hash::Hash for $wrapper {
455            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
456                use $crate::programs::links::Link as _;
457
458                self.id().hash(state)
459            }
460        }
461
462        impl hashbrown::Equivalent<$wrapper> for $wrapper_id {
463            fn equivalent(&self, key: &$wrapper) -> bool {
464                use $crate::programs::links::Link as _;
465
466                *self == key.id()
467            }
468        }
469    };
470}
471
472pub(crate) use id_as_key;
473
474macro_rules! define_link_wrapper {
475    ($wrapper:ident, $wrapper_id:ident, $base:ident, $base_id:ident, $program:ident $(,)?) => {
476        /// The type returned by
477        #[doc = concat!("[`", stringify!($program), "::attach`]")]
478        /// . Can be passed to
479        #[doc = concat!("[`", stringify!($program), "::detach`]")]
480        /// .
481        #[derive(Debug, Hash, Eq, PartialEq)]
482        pub struct $wrapper_id($base_id);
483
484        /// The link used by
485        #[doc = concat!("[`", stringify!($program), "`]")]
486        /// programs.
487        #[derive(Debug)]
488        pub struct $wrapper(Option<$base>);
489
490        #[expect(clippy::allow_attributes, reason = "macro")]
491        #[allow(dead_code, reason = "macro")]
492        // allow dead code since currently XDP/TC are the only consumers of inner and
493        // into_inner
494        impl $wrapper {
495            const fn new(base: $base) -> $wrapper {
496                $wrapper(Some(base))
497            }
498
499            const fn inner(&self) -> &$base {
500                self.0.as_ref().unwrap()
501            }
502
503            fn into_inner(mut self) -> $base {
504                self.0.take().unwrap()
505            }
506        }
507
508        impl Drop for $wrapper {
509            fn drop(&mut self) {
510                use $crate::programs::links::Link as _;
511
512                if let Some(base) = self.0.take() {
513                    let _unused: Result<(), ProgramError> = base.detach();
514                }
515            }
516        }
517
518        impl $crate::programs::Link for $wrapper {
519            type Id = $wrapper_id;
520
521            fn id(&self) -> Self::Id {
522                $wrapper_id(self.0.as_ref().unwrap().id())
523            }
524
525            fn detach(mut self) -> Result<(), ProgramError> {
526                self.0.take().unwrap().detach()
527            }
528        }
529
530        $crate::programs::links::id_as_key!($wrapper, $wrapper_id);
531
532        impl From<$base> for $wrapper {
533            fn from(b: $base) -> $wrapper {
534                $wrapper(Some(b))
535            }
536        }
537
538        impl From<$wrapper> for $base {
539            fn from(mut w: $wrapper) -> $base {
540                w.0.take().unwrap()
541            }
542        }
543
544        impl $program {
545            /// Detaches the program.
546            ///
547            /// See [`Self::attach`].
548            pub fn detach(&mut self, link_id: $wrapper_id) -> Result<(), ProgramError> {
549                self.data.links.remove(link_id)
550            }
551
552            /// Takes ownership of the link referenced by the provided `link_id`.
553            ///
554            /// The caller takes the responsibility of managing the lifetime of the link. When the
555            /// returned
556            #[doc = concat!("[`", stringify!($wrapper), "`]")]
557            /// is dropped, the link will be detached.
558            pub fn take_link(&mut self, link_id: $wrapper_id) -> Result<$wrapper, ProgramError> {
559                self.data.links.forget(link_id)
560            }
561        }
562    };
563}
564
565pub(crate) use define_link_wrapper;
566
567macro_rules! impl_try_into_fdlink {
568    ($wrapper:ident, $inner:ident) => {
569        impl TryFrom<$wrapper> for $crate::programs::FdLink {
570            type Error = $crate::programs::LinkError;
571
572            fn try_from(value: $wrapper) -> Result<Self, Self::Error> {
573                if let $inner::Fd(fd) = value.into_inner() {
574                    Ok(fd)
575                } else {
576                    Err($crate::programs::LinkError::InvalidLink)
577                }
578            }
579        }
580    };
581}
582
583pub(crate) use impl_try_into_fdlink;
584
585#[derive(Error, Debug)]
586/// Errors from operations on links.
587pub enum LinkError {
588    /// Invalid link.
589    #[error("Invalid link")]
590    InvalidLink,
591
592    /// The kernel type of this link is not understood by Aya.
593    /// Please open an issue on GitHub if you encounter this error.
594    #[error("unknown link type {0}")]
595    UnknownLinkType(u32),
596
597    /// Syscall failed.
598    #[error(transparent)]
599    SyscallError(#[from] SyscallError),
600}
601
602#[derive(Debug)]
603pub(crate) enum LinkRef {
604    Id(u32),
605    Fd(RawFd),
606}
607
608bitflags::bitflags! {
609    /// Flags which are use to build a set of MprogOptions.
610    #[derive(Clone, Copy, Debug, Default)]
611    pub(crate) struct MprogFlags: u32 {
612        const REPLACE = BPF_F_REPLACE;
613        const BEFORE = BPF_F_BEFORE;
614        const AFTER = BPF_F_AFTER;
615        const ID = BPF_F_ID;
616        const LINK = BPF_F_LINK;
617    }
618}
619
620/// Arguments required for interacting with the kernel's multi-prog API.
621///
622/// # Minimum kernel version
623///
624/// The minimum kernel version required to use this feature is 6.6.0.
625///
626/// # Example
627///
628/// ```no_run
629/// # let mut bpf = aya::Ebpf::load(&[])?;
630/// use aya::programs::{tc, SchedClassifier, TcAttachType, tc::TcAttachOptions, LinkOrder};
631///
632/// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?;
633/// prog.load()?;
634/// let options = TcAttachOptions::TcxOrder(LinkOrder::first());
635/// prog.attach_with_options("eth0", TcAttachType::Ingress, options)?;
636///
637/// # Ok::<(), aya::EbpfError>(())
638/// ```
639#[derive(Debug)]
640pub struct LinkOrder {
641    pub(crate) link_ref: LinkRef,
642    pub(crate) flags: MprogFlags,
643}
644
645/// Ensure that default link ordering is to be attached last.
646impl Default for LinkOrder {
647    fn default() -> Self {
648        Self {
649            link_ref: LinkRef::Fd(0),
650            flags: MprogFlags::AFTER,
651        }
652    }
653}
654
655impl LinkOrder {
656    /// Attach before all other links.
657    pub const fn first() -> Self {
658        Self {
659            link_ref: LinkRef::Id(0),
660            flags: MprogFlags::BEFORE,
661        }
662    }
663
664    /// Attach after all other links.
665    pub const fn last() -> Self {
666        Self {
667            link_ref: LinkRef::Id(0),
668            flags: MprogFlags::AFTER,
669        }
670    }
671
672    /// Attach before the given link.
673    pub fn before_link<L: MultiProgLink>(link: &L) -> Result<Self, LinkError> {
674        Ok(Self {
675            link_ref: LinkRef::Fd(link.fd()?.as_raw_fd()),
676            flags: MprogFlags::BEFORE | MprogFlags::LINK,
677        })
678    }
679
680    /// Attach after the given link.
681    pub fn after_link<L: MultiProgLink>(link: &L) -> Result<Self, LinkError> {
682        Ok(Self {
683            link_ref: LinkRef::Fd(link.fd()?.as_raw_fd()),
684            flags: MprogFlags::AFTER | MprogFlags::LINK,
685        })
686    }
687
688    /// Attach before the given program.
689    pub fn before_program<P: MultiProgram>(program: &P) -> Result<Self, ProgramError> {
690        Ok(Self {
691            link_ref: LinkRef::Fd(program.fd()?.as_raw_fd()),
692            flags: MprogFlags::BEFORE,
693        })
694    }
695
696    /// Attach after the given program.
697    pub fn after_program<P: MultiProgram>(program: &P) -> Result<Self, ProgramError> {
698        Ok(Self {
699            link_ref: LinkRef::Fd(program.fd()?.as_raw_fd()),
700            flags: MprogFlags::AFTER,
701        })
702    }
703
704    /// Attach before the program with the given id.
705    pub fn before_program_id(id: ProgramId) -> Self {
706        Self {
707            link_ref: LinkRef::Id(id.0),
708            flags: MprogFlags::BEFORE | MprogFlags::ID,
709        }
710    }
711
712    /// Attach after the program with the given id.
713    pub fn after_program_id(id: ProgramId) -> Self {
714        Self {
715            link_ref: LinkRef::Id(id.0),
716            flags: MprogFlags::AFTER | MprogFlags::ID,
717        }
718    }
719}
720
721#[cfg(test)]
722mod tests {
723    use std::{cell::RefCell, fs::File, rc::Rc};
724
725    use assert_matches::assert_matches;
726    use aya_obj::generated::{BPF_F_ALLOW_MULTI, BPF_F_ALLOW_OVERRIDE};
727    use tempfile::tempdir;
728
729    use super::{FdLink, Link, Links};
730    use crate::{
731        programs::{CgroupAttachMode, ProgramError},
732        sys::override_syscall,
733    };
734
735    #[derive(Debug, Hash, Eq, PartialEq)]
736    struct TestLinkId(u8, u8);
737
738    #[derive(Debug)]
739    struct TestLink {
740        id: (u8, u8),
741        detached: Rc<RefCell<u8>>,
742    }
743
744    impl TestLink {
745        fn new(a: u8, b: u8) -> Self {
746            Self {
747                id: (a, b),
748                detached: Rc::new(RefCell::new(0)),
749            }
750        }
751    }
752
753    impl Link for TestLink {
754        type Id = TestLinkId;
755
756        fn id(&self) -> Self::Id {
757            TestLinkId(self.id.0, self.id.1)
758        }
759
760        fn detach(self) -> Result<(), ProgramError> {
761            *self.detached.borrow_mut() += 1;
762            Ok(())
763        }
764    }
765
766    id_as_key!(TestLink, TestLinkId);
767
768    #[test]
769    fn test_link_map() {
770        let mut links = Links::new();
771        let l1 = TestLink::new(1, 2);
772        let l1_detached = Rc::clone(&l1.detached);
773        let l2 = TestLink::new(1, 3);
774        let l2_detached = Rc::clone(&l2.detached);
775
776        let id1 = links.insert(l1).unwrap();
777        let id2 = links.insert(l2).unwrap();
778
779        assert_eq!(*l1_detached.borrow(), 0);
780        assert_eq!(*l2_detached.borrow(), 0);
781
782        links.remove(id1).unwrap();
783        assert_eq!(*l1_detached.borrow(), 1);
784        assert_eq!(*l2_detached.borrow(), 0);
785
786        links.remove(id2).unwrap();
787        assert_eq!(*l1_detached.borrow(), 1);
788        assert_eq!(*l2_detached.borrow(), 1);
789    }
790
791    #[test]
792    fn test_already_attached() {
793        let mut links = Links::new();
794
795        links.insert(TestLink::new(1, 2)).unwrap();
796        assert_matches!(
797            links.insert(TestLink::new(1, 2)),
798            Err(ProgramError::AlreadyAttached)
799        );
800    }
801
802    #[test]
803    fn test_not_attached() {
804        let mut links = Links::new();
805
806        let l1 = TestLink::new(1, 2);
807        let l1_id1 = l1.id();
808        let l1_id2 = l1.id();
809        links.insert(TestLink::new(1, 2)).unwrap();
810        links.remove(l1_id1).unwrap();
811        assert_matches!(links.remove(l1_id2), Err(ProgramError::NotAttached));
812    }
813
814    #[test]
815    fn test_drop_detach() {
816        let l1 = TestLink::new(1, 2);
817        let l1_detached = Rc::clone(&l1.detached);
818        let l2 = TestLink::new(1, 3);
819        let l2_detached = Rc::clone(&l2.detached);
820
821        {
822            let mut links = Links::new();
823            let id1 = links.insert(l1).unwrap();
824            links.insert(l2).unwrap();
825            // manually remove one link
826            links.remove(id1).unwrap();
827            assert_eq!(*l1_detached.borrow(), 1);
828            assert_eq!(*l2_detached.borrow(), 0);
829        }
830        // remove the other on drop
831        assert_eq!(*l1_detached.borrow(), 1);
832        assert_eq!(*l2_detached.borrow(), 1);
833    }
834
835    #[test]
836    fn test_owned_detach() {
837        let l1 = TestLink::new(1, 2);
838        let l1_detached = Rc::clone(&l1.detached);
839        let l2 = TestLink::new(1, 3);
840        let l2_detached = Rc::clone(&l2.detached);
841
842        let owned_l1 = {
843            let mut links = Links::new();
844            let id1 = links.insert(l1).unwrap();
845            links.insert(l2).unwrap();
846            // manually forget one link
847            let owned_l1 = links.forget(id1);
848            assert_eq!(*l1_detached.borrow(), 0);
849            assert_eq!(*l2_detached.borrow(), 0);
850            owned_l1.unwrap()
851        };
852
853        // l2 is detached on `Drop`, but l1 is still alive
854        assert_eq!(*l1_detached.borrow(), 0);
855        assert_eq!(*l2_detached.borrow(), 1);
856
857        // manually detach l1
858        owned_l1.detach().unwrap();
859        assert_eq!(*l1_detached.borrow(), 1);
860        assert_eq!(*l2_detached.borrow(), 1);
861    }
862
863    #[test]
864    #[cfg_attr(miri, ignore = "`mkdir` not available when isolation is enabled")]
865    fn test_pin() {
866        let dir = tempdir().unwrap();
867        let f1 = File::create(dir.path().join("f1")).expect("unable to create file in tmpdir");
868        let fd_link = FdLink::new(f1.into());
869
870        // override syscall to allow for pin to happen in our tmpdir
871        override_syscall(|_| Ok(0));
872        // create the file that would have happened as a side-effect of a real pin operation
873        let pin = dir.path().join("f1-pin");
874        File::create(&pin).expect("unable to create file in tmpdir");
875        assert!(pin.exists());
876
877        let pinned_link = fd_link.pin(&pin).expect("pin failed");
878        pinned_link.unpin().expect("unpin failed");
879        assert!(!pin.exists());
880    }
881
882    #[test]
883    fn test_cgroup_attach_flag() {
884        assert_eq!(u32::from(CgroupAttachMode::Single), 0);
885        assert_eq!(
886            u32::from(CgroupAttachMode::AllowOverride),
887            BPF_F_ALLOW_OVERRIDE
888        );
889        assert_eq!(
890            u32::from(CgroupAttachMode::AllowMultiple),
891            BPF_F_ALLOW_MULTI
892        );
893    }
894}