From d1acacb7573d4d6e88b3ba59c13eaec40f3e2912 Mon Sep 17 00:00:00 2001 From: Frostie314159 Date: Mon, 19 Aug 2024 08:51:18 +0200 Subject: [PATCH] Implement Sniffer API (#1935) * Implemented queue_msg_waiting. * Fmt. * Adjusted changelog. * Fixed CI. * Fixed pointer mutability. * Implemented experimental sniffer api. * Fixed CI.. * Added safety comment. * Featured gated, PromiscuousPkt * Format. * Adjusted imports. * Added injection example. * Made RxControlInfo::from_raw public. * Format. * Added sniffer example. --- esp-wifi/CHANGELOG.md | 1 + esp-wifi/Cargo.toml | 1 + esp-wifi/src/esp_now/mod.rs | 100 +------------ esp-wifi/src/wifi/mod.rs | 226 +++++++++++++++++++++++++++++- examples/Cargo.toml | 1 + examples/src/bin/wifi_80211_tx.rs | 123 ++++++++++++++++ examples/src/bin/wifi_sniffer.rs | 85 +++++++++++ 7 files changed, 437 insertions(+), 100 deletions(-) create mode 100644 examples/src/bin/wifi_80211_tx.rs create mode 100644 examples/src/bin/wifi_sniffer.rs diff --git a/esp-wifi/CHANGELOG.md b/esp-wifi/CHANGELOG.md index cbff9b1a5..354ce7ca1 100644 --- a/esp-wifi/CHANGELOG.md +++ b/esp-wifi/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement `embedded_io::{ReadReady, WriteReady}` traits for `WifiStack` (#1882) - Implement `queue_msg_waiting` on the os_adapter (#1925) +- Added API for promiscuous mode (#1935) ### Changed diff --git a/esp-wifi/Cargo.toml b/esp-wifi/Cargo.toml index 5e59dcc76..dbcca81d4 100644 --- a/esp-wifi/Cargo.toml +++ b/esp-wifi/Cargo.toml @@ -133,6 +133,7 @@ dhcpv4 = ["wifi", "utils", "smoltcp?/proto-dhcpv4", "smoltcp?/socket-dhcpv4"] wifi-default = ["ipv4", "tcp", "udp", "icmp", "igmp", "dns", "dhcpv4"] defmt = ["dep:defmt", "smoltcp?/defmt", "esp-hal/defmt"] log = ["dep:log", "esp-hal/log"] +sniffer = ["wifi"] [package.metadata.docs.rs] features = [ diff --git a/esp-wifi/src/esp_now/mod.rs b/esp-wifi/src/esp_now/mod.rs index a0e2e7d49..e272f5e3f 100644 --- a/esp-wifi/src/esp_now/mod.rs +++ b/esp-wifi/src/esp_now/mod.rs @@ -19,7 +19,7 @@ use crate::{ binary::include::*, compat::queue::SimpleQueue, hal::peripheral::{Peripheral, PeripheralRef}, - wifi::Protocol, + wifi::{Protocol, RxControlInfo}, EspWifiInitialization, }; @@ -179,57 +179,6 @@ pub struct PeerInfo { // we always use STA for now } -#[cfg(not(any(esp32c6)))] -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct RxControlInfo { - pub rssi: i32, - pub rate: u32, - pub sig_mode: u32, - pub mcs: u32, - pub cwb: u32, - pub smoothing: u32, - pub not_sounding: u32, - pub aggregation: u32, - pub stbc: u32, - pub fec_coding: u32, - pub sgi: u32, - pub ampdu_cnt: u32, - pub channel: u32, - pub secondary_channel: u32, - pub timestamp: u32, - pub noise_floor: i32, - pub ant: u32, - pub sig_len: u32, - pub rx_state: u32, -} - -#[cfg(esp32c6)] -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct RxControlInfo { - pub rssi: i32, - pub rate: u32, - pub sig_len: u32, - pub rx_state: u32, - pub dump_len: u32, - pub he_sigb_len: u32, - pub cur_single_mpdu: u32, - pub cur_bb_format: u32, - pub rx_channel_estimate_info_vld: u32, - pub rx_channel_estimate_len: u32, - pub second: u32, - pub channel: u32, - pub data_rssi: i32, - pub noise_floor: u32, - pub is_group: u32, - pub rxend_state: u32, - pub rxmatch3: u32, - pub rxmatch2: u32, - pub rxmatch1: u32, - pub rxmatch0: u32, -} - #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ReceiveInfo { @@ -813,52 +762,7 @@ unsafe extern "C" fn rcv_cb( ]; let rx_cntl = (*esp_now_info).rx_ctrl; - #[cfg(not(any(esp32c6)))] - let rx_control = RxControlInfo { - rssi: (*rx_cntl).rssi(), - rate: (*rx_cntl).rate(), - sig_mode: (*rx_cntl).sig_mode(), - mcs: (*rx_cntl).mcs(), - cwb: (*rx_cntl).cwb(), - smoothing: (*rx_cntl).smoothing(), - not_sounding: (*rx_cntl).not_sounding(), - aggregation: (*rx_cntl).aggregation(), - stbc: (*rx_cntl).stbc(), - fec_coding: (*rx_cntl).fec_coding(), - sgi: (*rx_cntl).sgi(), - ampdu_cnt: (*rx_cntl).ampdu_cnt(), - channel: (*rx_cntl).channel(), - secondary_channel: (*rx_cntl).secondary_channel(), - timestamp: (*rx_cntl).timestamp(), - noise_floor: (*rx_cntl).noise_floor(), - ant: (*rx_cntl).ant(), - sig_len: (*rx_cntl).sig_len(), - rx_state: (*rx_cntl).rx_state(), - }; - - #[cfg(esp32c6)] - let rx_control = RxControlInfo { - rssi: (*rx_cntl).rssi(), - rate: (*rx_cntl).rate(), - sig_len: (*rx_cntl).sig_len(), - rx_state: (*rx_cntl).rx_state(), - dump_len: (*rx_cntl).dump_len(), - he_sigb_len: (*rx_cntl).he_sigb_len(), - cur_single_mpdu: (*rx_cntl).cur_single_mpdu(), - cur_bb_format: (*rx_cntl).cur_bb_format(), - rx_channel_estimate_info_vld: (*rx_cntl).rx_channel_estimate_info_vld(), - rx_channel_estimate_len: (*rx_cntl).rx_channel_estimate_len(), - second: (*rx_cntl).second(), - channel: (*rx_cntl).channel(), - data_rssi: (*rx_cntl).data_rssi(), - noise_floor: (*rx_cntl).noise_floor(), - is_group: (*rx_cntl).is_group(), - rxend_state: (*rx_cntl).rxend_state(), - rxmatch3: (*rx_cntl).rxmatch3(), - rxmatch2: (*rx_cntl).rxmatch2(), - rxmatch1: (*rx_cntl).rxmatch1(), - rxmatch0: (*rx_cntl).rxmatch0(), - }; + let rx_control = RxControlInfo::from_raw(rx_cntl); let info = ReceiveInfo { src_address: src, diff --git a/esp-wifi/src/wifi/mod.rs b/esp-wifi/src/wifi/mod.rs index f93dbc2e9..e4771f201 100644 --- a/esp-wifi/src/wifi/mod.rs +++ b/esp-wifi/src/wifi/mod.rs @@ -6,15 +6,23 @@ pub(crate) mod state; use core::{ cell::{RefCell, RefMut}, fmt::Debug, - mem, - mem::MaybeUninit, + mem::{self, MaybeUninit}, ptr::addr_of, time::Duration, }; use critical_section::{CriticalSection, Mutex}; use enumset::{EnumSet, EnumSetType}; +#[cfg(feature = "sniffer")] use esp_wifi_sys::include::{ + esp_wifi_80211_tx, + esp_wifi_set_promiscuous, + esp_wifi_set_promiscuous_rx_cb, + wifi_promiscuous_pkt_t, + wifi_promiscuous_pkt_type_t, +}; +use esp_wifi_sys::include::{ + wifi_pkt_rx_ctrl_t, WIFI_PROTOCOL_11AX, WIFI_PROTOCOL_11B, WIFI_PROTOCOL_11G, @@ -25,6 +33,8 @@ use num_derive::FromPrimitive; use num_traits::FromPrimitive; #[doc(hidden)] pub(crate) use os_adapter::*; +#[cfg(feature = "sniffer")] +use portable_atomic::AtomicBool; use portable_atomic::{AtomicUsize, Ordering}; #[cfg(feature = "smoltcp")] use smoltcp::phy::{Device, DeviceCapabilities, RxToken, TxToken}; @@ -1770,10 +1780,208 @@ fn convert_ap_info(record: &include::wifi_ap_record_t) -> AccessPointInfo { } } +#[cfg(not(any(esp32c6)))] +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RxControlInfo { + pub rssi: i32, + pub rate: u32, + pub sig_mode: u32, + pub mcs: u32, + pub cwb: u32, + pub smoothing: u32, + pub not_sounding: u32, + pub aggregation: u32, + pub stbc: u32, + pub fec_coding: u32, + pub sgi: u32, + pub ampdu_cnt: u32, + pub channel: u32, + pub secondary_channel: u32, + pub timestamp: u32, + pub noise_floor: i32, + pub ant: u32, + pub sig_len: u32, + pub rx_state: u32, +} + +#[cfg(esp32c6)] +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RxControlInfo { + pub rssi: i32, + pub rate: u32, + pub sig_len: u32, + pub rx_state: u32, + pub dump_len: u32, + pub he_sigb_len: u32, + pub cur_single_mpdu: u32, + pub cur_bb_format: u32, + pub rx_channel_estimate_info_vld: u32, + pub rx_channel_estimate_len: u32, + pub second: u32, + pub channel: u32, + pub data_rssi: i32, + pub noise_floor: u32, + pub is_group: u32, + pub rxend_state: u32, + pub rxmatch3: u32, + pub rxmatch2: u32, + pub rxmatch1: u32, + pub rxmatch0: u32, +} +impl RxControlInfo { + /// Create an instance from a raw pointer to [wifi_pkt_rx_ctrl_t]. + /// + /// # Safety + /// When calling this, you must ensure, that `rx_cntl` points to a valid + /// instance of [wifi_pkt_rx_ctrl_t]. + pub unsafe fn from_raw(rx_cntl: *const wifi_pkt_rx_ctrl_t) -> Self { + #[cfg(not(esp32c6))] + let rx_control_info = RxControlInfo { + rssi: (*rx_cntl).rssi(), + rate: (*rx_cntl).rate(), + sig_mode: (*rx_cntl).sig_mode(), + mcs: (*rx_cntl).mcs(), + cwb: (*rx_cntl).cwb(), + smoothing: (*rx_cntl).smoothing(), + not_sounding: (*rx_cntl).not_sounding(), + aggregation: (*rx_cntl).aggregation(), + stbc: (*rx_cntl).stbc(), + fec_coding: (*rx_cntl).fec_coding(), + sgi: (*rx_cntl).sgi(), + ampdu_cnt: (*rx_cntl).ampdu_cnt(), + channel: (*rx_cntl).channel(), + secondary_channel: (*rx_cntl).secondary_channel(), + timestamp: (*rx_cntl).timestamp(), + noise_floor: (*rx_cntl).noise_floor(), + ant: (*rx_cntl).ant(), + sig_len: (*rx_cntl).sig_len(), + rx_state: (*rx_cntl).rx_state(), + }; + #[cfg(esp32c6)] + let rx_control_info = RxControlInfo { + rssi: (*rx_cntl).rssi(), + rate: (*rx_cntl).rate(), + sig_len: (*rx_cntl).sig_len(), + rx_state: (*rx_cntl).rx_state(), + dump_len: (*rx_cntl).dump_len(), + he_sigb_len: (*rx_cntl).he_sigb_len(), + cur_single_mpdu: (*rx_cntl).cur_single_mpdu(), + cur_bb_format: (*rx_cntl).cur_bb_format(), + rx_channel_estimate_info_vld: (*rx_cntl).rx_channel_estimate_info_vld(), + rx_channel_estimate_len: (*rx_cntl).rx_channel_estimate_len(), + second: (*rx_cntl).second(), + channel: (*rx_cntl).channel(), + data_rssi: (*rx_cntl).data_rssi(), + noise_floor: (*rx_cntl).noise_floor(), + is_group: (*rx_cntl).is_group(), + rxend_state: (*rx_cntl).rxend_state(), + rxmatch3: (*rx_cntl).rxmatch3(), + rxmatch2: (*rx_cntl).rxmatch2(), + rxmatch1: (*rx_cntl).rxmatch1(), + rxmatch0: (*rx_cntl).rxmatch0(), + }; + rx_control_info + } +} +#[cfg(feature = "sniffer")] +pub struct PromiscuousPkt<'a> { + pub rx_cntl: RxControlInfo, + pub frame_type: wifi_promiscuous_pkt_type_t, + pub len: usize, + pub data: &'a [u8], +} +#[cfg(feature = "sniffer")] +impl PromiscuousPkt<'_> { + /// # Safety + /// When calling this, you have to ensure, that `buf` points to a valid + /// [wifi_promiscuous_pkt_t]. + pub(crate) unsafe fn from_raw( + buf: *const wifi_promiscuous_pkt_t, + frame_type: wifi_promiscuous_pkt_type_t, + ) -> Self { + let rx_cntl = RxControlInfo::from_raw(&(*buf).rx_ctrl); + let len = rx_cntl.sig_len as usize; + PromiscuousPkt { + rx_cntl, + frame_type, + len, + data: core::slice::from_raw_parts( + (buf as *const u8).add(size_of::()), + len, + ), + } + } +} + +#[cfg(feature = "sniffer")] +static SNIFFER_CB: Mutex>> = Mutex::new(RefCell::new(None)); + +#[cfg(feature = "sniffer")] +unsafe extern "C" fn promiscuous_rx_cb(buf: *mut core::ffi::c_void, frame_type: u32) { + critical_section::with(|cs| { + let Some(sniffer_callback) = *SNIFFER_CB.borrow_ref(cs) else { + return; + }; + let promiscuous_pkt = PromiscuousPkt::from_raw(buf as *const _, frame_type); + sniffer_callback(promiscuous_pkt); + }); +} + +#[cfg(feature = "sniffer")] +/// A wifi sniffer. +pub struct Sniffer { + promiscuous_mode_enabled: AtomicBool, +} +#[cfg(feature = "sniffer")] +impl Sniffer { + pub(crate) fn new() -> Self { + // This shouldn't fail, since the way this is created, means that wifi will + // always be initialized. + esp_wifi_result!(unsafe { esp_wifi_set_promiscuous_rx_cb(Some(promiscuous_rx_cb)) }) + .unwrap(); + Self { + promiscuous_mode_enabled: AtomicBool::new(false), + } + } + /// Set promiscuous mode enabled or disabled. + pub fn set_promiscuous_mode(&self, enabled: bool) -> Result<(), WifiError> { + esp_wifi_result!(unsafe { esp_wifi_set_promiscuous(enabled) })?; + self.promiscuous_mode_enabled + .store(enabled, Ordering::Relaxed); + Ok(()) + } + /// Transmit a raw frame. + pub fn send_raw_frame( + &mut self, + use_sta_interface: bool, + buffer: &[u8], + use_internal_seq_num: bool, + ) -> Result<(), WifiError> { + esp_wifi_result!(unsafe { + esp_wifi_80211_tx( + if use_sta_interface { 0 } else { 1 } as wifi_interface_t, + buffer.as_ptr() as *const _, + buffer.len() as i32, + use_internal_seq_num, + ) + }) + } + /// Set the callback for receiving a packet. + pub fn set_receive_cb(&mut self, cb: fn(PromiscuousPkt)) { + critical_section::with(|cs| { + *SNIFFER_CB.borrow_ref_mut(cs) = Some(cb); + }); + } +} + /// A wifi controller pub struct WifiController<'d> { _device: PeripheralRef<'d, crate::hal::peripherals::WIFI>, config: Configuration, + #[cfg(feature = "sniffer")] + sniffer_taken: AtomicBool, } impl<'d> WifiController<'d> { @@ -1792,6 +2000,8 @@ impl<'d> WifiController<'d> { let mut this = Self { _device, config: Default::default(), + #[cfg(feature = "sniffer")] + sniffer_taken: AtomicBool::new(false), }; let mode = WifiMode::try_from(&config)?; @@ -1801,6 +2011,18 @@ impl<'d> WifiController<'d> { this.set_configuration(&config)?; Ok(this) } + #[cfg(feature = "sniffer")] + pub fn take_sniffer(&self) -> Option { + if self + .sniffer_taken + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + == Ok(false) + { + Some(Sniffer::new()) + } else { + None + } + } /// Set the wifi protocol. /// diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4aa78acfa..c05bee62d 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -42,6 +42,7 @@ fugit = "0.3.7" heapless = "0.8.0" hex-literal = "0.4.1" hmac = { version = "0.12.1", default-features = false } +ieee80211 = { version = "0.4.0", default-features = false } ieee802154 = "0.6.1" lis3dh-async = "0.9.3" log = "0.4.22" diff --git a/examples/src/bin/wifi_80211_tx.rs b/examples/src/bin/wifi_80211_tx.rs new file mode 100644 index 000000000..d3d70ee06 --- /dev/null +++ b/examples/src/bin/wifi_80211_tx.rs @@ -0,0 +1,123 @@ +//! WiFi frame injection example +//! +//! Periodically transmits a beacon frame. + +//% FEATURES: esp-wifi esp-wifi/wifi-default esp-wifi/wifi esp-wifi/utils esp-wifi/sniffer +//% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 + +#![no_std] +#![no_main] + +use core::marker::PhantomData; + +use esp_backtrace as _; +use esp_hal::{ + clock::ClockControl, + delay::Delay, + peripherals::Peripherals, + prelude::*, + rng::Rng, + system::SystemControl, + timer::{timg::TimerGroup, ErasedTimer, PeriodicTimer}, +}; +use esp_wifi::{initialize, wifi, EspWifiInitFor}; +use ieee80211::{ + common::{CapabilitiesInformation, FCFFlags}, + element_chain, + elements::{DSSSParameterSetElement, RawIEEE80211Element, SSIDElement}, + mgmt_frame::{body::BeaconBody, header::ManagementFrameHeader, BeaconFrame}, + scroll::Pwrite, + supported_rates, +}; + +const SSID: &str = "esp-wifi 802.11 injection"; +/// This is an arbitrary MAC address, used for the fake beacon frames. +const MAC_ADDRESS: [u8; 6] = [0x00, 0x80, 0x41, 0x13, 0x37, 0x42]; + +#[entry] +fn main() -> ! { + esp_println::logger::init_logger_from_env(); + + let peripherals = Peripherals::take(); + + let system = SystemControl::new(peripherals.SYSTEM); + let clocks = ClockControl::max(system.clock_control).freeze(); + + let delay = Delay::new(&clocks); + + let timg0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let timer0: ErasedTimer = timg0.timer0.into(); + let timer = PeriodicTimer::new(timer0); + + let init = initialize( + EspWifiInitFor::Wifi, + timer, + Rng::new(peripherals.RNG), + peripherals.RADIO_CLK, + &clocks, + ) + .unwrap(); + + let wifi = peripherals.WIFI; + + // We must initialize some kind of interface and start it. + let (_, mut controller) = wifi::new_with_mode(&init, wifi, wifi::WifiApDevice).unwrap(); + controller.start().unwrap(); + + let mut sniffer = controller.take_sniffer().unwrap(); + + // Create a buffer, which can hold the enitre serialized beacon frame. + let mut beacon = [0u8; 300]; + let length = beacon + .pwrite( + BeaconFrame { + header: ManagementFrameHeader { + fcf_flags: FCFFlags::new(), + duration: 0, + receiver_address: [0xff; 6].into(), + transmitter_address: MAC_ADDRESS.into(), + bssid: MAC_ADDRESS.into(), + ..Default::default() + }, + body: BeaconBody { + timestamp: 0, + // We transmit a beacon every 100 ms/TUs + beacon_interval: 100, + capabilities_info: CapabilitiesInformation::new().with_is_ess(true), + elements: element_chain! { + SSIDElement::new(SSID).unwrap(), + // These are known good values. + supported_rates![ + 1 B, + 2 B, + 5.5 B, + 11 B, + 6, + 9, + 12, + 18 + ], + DSSSParameterSetElement { + current_channel: 1, + }, + // This contains the Traffic Indication Map(TIM), for which `ieee80211-rs` currently lacks support. + RawIEEE80211Element { + tlv_type: 5, + slice: [0x01, 0x02, 0x00, 0x00].as_slice(), + _phantom: PhantomData + } + }, + _phantom: PhantomData, + }, + }, + 0, + ) + .unwrap(); + // Only use the actually written bytes. + let beacon = &beacon[..length]; + + loop { + sniffer.send_raw_frame(false, beacon, false).unwrap(); + delay.delay(100.millis()); + } +} diff --git a/examples/src/bin/wifi_sniffer.rs b/examples/src/bin/wifi_sniffer.rs new file mode 100644 index 000000000..906263275 --- /dev/null +++ b/examples/src/bin/wifi_sniffer.rs @@ -0,0 +1,85 @@ +//! WiFi sniffer example +//! +//! Sniffs for beacon frames. + +//% FEATURES: esp-wifi esp-wifi/wifi-default esp-wifi/wifi esp-wifi/utils esp-wifi/sniffer +//% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 + +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::{ + collections::btree_set::BTreeSet, + string::{String, ToString}, +}; +use core::cell::RefCell; + +use critical_section::Mutex; +use esp_alloc::heap_allocator; +use esp_backtrace as _; +use esp_hal::{ + clock::ClockControl, + peripherals::Peripherals, + prelude::*, + rng::Rng, + system::SystemControl, + timer::{timg::TimerGroup, ErasedTimer, PeriodicTimer}, +}; +use esp_println::println; +use esp_wifi::{initialize, wifi, EspWifiInitFor}; +use ieee80211::{match_frames, mgmt_frame::BeaconFrame}; + +static KNOWN_SSIDS: Mutex>> = Mutex::new(RefCell::new(BTreeSet::new())); + +#[entry] +fn main() -> ! { + esp_println::logger::init_logger_from_env(); + + let peripherals = Peripherals::take(); + // Create a heap allocator, with 32kB of space. + heap_allocator!(32_168); + + let system = SystemControl::new(peripherals.SYSTEM); + let clocks = ClockControl::max(system.clock_control).freeze(); + + let timg0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let timer0: ErasedTimer = timg0.timer0.into(); + let timer = PeriodicTimer::new(timer0); + + let init = initialize( + EspWifiInitFor::Wifi, + timer, + Rng::new(peripherals.RNG), + peripherals.RADIO_CLK, + &clocks, + ) + .unwrap(); + + let wifi = peripherals.WIFI; + + // We must initialize some kind of interface and start it. + let (_, mut controller) = wifi::new_with_mode(&init, wifi, wifi::WifiApDevice).unwrap(); + controller.start().unwrap(); + + let mut sniffer = controller.take_sniffer().unwrap(); + sniffer.set_promiscuous_mode(true).unwrap(); + sniffer.set_receive_cb(|packet| { + let _ = match_frames! { + packet.data, + beacon = BeaconFrame => { + let Some(ssid) = beacon.ssid() else { + return; + }; + if critical_section::with(|cs| { + KNOWN_SSIDS.borrow_ref_mut(cs).insert(ssid.to_string()) + }) { + println!("Found new AP with SSID: {ssid}"); + } + } + }; + }); + + loop {} +}