Skip to main content

aya_friday/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_without_attach_type,
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, XdpMode, 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", XdpMode::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 Self { data } = self;
81        let (btf_fd, btf_id) = get_btf_info(program.as_fd(), func_name)?;
82
83        data.attach_btf_obj_fd = Some(btf_fd);
84        data.attach_prog_fd = Some(program);
85        data.attach_btf_id = Some(btf_id);
86        load_program_without_attach_type(BPF_PROG_TYPE_EXT, data)
87    }
88
89    /// Attaches the extension.
90    ///
91    /// Attaches the extension to the program and function name specified at load time,
92    /// effectively replacing the original target function.
93    ///
94    /// The returned value can be used to detach the extension and restore the
95    /// original function, see [`Extension::detach`].
96    pub fn attach(&mut self) -> Result<ExtensionLinkId, ProgramError> {
97        let prog_fd = self.fd()?;
98        let prog_fd = prog_fd.as_fd();
99        let target_fd = self
100            .data
101            .attach_prog_fd
102            .as_ref()
103            .ok_or(ProgramError::NotLoaded)?;
104        let target_fd = target_fd.as_fd();
105        let btf_id = self.data.attach_btf_id.ok_or(ProgramError::NotLoaded)?;
106        // the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS
107        let link_fd = bpf_link_create(
108            prog_fd,
109            LinkTarget::Fd(target_fd),
110            BPF_CGROUP_INET_INGRESS,
111            0,
112            Some(BpfLinkCreateArgs::TargetBtfId(btf_id)),
113        )
114        .map_err(|io_error| SyscallError {
115            call: "bpf_link_create",
116            io_error,
117        })?;
118        self.data
119            .links
120            .insert(ExtensionLink::new(FdLink::new(link_fd)))
121    }
122
123    /// Attaches the extension to another program.
124    ///
125    /// Attaches the extension to a program and/or function other than the one provided
126    /// at load time. You may only attach to another program/function if the BTF
127    /// type signature is identical to that which was verified on load. Attempting to
128    /// attach to an invalid program/function will result in an error.
129    ///
130    /// Once attached, the extension effectively replaces the original target function.
131    ///
132    /// The returned value can be used to detach the extension and restore the
133    /// original function, see [`Extension::detach`].
134    pub fn attach_to_program(
135        &mut self,
136        program: &ProgramFd,
137        func_name: &str,
138    ) -> Result<ExtensionLinkId, ProgramError> {
139        let target_fd = program.as_fd();
140        let (_, btf_id) = get_btf_info(target_fd, func_name)?;
141        let prog_fd = self.fd()?;
142        let prog_fd = prog_fd.as_fd();
143        // the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS
144        let link_fd = bpf_link_create(
145            prog_fd,
146            LinkTarget::Fd(target_fd),
147            BPF_CGROUP_INET_INGRESS,
148            0,
149            Some(BpfLinkCreateArgs::TargetBtfId(btf_id)),
150        )
151        .map_err(|io_error| SyscallError {
152            call: "bpf_link_create",
153            io_error,
154        })?;
155        self.data
156            .links
157            .insert(ExtensionLink::new(FdLink::new(link_fd)))
158    }
159}
160
161/// Retrieves the FD of the BTF object for the provided `prog_fd` and the BTF ID of the function
162/// with the name `func_name` within that BTF object.
163fn get_btf_info(
164    prog_fd: BorrowedFd<'_>,
165    func_name: &str,
166) -> Result<(crate::MockableFd, u32), ProgramError> {
167    // retrieve program information
168    let info = sys::bpf_prog_get_info_by_fd(prog_fd, &mut [])?;
169
170    // btf_id refers to the ID of the program btf that was loaded with bpf(BPF_BTF_LOAD)
171    if info.btf_id == 0 {
172        return Err(ProgramError::ExtensionError(ExtensionError::NoBTF));
173    }
174
175    // the bpf fd of the BTF object
176    let btf_fd = sys::bpf_btf_get_fd_by_id(info.btf_id)?;
177
178    // we need to read the btf bytes into a buffer but we don't know the size ahead of time.
179    // assume 4kb. if this is too small we can resize based on the size obtained in the response.
180    let mut buf = vec![0u8; 4096];
181    loop {
182        let info = sys::btf_obj_get_info_by_fd(btf_fd.as_fd(), &mut buf)?;
183        let btf_size = info.btf_size as usize;
184        if btf_size > buf.len() {
185            buf.resize(btf_size, 0u8);
186            continue;
187        }
188        buf.truncate(btf_size);
189        break;
190    }
191
192    let btf = Btf::parse(&buf, Endianness::default()).map_err(ProgramError::Btf)?;
193
194    let btf_id = btf
195        .id_by_type_name_kind(func_name, BtfKind::Func)
196        .map_err(ProgramError::Btf)?;
197
198    Ok((btf_fd, btf_id))
199}
200
201define_link_wrapper!(ExtensionLink, ExtensionLinkId, FdLink, FdLinkId, Extension);