Skip to main content

wifi_scan/
lib.rs

1// Copyright 2016 Mark Sta Ana, 2025 simon0302010.
2// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
3// http://www.apache.org/licenses/LICENSE-2.0>, at your option.
4// This file may not be copied, modified, or distributed except
5// according to those terms.
6
7// Inspired by Maurice Svay's node-wifiscanner (https://github.com/mauricesvay/node-wifiscanner)
8
9//! A crate to list WiFi hotspots in your area.
10//!
11//! As of v0.5.x macOS, Windows and Linux are supported.
12//! Use versions 0.6.* if you want a drop-in replacement for the original crate.
13//!
14//! # Usage
15//!
16//! This crate is on [crates.io](https://crates.io/crates/wifi_scan) and can be
17//! used by adding `wifi_scan` to the dependencies in your project's `Cargo.toml`.
18//!
19//! ```toml
20//! [dependencies]
21//! wifi_scan = "0.7.*"
22//! ```
23//!
24//! # Example
25//!
26//! ```no_run
27//! use wifi_scan;
28//! println!("{:?}", wifi_scan::scan());
29//! ```
30//!
31//! Alternatively if you've cloned the the Git repo, you can run the above example
32//! using: `cargo run --example scan`.
33
34mod misc;
35mod sys;
36
37use std::fmt;
38
39use crate::misc::yes_or_no;
40
41type Result<T> = std::result::Result<T, Error>;
42
43/// Erros for wifi_scan
44#[derive(Debug, PartialEq, Eq)]
45pub enum Error {
46    InterfaceError(String),
47    SocketError(String),
48    ScanFailed(String),
49}
50
51/// Enum of WiFi Securities wifi_scan can output.
52/// Not all implementations support all securities.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub enum WifiSecurity {
55    Open,
56    Wep,
57    Wpa2PersonalPsk,
58    Wpa3PersonalSae,
59    Wpa2EnterpriseEap,
60    Wpa2EnterpriseEap256,
61    Wpa3EnterpriseEap256,
62    Wpa3EnterpriseSuiteBEap256,
63    Wpa3EnterpriseEap,
64    Wpa2EnterpriseEapFt,
65    Wpa2PersonalPskFt,
66    Wpa2PersonalPsk256,
67    Wpa3PersonalSaeFt,
68    WpaPersonalPsk,
69    WpaEnterpriseEap,
70    TunneledDirectLinkSetup,
71    Unknown,
72    Other(String),
73}
74
75/// Wifi struct used to return information about wifi hotspots. Shows security on Linux since version 0.6.0.
76#[derive(Debug, PartialEq, Eq, Default, Clone)]
77pub struct Wifi {
78    /// MAC Address. May be empty on macOS.
79    pub mac: String,
80    /// Hotspot Name. May be empty on macOS.
81    pub ssid: String,
82    /// Channel the hotspot is on. Returns 0 if unknown.
83    pub channel: u32,
84    /// Wifi signal strength in dBm. Returns 0 if unknown.
85    pub signal_level: i32,
86    /// A list of all supported securities by the network
87    pub security: Vec<WifiSecurity>,
88}
89
90/// Human readable signal strength
91pub enum SignalStrength {
92    Unknown,
93    Weak,
94    Fair,
95    Good,
96    Excellent,
97}
98
99impl fmt::Display for Error {
100    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101        match self {
102            Error::SocketError(detail) => {
103                write!(f, "Error while creating socket: {}", detail)
104            }
105            Error::InterfaceError(detail) => {
106                write!(f, "Interface error: {}", detail)
107            }
108            Error::ScanFailed(detail) => {
109                write!(f, "Scan Failed: {}", detail)
110            }
111        }
112    }
113}
114
115impl fmt::Display for WifiSecurity {
116    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117        match self {
118            WifiSecurity::Open => write!(f, "Open"),
119            WifiSecurity::Other(sec) => write!(f, "{}", sec),
120            WifiSecurity::TunneledDirectLinkSetup => write!(f, "Tunneled Direct Link Setup"),
121            WifiSecurity::Unknown => write!(f, "Unknown"),
122            WifiSecurity::Wep => write!(f, "WEP"),
123            WifiSecurity::Wpa2EnterpriseEap => write!(f, "WPA2-Enterprise (EAP)"),
124            WifiSecurity::Wpa2EnterpriseEapFt => write!(f, "WPA2-Enterprise (EAP-FT)"),
125            WifiSecurity::Wpa2PersonalPsk => write!(f, "WPA2-Personal (PSK)"),
126            WifiSecurity::Wpa2PersonalPskFt => write!(f, "WPA2-Personal (PSK-FT)"),
127            WifiSecurity::Wpa3EnterpriseEap256 => write!(f, "WPA3-Enterprise (EAP-256)"),
128            WifiSecurity::Wpa3EnterpriseSuiteBEap256 => {
129                write!(f, "WPA3-Enterprise (Suite B EAP-256)")
130            }
131            WifiSecurity::Wpa2PersonalPsk256 => write!(f, "WPA2-Personal (PSK-256)"),
132            WifiSecurity::Wpa3PersonalSae => write!(f, "WPA3-Personal (SAE)"),
133            WifiSecurity::Wpa3PersonalSaeFt => write!(f, "WPA3-Personal (SAE-FT)"),
134            WifiSecurity::WpaEnterpriseEap => write!(f, "WPA-Enterprise"),
135            WifiSecurity::WpaPersonalPsk => write!(f, "WPA-Personal"),
136            WifiSecurity::Wpa2EnterpriseEap256 => write!(f, "WPA2-Enterprise (EPA-256)"),
137            WifiSecurity::Wpa3EnterpriseEap => write!(f, "WPA3-Enterprise (EAP)"),
138        }
139    }
140}
141
142impl fmt::Display for Wifi {
143    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144        write!(
145            f,
146            "BSSID: {} | SSID: {} | Channel: {} | RSSI: {} dBm | Security: {} | Hidden: {}",
147            self.mac,
148            self.ssid,
149            self.channel,
150            self.signal_level,
151            self.security
152                .iter()
153                .map(|s| s.to_string())
154                .collect::<Vec<_>>()
155                .join(", "),
156            yes_or_no(self.is_hidden())
157        )
158    }
159}
160
161impl Wifi {
162    /// Returns `true` if the network is open
163    pub fn is_open(&self) -> bool {
164        self.security.len() == 1 && self.security[0] == WifiSecurity::Open
165    }
166
167    /// Returns `true` if the network supports WPA3
168    pub fn is_wpa3(&self) -> bool {
169        self.security.iter().any(|s| {
170            matches!(
171                s,
172                WifiSecurity::Wpa3EnterpriseEap256
173                    | WifiSecurity::Wpa3EnterpriseSuiteBEap256
174                    | WifiSecurity::Wpa3PersonalSae
175                    | WifiSecurity::Wpa3PersonalSaeFt
176                    | WifiSecurity::Wpa3EnterpriseEap
177            )
178        })
179    }
180
181    /// Returns `true` if the network supports WPA2
182    pub fn is_wpa2(&self) -> bool {
183        self.security.iter().any(|s| {
184            matches!(
185                s,
186                WifiSecurity::Wpa2EnterpriseEap
187                    | WifiSecurity::Wpa2EnterpriseEapFt
188                    | WifiSecurity::Wpa2PersonalPsk
189                    | WifiSecurity::Wpa2PersonalPskFt
190                    | WifiSecurity::Wpa2PersonalPsk256
191                    | WifiSecurity::Wpa2EnterpriseEap256
192            )
193        })
194    }
195
196    /// Returns `true` if the network is an enterprise network
197    pub fn is_enterprise(&self) -> bool {
198        self.security.iter().any(|s| {
199            matches!(
200                s,
201                WifiSecurity::WpaEnterpriseEap
202                    | WifiSecurity::Wpa2EnterpriseEap
203                    | WifiSecurity::Wpa2EnterpriseEapFt
204                    | WifiSecurity::Wpa3EnterpriseEap256
205                    | WifiSecurity::Wpa3EnterpriseSuiteBEap256
206                    | WifiSecurity::Wpa3EnterpriseEap
207                    | WifiSecurity::Wpa2EnterpriseEap256
208            )
209        })
210    }
211
212    /// Returns `true` if the wifi is a personal network
213    pub fn is_personal(&self) -> bool {
214        self.security.iter().any(|s| {
215            matches!(
216                s,
217                WifiSecurity::WpaPersonalPsk
218                    | WifiSecurity::Wpa2PersonalPsk
219                    | WifiSecurity::Wpa2PersonalPskFt
220                    | WifiSecurity::Wpa3PersonalSae
221                    | WifiSecurity::Wpa3PersonalSaeFt
222                    | WifiSecurity::Wpa2PersonalPsk256
223            )
224        })
225    }
226
227    /// Returns signal strength as a categorial value
228    pub fn readable_signal(&self) -> SignalStrength {
229        match self.signal_level {
230            0 => SignalStrength::Unknown,
231            -50..=0 => SignalStrength::Excellent,
232            -70..=-61 => SignalStrength::Good,
233            -80..=-71 => SignalStrength::Fair,
234            _ => SignalStrength::Weak,
235        }
236    }
237
238    /// Returns `true` if the network is hidden
239    pub fn is_hidden(&self) -> bool {
240        self.ssid.is_empty()
241    }
242
243    /// Returns WiFi frequency in MHz
244    pub fn get_frequency(&self) -> u32 {
245        match self.channel {
246            1..=13 => 2407 + self.channel * 5,          // 2.4 GHz
247            14 => 2484,                                 // 2.4 GHz (Japan)
248            36..=165 => 5000 + self.channel * 5,        // 5 GHz
249            167..=233 => 5950 + (self.channel - 1) * 5, // 6 GHz
250            _ => 0,                                     // Invalid
251        }
252    }
253}
254
255impl std::error::Error for Error {}
256
257pub trait WlanScanner {
258    fn scan(&mut self) -> Result<Vec<Wifi>>;
259}
260
261/// Returns a list of WiFi hotspots in your area.
262/// Uses `corewlan` on macOS and `win32-wlan` on Windows.
263/// `nl80211-rs` and `netlink-rust` crates are being used on machines running Linux.
264///
265/// Example:
266///
267/// ```rust,no_run
268/// use wifi_scan;
269/// println!("{:?}", wifi_scan::scan());
270/// ```
271pub fn scan() -> Result<Vec<Wifi>> {
272    #[cfg(target_os = "macos")]
273    let mut scanner = sys::macos::ScanMac;
274
275    #[cfg(target_os = "linux")]
276    let mut scanner = sys::linux::ScanLinux;
277
278    #[cfg(target_os = "windows")]
279    let mut scanner = sys::windows::ScanWindows;
280
281    #[cfg(target_os = "openbsd")]
282    let mut scanner = sys::openbsd::ScanOpenBsd;
283
284    #[cfg(target_os = "freebsd")]
285    let mut scanner = sys::freebsd::ScanFreeBsd;
286
287    #[cfg(target_os = "netbsd")]
288    let mut scanner = sys::netbsd::ScanNetBsd;
289
290    #[cfg(not(any(
291        target_os = "macos",
292        target_os = "linux",
293        target_os = "windows",
294        target_os = "openbsd",
295        target_os = "freebsd",
296        target_os = "netbsd"
297    )))]
298    compile_error!("wifi_scan does not support this platform");
299
300    scanner.scan()
301}