Skip to main content

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}