Skip to main content

esp_radio/wifi/
sniffer.rs

1//! Wi-Fi sniffer.
2
3use esp_sync::NonReentrantMutex;
4
5use super::{RxControlInfo, SNIFFER_BIT, WifiRefGuard, release, try_acquire};
6use crate::{
7    WifiError,
8    sys::include::{
9        esp_wifi_80211_tx,
10        esp_wifi_set_promiscuous,
11        esp_wifi_set_promiscuous_rx_cb,
12        wifi_interface_t,
13        wifi_interface_t_WIFI_IF_AP,
14        wifi_interface_t_WIFI_IF_STA,
15        wifi_pkt_rx_ctrl_t,
16        wifi_promiscuous_pkt_t,
17        wifi_promiscuous_pkt_type_t,
18    },
19    wifi::esp_wifi_result,
20};
21
22/// Represents a Wi-Fi packet in promiscuous mode.
23#[derive(Debug)]
24#[cfg_attr(feature = "defmt", derive(defmt::Format))]
25#[instability::unstable]
26pub struct PromiscuousPkt<'a> {
27    /// Control information related to packet reception.
28    pub rx_cntl: RxControlInfo,
29    /// Frame type of the received packet.
30    pub frame_type: wifi_promiscuous_pkt_type_t,
31    /// Length of the received packet.
32    pub len: usize,
33    /// Data contained in the received packet.
34    pub data: &'a [u8],
35}
36
37#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
38impl PromiscuousPkt<'_> {
39    /// # Safety
40    ///
41    /// When calling this, you have to ensure, that `buf` points to a valid
42    /// [wifi_promiscuous_pkt_t].
43    pub(crate) unsafe fn from_raw(
44        buf: *const wifi_promiscuous_pkt_t,
45        frame_type: wifi_promiscuous_pkt_type_t,
46    ) -> Self {
47        let rx_cntl = unsafe { RxControlInfo::from_raw(&(*buf).rx_ctrl) };
48        let len = rx_cntl.sig_len as usize;
49        PromiscuousPkt {
50            rx_cntl,
51            frame_type,
52            len,
53            data: unsafe {
54                core::slice::from_raw_parts(
55                    (buf as *const u8).add(core::mem::size_of::<wifi_pkt_rx_ctrl_t>()),
56                    len,
57                )
58            },
59        }
60    }
61}
62
63static SNIFFER_CB: NonReentrantMutex<Option<fn(PromiscuousPkt<'_>)>> = NonReentrantMutex::new(None);
64
65unsafe extern "C" fn promiscuous_rx_cb(buf: *mut core::ffi::c_void, frame_type: u32) {
66    unsafe {
67        if let Some(sniffer_callback) = SNIFFER_CB.with(|callback| *callback) {
68            let promiscuous_pkt = PromiscuousPkt::from_raw(buf as *const _, frame_type);
69            sniffer_callback(promiscuous_pkt);
70        }
71    }
72}
73
74/// A Wi-Fi sniffer.
75#[derive(Debug)]
76#[cfg_attr(feature = "defmt", derive(defmt::Format))]
77#[instability::unstable]
78#[non_exhaustive]
79pub struct Sniffer {
80    _wifi_guard: WifiRefGuard,
81}
82
83impl Sniffer {
84    pub(crate) fn new(guard: WifiRefGuard) -> Self {
85        assert!(try_acquire(SNIFFER_BIT), "sniffer already in use");
86
87        // If registering the callback fails we panic, so release the singleton
88        // bit first so the panic doesn't leave the slot permanently occupied.
89        let res =
90            esp_wifi_result!(unsafe { esp_wifi_set_promiscuous_rx_cb(Some(promiscuous_rx_cb)) });
91        if res.is_err() {
92            release(SNIFFER_BIT);
93            unwrap!(res);
94        }
95
96        Self { _wifi_guard: guard }
97    }
98
99    /// Set promiscuous mode enabled or disabled.
100    #[instability::unstable]
101    pub fn set_promiscuous_mode(&self, enabled: bool) -> Result<(), WifiError> {
102        esp_wifi_result!(unsafe { esp_wifi_set_promiscuous(enabled) })?;
103        Ok(())
104    }
105
106    /// Transmit a raw frame.
107    #[instability::unstable]
108    pub fn send_raw_frame(
109        &mut self,
110        use_sta_interface: bool,
111        buffer: &[u8],
112        use_internal_seq_num: bool,
113    ) -> Result<(), WifiError> {
114        esp_wifi_result!(unsafe {
115            esp_wifi_80211_tx(
116                if use_sta_interface {
117                    wifi_interface_t_WIFI_IF_STA
118                } else {
119                    wifi_interface_t_WIFI_IF_AP
120                } as wifi_interface_t,
121                buffer.as_ptr() as *const _,
122                buffer.len() as i32,
123                use_internal_seq_num,
124            )
125        })
126    }
127
128    /// Set the callback for receiving a packet.
129    #[instability::unstable]
130    pub fn set_receive_cb(&mut self, cb: fn(PromiscuousPkt<'_>)) {
131        SNIFFER_CB.with(|callback| *callback = Some(cb));
132    }
133}
134
135impl Drop for Sniffer {
136    fn drop(&mut self) {
137        // Clear the user callback first so the C trampoline becomes a no-op even
138        // if it fires during the teardown window below.
139        SNIFFER_CB.with(|callback| *callback = None);
140        // Best-effort cleanup: log on failure but keep going so we still release
141        // the singleton bit, otherwise a future Sniffer could never be created.
142        if let Err(e) = esp_wifi_result!(unsafe { esp_wifi_set_promiscuous(false) }) {
143            warn!(
144                "Failed to disable promiscuous mode on sniffer drop: {:?}",
145                e
146            );
147        }
148        if let Err(e) = esp_wifi_result!(unsafe { esp_wifi_set_promiscuous_rx_cb(None) }) {
149            warn!(
150                "Failed to unregister promiscuous rx cb on sniffer drop: {:?}",
151                e
152            );
153        }
154        release(SNIFFER_BIT);
155    }
156}