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}