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);