1use std::{collections::HashSet, os::fd::AsRawFd as _, sync::Arc, path::PathBuf};
2use getset::Getters;
3use nix::sys::socket;
4
5use crate::{error::Error, AkmType, CipherType, IFaceStatus, platform::WiFiInterface, profile::Profile, Result};
6use super::{util, Handle, socket_file};
7
8const CTRL_IFACE_RETRY: usize = 3;
9const REPLY_SIZE: usize = 4096;
10
11#[derive(Debug, Clone, Getters)]
12pub struct Interface {
13 #[getset(get = "pub")]
14 pub(crate) name: String,
15 pub(crate) handle: Arc<Handle>,
16}
17
18unsafe impl Sync for Interface {}
19unsafe impl Send for Interface {}
20
21impl Interface {
22 pub(crate) fn new(ctrl_iface: PathBuf) -> Result<Option<Self>> {
23 let iface = ctrl_iface.file_name()
24 .ok_or(Error::Other(format!("{:?} is no filename", ctrl_iface).into()))?
25 .to_os_string()
26 .into_string()
27 .map_err(Into::<Error>::into)?;
28 let sock_file = socket_file(&iface);
29 util::remove_file(&sock_file)?;
30
31 let sock = socket::socket(
32 socket::AddressFamily::Unix,
33 socket::SockType::Datagram,
34 socket::SockFlag::empty(), None
35 )
36 .map_err(Into::<Error>::into)?
37 .as_raw_fd();
38 let addr = socket::UnixAddr::new(sock_file.as_str())
39 .map_err(Into::<Error>::into)?;
40 socket::bind(sock, &addr)
41 .map_err(Into::<Error>::into)?;
42 let addr = socket::UnixAddr::new(ctrl_iface.as_path())
43 .map_err(Into::<Error>::into)?;
44 socket::connect(sock, &addr)
45 .map_err(Into::<Error>::into)?;
46
47 let len = socket::send(sock, "PING".as_bytes(), socket::MsgFlags::empty())
48 .map_err(Into::<Error>::into)?;
49 rsutil::debug!("Sent `PING` returned: {}", len);
50 for _ in 0..CTRL_IFACE_RETRY {
51 let mut buffer = [0u8; REPLY_SIZE];
52 let reply = socket::recv(sock, &mut buffer, socket::MsgFlags::empty())
53 .map_err(Into::<Error>::into)?;
54 if reply == 0 {
55 rsutil::error!("Connection to {} is broken!", iface);
56 break
57 }
58
59 if String::from_utf8_lossy(&buffer[..reply]).starts_with("PONG") {
60 rsutil::info!("Connection to socket {} successfully!", iface);
61 return Ok(Some(Self {
62 name: iface.clone(),
63 handle: Arc::new(Handle {
64 iface,
65 fd: sock,
66 }),
67 }));
68 }
69 }
70
71 Ok(None)
72 }
73
74 fn _send_cmd_to_wpas(&self, cmd: &str, get_repy: bool) -> Result<Option<String>> {
75 let len = socket::send(self.handle.fd, cmd.as_bytes(), socket::MsgFlags::empty())
76 .map_err(Into::<Error>::into)?;
77 if !cmd.contains("psk") {
78 rsutil::debug!("Sending command: {} to wpa_s: {}", cmd, len);
79 }
80 if len == 0 {
81 return Err(Error::Other(format!("Failed to send command: {}", cmd).into()));
82 }
83
84 let mut buffer = [0u8; REPLY_SIZE];
85 let reply = socket::recv(self.handle.fd, &mut buffer, socket::MsgFlags::empty())
86 .map_err(Into::<Error>::into)?;
87 let result = String::from_utf8_lossy(&buffer[..reply]);
88 if get_repy {
89 return Ok(Some(result.to_string()));
90 }
91
92 if !result.eq_ignore_ascii_case("Ok\n") {
93 rsutil::error!("Unexpected resp '{}' for Command '{}'", result, cmd);
94 }
95
96 Ok(None)
97 }
98}
99
100impl WiFiInterface for Interface {
101 fn scan(&self) -> Result<()> {
102 let _ = self._send_cmd_to_wpas("SCAN", false)?;
103 Ok(())
104 }
105
106 fn scan_results(&self) -> Result<HashSet<Profile>> {
107 let reply = self._send_cmd_to_wpas("SCAN_RESULTS", true)?
108 .unwrap();
109
110 Ok(reply.lines()
111 .skip(1)
112 .filter_map(|line| {
113 let mut parts = line.split_whitespace();
114 let bssid = parts.next()?;
115 let _ = parts.next()?; let _ = parts.next()?; let akm = parts.next()?;
118 let ssid = parts.next()?;
119 let mut profile = Profile::new(ssid)
120 .with_bssid(Some(bssid.into()));
121 if akm.contains("WPA-PSK") {
122 profile.add_akm(AkmType::WpaPsk);
123 }
124 if akm.contains("WPA2-PSK") {
125 profile.add_akm(AkmType::Wpa2Psk);
126 }
127 if akm.contains("WPA-EAP") {
128 profile.add_akm(AkmType::Wpa);
129 }
130 if akm.contains("WPA2-EAP") {
131 profile.add_akm(AkmType::Wpa2);
132 }
133
134 Some(profile)
135 })
136 .collect::<HashSet<_>>())
137 }
138
139 fn connect(&self, ssid: &str) -> Result<bool> {
140 rsutil::debug!("Connecting to network: {}", ssid);
141 let mut flag = false;
142 for (i, s) in self.network_profile_name_list()?
143 .iter()
144 .enumerate() {
145 if ssid == s {
146 let reply = self._send_cmd_to_wpas(&format!("SELECT_NETWORK {}", i), true)?.unwrap();
147 if reply.to_lowercase() != "OK" {
148 rsutil::error!("Failed({}) to connect to network: {}", reply, ssid);
149 }
150 else {
151 rsutil::info!("Connected to network: {}", ssid);
152 flag = true;
153 }
154 break;
155 }
156 }
157
158 Ok(flag)
159 }
160
161 fn disconnect(&self) -> Result<()> {
162 self._send_cmd_to_wpas("DISCONNECT", false)?;
163 Ok(())
164 }
165
166 fn add_network_profile(&self, profile: &Profile) -> Result<()> {
167 let reply = self._send_cmd_to_wpas("ADD_NETWORK", true)?.unwrap();
168 let id = reply.trim();
169
170 let _ = self._send_cmd_to_wpas(&format!("SET_NETWORK {} ssid \"{}\"", id, profile.ssid()), false)?;
171 let akm: &AkmType = profile.akm()
172 .last()
173 .unwrap_or(&AkmType::None);
174 let key_mgmt = akm.key_mgmt();
175 if !key_mgmt.is_empty() {
176 let _ = self._send_cmd_to_wpas(&format!("SET_NETWORK {} key_mgmt {}", id, key_mgmt), false)?;
177 }
178 let proto = akm.proto();
179 if !proto.is_empty() {
180 let _ = self._send_cmd_to_wpas(&format!("SET_NETWORK {} proto {}", id, proto), false)?;
181 }
182 if akm.key_required() {
183 let key = match profile.key() {
184 Some(v) => v,
185 None => &String::default(),
186 };
187 let _ = self._send_cmd_to_wpas(&format!("SET_NETWORK {} psk \"{}\"", id, key), false)?;
188 }
189
190 Ok(())
191 }
192
193 fn network_profile_name_list(&self) -> Result<Vec<String>> {
194 let reply = self._send_cmd_to_wpas("LIST_NETWORKS", true)?.unwrap();
195 Ok(reply.lines()
196 .skip(1)
197 .filter_map(|line| {
198 let ssid = line.split_whitespace()
199 .skip(1)
200 .nth(1)?;
201 Some(ssid.into())
202 })
203 .collect())
204 }
205
206 fn network_profiles(&self) -> Result<Vec<Profile>> {
207 let len = self.network_profile_name_list()?.len();
208
209 let mut results = vec![];
210 for i in 0..len {
211 let ssid = self._send_cmd_to_wpas(&format!("GET_NETWORK {} ssid", i), true)?.unwrap();
212 let key_mgmt = self._send_cmd_to_wpas(&format!("GET_NETWORK {} key_mgmt", i), true)?.unwrap();
213 let key_mgmt = key_mgmt.to_uppercase();
214 if key_mgmt.contains("FAIL") {
215 continue;
216 }
217 let ciphers = self._send_cmd_to_wpas(&format!("GET_NETWORK {} pairwise", i), true)?.unwrap();
218 let ciphers = ciphers.to_uppercase();
219 if ciphers.contains("FAIL") {
220 continue;
221 }
222 let mut profile = Profile::new(ssid.trim());
223 profile.id = i;
224 if key_mgmt.contains("WPA-PSK") {
225 let rep = self._send_cmd_to_wpas(&format!("GET_NETWORK {} proto", i), true)?.unwrap();
226 if rep.to_uppercase() == "RSN" {
227 profile.add_akm(AkmType::Wpa2Psk);
228 }
229 else {
230 profile.add_akm(AkmType::WpaPsk);
231 }
232 }
233 else if key_mgmt.contains("WPA-EAP") {
234 let rep = self._send_cmd_to_wpas(&format!("GET_NETWORK {} proto", i), true)?.unwrap();
235 if rep.to_uppercase() == "RSN" {
236 profile.add_akm(AkmType::Wpa2);
237 }
238 else {
239 profile.add_akm(AkmType::Wpa);
240 }
241 }
242
243 let ciphers = ciphers.split_whitespace();
244 let mut first = None;
245 for c in ciphers {
246 if c.contains("CCMP") {
247 profile.cipher = CipherType::Ccmp;
248 first = None;
249 break;
250 }
251 first = Some(c);
252 }
253 if let Some(v) = first {
254 profile.cipher = match v {
255 "CCMP" => CipherType::Ccmp,
256 "TKIP" => CipherType::Tkip,
257 "WEP" => CipherType::Wep,
258 _ => CipherType::Unknown,
259 }
260 }
261
262 results.push(profile);
263 }
264
265 Ok(results)
266 }
267
268 fn remove_network_profile(&self, name: &str) -> Result<()> {
269 let profiles = self.network_profiles()?;
270 for p in profiles {
271 if p.ssid == name {
272 let _ = self._send_cmd_to_wpas(&format!("REMOVE_NETWORK {}", p.id), false)?;
273 break;
274 }
275 }
276
277 Ok(())
278 }
279
280 fn remove_all_network_profiles(&self) -> Result<()> {
281 let _ = self._send_cmd_to_wpas("REMOVE_NETWORK all", false)?;
282 Ok(())
283 }
284
285 fn status(&self) -> Result<IFaceStatus> {
286 let reply = self._send_cmd_to_wpas("STATUS", true)?.unwrap();
287 let mut status = IFaceStatus::Unknown;
288 for line in reply.lines() {
289 if line.starts_with("wpa_state=") {
290 status = line.split('=').nth(1)
291 .ok_or(Error::Other("Failed to parse wpa_state".into()))?
292 .to_lowercase()
293 .into();
294 break;
295 }
296 }
297
298 Ok(status)
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use std::path::PathBuf;
305 use crate::WiFiInterface;
306 use super::Interface;
307
308 #[test]
309 fn test_new() -> anyhow::Result<()> {
310 let iface = Interface::new(PathBuf::from("/var/run/wpa_supplicant/wlp11s0"))?
311 .unwrap();
312 let result = iface.scan_results()?;
313 println!("result: {:?}", result);
314 iface.connect("TestSSID")?;
315
316 Ok(())
317 }
318}
319
320