1#![cfg(unix)]
49#![warn(missing_docs)]
50#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
51
52#[cfg(feature = "serde")]
53use serde::{Serialize, Deserialize};
54
55mod utils;
56
57const BUF_SIZE: usize = 512;
58const DEFAULT_ROOT: &str = "/var/run/wpa_supplicant/";
59
60#[cfg(not(unix))]
61compile_error!("Supports only unix targets");
62
63#[cfg(target_os = "android")]
64const LOCAL_SOCKET_DIR: &str = "/data/misc/wifi/sockets";
65#[cfg(not(target_os = "android"))]
66const LOCAL_SOCKET_DIR: &str = "/tmp";
67const LOCAL_SOCKET_PREFIX: &str = "wpa_crtl_";
68const UNSOLICITED_PREFIX: char = '<';
69type LocalSocketName = str_buf::StrBuf<23>;
70
71use std::os::unix::net::UnixDatagram;
72use std::{fs, io, path, net};
73use core::{str, time};
74use core::sync::atomic::{AtomicU32, Ordering};
75use core::fmt::{self, Write};
76
77pub struct QuotedValue<T: fmt::Display>(pub T);
79
80impl<T: fmt::Display> fmt::Display for QuotedValue<T> {
81 #[inline]
82 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
83 fmt.write_fmt(format_args!("\"{}\"", self.0))
84 }
85}
86
87fn local_socket_name() -> LocalSocketName {
88 static COUNTER: AtomicU32 = AtomicU32::new(1);
89
90 let mut name = LocalSocketName::new();
91 let _ = write!(&mut name, "{}{}", LOCAL_SOCKET_PREFIX, COUNTER.fetch_add(1, Ordering::SeqCst));
92 name
93}
94
95#[derive(Copy, Clone, Debug)]
97pub struct WpaControllerBuilder<'a> {
98 pub root: &'a str,
100 pub read_timeout: Option<time::Duration>,
102}
103
104impl WpaControllerBuilder<'static> {
105 pub const fn new() -> Self {
110 Self {
111 root: DEFAULT_ROOT,
112 read_timeout: Some(time::Duration::from_secs(10)),
113 }
114 }
115}
116
117impl<'a> WpaControllerBuilder<'a> {
118 #[inline]
119 #[allow(clippy::needless_lifetimes)]
120 pub const fn set_root<'b>(self, new: &'b str) -> WpaControllerBuilder<'b> {
122 WpaControllerBuilder {
123 root: new,
124 read_timeout: self.read_timeout,
125 }
126 }
127
128 #[inline]
129 pub const fn set_read_timeout(mut self, read_timeout: Option<time::Duration>) -> Self {
134 self.read_timeout = read_timeout;
135 self
136 }
137
138 #[inline]
139 pub fn open(self, interface: &str) -> Result<WpaController, io::Error> {
141 let path = path::Path::new(self.root).join(interface);
142 WpaController::open_path(&path)
143 }
144}
145
146pub struct WpaControlReq {
150 buf: str_buf::StrBuf<127>,
151}
152
153impl WpaControlReq {
154 #[inline]
155 pub const fn raw(text: &str) -> Self {
159 Self {
160 buf: str_buf::StrBuf::from_str(text)
161 }
162 }
163
164 #[inline]
165 pub const fn ping() -> Self {
167 Self::raw("PING")
168 }
169
170 #[inline]
171 pub const fn status() -> Self {
173 Self::raw("STATUS")
174 }
175
176 #[inline]
177 pub const fn scan() -> Self {
179 Self::raw("SCAN")
180 }
181
182 #[inline]
183 pub const fn scan_results() -> Self {
185 Self::raw("SCAN_RESULTS")
186 }
187
188 #[inline]
189 pub const fn disconnect() -> Self {
191 Self::raw("DISCONNECT")
192 }
193
194 #[inline]
195 pub const fn reassociate() -> Self {
197 Self::raw("REASSOCIATE")
198 }
199
200 #[inline]
201 pub const fn list_networks() -> Self {
203 Self::raw("LIST_NETWORKS")
204 }
205
206 #[inline]
207 pub fn add_network() -> Self {
209 Self::raw("ADD_NETWORK")
210 }
211
212 #[inline]
213 pub fn get_network(id: Id, var: &str) -> Self {
215 let mut this = Self::raw("SET_NETWORK");
216 let _ = write!(&mut this.buf, " {}", id.0);
217 this.buf.push_str(" ");
218 this.buf.push_str(var);
219 this
220 }
221
222 #[inline]
223 pub fn set_network(id: Id, var: &str, value: impl fmt::Display) -> Self {
225 let mut this = Self::raw("SET_NETWORK");
226 let _ = write!(&mut this.buf, " {}", id.0);
227 this.buf.push_str(" ");
228 this.buf.push_str(var);
229 let _ = write!(&mut this.buf, " {}", value);
230 this
231 }
232
233 #[inline]
234 pub fn select_network(id: Id) -> Self {
236 let mut this = Self::raw("SELECT_NETWORK");
237 let _ = write!(&mut this.buf, " {}", id.0);
238 this
239 }
240
241 #[inline]
242 pub fn enable_network(id: Id) -> Self {
244 let mut this = Self::raw("ENABLE_NETWORK");
245 let _ = write!(&mut this.buf, " {}", id.0);
246 this
247 }
248
249 #[inline]
250 pub fn disable_network(id: Id) -> Self {
252 let mut this = Self::raw("DISABLE_NETWORK");
253 let _ = write!(&mut this.buf, " {}", id.0);
254 this
255 }
256
257 #[inline]
258 pub fn remove_network(id: Id) -> Self {
260 let mut this = Self::raw("REMOVE_NETWORK");
261 let _ = write!(&mut this.buf, " {}", id.0);
262 this
263 }
264
265 #[inline]
266 pub fn remove_network_all() -> Self {
268 Self::raw("REMOVE_NETWORK all")
269 }
270
271
272 #[inline]
273 pub fn save_config() -> Self {
275 Self::raw("SAVE_CONFIG")
276 }
277}
278
279impl fmt::Debug for WpaControlReq {
280 #[inline(always)]
281 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
282 fmt::Debug::fmt(&self.buf, fmt)
283 }
284}
285
286
287#[derive(Copy, Clone, Debug)]
288pub struct Success;
290
291#[derive(Copy, Clone, Debug)]
292pub struct Fail;
294
295#[derive(Copy, Clone, Debug)]
296pub struct Pong;
298
299#[derive(Copy, Clone, Debug)]
300#[repr(transparent)]
301#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
302#[cfg_attr(feature = "serde", serde(transparent))]
303pub struct Id(pub u32);
305
306#[derive(Copy, Clone, Debug)]
307pub enum WpaState {
309 Unknown,
311 Disconnected,
316 InterfaceDisabled,
322 Inactive,
328 Scanning,
332 Authenticating,
338 Associating,
345 Associated,
351 Handshake,
357 GroupHandshake,
361 Completed
363}
364
365impl WpaState {
366 pub fn from_str(text: &str) -> Self {
368 if text.eq_ignore_ascii_case("COMPLETED") {
369 Self::Completed
370 } else if text.eq_ignore_ascii_case("GROUP_HANDSHAKE") {
371 Self::GroupHandshake
372 } else if text.eq_ignore_ascii_case("4WAY_HANDSHAKE") {
373 Self::Handshake
374 } else if text.eq_ignore_ascii_case("ASSOCIATED") {
375 Self::Associated
376 } else if text.eq_ignore_ascii_case("ASSOCIATING") {
377 Self::Associating
378 } else if text.eq_ignore_ascii_case("AUTHENTICATING") {
379 Self::Authenticating
380 } else if text.eq_ignore_ascii_case("SCANNING") {
381 Self::Scanning
382 } else if text.eq_ignore_ascii_case("INACTIVE") {
383 Self::Inactive
384 } else if text.eq_ignore_ascii_case("INTERFACE_DISABLED") {
385 Self::InterfaceDisabled
386 } else if text.eq_ignore_ascii_case("DISCONNECTED") {
387 Self::Disconnected
388 } else {
389 Self::Unknown
390 }
391 }
392}
393
394#[derive(Clone, Debug)]
395pub struct WpaStatus {
397 pub state: WpaState,
399 pub ip: Option<net::IpAddr>,
401 pub ssid: Option<String>
403}
404
405impl WpaStatus {
406 pub fn from_str(text: &str) -> Option<Self> {
410 let mut state = None;
411 let mut ip = None;
412 let mut ssid = None;
413
414 for line in text.lines() {
415 if line.is_empty() {
416 continue;
417 }
418
419 let mut split = line.splitn(2, '=');
420 let var = split.next().unwrap();
421 if let Some(value) = split.next() {
422 if var.eq_ignore_ascii_case("wpa_state") {
423 state = Some(WpaState::from_str(value));
424 } else if var.eq_ignore_ascii_case("ip_address") {
425 ip = value.parse().ok();
426 } else if var.eq_ignore_ascii_case("ssid") {
427 ssid = Some(value.to_owned());
428 } else {
429 continue;
431 }
432 } else {
433 return None;
435 }
436 }
437
438 state.map(|state| Self {
439 state,
440 ip,
441 ssid,
442 })
443 }
444}
445
446
447#[derive(Clone, Copy, Debug, Default)]
449#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
450pub struct WpaNetworkFlags {
451 pub current: bool,
453 pub disabled: bool,
455 pub p2p_persistent: bool
457}
458
459impl WpaNetworkFlags {
460 #[inline(always)]
461 pub fn from_str(mut text: &str) -> Self {
463 let mut result = WpaNetworkFlags {
464 current: false,
465 disabled: false,
466 p2p_persistent: false,
467 };
468
469 if !text.is_empty() {
470 while let Some(start_flag) = text.strip_prefix('[') {
471 if let Some(end) = start_flag.find(']') {
472 let flag = &start_flag[..end];
473 if flag.eq_ignore_ascii_case("CURRENT") {
474 result.current = true;
475 } else if flag.eq_ignore_ascii_case("DISABLED") {
476 result.disabled = true;
477 } else if flag.eq_ignore_ascii_case("P2P-PERSISTENT") {
478 result.p2p_persistent = true;
479 }
480 text = &start_flag[end..];
481 } else {
482 break;
483 }
484 }
485 }
486
487 result
488 }
489}
490
491#[derive(Clone, Debug)]
493#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
494pub struct WpaNetwork {
495 pub id: Id,
497 pub ssid: String,
499 pub bssid: String,
501 pub flags: WpaNetworkFlags,
503}
504
505pub struct WpaNetworkList<'a> {
507 lines: str::Lines<'a>,
508}
509
510impl<'a> Iterator for WpaNetworkList<'a> {
511 type Item = WpaNetwork;
512
513 #[inline]
514 fn next(&mut self) -> Option<Self::Item> {
515 let line = self.lines.next()?;
516 let mut parts = line.splitn(4, '\t');
517 let network = WpaNetwork {
518 id: Id(parts.next().unwrap().parse().ok()?),
519 ssid: parts.next()?.to_owned(),
520 bssid: parts.next()?.to_owned(),
521 flags: WpaNetworkFlags::from_str(parts.next().unwrap_or("")),
522 };
523 Some(network)
524 }
525}
526
527pub struct WpaControlMessage<'a> {
529 pub raw: &'a str,
531}
532
533impl<'a> WpaControlMessage<'a> {
534 #[inline(always)]
535 pub const fn is_unsolicited(&self) -> bool {
537 !self.raw.is_empty() && self.raw.as_bytes()[0] == UNSOLICITED_PREFIX as u8
538 }
539
540 pub fn as_pong(&self) -> Option<Pong> {
542 if self.raw.eq_ignore_ascii_case("pong") {
543 Some(Pong)
544 } else {
545 None
546 }
547 }
548
549 pub fn as_success(&self) -> Option<Success> {
551 if self.raw.eq_ignore_ascii_case("ok") {
552 Some(Success)
553 } else {
554 None
555 }
556 }
557
558 pub fn as_fail(&self) -> Option<Fail> {
560 if self.raw.eq_ignore_ascii_case("fail") {
561 Some(Fail)
562 } else {
563 None
564 }
565 }
566
567 pub fn as_status(&self) -> Option<WpaStatus> {
569 WpaStatus::from_str(self.raw)
570 }
571
572 pub fn as_network_id(&self) -> Option<Id> {
574 self.raw.parse().map(|id| Id(id)).ok()
575 }
576
577 pub fn as_network_list(&self) -> Option<WpaNetworkList<'_>> {
579 let mut lines = self.raw.lines();
580 let line = lines.next().unwrap();
581 let header = utils::split::<4>(line, '/')?;
582 if header[0] == "network id" && header[1] == "ssid" && header[2] == "bssid" && header[3] == "flags" {
583 Some(WpaNetworkList {
584 lines
585 })
586 } else {
587 None
588 }
589 }
590}
591
592impl<'a> fmt::Debug for WpaControlMessage<'a> {
593 #[inline(always)]
594 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
595 fmt::Debug::fmt(self.raw, fmt)
596 }
597}
598
599pub struct WpaController {
601 buffer: [u8; BUF_SIZE],
602 socket: UnixDatagram,
603 local: path::PathBuf,
604}
605
606impl WpaController {
607 #[inline(always)]
608 pub fn open<P: AsRef<path::Path>>(path: P) -> Result<Self, io::Error> {
610 Self::open_path(path.as_ref())
611 }
612
613 pub fn open_path(path: &path::Path) -> Result<Self, io::Error> {
615 let local_name = local_socket_name();
616 let local = path::Path::new(LOCAL_SOCKET_DIR).join(local_name.as_str());
617 let socket = UnixDatagram::bind(&local)?;
618 let this = Self {
619 buffer: [0; BUF_SIZE],
620 socket,
621 local,
622 };
623 this.socket.connect(path)?;
624 Ok(this)
625 }
626
627 #[inline]
628 pub fn request(&mut self, req: WpaControlReq) -> Result<usize, io::Error> {
630 self.socket.send(req.buf.as_bytes())
631 }
632
633 pub fn recv(&mut self) -> Result<Option<WpaControlMessage<'_>>, io::Error> {
635 loop {
636 match self.socket.recv(&mut self.buffer) {
637 Ok(len) => {
638 let msg = match std::str::from_utf8(&self.buffer[..len]) {
639 Ok(msg) => msg.trim(),
640 Err(error) => break Err(io::Error::new(io::ErrorKind::InvalidData, error))
641 };
642
643 break Ok(Some(WpaControlMessage {
644 raw: msg,
645 }))
646 },
647 Err(error) => match error.kind() {
648 io::ErrorKind::Interrupted => continue,
649 io::ErrorKind::TimedOut => break Ok(None),
650 _ => break Err(error),
651 }
652 }
653 }
654 }
655
656 pub fn recv_req_result(&mut self) -> Option<Result<Result<(), ()>, io::Error>> {
667 loop {
668 match self.recv() {
669 Ok(Some(msg)) => {
670 if msg.as_success().is_some() {
671 break Some(Ok(Ok(())));
672 } else if msg.as_fail().is_some() {
673 break Some(Ok(Err(())));
674 } else {
675 continue
676 }
677 },
678 Ok(None) => break None,
679 Err(error) => return Some(Err(error)),
680 }
681 }
682 }
683
684 pub fn add_network(&mut self, ssid: &str, wpa_pass: Option<&str>, hidden: bool) -> Result<Id, io::Error> {
696 self.request(WpaControlReq::add_network())?;
697 let id = loop {
698 match self.recv()? {
699 Some(msg) => match msg.as_network_id() {
700 Some(id) => break id,
701 None => continue,
702 },
703 None => return Err(io::Error::new(io::ErrorKind::TimedOut, "no response to add_network")),
704 }
705 };
706
707 self.request(WpaControlReq::set_network(id, "ssid", QuotedValue(ssid)))?;
708 match self.recv_req_result() {
709 Some(Ok(Ok(()))) => (),
710 Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} ssid {} failed", id.0, QuotedValue(ssid)))),
711 Some(Err(error)) => return Err(error),
712 None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} ssid {} had no ok/fail reply", id.0, QuotedValue(ssid)))),
713 }
714 match wpa_pass {
715 Some(wpa_pass) => {
716 let wpa_pass = QuotedValue(wpa_pass);
717 self.request(WpaControlReq::set_network(id, "psk", &wpa_pass))?;
718 match self.recv_req_result() {
719 Some(Ok(Ok(()))) => (),
720 Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} psk {} failed", id.0, wpa_pass))),
721 Some(Err(error)) => return Err(error),
722 None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} psk {} had no ok/fail reply", id.0, wpa_pass))),
723 }
724 },
725 None => {
726 self.request(WpaControlReq::set_network(id, "key_mgmt", "NONE"))?;
727 match self.recv_req_result() {
728 Some(Ok(Ok(()))) => (),
729 Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} key_mgmt NONE failed", id.0))),
730 Some(Err(error)) => return Err(error),
731 None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} key_mgmt NONE had no ok/fail reply", id.0))),
732 }
733 },
734 }
735
736 if hidden {
737 self.request(WpaControlReq::set_network(id, "scan_ssid", 1))?;
738 match self.recv_req_result() {
739 Some(Ok(Ok(()))) => (),
740 Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} scan_ssid 1 failed", id.0))),
741 Some(Err(error)) => return Err(error),
742 None => return Err(io::Error::new(io::ErrorKind::Other, format!("set_network id={} scan_ssid 1 had no ok/fail reply", id.0))),
743 }
744 }
745
746 Ok(id)
747 }
748
749 pub fn remove_network(&mut self, id: Id) -> Result<(), io::Error> {
751 self.request(WpaControlReq::remove_network(id))?;
752 match self.recv_req_result() {
753 Some(Ok(Ok(()))) => Ok(()),
754 Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("remove_network id={}", id.0))),
755 Some(Err(error)) => return Err(error),
756 None => return Err(io::Error::new(io::ErrorKind::Other, format!("remove_network id={} has no reply", id.0))),
757 }
758 }
759
760 pub fn select_network(&mut self, id: Id) -> Result<(), io::Error> {
762 self.request(WpaControlReq::select_network(id))?;
763 match self.recv_req_result() {
764 Some(Ok(Ok(()))) => Ok(()),
765 Some(Ok(Err(()))) => return Err(io::Error::new(io::ErrorKind::Other, format!("select_network id={}", id.0))),
766 Some(Err(error)) => return Err(error),
767 None => return Err(io::Error::new(io::ErrorKind::Other, format!("select_network id={} has no reply", id.0))),
768 }
769 }
770
771 pub fn reconfigure(&mut self) -> Result<(), io::Error> {
773 self.request(WpaControlReq::raw("RECONFIGURE"))?;
774 match self.recv_req_result() {
775 Some(Ok(Ok(()))) => Ok(()),
776 Some(Ok(Err(r))) => return Err(io::Error::new(io::ErrorKind::Other, format!("reconfigure ret={:?}", r))),
777 Some(Err(error)) => return Err(error),
778 None => return Err(io::Error::new(io::ErrorKind::Other, "reconfigure has no reply".to_owned())),
779 }
780 }
781}
782
783impl Drop for WpaController {
784 #[inline]
785 fn drop(&mut self) {
786 let _ = self.socket.shutdown(net::Shutdown::Both);
787 let _ = fs::remove_file(&self.local);
788 }
789}