Skip to main content

esp_radio/wifi/
sniffer.rs

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