diff --git a/examples/dump_nl80211_scan.rs b/examples/dump_nl80211_scan.rs new file mode 100644 index 0000000..4c132c0 --- /dev/null +++ b/examples/dump_nl80211_scan.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +use std::env::args; + +use anyhow::{bail, Context, Error}; +use futures::stream::TryStreamExt; + +fn main() -> Result<(), Error> { + let argv: Vec<_> = args().collect(); + + if argv.len() < 2 { + eprintln!("Usage: dump_nl80211_scan "); + bail!("Required arguments not given"); + } + + let err_msg = format!("Invalid interface index value: {}", argv[1]); + let index = argv[1].parse::().context(err_msg)?; + + let rt = tokio::runtime::Builder::new_current_thread() + .enable_io() + .build() + .unwrap(); + rt.block_on(dump_scan(index)); + + Ok(()) +} + +async fn dump_scan(if_index: u32) { + let (connection, handle, _) = wl_nl80211::new_connection().unwrap(); + tokio::spawn(connection); + + let mut scan_handle = handle.scan().dump(if_index).execute().await; + + let mut msgs = Vec::new(); + while let Some(msg) = scan_handle.try_next().await.unwrap() { + msgs.push(msg); + } + assert!(!msgs.is_empty()); + for msg in msgs { + println!("{:?}", msg); + } +} diff --git a/src/attr.rs b/src/attr.rs index bf50f5e..efe45d4 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -39,8 +39,8 @@ use netlink_packet_utils::{ use crate::{ bytes::{write_u16, write_u32, write_u64}, wiphy::Nl80211Commands, - Nl80211Band, Nl80211BandTypes, Nl80211ChannelWidth, Nl80211CipherSuit, - Nl80211Command, Nl80211ExtFeature, Nl80211ExtFeatures, + Nl80211Band, Nl80211BandTypes, Nl80211BssInfo, Nl80211ChannelWidth, + Nl80211CipherSuit, Nl80211Command, Nl80211ExtFeature, Nl80211ExtFeatures, Nl80211ExtendedCapability, Nl80211Features, Nl80211HtCapabilityMask, Nl80211HtWiphyChannelType, Nl80211IfMode, Nl80211IfTypeExtCapa, Nl80211IfTypeExtCapas, Nl80211IfaceComb, Nl80211IfaceFrameType, @@ -163,7 +163,7 @@ const NL80211_ATTR_MAX_NUM_SCAN_SSIDS: u16 = 43; // const NL80211_ATTR_SCAN_FREQUENCIES:u16 = 44; // const NL80211_ATTR_SCAN_SSIDS:u16 = 45; const NL80211_ATTR_GENERATION: u16 = 46; -// const NL80211_ATTR_BSS:u16 = 47; +const NL80211_ATTR_BSS: u16 = 47; // const NL80211_ATTR_REG_INITIATOR:u16 = 48; // const NL80211_ATTR_REG_TYPE:u16 = 49; const NL80211_ATTR_SUPPORTED_COMMANDS: u16 = 50; @@ -544,6 +544,8 @@ pub enum Nl80211Attr { /// not specifying an address with set hardware timestamp) is /// supported. MaxHwTimestampPeers(u16), + /// Basic Service Set (BSS) + Bss(Vec), Other(DefaultNla), } @@ -633,6 +635,7 @@ impl Nla for Nl80211Attr { | Self::MaxNumAkmSuites(_) | Self::MaxHwTimestampPeers(_) => 2, Self::Bands(_) => Nl80211BandTypes::LENGTH, + Self::Bss(v) => v.as_slice().buffer_len(), Self::Other(attr) => attr.value_len(), } } @@ -729,6 +732,7 @@ impl Nla for Nl80211Attr { Self::Bands(_) => NL80211_ATTR_BANDS, Self::MaxNumAkmSuites(_) => NL80211_ATTR_MAX_NUM_AKM_SUITES, Self::MaxHwTimestampPeers(_) => NL80211_ATTR_MAX_HW_TIMESTAMP_PEERS, + Self::Bss(_) => NL80211_ATTR_BSS, Self::Other(attr) => attr.kind(), } } @@ -832,6 +836,7 @@ impl Nla for Nl80211Attr { | Self::MaxNumAkmSuites(d) | Self::MaxHwTimestampPeers(d) => write_u16(buffer, *d), Self::Bands(v) => v.emit(buffer), + Self::Bss(v) => v.as_slice().emit(buffer), Self::Other(attr) => attr.emit(buffer), } } @@ -892,6 +897,16 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nl80211Attr { ); Self::Generation(parse_u32(payload).context(err_msg)?) } + NL80211_ATTR_BSS => { + let err_msg = + format!("Invalid NL80211_ATTR_BSS value {:?}", payload); + let mut nlas = Vec::new(); + for nla in NlasIterator::new(payload) { + let nla = &nla.context(err_msg.clone())?; + nlas.push(Nl80211BssInfo::parse(nla)?); + } + Self::Bss(nlas) + } NL80211_ATTR_4ADDR => { let err_msg = format!("Invalid NL80211_ATTR_4ADDR value {:?}", payload); diff --git a/src/bytes.rs b/src/bytes.rs index 709cfe6..3148a1b 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT +use netlink_packet_utils::DecodeError; + pub(crate) fn write_u16(buffer: &mut [u8], value: u16) { buffer[..2].copy_from_slice(&value.to_ne_bytes()) } @@ -12,10 +14,18 @@ pub(crate) fn write_u32(buffer: &mut [u8], value: u32) { buffer[..4].copy_from_slice(&value.to_ne_bytes()) } +pub(crate) fn write_u32_le(buffer: &mut [u8], value: u32) { + buffer[..4].copy_from_slice(&value.to_le_bytes()) +} + pub(crate) fn write_u64(buffer: &mut [u8], value: u64) { buffer[..8].copy_from_slice(&value.to_ne_bytes()) } +pub(crate) fn write_i32(buffer: &mut [u8], value: i32) { + buffer[..4].copy_from_slice(&value.to_ne_bytes()) +} + pub(crate) fn get_bit(data: &[u8], pos: usize) -> bool { let index: usize = pos / 8; let bit_pos: usize = pos % 8; @@ -44,3 +54,10 @@ pub(crate) fn get_bits_as_u8(data: &[u8], start: usize, end: usize) -> u8 { } ret } + +pub(crate) fn parse_u16_le(payload: &[u8]) -> Result { + if payload.len() < 2 { + return Err(format!("Invalid payload for u16: {:?}", payload).into()); + } + Ok(u16::from_le_bytes([payload[0], payload[1]])) +} diff --git a/src/element.rs b/src/element.rs new file mode 100644 index 0000000..2ba1523 --- /dev/null +++ b/src/element.rs @@ -0,0 +1,984 @@ +// SPDX-License-Identifier: MIT + +use anyhow::Context; +use netlink_packet_utils::{ + parsers::{parse_string, parse_u8}, + DecodeError, Emitable, Parseable, +}; + +use crate::bytes::{parse_u16_le, write_u16_le, write_u32_le}; + +pub(crate) struct Nl80211Elements(Vec); + +impl + ?Sized> Parseable for Nl80211Elements { + fn parse(buf: &T) -> Result { + let buf = buf.as_ref(); + let mut offset = 0; + let mut ret = Vec::new(); + while offset < buf.len() && offset + 1 < buf.len() { + let length = buf[offset + 1] as usize + 2; + if buf.len() < offset + length { + break; + } + let element = Nl80211Element::parse(&buf[offset..offset + length])?; + offset += length; + ret.push(element); + } + Ok(Self(ret)) + } +} + +impl Emitable for Nl80211Elements { + fn buffer_len(&self) -> usize { + self.0.as_slice().iter().map(|e| e.buffer_len()).sum() + } + + fn emit(&self, buffer: &mut [u8]) { + let mut offset = 0; + for element in self.0.as_slice().iter() { + element.emit(&mut buffer[offset..(offset + element.buffer_len())]); + offset += element.buffer_len(); + } + } +} + +impl From<&Vec> for Nl80211Elements { + fn from(d: &Vec) -> Self { + Self(d.to_vec()) + } +} + +impl From for Vec { + fn from(v: Nl80211Elements) -> Vec { + v.0 + } +} + +// These are `Element IDs` defined in IEEE 802.11-2020 +const ELEMENT_ID_SSID: u8 = 0; +const ELEMENT_ID_SUPPORTED_RATES: u8 = 1; +const ELEMENT_ID_CHANNEL: u8 = 3; +const ELEMENT_ID_COUNTRY: u8 = 7; +const ELEMENT_ID_RSN: u8 = 48; +const ELEMENT_ID_VENDOR: u8 = 221; + +/// IEEE 802.11-2020 `9.4.2 Elements` +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum Nl80211Element { + Ssid(String), + /// Supported rates in units of 500 kb/s, if necessary rounded up to the + /// next 500 kb/ + SupportedRatesAndSelectors(Vec), + /// Allow channel number identification for STAs. + Channel(u8), + Country(Nl80211ElementCountry), + Rsn(Nl80211ElementRsn), + /// Vendor specific data. + Vendor(Vec), + Other(u8, Vec), +} + +impl Nl80211Element { + /// The ID field in IEEE 802.11-2020 `Figure 9-145 Element format` + pub(crate) fn id(&self) -> u8 { + match self { + Self::Ssid(_) => ELEMENT_ID_SSID, + Self::SupportedRatesAndSelectors(_) => ELEMENT_ID_SUPPORTED_RATES, + Self::Channel(_) => ELEMENT_ID_CHANNEL, + Self::Country(_) => ELEMENT_ID_COUNTRY, + Self::Rsn(_) => ELEMENT_ID_RSN, + Self::Vendor(_) => ELEMENT_ID_VENDOR, + Self::Other(id, _) => *id, + } + } + + /// The length field in IEEE 802.11-2020 `Figure 9-145 Element format` + pub(crate) fn length(&self) -> u8 { + match self { + Self::Ssid(v) => v.len() as u8, + Self::SupportedRatesAndSelectors(v) => v.len() as u8, + Self::Channel(_) => 1, + Self::Country(v) => v.buffer_len() as u8, + Self::Rsn(v) => v.buffer_len() as u8, + Self::Vendor(v) => v.len() as u8, + Self::Other(_, data) => (data.len()) as u8, + } + } +} + +impl + ?Sized> Parseable for Nl80211Element { + fn parse(buf: &T) -> Result { + let buf = buf.as_ref(); + if buf.len() <= 2 { + return Err( + format!("Invalid length of Nl80211Element {buf:?}").into() + ); + } + let id = buf[0]; + let length = buf[1]; + let payload = &buf[2..length as usize + 2]; + Ok(match id { + ELEMENT_ID_SSID => Self::Ssid( + parse_string(payload) + .context(format!("Invalid SSID {payload:?}"))?, + ), + ELEMENT_ID_SUPPORTED_RATES => Self::SupportedRatesAndSelectors( + payload + .iter() + .map(|d| Nl80211RateAndSelector::from(*d)) + .collect(), + ), + ELEMENT_ID_CHANNEL => Self::Channel(parse_u8(payload).context( + format!("Invalid DSSS(channel) element {payload:?}"), + )?), + ELEMENT_ID_COUNTRY => { + Self::Country(Nl80211ElementCountry::parse(payload)?) + } + ELEMENT_ID_RSN => Self::Rsn(Nl80211ElementRsn::parse(payload)?), + ELEMENT_ID_VENDOR => Self::Vendor(payload.to_vec()), + _ => Self::Other(id, payload.to_vec()), + }) + } +} + +impl Emitable for Nl80211Element { + fn buffer_len(&self) -> usize { + self.length() as usize + 2 + } + + fn emit(&self, buffer: &mut [u8]) { + buffer[0] = self.id(); + buffer[1] = self.length(); + let payload = &mut buffer[2..self.length() as usize + 2]; + match self { + Self::Ssid(s) => { + // IEEE 802.11-2020 indicate it is optional to have NULL + // terminator for this string. + payload.copy_from_slice(s.as_bytes()); + } + Self::SupportedRatesAndSelectors(v) => { + let raw: Vec = + v.as_slice().iter().map(|v| u8::from(*v)).collect(); + payload.copy_from_slice(raw.as_slice()); + } + Self::Channel(v) => buffer[0] = *v, + Self::Country(v) => v.emit(buffer), + Self::Rsn(v) => v.emit(buffer), + Self::Vendor(v) => buffer[..v.len()].copy_from_slice(v.as_slice()), + Self::Other(_, data) => { + payload.copy_from_slice(data.as_slice()); + } + } + } +} + +const BSS_MEMBERSHIP_SELECTOR_SAE_HASH: u8 = 123; +const BSS_MEMBERSHIP_SELECTOR_EPD: u8 = 124; +const BSS_MEMBERSHIP_SELECTOR_GLK: u8 = 125; +const BSS_MEMBERSHIP_SELECTOR_VHT_PHY: u8 = 126; +const BSS_MEMBERSHIP_SELECTOR_HT_PHY: u8 = 127; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub enum Nl80211RateAndSelector { + /// BSS basic rate set in Mb/s. + BssBasicRateSet(u8), + /// Rate in Mb/s. + Rate(u8), + SelectorHt, + SelectorVht, + /// Indicates that support for the mandatory features of 11.50 is required + /// in order to join the BSS that was the source of the Supported Rates and + /// BSS Membership Selectors element or Extended Supported Rates and BSS + /// Membership Selectors element containing this value. + SelectorGlk, + /// Indicates that support for EPD is required in order to join the BSS + /// that was the source of the Supported Rates and BSS Membership + /// Selectors element or Extended Supported Rates and BSS Membership + /// Selectors element containing this value. + SelectorEpd, + /// ndicates that support for the direct hashing to element technique in + /// SAE is required in order to join the BSS. + SelectorSaeHash, +} + +impl From for Nl80211RateAndSelector { + fn from(d: u8) -> Self { + let msb: bool = (d & 1 << 7) > 0; + let value = d & 0b01111111; + if msb { + match value { + BSS_MEMBERSHIP_SELECTOR_SAE_HASH => Self::SelectorSaeHash, + BSS_MEMBERSHIP_SELECTOR_EPD => Self::SelectorEpd, + BSS_MEMBERSHIP_SELECTOR_GLK => Self::SelectorGlk, + BSS_MEMBERSHIP_SELECTOR_VHT_PHY => Self::SelectorVht, + BSS_MEMBERSHIP_SELECTOR_HT_PHY => Self::SelectorHt, + _ => Self::BssBasicRateSet(value / 2), + } + } else { + Self::Rate(value / 2) + } + } +} + +impl From for u8 { + fn from(v: Nl80211RateAndSelector) -> u8 { + match v { + Nl80211RateAndSelector::BssBasicRateSet(r) => (r * 2) & 1 << 7, + Nl80211RateAndSelector::SelectorHt => { + BSS_MEMBERSHIP_SELECTOR_HT_PHY & 1 << 7 + } + Nl80211RateAndSelector::SelectorVht => { + BSS_MEMBERSHIP_SELECTOR_VHT_PHY & 1 << 7 + } + Nl80211RateAndSelector::SelectorGlk => { + BSS_MEMBERSHIP_SELECTOR_GLK & 1 << 7 + } + Nl80211RateAndSelector::SelectorEpd => { + BSS_MEMBERSHIP_SELECTOR_EPD & 1 << 7 + } + Nl80211RateAndSelector::SelectorSaeHash => { + BSS_MEMBERSHIP_SELECTOR_SAE_HASH & 1 << 7 + } + Nl80211RateAndSelector::Rate(r) => r * 2, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub struct Nl80211ElementCountry { + pub country: String, + pub environment: Nl80211ElementCountryEnvironment, + pub triplets: Vec, +} + +impl + ?Sized> Parseable for Nl80211ElementCountry { + fn parse(buf: &T) -> Result { + let buf = buf.as_ref(); + // IEEE 802.11-2020 said the minimum size of this element is 8 octets. + if buf.len() < 6 { + return Err(format!( + "Buffer for Nl80211ElementCountry is smaller \ + than mandatory 6 byte: {:?}", + buf + ) + .into()); + } + let country = String::from_utf8(buf[0..2].to_vec()).map_err(|e| { + DecodeError::from(format!( + "Invalid country string {:?}: {e}", + &buf[0..2] + )) + })?; + let environment = Nl80211ElementCountryEnvironment::from(buf[2]); + let mut triplets: Vec = Vec::new(); + for i in 0..((buf.len() - 3) / 3) { + let payload = &buf[(i + 1) * 3..(i + 2) * 3]; + triplets.push(Nl80211ElementCountryTriplet::parse(payload)?); + } + Ok(Self { + country, + environment, + triplets, + }) + } +} + +impl Emitable for Nl80211ElementCountry { + fn buffer_len(&self) -> usize { + (self.triplets.len() * 3 + 3 + 1) / 2 * 2 + } + + fn emit(&self, buffer: &mut [u8]) { + if self.country.len() != 2 { + log::warn!( + "Invalid country string {} for Nl80211ElementCountry, \ + should be 2 ASCII characters", + self.country + ); + } else { + buffer[0] = self.country.as_bytes()[0]; + buffer[1] = self.country.as_bytes()[1]; + } + buffer[3] = self.environment.into(); + for (i, triplet) in self.triplets.as_slice().iter().enumerate() { + triplet.emit(&mut buffer[(i + 1) * 3..(i + 2) * 3]); + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub enum Nl80211ElementCountryEnvironment { + Indoor, + Outdoor, + IndoorAndOutdoor, + Noncountry, + Other(u8), +} + +impl From for u8 { + fn from(v: Nl80211ElementCountryEnvironment) -> u8 { + match v { + Nl80211ElementCountryEnvironment::IndoorAndOutdoor => b' ', + Nl80211ElementCountryEnvironment::Indoor => b'I', + Nl80211ElementCountryEnvironment::Outdoor => b'O', + Nl80211ElementCountryEnvironment::Noncountry => b'X', + Nl80211ElementCountryEnvironment::Other(d) => d, + } + } +} + +impl From for Nl80211ElementCountryEnvironment { + fn from(d: u8) -> Self { + match d { + b' ' => Self::IndoorAndOutdoor, + b'I' => Self::Indoor, + b'O' => Self::Outdoor, + b'X' => Self::Noncountry, + _ => Self::Other(d), + } + } +} + +const IEEE80211_COUNTRY_EXTENSION_ID: u8 = 201; + +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +pub enum Nl80211ElementCountryTriplet { + Subband(Nl80211ElementSubBand), + Operating(Nl80211ElementOperating), +} + +impl Emitable for Nl80211ElementCountryTriplet { + fn buffer_len(&self) -> usize { + 3 + } + + fn emit(&self, buffer: &mut [u8]) { + match self { + Self::Subband(v) => v.emit(buffer), + Self::Operating(v) => v.emit(buffer), + } + } +} + +impl Nl80211ElementCountryTriplet { + pub fn parse(payload: &[u8]) -> Result { + if payload.len() != 3 { + return Err(format!( + "Invalid buffer for Nl80211ElementCountryTriplet, \ + expecting [u8;3], but got {:?}", + payload + ) + .into()); + } + if payload[0] >= IEEE80211_COUNTRY_EXTENSION_ID { + Ok(Self::Operating(Nl80211ElementOperating::from([ + payload[0], payload[1], payload[2], + ]))) + } else { + Ok(Self::Subband(Nl80211ElementSubBand::from([ + payload[0], payload[1], payload[2], + ]))) + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Nl80211ElementSubBand { + pub channel_start: u8, + pub channel_count: u8, + /// The Maximum Transmit Power Level field indicates the maximum power, in + /// dBm, allowed to be transmitted + pub max_power_level: i8, +} + +impl Emitable for Nl80211ElementSubBand { + fn buffer_len(&self) -> usize { + 3 + } + + fn emit(&self, buffer: &mut [u8]) { + buffer[0] = self.channel_start; + buffer[1] = self.channel_count; + buffer[2] = self.max_power_level as u8; + } +} + +impl From<[u8; 3]> for Nl80211ElementSubBand { + fn from(buf: [u8; 3]) -> Self { + Self { + channel_start: buf[0], + channel_count: buf[1], + max_power_level: buf[2] as i8, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Nl80211ElementOperating { + pub extention_id: u8, + pub operating_class: u8, + /// The `aAirPropagationTime` is `coverage_class` * 3 in μs for range + /// between 0 - 31. Bigger than 31 is reserved. + pub coverage_class: u8, +} + +impl Emitable for Nl80211ElementOperating { + fn buffer_len(&self) -> usize { + 3 + } + + fn emit(&self, buffer: &mut [u8]) { + buffer[0] = self.extention_id; + buffer[1] = self.operating_class; + buffer[2] = self.coverage_class; + } +} + +impl From<[u8; 3]> for Nl80211ElementOperating { + fn from(buf: [u8; 3]) -> Self { + Self { + extention_id: buf[0], + operating_class: buf[1], + coverage_class: buf[2], + } + } +} + +/// Robust Security Network Element +#[derive(Debug, PartialEq, Eq, Clone, Default)] +pub struct Nl80211ElementRsn { + pub version: u16, + pub group_cipher: Option, + pub pairwise_ciphers: Vec, + /// Authentication Key Management(AKM) suits + pub akm_suits: Vec, + pub rsn_capbilities: Option, + pub pmkids: Vec, + pub group_mgmt_cipher: Option, +} + +impl Nl80211ElementRsn { + pub fn parse(payload: &[u8]) -> Result { + if payload.len() != 2 && payload.len() < 8 { + return Err(format!( + "Invalid buffer length of Nl80211ElementRsn, \ + expecting 2 or bigger than 7, but got {payload:?}" + ) + .into()); + } + let mut ret = Self { + version: u16::from_le_bytes([payload[0], payload[1]]), + ..Default::default() + }; + + let mut offset = 2; + + if offset >= payload.len() { + return Ok(ret); + } + + ret.group_cipher = Some(Nl80211CipherSuite::parse( + &payload[offset..offset + Nl80211CipherSuite::LENGTH], + )?); + offset += Nl80211CipherSuite::LENGTH; + + if offset >= payload.len() || offset + 2 >= payload.len() { + return Ok(ret); + } + let pairwise_cipher_count = + u16::from_le_bytes([payload[offset], payload[offset + 1]]) as usize; + offset += 2; + if offset >= payload.len() { + return Ok(ret); + } + + for _ in 0..pairwise_cipher_count { + if offset + Nl80211CipherSuite::LENGTH >= payload.len() { + return Ok(ret); + } + ret.pairwise_ciphers.push(Nl80211CipherSuite::parse( + &payload[offset..offset + Nl80211CipherSuite::LENGTH], + )?); + offset += Nl80211CipherSuite::LENGTH; + } + + if offset >= payload.len() || offset + 2 >= payload.len() { + return Ok(ret); + } + let akm_count = + u16::from_le_bytes([payload[offset], payload[offset + 1]]) as usize; + offset += 2; + if offset >= payload.len() { + return Ok(ret); + } + for _ in 0..akm_count { + if offset + Nl80211AkmSuite::LENGTH >= payload.len() { + return Ok(ret); + } + ret.akm_suits.push(Nl80211AkmSuite::parse( + &payload[offset..offset + Nl80211AkmSuite::LENGTH], + )?); + offset += Nl80211AkmSuite::LENGTH; + } + if offset >= payload.len() || offset + 2 >= payload.len() { + return Ok(ret); + } + + ret.rsn_capbilities = + Some(Nl80211RsnCapbilities::parse(&payload[offset..offset + 2])?); + offset += 2; + + if offset >= payload.len() || offset + 2 >= payload.len() { + return Ok(ret); + } + let pmkids_count = + u16::from_le_bytes([payload[offset], payload[offset + 1]]) as usize; + offset += 2; + if offset >= payload.len() { + return Ok(ret); + } + for _ in 0..pmkids_count { + if offset + Nl80211Pmkid::LENGTH >= payload.len() { + return Ok(ret); + } + ret.pmkids.push(Nl80211Pmkid::parse( + &payload[offset..offset + Nl80211Pmkid::LENGTH], + )?); + offset += Nl80211Pmkid::LENGTH; + } + + if offset >= payload.len() + || offset + Nl80211CipherSuite::LENGTH >= payload.len() + { + return Ok(ret); + } + + ret.group_mgmt_cipher = Some(Nl80211CipherSuite::parse( + &payload[offset..offset + Nl80211CipherSuite::LENGTH], + )?); + + Ok(ret) + } +} + +impl Emitable for Nl80211ElementRsn { + fn buffer_len(&self) -> usize { + // version field + let mut len = 2usize; + if self.group_cipher.is_none() { + return len; + } else { + len += Nl80211CipherSuite::LENGTH; + } + + if self.pairwise_ciphers.is_empty() { + return len; + } else { + len += 2 + self.pairwise_ciphers.len() * Nl80211CipherSuite::LENGTH; + } + + if self.akm_suits.is_empty() { + return len; + } else { + len += 2 + self.akm_suits.len() * Nl80211AkmSuite::LENGTH; + } + + if self.rsn_capbilities.is_none() { + return len; + } else { + len += 2; + } + + if self.pmkids.is_empty() { + return len; + } else { + len += 2 + self.pmkids.len() * Nl80211Pmkid::LENGTH; + } + if self.group_mgmt_cipher.is_none() { + return len; + } else { + len += Nl80211CipherSuite::LENGTH; + } + + len + } + + fn emit(&self, buffer: &mut [u8]) { + write_u16_le(&mut buffer[0..2], self.version); + if let Some(g) = self.group_cipher { + write_u32_le(&mut buffer[2..6], u32::from(g)); + write_u16_le(&mut buffer[6..8], self.pairwise_ciphers.len() as u16); + } + for (i, cipher) in self.pairwise_ciphers.as_slice().iter().enumerate() { + write_u32_le( + &mut buffer[(8 + i * 4)..(12 + i * 4)], + u32::from(*cipher), + ); + } + } +} + +const IEEE_80211_OUI: u32 = 0x00ac0f00; +const CIPHER_USE_GROUP: u32 = IEEE_80211_OUI; +const CIPHER_WEP_40: u32 = IEEE_80211_OUI | 1 << 24; +const CIPHER_TKIP: u32 = IEEE_80211_OUI | 2 << 24; +const CIPHER_CCMP_128: u32 = IEEE_80211_OUI | 4 << 24; +const CIPHER_WEP_104: u32 = IEEE_80211_OUI | 5 << 24; +const CIPHER_BIP_CMAC_128: u32 = IEEE_80211_OUI | 6 << 24; +const CIPHER_GROUP_ADDRESSED_TRACFFIC_NOT_ALLOWED: u32 = + IEEE_80211_OUI | 7 << 24; +const CIPHER_GCMP_128: u32 = IEEE_80211_OUI | 8 << 24; +const CIPHER_GCMP_256: u32 = IEEE_80211_OUI | 9 << 24; +const CIPHER_CCMP_256: u32 = IEEE_80211_OUI | 10 << 24; +const CIPHER_BIP_GMAC_128: u32 = IEEE_80211_OUI | 11 << 24; +const CIPHER_BIP_GMAC_256: u32 = IEEE_80211_OUI | 12 << 24; +const CIPHER_BIP_CMAC_256: u32 = IEEE_80211_OUI | 13 << 24; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +#[non_exhaustive] +pub enum Nl80211CipherSuite { + UseGroup, + Wep40, + Tkip, + // The 802.11-2020 said only non-DMG default to CCMP-128. + // But considering 60G 802.11ad(DMG) is rarely used, it is reasonable to + // assume Ccmp128 is default + #[default] + Ccmp128, + Wep104, + BipCmac128, + GroupAddressedTrafficNotAllowed, + Gcmp128, + Gcmp256, + Ccmp256, + BipGmac128, + BipGmac256, + BipCmac256, + Other(u32), +} + +impl From for Nl80211CipherSuite { + fn from(d: u32) -> Self { + match d { + CIPHER_USE_GROUP => Self::UseGroup, + CIPHER_WEP_40 => Self::Wep40, + CIPHER_TKIP => Self::Tkip, + CIPHER_CCMP_128 => Self::Ccmp128, + CIPHER_WEP_104 => Self::Wep104, + CIPHER_BIP_CMAC_128 => Self::BipCmac128, + CIPHER_GROUP_ADDRESSED_TRACFFIC_NOT_ALLOWED => { + Self::GroupAddressedTrafficNotAllowed + } + CIPHER_GCMP_128 => Self::Gcmp128, + CIPHER_GCMP_256 => Self::Gcmp256, + CIPHER_CCMP_256 => Self::Ccmp256, + CIPHER_BIP_GMAC_128 => Self::BipGmac128, + CIPHER_BIP_GMAC_256 => Self::BipGmac256, + CIPHER_BIP_CMAC_256 => Self::BipCmac256, + _ => Self::Other(d), + } + } +} + +impl From for u32 { + fn from(v: Nl80211CipherSuite) -> u32 { + match v { + Nl80211CipherSuite::UseGroup => CIPHER_USE_GROUP, + Nl80211CipherSuite::Wep40 => CIPHER_WEP_40, + Nl80211CipherSuite::Tkip => CIPHER_TKIP, + Nl80211CipherSuite::Ccmp128 => CIPHER_CCMP_128, + Nl80211CipherSuite::Wep104 => CIPHER_WEP_104, + Nl80211CipherSuite::BipCmac128 => CIPHER_BIP_CMAC_128, + Nl80211CipherSuite::GroupAddressedTrafficNotAllowed => { + CIPHER_GROUP_ADDRESSED_TRACFFIC_NOT_ALLOWED + } + Nl80211CipherSuite::Gcmp128 => CIPHER_GCMP_128, + Nl80211CipherSuite::Gcmp256 => CIPHER_GCMP_256, + Nl80211CipherSuite::Ccmp256 => CIPHER_CCMP_256, + Nl80211CipherSuite::BipGmac128 => CIPHER_BIP_GMAC_128, + Nl80211CipherSuite::BipGmac256 => CIPHER_BIP_GMAC_256, + Nl80211CipherSuite::BipCmac256 => CIPHER_BIP_CMAC_256, + Nl80211CipherSuite::Other(d) => d, + } + } +} + +impl Nl80211CipherSuite { + pub const LENGTH: usize = 4; + + pub fn parse(payload: &[u8]) -> Result { + if payload.len() < 4 { + Err(format!( + "Invalid buffer length for Nl80211CipherSuite, \ + expecting 4, but got {payload:?}" + ) + .into()) + } else { + Ok(u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]) + .into()) + } + } +} +const AKM_1X: u32 = IEEE_80211_OUI | 1 << 24; +const AKM_PSK: u32 = IEEE_80211_OUI | 2 << 24; +const AKM_FT_1X: u32 = IEEE_80211_OUI | 3 << 24; +const AKM_FT_PSK: u32 = IEEE_80211_OUI | 4 << 24; +const AKM_1X_SHA256: u32 = IEEE_80211_OUI | 5 << 24; +const AKM_PSK_SHA256: u32 = IEEE_80211_OUI | 6 << 24; +const AKM_TDLS: u32 = IEEE_80211_OUI | 7 << 24; +const AKM_SAE: u32 = IEEE_80211_OUI | 8 << 24; +const AKM_FT_SAE: u32 = IEEE_80211_OUI | 9 << 24; +const AKM_AP_PEER_KEY: u32 = IEEE_80211_OUI | 10 << 24; +const AKM_1X_SUITB: u32 = IEEE_80211_OUI | 11 << 24; +const AKM_1X_CNSA: u32 = IEEE_80211_OUI | 12 << 24; +const AKM_FT_1X_SHA384: u32 = IEEE_80211_OUI | 13 << 24; +const AKM_FILS_SHA256_AES_SIV256_OR_1X: u32 = IEEE_80211_OUI | 14 << 24; +const AKM_FILS_SHA384_AES_SIV512_OR_1X: u32 = IEEE_80211_OUI | 15 << 24; +const AKM_FT_FILS_SHA256_AES_SIV256_OR_1X: u32 = IEEE_80211_OUI | 16 << 24; +const AKM_FT_FILS_SHA384_AES_SIV512_OR_1X: u32 = IEEE_80211_OUI | 17 << 24; +const AKM_FT_PSK_SHA384: u32 = IEEE_80211_OUI | 19 << 24; +const AKM_PSK_SHA384: u32 = IEEE_80211_OUI | 20 << 24; +const AKM_SAE_GROUP_HASH: u32 = IEEE_80211_OUI | 24 << 24; +const AKM_FT_SAE_GROUP_HASH: u32 = IEEE_80211_OUI | 25 << 24; + +/// Authentication Key Management Suite +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub enum Nl80211AkmSuite { + Ieee8021x, + Psk, + FtIeee8021x, + FtPsk, + Ieee8021xSha256, + PskSha256, + Tdls, + Sae, + FtSae, + ApPeerKey, + Ieee8021xSuiteB, + Ieee8021xCnsa, + FtIeee8021xSha384, + FilsSha256AesSiv256OrIeee8021x, + FilsSha384AesSiv512OrIeee8021x, + FtFilsSha256AesSiv256OrIeee8021x, + FtFilsSha384AesSiv512OrIeee8021x, + FtPskSha384, + PskSha384, + // Defined in WPA 3 as 00-0F-AC:24 + SaeGroupDependentHash, + // Defined in WPA 3 as 00-0F-AC:25 + FtSaeGroupDependentHash, + Other(u32), +} + +impl From for Nl80211AkmSuite { + fn from(d: u32) -> Self { + match d { + AKM_1X => Self::Ieee8021x, + AKM_PSK => Self::Psk, + AKM_FT_1X => Self::FtIeee8021x, + AKM_FT_PSK => Self::FtPsk, + AKM_1X_SHA256 => Self::Ieee8021xSha256, + AKM_PSK_SHA256 => Self::PskSha256, + AKM_TDLS => Self::Tdls, + AKM_SAE => Self::Sae, + AKM_FT_SAE => Self::FtSae, + AKM_AP_PEER_KEY => Self::ApPeerKey, + AKM_1X_SUITB => Self::Ieee8021xSuiteB, + AKM_1X_CNSA => Self::Ieee8021xCnsa, + AKM_FT_1X_SHA384 => Self::FtIeee8021xSha384, + AKM_FILS_SHA256_AES_SIV256_OR_1X => { + Self::FilsSha256AesSiv256OrIeee8021x + } + AKM_FILS_SHA384_AES_SIV512_OR_1X => { + Self::FilsSha384AesSiv512OrIeee8021x + } + AKM_FT_FILS_SHA256_AES_SIV256_OR_1X => { + Self::FtFilsSha256AesSiv256OrIeee8021x + } + AKM_FT_FILS_SHA384_AES_SIV512_OR_1X => { + Self::FtFilsSha384AesSiv512OrIeee8021x + } + AKM_FT_PSK_SHA384 => Self::FtPskSha384, + AKM_PSK_SHA384 => Self::PskSha384, + AKM_SAE_GROUP_HASH => Self::SaeGroupDependentHash, + AKM_FT_SAE_GROUP_HASH => Self::FtSaeGroupDependentHash, + _ => Self::Other(d), + } + } +} + +impl From for u32 { + fn from(v: Nl80211AkmSuite) -> u32 { + match v { + Nl80211AkmSuite::Ieee8021x => AKM_1X, + Nl80211AkmSuite::Psk => AKM_PSK, + Nl80211AkmSuite::FtIeee8021x => AKM_FT_1X, + Nl80211AkmSuite::FtPsk => AKM_FT_PSK, + Nl80211AkmSuite::Ieee8021xSha256 => AKM_1X_SHA256, + Nl80211AkmSuite::PskSha256 => AKM_PSK_SHA256, + Nl80211AkmSuite::Tdls => AKM_TDLS, + Nl80211AkmSuite::Sae => AKM_SAE, + Nl80211AkmSuite::FtSae => AKM_FT_SAE, + Nl80211AkmSuite::ApPeerKey => AKM_AP_PEER_KEY, + Nl80211AkmSuite::Ieee8021xSuiteB => AKM_1X_SUITB, + Nl80211AkmSuite::Ieee8021xCnsa => AKM_1X_CNSA, + Nl80211AkmSuite::FtIeee8021xSha384 => AKM_FT_1X_SHA384, + Nl80211AkmSuite::FilsSha256AesSiv256OrIeee8021x => { + AKM_FILS_SHA256_AES_SIV256_OR_1X + } + Nl80211AkmSuite::FilsSha384AesSiv512OrIeee8021x => { + AKM_FILS_SHA384_AES_SIV512_OR_1X + } + Nl80211AkmSuite::FtFilsSha256AesSiv256OrIeee8021x => { + AKM_FT_FILS_SHA256_AES_SIV256_OR_1X + } + Nl80211AkmSuite::FtFilsSha384AesSiv512OrIeee8021x => { + AKM_FT_FILS_SHA384_AES_SIV512_OR_1X + } + Nl80211AkmSuite::FtPskSha384 => AKM_FT_PSK_SHA384, + Nl80211AkmSuite::PskSha384 => AKM_PSK_SHA384, + Nl80211AkmSuite::SaeGroupDependentHash => AKM_SAE_GROUP_HASH, + Nl80211AkmSuite::FtSaeGroupDependentHash => AKM_FT_SAE_GROUP_HASH, + Nl80211AkmSuite::Other(d) => d, + } + } +} +impl Nl80211AkmSuite { + pub const LENGTH: usize = 4; + + pub fn parse(payload: &[u8]) -> Result { + if payload.len() < 4 { + Err(format!( + "Invalid buffer length for Nl80211AkmSuite, \ + expecting 4, but got {payload:?}" + ) + .into()) + } else { + Ok(u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]) + .into()) + } + } +} + +const RSN_CAP_PRE_AUTH: u16 = 1 << 0; +const RSN_CAP_NO_PAIRWISE: u16 = 1 << 1; +const RSN_CAP_PTKSA_REPLAY_COUNT_2: u16 = 1 << 2; +const RSN_CAP_PTKSA_REPLAY_COUNT_4: u16 = 1 << 3; +const RSN_CAP_GTKSA_REPLAY_COUNT_2: u16 = 1 << 4; +const RSN_CAP_GTKSA_REPLAY_COUNT_4: u16 = 1 << 5; +const RSN_CAP_MFPR: u16 = 1 << 6; +const RSN_CAP_MFPC: u16 = 1 << 7; +const RSN_CAP_JOINT_MULTI_BAND_RSNA: u16 = 1 << 8; +const RSN_CAP_PEER_KEY_ENABLED: u16 = 1 << 9; +const RSN_CAP_SPP_A_MSDU_CAPABLE: u16 = 1 << 10; +const RSN_CAP_SPP_A_MSDU_REQUIRED: u16 = 1 << 11; +const RSN_CAP_PBAC: u16 = 1 << 12; +const RSN_CAP_EXTENDED_KEY_ID_PTKSA: u16 = 1 << 13; +const RSN_CAP_OCVC: u16 = 1 << 14; + +bitflags::bitflags! { + /// If not bands are set, it means don't care and the device will decide + /// what to use + #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] + #[non_exhaustive] + pub struct Nl80211RsnCapbilities: u16 { + /// Indicates the AP support preauthentication. + const PreAuth = RSN_CAP_PRE_AUTH; + /// Indicates the STA does not support WEP default key 0 simultaneously + /// with a pairwise key. + const NoPairwise = RSN_CAP_NO_PAIRWISE; + /// When Both PtksaReplayCount2 and PtksaReplayCount4 are set, + /// it means 16 replay conters per PTKSA. + /// When Neither PtksaReplayCount2 or PtksaReplayCount4 is set, + /// it means 1 reply counter per PTKSA + const PtksaReplayCount2 = RSN_CAP_PTKSA_REPLAY_COUNT_2; + const PtksaReplayCount4 = RSN_CAP_PTKSA_REPLAY_COUNT_4; + /// When Both GtksaReplayCount2 and GtksaReplayCount4 are set, + /// it means 16 replay conters per GTKSA. + /// When Neither GtksaReplayCount2 or GtksaReplayCount4 is set, + /// it means 1 reply counter per GTKSA + const GtksaReplayCount2 = RSN_CAP_GTKSA_REPLAY_COUNT_2; + const GtksaReplayCount4 = RSN_CAP_GTKSA_REPLAY_COUNT_4; + /// Indicates STA advertise that protection of robust Management frames + /// is mandatory + const Mfpr = RSN_CAP_MFPR; + /// Indicates STA protection of robust Management frames is enabled. + const Mfpc = RSN_CAP_MFPC; + /// Joint Multi-band RSNA. + /// Indicate a STA supports the Joint Multi-band RSNA. + const JointMultiBandRsna = RSN_CAP_JOINT_MULTI_BAND_RSNA; + /// An AP indicate it supports PeerKey handshake + const PeerKeyEnabled = RSN_CAP_PEER_KEY_ENABLED; + /// A STA indicate it supports signaling and payload protected A-MSDUs. + const SppAMsduCapable = RSN_CAP_SPP_A_MSDU_CAPABLE; + /// A STA indicate it allows only SPP A-MSDUs. + const SppAMsduRequired = RSN_CAP_SPP_A_MSDU_REQUIRED; + /// Protected block ack agreement capable. + const Pbac = RSN_CAP_PBAC; + /// Extended Key ID for Individually Addressed Frames. + /// Indicate that the STA supports Key ID values in the range 0 to 1 for + /// a PTKSA when the cipher suite is CCMP or GCMP. + /// When unset, indicates that the STA only supports Key ID 0 for a + /// PTKSA + const ExtendedKeyIdPtksa = RSN_CAP_EXTENDED_KEY_ID_PTKSA; + /// Indicates the STA supports operating channel validation by including + /// Operating Channel Information (OCI) in RSNA exchanges and validates + /// the information when received from another STA that indicated this + /// capability. + const Ocvc = RSN_CAP_OCVC; + const _ = !0; + } +} + +impl Nl80211RsnCapbilities { + pub const LENGTH: usize = 2; + + pub fn parse(raw: &[u8]) -> Result { + Ok(Self::from_bits_retain(parse_u16_le(raw).context( + format!("Invalid Nl80211RsnCapbilities payload {raw:?}"), + )?)) + } +} + +impl Emitable for Nl80211RsnCapbilities { + fn buffer_len(&self) -> usize { + Self::LENGTH + } + + fn emit(&self, buffer: &mut [u8]) { + buffer.copy_from_slice(&self.bits().to_le_bytes()) + } +} + +/// Authentication Key Management Suite +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Nl80211Pmkid(pub [u8; 16]); + +impl Nl80211Pmkid { + pub const LENGTH: usize = 16; + + pub fn parse(payload: &[u8]) -> Result { + if payload.len() < Self::LENGTH { + Err(format!( + "Invalid buffer length for Nl80211Pmkid, \ + expecting {}, but got {payload:?}", + Self::LENGTH + ) + .into()) + } else { + let mut raw = [0u8; Self::LENGTH]; + raw.copy_from_slice(&payload[..Self::LENGTH]); + Ok(Self(raw)) + } + } +} diff --git a/src/handle.rs b/src/handle.rs index 07344a0..7cbf690 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -8,7 +8,7 @@ use netlink_packet_utils::DecodeError; use crate::{ try_nl80211, Nl80211Error, Nl80211InterfaceHandle, Nl80211Message, - Nl80211StationHandle, Nl80211WiphyHandle, + Nl80211ScanHandle, Nl80211StationHandle, Nl80211WiphyHandle, }; #[derive(Clone, Debug)] @@ -36,6 +36,11 @@ impl Nl80211Handle { Nl80211WiphyHandle::new(self.clone()) } + // equivalent to `iw dev DEVICE scan` command + pub fn scan(&self) -> Nl80211ScanHandle { + Nl80211ScanHandle::new(self.clone()) + } + pub async fn request( &mut self, message: NetlinkMessage>, diff --git a/src/lib.rs b/src/lib.rs index c3b8ed0..f7f4eed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod attr; mod channel; mod connection; +mod element; mod error; mod ext_cap; mod feature; @@ -12,6 +13,7 @@ mod iface; mod macros; mod message; mod mlo; +mod scan; mod station; mod stats; mod wifi4; @@ -27,6 +29,7 @@ pub use self::channel::Nl80211ChannelWidth; #[cfg(feature = "tokio_socket")] pub use self::connection::new_connection; pub use self::connection::new_connection_with_socket; +pub use self::element::Nl80211Element; pub use self::error::Nl80211Error; pub use self::ext_cap::{ Nl80211ExtendedCapability, Nl80211IfTypeExtCapa, Nl80211IfTypeExtCapas, @@ -41,6 +44,10 @@ pub use self::iface::{ }; pub use self::message::{Nl80211Cmd, Nl80211Message}; pub use self::mlo::Nl80211MloLink; +pub use self::scan::{ + Nl80211BssCapabilities, Nl80211BssInfo, Nl80211BssUseFor, + Nl80211ScanGetRequest, Nl80211ScanHandle, +}; pub use self::station::{ Nl80211RateInfo, Nl80211StationGetRequest, Nl80211StationHandle, Nl80211StationInfo, @@ -72,6 +79,7 @@ pub use self::wiphy::{ Nl80211WowlanTrigersSupport, }; +pub(crate) use self::element::Nl80211Elements; pub(crate) use self::feature::Nl80211ExtFeatures; pub(crate) use self::handle::nl80211_execute; pub(crate) use self::iface::Nl80211InterfaceTypes; diff --git a/src/message.rs b/src/message.rs index 2c59035..3daadfb 100644 --- a/src/message.rs +++ b/src/message.rs @@ -14,6 +14,8 @@ const NL80211_CMD_GET_INTERFACE: u8 = 5; const NL80211_CMD_NEW_INTERFACE: u8 = 7; const NL80211_CMD_GET_STATION: u8 = 17; const NL80211_CMD_NEW_STATION: u8 = 19; +const NL80211_CMD_GET_SCAN: u8 = 32; +const NL80211_CMD_NEW_SCAN_RESULTS: u8 = 34; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Nl80211Cmd { @@ -23,6 +25,7 @@ pub enum Nl80211Cmd { StationNew, WiphyGet, WiphyNew, + ScanGet, } impl From for u8 { @@ -34,6 +37,7 @@ impl From for u8 { Nl80211Cmd::StationNew => NL80211_CMD_NEW_STATION, Nl80211Cmd::WiphyGet => NL80211_CMD_GET_WIPHY, Nl80211Cmd::WiphyNew => NL80211_CMD_NEW_WIPHY, + Nl80211Cmd::ScanGet => NL80211_CMD_GET_SCAN, } } } @@ -79,6 +83,13 @@ impl Nl80211Message { attributes: vec![Nl80211Attr::SplitWiphyDump], } } + + pub fn new_scan_get(attributes: Vec) -> Self { + Self { + cmd: Nl80211Cmd::ScanGet, + attributes, + } + } } impl Emitable for Nl80211Message { @@ -123,6 +134,10 @@ impl ParseableParametrized<[u8], GenlHeader> for Nl80211Message { cmd: Nl80211Cmd::WiphyNew, attributes: parse_nlas(buffer)?, }, + NL80211_CMD_NEW_SCAN_RESULTS => Self { + cmd: Nl80211Cmd::ScanGet, + attributes: parse_nlas(buffer)?, + }, cmd => { return Err(DecodeError::from(format!( "Unsupported nl80211 reply command: {}", diff --git a/src/scan/bss_info.rs b/src/scan/bss_info.rs new file mode 100644 index 0000000..a5e2802 --- /dev/null +++ b/src/scan/bss_info.rs @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: MIT + +// Most documentation comments are copied and modified from linux kernel +// include/uapi/linux/nl80211.h which is holding these license disclaimer: +/* + * 802.11 netlink interface public header + * + * Copyright 2006-2010 Johannes Berg + * Copyright 2008 Michael Wu + * Copyright 2008 Luis Carlos Cobo + * Copyright 2008 Michael Buesch + * Copyright 2008, 2009 Luis R. Rodriguez + * Copyright 2008 Jouni Malinen + * Copyright 2008 Colin McCabe + * Copyright 2015-2017 Intel Deutschland GmbH + * Copyright (C) 2018-2024 Intel Corporation + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +use std::convert::TryInto; +use std::fmt::Debug; + +use anyhow::Context; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer}, + parsers::{parse_u16, parse_u32, parse_u64, parse_u8}, + DecodeError, Emitable, Parseable, +}; + +use crate::{ + bytes::{write_i32, write_u16, write_u32, write_u64}, + Nl80211Element, Nl80211Elements, +}; + +bitflags::bitflags! { + /// IEEE 802.11-202, 9.4.1.4 Capability Information field + #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] + #[non_exhaustive] + pub struct Nl80211BssCapabilities: u16 { + const Ess = 1 << 0; + const Ibss = 1 << 1; + const Privacy = 1 << 4; + const ShortPreamble = 1 << 5; + const SpectrumManagement = 1 << 8; + const Qos = 1 << 9; + const ShortSlotTime = 1 << 10; + const Apsd = 1 << 11; + const RadioMeasurement = 1 << 12 ; + const Epd = 1 << 13; + const _ = !0; + } +} + +impl + ?Sized> Parseable for Nl80211BssCapabilities { + fn parse(buf: &T) -> Result { + let buf: &[u8] = buf.as_ref(); + Ok(Self::from_bits_retain(parse_u16(buf).context(format!( + "Invalid Nl80211BssCapabilities payload {buf:?}" + ))?)) + } +} + +impl Nl80211BssCapabilities { + pub const LENGTH: usize = 2; +} + +impl Emitable for Nl80211BssCapabilities { + fn buffer_len(&self) -> usize { + Self::LENGTH + } + + fn emit(&self, buffer: &mut [u8]) { + buffer.copy_from_slice(&self.bits().to_ne_bytes()) + } +} + +bitflags::bitflags! { + #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] + #[non_exhaustive] + pub struct Nl80211BssUseFor: u32 { + const Normal = 1 << 0; + const MldLink = 1 << 1; + const _ = !0; + } +} + +impl + ?Sized> Parseable for Nl80211BssUseFor { + fn parse(buf: &T) -> Result { + let buf: &[u8] = buf.as_ref(); + Ok(Self::from_bits_retain(parse_u32(buf).context(format!( + "Invalid Nl80211BssUseFor payload {buf:?}" + ))?)) + } +} + +impl Nl80211BssUseFor { + pub const LENGTH: usize = 4; +} + +impl Emitable for Nl80211BssUseFor { + fn buffer_len(&self) -> usize { + Self::LENGTH + } + + fn emit(&self, buffer: &mut [u8]) { + buffer.copy_from_slice(&self.bits().to_ne_bytes()) + } +} + +const ETH_ALEN: usize = 6; + +const NL80211_BSS_BSSID: u16 = 1; +const NL80211_BSS_FREQUENCY: u16 = 2; +const NL80211_BSS_TSF: u16 = 3; +const NL80211_BSS_BEACON_INTERVAL: u16 = 4; +const NL80211_BSS_CAPABILITY: u16 = 5; +const NL80211_BSS_INFORMATION_ELEMENTS: u16 = 6; +const NL80211_BSS_SIGNAL_MBM: u16 = 7; +const NL80211_BSS_SIGNAL_UNSPEC: u16 = 8; +const NL80211_BSS_STATUS: u16 = 9; +const NL80211_BSS_SEEN_MS_AGO: u16 = 10; +const NL80211_BSS_BEACON_IES: u16 = 11; +const NL80211_BSS_CHAN_WIDTH: u16 = 12; +const NL80211_BSS_BEACON_TSF: u16 = 13; +const NL80211_BSS_PRESP_DATA: u16 = 14; +const NL80211_BSS_LAST_SEEN_BOOTTIME: u16 = 15; +//NL80211_BSS_PAD 16, +//NL80211_BSS_PARENT_TSF 17 , +//NL80211_BSS_PARENT_BSSID 18, +//NL80211_BSS_CHAIN_SIGNAL 19, +const NL80211_BSS_FREQUENCY_OFFSET: u16 = 20; +//NL80211_BSS_MLO_LINK_ID 21, +//NL80211_BSS_MLD_ADDR 22 , +const NL80211_BSS_USE_FOR: u16 = 23; +//NL80211_BSS_CANNOT_USE_REASONS 24, + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Nl80211BssInfo { + Bssid([u8; ETH_ALEN]), + /// Frequency in MHz + Frequency(u32), + /// Timing Synchronization Function (TSF) of received probe response/beacon + /// in microsecond(μs). + Tsf(u64), + /// Beacon interval of the (I)BSS + BeaconInterval(u16), + Capability(Nl80211BssCapabilities), + InformationElements(Vec), + SignalMbm(i32), + SignalUnspec(u8), + Status(u32), + SeenMsAgo(u32), + BeaconInformationElements(Vec), + ChanWidth(u32), + BeaconTsf(u64), + ProbeResponseInformationElements(Vec), + /// `CLOCK_BOOTTIME` timestamp when this entry was last updated by a + /// received frame. The value is expected to be accurate to about 10ms. + /// (u64, nanoseconds) + LastSeenBootTime(u64), + /// Frequency offset in KHz + FrequencyOffset(u32), + UseFor(Nl80211BssUseFor), + Other(DefaultNla), +} + +impl Nla for Nl80211BssInfo { + fn value_len(&self) -> usize { + match self { + Self::Bssid(_) => ETH_ALEN, + Self::SignalUnspec(_) => 1, + Self::BeaconInterval(_) => 2, + Self::Frequency(_) + | Self::SignalMbm(_) + | Self::Status(_) + | Self::SeenMsAgo(_) + | Self::ChanWidth(_) + | Self::FrequencyOffset(_) => 4, + Self::BeaconTsf(_) | Self::Tsf(_) | Self::LastSeenBootTime(_) => 8, + Self::InformationElements(v) + | Self::BeaconInformationElements(v) + | Self::ProbeResponseInformationElements(v) => { + Nl80211Elements::from(v).buffer_len() + } + Self::Capability(_) => Nl80211BssCapabilities::LENGTH, + Self::UseFor(_) => Nl80211BssUseFor::LENGTH, + Self::Other(attr) => attr.value_len(), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Bssid(_) => NL80211_BSS_BSSID, + Self::Frequency(_) => NL80211_BSS_FREQUENCY, + Self::Tsf(_) => NL80211_BSS_TSF, + Self::BeaconInterval(_) => NL80211_BSS_BEACON_INTERVAL, + Self::InformationElements(_) => NL80211_BSS_INFORMATION_ELEMENTS, + Self::SignalMbm(_) => NL80211_BSS_SIGNAL_MBM, + Self::SignalUnspec(_) => NL80211_BSS_SIGNAL_UNSPEC, + Self::Status(_) => NL80211_BSS_STATUS, + Self::SeenMsAgo(_) => NL80211_BSS_SEEN_MS_AGO, + Self::ChanWidth(_) => NL80211_BSS_CHAN_WIDTH, + Self::BeaconTsf(_) => NL80211_BSS_BEACON_TSF, + Self::BeaconInformationElements(_) => NL80211_BSS_BEACON_IES, + Self::Capability(_) => NL80211_BSS_CAPABILITY, + Self::ProbeResponseInformationElements(_) => NL80211_BSS_PRESP_DATA, + Self::LastSeenBootTime(_) => NL80211_BSS_LAST_SEEN_BOOTTIME, + Self::FrequencyOffset(_) => NL80211_BSS_FREQUENCY_OFFSET, + Self::UseFor(_) => NL80211_BSS_USE_FOR, + Self::Other(attr) => attr.kind(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Bssid(v) => buffer[..ETH_ALEN].copy_from_slice(v), + Self::SignalUnspec(d) => buffer[0] = *d, + Self::BeaconInterval(d) => write_u16(buffer, *d), + Self::Frequency(d) + | Self::Status(d) + | Self::SeenMsAgo(d) + | Self::ChanWidth(d) + | Self::FrequencyOffset(d) => write_u32(buffer, *d), + Self::SignalMbm(d) => write_i32(buffer, *d), + Self::BeaconTsf(d) | Self::Tsf(d) | Self::LastSeenBootTime(d) => { + write_u64(buffer, *d) + } + Self::InformationElements(v) + | Self::BeaconInformationElements(v) + | Self::ProbeResponseInformationElements(v) => { + Nl80211Elements::from(v).emit(buffer) + } + Self::Capability(v) => v.emit(buffer), + Self::UseFor(v) => v.emit(buffer), + Self::Other(ref attr) => attr.emit(buffer), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for Nl80211BssInfo +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + NL80211_BSS_BSSID => { + if payload.len() < ETH_ALEN { + return Err(format!( + "Invalid NL80211_BSS_BSSID {payload:?}" + ) + .into()); + } + let mut bssid = [0u8; ETH_ALEN]; + bssid.copy_from_slice(&payload[..ETH_ALEN]); + Self::Bssid(bssid) + } + NL80211_BSS_TSF => { + let err_msg = + format!("Invalid NL80211_BSS_TSF value {:?}", payload); + Self::Tsf(parse_u64(payload).context(err_msg)?) + } + NL80211_BSS_FREQUENCY => { + let err_msg = format!( + "Invalid NL80211_BSS_FREQUENCY value {:?}", + payload + ); + Self::Frequency(parse_u32(payload).context(err_msg)?) + } + NL80211_BSS_BEACON_INTERVAL => { + let err_msg = format!( + "Invalid NL80211_BSS_BEACON_INTERVAL value {:?}", + payload + ); + Self::BeaconInterval(parse_u16(payload).context(err_msg)?) + } + NL80211_BSS_CAPABILITY => { + Self::Capability(Nl80211BssCapabilities::parse(payload)?) + } + NL80211_BSS_BEACON_IES => Self::BeaconInformationElements( + Nl80211Elements::parse(payload)?.into(), + ), + NL80211_BSS_INFORMATION_ELEMENTS => Self::InformationElements( + Nl80211Elements::parse(payload)?.into(), + ), + NL80211_BSS_PRESP_DATA => Self::ProbeResponseInformationElements( + Nl80211Elements::parse(payload)?.into(), + ), + NL80211_BSS_SIGNAL_MBM => { + let err_msg = format!( + "Invalid NL80211_BSS_SIGNAL_MBM value {:?}", + payload + ); + Self::SignalMbm(i32::from_ne_bytes( + payload.try_into().context(err_msg)?, + )) + } + NL80211_BSS_SIGNAL_UNSPEC => { + let err_msg = format!( + "Invalid NL80211_BSS_SIGNAL_UNSPEC value {:?}", + payload + ); + Self::SignalUnspec(parse_u8(payload).context(err_msg)?) + } + NL80211_BSS_STATUS => { + let err_msg = + format!("Invalid NL80211_BSS_STATUS value {:?}", payload); + Self::Status(parse_u32(payload).context(err_msg)?) + } + NL80211_BSS_SEEN_MS_AGO => { + let err_msg = format!( + "Invalid NL80211_BSS_SEEN_MS_AGO value {:?}", + payload + ); + Self::SeenMsAgo(parse_u32(payload).context(err_msg)?) + } + NL80211_BSS_CHAN_WIDTH => { + let err_msg = format!( + "Invalid NL80211_BSS_CHAN_WIDTH value {:?}", + payload + ); + Self::ChanWidth(parse_u32(payload).context(err_msg)?) + } + NL80211_BSS_BEACON_TSF => { + let err_msg = format!( + "Invalid NL80211_BSS_BEACON_TSF value {:?}", + payload + ); + Self::BeaconTsf(parse_u64(payload).context(err_msg)?) + } + NL80211_BSS_LAST_SEEN_BOOTTIME => { + let err_msg = format!( + "Invalid NL80211_BSS_LAST_SEEN_BOOTTIME value {:?}", + payload + ); + Self::LastSeenBootTime(parse_u64(payload).context(err_msg)?) + } + NL80211_BSS_FREQUENCY_OFFSET => { + Self::FrequencyOffset(parse_u32(payload).context(format!( + "Invalid NL80211_BSS_FREQUENCY_OFFSET {:?}", + payload + ))?) + } + NL80211_BSS_USE_FOR => { + Self::UseFor(Nl80211BssUseFor::parse(payload)?) + } + _ => Self::Other( + DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?, + ), + }) + } +} diff --git a/src/scan/get.rs b/src/scan/get.rs new file mode 100644 index 0000000..449ffde --- /dev/null +++ b/src/scan/get.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +use futures::TryStream; +use netlink_packet_generic::GenlMessage; + +use crate::{ + nl80211_execute, Nl80211Attr, Nl80211Error, Nl80211Handle, Nl80211Message, +}; + +pub struct Nl80211ScanGetRequest { + handle: Nl80211Handle, + if_index: u32, +} + +impl Nl80211ScanGetRequest { + pub(crate) fn new(handle: Nl80211Handle, if_index: u32) -> Self { + Nl80211ScanGetRequest { handle, if_index } + } + + pub async fn execute( + self, + ) -> impl TryStream, Error = Nl80211Error> + { + let Nl80211ScanGetRequest { + mut handle, + if_index, + } = self; + + let nlas = vec![Nl80211Attr::IfIndex(if_index)]; + let nl80211_msg = Nl80211Message::new_scan_get(nlas); + + nl80211_execute(&mut handle, nl80211_msg).await + } +} diff --git a/src/scan/handle.rs b/src/scan/handle.rs new file mode 100644 index 0000000..2177a03 --- /dev/null +++ b/src/scan/handle.rs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +use crate::{Nl80211Handle, Nl80211ScanGetRequest}; + +pub struct Nl80211ScanHandle(Nl80211Handle); + +impl Nl80211ScanHandle { + pub fn new(handle: Nl80211Handle) -> Self { + Nl80211ScanHandle(handle) + } + + /// Retrieve the current scan data + /// (equivalent to `iw dev DEVICE scan dump`) + pub fn dump(&mut self, if_index: u32) -> Nl80211ScanGetRequest { + Nl80211ScanGetRequest::new(self.0.clone(), if_index) + } +} diff --git a/src/scan/mod.rs b/src/scan/mod.rs new file mode 100644 index 0000000..ca4a044 --- /dev/null +++ b/src/scan/mod.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +mod bss_info; +mod get; +mod handle; + +pub use self::bss_info::{ + Nl80211BssCapabilities, Nl80211BssInfo, Nl80211BssUseFor, +}; +pub use self::get::Nl80211ScanGetRequest; +pub use self::handle::Nl80211ScanHandle;