use crate::dbus_api::active_connection::ActiveConnectionClient;
use crate::dbus_api::devices::Device;
use crate::dbus_api::devices::wireless::WirelessClient;
use crate::dbus_api::network_manager::NetworkManagerClient;
use crate::dbus_api::settings::connection::ConnectionSettingsClient;
use crate::dbus_api::settings::{NM_SETTINGS_PATH, SettingsClient};
use crate::dbus_api::{
ActiveConnectionState, NM80211ApFlags, NM80211ApSecurityFlags, NMConnection,
NMConnectionSettings, Options,
};
use crate::utils::{frequency_to_channel, parse_dbus_variant};
use log::{error, info, warn};
use std::collections::HashSet;
use std::io::{Error, ErrorKind};
use std::sync::Arc;
use std::time::{Duration, Instant};
use zbus::Connection;
use zbus::zvariant::{Array, OwnedValue, Str};
pub mod dbus_api;
mod test_all;
mod utils;
pub const NM_SERVICE: &str = "org.freedesktop.NetworkManager";
pub const NM_SERVICE_PATH: &str = "/org/freedesktop/NetworkManager";
pub async fn wireless_devices() -> Result<Vec<Device>, Error> {
all_devices().await.map(|v| {
v.into_iter()
.filter(|d| matches!(d, Device::Wireless(_)))
.collect()
})
}
pub async fn wireless_device(name: &str) -> Result<Option<Device>, Error> {
let name = name.to_owned();
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let devices = nm
.get_devices()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
for device in devices {
match &device {
Device::Wireless(wireless) => {
let interface_name = wireless
.interface()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
if interface_name.eq_ignore_ascii_case(&name) {
return Ok(Some(device));
}
}
_ => continue,
}
}
Ok(None)
}
pub async fn all_devices() -> Result<Vec<Device>, Error> {
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
nm.get_devices()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))
}
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct AccessPointInfo {
pub service_path: String,
pub ssid: String,
pub strength: u8,
pub freq: u32,
pub channel: u32,
pub flags: u32,
pub wpa_flags: u32,
pub rsn_flags: u32,
}
pub async fn scan_all_ssids(device: Device) -> Result<Vec<AccessPointInfo>, Error> {
match device {
Device::Wireless(wifi_device) => {
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
wifi_device
.simple_scan()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let mut access_points = wifi_device
.get_all_access_points(nm.connection())
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let now = Instant::now();
while access_points.is_empty() && Instant::now().duration_since(now).as_secs() < 30 {
tokio::time::sleep(Duration::from_secs(1)).await;
access_points = wifi_device
.get_all_access_points(nm.connection())
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
}
let mut ap_info = Vec::new();
for ap in access_points {
match async {
let ssid = ap.ssid().await?;
let strength = ap.strength().await?;
let freq = ap.frequency().await?;
let flags = ap.flags().await?;
let wpa_flags = ap.wpa_flags().await?;
let rsn_flags = ap.rsn_flags().await?;
let channel = frequency_to_channel(freq).unwrap_or_default();
Ok::<AccessPointInfo, Error>(AccessPointInfo {
service_path: ap.service_path().to_string(),
ssid,
strength,
freq,
channel,
flags,
wpa_flags,
rsn_flags,
})
}
.await
{
Ok(info) => ap_info.push(info),
Err(_) => continue, }
}
let mut de_duped: Vec<AccessPointInfo> = HashSet::<AccessPointInfo>::from_iter(ap_info)
.into_iter()
.collect();
de_duped.sort_by_key(|ap| ap.strength);
de_duped.reverse();
Ok(de_duped)
}
_ => Err(Error::new(ErrorKind::Other, "unsupported device type")),
}
}
pub async fn find_access_point(
device: Device,
ssid: String,
) -> Result<Option<AccessPointInfo>, Error> {
let all_ssids = scan_all_ssids(device).await?;
Ok(all_ssids
.into_iter()
.find(|access_point| !access_point.ssid.is_empty() && access_point.ssid == ssid))
}
pub async fn connection_exists(id: &str) -> Result<Option<ConnectionSettingsClient>, Error> {
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let settings = SettingsClient::new(nm.connection(), NM_SETTINGS_PATH.to_string())
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let connections = settings
.list_connections()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
for con in connections {
let settings = con
.get_settings()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
if let Some(connection_settings) = settings.get("connection") {
if let Some(val) = connection_settings.get("id") {
if parse_dbus_variant(val.clone().into()) == id {
return Ok(Some(con));
}
}
}
}
Ok(None)
}
pub async fn try_existing_connections(device: Device) -> Result<bool, Error> {
let is_wireless = matches!(device, Device::Wireless(_));
let (interface, device_service_path) = match &device {
Device::Wireless(wifi_device) => (
wifi_device
.interface()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?,
wifi_device.service_path().to_string(),
),
Device::Ethernet(wired_device) => (
wired_device
.interface()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?,
wired_device.service_path().to_string(),
),
_ => {
return Err(Error::new(ErrorKind::Other, "unsupported device type"));
}
};
info!("Trying to Connect using Device {}", device_service_path);
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let settings = SettingsClient::new(nm.connection(), NM_SETTINGS_PATH.to_string())
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let connections = settings
.list_connections()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
for con in connections {
let settings_map = con
.get_settings()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
if is_hotspot(&settings_map) {
continue;
}
let interface_name = get_setting_string(&settings_map, "connection", "interface-name");
if interface_name.as_ref() != Some(&interface) {
warn!("Skipping Connection, Not valid for Interface: {interface}");
continue;
}
let ssid = if is_wireless {
get_setting_string(&settings_map, "802-11-wireless", "ssid").unwrap_or_default()
} else {
String::new()
};
if is_wireless {
info!("Trying to Connect to {}", ssid);
} else {
info!("Trying to Connect using Device {interface}");
}
match nm
.activate_connection(con.service_path(), &device_service_path, "/")
.await
{
Ok(_) => {
if is_wireless {
info!("Successfully Connected to {ssid}");
} else {
info!("Successfully Connected using {interface}");
}
return Ok(true);
}
Err(e) => {
error!("Failed to activate Connection: {:?}", e);
}
}
}
Ok(false)
}
fn is_hotspot(settings: &NMConnectionSettings) -> bool {
match get_setting_string(settings, "802-11-wireless", "mode") {
Some(mode) => mode == "ap",
None => false,
}
}
fn get_setting_string(settings: &NMConnectionSettings, section: &str, key: &str) -> Option<String> {
if let Some(section) = settings.get(section) {
if let Some(value) = section.get(key) {
return Some(parse_dbus_variant(value.clone().into()));
}
}
None
}
pub async fn connect_to_access_point(
device: Device,
ssid: String,
password: Option<String>,
identity: Option<String>,
) -> Result<(), Error> {
match find_access_point(device.clone(), ssid.clone()).await? {
None => Err(Error::new(
ErrorKind::Other,
format!("Failed to Find Access point with SSID {ssid}"),
)),
Some(access_point) => match device {
Device::Wireless(wifi_device) => {
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
if let Some(old_connection) = wifi_device.active_connection().await? {
nm.deactivate_connection(old_connection.service_path())
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
loop {
match old_connection.state().await {
Ok(ActiveConnectionState::Deactivating) => {
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
Ok(ActiveConnectionState::Deactivated) | Err(_) => break,
_ => {
return Err(Error::new(
ErrorKind::Other,
"Failure when disconnecting old connection",
));
}
}
}
}
let ssid_string = access_point.ssid.clone();
info!("Trying to Connect to SSID: {ssid_string}");
info!("Access Point Security Info:");
info!(" Flags: 0x{:X}", access_point.flags);
info!(" WPA Flags: 0x{:X}", access_point.wpa_flags);
info!(" RSN Flags: 0x{:X}", access_point.rsn_flags);
let security_type = determine_security_type(&access_point);
info!("Detected security type: {:?}", security_type);
if security_type == SecurityType::Wpa3Sae {
info!("Attempting WPA2 Before WPA3: {:?}", security_type);
let mut err = None;
for security_type in [SecurityType::Wpa2Psk, SecurityType::Wpa3Sae] {
match connect(
&nm,
&access_point,
security_type,
ssid_string.clone(),
password.clone(),
identity.clone(),
&wifi_device,
)
.await
{
Ok(_) => return Ok(()),
Err(e) => err = Some(e),
}
}
if let Some(err) = err {
error!("Failed to Connect to {ssid_string}: {err:?}");
Err(err)
} else {
Ok(())
}
} else {
connect(
&nm,
&access_point,
security_type,
ssid_string.clone(),
password.clone(),
identity.clone(),
&wifi_device,
)
.await
}
}
_ => Err(Error::new(ErrorKind::Other, "unsupported device type")),
},
}
}
async fn connect(
nm: &NetworkManagerClient,
access_point: &AccessPointInfo,
security_type: SecurityType,
ssid: String,
password: Option<String>,
identity: Option<String>,
wifi_device: &WirelessClient,
) -> Result<(), Error> {
let mut connection = NMConnection::new();
let mut wireless_settings = Options::new();
let ssid_bytes = access_point.ssid.as_bytes().to_vec();
wireless_settings.insert(
"ssid".to_string(),
Array::from(ssid_bytes.clone())
.try_into()
.map_err(|e| Error::new(ErrorKind::Other, format!("{e:?}")))?,
);
configure_security(
&mut connection,
&mut wireless_settings,
&security_type,
password,
identity,
&ssid,
)?;
connection.insert("802-11-wireless".to_string(), wireless_settings);
let existing_connection = connection_exists(&access_point.ssid).await?;
let connection_path = if let Some(con) = existing_connection {
info!("Connection Exists: Updating... {:?}", access_point.ssid);
let mut settings = con
.get_settings()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
for (k, v) in connection {
settings.insert(k, v);
}
con.update(settings)
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
info!("Activating existing connection...");
nm.activate_connection(
con.service_path(),
wifi_device.service_path(),
&access_point.service_path,
)
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?
} else {
info!("Creating new connection: {:?}", access_point.ssid);
let mut connection_settings = Options::new();
connection_settings.insert(
"id".to_string(),
Str::from(access_point.ssid.clone()).into(),
);
connection_settings.insert("type".to_string(), Str::from("802-11-wireless").into());
connection_settings.insert("autoconnect".to_string(), OwnedValue::from(true));
connection.insert("connection".to_string(), connection_settings);
nm.add_and_activate_connection(
connection.clone(),
wifi_device.service_path(),
&access_point.service_path,
)
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?
.1
};
wait_to_activate(nm.connection(), connection_path, &ssid).await
}
#[derive(Debug, Clone, PartialOrd, PartialEq)]
enum SecurityType {
Open,
Wep,
WpaPsk,
Wpa2Psk,
Wpa3Sae,
WpaEnterprise,
Wpa2Enterprise,
Mixed, }
fn determine_security_type(access_point: &AccessPointInfo) -> SecurityType {
if access_point.flags & (NM80211ApFlags::Privacy as u32) == 0 {
return SecurityType::Open;
}
if access_point.rsn_flags & (NM80211ApSecurityFlags::KeyMgmtSae as u32) != 0 {
return SecurityType::Wpa3Sae;
}
let has_wpa_enterprise =
access_point.wpa_flags & (NM80211ApSecurityFlags::KeyMgmt802_1x as u32) != 0;
let has_wpa2_enterprise =
access_point.rsn_flags & (NM80211ApSecurityFlags::KeyMgmt802_1x as u32) != 0;
if has_wpa_enterprise && has_wpa2_enterprise {
return SecurityType::WpaEnterprise; } else if has_wpa2_enterprise {
return SecurityType::Wpa2Enterprise;
} else if has_wpa_enterprise {
return SecurityType::WpaEnterprise;
}
let has_wpa_psk = access_point.wpa_flags & (NM80211ApSecurityFlags::KeyMgmtPsk as u32) != 0;
let has_wpa2_psk = access_point.rsn_flags & (NM80211ApSecurityFlags::KeyMgmtPsk as u32) != 0;
if has_wpa_psk && has_wpa2_psk {
return SecurityType::Mixed;
} else if has_wpa2_psk {
return SecurityType::Wpa2Psk;
} else if has_wpa_psk {
return SecurityType::WpaPsk;
}
if access_point.wpa_flags == 0 && access_point.rsn_flags == 0 {
return SecurityType::Wep;
}
SecurityType::Wpa2Psk
}
fn configure_security(
connection: &mut NMConnection,
wireless_settings: &mut Options,
security_type: &SecurityType,
password: Option<String>,
identity: Option<String>,
ssid: &str,
) -> Result<(), Error> {
match security_type {
SecurityType::Open => {
info!("SSID: '{ssid}' is using Open Security (no password)");
}
SecurityType::Wep => {
info!("SSID: '{ssid}' is using WEP Security");
let password = password.ok_or_else(|| {
Error::new(ErrorKind::InvalidInput, "WEP network requires a password")
})?;
wireless_settings.insert(
"security".to_string(),
Str::from("802-11-wireless-security").into(),
);
let mut security_settings = Options::new();
security_settings.insert("key-mgmt".to_string(), Str::from("none").into());
security_settings.insert("wep-key0".to_string(), Str::from(password).into());
security_settings.insert("wep-key-type".to_string(), OwnedValue::from(1u32)); security_settings.insert("wep-tx-keyidx".to_string(), OwnedValue::from(0u32));
connection.insert("802-11-wireless-security".to_string(), security_settings);
}
SecurityType::WpaPsk | SecurityType::Wpa2Psk | SecurityType::Mixed => {
let mode_name = match security_type {
SecurityType::WpaPsk => "WPA",
SecurityType::Wpa2Psk => "WPA2",
SecurityType::Mixed => "WPA/WPA2 Mixed",
_ => unreachable!(),
};
info!("SSID: '{ssid}' is using {mode_name} PSK Security");
let password = password.ok_or_else(|| {
Error::new(
ErrorKind::InvalidInput,
format!("{mode_name} network requires a password"),
)
})?;
wireless_settings.insert(
"security".to_string(),
Str::from("802-11-wireless-security").into(),
);
let mut security_settings = Options::new();
security_settings.insert("key-mgmt".to_string(), Str::from("wpa-psk").into());
security_settings.insert("psk".to_string(), Str::from(password).into());
match security_type {
SecurityType::WpaPsk => {
security_settings.insert(
"proto".to_string(),
Array::from(vec![Str::from("wpa")])
.try_into()
.map_err(|e| Error::new(ErrorKind::Other, format!("{e:?}")))?,
);
}
SecurityType::Wpa2Psk => {
security_settings.insert(
"proto".to_string(),
Array::from(vec![Str::from("rsn")])
.try_into()
.map_err(|e| Error::new(ErrorKind::Other, format!("{e:?}")))?,
);
}
SecurityType::Mixed => {
security_settings.insert(
"proto".to_string(),
Array::from(vec![Str::from("wpa"), Str::from("rsn")])
.try_into()
.map_err(|e| Error::new(ErrorKind::Other, format!("{e:?}")))?,
);
}
_ => unreachable!(),
}
connection.insert("802-11-wireless-security".to_string(), security_settings);
}
SecurityType::Wpa3Sae => {
info!("SSID: '{ssid}' is using WPA3 SAE Security");
let password = password.ok_or_else(|| {
Error::new(ErrorKind::InvalidInput, "WPA3 network requires a password")
})?;
wireless_settings.insert(
"security".to_string(),
Str::from("802-11-wireless-security").into(),
);
let mut security_settings = Options::new();
security_settings.insert("key-mgmt".to_string(), Str::from("sae").into());
security_settings.insert("psk".to_string(), Str::from(password).into());
security_settings.insert(
"proto".to_string(),
Array::from(vec![Str::from("rsn")])
.try_into()
.map_err(|e| Error::new(ErrorKind::Other, format!("{e:?}")))?,
);
connection.insert("802-11-wireless-security".to_string(), security_settings);
}
SecurityType::WpaEnterprise | SecurityType::Wpa2Enterprise => {
let mode_name = match security_type {
SecurityType::WpaEnterprise => "WPA Enterprise",
SecurityType::Wpa2Enterprise => "WPA2 Enterprise",
_ => unreachable!(),
};
info!("SSID: '{ssid}' is using {mode_name} Security");
let password = password.ok_or_else(|| {
Error::new(
ErrorKind::InvalidInput,
"Enterprise network requires a password",
)
})?;
let identity = identity.ok_or_else(|| {
Error::new(
ErrorKind::InvalidInput,
"Enterprise network requires an identity/username",
)
})?;
wireless_settings.insert(
"security".to_string(),
Str::from("802-11-wireless-security").into(),
);
let mut security_settings = Options::new();
security_settings.insert("key-mgmt".to_string(), Str::from("wpa-eap").into());
match security_type {
SecurityType::WpaEnterprise => {
security_settings.insert(
"proto".to_string(),
Array::from(vec![Str::from("wpa")])
.try_into()
.map_err(|e| Error::new(ErrorKind::Other, format!("{e:?}")))?,
);
}
SecurityType::Wpa2Enterprise => {
security_settings.insert(
"proto".to_string(),
Array::from(vec![Str::from("rsn")])
.try_into()
.map_err(|e| Error::new(ErrorKind::Other, format!("{e:?}")))?,
);
}
_ => unreachable!(),
}
connection.insert("802-11-wireless-security".to_string(), security_settings);
let mut eap_settings = Options::new();
eap_settings.insert(
"eap".to_string(),
Array::from(vec![Str::from("peap")])
.try_into()
.map_err(|e| Error::new(ErrorKind::Other, format!("{e:?}")))?,
);
eap_settings.insert("identity".to_string(), Str::from(identity).into());
eap_settings.insert("password".to_string(), Str::from(password).into());
eap_settings.insert("phase2-auth".to_string(), Str::from("mschapv2").into());
connection.insert("802-1x".to_string(), eap_settings);
}
}
Ok(())
}
pub async fn wait_to_activate(
zconnection: Arc<Connection>,
connection_path: String,
ssid_string: &str,
) -> Result<(), Error> {
let active_connection = ActiveConnectionClient::new(zconnection, connection_path.clone())
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
info!(
"Monitoring Wifi Connection {}",
active_connection
.id()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))
.map_err(|e| Error::new(
ErrorKind::Other,
format!("Connection is no Longer Active: {e}")
))?
);
loop {
match active_connection.state().await {
Ok(ActiveConnectionState::Unknown) => {
return Err(Error::new(
ErrorKind::Other,
format!("Unknown Failure when Connecting to {ssid_string}"),
));
}
Ok(ActiveConnectionState::Activating) => {
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
Ok(ActiveConnectionState::Activated) => break,
Ok(ActiveConnectionState::Deactivating) => {
return Err(Error::new(
ErrorKind::Other,
format!("Failure when Connecting to {ssid_string}, State is Deactivating"),
));
}
Ok(ActiveConnectionState::Deactivated) => {
return Err(Error::new(
ErrorKind::Other,
format!("Failure when Connecting to {ssid_string}, State is Deactivated"),
));
}
Err(e) => {
return Err(Error::new(
ErrorKind::Other,
format!("Connection is no Longer Active: {e}"),
));
}
}
}
info!("Connection Finished");
Ok(())
}
pub async fn disconnect(device: Device) -> Result<(), Error> {
match device {
Device::Wireless(wifi_device) => wifi_device
.disconnect()
.await
.map_err(|e| Error::new(ErrorKind::Other, e)),
Device::Ethernet(ethernet) => ethernet
.disconnect()
.await
.map_err(|e| Error::new(ErrorKind::Other, e)),
_ => Err(Error::new(
ErrorKind::InvalidInput,
"invalid Device Passed to Disconect",
)),
}
}
pub static HOTSPOT_ID: &str = "HOTSPOT";
pub async fn create_hotspot(
device: Device,
id: Option<String>,
ssid: String,
password: Option<String>,
) -> Result<(), Error> {
match device {
Device::Wireless(wifi_device) => {
let id = id.unwrap_or(HOTSPOT_ID.to_string());
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let mut hotspot = NMConnection::new();
let mut wireless_settings = Options::new();
wireless_settings.insert(
"ssid".to_string(),
Array::from(ssid.as_bytes())
.try_into()
.map_err(|e| Error::new(ErrorKind::Other, format!("{e:?}")))?,
);
wireless_settings.insert("band".to_string(), Str::from("bg").into());
wireless_settings.insert("channel".to_string(), 11u32.into());
wireless_settings.insert("hidden".to_string(), false.into());
wireless_settings.insert("mode".to_string(), Str::from("ap").into());
let mut connection_settings = Options::new();
connection_settings.insert("autoconnect".to_string(), true.into());
connection_settings.insert("id".to_string(), Str::from(&id).into());
connection_settings.insert(
"interface-name".to_string(),
Str::from(
wifi_device
.interface()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?,
)
.into(),
);
connection_settings.insert("type".to_string(), Str::from("802-11-wireless").into());
let mut ipv4_settings = Options::new();
ipv4_settings.insert("method".to_string(), Str::from("shared").into());
if let Some(password) = password {
if !password.is_empty() {
wireless_settings.insert(
"security".to_string(),
Str::from("802-11-wireless-security").into(),
);
let mut security_settings = Options::new();
security_settings.insert("key-mgmt".to_string(), Str::from("wpa-psk").into());
security_settings.insert("psk".to_string(), Str::from(password).into());
security_settings.insert("pmf".to_string(), 1u32.into());
hotspot.insert("802-11-wireless-security".to_string(), security_settings);
} else {
warn!("Empty Password Provided, Ignoring");
}
}
hotspot.insert("802-11-wireless".to_string(), wireless_settings);
hotspot.insert("connection".to_string(), connection_settings);
hotspot.insert("ipv4".to_string(), ipv4_settings);
let existing_connection = connection_exists(&id).await?;
let connection_path = if let Some(con) = existing_connection {
info!("Connection Exists: Updating hotspot {:?}", id);
let mut settings = con
.get_settings()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
for (k, v) in hotspot {
settings.insert(k, v);
}
con.update(settings)
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
nm.activate_connection(con.service_path(), wifi_device.service_path(), "/")
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?
} else {
info!("Creating Hotspot: {:?}", ssid);
nm.add_and_activate_connection(hotspot, wifi_device.service_path(), "/")
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?
.1
};
let active_connection = ActiveConnectionClient::new(nm.connection(), connection_path)
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
info!(
"Monitoring Hotspot Connection {}",
active_connection
.id()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))
.map_err(|e| Error::new(
ErrorKind::Other,
format!("Connection is no Longer Active: {e}")
))?
);
loop {
match active_connection.state().await {
Ok(ActiveConnectionState::Unknown) => {
return Err(Error::new(
ErrorKind::Other,
"Unknown Failure when starting Hotspot",
));
}
Ok(ActiveConnectionState::Activating) => {
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
Ok(ActiveConnectionState::Activated) => break,
Ok(ActiveConnectionState::Deactivating) => {
return Err(Error::new(
ErrorKind::Other,
"Failure when starting Hotspot, State is Deactivating",
));
}
Ok(ActiveConnectionState::Deactivated) => {
return Err(Error::new(
ErrorKind::Other,
"Failure when starting Hotspot, State is Deactivated",
));
}
Err(e) => {
return Err(Error::new(
ErrorKind::Other,
format!("Connection is no Longer Active: {e}"),
));
}
}
}
info!("Hotspot Created Successfully");
Ok(())
}
_ => Err(Error::new(ErrorKind::Other, "unsupported device type")),
}
}
pub async fn find_active_hotspots() -> Result<Vec<ActiveConnectionClient>, Error> {
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let active_connections = nm
.active_connections()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let mut hotspot_connections = Vec::new();
for conn in active_connections {
let conn_settings = conn
.connection()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let settings = conn_settings
.get_settings()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
if is_hotspot(&settings) {
hotspot_connections.push(conn);
}
}
Ok(hotspot_connections)
}
pub async fn find_all_hotspots() -> Result<Vec<ConnectionSettingsClient>, Error> {
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let settings = SettingsClient::new(nm.connection(), NM_SETTINGS_PATH.to_string())
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let connections = settings
.list_connections()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let mut hotspot_connections = Vec::new();
for con in connections {
let settings = con
.get_settings()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
if is_hotspot(&settings) {
hotspot_connections.push(con);
}
}
Ok(hotspot_connections)
}
pub async fn is_active_connection(connection_path: String) -> Result<bool, Error> {
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let active_connection =
match ActiveConnectionClient::new(nm.connection(), connection_path.clone()).await {
Ok(conn) => conn,
Err(_) => return Ok(false),
};
match active_connection.state().await {
Ok(ActiveConnectionState::Activating) | Ok(ActiveConnectionState::Activated) => Ok(true),
_ => Ok(false),
}
}
pub async fn reset_active_connection(
active_connection: ActiveConnectionClient,
) -> Result<bool, Error> {
let nm = NetworkManagerClient::new()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let device_paths = active_connection
.devices()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let device_path = device_paths
.first()
.ok_or_else(|| Error::new(ErrorKind::NotFound, "No Device found for Active Connection"))?;
let specific_object = active_connection
.specific_object()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let conn_id = active_connection.id().await.map_err(|e| {
Error::new(
ErrorKind::Other,
format!("Connection is no Longer Active: {e}"),
)
})?;
let connection_settings = active_connection
.connection()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
info!("Waiting for Hotspot({conn_id:?}) to Shutdown");
nm.deactivate_connection(active_connection.service_path())
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
loop {
match active_connection.state().await {
Ok(ActiveConnectionState::Deactivating) => {
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
Ok(ActiveConnectionState::Deactivated) | Err(_) => break,
_ => {
return Err(Error::new(
ErrorKind::Other,
"Failure when shutting down Hotspot",
));
}
}
}
info!("Hotspot({conn_id:?}) Stopped, Restarting");
let con_path = nm
.activate_connection(
connection_settings.service_path(),
device_path,
&specific_object,
)
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
let connection = ActiveConnectionClient::new(nm.connection(), con_path)
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
loop {
match connection.state().await {
Ok(ActiveConnectionState::Activating) => {
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
Ok(ActiveConnectionState::Activated) => break,
_ => {
return Err(Error::new(
ErrorKind::Other,
"Failure when restarting Hotspot",
));
}
}
}
info!("Hotspot({conn_id:?}) Started");
Ok(true)
}
pub async fn delete_active_connection(connection: ActiveConnectionClient) -> Result<bool, Error> {
let conn_settings = connection
.connection()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
conn_settings
.delete()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
Ok(true)
}
pub async fn delete_connection(settings: ConnectionSettingsClient) -> Result<bool, Error> {
settings
.delete()
.await
.map_err(|e| Error::new(ErrorKind::Other, e.to_string()))?;
Ok(true)
}