rswifi/platform/linux/
interface.rs

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()?;  // frequency
116                let _ = parts.next()?;  // rssi
117                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