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