Skip to main content

aya_friday/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: impl Into<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        let attach_type = attach_type.into();
409        bpf_prog_attach(prog_fd.as_fd(), target_fd.as_fd(), attach_type, mode.into())?;
410
411        let prog_fd = ProgramFd(prog_fd);
412        Ok(Self {
413            prog_fd,
414            target_fd,
415            attach_type,
416        })
417    }
418}
419
420impl Link for ProgAttachLink {
421    type Id = ProgAttachLinkId;
422
423    fn id(&self) -> Self::Id {
424        ProgAttachLinkId(
425            self.prog_fd.as_fd().as_raw_fd(),
426            self.target_fd.as_raw_fd(),
427            self.attach_type,
428        )
429    }
430
431    fn detach(self) -> Result<(), ProgramError> {
432        bpf_prog_detach(
433            self.prog_fd.as_fd(),
434            self.target_fd.as_fd(),
435            self.attach_type,
436        )
437        .map_err(Into::into)
438    }
439}
440
441id_as_key!(ProgAttachLink, ProgAttachLinkId);
442
443macro_rules! id_as_key {
444    ($wrapper:ident, $wrapper_id:ident) => {
445        impl PartialEq for $wrapper {
446            fn eq(&self, other: &Self) -> bool {
447                use $crate::programs::links::Link as _;
448
449                self.id() == other.id()
450            }
451        }
452
453        impl Eq for $wrapper {}
454
455        impl std::hash::Hash for $wrapper {
456            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
457                use $crate::programs::links::Link as _;
458
459                self.id().hash(state)
460            }
461        }
462
463        impl hashbrown::Equivalent<$wrapper> for $wrapper_id {
464            fn equivalent(&self, key: &$wrapper) -> bool {
465                use $crate::programs::links::Link as _;
466
467                *self == key.id()
468            }
469        }
470    };
471}
472
473pub(crate) use id_as_key;
474
475macro_rules! define_link_wrapper {
476    ($wrapper:ident, $wrapper_id:ident, $base:ident, $base_id:ident, $program:ident $(,)?) => {
477        /// The type returned by
478        #[doc = concat!("[`", stringify!($program), "::attach`]")]
479        /// . Can be passed to
480        #[doc = concat!("[`", stringify!($program), "::detach`]")]
481        /// .
482        #[derive(Debug, Hash, Eq, PartialEq)]
483        pub struct $wrapper_id($base_id);
484
485        /// The link used by
486        #[doc = concat!("[`", stringify!($program), "`]")]
487        /// programs.
488        #[derive(Debug)]
489        pub struct $wrapper(Option<$base>);
490
491        #[expect(clippy::allow_attributes, reason = "macro")]
492        #[allow(dead_code, reason = "macro")]
493        // allow dead code since currently XDP/TC are the only consumers of inner and
494        // into_inner
495        impl $wrapper {
496            const fn new(base: $base) -> $wrapper {
497                $wrapper(Some(base))
498            }
499
500            const fn inner(&self) -> &$base {
501                self.0.as_ref().unwrap()
502            }
503
504            fn into_inner(mut self) -> $base {
505                self.0.take().unwrap()
506            }
507        }
508
509        impl Drop for $wrapper {
510            fn drop(&mut self) {
511                use $crate::programs::links::Link as _;
512
513                if let Some(base) = self.0.take() {
514                    let _unused: Result<(), ProgramError> = base.detach();
515                }
516            }
517        }
518
519        impl $crate::programs::Link for $wrapper {
520            type Id = $wrapper_id;
521
522            fn id(&self) -> Self::Id {
523                $wrapper_id(self.0.as_ref().unwrap().id())
524            }
525
526            fn detach(mut self) -> Result<(), ProgramError> {
527                self.0.take().unwrap().detach()
528            }
529        }
530
531        $crate::programs::links::id_as_key!($wrapper, $wrapper_id);
532
533        impl From<$base> for $wrapper {
534            fn from(b: $base) -> $wrapper {
535                $wrapper(Some(b))
536            }
537        }
538
539        impl From<$wrapper> for $base {
540            fn from(mut w: $wrapper) -> $base {
541                w.0.take().unwrap()
542            }
543        }
544
545        impl $program {
546            /// Detaches the program.
547            ///
548            /// See [`Self::attach`].
549            pub fn detach(&mut self, link_id: $wrapper_id) -> Result<(), ProgramError> {
550                self.data.links.remove(link_id)
551            }
552
553            /// Takes ownership of the link referenced by the provided `link_id`.
554            ///
555            /// The caller takes the responsibility of managing the lifetime of the link. When the
556            /// returned
557            #[doc = concat!("[`", stringify!($wrapper), "`]")]
558            /// is dropped, the link will be detached.
559            pub fn take_link(&mut self, link_id: $wrapper_id) -> Result<$wrapper, ProgramError> {
560                self.data.links.forget(link_id)
561            }
562        }
563    };
564}
565
566pub(crate) use define_link_wrapper;
567
568macro_rules! impl_try_into_fdlink {
569    ($wrapper:ident, $inner:ident) => {
570        impl TryFrom<$wrapper> for $crate::programs::FdLink {
571            type Error = $crate::programs::LinkError;
572
573            fn try_from(value: $wrapper) -> Result<Self, Self::Error> {
574                if let $inner::Fd(fd) = value.into_inner() {
575                    Ok(fd)
576                } else {
577                    Err($crate::programs::LinkError::InvalidLink)
578                }
579            }
580        }
581    };
582}
583
584pub(crate) use impl_try_into_fdlink;
585
586macro_rules! impl_try_from_fdlink {
587    ($wrapper:ident, $inner:ident, $link_type:expr) => {
588        impl TryFrom<$crate::programs::FdLink> for $wrapper {
589            type Error = $crate::programs::LinkError;
590
591            fn try_from(fd_link: $crate::programs::FdLink) -> Result<Self, Self::Error> {
592                use std::os::fd::AsFd as _;
593
594                let info = $crate::sys::bpf_link_get_info_by_fd(fd_link.fd.as_fd())?;
595                if info.type_ == ($link_type as u32) {
596                    return Ok(Self::new($inner::Fd(fd_link)));
597                }
598                Err($crate::programs::LinkError::InvalidLink)
599            }
600        }
601    };
602}
603
604pub(crate) use impl_try_from_fdlink;
605
606#[derive(Error, Debug)]
607/// Errors from operations on links.
608pub enum LinkError {
609    /// Invalid link.
610    #[error("Invalid link")]
611    InvalidLink,
612
613    /// The kernel type of this link is not understood by Aya.
614    /// Please open an issue on GitHub if you encounter this error.
615    #[error("unknown link type {0}")]
616    UnknownLinkType(u32),
617
618    /// Syscall failed.
619    #[error(transparent)]
620    SyscallError(#[from] SyscallError),
621}
622
623#[derive(Debug)]
624pub(crate) enum LinkRef {
625    Id(u32),
626    Fd(RawFd),
627}
628
629bitflags::bitflags! {
630    /// Flags which are use to build a set of MprogOptions.
631    #[derive(Clone, Copy, Debug, Default)]
632    pub(crate) struct MprogFlags: u32 {
633        const REPLACE = BPF_F_REPLACE;
634        const BEFORE = BPF_F_BEFORE;
635        const AFTER = BPF_F_AFTER;
636        const ID = BPF_F_ID;
637        const LINK = BPF_F_LINK;
638    }
639}
640
641/// Arguments required for interacting with the kernel's multi-prog API.
642///
643/// # Minimum kernel version
644///
645/// The minimum kernel version required to use this feature is 6.6.0.
646///
647/// # Example
648///
649/// ```no_run
650/// # let mut bpf = aya::Ebpf::load(&[])?;
651/// use aya::programs::{tc, SchedClassifier, TcAttachType, tc::TcAttachOptions, LinkOrder};
652///
653/// let prog: &mut SchedClassifier = bpf.program_mut("redirect_ingress").unwrap().try_into()?;
654/// prog.load()?;
655/// let options = TcAttachOptions::TcxOrder(LinkOrder::first());
656/// prog.attach_with_options("eth0", TcAttachType::Ingress, options)?;
657///
658/// # Ok::<(), aya::EbpfError>(())
659/// ```
660#[derive(Debug)]
661pub struct LinkOrder {
662    pub(crate) link_ref: LinkRef,
663    pub(crate) flags: MprogFlags,
664}
665
666/// Ensure that default link ordering is to be attached last.
667impl Default for LinkOrder {
668    fn default() -> Self {
669        Self {
670            link_ref: LinkRef::Fd(0),
671            flags: MprogFlags::AFTER,
672        }
673    }
674}
675
676impl LinkOrder {
677    /// Attach before all other links.
678    pub const fn first() -> Self {
679        Self {
680            link_ref: LinkRef::Id(0),
681            flags: MprogFlags::BEFORE,
682        }
683    }
684
685    /// Attach after all other links.
686    pub const fn last() -> Self {
687        Self {
688            link_ref: LinkRef::Id(0),
689            flags: MprogFlags::AFTER,
690        }
691    }
692
693    /// Attach before the given link.
694    pub fn before_link<L: MultiProgLink>(link: &L) -> Result<Self, LinkError> {
695        Ok(Self {
696            link_ref: LinkRef::Fd(link.fd()?.as_raw_fd()),
697            flags: MprogFlags::BEFORE | MprogFlags::LINK,
698        })
699    }
700
701    /// Attach after the given link.
702    pub fn after_link<L: MultiProgLink>(link: &L) -> Result<Self, LinkError> {
703        Ok(Self {
704            link_ref: LinkRef::Fd(link.fd()?.as_raw_fd()),
705            flags: MprogFlags::AFTER | MprogFlags::LINK,
706        })
707    }
708
709    /// Attach before the given program.
710    pub fn before_program<P: MultiProgram>(program: &P) -> Result<Self, ProgramError> {
711        Ok(Self {
712            link_ref: LinkRef::Fd(program.fd()?.as_raw_fd()),
713            flags: MprogFlags::BEFORE,
714        })
715    }
716
717    /// Attach after the given program.
718    pub fn after_program<P: MultiProgram>(program: &P) -> Result<Self, ProgramError> {
719        Ok(Self {
720            link_ref: LinkRef::Fd(program.fd()?.as_raw_fd()),
721            flags: MprogFlags::AFTER,
722        })
723    }
724
725    /// Attach before the program with the given id.
726    pub fn before_program_id(id: ProgramId) -> Self {
727        Self {
728            link_ref: LinkRef::Id(id.0),
729            flags: MprogFlags::BEFORE | MprogFlags::ID,
730        }
731    }
732
733    /// Attach after the program with the given id.
734    pub fn after_program_id(id: ProgramId) -> Self {
735        Self {
736            link_ref: LinkRef::Id(id.0),
737            flags: MprogFlags::AFTER | MprogFlags::ID,
738        }
739    }
740}
741
742#[cfg(test)]
743mod tests {
744    use std::{cell::RefCell, fs::File, rc::Rc};
745
746    use assert_matches::assert_matches;
747    use aya_obj::generated::{BPF_F_ALLOW_MULTI, BPF_F_ALLOW_OVERRIDE};
748    use tempfile::tempdir;
749
750    use super::{FdLink, Link, Links};
751    use crate::{
752        programs::{CgroupAttachMode, ProgramError},
753        sys::override_syscall,
754    };
755
756    #[derive(Debug, Hash, Eq, PartialEq)]
757    struct TestLinkId(u8, u8);
758
759    #[derive(Debug)]
760    struct TestLink {
761        id: (u8, u8),
762        detached: Rc<RefCell<u8>>,
763    }
764
765    impl TestLink {
766        fn new(a: u8, b: u8) -> Self {
767            Self {
768                id: (a, b),
769                detached: Rc::new(RefCell::new(0)),
770            }
771        }
772    }
773
774    impl Link for TestLink {
775        type Id = TestLinkId;
776
777        fn id(&self) -> Self::Id {
778            TestLinkId(self.id.0, self.id.1)
779        }
780
781        fn detach(self) -> Result<(), ProgramError> {
782            *self.detached.borrow_mut() += 1;
783            Ok(())
784        }
785    }
786
787    id_as_key!(TestLink, TestLinkId);
788
789    #[test]
790    fn test_link_map() {
791        let mut links = Links::new();
792        let l1 = TestLink::new(1, 2);
793        let l1_detached = Rc::clone(&l1.detached);
794        let l2 = TestLink::new(1, 3);
795        let l2_detached = Rc::clone(&l2.detached);
796
797        let id1 = links.insert(l1).unwrap();
798        let id2 = links.insert(l2).unwrap();
799
800        assert_eq!(*l1_detached.borrow(), 0);
801        assert_eq!(*l2_detached.borrow(), 0);
802
803        links.remove(id1).unwrap();
804        assert_eq!(*l1_detached.borrow(), 1);
805        assert_eq!(*l2_detached.borrow(), 0);
806
807        links.remove(id2).unwrap();
808        assert_eq!(*l1_detached.borrow(), 1);
809        assert_eq!(*l2_detached.borrow(), 1);
810    }
811
812    #[test]
813    fn test_already_attached() {
814        let mut links = Links::new();
815
816        links.insert(TestLink::new(1, 2)).unwrap();
817        assert_matches!(
818            links.insert(TestLink::new(1, 2)),
819            Err(ProgramError::AlreadyAttached)
820        );
821    }
822
823    #[test]
824    fn test_not_attached() {
825        let mut links = Links::new();
826
827        let l1 = TestLink::new(1, 2);
828        let l1_id1 = l1.id();
829        let l1_id2 = l1.id();
830        links.insert(TestLink::new(1, 2)).unwrap();
831        links.remove(l1_id1).unwrap();
832        assert_matches!(links.remove(l1_id2), Err(ProgramError::NotAttached));
833    }
834
835    #[test]
836    fn test_drop_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        {
843            let mut links = Links::new();
844            let id1 = links.insert(l1).unwrap();
845            links.insert(l2).unwrap();
846            // manually remove one link
847            links.remove(id1).unwrap();
848            assert_eq!(*l1_detached.borrow(), 1);
849            assert_eq!(*l2_detached.borrow(), 0);
850        }
851        // remove the other on drop
852        assert_eq!(*l1_detached.borrow(), 1);
853        assert_eq!(*l2_detached.borrow(), 1);
854    }
855
856    #[test]
857    fn test_owned_detach() {
858        let l1 = TestLink::new(1, 2);
859        let l1_detached = Rc::clone(&l1.detached);
860        let l2 = TestLink::new(1, 3);
861        let l2_detached = Rc::clone(&l2.detached);
862
863        let owned_l1 = {
864            let mut links = Links::new();
865            let id1 = links.insert(l1).unwrap();
866            links.insert(l2).unwrap();
867            // manually forget one link
868            let owned_l1 = links.forget(id1);
869            assert_eq!(*l1_detached.borrow(), 0);
870            assert_eq!(*l2_detached.borrow(), 0);
871            owned_l1.unwrap()
872        };
873
874        // l2 is detached on `Drop`, but l1 is still alive
875        assert_eq!(*l1_detached.borrow(), 0);
876        assert_eq!(*l2_detached.borrow(), 1);
877
878        // manually detach l1
879        owned_l1.detach().unwrap();
880        assert_eq!(*l1_detached.borrow(), 1);
881        assert_eq!(*l2_detached.borrow(), 1);
882    }
883
884    #[test]
885    #[cfg_attr(miri, ignore = "`mkdir` not available when isolation is enabled")]
886    fn test_pin() {
887        let dir = tempdir().unwrap();
888        let f1 = File::create(dir.path().join("f1")).expect("unable to create file in tmpdir");
889        let fd_link = FdLink::new(f1.into());
890
891        // override syscall to allow for pin to happen in our tmpdir
892        override_syscall(|_| Ok(0));
893        // create the file that would have happened as a side-effect of a real pin operation
894        let pin = dir.path().join("f1-pin");
895        File::create(&pin).expect("unable to create file in tmpdir");
896        assert!(pin.exists());
897
898        let pinned_link = fd_link.pin(&pin).expect("pin failed");
899        pinned_link.unpin().expect("unpin failed");
900        assert!(!pin.exists());
901    }
902
903    #[test]
904    fn test_cgroup_attach_flag() {
905        assert_eq!(u32::from(CgroupAttachMode::Single), 0);
906        assert_eq!(
907            u32::from(CgroupAttachMode::AllowOverride),
908            BPF_F_ALLOW_OVERRIDE
909        );
910        assert_eq!(
911            u32::from(CgroupAttachMode::AllowMultiple),
912            BPF_F_ALLOW_MULTI
913        );
914    }
915}