bpf/
bpf_linux.rs

1use libc::{c_int, c_ushort, c_void, setsockopt, socklen_t, SOL_SOCKET};
2use std::io::Error;
3use std::mem::size_of_val;
4use std::os::unix::io::RawFd;
5use std::ptr::null;
6
7/// Represents a single BPF instruction (operation).
8///
9/// This struct directly maps to the Linux kernel's `sock_filter` structure
10/// used for BPF programs. Each operation consists of:
11/// - a 16-bit code that defines the operation
12/// - 8-bit jump targets for true/false conditions
13/// - a 32-bit immediate constant value (k)
14///
15/// The memory layout must match the kernel's expectation, so we use `repr(C)`.
16#[repr(C)]
17#[derive(Debug, Clone)]
18pub struct Op {
19    /// The operation code (what action to perform)
20    pub code: u16,
21    /// Jump target offset if the condition is true
22    pub jt: u8,
23    /// Jump target offset if the condition is false
24    pub jf: u8,
25    /// Generic field used for various purposes depending on the operation
26    pub k: u32,
27}
28
29impl Op {
30    /// Creates a new BPF operation with the specified parameters.
31    ///
32    /// # Parameters
33    ///
34    /// * `code` - The operation code (e.g., `BPF_LD|BPF_H|BPF_ABS`)
35    /// * `jt` - Jump target offset for true condition
36    /// * `jf` - Jump target offset for false condition
37    /// * `k` - Immediate value whose meaning depends on the operation code
38    ///
39    /// # Examples
40    ///
41    /// ```
42    /// use bpf::Op;
43    ///
44    /// // Load the 2-byte value at position 12 (protocol field in Ethernet header)
45    /// let load_protocol = Op::new(0x28, 0, 0, 12); // ldh [12]
46    /// ```
47    pub fn new(code: u16, jt: u8, jf: u8, k: u32) -> Self {
48        Self { code, jt, jf, k }
49    }
50}
51
52/// Represents a complete BPF program, consisting of a sequence of operations.
53///
54/// This struct directly maps to the Linux kernel's `sock_fprog` structure.
55/// A program is an array of BPF instructions that are executed sequentially
56/// by the kernel to filter packets.
57///
58/// The memory layout must match the kernel's expectation, so we use `repr(C)`.
59/// The struct manages the memory of the operations to ensure safety.
60#[repr(C)]
61#[derive(Debug)]
62pub struct Prog {
63    /// Length of the filter program
64    len: c_ushort,
65    /// Pointer to the filter operations
66    filter: *mut Op,
67    /// Hold the original boxed slice to properly manage memory
68    /// This field is excluded from documentation to match the C structure layout
69    #[cfg(not(doc))]
70    _ops: Option<Box<[Op]>>,
71}
72
73impl Prog {
74    /// Creates a new BPF program from a vector of operations.
75    ///
76    /// This function takes ownership of the operations vector and converts it
77    /// into a format suitable for the Linux kernel's BPF filter system.
78    /// The operations will be executed in sequence when packets arrive on a socket.
79    ///
80    /// # Parameters
81    ///
82    /// * `ops` - A vector of BPF operations that make up the program
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use bpf::{Op, Prog};
88    ///
89    /// // Create a simple program that accepts all packets
90    /// let mut ops = Vec::new();
91    /// ops.push(Op::new(0x06, 0, 0, 0xFFFFFFFF)); // ret #UINT_MAX (accept)
92    /// let prog = Prog::new(ops);
93    /// ```
94    ///
95    /// # Note
96    ///
97    /// It's generally easier to use the `bpfprog!` macro to create programs.
98    pub fn new(ops: Vec<Op>) -> Self {
99        let mut ops = ops.into_boxed_slice();
100        let len = ops.len();
101        let ptr = ops.as_mut_ptr();
102
103        Self {
104            len: len as _,
105            filter: ptr,
106            _ops: Some(ops),
107        }
108    }
109}
110
111// No longer need custom Drop impl as we're using proper Rust ownership
112
113const SO_ATTACH_FILTER: c_int = 26;
114const SO_DETACH_FILTER: c_int = 27;
115const SO_LOCK_FILTER: c_int = 44;
116
117/// Macro for creating BPF programs with a more concise syntax.
118///
119/// This macro allows you to create BPF programs by specifying the operations
120/// as a sequence of `code jt jf k` tuples, making it easier to translate BPF
121/// assembly code into Rust.
122///
123/// # Parameters
124///
125/// * `$count` - The number of operations in the program (for capacity pre-allocation)
126/// * `$code $jt $jf $k` - Repeated tuples of operation code, jump-true offset,
127///   jump-false offset, and k-value for each operation
128///
129/// # Examples
130///
131/// ```
132/// use bpf::bpfprog;
133///
134/// // Create a BPF program that accepts only IPv4 TCP packets
135/// let filter = bpfprog!(4,
136///     0x28 0 0 0x0000000c,  // ldh [12]             ; load ethertype
137///     0x15 0 2 0x00000800,  // jeq #0x800, L1, L3   ; if IPv4, goto L1, else L3
138///     0x30 0 0 0x00000017,  // ldb [23]             ; load protocol
139///     0x15 0 1 0x00000006   // jeq #6, L2, L3       ; if TCP, accept, else drop
140/// );
141/// ```
142#[macro_export]
143macro_rules! bpfprog {
144    ($count:expr, $($code:tt $jt:tt $jf:tt $k:tt),*) => {
145        {
146            let mut ops = Vec::with_capacity($count);
147            $(ops.push($crate::Op::new($code, $jt, $jf, $k));)*
148            $crate::Prog::new(ops)
149        }
150    }
151}
152
153/// Attaches a BPF filter program to a socket.
154///
155/// Once attached, the BPF program will filter all incoming packets on the
156/// socket. Only packets that match the filter criteria will be delivered
157/// to the application.
158///
159/// # Parameters
160///
161/// * `fd` - Raw file descriptor of the socket
162/// * `prog` - The BPF program to attach
163///
164/// # Returns
165///
166/// * `Ok(())` if the filter was successfully attached
167/// * `Err(Error)` with the system error if attachment failed
168///
169/// # Examples
170///
171/// ```
172/// use bpf::{bpfprog, attach_filter};
173/// use std::net::UdpSocket;
174/// use std::os::unix::io::AsRawFd;
175///
176/// let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
177/// let filter = bpfprog!(1, 0x06 0 0 0x00000001); // ret #1 (accept 1 byte)
178///
179/// // Attach the filter to the socket
180/// let result = attach_filter(socket.as_raw_fd(), filter);
181/// ```
182///
183/// # Safety
184///
185/// This function is safe to call, but internally uses unsafe code to interact
186/// with the operating system. The `fd` must refer to a valid socket.
187pub fn attach_filter(fd: RawFd, prog: Prog) -> Result<(), Error> {
188    let ret = unsafe {
189        setsockopt(
190            fd as c_int,
191            SOL_SOCKET,
192            SO_ATTACH_FILTER,
193            &prog as *const _ as *const c_void,
194            size_of_val(&prog) as socklen_t,
195        )
196    };
197
198    if ret == 0 {
199        Ok(())
200    } else {
201        Err(Error::last_os_error())
202    }
203}
204
205/// Detaches any BPF filter program from a socket.
206///
207/// This removes any previously attached filter, allowing all packets to be
208/// delivered to the application again.
209///
210/// # Parameters
211///
212/// * `fd` - Raw file descriptor of the socket
213///
214/// # Returns
215///
216/// * `Ok(())` if the filter was successfully detached
217/// * `Err(Error)` with the system error if detachment failed
218///
219/// # Examples
220///
221/// ```
222/// use bpf::detach_filter;
223/// use std::net::UdpSocket;
224/// use std::os::unix::io::AsRawFd;
225///
226/// let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
227///
228/// // Detach any filter from the socket
229/// let result = detach_filter(socket.as_raw_fd());
230/// ```
231///
232/// # Safety
233///
234/// This function is safe to call, but internally uses unsafe code to interact
235/// with the operating system. The `fd` must refer to a valid socket.
236pub fn detach_filter(fd: RawFd) -> Result<(), Error> {
237    let ret = unsafe { setsockopt(fd as c_int, SOL_SOCKET, SO_DETACH_FILTER, null(), 0) };
238
239    if ret == 0 {
240        Ok(())
241    } else {
242        Err(Error::last_os_error())
243    }
244}
245
246/// Locks the BPF filter on a socket to prevent it from being replaced.
247///
248/// Once locked, the filter cannot be modified or removed for the lifetime
249/// of the socket. This is a security measure to prevent privilege escalation
250/// attacks where a program running with lower privileges might try to replace
251/// a filter set by a privileged program.
252///
253/// # Parameters
254///
255/// * `fd` - Raw file descriptor of the socket
256///
257/// # Returns
258///
259/// * `Ok(())` if the filter was successfully locked
260/// * `Err(Error)` with the system error if locking failed
261///
262/// # Examples
263///
264/// ```
265/// use bpf::{bpfprog, attach_filter, lock_filter};
266/// use std::net::UdpSocket;
267/// use std::os::unix::io::AsRawFd;
268///
269/// let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
270/// let filter = bpfprog!(1, 0x06 0 0 0x00000001); // ret #1 (accept 1 byte)
271///
272/// // Attach the filter to the socket
273/// attach_filter(socket.as_raw_fd(), filter).unwrap();
274///
275/// // Lock the filter to prevent it from being modified
276/// let result = lock_filter(socket.as_raw_fd());
277/// ```
278///
279/// # Safety
280///
281/// This function is safe to call, but internally uses unsafe code to interact
282/// with the operating system. The `fd` must refer to a valid socket.
283///
284/// # Note
285///
286/// This operation is irreversible for the lifetime of the socket.
287pub fn lock_filter(fd: RawFd) -> Result<(), Error> {
288    let one: c_int = 1;
289    let ret = unsafe {
290        setsockopt(
291            fd as c_int,
292            SOL_SOCKET,
293            SO_LOCK_FILTER,
294            &one as *const _ as *const c_void,
295            size_of_val(&one) as socklen_t,
296        )
297    };
298
299    if ret == 0 {
300        Ok(())
301    } else {
302        Err(Error::last_os_error())
303    }
304}