Skip to main content

profile_bee_aya/programs/
extension.rs

1//! Extension programs.
2
3use std::os::fd::{AsFd as _, BorrowedFd};
4
5use aya_obj::{
6    btf::BtfKind,
7    generated::{bpf_attach_type::BPF_CGROUP_INET_INGRESS, bpf_prog_type::BPF_PROG_TYPE_EXT},
8};
9use object::Endianness;
10use thiserror::Error;
11
12use crate::{
13    Btf,
14    programs::{
15        FdLink, FdLinkId, ProgramData, ProgramError, ProgramFd, ProgramType, define_link_wrapper,
16        load_program,
17    },
18    sys::{self, BpfLinkCreateArgs, LinkTarget, SyscallError, bpf_link_create},
19};
20
21/// The type returned when loading or attaching an [`Extension`] fails.
22#[derive(Debug, Error)]
23pub enum ExtensionError {
24    /// Target BPF program does not have BTF loaded to the kernel.
25    #[error("target BPF program does not have BTF loaded to the kernel")]
26    NoBTF,
27}
28
29/// A program used to extend existing BPF programs.
30///
31/// [`Extension`] programs can be loaded to replace a global
32/// function in a program that has already been loaded.
33///
34/// # Minimum kernel version
35///
36/// The minimum kernel version required to use this feature is 5.9
37///
38/// # Examples
39///
40/// ```no_run
41/// use aya::{EbpfLoader, programs::{Xdp, XdpFlags, Extension}};
42///
43/// let mut bpf = EbpfLoader::new().extension("extension").load_file("app.o")?;
44/// let prog: &mut Xdp = bpf.program_mut("main").unwrap().try_into()?;
45/// prog.load()?;
46/// prog.attach("eth0", XdpFlags::default())?;
47///
48/// let prog_fd = prog.fd().unwrap();
49/// let prog_fd = prog_fd.try_clone().unwrap();
50/// let ext: &mut Extension = bpf.program_mut("extension").unwrap().try_into()?;
51/// ext.load(prog_fd, "function_to_replace")?;
52/// ext.attach()?;
53/// Ok::<(), aya::EbpfError>(())
54/// ```
55#[derive(Debug)]
56#[doc(alias = "BPF_PROG_TYPE_EXT")]
57pub struct Extension {
58    pub(crate) data: ProgramData<ExtensionLink>,
59}
60
61impl Extension {
62    /// The type of the program according to the kernel.
63    pub const PROGRAM_TYPE: ProgramType = ProgramType::Extension;
64
65    /// Loads the extension inside the kernel.
66    ///
67    /// Prepares the code included in the extension to replace the code of the function
68    /// `func_name` within the eBPF program represented by the `program` file descriptor.
69    /// This requires that both the [`Extension`] and `program` have had their BTF
70    /// loaded into the kernel.
71    ///
72    /// The BPF verifier requires that we specify the target program and function name
73    /// at load time, so it can identify that the program and target are BTF compatible
74    /// and to enforce this constraint when programs are attached.
75    ///
76    /// The extension code will be loaded but inactive until it's attached.
77    /// There are no restrictions on what functions may be replaced, so you could replace
78    /// the main entry point of your program with an extension.
79    pub fn load(&mut self, program: ProgramFd, func_name: &str) -> Result<(), ProgramError> {
80        let (btf_fd, btf_id) = get_btf_info(program.as_fd(), func_name)?;
81
82        self.data.attach_btf_obj_fd = Some(btf_fd);
83        self.data.attach_prog_fd = Some(program);
84        self.data.attach_btf_id = Some(btf_id);
85        load_program(BPF_PROG_TYPE_EXT, &mut self.data)
86    }
87
88    /// Attaches the extension.
89    ///
90    /// Attaches the extension to the program and function name specified at load time,
91    /// effectively replacing the original target function.
92    ///
93    /// The returned value can be used to detach the extension and restore the
94    /// original function, see [`Extension::detach`].
95    pub fn attach(&mut self) -> Result<ExtensionLinkId, ProgramError> {
96        let prog_fd = self.fd()?;
97        let prog_fd = prog_fd.as_fd();
98        let target_fd = self
99            .data
100            .attach_prog_fd
101            .as_ref()
102            .ok_or(ProgramError::NotLoaded)?;
103        let target_fd = target_fd.as_fd();
104        let btf_id = self.data.attach_btf_id.ok_or(ProgramError::NotLoaded)?;
105        // the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS
106        let link_fd = bpf_link_create(
107            prog_fd,
108            LinkTarget::Fd(target_fd),
109            BPF_CGROUP_INET_INGRESS,
110            0,
111            Some(BpfLinkCreateArgs::TargetBtfId(btf_id)),
112        )
113        .map_err(|io_error| SyscallError {
114            call: "bpf_link_create",
115            io_error,
116        })?;
117        self.data
118            .links
119            .insert(ExtensionLink::new(FdLink::new(link_fd)))
120    }
121
122    /// Attaches the extension to another program.
123    ///
124    /// Attaches the extension to a program and/or function other than the one provided
125    /// at load time. You may only attach to another program/function if the BTF
126    /// type signature is identical to that which was verified on load. Attempting to
127    /// attach to an invalid program/function will result in an error.
128    ///
129    /// Once attached, the extension effectively replaces the original target function.
130    ///
131    /// The returned value can be used to detach the extension and restore the
132    /// original function, see [`Extension::detach`].
133    pub fn attach_to_program(
134        &mut self,
135        program: &ProgramFd,
136        func_name: &str,
137    ) -> Result<ExtensionLinkId, ProgramError> {
138        let target_fd = program.as_fd();
139        let (_, btf_id) = get_btf_info(target_fd, func_name)?;
140        let prog_fd = self.fd()?;
141        let prog_fd = prog_fd.as_fd();
142        // the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS
143        let link_fd = bpf_link_create(
144            prog_fd,
145            LinkTarget::Fd(target_fd),
146            BPF_CGROUP_INET_INGRESS,
147            0,
148            Some(BpfLinkCreateArgs::TargetBtfId(btf_id)),
149        )
150        .map_err(|io_error| SyscallError {
151            call: "bpf_link_create",
152            io_error,
153        })?;
154        self.data
155            .links
156            .insert(ExtensionLink::new(FdLink::new(link_fd)))
157    }
158}
159
160/// Retrieves the FD of the BTF object for the provided `prog_fd` and the BTF ID of the function
161/// with the name `func_name` within that BTF object.
162fn get_btf_info(
163    prog_fd: BorrowedFd<'_>,
164    func_name: &str,
165) -> Result<(crate::MockableFd, u32), ProgramError> {
166    // retrieve program information
167    let info = sys::bpf_prog_get_info_by_fd(prog_fd, &mut [])?;
168
169    // btf_id refers to the ID of the program btf that was loaded with bpf(BPF_BTF_LOAD)
170    if info.btf_id == 0 {
171        return Err(ProgramError::ExtensionError(ExtensionError::NoBTF));
172    }
173
174    // the bpf fd of the BTF object
175    let btf_fd = sys::bpf_btf_get_fd_by_id(info.btf_id)?;
176
177    // we need to read the btf bytes into a buffer but we don't know the size ahead of time.
178    // assume 4kb. if this is too small we can resize based on the size obtained in the response.
179    let mut buf = vec![0u8; 4096];
180    loop {
181        let info = sys::btf_obj_get_info_by_fd(btf_fd.as_fd(), &mut buf)?;
182        let btf_size = info.btf_size as usize;
183        if btf_size > buf.len() {
184            buf.resize(btf_size, 0u8);
185            continue;
186        }
187        buf.truncate(btf_size);
188        break;
189    }
190
191    let btf = Btf::parse(&buf, Endianness::default()).map_err(ProgramError::Btf)?;
192
193    let btf_id = btf
194        .id_by_type_name_kind(func_name, BtfKind::Func)
195        .map_err(ProgramError::Btf)?;
196
197    Ok((btf_fd, btf_id))
198}
199
200define_link_wrapper!(ExtensionLink, ExtensionLinkId, FdLink, FdLinkId, Extension);