#![cfg(unix)]
#![warn(missing_docs)]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
mod utils;
const BUF_SIZE: usize = 512;
const DEFAULT_ROOT: &str = "/var/run/wpa_supplicant/";
#[cfg(not(unix))]
compile_error!("Supports only unix targets");
#[cfg(target_os = "android")]
const LOCAL_SOCKET_DIR: &str = "/data/misc/wifi/sockets";
#[cfg(not(target_os = "android"))]
const LOCAL_SOCKET_DIR: &str = "/tmp";
const LOCAL_SOCKET_PREFIX: &str = "wpa_crtl_";
const UNSOLICITED_PREFIX: char = '<';
type LocalSocketName = str_buf::StrBuf<23>;
use std::os::unix::net::UnixDatagram;
use std::{fs, io, path, net};
use core::{str, time};
use core::sync::atomic::{AtomicU32, Ordering};
use core::fmt::{self, Write};
pub struct QuotedValue<T: fmt::Display>(pub T);
impl<T: fmt::Display> fmt::Display for QuotedValue<T> {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_fmt(format_args!("\"{}\"", self.0))
}
}
fn local_socket_name() -> LocalSocketName {
static COUNTER: AtomicU32 = AtomicU32::new(1);
let mut name = LocalSocketName::new();
let _ = write!(&mut name, "{}{}", LOCAL_SOCKET_PREFIX, COUNTER.fetch_add(1, Ordering::SeqCst));
name
}
#[derive(Copy, Clone, Debug)]
pub struct WpaControllerBuilder<'a> {
pub root: &'a str,
pub read_timeout: Option<time::Duration>,
}
impl WpaControllerBuilder<'static> {
pub const fn new() -> Self {
Self {
root: DEFAULT_ROOT,
read_timeout: Some(time::Duration::from_secs(10)),
}
}
}
impl<'a> WpaControllerBuilder<'a> {
#[inline]
#[allow(clippy::needless_lifetimes)]
pub const fn set_root<'b>(self, new: &'b str) -> WpaControllerBuilder<'b> {
WpaControllerBuilder {
root: new,
read_timeout: self.read_timeout,
}
}
#[inline]
pub const fn set_read_timeout(mut self, read_timeout: Option<time::Duration>) -> Self {
self.read_timeout = read_timeout;
self
}
#[inline]
pub fn open(self, interface: &str) -> Result<WpaController, io::Error> {
let path = path::Path::new(self.root).join(interface);
WpaController::open_path(&path)
}
}
pub struct WpaControlReq {
buf: str_buf::StrBuf<127>,
}
impl WpaControlReq {
#[inline]
pub const fn raw(text: &str) -> Self {
Self {
buf: str_buf::StrBuf::from_str(text)
}
}
#[inline]
pub const fn ping() -> Self {
Self::raw("PING")
}
#[inline]
pub const fn status() -> Self {
Self::raw("STATUS")
}
#[inline]
pub const fn scan() -> Self {
Self::raw("SCAN")
}
#[inline]
pub const fn scan_results() -> Self {
Self::raw("SCAN_RESULTS")
}
#[inline]
pub const fn disconnect() -> Self {
Self::raw("DISCONNECT")
}
#[inline]
pub const fn reassociate() -> Self {
Self::raw("REASSOCIATE")
}
#[inline]
pub const fn list_networks() -> Self {
Self::raw("LIST_NETWORKS")
}
#[inline]
pub fn add_network() -> Self {
Self::raw("ADD_NETWORK")
}
#[inline]
pub fn get_network(id: Id, var: &str) -> Self {
let mut this = Self::raw("SET_NETWORK");
let _ = write!(&mut this.buf, " {}", id.0);
this.buf.push_str(" ");
this.buf.push_str(var);
this
}
#[inline]
pub fn set_network(id: Id, var: &str, value: impl fmt::Display) -> Self {
let mut this = Self::raw("SET_NETWORK");
let _ = write!(&mut this.buf, " {}", id.0);
this.buf.push_str(" ");
this.buf.push_str(var);
let _ = write!(&mut this.buf, " {}", value);
this
}
#[inline]
pub fn select_network(id: Id) -> Self {
let mut this = Self::raw("SELECT_NETWORK");
let _ = write!(&mut this.buf, " {}", id.0);
this
}
#[inline]
pub fn enable_network(id: Id) -> Self {
let mut this = Self::raw("ENABLE_NETWORK");
let _ = write!(&mut this.buf, " {}", id.0);
this
}
#[inline]
pub fn disable_network(id: Id) -> Self {
let mut this = Self::raw("DISABLE_NETWORK");
let _ = write!(&mut this.buf, " {}", id.0);
this
}
#[inline]
pub fn remove_network(id: Id) -> Self {
let mut this = Self::raw("REMOVE_NETWORK");
let _ = write!(&mut this.buf, " {}", id.0);
this
}
#[inline]
pub fn remove_network_all() -> Self {
Self::raw("REMOVE_NETWORK all")
}
#[inline]
pub fn save_config() -> Self {
Self::raw("SAVE_CONFIG")
}
}
impl fmt::Debug for WpaControlReq {
#[inline(always)]
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.buf, fmt)
}
}
#[derive(Copy, Clone, Debug)]
pub struct Success;
#[derive(Copy, Clone, Debug)]
pub struct Fail;
#[derive(Copy, Clone, Debug)]
pub struct Pong;
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Id(pub u32);
#[derive(Copy, Clone, Debug)]
pub enum WpaState {
Unknown,
Disconnected,
InterfaceDisabled,
Inactive,
Scanning,
Authenticating,
Associating,
Associated,
Handshake,
GroupHandshake,
Completed
}
impl WpaState {
pub fn from_str(text: &str) -> Self {
if text.eq_ignore_ascii_case("COMPLETED") {
Self::Completed
} else if text.eq_ignore_ascii_case("GROUP_HANDSHAKE") {
Self::GroupHandshake
} else if text.eq_ignore_ascii_case("4WAY_HANDSHAKE") {
Self::Handshake
} else if text.eq_ignore_ascii_case("ASSOCIATED") {
Self::Associated
} else if text.eq_ignore_ascii_case("ASSOCIATING") {
Self::Associating
} else if text.eq_ignore_ascii_case("AUTHENTICATING") {
Self::Authenticating
} else if text.eq_ignore_ascii_case("SCANNING") {
Self::Scanning
} else if text.eq_ignore_ascii_case("INACTIVE") {
Self::Inactive
} else if text.eq_ignore_ascii_case("INTERFACE_DISABLED") {
Self::InterfaceDisabled
} else if text.eq_ignore_ascii_case("DISCONNECTED") {
Self::Disconnected
} else {
Self::Unknown
}
}
}
#[derive(Clone, Debug)]
pub struct WpaStatus {
pub state: WpaState,
pub ip: Option<net::IpAddr>,
pub ssid: Option<String>
}
impl WpaStatus {
pub fn from_str(text: &str) -> Option<Self> {
let mut state = None;
let mut ip = None;
let mut ssid = None;
for line in text.lines() {
if line.is_empty() {
continue;
}
let mut split = line.splitn(2, '=');
let var = split.next().unwrap();
if let Some(value) = split.next() {
if var.eq_ignore_ascii_case("wpa_state") {
state = Some(WpaState::from_str(value));
} else if var.eq_ignore_ascii_case("ip_address") {
ip = value.parse().ok();
} else if var.eq_ignore_ascii_case("ssid") {
ssid = Some(value.to_owned());
} else {
continue;
}
} else {
return None;
}
}
state.map(|state| Self {
state,
ip,
ssid,
})
}
}
#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct WpaNetworkFlags {
pub current: bool,
pub disabled: bool,
pub p2p_persistent: bool
}
impl WpaNetworkFlags {
#[inline(always)]
pub fn from_str(mut text: &str) -> Self {
let mut result = WpaNetworkFlags {
current: false,
disabled: false,
p2p_persistent: false,
};
if !text.is_empty() {
while let Some(start_flag) = text.strip_prefix('[') {
if let Some(end) = start_flag.find(']') {
let flag = &start_flag[..end];
if flag.eq_ignore_ascii_case("CURRENT") {
result.current = true;
} else if flag.eq_ignore_ascii_case("DISABLED") {
result.disabled = true;
} else if flag.eq_ignore_ascii_case("P2P-PERSISTENT") {
result.p2p_persistent = true;
}
text = &start_flag[end..];
} else {
break;
}
}
}
result
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct WpaNetwork {
pub id: Id,
pub ssid: String,
pub bssid: String,
pub flags: WpaNetworkFlags,
}
pub struct WpaNetworkList<'a> {
lines: str::Lines<'a>,
}
impl<'a> Iterator for WpaNetworkList<'a> {
type Item = WpaNetwork;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let line = self.lines.next()?;
let mut parts = line.splitn(4, '\t');
let network = WpaNetwork {
id: Id(parts.next().unwrap().parse().ok()?),
ssid: parts.next()?.to_owned(),
bssid: parts.next()?.to_owned(),
flags: WpaNetworkFlags::from_str(parts.next().unwrap_or("")),
};
Some(network)
}
}
pub struct WpaControlMessage<'a> {
pub raw: &'a str,
}
impl<'a> WpaControlMessage<'a> {
#[inline(always)]
pub const fn is_unsolicited(&self) -> bool {
!self.raw.is_empty() && self.raw.as_bytes()[0] == UNSOLICITED_PREFIX as u8
}
pub fn as_pong(&self) -> Option<Pong> {
if self.raw.eq_ignore_ascii_case("pong") {
Some(Pong)
} else {
None
}
}
pub fn as_success(&self) -> Option<Success> {
if self.raw.eq_ignore_ascii_case("ok") {
Some(Success)
} else {
None
}
}
pub fn as_fail(&self) -> Option<Fail> {
if self.raw.eq_ignore_ascii_case("fail") {
Some(Fail)
} else {
None
}
}
pub fn as_status(&self) -> Option<WpaStatus> {
WpaStatus::from_str(self.raw)
}
pub fn as_network_id(&self) -> Option<Id> {
self.raw.parse().map(|id| Id(id)).ok()
}
pub fn as_network_list(&self) -> Option<WpaNetworkList<'_>> {
let mut lines = self.raw.lines();
let line = lines.next().unwrap();
let header = utils::split::<4>(line, '/')?;
if header[0] == "network id" && header[1] == "ssid" && header[2] == "bssid" && header[3] == "flags" {
Some(WpaNetworkList {
lines
})
} else {
None
}
}
}
impl<'a> fmt::Debug for WpaControlMessage<'a> {
#[inline(always)]
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.raw, fmt)
}
}
pub struct WpaController {
buffer: [u8; BUF_SIZE],
socket: UnixDatagram,
local: path::PathBuf,
}
impl WpaController {
#[inline(always)]
pub fn open<P: AsRef<path::Path>>(path: P) -> Result<Self, io::Error> {
Self::open_path(path.as_ref())
}
pub fn open_path(path: &path::Path) -> Result<Self, io::Error> {
let local_name = local_socket_name();
let local = path::Path::new(LOCAL_SOCKET_DIR).join(local_name.as_str());
let socket = UnixDatagram::bind(&local)?;
let this = Self {
buffer: [0; BUF_SIZE],
socket,
local,
};
this.socket.connect(path)?;
Ok(this)
}
#[inline]
pub fn request(&mut self, req: WpaControlReq) -> Result<usize, io::Error> {
println!("req={:?}", req);
self.socket.send(req.buf.as_bytes())
}
pub fn recv(&mut self) -> Result<Option<WpaControlMessage<'_>>, io::Error> {
loop {
match self.socket.recv(&mut self.buffer) {
Ok(len) => {
let msg = match std::str::from_utf8(&self.buffer[..len]) {
Ok(msg) => msg.trim(),
Err(error) => break Err(io::Error::new(io::ErrorKind::InvalidData, error))
};
break Ok(Some(WpaControlMessage {
raw: msg,
}))
},
Err(error) => match error.kind() {
io::ErrorKind::Interrupted => continue,
io::ErrorKind::TimedOut => break Ok(None),
_ => break Err(error),
}
}
}
}
pub fn recv_req_result(&mut self) -> Option<Result<Result<(), ()>, io::Error>> {
loop {
match self.recv() {
Ok(Some(msg)) => {
if msg.as_success().is_some() {
break Some(Ok(Ok(())));
} else if msg.as_fail().is_some() {
break Some(Ok(Err(())));
} else {
continue
}
},
Ok(None) => break None,
Err(error) => return Some(Err(error)),
}
}
}
pub fn add_network(&mut self, ssid: &str, wpa_pass: Option<&str>, hidden: bool) -> Result<Id, io::Error> {
self.request(WpaControlReq::add_network())?;
let id = loop {
match self.recv()? {
Some(msg) => match msg.as_network_id() {
Some(id) => break id,
None => continue,
},
None => return Err(io::Error::new(io::ErrorKind::TimedOut, "no response to add_network")),
}
};
self.request(WpaControlReq::set_network(id, "ssid", QuotedValue(ssid)))?;
match self.recv_req_result() {
Some(Ok(Ok(()))) => (),
Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} ssid {} failed", id.0, QuotedValue(ssid)))),
Some(Err(error)) => return Err(error),
None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} ssid {} had no ok/fail reply", id.0, QuotedValue(ssid)))),
}
match wpa_pass {
Some(wpa_pass) => {
let wpa_pass = QuotedValue(wpa_pass);
self.request(WpaControlReq::set_network(id, "psk", &wpa_pass))?;
match self.recv_req_result() {
Some(Ok(Ok(()))) => (),
Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} psk {} failed", id.0, wpa_pass))),
Some(Err(error)) => return Err(error),
None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} psk {} had no ok/fail reply", id.0, wpa_pass))),
}
},
None => {
self.request(WpaControlReq::set_network(id, "key_mgmt", "NONE"))?;
match self.recv_req_result() {
Some(Ok(Ok(()))) => (),
Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} key_mgmt NONE failed", id.0))),
Some(Err(error)) => return Err(error),
None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} key_mgmt NONE had no ok/fail reply", id.0))),
}
},
}
if hidden {
self.request(WpaControlReq::set_network(id, "scan_ssid", 1))?;
match self.recv_req_result() {
Some(Ok(Ok(()))) => (),
Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} scan_ssid 1 failed", id.0))),
Some(Err(error)) => return Err(error),
None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} scan_ssid 1 had no ok/fail reply", id.0))),
}
}
Ok(id)
}
pub fn remove_network(&mut self, id: Id) -> Result<(), io::Error> {
self.request(WpaControlReq::remove_network(id))?;
match self.recv_req_result() {
Some(Ok(Ok(()))) => Ok(()),
Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("remove_network id={}", id.0))),
Some(Err(error)) => return Err(error),
None => return Err(io::Error::new(io::ErrorKind::Other, format!("remove_network id={} has no reply", id.0))),
}
}
pub fn select_network(&mut self, id: Id) -> Result<(), io::Error> {
self.request(WpaControlReq::select_network(id))?;
match self.recv_req_result() {
Some(Ok(Ok(()))) => Ok(()),
Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("select_network id={}", id.0))),
Some(Err(error)) => return Err(error),
None => return Err(io::Error::new(io::ErrorKind::Other, format!("select_network id={} has no reply", id.0))),
}
}
pub fn reconfigure(&mut self) -> Result<(), io::Error> {
self.request(WpaControlReq::raw("RECONFIGURE"))?;
match self.recv_req_result() {
Some(Ok(Ok(()))) => Ok(()),
Some(Ok(Err(r))) => return Err(io::Error::new(io::ErrorKind::Other, format!("reconfigure ret={:?}", r))),
Some(Err(error)) => return Err(error),
None => return Err(io::Error::new(io::ErrorKind::Other, "reconfigure has no reply".to_owned())),
}
}
}
impl Drop for WpaController {
#[inline]
fn drop(&mut self) {
let _ = self.socket.shutdown(net::Shutdown::Both);
let _ = fs::remove_file(&self.local);
}
}