diff --git a/Cargo.toml b/Cargo.toml index 19c32f7..8b57099 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,8 @@ smol_socket = ["netlink-proto/smol_socket", "async-std"] [dependencies] async-std = { version = "1.9.0", optional = true} bitflags = "2" +bstr = "1.12.0" +byteorder = "1.4.3" futures = "0.3.17" log = "0.4.14" thiserror = "1.0.29" diff --git a/examples/dump_nl80211_scan.rs b/examples/dump_nl80211_scan.rs index a89e67e..b280b2f 100644 --- a/examples/dump_nl80211_scan.rs +++ b/examples/dump_nl80211_scan.rs @@ -3,7 +3,7 @@ use std::env::args; use futures::stream::TryStreamExt; -use netlink_packet_core::{DecodeError, ErrorContext}; +use netlink_packet_core::{DecodeError, Emitable, ErrorContext, Parseable}; fn main() -> Result<(), Box> { let argv: Vec<_> = args().collect(); @@ -41,5 +41,21 @@ async fn dump_scan(if_index: u32) { assert!(!msgs.is_empty()); for msg in msgs { println!("{msg:?}"); + + println!("Information Elements"); + let ie: Vec<_> = + msg.payload.attributes.iter().filter_map(|attr| match attr { + wl_nl80211::Nl80211Attr::Bss(info) => { + let ies: Vec<_> = info.iter().filter_map(|info| match info { + wl_nl80211::Nl80211BssInfo::BeaconInformationElements(ie)| wl_nl80211::Nl80211BssInfo::InformationElements(ie) | wl_nl80211::Nl80211BssInfo::ProbeResponseInformationElements(ie) => + Some(wl_nl80211::Nl80211Elements::parse(ie).unwrap()), + _ => None, + }).collect(); + Some(ies) + }, + _ => None, + }).flatten().collect(); + + println!("{ie:?}"); } } diff --git a/examples/nl80211_trigger_scan.rs b/examples/nl80211_trigger_scan.rs index 8c83b89..8bac663 100644 --- a/examples/nl80211_trigger_scan.rs +++ b/examples/nl80211_trigger_scan.rs @@ -2,15 +2,26 @@ use std::env::args; -use futures::stream::TryStreamExt; -use netlink_packet_core::{DecodeError, ErrorContext}; +use futures::{stream::TryStreamExt, StreamExt}; +use netlink_packet_core::{DecodeError, ErrorContext, ParseableParametrized}; +use netlink_packet_core::{NetlinkMessage, NetlinkPayload, NLM_F_REQUEST}; +use netlink_packet_generic::{ + ctrl::{ + nlas::{GenlCtrlAttrs, McastGrpAttrs}, + GenlCtrl, GenlCtrlCmd, + }, + GenlMessage, +}; +use netlink_sys::AsyncSocket; +use wl_nl80211::{Nl80211Command, Nl80211Message}; fn main() -> Result<(), Box> { let argv: Vec<_> = args().collect(); if argv.len() < 2 { - eprintln!("Usage: nl80211_trigger_scan "); - panic!("Required arguments not given"); + return Err(Box::new(std::io::Error::other( + "Usage: nl80211_trigger_scan ", + ))); } let err_msg = format!("Invalid interface index value: {}", argv[1]); @@ -30,12 +41,18 @@ fn main() -> Result<(), Box> { } async fn dump_scan(if_index: u32) -> Result<(), Box> { - let (connection, handle, _) = wl_nl80211::new_connection()?; + let (mut connection, handle, mut messages) = wl_nl80211::new_connection()?; + + // Attach the connection socket to the multicast scan group to find out, + // when the scan is finished. + let socket = connection.socket_mut().socket_mut(); + socket.bind_auto()?; + socket.add_membership(get_scan_multicast_id().await?)?; + tokio::spawn(connection); - let duration = 5000; let attrs = wl_nl80211::Nl80211Scan::new(if_index) - .duration(duration) + .duration(5000) .passive(true) .build(); @@ -45,7 +62,21 @@ async fn dump_scan(if_index: u32) -> Result<(), Box> { while let Some(msg) = scan_handle.try_next().await? { msgs.push(msg); } - tokio::time::sleep(std::time::Duration::from_millis(duration.into())).await; + + while let Some((message, _)) = messages.next().await { + match message.payload { + NetlinkPayload::InnerMessage(msg) => { + let msg = Nl80211Message::parse_with_param( + msg.payload.as_slice(), + msg.header, + )?; + if msg.cmd == Nl80211Command::NewScanResults { + break; + } + } + _ => continue, + } + } let mut dump = handle.scan().dump(if_index).execute().await; let mut msgs = Vec::new(); @@ -58,3 +89,62 @@ async fn dump_scan(if_index: u32) -> Result<(), Box> { } Ok(()) } + +async fn get_scan_multicast_id() -> Result> { + let (conn, mut handle, _) = wl_nl80211::new_connection()?; + tokio::spawn(conn); + + let mut nl_msg = + NetlinkMessage::from(GenlMessage::from_payload(GenlCtrl { + cmd: GenlCtrlCmd::GetFamily, + nlas: vec![GenlCtrlAttrs::FamilyName("nl80211".to_owned())], + })); + + // To get the mcast groups for the nl80211 family, we must also set the + // message type id + nl_msg.header.message_type = + handle.handle.resolve_family_id::().await?; + // This is a request, but not a dump. Which means, the family name has to be + // specified, to obtain it's information. + nl_msg.header.flags = NLM_F_REQUEST; + + let responses = handle.handle.request(nl_msg).await?; + let nl80211_family: Vec> = responses + .try_filter_map(|msg| async move { + match msg.payload { + NetlinkPayload::InnerMessage(genlmsg) + if genlmsg.payload.cmd == GenlCtrlCmd::NewFamily + && genlmsg.payload.nlas.contains( + &GenlCtrlAttrs::FamilyName("nl80211".to_owned()), + ) => + { + Ok(Some(genlmsg.payload.nlas.clone())) + } + _ => Ok(None), + } + }) + .try_collect() + .await?; + + // Now get the mcid for "nl80211" "scan" group + let scan_multicast_id = nl80211_family + .first() + .ok_or_else(|| anyhow!("Missing \"nl80211\" family"))? + .iter() + .find_map(|attr| match attr { + GenlCtrlAttrs::McastGroups(mcast_groups) => Some(mcast_groups), + _ => None, + }) + .ok_or_else(|| anyhow!("Missing McastGroup attribute"))? + .iter() + .find(|grp| grp.contains(&McastGrpAttrs::Name("scan".to_owned()))) + .ok_or_else(|| anyhow!("Missing scan group"))? + .iter() + .find_map(|grp_attr| match grp_attr { + McastGrpAttrs::Id(id) => Some(*id), + _ => None, + }) + .ok_or_else(|| anyhow!("No multicast id defined for scan group"))?; + + Ok(scan_multicast_id) +} diff --git a/src/attr.rs b/src/attr.rs index 4808c39..3efca62 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -29,8 +29,9 @@ * */ +use bstr::BString; use netlink_packet_core::{ - parse_string, parse_u16, parse_u32, parse_u64, parse_u8, DecodeError, + parse_u16, parse_u32, parse_u64, parse_u8, DecodeError, DefaultNla, Emitable, ErrorContext, Nla, NlaBuffer, NlasIterator, Parseable, ParseableParametrized, }; @@ -50,6 +51,21 @@ use crate::{ Nl80211VhtCapability, Nl80211WowlanTriggersSupport, }; +// TODO: move to netlink_packet_utils +pub fn parse_bstring(payload: &[u8]) -> Result { + if payload.is_empty() { + return Ok(BString::new(Vec::new())); + } + // iproute2 is a bit inconsistent with null-terminated strings. + let slice = if payload[payload.len() - 1] == 0 { + &payload[..payload.len() - 1] + } else { + &payload[..payload.len()] + }; + let s = BString::new(slice.to_vec()); + Ok(s) +} + const ETH_ALEN: usize = 6; struct MacAddressNlas(Vec); @@ -459,9 +475,9 @@ const NL80211_ATTR_MAX_HW_TIMESTAMP_PEERS: u16 = 323; #[non_exhaustive] pub enum Nl80211Attr { Wiphy(u32), - WiphyName(String), + WiphyName(BString), IfIndex(u32), - IfName(String), + IfName(BString), IfType(Nl80211InterfaceType), IfTypeExtCap(Vec), Mac([u8; ETH_ALEN]), @@ -477,7 +493,7 @@ pub enum Nl80211Attr { CenterFreq1(u32), CenterFreq2(u32), WiphyTxPowerLevel(u32), - Ssid(String), + Ssid(BString), StationInfo(Vec), SurveyInfo(Vec), TransmitQueueStats(Vec), @@ -550,7 +566,7 @@ pub enum Nl80211Attr { MaxHwTimestampPeers(u16), /// Basic Service Set (BSS) Bss(Vec), - ScanSsids(Vec), + ScanSsids(Vec), ScanFlags(Nl80211ScanFlags), MeasurementDuration(u16), /// Scan interval in millisecond(ms) @@ -829,7 +845,7 @@ impl Nla for Nl80211Attr { MacAddressNlas::from(s).as_slice().emit(buffer) } Self::IfName(s) | Self::Ssid(s) | Self::WiphyName(s) => { - buffer[..s.len()].copy_from_slice(s.as_bytes()); + buffer[..s.len()].copy_from_slice(s.as_slice()); buffer[s.len()] = 0; } Self::Use4Addr(d) => buffer[0] = *d as u8, @@ -930,12 +946,12 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nl80211Attr { let err_msg = format!( "Invalid NL80211_ATTR_WIPHY_NAME value {payload:?}" ); - Self::WiphyName(parse_string(payload).context(err_msg)?) + Self::WiphyName(parse_bstring(payload).context(err_msg)?) } NL80211_ATTR_IFNAME => { let err_msg = format!("Invalid NL80211_ATTR_IFNAME value {payload:?}"); - Self::IfName(parse_string(payload).context(err_msg)?) + Self::IfName(parse_bstring(payload).context(err_msg)?) } NL80211_ATTR_IFTYPE => { Self::IfType(Nl80211InterfaceType::parse(payload)?) @@ -1040,7 +1056,7 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nl80211Attr { NL80211_ATTR_SSID => { let err_msg = format!("Invalid NL80211_ATTR_SSID value {payload:?}"); - Self::Ssid(parse_string(payload).context(err_msg)?) + Self::Ssid(parse_bstring(payload).context(err_msg)?) } NL80211_ATTR_STA_INFO => { let err_msg = diff --git a/src/builder.rs b/src/builder.rs index 2190a9c..48585d8 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -63,6 +63,6 @@ impl Nl80211AttrsBuilder { } pub fn ssid(self, ssid: &str) -> Self { - self.append(Nl80211Attr::Ssid(ssid.to_string())) + self.append(Nl80211Attr::Ssid(bstr::BString::from(ssid))) } } diff --git a/src/element.rs b/src/element.rs index eacf1ae..1eb7db7 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT +use bstr::BString; use netlink_packet_core::{ - parse_string, parse_u8, DecodeError, Emitable, ErrorContext, Parseable, + parse_u8, DecodeError, Emitable, ErrorContext, Parseable, }; use crate::{ @@ -9,7 +10,41 @@ use crate::{ Nl80211ElementHtCap, }; -pub(crate) struct Nl80211Elements(Vec); +/// Unparsed representation of IEEE 802.11-2020 `9.4.2 Elements` +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct RawNl80211Elements(pub Vec); + +impl + ?Sized> Parseable for RawNl80211Elements { + fn parse(raw: &T) -> Result { + let v = Vec::from(raw.as_ref()); + Ok(RawNl80211Elements::from(v)) + } +} + +impl Parseable for Nl80211Elements { + fn parse(raw: &RawNl80211Elements) -> Result { + Nl80211Elements::parse(&raw.0) + } +} + +impl Emitable for RawNl80211Elements { + fn buffer_len(&self) -> usize { + self.0.len() + } + + fn emit(&self, buffer: &mut [u8]) { + buffer.copy_from_slice(self.0.as_slice()); + } +} + +impl From> for RawNl80211Elements { + fn from(value: Vec) -> Self { + RawNl80211Elements(value) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Nl80211Elements(Vec); impl + ?Sized> Parseable for Nl80211Elements { fn parse(buf: &T) -> Result { @@ -68,7 +103,7 @@ const ELEMENT_ID_VENDOR: u8 = 221; #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum Nl80211Element { - Ssid(String), + Ssid(BString), /// Supported rates in units of 500 kb/s, if necessary rounded up to the /// next 500 kb/ SupportedRatesAndSelectors(Vec), @@ -124,10 +159,7 @@ impl + ?Sized> Parseable for Nl80211Element { 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_SSID => Self::Ssid(BString::from(payload)), ELEMENT_ID_SUPPORTED_RATES => Self::SupportedRatesAndSelectors( payload .iter() @@ -163,18 +195,18 @@ impl Emitable for Nl80211Element { 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()); + payload.copy_from_slice(s.as_slice()); } 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::HtCapability(v) => v.emit(buffer), + Self::Channel(v) => payload[0] = *v, + Self::Country(v) => v.emit(payload), + Self::Rsn(v) => v.emit(payload), + Self::Vendor(v) => payload[..v.len()].copy_from_slice(v.as_slice()), + Self::HtCapability(v) => v.emit(payload), Self::Other(_, data) => { payload.copy_from_slice(data.as_slice()); } @@ -191,9 +223,10 @@ 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. + /// BSS basic rate in units of 500 kb/s, if necessary rounded up to the + /// next 500 kbs. BssBasicRateSet(u8), - /// Rate in Mb/s. + /// Rate in units of 500 kb/s, if necessary rounded up to the next 500 kbs. Rate(u8), SelectorHt, SelectorVht, @@ -214,8 +247,9 @@ pub enum Nl80211RateAndSelector { impl From for Nl80211RateAndSelector { fn from(d: u8) -> Self { - let msb: bool = (d & 1 << 7) > 0; - let value = d & 0b01111111; + const MSB_MASK: u8 = 0b1000_0000; + let msb: bool = (d & MSB_MASK) == MSB_MASK; + let value = d & !MSB_MASK; if msb { match value { BSS_MEMBERSHIP_SELECTOR_SAE_HASH => Self::SelectorSaeHash, @@ -223,34 +257,35 @@ impl From for Nl80211RateAndSelector { BSS_MEMBERSHIP_SELECTOR_GLK => Self::SelectorGlk, BSS_MEMBERSHIP_SELECTOR_VHT_PHY => Self::SelectorVht, BSS_MEMBERSHIP_SELECTOR_HT_PHY => Self::SelectorHt, - _ => Self::BssBasicRateSet(value / 2), + _ => Self::BssBasicRateSet(value), } } else { - Self::Rate(value / 2) + Self::Rate(value) } } } impl From for u8 { fn from(v: Nl80211RateAndSelector) -> u8 { + const MSB: u8 = 0b1000_0000; match v { - Nl80211RateAndSelector::BssBasicRateSet(r) => (r * 2) & 1 << 7, + Nl80211RateAndSelector::BssBasicRateSet(r) => r & !MSB | MSB, Nl80211RateAndSelector::SelectorHt => { - BSS_MEMBERSHIP_SELECTOR_HT_PHY & 1 << 7 + BSS_MEMBERSHIP_SELECTOR_HT_PHY | MSB } Nl80211RateAndSelector::SelectorVht => { - BSS_MEMBERSHIP_SELECTOR_VHT_PHY & 1 << 7 + BSS_MEMBERSHIP_SELECTOR_VHT_PHY | MSB } Nl80211RateAndSelector::SelectorGlk => { - BSS_MEMBERSHIP_SELECTOR_GLK & 1 << 7 + BSS_MEMBERSHIP_SELECTOR_GLK | MSB } Nl80211RateAndSelector::SelectorEpd => { - BSS_MEMBERSHIP_SELECTOR_EPD & 1 << 7 + BSS_MEMBERSHIP_SELECTOR_EPD | MSB } Nl80211RateAndSelector::SelectorSaeHash => { - BSS_MEMBERSHIP_SELECTOR_SAE_HASH & 1 << 7 + BSS_MEMBERSHIP_SELECTOR_SAE_HASH | MSB } - Nl80211RateAndSelector::Rate(r) => r * 2, + Nl80211RateAndSelector::Rate(r) => r, } } } @@ -310,7 +345,7 @@ impl Emitable for Nl80211ElementCountry { buffer[0] = self.country.as_bytes()[0]; buffer[1] = self.country.as_bytes()[1]; } - buffer[3] = self.environment.into(); + buffer[2] = self.environment.into(); for (i, triplet) in self.triplets.as_slice().iter().enumerate() { triplet.emit(&mut buffer[(i + 1) * 3..(i + 2) * 3]); } @@ -471,6 +506,11 @@ pub struct Nl80211ElementRsn { impl Nl80211ElementRsn { pub fn parse(payload: &[u8]) -> Result { + let wrong_buffer_len = || { + DecodeError::from(format!( + "Invalid buffer length for Nl80211ElementRsn, got {payload:?}" + )) + }; if payload.len() != 2 && payload.len() < 8 { return Err(format!( "Invalid buffer length of Nl80211ElementRsn, \ @@ -490,83 +530,83 @@ impl Nl80211ElementRsn { } ret.group_cipher = Some(Nl80211CipherSuite::parse( - &payload[offset..offset + Nl80211CipherSuite::LENGTH], + payload + .get(offset..offset + Nl80211CipherSuite::LENGTH) + .ok_or_else(wrong_buffer_len)?, )?); 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); } + let pairwise_cipher_count = parse_u16_le( + payload + .get(offset..offset + 2) + .ok_or_else(wrong_buffer_len)?, + )? as usize; + offset += 2; 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], + payload + .get(offset..offset + Nl80211CipherSuite::LENGTH) + .ok_or_else(wrong_buffer_len)?, )?); 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); } + + let akm_count = parse_u16_le( + payload + .get(offset..offset + 2) + .ok_or_else(wrong_buffer_len)?, + )? as usize; + offset += 2; 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], + payload + .get(offset..offset + Nl80211AkmSuite::LENGTH) + .ok_or_else(wrong_buffer_len)?, )?); offset += Nl80211AkmSuite::LENGTH; } - if offset >= payload.len() || offset + 2 >= payload.len() { + if offset >= 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; + ret.rsn_capbilities = Some(Nl80211RsnCapbilities::parse( + payload + .get(offset..offset + 2) + .ok_or_else(wrong_buffer_len)?, + )?); offset += 2; if offset >= payload.len() { return Ok(ret); } + + let pmkids_count = parse_u16_le( + payload + .get(offset..offset + 2) + .ok_or_else(wrong_buffer_len)?, + )? as usize; + offset += 2; for _ in 0..pmkids_count { - if offset + Nl80211Pmkid::LENGTH >= payload.len() { - return Ok(ret); - } ret.pmkids.push(Nl80211Pmkid::parse( - &payload[offset..offset + Nl80211Pmkid::LENGTH], + payload + .get(offset..offset + Nl80211Pmkid::LENGTH) + .ok_or_else(wrong_buffer_len)?, )?); offset += Nl80211Pmkid::LENGTH; } - - if offset >= payload.len() - || offset + Nl80211CipherSuite::LENGTH >= payload.len() - { + if offset >= payload.len() { return Ok(ret); } ret.group_mgmt_cipher = Some(Nl80211CipherSuite::parse( - &payload[offset..offset + Nl80211CipherSuite::LENGTH], + payload + .get(offset..offset + Nl80211CipherSuite::LENGTH) + .ok_or_else(wrong_buffer_len)?, )?); Ok(ret) @@ -577,56 +617,119 @@ 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 any of the following fields do have some content, the next field + // has to be parsed into bytes even if is 0 or 0 for the length + // field. + let fields_with_content = [ + (self.group_cipher.is_some(), Nl80211CipherSuite::LENGTH), + ( + !self.pairwise_ciphers.is_empty(), + 2 + self.pairwise_ciphers.len() * Nl80211CipherSuite::LENGTH, + ), + ( + !self.akm_suits.is_empty(), + 2 + self.akm_suits.len() * Nl80211AkmSuite::LENGTH, + ), + (self.rsn_capbilities.is_some(), 2), + ( + !self.pmkids.is_empty(), + 2 + self.pmkids.len() * Nl80211Pmkid::LENGTH, + ), + (self.group_mgmt_cipher.is_some(), Nl80211CipherSuite::LENGTH), + ]; + + let mut i = 0; + while fields_with_content[i..] + .iter() + .any(|&(has_data, _)| has_data) + { + len += fields_with_content[i].1; + i += 1; } - if self.akm_suits.is_empty() { - return len; - } else { - len += 2 + self.akm_suits.len() * Nl80211AkmSuite::LENGTH; + len + } + + fn emit(&self, buffer: &mut [u8]) { + let mut position = 0; + + write_u16_le(&mut buffer[position..], self.version); + position += 2; + + // If any of the following fields do have some content, the next field + // has to be parsed into bytes even if is 0 or 0 for the length + // field. + let fields_with_content = [ + self.group_cipher.is_some(), + !self.pairwise_ciphers.is_empty(), + !self.akm_suits.is_empty(), + self.rsn_capbilities.is_some(), + !self.pmkids.is_empty(), + self.group_mgmt_cipher.is_some(), + ]; + let mut fields_with_content = + crate::helper::emit::FieldFlags::new(&fields_with_content); + + if !fields_with_content.should_emit() { + return; } - if self.rsn_capbilities.is_none() { - return len; - } else { - len += 2; + write_u32_le( + &mut buffer[position..], + u32::from(self.group_cipher.unwrap_or_default()), + ); + position += 4; + + if !fields_with_content.should_emit() { + return; + } + write_u16_le( + &mut buffer[position..], + self.pairwise_ciphers.len() as u16, + ); + position += 2; + for cipher in &self.pairwise_ciphers { + write_u32_le(&mut buffer[position..], u32::from(*cipher)); + position += 4; } - if self.pmkids.is_empty() { - return len; - } else { - len += 2 + self.pmkids.len() * Nl80211Pmkid::LENGTH; + if !fields_with_content.should_emit() { + return; } - if self.group_mgmt_cipher.is_none() { - return len; - } else { - len += Nl80211CipherSuite::LENGTH; + write_u16_le(&mut buffer[position..], self.akm_suits.len() as u16); + position += 2; + for akm_suite in &self.akm_suits { + write_u32_le(&mut buffer[position..], u32::from(*akm_suite)); + position += 4; } - len - } + if !fields_with_content.should_emit() { + return; + } + write_u16_le( + &mut buffer[position..], + self.rsn_capbilities.unwrap_or_default().bits(), + ); + position += 2; + + if !fields_with_content.should_emit() { + return; + } + write_u16_le(&mut buffer[6..8], self.pairwise_ciphers.len() as u16); + position += 2; + for pkmid in &self.pmkids { + buffer[position..].copy_from_slice(&pkmid.0); + position += pkmid.0.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), - ); + if !fields_with_content.should_emit() { + return; } + write_u32_le( + &mut buffer[position..], + u32::from(self.group_mgmt_cipher.unwrap_or_default()), + ); } } @@ -989,3 +1092,60 @@ impl Nl80211Pmkid { } } } + +#[cfg(test)] +mod test { + use super::*; + use crate::macros::test::roundtrip_emit_parse_test; + + roundtrip_emit_parse_test!( + ssid, + Nl80211Element, + Nl80211Element::Ssid(BString::from("test-ssid")), + ); + roundtrip_emit_parse_test!( + rates_and_selectors, + Nl80211Element, + Nl80211Element::SupportedRatesAndSelectors(vec![ + Nl80211RateAndSelector::BssBasicRateSet(1), + Nl80211RateAndSelector::Rate(1), + Nl80211RateAndSelector::SelectorHt, + Nl80211RateAndSelector::SelectorVht, + Nl80211RateAndSelector::SelectorGlk, + ]) + ); + roundtrip_emit_parse_test!( + channel, + Nl80211Element, + Nl80211Element::Channel(7) + ); + roundtrip_emit_parse_test!( + country, + Nl80211Element, + Nl80211Element::Country(Nl80211ElementCountry { + country: "DE".to_owned(), + environment: Nl80211ElementCountryEnvironment::IndoorAndOutdoor, + triplets: vec![Nl80211ElementCountryTriplet::Subband( + Nl80211ElementSubBand { + channel_start: 1, + channel_count: 13, + max_power_level: 20, + } + )], + }), + ); + + roundtrip_emit_parse_test!( + rsn, + Nl80211Element, + Nl80211Element::Rsn(Nl80211ElementRsn { + version: 1, + group_cipher: Some(Nl80211CipherSuite::Ccmp128), + pairwise_ciphers: vec![Nl80211CipherSuite::Ccmp128], + akm_suits: vec![Nl80211AkmSuite::Psk], + rsn_capbilities: None, + pmkids: Vec::new(), + group_mgmt_cipher: None, + }) + ); +} diff --git a/src/helper.rs b/src/helper.rs new file mode 100644 index 0000000..6aaba64 --- /dev/null +++ b/src/helper.rs @@ -0,0 +1,18 @@ +pub(crate) mod emit { + pub(crate) struct FieldFlags<'a> { + flags: &'a [bool], + pos: usize, + } + + impl<'a> FieldFlags<'a> { + pub(crate) fn new(flags: &'a [bool]) -> Self { + Self { flags, pos: 0 } + } + + pub(crate) fn should_emit(&mut self) -> bool { + let emit = self.flags[self.pos..].iter().any(|&f| f); + self.pos += 1; + emit + } + } +} diff --git a/src/iface/set.rs b/src/iface/set.rs index 296cd37..dc902c9 100644 --- a/src/iface/set.rs +++ b/src/iface/set.rs @@ -33,15 +33,8 @@ impl Nl80211InterfaceSetRequest { let nl80211_msg = Nl80211Message { cmd: Nl80211Command::SetInterface, - // TODO: Should this be open to every attribute or restricted to - // Interface ATTR? - attributes: dbg!(attributes), + attributes, }; - // TODO: is the in iw dev set - // I don't understand the correct setting of flags yet. In case of set - // `iw` does not set any flag, but here NLM_F_REQUEST is always - // set. Curentliy infreadead.org libnl documentation website is - // down. These are the same flags as in scan.rs let flags = NLM_F_REQUEST | NLM_F_ACK; nl80211_execute(&mut handle, nl80211_msg, flags).await diff --git a/src/lib.rs b/src/lib.rs index df74fd6..ff86444 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod ext_cap; mod feature; mod frame_type; mod handle; +mod helper; mod iface; mod macros; mod message; @@ -34,7 +35,7 @@ pub use self::command::Nl80211Command; #[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::element::{Nl80211Element, Nl80211Elements, RawNl80211Elements}; pub use self::error::Nl80211Error; pub use self::ext_cap::{ Nl80211ExtendedCapability, Nl80211IfTypeExtCapa, Nl80211IfTypeExtCapas, @@ -94,7 +95,6 @@ pub use self::wiphy::{ Nl80211WowlanTrigerPatternSupport, Nl80211WowlanTriggersSupport, }; -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/macros.rs b/src/macros.rs index 867a4cc..e528756 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -30,3 +30,44 @@ macro_rules! try_nl80211 { } }}; } + +#[cfg(test)] +pub(crate) mod test { + #[macro_export] + macro_rules! roundtrip_emit_parse_test { + ($name:ident, $ty:ty, $new:expr$(,)?) => { + #[test] + fn $name() { + let val: $ty = $new; + + let mut buffer = vec![0; val.buffer_len() + // To check if the type can be emitted to a buffer greater than + // the needed size + + 1 + ]; + val.emit(buffer.as_mut_slice()); + + assert_eq!( + <$ty>::parse(&buffer[0..val.buffer_len()]).unwrap(), + val, + ); + } + }; + } + + macro_rules! roundtrip_from_test { + ($name:ident, $from:ty => $into:ty, $new:expr$(,)?) => { + #[test] + fn $name() { + let val: $from = $new; + + let into: $into = val.into(); + + assert_eq!(<$from>::from(into), val,); + } + }; + } + + pub(crate) use roundtrip_emit_parse_test; + pub(crate) use roundtrip_from_test; +} diff --git a/src/scan/attr.rs b/src/scan/attr.rs index 77eafcc..31d224a 100644 --- a/src/scan/attr.rs +++ b/src/scan/attr.rs @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT +use bstr::{BString, ByteSlice}; use netlink_packet_core::{ - parse_string, parse_u32, DecodeError, Emitable, ErrorContext, Nla, - NlasIterator, Parseable, + parse_u32, DecodeError, Emitable, ErrorContext, Nla, NlasIterator, + Parseable, }; use crate::bytes::write_u32; @@ -12,7 +13,7 @@ use crate::Nl80211Attr; #[derive(Debug, Clone)] pub(crate) struct Nla80211ScanSsidNla { index: u16, - ssid: String, + ssid: BString, } impl Nla for Nla80211ScanSsidNla { @@ -41,21 +42,23 @@ impl std::ops::Deref for Nla80211ScanSsidNlas { } } -impl From<&Vec> for Nla80211ScanSsidNlas { - fn from(ssids: &Vec) -> Self { - let mut nlas = Vec::new(); - for (i, ssid) in ssids.iter().enumerate() { - let nla = Nla80211ScanSsidNla { - index: i as u16, - ssid: ssid.to_string(), - }; - nlas.push(nla); - } - Nla80211ScanSsidNlas(nlas) +impl From<&Vec> for Nla80211ScanSsidNlas { + fn from(ssids: &Vec) -> Self { + Nla80211ScanSsidNlas( + ssids + .iter() + .cloned() + .enumerate() + .map(|(i, ssid)| Nla80211ScanSsidNla { + index: i as u16, + ssid, + }) + .collect(), + ) } } -impl From for Vec { +impl From for Vec { fn from(ssids: Nla80211ScanSsidNlas) -> Self { let mut ssids = ssids; ssids.0.drain(..).map(|c| c.ssid).collect() @@ -68,7 +71,7 @@ impl Nla80211ScanSsidNlas { for (index, nla) in NlasIterator::new(payload).enumerate() { let error_msg = format!("Invalid NL80211_ATTR_SCAN_SSIDS: {nla:?}"); let nla = &nla.context(error_msg.clone())?; - let ssid = parse_string(nla.value()) + let ssid = crate::attr::parse_bstring(nla.value()) .context(format!("Invalid NL80211_ATTR_SCAN_SSIDS: {nla:?}"))?; ssids.push(Nla80211ScanSsidNla { index: index as u16, diff --git a/src/scan/bss_info.rs b/src/scan/bss_info.rs index d7be908..f02a938 100644 --- a/src/scan/bss_info.rs +++ b/src/scan/bss_info.rs @@ -39,7 +39,7 @@ use netlink_packet_core::{ use crate::{ bytes::{write_i32, write_u16, write_u32, write_u64}, - Nl80211Element, Nl80211Elements, + RawNl80211Elements, }; bitflags::bitflags! { @@ -155,15 +155,15 @@ pub enum Nl80211BssInfo { /// Beacon interval of the (I)BSS BeaconInterval(u16), Capability(Nl80211BssCapabilities), - InformationElements(Vec), + InformationElements(RawNl80211Elements), SignalMbm(i32), SignalUnspec(u8), Status(u32), SeenMsAgo(u32), - BeaconInformationElements(Vec), + BeaconInformationElements(RawNl80211Elements), ChanWidth(u32), BeaconTsf(u64), - ProbeResponseInformationElements(Vec), + ProbeResponseInformationElements(RawNl80211Elements), /// `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) @@ -189,9 +189,7 @@ impl Nla for Nl80211BssInfo { Self::BeaconTsf(_) | Self::Tsf(_) | Self::LastSeenBootTime(_) => 8, Self::InformationElements(v) | Self::BeaconInformationElements(v) - | Self::ProbeResponseInformationElements(v) => { - Nl80211Elements::from(v).buffer_len() - } + | Self::ProbeResponseInformationElements(v) => v.buffer_len(), Self::Capability(_) => Nl80211BssCapabilities::LENGTH, Self::UseFor(_) => Nl80211BssUseFor::LENGTH, Self::Other(attr) => attr.value_len(), @@ -237,9 +235,7 @@ impl Nla for Nl80211BssInfo { } Self::InformationElements(v) | Self::BeaconInformationElements(v) - | Self::ProbeResponseInformationElements(v) => { - Nl80211Elements::from(v).emit(buffer) - } + | Self::ProbeResponseInformationElements(v) => v.emit(buffer), Self::Capability(v) => v.emit(buffer), Self::UseFor(v) => v.emit(buffer), Self::Other(ref attr) => attr.emit(buffer), @@ -284,13 +280,13 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> 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(), + RawNl80211Elements::parse(payload)?, ), + NL80211_BSS_INFORMATION_ELEMENTS => { + Self::InformationElements(RawNl80211Elements::parse(payload)?) + } NL80211_BSS_PRESP_DATA => Self::ProbeResponseInformationElements( - Nl80211Elements::parse(payload)?.into(), + RawNl80211Elements::parse(payload)?, ), NL80211_BSS_SIGNAL_MBM => { let err_msg = diff --git a/src/scan/handle.rs b/src/scan/handle.rs index ef13d1b..0166117 100644 --- a/src/scan/handle.rs +++ b/src/scan/handle.rs @@ -8,6 +8,7 @@ use crate::{ Nl80211ScanScheduleStopRequest, Nl80211ScanTriggerRequest, Nl80211SchedScanMatch, Nl80211SchedScanPlan, }; +use bstr::BString; #[derive(Debug, Clone)] pub struct Nl80211ScanHandle(Nl80211Handle); @@ -60,14 +61,14 @@ impl Nl80211Scan { pub fn new(if_index: u32) -> Nl80211AttrsBuilder { Nl80211AttrsBuilder::::new() .if_index(if_index) - .ssids(vec!["".to_string()]) + .ssids(vec![BString::from("")]) } } impl Nl80211AttrsBuilder { /// SSIDs to send probe request during active scan. /// `vec!["".to_string()]` means wildcard. - pub fn ssids(self, ssids: Vec) -> Self { + pub fn ssids(self, ssids: Vec) -> Self { self.replace(Nl80211Attr::ScanSsids(ssids)) } @@ -87,7 +88,7 @@ impl Nl80211AttrsBuilder { if value { self.remove(Nl80211Attr::ScanSsids(Vec::new()).kind()) } else { - self.replace(Nl80211Attr::ScanSsids(vec!["".to_string()])) + self.replace(Nl80211Attr::ScanSsids(vec![BString::from("")])) } } diff --git a/src/wifi4.rs b/src/wifi4.rs index 8695dfd..c31770c 100644 --- a/src/wifi4.rs +++ b/src/wifi4.rs @@ -123,7 +123,7 @@ impl Emitable for Nl80211HtCaps { } fn emit(&self, buffer: &mut [u8]) { - buffer.copy_from_slice(&self.bits().to_ne_bytes()) + buffer[0..self.buffer_len()].copy_from_slice(&self.bits().to_ne_bytes()) } } @@ -469,9 +469,9 @@ impl From for [u8; 2] { fn from(v: Nl80211HtExtendedCap) -> [u8; 2] { [ v.pco as u8 | (v.pco_trans_time << 1) | (v.mcs_feedback & 0b1) << 7, - ((v.mcs_feedback & 0b10) >> 1) - | ((v.support_ht_control as u8) << 1) - | ((v.rd_responder as u8) << 2), + (v.mcs_feedback & 0b11) + | ((v.support_ht_control as u8) << 2) + | ((v.rd_responder as u8) << 3), ] } } @@ -626,7 +626,7 @@ impl Emitable for Nl80211HtTransmitBeamformingCaps { } fn emit(&self, buffer: &mut [u8]) { - buffer.copy_from_slice(&self.bits().to_ne_bytes()) + buffer[0..self.buffer_len()].copy_from_slice(&self.bits().to_ne_bytes()) } } @@ -674,6 +674,94 @@ impl Emitable for Nl80211HtAselCaps { } fn emit(&self, buffer: &mut [u8]) { - buffer.copy_from_slice(&self.bits().to_ne_bytes()) - } + buffer[0..self.buffer_len()].copy_from_slice(&self.bits().to_ne_bytes()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::macros::test::{roundtrip_emit_parse_test, roundtrip_from_test}; + + roundtrip_emit_parse_test!(caps, Nl80211HtCaps, Nl80211HtCaps::all()); + roundtrip_emit_parse_test!( + asel_caps, + Nl80211HtAselCaps, + Nl80211HtAselCaps::all() + ); + roundtrip_emit_parse_test!( + transmit_beamforming_cap, + Nl80211HtTransmitBeamformingCaps, + Nl80211HtTransmitBeamformingCaps::all(), + ); + + roundtrip_from_test!(tx_params, Nl80211HtTxParameter => u8, Nl80211HtTxParameter { + mcs_set_defined: false, + tx_rx_mcs_set_not_equal: false, + max_spatial_streams: 1, + unequal_modulation_supported: false, + }); + + roundtrip_from_test!(ht_wiphy_no_ht, Nl80211HtWiphyChannelType => u32, Nl80211HtWiphyChannelType::NoHt); + roundtrip_from_test!(ht_wiphy_ht_20, Nl80211HtWiphyChannelType => u32, Nl80211HtWiphyChannelType::Ht20); + roundtrip_from_test!(ht_wiphy_other, Nl80211HtWiphyChannelType => u32, Nl80211HtWiphyChannelType::Other(NL80211_CHAN_HT40PLUS + 1)); + + roundtrip_emit_parse_test!( + mcs_info, + Nl80211HtMcsInfo, + Nl80211HtMcsInfo { + rx_mask: [0xA5; IEEE80211_HT_MCS_MASK_LEN], + rx_highest: u16::MAX, + tx_params: Nl80211HtTxParameter { + mcs_set_defined: false, + tx_rx_mcs_set_not_equal: false, + max_spatial_streams: 1, + unequal_modulation_supported: false, + }, + }, + ); + + roundtrip_from_test!(a_mpdu_para, Nl80211HtAMpduPara => u8, Nl80211HtAMpduPara { + max_len_exponent: u8::MAX & 0b11, + min_space: u8::MAX & 0b111, + }); + + roundtrip_from_test!(extend_cap, Nl80211HtExtendedCap => [u8; 2], Nl80211HtExtendedCap { + pco: true, + pco_trans_time: 1, + mcs_feedback: 1, + support_ht_control: true, + rd_responder: true, + }); + + roundtrip_emit_parse_test!( + cap_mask, + Nl80211ElementHtCap, + Nl80211ElementHtCap { + caps: Nl80211HtCaps::all(), + a_mpdu_para: Nl80211HtAMpduPara { + max_len_exponent: 3, + min_space: 7, + }, + mcs_set: Nl80211HtMcsInfo { + rx_mask: [0xA5; IEEE80211_HT_MCS_MASK_LEN], + rx_highest: u16::MAX, + tx_params: Nl80211HtTxParameter { + mcs_set_defined: false, + tx_rx_mcs_set_not_equal: false, + max_spatial_streams: 1, + unequal_modulation_supported: false, + }, + }, + ht_ext_cap: Nl80211HtExtendedCap { + pco: true, + pco_trans_time: 2, + mcs_feedback: 2, + support_ht_control: true, + rd_responder: true, + }, + transmit_beamforming_cap: Nl80211HtTransmitBeamformingCaps::all(), + asel_cap: Nl80211HtAselCaps::all(), + }, + ); }