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.
This commit is contained in:
Frostie314159 2024-08-19 08:51:18 +02:00 committed by GitHub
parent 59728c523f
commit d1acacb757
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 437 additions and 100 deletions

View File

@ -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 `embedded_io::{ReadReady, WriteReady}` traits for `WifiStack` (#1882)
- Implement `queue_msg_waiting` on the os_adapter (#1925) - Implement `queue_msg_waiting` on the os_adapter (#1925)
- Added API for promiscuous mode (#1935)
### Changed ### Changed

View File

@ -133,6 +133,7 @@ dhcpv4 = ["wifi", "utils", "smoltcp?/proto-dhcpv4", "smoltcp?/socket-dhcpv4"]
wifi-default = ["ipv4", "tcp", "udp", "icmp", "igmp", "dns", "dhcpv4"] wifi-default = ["ipv4", "tcp", "udp", "icmp", "igmp", "dns", "dhcpv4"]
defmt = ["dep:defmt", "smoltcp?/defmt", "esp-hal/defmt"] defmt = ["dep:defmt", "smoltcp?/defmt", "esp-hal/defmt"]
log = ["dep:log", "esp-hal/log"] log = ["dep:log", "esp-hal/log"]
sniffer = ["wifi"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [ features = [

View File

@ -19,7 +19,7 @@ use crate::{
binary::include::*, binary::include::*,
compat::queue::SimpleQueue, compat::queue::SimpleQueue,
hal::peripheral::{Peripheral, PeripheralRef}, hal::peripheral::{Peripheral, PeripheralRef},
wifi::Protocol, wifi::{Protocol, RxControlInfo},
EspWifiInitialization, EspWifiInitialization,
}; };
@ -179,57 +179,6 @@ pub struct PeerInfo {
// we always use STA for now // 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)] #[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ReceiveInfo { pub struct ReceiveInfo {
@ -813,52 +762,7 @@ unsafe extern "C" fn rcv_cb(
]; ];
let rx_cntl = (*esp_now_info).rx_ctrl; let rx_cntl = (*esp_now_info).rx_ctrl;
#[cfg(not(any(esp32c6)))] let rx_control = RxControlInfo::from_raw(rx_cntl);
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 info = ReceiveInfo { let info = ReceiveInfo {
src_address: src, src_address: src,

View File

@ -6,15 +6,23 @@ pub(crate) mod state;
use core::{ use core::{
cell::{RefCell, RefMut}, cell::{RefCell, RefMut},
fmt::Debug, fmt::Debug,
mem, mem::{self, MaybeUninit},
mem::MaybeUninit,
ptr::addr_of, ptr::addr_of,
time::Duration, time::Duration,
}; };
use critical_section::{CriticalSection, Mutex}; use critical_section::{CriticalSection, Mutex};
use enumset::{EnumSet, EnumSetType}; use enumset::{EnumSet, EnumSetType};
#[cfg(feature = "sniffer")]
use esp_wifi_sys::include::{ 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_11AX,
WIFI_PROTOCOL_11B, WIFI_PROTOCOL_11B,
WIFI_PROTOCOL_11G, WIFI_PROTOCOL_11G,
@ -25,6 +33,8 @@ use num_derive::FromPrimitive;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
#[doc(hidden)] #[doc(hidden)]
pub(crate) use os_adapter::*; pub(crate) use os_adapter::*;
#[cfg(feature = "sniffer")]
use portable_atomic::AtomicBool;
use portable_atomic::{AtomicUsize, Ordering}; use portable_atomic::{AtomicUsize, Ordering};
#[cfg(feature = "smoltcp")] #[cfg(feature = "smoltcp")]
use smoltcp::phy::{Device, DeviceCapabilities, RxToken, TxToken}; 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::<wifi_pkt_rx_ctrl_t>()),
len,
),
}
}
}
#[cfg(feature = "sniffer")]
static SNIFFER_CB: Mutex<RefCell<Option<fn(PromiscuousPkt)>>> = 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 /// A wifi controller
pub struct WifiController<'d> { pub struct WifiController<'d> {
_device: PeripheralRef<'d, crate::hal::peripherals::WIFI>, _device: PeripheralRef<'d, crate::hal::peripherals::WIFI>,
config: Configuration, config: Configuration,
#[cfg(feature = "sniffer")]
sniffer_taken: AtomicBool,
} }
impl<'d> WifiController<'d> { impl<'d> WifiController<'d> {
@ -1792,6 +2000,8 @@ impl<'d> WifiController<'d> {
let mut this = Self { let mut this = Self {
_device, _device,
config: Default::default(), config: Default::default(),
#[cfg(feature = "sniffer")]
sniffer_taken: AtomicBool::new(false),
}; };
let mode = WifiMode::try_from(&config)?; let mode = WifiMode::try_from(&config)?;
@ -1801,6 +2011,18 @@ impl<'d> WifiController<'d> {
this.set_configuration(&config)?; this.set_configuration(&config)?;
Ok(this) Ok(this)
} }
#[cfg(feature = "sniffer")]
pub fn take_sniffer(&self) -> Option<Sniffer> {
if self
.sniffer_taken
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
== Ok(false)
{
Some(Sniffer::new())
} else {
None
}
}
/// Set the wifi protocol. /// Set the wifi protocol.
/// ///

View File

@ -42,6 +42,7 @@ fugit = "0.3.7"
heapless = "0.8.0" heapless = "0.8.0"
hex-literal = "0.4.1" hex-literal = "0.4.1"
hmac = { version = "0.12.1", default-features = false } hmac = { version = "0.12.1", default-features = false }
ieee80211 = { version = "0.4.0", default-features = false }
ieee802154 = "0.6.1" ieee802154 = "0.6.1"
lis3dh-async = "0.9.3" lis3dh-async = "0.9.3"
log = "0.4.22" log = "0.4.22"

View File

@ -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());
}
}

View File

@ -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<RefCell<BTreeSet<String>>> = 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 {}
}