Skip to main content

pcap/
compile.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fidl_fuchsia_ebpf as febpf;
6use fidl_fuchsia_net_debug as fnet_debug;
7
8use std::ffi::{CString, NulError};
9use std::os::raw::c_int;
10use thiserror::Error;
11
12use crate::bindings::{bpf_program, pcap_close, pcap_compile, pcap_freecode, pcap_open_dead};
13
14/// Errors that can occur while compiling a pcap filter.
15#[derive(Error, Debug)]
16pub enum CompilationError {
17    /// pcap_open_dead failed.
18    #[error("pcap_open_dead failed")]
19    OpenDeadFailed,
20
21    /// pcap_compile failed.
22    #[error("pcap_compile failed")]
23    CompileFailed,
24
25    /// The filter string contained a null byte.
26    #[error("filter string contained a null byte")]
27    NullByteInFilter,
28}
29
30// This value comes from pcap.h.
31//
32// See https://github.com/the-tcpdump-group/libpcap/blob/78415bc13031d145dc24d3c3ffffc281d3d629f1/pcap/pcap.h#L387
33const PCAP_NETMASK_UNKNOWN: u32 = 0xffffffff;
34
35/// Compiles a pcap filter string to BPF bytecode.
36///
37/// Currently the pcap filter is intended for Ethernet links.
38pub fn compile_filter(filter: &str) -> Result<febpf::VerifiedProgram, CompilationError> {
39    let c_filter =
40        CString::new(filter).map_err(|_: NulError| CompilationError::NullByteInFilter)?;
41
42    let link_type: c_int = u16::from(crate::LinkType::Ethernet).try_into().expect("fits in c_int");
43    let snap_len: c_int = fnet_debug::DEFAULT_SNAP_LEN.try_into().expect("fits in c_int");
44
45    // SAFETY: `pcap_open_dead` does not dereference any pointers. It merely
46    // allocates and returns a placeholder pcap handle.
47    let handle = unsafe { pcap_open_dead(link_type, snap_len) };
48    if handle.is_null() {
49        return Err(CompilationError::OpenDeadFailed);
50    }
51
52    let mut program = bpf_program { bf_len: 0, bf_insns: std::ptr::null_mut() };
53
54    // SAFETY:
55    // - `handle` is a valid pcap handle returned by `pcap_open_dead` and is not null.
56    // - `program` is a valid pointer to a stack-allocated `bpf_program`.
57    // - `c_filter` is a valid null-terminated C string.
58    let res = unsafe {
59        pcap_compile(
60            handle,
61            &mut program,
62            c_filter.as_ptr(),
63            1, /* optimize */
64            PCAP_NETMASK_UNKNOWN,
65        )
66    };
67    if res != 0 {
68        // SAFETY: `handle` is a valid pcap handle that has not been closed yet.
69        unsafe {
70            pcap_close(handle);
71        }
72        return Err(CompilationError::CompileFailed);
73    }
74    // SAFETY: `pcap_compile` succeeded, so `program.bf_insns` points to a valid
75    // array of `program.bf_len` instructions allocated by libpcap. This data is
76    // valid until `pcap_freecode` is called below. We copy the data out before
77    // freeing it.
78    let insns = unsafe { std::slice::from_raw_parts(program.bf_insns, program.bf_len as usize) };
79    // Convert generated bpf_insn to linux_uapi::sock_filter.
80    let cbpf_insns: &[linux_uapi::sock_filter] = zerocopy::transmute_ref!(insns);
81
82    let verified = ebpf::converter::convert_and_verify_cbpf(
83        cbpf_insns,
84        ebpf_api::SOCKET_FILTER_SK_BUF_TYPE.clone(),
85        &ebpf_api::SOCKET_FILTER_CBPF_CONFIG,
86    )
87    .expect("failed to convert cBPF to eBPF");
88    // These must always be empty when converting cBPF to eBPF.
89    // cBPF does not support maps or direct struct access.
90    assert_eq!(verified.struct_access_instructions(), &[]);
91    assert_eq!(verified.maps(), &[]);
92
93    // SAFETY:
94    // - `program` was successfully initialized by `pcap_compile`.
95    // - `handle` is a valid pcap handle that has not been closed yet.
96    unsafe {
97        pcap_freecode(&mut program);
98        pcap_close(handle);
99    }
100
101    Ok(febpf::VerifiedProgram {
102        code: Some(verified.code().iter().map(ebpf::EbpfInstruction::get).collect()),
103        struct_access_instructions: Some(Vec::new()),
104        maps: Some(Vec::new()),
105        ..Default::default()
106    })
107}