network_manager/
connection.rs

1use std::rc::Rc;
2use std::fmt;
3use std::net::Ipv4Addr;
4
5use errors::*;
6use dbus_nm::DBusNetworkManager;
7
8use wifi::{AccessPoint, AccessPointCredentials};
9use device::{get_active_connection_devices, Device};
10use ssid::{AsSsidSlice, Ssid};
11
12#[derive(Clone)]
13pub struct Connection {
14    dbus_manager: Rc<DBusNetworkManager>,
15    path: String,
16    settings: ConnectionSettings,
17}
18
19impl Connection {
20    fn init(dbus_manager: &Rc<DBusNetworkManager>, path: &str) -> Result<Self> {
21        let settings = dbus_manager.get_connection_settings(path)?;
22
23        Ok(Connection {
24            dbus_manager: Rc::clone(dbus_manager),
25            path: path.to_string(),
26            settings: settings,
27        })
28    }
29
30    pub fn settings(&self) -> &ConnectionSettings {
31        &self.settings
32    }
33
34    pub fn get_state(&self) -> Result<ConnectionState> {
35        let active_path_option = get_connection_active_path(&self.dbus_manager, &self.path)?;
36
37        if let Some(active_path) = active_path_option {
38            let state = self.dbus_manager.get_connection_state(&active_path)?;
39
40            Ok(state)
41        } else {
42            Ok(ConnectionState::Deactivated)
43        }
44    }
45
46    pub fn delete(&self) -> Result<()> {
47        self.dbus_manager.delete_connection(&self.path)
48    }
49
50    /// Activate a Network Manager connection.
51    ///
52    /// # Examples
53    ///
54    /// ```no_run
55    /// use network_manager::NetworkManager;
56    /// let manager = NetworkManager::new();
57    /// let connections = manager.get_connections().unwrap();
58    /// connections[0].activate().unwrap();
59    /// ```
60    pub fn activate(&self) -> Result<ConnectionState> {
61        let state = self.get_state()?;
62
63        match state {
64            ConnectionState::Activated => Ok(ConnectionState::Activated),
65            ConnectionState::Activating => wait(
66                self,
67                &ConnectionState::Activated,
68                self.dbus_manager.method_timeout(),
69            ),
70            ConnectionState::Unknown => bail!(ErrorKind::NetworkManager(
71                "Unable to get connection state".into()
72            )),
73            _ => {
74                self.dbus_manager.activate_connection(&self.path)?;
75
76                wait(
77                    self,
78                    &ConnectionState::Activated,
79                    self.dbus_manager.method_timeout(),
80                )
81            },
82        }
83    }
84
85    /// Deactivates a Network Manager connection.
86    ///
87    /// # Examples
88    ///
89    /// ```no_run
90    /// use network_manager::NetworkManager;
91    /// let manager = NetworkManager::new();
92    /// let connections = manager.get_connections().unwrap();
93    /// connections[0].deactivate().unwrap();
94    /// ```
95    pub fn deactivate(&self) -> Result<ConnectionState> {
96        let state = self.get_state()?;
97
98        match state {
99            ConnectionState::Deactivated => Ok(ConnectionState::Deactivated),
100            ConnectionState::Deactivating => wait(
101                self,
102                &ConnectionState::Deactivated,
103                self.dbus_manager.method_timeout(),
104            ),
105            ConnectionState::Unknown => bail!(ErrorKind::NetworkManager(
106                "Unable to get connection state".into()
107            )),
108            _ => {
109                let active_path_option =
110                    get_connection_active_path(&self.dbus_manager, &self.path)?;
111
112                if let Some(active_path) = active_path_option {
113                    self.dbus_manager.deactivate_connection(&active_path)?;
114
115                    wait(
116                        self,
117                        &ConnectionState::Deactivated,
118                        self.dbus_manager.method_timeout(),
119                    )
120                } else {
121                    Ok(ConnectionState::Deactivated)
122                }
123            },
124        }
125    }
126
127    pub fn get_devices(&self) -> Result<Vec<Device>> {
128        let active_path_option = get_connection_active_path(&self.dbus_manager, &self.path)?;
129
130        if let Some(active_path) = active_path_option {
131            get_active_connection_devices(&self.dbus_manager, &active_path)
132        } else {
133            Ok(vec![])
134        }
135    }
136}
137
138impl Ord for Connection {
139    fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
140        i32::from(self).cmp(&i32::from(other))
141    }
142}
143
144impl PartialOrd for Connection {
145    fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
146        Some(self.cmp(other))
147    }
148}
149
150impl PartialEq for Connection {
151    fn eq(&self, other: &Connection) -> bool {
152        i32::from(self) == i32::from(other)
153    }
154}
155
156impl Eq for Connection {}
157
158impl fmt::Debug for Connection {
159    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160        write!(
161            f,
162            "Connection {{ path: {:?}, settings: {:?} }}",
163            self.path, self.settings
164        )
165    }
166}
167
168impl<'a> From<&'a Connection> for i32 {
169    fn from(val: &Connection) -> i32 {
170        val.clone()
171            .path
172            .rsplit('/')
173            .nth(0)
174            .unwrap()
175            .parse::<i32>()
176            .unwrap()
177    }
178}
179
180#[derive(Default, Debug, Clone, Eq, PartialEq)]
181pub struct ConnectionSettings {
182    pub kind: String, // `type` is a reserved word, so we are using `kind` instead
183    pub id: String,
184    pub uuid: String,
185    pub ssid: Ssid,
186    pub mode: String,
187}
188
189#[derive(Debug, Clone, Eq, PartialEq)]
190pub enum ConnectionState {
191    Unknown = 0,
192    Activating = 1,
193    Activated = 2,
194    Deactivating = 3,
195    Deactivated = 4,
196}
197
198impl From<i64> for ConnectionState {
199    fn from(state: i64) -> Self {
200        match state {
201            0 => ConnectionState::Unknown,
202            1 => ConnectionState::Activating,
203            2 => ConnectionState::Activated,
204            3 => ConnectionState::Deactivating,
205            4 => ConnectionState::Deactivated,
206            _ => {
207                warn!("Undefined connection state: {}", state);
208                ConnectionState::Unknown
209            },
210        }
211    }
212}
213
214pub fn get_connections(dbus_manager: &Rc<DBusNetworkManager>) -> Result<Vec<Connection>> {
215    let paths = dbus_manager.list_connections()?;
216
217    let mut connections = Vec::with_capacity(paths.len());
218
219    for path in &paths {
220        connections.push(Connection::init(dbus_manager, path)?)
221    }
222
223    connections.sort();
224
225    Ok(connections)
226}
227
228pub fn get_active_connections(dbus_manager: &Rc<DBusNetworkManager>) -> Result<Vec<Connection>> {
229    let active_paths = dbus_manager.get_active_connections()?;
230
231    let mut connections = Vec::with_capacity(active_paths.len());
232
233    for active_path in active_paths {
234        if let Some(path) = dbus_manager.get_active_connection_path(&active_path) {
235            connections.push(Connection::init(dbus_manager, &path)?)
236        }
237    }
238
239    connections.sort();
240
241    Ok(connections)
242}
243
244pub fn connect_to_access_point(
245    dbus_manager: &Rc<DBusNetworkManager>,
246    device_path: &str,
247    access_point: &AccessPoint,
248    credentials: &AccessPointCredentials,
249) -> Result<(Connection, ConnectionState)> {
250    let (path, _) = dbus_manager.connect_to_access_point(device_path, access_point, credentials)?;
251
252    let connection = Connection::init(dbus_manager, &path)?;
253
254    let state = wait(
255        &connection,
256        &ConnectionState::Activated,
257        dbus_manager.method_timeout(),
258    )?;
259
260    Ok((connection, state))
261}
262
263pub fn create_hotspot<S>(
264    dbus_manager: &Rc<DBusNetworkManager>,
265    device_path: &str,
266    interface: &str,
267    ssid: &S,
268    password: Option<&str>,
269    address: Option<Ipv4Addr>,
270) -> Result<(Connection, ConnectionState)>
271where
272    S: AsSsidSlice + ?Sized,
273{
274    let (path, _) = dbus_manager.create_hotspot(device_path, interface, ssid, password, address)?;
275
276    let connection = Connection::init(dbus_manager, &path)?;
277
278    let state = wait(
279        &connection,
280        &ConnectionState::Activated,
281        dbus_manager.method_timeout(),
282    )?;
283
284    Ok((connection, state))
285}
286
287fn get_connection_active_path(
288    dbus_manager: &DBusNetworkManager,
289    connection_path: &str,
290) -> Result<Option<String>> {
291    let active_paths = dbus_manager.get_active_connections()?;
292
293    for active_path in active_paths {
294        if let Some(settings_path) = dbus_manager.get_active_connection_path(&active_path) {
295            if connection_path == settings_path {
296                return Ok(Some(active_path));
297            }
298        }
299    }
300
301    Ok(None)
302}
303
304fn wait(
305    connection: &Connection,
306    target_state: &ConnectionState,
307    timeout: u64,
308) -> Result<ConnectionState> {
309    if timeout == 0 {
310        return connection.get_state();
311    }
312
313    debug!("Waiting for connection state: {:?}", target_state);
314
315    let mut total_time = 0;
316
317    loop {
318        ::std::thread::sleep(::std::time::Duration::from_secs(1));
319
320        let state = connection.get_state()?;
321
322        total_time += 1;
323
324        if state == *target_state {
325            debug!(
326                "Connection target state reached: {:?} / {}s elapsed",
327                state, total_time
328            );
329
330            return Ok(state);
331        } else if total_time >= timeout {
332            debug!(
333                "Timeout reached in waiting for connection state ({:?}): {:?} / {}s elapsed",
334                target_state, state, total_time
335            );
336
337            return Ok(state);
338        }
339
340        debug!(
341            "Still waiting for connection state ({:?}): {:?} / {}s elapsed",
342            target_state, state, total_time
343        );
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use super::super::NetworkManager;
350    use super::*;
351
352    #[test]
353    fn test_connection_enable_disable() {
354        let manager = NetworkManager::new();
355
356        let connections = manager.get_connections().unwrap();
357
358        // set environment variable $TEST_WIFI_SSID with the wifi's SSID that you want to test
359        // e.g.  export TEST_WIFI_SSID="Resin.io Wifi"
360        let wifi_env_var = "TEST_WIFI_SSID";
361        let connection = match ::std::env::var(wifi_env_var) {
362            Ok(ssid) => connections
363                .iter()
364                .filter(|c| c.settings().ssid.as_str().unwrap() == ssid)
365                .nth(0)
366                .unwrap()
367                .clone(),
368            Err(e) => panic!(
369                "couldn't retrieve environment variable {}: {}",
370                wifi_env_var, e
371            ),
372        };
373
374        let state = connection.get_state().unwrap();
375
376        if state == ConnectionState::Activated {
377            let state = connection.deactivate().unwrap();
378            assert_eq!(ConnectionState::Deactivated, state);
379
380            ::std::thread::sleep(::std::time::Duration::from_secs(5));
381
382            let state = connection.activate().unwrap();
383            assert_eq!(ConnectionState::Activated, state);
384
385            ::std::thread::sleep(::std::time::Duration::from_secs(5));
386        } else {
387            let state = connection.activate().unwrap();
388            assert_eq!(ConnectionState::Activated, state);
389
390            ::std::thread::sleep(::std::time::Duration::from_secs(5));
391
392            let state = connection.deactivate().unwrap();
393            assert_eq!(ConnectionState::Deactivated, state);
394
395            ::std::thread::sleep(::std::time::Duration::from_secs(5));
396        }
397    }
398}