diff --git a/Cargo.toml b/Cargo.toml index 65ecff7..4695b8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,5 +39,8 @@ netlink-proto = { default-features = false, version = "0.11.2" } netlink-sys = { version = "0.8.4" } [dev-dependencies] -tokio = { version = "1.11.0", features = ["macros", "rt", "rt-multi-thread"] } env_logger = "0.9.0" + +[dev-dependencies.tokio] +version = "1.11.0" +features = ["macros", "rt", "rt-multi-thread", "time"] diff --git a/examples/nl80211_trigger_scan.rs b/examples/nl80211_trigger_scan.rs new file mode 100644 index 0000000..bde9e07 --- /dev/null +++ b/examples/nl80211_trigger_scan.rs @@ -0,0 +1,55 @@ +// 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: nl80211_trigger_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() + .enable_time() + .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 attrs = wl_nl80211::Nl80211Scan::new(if_index) + .duration(5000) + .passive(true) + .build(); + + let mut scan_handle = handle.scan().trigger(attrs).execute().await; + + let mut msgs = Vec::new(); + while let Some(msg) = scan_handle.try_next().await.unwrap() { + msgs.push(msg); + } + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + + let mut dump = handle.scan().dump(if_index).execute().await; + let mut msgs = Vec::new(); + while let Some(msg) = dump.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 efe45d4..2672a90 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -38,6 +38,7 @@ use netlink_packet_utils::{ use crate::{ bytes::{write_u16, write_u32, write_u64}, + scan::{Nla80211ScanFreqNlas, Nla80211ScanSsidNlas}, wiphy::Nl80211Commands, Nl80211Band, Nl80211BandTypes, Nl80211BssInfo, Nl80211ChannelWidth, Nl80211CipherSuit, Nl80211Command, Nl80211ExtFeature, Nl80211ExtFeatures, @@ -45,10 +46,13 @@ use crate::{ Nl80211HtWiphyChannelType, Nl80211IfMode, Nl80211IfTypeExtCapa, Nl80211IfTypeExtCapas, Nl80211IfaceComb, Nl80211IfaceFrameType, Nl80211InterfaceType, Nl80211InterfaceTypes, Nl80211MloLink, + Nl80211ScanFlags, Nl80211SchedScanMatch, Nl80211SchedScanPlan, Nl80211StationInfo, Nl80211TransmitQueueStat, Nl80211VhtCapability, Nl80211WowlanTrigersSupport, }; +const ETH_ALEN: usize = 6; + struct MacAddressNlas(Vec); impl std::ops::Deref for MacAddressNlas { @@ -160,8 +164,8 @@ const NL80211_ATTR_WIPHY_CHANNEL_TYPE: u16 = 39; // const NL80211_ATTR_MGMT_SUBTYPE:u16 = 41; // const NL80211_ATTR_IE:u16 = 42; 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_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_REG_INITIATOR:u16 = 48; @@ -236,7 +240,7 @@ const NL80211_ATTR_SUPPORT_MESH_AUTH: u16 = 115; // const NL80211_ATTR_STA_PLINK_STATE:u16 = 116; // const NL80211_ATTR_WOWLAN_TRIGGERS:u16 = 117; const NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED: u16 = 118; -// const NL80211_ATTR_SCHED_SCAN_INTERVAL:u16 = 119; +const NL80211_ATTR_SCHED_SCAN_INTERVAL: u16 = 119; const NL80211_ATTR_INTERFACE_COMBINATIONS: u16 = 120; const NL80211_ATTR_SOFTWARE_IFTYPES: u16 = 121; // const NL80211_ATTR_REKEY_DATA:u16 = 122; @@ -249,7 +253,7 @@ const NL80211_ATTR_MAX_SCHED_SCAN_IE_LEN: u16 = 124; // const NL80211_ATTR_STA_WME:u16 = 129; const NL80211_ATTR_SUPPORT_AP_UAPSD: u16 = 130; const NL80211_ATTR_ROAM_SUPPORT: u16 = 131; -// const NL80211_ATTR_SCHED_SCAN_MATCH:u16 = 132; +const NL80211_ATTR_SCHED_SCAN_MATCH: u16 = 132; const NL80211_ATTR_MAX_MATCH_SETS: u16 = 133; // const NL80211_ATTR_PMKSA_CANDIDATE:u16 = 134; // const NL80211_ATTR_TX_NO_CCK_RATE:u16 = 135; @@ -275,7 +279,7 @@ const NL80211_ATTR_WDEV: u16 = 153; // const NL80211_ATTR_CONN_FAILED_REASON:u16 = 155; // const NL80211_ATTR_AUTH_DATA:u16 = 156; const NL80211_ATTR_VHT_CAPABILITY: u16 = 157; -// const NL80211_ATTR_SCAN_FLAGS:u16 = 158; +const NL80211_ATTR_SCAN_FLAGS: u16 = 158; const NL80211_ATTR_CHANNEL_WIDTH: u16 = 159; const NL80211_ATTR_CENTER_FREQ1: u16 = 160; const NL80211_ATTR_CENTER_FREQ2: u16 = 161; @@ -332,17 +336,17 @@ const NL80211_ATTR_MAX_CSA_COUNTERS: u16 = 206; // const NL80211_ATTR_ADMITTED_TIME:u16 = 212; // const NL80211_ATTR_SMPS_MODE:u16 = 213; // const NL80211_ATTR_OPER_CLASS:u16 = 214; -// const NL80211_ATTR_MAC_MASK:u16 = 215; +const NL80211_ATTR_MAC_MASK: u16 = 215; const NL80211_ATTR_WIPHY_SELF_MANAGED_REG: u16 = 216; const NL80211_ATTR_EXT_FEATURES: u16 = 217; // const NL80211_ATTR_SURVEY_RADIO_STATS:u16 = 218; // const NL80211_ATTR_NETNS_FD:u16 = 219; -// const NL80211_ATTR_SCHED_SCAN_DELAY:u16 = 220; +const NL80211_ATTR_SCHED_SCAN_DELAY: u16 = 220; // const NL80211_ATTR_REG_INDOOR:u16 = 221; const NL80211_ATTR_MAX_NUM_SCHED_SCAN_PLANS: u16 = 222; const NL80211_ATTR_MAX_SCAN_PLAN_INTERVAL: u16 = 223; const NL80211_ATTR_MAX_SCAN_PLAN_ITERATIONS: u16 = 224; -// const NL80211_ATTR_SCHED_SCAN_PLANS:u16 = 225; +const NL80211_ATTR_SCHED_SCAN_PLANS: u16 = 225; // const NL80211_ATTR_PBSS:u16 = 226; // const NL80211_ATTR_BSS_SELECT:u16 = 227; // const NL80211_ATTR_STA_SUPPORT_P2P_PS:u16 = 228; @@ -352,7 +356,7 @@ const NL80211_ATTR_IFTYPE_EXT_CAPA: u16 = 230; // const NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR:u16 = 232; // const NL80211_ATTR_SCAN_START_TIME_TSF:u16 = 233; // const NL80211_ATTR_SCAN_START_TIME_TSF_BSSID:u16 = 234; -// const NL80211_ATTR_MEASUREMENT_DURATION:u16 = 235; +const NL80211_ATTR_MEASUREMENT_DURATION: u16 = 235; // const NL80211_ATTR_MEASUREMENT_DURATION_MANDATORY:u16 = 236; // const NL80211_ATTR_MESH_PEER_AID:u16 = 237; // const NL80211_ATTR_NAN_MASTER_PREF:u16 = 238; @@ -452,8 +456,6 @@ const NL80211_ATTR_MAX_HW_TIMESTAMP_PEERS: u16 = 323; // const NL80211_ATTR_WIPHY_RADIOS:u16 = 331; // const NL80211_ATTR_WIPHY_INTERFACE_COMBINATIONS:u16 = 332; -const ETH_ALEN: usize = 6; - #[derive(Debug, PartialEq, Eq, Clone)] #[non_exhaustive] pub enum Nl80211Attr { @@ -464,6 +466,7 @@ pub enum Nl80211Attr { IfType(Nl80211InterfaceType), IfTypeExtCap(Vec), Mac([u8; ETH_ALEN]), + MacMask([u8; ETH_ALEN]), MacAddrs(Vec<[u8; ETH_ALEN]>), Wdev(u64), Generation(u32), @@ -546,6 +549,33 @@ pub enum Nl80211Attr { MaxHwTimestampPeers(u16), /// Basic Service Set (BSS) Bss(Vec), + ScanSsids(Vec), + ScanFlags(Nl80211ScanFlags), + MeasurementDuration(u16), + /// Scan interval in millisecond(ms) + SchedScanInterval(u32), + /// Delay before the first cycle of a scheduled scan is started. Or the + /// delay before a WoWLAN net-detect scan is started, counting from the + /// moment the system is suspended. This value is in seconds. + SchedScanDelay(u32), + /// Scan frequencies in MHz. + ScanFrequencies(Vec), + /// Sets of attributes to match during scheduled scans. Only BSSs + /// that match any of the sets will be reported. These are pass-thru + /// filter rules. For a match to succeed, the BSS must match all + /// attributes of a set. Since not every hardware supports matching all + /// types of attributes, there is no guarantee that the reported BSSs are + /// fully complying with the match sets and userspace needs to be able to + /// ignore them by itself. Thus, the implementation is somewhat + /// hardware-dependent, but this is only an optimization and the userspace + /// application needs to handle all the non-filtered results anyway. + SchedScanMatch(Vec), + /// A list of scan plans for scheduled scan. Each scan plan defines the + /// number of scan iterations and the interval between scans. The last scan + /// plan will always run infinitely, thus it must not specify the number of + /// iterations, only the interval between scans. The scan plans are + /// executed sequentially. + SchedScanPlans(Vec), Other(DefaultNla), } @@ -576,10 +606,12 @@ impl Nla for Nl80211Attr { | Self::SchedScanMaxReqs(_) | Self::TransmitQueueLimit(_) | Self::TransmitQueueMemoryLimit(_) - | Self::TransmitQueueQuantum(_) => 4, + | Self::TransmitQueueQuantum(_) + | Self::SchedScanInterval(_) + | Self::SchedScanDelay(_) => 4, Self::Wdev(_) => 8, Self::IfName(s) | Self::Ssid(s) | Self::WiphyName(s) => s.len() + 1, - Self::Mac(_) => ETH_ALEN, + Self::Mac(_) | Self::MacMask(_) => ETH_ALEN, Self::MacAddrs(s) => { MacAddressNlas::from(s).as_slice().buffer_len() } @@ -633,9 +665,19 @@ impl Nla for Nl80211Attr { Self::EmlCapability(_) | Self::MldCapaAndOps(_) | Self::MaxNumAkmSuites(_) - | Self::MaxHwTimestampPeers(_) => 2, + | Self::MaxHwTimestampPeers(_) + | Self::MeasurementDuration(_) => 2, Self::Bands(_) => Nl80211BandTypes::LENGTH, Self::Bss(v) => v.as_slice().buffer_len(), + Self::ScanSsids(v) => { + Nla80211ScanSsidNlas::from(v).as_slice().buffer_len() + } + Self::ScanFlags(v) => v.buffer_len(), + Self::ScanFrequencies(v) => { + Nla80211ScanFreqNlas::from(v).as_slice().buffer_len() + } + Self::SchedScanMatch(v) => v.as_slice().buffer_len(), + Self::SchedScanPlans(v) => v.as_slice().buffer_len(), Self::Other(attr) => attr.value_len(), } } @@ -648,6 +690,7 @@ impl Nla for Nl80211Attr { Self::IfName(_) => NL80211_ATTR_IFNAME, Self::IfType(_) => NL80211_ATTR_IFTYPE, Self::Mac(_) => NL80211_ATTR_MAC, + Self::MacMask(_) => NL80211_ATTR_MAC_MASK, Self::MacAddrs(_) => NL80211_ATTR_MAC_ADDRS, Self::Wdev(_) => NL80211_ATTR_WDEV, Self::Generation(_) => NL80211_ATTR_GENERATION, @@ -733,6 +776,14 @@ impl Nla for Nl80211Attr { Self::MaxNumAkmSuites(_) => NL80211_ATTR_MAX_NUM_AKM_SUITES, Self::MaxHwTimestampPeers(_) => NL80211_ATTR_MAX_HW_TIMESTAMP_PEERS, Self::Bss(_) => NL80211_ATTR_BSS, + Self::ScanSsids(_) => NL80211_ATTR_SCAN_SSIDS, + Self::ScanFlags(_) => NL80211_ATTR_SCAN_FLAGS, + Self::MeasurementDuration(_) => NL80211_ATTR_MEASUREMENT_DURATION, + Self::SchedScanInterval(_) => NL80211_ATTR_SCHED_SCAN_INTERVAL, + Self::SchedScanDelay(_) => NL80211_ATTR_SCHED_SCAN_DELAY, + Self::ScanFrequencies(_) => NL80211_ATTR_SCAN_FREQUENCIES, + Self::SchedScanMatch(_) => NL80211_ATTR_SCHED_SCAN_MATCH, + Self::SchedScanPlans(_) => NL80211_ATTR_SCHED_SCAN_PLANS, Self::Other(attr) => attr.kind(), } } @@ -760,13 +811,15 @@ impl Nla for Nl80211Attr { | Self::SchedScanMaxReqs(d) | Self::TransmitQueueLimit(d) | Self::TransmitQueueMemoryLimit(d) - | Self::TransmitQueueQuantum(d) => write_u32(buffer, *d), + | Self::TransmitQueueQuantum(d) + | Self::SchedScanInterval(d) + | Self::SchedScanDelay(d) => write_u32(buffer, *d), Self::MaxScanIeLen(d) | Self::MaxSchedScanIeLen(d) => { write_u16(buffer, *d) } Self::Wdev(d) => write_u64(buffer, *d), Self::IfType(d) => write_u32(buffer, (*d).into()), - Self::Mac(s) => buffer.copy_from_slice(s), + Self::Mac(s) | Self::MacMask(s) => buffer.copy_from_slice(s), Self::MacAddrs(s) => { MacAddressNlas::from(s).as_slice().emit(buffer) } @@ -834,9 +887,19 @@ impl Nla for Nl80211Attr { Self::EmlCapability(d) | Self::MldCapaAndOps(d) | Self::MaxNumAkmSuites(d) - | Self::MaxHwTimestampPeers(d) => write_u16(buffer, *d), + | Self::MaxHwTimestampPeers(d) + | Self::MeasurementDuration(d) => write_u16(buffer, *d), Self::Bands(v) => v.emit(buffer), Self::Bss(v) => v.as_slice().emit(buffer), + Self::ScanSsids(v) => { + Nla80211ScanSsidNlas::from(v).as_slice().emit(buffer) + } + Self::ScanFlags(v) => v.emit(buffer), + Self::ScanFrequencies(v) => { + Nla80211ScanFreqNlas::from(v).as_slice().emit(buffer) + } + Self::SchedScanMatch(v) => v.as_slice().emit(buffer), + Self::SchedScanPlans(v) => v.as_slice().emit(buffer), Self::Other(attr) => attr.emit(buffer), } } @@ -882,11 +945,26 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nl80211Attr { ret } else { return Err(format!( - "Invalid length of NL80211_ATTR_MAC, expected length {} got {:?}", + "Invalid length of NL80211_ATTR_MAC, \ + expected length {} got {:?}", ETH_ALEN, payload ) .into()); }), + NL80211_ATTR_MAC_MASK => { + Self::MacMask(if payload.len() == ETH_ALEN { + let mut ret = [0u8; ETH_ALEN]; + ret.copy_from_slice(&payload[..ETH_ALEN]); + ret + } else { + return Err(format!( + "Invalid length of NL80211_ATTR_MAC_MASK, \ + expected length {} got {:?}", + ETH_ALEN, payload + ) + .into()); + }) + } NL80211_ATTR_MAC_ADDRS => { Self::MacAddrs(MacAddressNlas::parse(payload)?.into()) } @@ -1347,6 +1425,60 @@ impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> for Nl80211Attr { payload ))?, ), + NL80211_ATTR_SCAN_SSIDS => { + Self::ScanSsids(Nla80211ScanSsidNlas::parse(payload)?.into()) + } + NL80211_ATTR_SCAN_FLAGS => { + Self::ScanFlags(Nl80211ScanFlags::parse(payload)?) + } + NL80211_ATTR_MEASUREMENT_DURATION => { + let err_msg = format!( + "Invalid NL80211_ATTR_MEASUREMENT_DURATION value {:?}", + payload + ); + Self::MeasurementDuration(parse_u16(payload).context(err_msg)?) + } + NL80211_ATTR_SCHED_SCAN_INTERVAL => { + let err_msg = format!( + "Invalid NL80211_ATTR_SCHED_SCAN_INTERVAL value {:?}", + payload + ); + Self::SchedScanInterval(parse_u32(payload).context(err_msg)?) + } + NL80211_ATTR_SCHED_SCAN_DELAY => { + let err_msg = format!( + "Invalid NL80211_ATTR_SCHED_SCAN_DELAY value {:?}", + payload + ); + Self::SchedScanDelay(parse_u32(payload).context(err_msg)?) + } + NL80211_ATTR_SCAN_FREQUENCIES => Self::ScanFrequencies( + Nla80211ScanFreqNlas::parse(payload)?.into(), + ), + NL80211_ATTR_SCHED_SCAN_MATCH => { + let err_msg = format!( + "Invalid NL80211_ATTR_SCHED_SCAN_MATCH value {:?}", + payload + ); + let mut nlas = Vec::new(); + for nla in NlasIterator::new(payload) { + let nla = &nla.context(err_msg.clone())?; + nlas.push(Nl80211SchedScanMatch::parse(nla)?); + } + Self::SchedScanMatch(nlas) + } + NL80211_ATTR_SCHED_SCAN_PLANS => { + let err_msg = format!( + "Invalid NL80211_ATTR_SCHED_SCAN_PLANS value {:?}", + payload + ); + let mut nlas = Vec::new(); + for nla in NlasIterator::new(payload) { + let nla = &nla.context(err_msg.clone())?; + nlas.push(Nl80211SchedScanPlan::parse(nla)?); + } + Self::SchedScanPlans(nlas) + } _ => Self::Other( DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?, ), diff --git a/src/builder.rs b/src/builder.rs new file mode 100644 index 0000000..fe4f94d --- /dev/null +++ b/src/builder.rs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT + +use std::collections::HashMap; +use std::marker::PhantomData; + +use netlink_packet_utils::nla::Nla; + +use crate::Nl80211Attr; + +#[derive(Debug)] +pub struct Nl80211AttrsBuilder { + attribute_map: HashMap>, + _phantom: PhantomData, +} + +impl Default for Nl80211AttrsBuilder { + fn default() -> Self { + Self::new() + } +} + +impl Nl80211AttrsBuilder { + pub fn new() -> Self { + Self { + attribute_map: HashMap::new(), + _phantom: PhantomData, + } + } + + /// Add Nl80211Attr by removing other Nl80211Attr holding the same + /// Nl80211Attr.kind() + pub fn replace(self, attr: Nl80211Attr) -> Self { + let mut ret = self; + ret.attribute_map.insert(attr.kind(), vec![attr]); + ret + } + + pub fn append(self, attr: Nl80211Attr) -> Self { + let mut ret = self; + ret.attribute_map.entry(attr.kind()).or_default().push(attr); + ret + } + + pub fn remove(self, kind: u16) -> Self { + let mut ret = self; + ret.attribute_map.remove(&kind); + ret + } + + pub fn build(self) -> Vec { + let mut data = self; + let mut ret: Vec = Vec::new(); + for (_, mut v) in data.attribute_map.drain() { + ret.append(&mut v) + } + + ret.sort_unstable_by_key(|a| a.kind()); + ret + } + + pub fn if_index(self, if_index: u32) -> Self { + self.replace(Nl80211Attr::IfIndex(if_index)) + } + + pub fn ssid(self, ssid: &str) -> Self { + self.append(Nl80211Attr::Ssid(ssid.to_string())) + } +} diff --git a/src/bytes.rs b/src/bytes.rs index a0c1351..636b4bb 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -18,14 +18,14 @@ 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 write_u64(buffer: &mut [u8], value: u64) { + buffer[..8].copy_from_slice(&value.to_ne_bytes()) +} + /// The `pos` is index from bit 0. pub(crate) fn get_bit(data: &[u8], pos: usize) -> bool { let index: usize = pos / 8; diff --git a/src/handle.rs b/src/handle.rs index 7cbf690..7aa2f89 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -2,7 +2,7 @@ use futures::{future::Either, FutureExt, Stream, StreamExt, TryStream}; use genetlink::GenetlinkHandle; -use netlink_packet_core::{NetlinkMessage, NLM_F_DUMP, NLM_F_REQUEST}; +use netlink_packet_core::NetlinkMessage; use netlink_packet_generic::GenlMessage; use netlink_packet_utils::DecodeError; @@ -65,13 +65,12 @@ impl Nl80211Handle { pub(crate) async fn nl80211_execute( handle: &mut Nl80211Handle, nl80211_msg: Nl80211Message, + header_flags: u16, ) -> impl TryStream, Error = Nl80211Error> { - let nl_header_flags = NLM_F_REQUEST | NLM_F_DUMP; - let mut nl_msg = NetlinkMessage::from(GenlMessage::from_payload(nl80211_msg)); - nl_msg.header.flags = nl_header_flags; + nl_msg.header.flags = header_flags; match handle.request(nl_msg).await { Ok(response) => { diff --git a/src/iface/get.rs b/src/iface/get.rs index 3b97c6f..0d971d2 100644 --- a/src/iface/get.rs +++ b/src/iface/get.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT use futures::TryStream; +use netlink_packet_core::{NLM_F_DUMP, NLM_F_REQUEST}; use netlink_packet_generic::GenlMessage; use crate::{ @@ -27,6 +28,8 @@ impl Nl80211InterfaceGetRequest { cmd: Nl80211Command::GetInterface, attributes: vec![], }; - nl80211_execute(&mut handle, nl80211_msg).await + let flags = NLM_F_REQUEST | NLM_F_DUMP; + + nl80211_execute(&mut handle, nl80211_msg, flags).await } } diff --git a/src/lib.rs b/src/lib.rs index 245c5f5..ebdcdb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT mod attr; +mod builder; mod channel; mod command; mod connection; @@ -26,6 +27,7 @@ mod wiphy; pub(crate) mod bytes; pub use self::attr::Nl80211Attr; +pub use self::builder::Nl80211AttrsBuilder; pub use self::channel::Nl80211ChannelWidth; pub use self::command::Nl80211Command; #[cfg(feature = "tokio_socket")] @@ -47,12 +49,16 @@ pub use self::iface::{ pub use self::message::Nl80211Message; pub use self::mlo::Nl80211MloLink; pub use self::scan::{ - Nl80211BssCapabilities, Nl80211BssInfo, Nl80211BssUseFor, - Nl80211ScanGetRequest, Nl80211ScanHandle, + Nl80211BssCapabilities, Nl80211BssInfo, Nl80211BssUseFor, Nl80211Scan, + Nl80211ScanFlags, Nl80211ScanGetRequest, Nl80211ScanHandle, + Nl80211ScanScheduleRequest, Nl80211ScanScheduleStopRequest, + Nl80211ScanTriggerRequest, Nl80211SchedScanMatch, Nl80211SchedScanPlan, }; pub use self::station::{ - Nl80211RateInfo, Nl80211StationGetRequest, Nl80211StationHandle, - Nl80211StationInfo, + Nl80211EhtGi, Nl80211EhtRuAllocation, Nl80211HeGi, Nl80211HeRuAllocation, + Nl80211MeshPowerMode, Nl80211PeerLinkState, Nl80211RateInfo, + Nl80211StationBssParam, Nl80211StationFlag, Nl80211StationFlagUpdate, + Nl80211StationGetRequest, Nl80211StationHandle, Nl80211StationInfo, }; pub use self::stats::{ NestedNl80211TidStats, Nl80211TidStats, Nl80211TransmitQueueStat, diff --git a/src/scan/attr.rs b/src/scan/attr.rs new file mode 100644 index 0000000..4b2a018 --- /dev/null +++ b/src/scan/attr.rs @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: MIT + +use anyhow::Context; + +use netlink_packet_utils::{ + nla::{Nla, NlasIterator}, + parsers::{parse_string, parse_u32}, + DecodeError, Emitable, Parseable, +}; + +use crate::bytes::write_u32; +#[cfg(doc)] +use crate::Nl80211Attr; + +#[derive(Debug, Clone)] +pub(crate) struct Nla80211ScanSsidNla { + index: u16, + ssid: String, +} + +impl Nla for Nla80211ScanSsidNla { + fn value_len(&self) -> usize { + self.ssid.len() + } + + fn emit_value(&self, buffer: &mut [u8]) { + buffer.copy_from_slice(self.ssid.as_bytes()) + } + + fn kind(&self) -> u16 { + // Linux kernel has no check on this value, but iw `scan.c` + // index start from 1. + self.index + 1 + } +} + +pub(crate) struct Nla80211ScanSsidNlas(Vec); + +impl std::ops::Deref for Nla80211ScanSsidNlas { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +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 for Vec { + fn from(ssids: Nla80211ScanSsidNlas) -> Self { + let mut ssids = ssids; + ssids.0.drain(..).map(|c| c.ssid).collect() + } +} + +impl Nla80211ScanSsidNlas { + pub(crate) fn parse(payload: &[u8]) -> Result { + let mut ssids: Vec = Vec::new(); + 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()) + .context(format!("Invalid NL80211_ATTR_SCAN_SSIDS: {nla:?}"))?; + ssids.push(Nla80211ScanSsidNla { + index: index as u16, + ssid, + }); + } + Ok(Self(ssids)) + } +} + +bitflags::bitflags! { + /// Scan request control flags + // Kernel data type: enum nl80211_scan_flags + #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] + #[non_exhaustive] + pub struct Nl80211ScanFlags: u32 { + /// Scan request has low priority + const LowPriority = 1 << 0; + /// Flush cache before scanning + const Flush = 1 << 1; + /// Force a scan even if the interface is configured as AP and the + /// beaconing has already been configured. This attribute is dangerous + /// because will destroy stations performance as a lot of frames will be + /// lost while scanning off-channel, therefore it must be used only when + /// really needed + const Ap = 1 << 2; + /// Use a random MAC address for this scan (or for scheduled scan: a + /// different one for every scan iteration). When the flag is set, + /// depending on device capabilities the [Nl80211Attr::Mac] and + /// [Nl80211Attr::MacMask] attributes may also be given in which case + /// only the masked bits will be preserved from the MAC address and the + /// remainder randomised. If the attributes are not given full + /// randomisation (46 bits, locally administered 1, multicast 0) is + /// assumed. This flag must not be requested when the feature isn't + /// supported, check the [Nl80211Attr::Features] for the device. + const RandomAddr = 1 << 3; + /// Fill the dwell time in the FILS request parameters IE in the probe + /// request + const FilsMaxChannelTime = 1 << 4; + /// Accept broadcast probe responses + const AcceptBcastProbeResp = 1 << 5; + /// Send probe request frames at rate of at least 5.5M. In case non-OCE + /// AP is discovered in the channel, only the first probe req in the + /// channel will be sent in high rate. + const OceProbeReqHighTxRate = 1 << 6; + /// Allow probe request tx deferral (dot11FILSProbeDelay shall be set to + /// 15ms) and suppression (if it has received a broadcast Probe Response + /// frame, Beacon frame or FILS Discovery frame from an AP that the STA + /// considers a suitable candidate for (re-)association - suitable in + /// terms of SSID and/or RSSI. + const OceProbeReqDeferralSuppression = 1 << 7; + /// Span corresponds to the total time taken to accomplish the scan. + /// Thus, this flag intends the driver to perform the scan request with + /// lesser span/duration. It is specific to the driver implementations + /// on how this is accomplished. Scan accuracy may get impacted with + /// this flag. + const LowSpan = 1 << 8; + /// This flag intends the scan attempts to consume optimal possible + /// power. Drivers can resort to their specific means to optimize the + /// power. Scan accuracy may get impacted with this flag. + const LowPower = 1 << 9; + /// Accuracy here intends to the extent of scan results obtained. Thus + /// HIGH_ACCURACY scan flag aims to get maximum possible scan results. + /// This flag hints the driver to use the best possible scan + /// configuration to improve the accuracy in scanning. Latency and + /// power use may get impacted with this flag. + const HighAccuracy = 1 << 10; + /// Randomize the sequence number in probe request frames from this + /// scan to avoid correlation/tracking being possible. + const RandomSn = 1 << 11; + /// Minimize probe request content to only have supported rates and no + /// additional capabilities (unless added by userspace explicitly.) + const MinPreqContent = 1 << 12; + /// Report scan results with [Nl80211Attr::ScanFreqKhz]. + /// This also means [Nl80211Attr::ScanFreq] will not be included. + const FreqKhz = 1 << 13; + /// Scan for collocated APs reported by 2.4/5 GHz APs. When the flag is + /// set, the scan logic will use the information from the RNR element + /// found in beacons/probe responses received on the 2.4/5 GHz channels + /// to actively scan only the 6GHz channels on which APs are expected to + /// be found. Note that when not set, the scan logic would scan all 6GHz + /// channels, but since transmission of probe requests on non-PSC + /// channels is limited, it is highly likely that these channels would + /// passively be scanned. Also note that when the flag is set, in + /// addition to the colocated APs, PSC channels would also be scanned if + /// the user space has asked for it. + const Colocated6Ghz = 1 << 14; + const _ = !0; + } +} + +impl + ?Sized> Parseable for Nl80211ScanFlags { + fn parse(buf: &T) -> Result { + let buf: &[u8] = buf.as_ref(); + Ok(Self::from_bits_retain(parse_u32(buf).context(format!( + "Invalid Nl80211ScanFlags payload {buf:?}" + ))?)) + } +} + +impl Nl80211ScanFlags { + pub const LENGTH: usize = 4; +} + +impl Emitable for Nl80211ScanFlags { + fn buffer_len(&self) -> usize { + Self::LENGTH + } + + fn emit(&self, buffer: &mut [u8]) { + buffer.copy_from_slice(&self.bits().to_ne_bytes()) + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct Nla80211ScanFreqNla { + index: u16, + freq: u32, +} + +impl Nla for Nla80211ScanFreqNla { + fn value_len(&self) -> usize { + 4 + } + + fn emit_value(&self, buffer: &mut [u8]) { + write_u32(buffer, self.freq) + } + + fn kind(&self) -> u16 { + self.index + } +} + +pub(crate) struct Nla80211ScanFreqNlas(Vec); + +impl std::ops::Deref for Nla80211ScanFreqNlas { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From<&Vec> for Nla80211ScanFreqNlas { + fn from(freqs: &Vec) -> Self { + let mut nlas = Vec::new(); + for (i, freq) in freqs.iter().enumerate() { + let nla = Nla80211ScanFreqNla { + index: i as u16, + freq: *freq, + }; + nlas.push(nla); + } + Nla80211ScanFreqNlas(nlas) + } +} + +impl From for Vec { + fn from(freqs: Nla80211ScanFreqNlas) -> Self { + let mut freqs = freqs; + freqs.0.drain(..).map(|c| c.freq).collect() + } +} + +impl Nla80211ScanFreqNlas { + pub(crate) fn parse(payload: &[u8]) -> Result { + let mut freqs: Vec = Vec::new(); + for (index, nla) in NlasIterator::new(payload).enumerate() { + let error_msg = + format!("Invalid NL80211_ATTR_SCAN_FREQUENCIES: {nla:?}"); + let nla = &nla.context(error_msg.clone())?; + let freq = parse_u32(nla.value()).context(format!( + "Invalid NL80211_ATTR_SCAN_FREQUENCIES: {nla:?}" + ))?; + freqs.push(Nla80211ScanFreqNla { + index: index as u16, + freq, + }); + } + Ok(Self(freqs)) + } +} diff --git a/src/scan/get.rs b/src/scan/get.rs index 1bad6c9..ffe685a 100644 --- a/src/scan/get.rs +++ b/src/scan/get.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT use futures::TryStream; +use netlink_packet_core::{NLM_F_DUMP, NLM_F_REQUEST}; use netlink_packet_generic::GenlMessage; use crate::{ @@ -33,6 +34,8 @@ impl Nl80211ScanGetRequest { attributes, }; - nl80211_execute(&mut handle, nl80211_msg).await + let flags = NLM_F_REQUEST | NLM_F_DUMP; + + nl80211_execute(&mut handle, nl80211_msg, flags).await } } diff --git a/src/scan/handle.rs b/src/scan/handle.rs index 2177a03..0df04bd 100644 --- a/src/scan/handle.rs +++ b/src/scan/handle.rs @@ -1,7 +1,15 @@ // SPDX-License-Identifier: MIT -use crate::{Nl80211Handle, Nl80211ScanGetRequest}; +use netlink_packet_utils::nla::Nla; +use crate::{ + Nl80211Attr, Nl80211AttrsBuilder, Nl80211Handle, Nl80211ScanFlags, + Nl80211ScanGetRequest, Nl80211ScanScheduleRequest, + Nl80211ScanScheduleStopRequest, Nl80211ScanTriggerRequest, + Nl80211SchedScanMatch, Nl80211SchedScanPlan, +}; + +#[derive(Debug, Clone)] pub struct Nl80211ScanHandle(Nl80211Handle); impl Nl80211ScanHandle { @@ -14,4 +22,120 @@ impl Nl80211ScanHandle { pub fn dump(&mut self, if_index: u32) -> Nl80211ScanGetRequest { Nl80211ScanGetRequest::new(self.0.clone(), if_index) } + + /// Trigger a scan (equivalent to `iw dev DEVICE scan trigger`) + /// The return of this function only means the scan trigger request + /// sent, it does not mean the scan is finished. + /// The `attributes: Vec` could be generated by + /// [Nl80211Scan]. For example: + /// ```no_run + #[doc = include_str!("../../examples/nl80211_trigger_scan.rs")] + /// ``` + pub fn trigger( + &mut self, + attributes: Vec, + ) -> Nl80211ScanTriggerRequest { + Nl80211ScanTriggerRequest::new(self.0.clone(), attributes) + } + + /// Start a scan schedule (equivalent to `iw dev DEVICE scan sched_start`) + pub fn schedule_start( + &mut self, + attributes: Vec, + ) -> Nl80211ScanScheduleRequest { + Nl80211ScanScheduleRequest::new(self.0.clone(), attributes) + } + + /// Stop all scan schedule (equivalent to `iw dev DEVICE scan sched_stop`) + pub fn schedule_stop_all(&mut self) -> Nl80211ScanScheduleStopRequest { + Nl80211ScanScheduleStopRequest::new(self.0.clone(), Vec::new()) + } +} + +#[derive(Debug)] +pub struct Nl80211Scan; + +impl Nl80211Scan { + /// Perform active scan on specified interface + pub fn new(if_index: u32) -> Nl80211AttrsBuilder { + Nl80211AttrsBuilder::::new() + .if_index(if_index) + .ssids(vec!["".to_string()]) + } +} + +impl Nl80211AttrsBuilder { + /// SSIDs to send probe request during active scan. + /// `vec!["".to_string()]` means wildcard. + pub fn ssids(self, ssids: Vec) -> Self { + self.replace(Nl80211Attr::ScanSsids(ssids)) + } + + pub fn scan_flags(self, flags: Nl80211ScanFlags) -> Self { + self.replace(Nl80211Attr::ScanFlags(flags)) + } + + /// Enable passive scan or active scan. + /// During an active scan, the client radio transmits a probe request and + /// listens for a probe response from an AP. With a passive scan, the client + /// radio listens on each channel for beacons sent periodically by an AP. + /// This method will override existing ssids defined before: + /// * Enable passive scan will remove all SSIDs + /// * Disable passive scan will replace existing SSIDs with + /// `vec!["".to_string()]` + pub fn passive(self, value: bool) -> Self { + if value { + self.remove(Nl80211Attr::ScanSsids(Vec::new()).kind()) + } else { + self.replace(Nl80211Attr::ScanSsids(vec!["".to_string()])) + } + } + + /// Duration in unit of TU(1024 microseconds(µs) + pub fn duration(self, value: u16) -> Self { + self.replace(Nl80211Attr::MeasurementDuration(value)) + } + + /// Scan interval in millisecond(ms), only available for schedule scan + pub fn interval(self, value: u32) -> Self { + self.replace(Nl80211Attr::SchedScanInterval(value)) + } + + /// Delay before the first cycle of a scheduled scan is started. Or the + /// delay before a WoWLAN net-detect scan is started, counting from the + /// moment the system is suspended. This value is in seconds. + pub fn delay(self, value: u32) -> Self { + self.replace(Nl80211Attr::SchedScanDelay(value)) + } + + /// Scan frequencies in MHz. + pub fn scan_frequncies(self, freqs: Vec) -> Self { + self.replace(Nl80211Attr::ScanFrequencies(freqs)) + } + + /// Sets of attributes to match during scheduled scans. Only BSSs + /// that match any of the sets will be reported. These are pass-thru + /// filter rules. For a match to succeed, the BSS must match all + /// attributes of a set. Since not every hardware supports matching all + /// types of attributes, there is no guarantee that the reported BSSs are + /// fully complying with the match sets and userspace needs to be able to + /// ignore them by itself. Thus, the implementation is somewhat + /// hardware-dependent, but this is only an optimization and the userspace + /// application needs to handle all the non-filtered results anyway. + /// If omitted, no filtering is done. + pub fn schedule_scan_match( + self, + matches: Vec, + ) -> Self { + self.replace(Nl80211Attr::SchedScanMatch(matches)) + } + + /// A list of scan plans for scheduled scan. Each scan plan defines the + /// number of scan iterations and the interval between scans. The last scan + /// plan will always run infinitely, thus it must not specify the number of + /// iterations, only the interval between scans. The scan plans are + /// executed sequentially. + pub fn schedule_scan_plan(self, plans: Vec) -> Self { + self.replace(Nl80211Attr::SchedScanPlans(plans)) + } } diff --git a/src/scan/mod.rs b/src/scan/mod.rs index ca4a044..7b5bf82 100644 --- a/src/scan/mod.rs +++ b/src/scan/mod.rs @@ -1,11 +1,22 @@ // SPDX-License-Identifier: MIT +mod attr; mod bss_info; mod get; mod handle; +mod schedule; +mod trigger; +pub use self::attr::Nl80211ScanFlags; pub use self::bss_info::{ Nl80211BssCapabilities, Nl80211BssInfo, Nl80211BssUseFor, }; pub use self::get::Nl80211ScanGetRequest; -pub use self::handle::Nl80211ScanHandle; +pub use self::handle::{Nl80211Scan, Nl80211ScanHandle}; +pub use self::schedule::{ + Nl80211ScanScheduleRequest, Nl80211ScanScheduleStopRequest, + Nl80211SchedScanMatch, Nl80211SchedScanPlan, +}; +pub use self::trigger::Nl80211ScanTriggerRequest; + +pub(crate) use self::attr::{Nla80211ScanFreqNlas, Nla80211ScanSsidNlas}; diff --git a/src/scan/schedule.rs b/src/scan/schedule.rs new file mode 100644 index 0000000..7095b5e --- /dev/null +++ b/src/scan/schedule.rs @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: MIT + +use anyhow::Context; +use futures::TryStream; +use netlink_packet_core::{NLM_F_ACK, NLM_F_REQUEST}; +use netlink_packet_generic::GenlMessage; +use netlink_packet_utils::{ + nla::{DefaultNla, Nla, NlaBuffer}, + parsers::{parse_i32, parse_string, parse_u32}, + DecodeError, Emitable, Parseable, +}; + +use crate::{ + bytes::{write_i32, write_u32}, + nl80211_execute, Nl80211Attr, Nl80211Command, Nl80211Error, Nl80211Handle, + Nl80211Message, +}; + +#[derive(Debug, Clone)] +pub struct Nl80211ScanScheduleRequest { + handle: Nl80211Handle, + attributes: Vec, +} + +impl Nl80211ScanScheduleRequest { + pub(crate) fn new( + handle: Nl80211Handle, + attributes: Vec, + ) -> Self { + Self { handle, attributes } + } + + pub async fn execute( + self, + ) -> impl TryStream, Error = Nl80211Error> + { + let Self { + mut handle, + attributes, + } = self; + + let nl80211_msg = Nl80211Message { + cmd: Nl80211Command::StartSchedScan, + attributes, + }; + let flags = NLM_F_REQUEST | NLM_F_ACK; + + nl80211_execute(&mut handle, nl80211_msg, flags).await + } +} + +#[derive(Debug, Clone)] +pub struct Nl80211ScanScheduleStopRequest { + handle: Nl80211Handle, + attributes: Vec, +} + +impl Nl80211ScanScheduleStopRequest { + pub(crate) fn new( + handle: Nl80211Handle, + attributes: Vec, + ) -> Self { + Self { handle, attributes } + } + + pub async fn execute( + self, + ) -> impl TryStream, Error = Nl80211Error> + { + let Self { + mut handle, + attributes, + } = self; + + let nl80211_msg = Nl80211Message { + cmd: Nl80211Command::StopSchedScan, + attributes, + }; + let flags = NLM_F_REQUEST | NLM_F_ACK; + + nl80211_execute(&mut handle, nl80211_msg, flags).await + } +} + +const ETH_ALEN: usize = 6; + +const NL80211_SCHED_SCAN_MATCH_ATTR_SSID: u16 = 1; +const NL80211_SCHED_SCAN_MATCH_ATTR_RSSI: u16 = 2; +// Linux kernel 6.11 has no code parsing these two values, only documented +// const NL80211_SCHED_SCAN_MATCH_ATTR_RELATIVE_RSSI: u16 = 3; +// const NL80211_SCHED_SCAN_MATCH_ATTR_RSSI_ADJUST: u16 = 4; +const NL80211_SCHED_SCAN_MATCH_ATTR_BSSID: u16 = 5; +// Linux kernel has this one marked as obsolete +// const NL80211_SCHED_SCAN_MATCH_PER_BAND_RSSI: u16 = 6; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Nl80211SchedScanMatch { + /// SSID to be used for matching. Cannot use with + /// [Nl80211SchedScanMatch::Bssid]. + Ssid(String), + /// RSSI threshold (in dBm) for reporting a BSS in scan results. Filtering + /// is turned off if not specified. Note that if this attribute is in a + /// match set of its own, then it is treated as the default value for all + /// matchsets with an SSID, rather than being a matchset of its own without + /// an RSSI filter. This is due to problems with how this API was + /// implemented in the past. Also, due to the same problem, the only way to + /// create a matchset with only an RSSI filter (with this attribute) is if + /// there's only a single matchset with the RSSI attribute. + Rssi(i32), + /// BSSID to be used for matching. Cannot use with + /// [Nl80211SchedScanMatch::Ssid]. + Bssid([u8; ETH_ALEN]), + Other(DefaultNla), +} + +impl Nla for Nl80211SchedScanMatch { + fn value_len(&self) -> usize { + match self { + Self::Ssid(v) => v.len(), + Self::Bssid(_) => ETH_ALEN, + Self::Rssi(_) => 4, + Self::Other(v) => v.value_len(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Ssid(v) => buffer.copy_from_slice(v.as_bytes()), + Self::Bssid(v) => buffer.copy_from_slice(v), + Self::Rssi(d) => write_i32(buffer, *d), + Self::Other(attr) => attr.emit(buffer), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Ssid(_) => NL80211_SCHED_SCAN_MATCH_ATTR_SSID, + Self::Bssid(_) => NL80211_SCHED_SCAN_MATCH_ATTR_BSSID, + Self::Rssi(_) => NL80211_SCHED_SCAN_MATCH_ATTR_RSSI, + Self::Other(attr) => attr.kind(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for Nl80211SchedScanMatch +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + NL80211_SCHED_SCAN_MATCH_ATTR_SSID => { + let err_msg = format!( + "Invalid NL80211_SCHED_SCAN_MATCH_ATTR_SSID value {:?}", + payload + ); + Self::Ssid(parse_string(payload).context(err_msg)?) + } + NL80211_SCHED_SCAN_MATCH_ATTR_RSSI => { + let err_msg = format!( + "Invalid NL80211_SCHED_SCAN_MATCH_ATTR_RSSI value {:?}", + payload + ); + Self::Rssi(parse_i32(payload).context(err_msg)?) + } + NL80211_SCHED_SCAN_MATCH_ATTR_BSSID => { + if payload.len() < ETH_ALEN { + return Err(format!( + "Invalid NL80211_SCHED_SCAN_MATCH_ATTR_BSSID \ + {payload:?}" + ) + .into()); + } + let mut bssid = [0u8; ETH_ALEN]; + bssid.copy_from_slice(&payload[..ETH_ALEN]); + Self::Bssid(bssid) + } + _ => Self::Other( + DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?, + ), + }) + } +} + +const NL80211_SCHED_SCAN_PLAN_INTERVAL: u16 = 1; +const NL80211_SCHED_SCAN_PLAN_ITERATIONS: u16 = 2; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Nl80211SchedScanPlan { + /// Interval between scan iterations in seconds. + Interval(u32), + /// Number of scan iterations in this scan plan. The last scan plan + /// must not specify this attribute because it will run infinitely. A value + /// of zero is invalid as it will make the scan plan meaningless. + Iterations(u32), + Other(DefaultNla), +} + +impl Nla for Nl80211SchedScanPlan { + fn value_len(&self) -> usize { + match self { + Self::Interval(_) => 4, + Self::Iterations(_) => 4, + Self::Other(v) => v.value_len(), + } + } + + fn emit_value(&self, buffer: &mut [u8]) { + match self { + Self::Interval(d) | Self::Iterations(d) => write_u32(buffer, *d), + Self::Other(attr) => attr.emit(buffer), + } + } + + fn kind(&self) -> u16 { + match self { + Self::Interval(_) => NL80211_SCHED_SCAN_PLAN_INTERVAL, + Self::Iterations(_) => NL80211_SCHED_SCAN_PLAN_ITERATIONS, + Self::Other(attr) => attr.kind(), + } + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Parseable> + for Nl80211SchedScanPlan +{ + fn parse(buf: &NlaBuffer<&'a T>) -> Result { + let payload = buf.value(); + Ok(match buf.kind() { + NL80211_SCHED_SCAN_PLAN_INTERVAL => { + let err_msg = format!( + "Invalid NL80211_SCHED_SCAN_PLAN_INTERVAL value {:?}", + payload + ); + Self::Interval(parse_u32(payload).context(err_msg)?) + } + NL80211_SCHED_SCAN_PLAN_ITERATIONS => { + let err_msg = format!( + "Invalid NL80211_SCHED_SCAN_PLAN_ITERATIONS value {:?}", + payload + ); + Self::Iterations(parse_u32(payload).context(err_msg)?) + } + _ => Self::Other( + DefaultNla::parse(buf).context("invalid NLA (unknown kind)")?, + ), + }) + } +} diff --git a/src/scan/trigger.rs b/src/scan/trigger.rs new file mode 100644 index 0000000..41dcf93 --- /dev/null +++ b/src/scan/trigger.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +use futures::TryStream; +use netlink_packet_core::{NLM_F_ACK, NLM_F_REQUEST}; +use netlink_packet_generic::GenlMessage; + +use crate::{ + nl80211_execute, Nl80211Attr, Nl80211Command, Nl80211Error, Nl80211Handle, + Nl80211Message, +}; + +pub struct Nl80211ScanTriggerRequest { + handle: Nl80211Handle, + attributes: Vec, +} + +impl Nl80211ScanTriggerRequest { + pub(crate) fn new( + handle: Nl80211Handle, + attributes: Vec, + ) -> Self { + Nl80211ScanTriggerRequest { handle, attributes } + } + + pub async fn execute( + self, + ) -> impl TryStream, Error = Nl80211Error> + { + let Nl80211ScanTriggerRequest { + mut handle, + attributes, + } = self; + + let nl80211_msg = Nl80211Message { + cmd: Nl80211Command::TriggerScan, + attributes, + }; + let flags = NLM_F_REQUEST | NLM_F_ACK; + + nl80211_execute(&mut handle, nl80211_msg, flags).await + } +} diff --git a/src/station/get.rs b/src/station/get.rs index a7d4935..045ad8d 100644 --- a/src/station/get.rs +++ b/src/station/get.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT use futures::TryStream; +use netlink_packet_core::{NLM_F_DUMP, NLM_F_REQUEST}; use netlink_packet_generic::GenlMessage; use crate::{ @@ -49,6 +50,8 @@ impl Nl80211StationGetRequest { attributes, }; - nl80211_execute(&mut handle, nl80211_msg).await + let flags = NLM_F_REQUEST | NLM_F_DUMP; + + nl80211_execute(&mut handle, nl80211_msg, flags).await } } diff --git a/src/station/mod.rs b/src/station/mod.rs index 0e855f2..b06fd70 100644 --- a/src/station/mod.rs +++ b/src/station/mod.rs @@ -5,7 +5,13 @@ mod handle; mod rate_info; mod station_info; -pub use get::Nl80211StationGetRequest; -pub use handle::Nl80211StationHandle; -pub use rate_info::Nl80211RateInfo; -pub use station_info::Nl80211StationInfo; +pub use self::get::Nl80211StationGetRequest; +pub use self::handle::Nl80211StationHandle; +pub use self::rate_info::{ + Nl80211EhtGi, Nl80211EhtRuAllocation, Nl80211HeGi, Nl80211HeRuAllocation, + Nl80211RateInfo, +}; +pub use self::station_info::{ + Nl80211MeshPowerMode, Nl80211PeerLinkState, Nl80211StationBssParam, + Nl80211StationFlag, Nl80211StationFlagUpdate, Nl80211StationInfo, +}; diff --git a/src/station/rate_info.rs b/src/station/rate_info.rs index 34ccf4e..ca0cd35 100644 --- a/src/station/rate_info.rs +++ b/src/station/rate_info.rs @@ -64,7 +64,7 @@ pub enum Nl80211RateInfo { /// HE DCM value HeDcm(u8), /// HE RU allocation, if not present then non-OFDMA was used. - /// See [`Nl80211HeRuAlloc`] + /// See [`Nl80211HeRuAllocation`] HeRuAlloc(Nl80211HeRuAllocation), /// S1G MCS index S1gMcs(u8), @@ -77,7 +77,7 @@ pub enum Nl80211RateInfo { /// EHT guard interval identifier [`Nl80211EhtGi`] EhtGi(Nl80211EhtGi), /// EHT RU allocation, if not present then non-OFDMA was used. - /// See [`Nl80211EhtRuAlloc`] + /// See [`Nl80211EhtRuAllocation`] EhtRuAlloc(Nl80211EhtRuAllocation), Other(DefaultNla), diff --git a/src/station/station_info.rs b/src/station/station_info.rs index 1b8f0c3..1407109 100644 --- a/src/station/station_info.rs +++ b/src/station/station_info.rs @@ -13,6 +13,8 @@ use netlink_packet_utils::{ use std::fmt::Debug; use crate::NestedNl80211TidStats; +#[cfg(doc)] +use crate::Nl80211Attr; use super::Nl80211RateInfo; @@ -61,7 +63,7 @@ const NL80211_STA_INFO_CONNECTED_TO_AS: u16 = 43; /// Station information /// -/// These attribute types are used with [`Nl80211Attr::Nl80211StationInfo`] +/// These attribute types are used with [`Nl80211Attr::StationInfo`] /// when getting information about a station. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Nl80211StationInfo { @@ -79,11 +81,10 @@ pub enum Nl80211StationInfo { Signal(i8), /// Signal strength average (u8, dBm) SignalAvg(i8), - /// Current unicast transmission rate, nested attribute containing info as + /// Current unicast transmission rate. /// possible, see [`Nl80211RateInfo`] TxBitrate(Vec), - /// Last unicast data frame receive rate, nested attribute, like - /// [`NL80211_STA_INFO_TX_BITRATE`]. + /// Last unicast data frame receive rate. RxBitrate(Vec), /// Total transmitted packets (MSDUs and MMPDUs) TxPackets(u32), @@ -107,8 +108,7 @@ pub enum Nl80211StationInfo { Plid(u16), /// Peer link state for the station (see [`Nl80211PeerLinkState`]) PeerLinkState(Nl80211PeerLinkState), - /// Current station's view of BSS, nested attribute containing info as - /// possible, see [`Nl80211StationBssParam`] + /// Current station's view of BSS, BssParam(Vec), /// Time since the station is last connected ConnectedTime(u32), @@ -122,7 +122,7 @@ pub enum Nl80211StationInfo { PeerPowerMode(Nl80211MeshPowerMode), /// Neighbor mesh station power save mode towards non-peer station NonPeerPowerMode(Nl80211MeshPowerMode), - /// Per-chain signal strength of last PPDU. Contains a nested array of + /// Per-chain signal strength of last PPDU. Contains a array of /// signal strength attributes (dBm) ChainSignal(Vec), /// Per-chain signal strength average. Same format as @@ -903,7 +903,7 @@ pub enum Nl80211StationFlag { /// silently be ignored (rather than rejected as errors.) TdlsPeer, /// station is associated; used with drivers that support - /// [`NL80211_FEATURE_FULL_AP_CLIENT_STATE`] to transition a previously + /// [crate::Nl80211Features::FullApClientState] to transition a previously /// added station into associated state Associated, // Reserved: 25 bits, diff --git a/src/wiphy/command.rs b/src/wiphy/command.rs index d78efb2..45c451e 100644 --- a/src/wiphy/command.rs +++ b/src/wiphy/command.rs @@ -62,7 +62,7 @@ impl From for Vec { } impl Nl80211Commands { - pub fn parse(payload: &[u8]) -> Result { + pub(crate) fn parse(payload: &[u8]) -> Result { let mut cmds: Vec = Vec::new(); for (index, nla) in NlasIterator::new(payload).enumerate() { let error_msg = diff --git a/src/wiphy/get.rs b/src/wiphy/get.rs index 9449f9d..5cea4d2 100644 --- a/src/wiphy/get.rs +++ b/src/wiphy/get.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT use futures::TryStream; +use netlink_packet_core::{NLM_F_DUMP, NLM_F_REQUEST}; use netlink_packet_generic::GenlMessage; use crate::{ @@ -27,6 +28,9 @@ impl Nl80211WiphyGetRequest { cmd: Nl80211Command::GetWiphy, attributes: vec![Nl80211Attr::SplitWiphyDump], }; - nl80211_execute(&mut handle, nl80211_msg).await + + let flags = NLM_F_REQUEST | NLM_F_DUMP; + + nl80211_execute(&mut handle, nl80211_msg, flags).await } }