aya_friday/programs/socket_filter.rs
1//! Socket filter programs.
2use std::{
3 io,
4 os::fd::{AsFd, AsRawFd as _},
5 path::Path,
6 ptr,
7};
8
9use aya_obj::generated::{
10 SO_ATTACH_BPF, SO_DETACH_BPF, bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER,
11};
12use libc::{SOL_SOCKET, setsockopt};
13use thiserror::Error;
14
15use crate::{
16 VerifierLogLevel,
17 programs::{
18 ProgramData, ProgramError, ProgramType, links::FdLink, load_program_without_attach_type,
19 },
20};
21
22macro_rules! setsockopt_socket_filter {
23 ($socket:expr, $option:ident, $value:expr) => {{
24 let value = $value;
25 let ret = unsafe {
26 setsockopt(
27 $socket,
28 SOL_SOCKET,
29 $option as libc::c_int,
30 ptr::from_ref(value).cast(),
31 size_of_val(value) as libc::socklen_t,
32 )
33 };
34 if ret < 0 {
35 Err(SocketFilterError::SetsockoptError {
36 option: stringify!($option),
37 io_error: io::Error::last_os_error(),
38 })
39 } else {
40 Ok(())
41 }
42 }};
43}
44
45/// The type returned when a [`SocketFilter`] socket option operation fails.
46#[derive(Debug, Error)]
47pub enum SocketFilterError {
48 /// Setting a socket filter socket option failed.
49 #[error("setsockopt {option} failed")]
50 SetsockoptError {
51 /// Socket option passed to `setsockopt`.
52 option: &'static str,
53 /// Underlying OS error.
54 #[source]
55 io_error: io::Error,
56 },
57}
58
59/// A program used to inspect and filter incoming packets on a socket.
60///
61/// [`SocketFilter`] programs are attached on sockets and can be used to inspect
62/// and filter incoming packets.
63///
64/// Each socket has one filter slot. Attaching a new [`SocketFilter`] replaces
65/// the socket's current filter, and detaching clears that current filter
66/// regardless of which program installed it. Aya therefore does not expose a
67/// link-style attachment handle for [`SocketFilter`] or automatically track
68/// socket filter attachments for cleanup. Dropping [`SocketFilter`] or
69/// [`crate::Ebpf`] does not detach the filter; call [`SocketFilter::detach`]
70/// explicitly when you want to remove it, or close the socket.
71///
72/// # Minimum kernel version
73///
74/// The minimum kernel version required to use this feature is 3.19.
75/// `BPF_PROG_TYPE_SOCKET_FILTER` and `SO_ATTACH_BPF` are present in Linux
76/// v3.19:
77/// <https://github.com/torvalds/linux/blob/v3.19/include/uapi/linux/bpf.h#L118-L120>
78/// <https://github.com/torvalds/linux/blob/v3.19/include/uapi/asm-generic/socket.h#L87-L88>
79///
80/// # Examples
81///
82/// ```no_run
83/// # #[derive(Debug, thiserror::Error)]
84/// # enum Error {
85/// # #[error(transparent)]
86/// # IO(#[from] std::io::Error),
87/// # #[error(transparent)]
88/// # Map(#[from] aya::maps::MapError),
89/// # #[error(transparent)]
90/// # Program(#[from] aya::programs::ProgramError),
91/// # #[error(transparent)]
92/// # Ebpf(#[from] aya::EbpfError)
93/// # }
94/// # let mut bpf = aya::Ebpf::load(&[])?;
95/// use std::net::TcpStream;
96/// use aya::programs::SocketFilter;
97///
98/// let mut client = TcpStream::connect("127.0.0.1:1234")?;
99/// let prog: &mut SocketFilter = bpf.program_mut("filter_packets").unwrap().try_into()?;
100/// prog.load()?;
101/// prog.attach(&client)?;
102/// # Ok::<(), Error>(())
103/// ```
104#[derive(Debug)]
105#[doc(alias = "BPF_PROG_TYPE_SOCKET_FILTER")]
106pub struct SocketFilter {
107 pub(crate) data: ProgramData<FdLink>,
108}
109
110impl SocketFilter {
111 /// The type of the program according to the kernel.
112 pub const PROGRAM_TYPE: ProgramType = ProgramType::SocketFilter;
113
114 /// Loads the program inside the kernel.
115 pub fn load(&mut self) -> Result<(), ProgramError> {
116 let Self { data } = self;
117 load_program_without_attach_type(BPF_PROG_TYPE_SOCKET_FILTER, data)
118 }
119
120 /// Attaches the filter on the given socket.
121 ///
122 /// If the socket already has a filter attached, attaching again replaces
123 /// the current filter instead of returning an already-attached error. This
124 /// follows the kernel model: each socket has one filter slot and cannot run
125 /// multiple socket filters together. The kernel detach API also clears the
126 /// socket's current filter slot; it cannot detach a specific program
127 /// attachment. For that reason, Aya does not provide link-level RAII
128 /// semantics for socket filters. Dropping [`SocketFilter`] or [`crate::Ebpf`]
129 /// does not detach the filter. Call [`SocketFilter::detach`] explicitly when
130 /// you want to remove it, or close the socket.
131 pub fn attach<T: AsFd>(&self, socket: T) -> Result<(), ProgramError> {
132 let prog_fd = self.fd()?;
133 let prog_fd = prog_fd.as_fd().as_raw_fd();
134 let socket = socket.as_fd().as_raw_fd();
135
136 setsockopt_socket_filter!(socket, SO_ATTACH_BPF, &prog_fd)?;
137 Ok(())
138 }
139
140 /// Detaches the current filter from the given socket.
141 ///
142 /// Detaching clears the socket's current filter slot, regardless of which
143 /// program was used to attach that filter. Unlike [`SocketFilter::attach`],
144 /// this operation does not require the program to remain loaded in this
145 /// process. If another filter replaced this program on the same socket,
146 /// detaching will remove that replacement filter.
147 pub fn detach<T: AsFd>(socket: T) -> Result<(), ProgramError> {
148 let socket = socket.as_fd().as_raw_fd();
149
150 // `SO_DETACH_BPF` is an alias for `SO_DETACH_FILTER`; Linux's
151 // `sk_setsockopt()` requires an `int` optval, but the detach branch only
152 // calls `sk_detach_filter(sk)` and does not use a program fd:
153 // https://github.com/torvalds/linux/blob/v6.9/net/core/sock.c#L1413-L1414
154 let dummy: libc::c_int = 0;
155 setsockopt_socket_filter!(socket, SO_DETACH_BPF, &dummy)?;
156 Ok(())
157 }
158
159 /// Creates a program from a pinned entry on a bpffs.
160 ///
161 /// `SocketFilter` does not use link-style attachments, so this only
162 /// restores access to the pinned program itself.
163 ///
164 /// Dropping the returned value unloads the local program FD, but does not
165 /// detach the filter from any socket. This will also not unload the program
166 /// from the kernel while it remains pinned.
167 pub fn from_pin<P: AsRef<Path>>(path: P) -> Result<Self, ProgramError> {
168 let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?;
169 Ok(Self { data })
170 }
171}