-
Notifications
You must be signed in to change notification settings - Fork 134
feat(network): add VPN connection management #1087
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
use cosmic_dbus_networkmanager::settings::{NetworkManagerSettings, connection::Settings}; | ||
use zbus::Connection; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct VpnConnection { | ||
pub name: String, | ||
pub uuid: String, | ||
} | ||
|
||
/// Load all available VPN connections from NetworkManager settings | ||
pub async fn load_vpn_connections(conn: &Connection) -> anyhow::Result<Vec<VpnConnection>> { | ||
let nm_settings = NetworkManagerSettings::new(conn).await?; | ||
let connections = nm_settings.list_connections().await?; | ||
|
||
let mut vpn_connections = Vec::new(); | ||
|
||
for connection in connections { | ||
let settings_map = match connection.get_settings().await { | ||
Ok(s) => s, | ||
Err(_) => continue, | ||
}; | ||
|
||
let settings = Settings::new(settings_map); | ||
|
||
// Check if this is a VPN connection | ||
if let Some(connection_settings) = &settings.connection { | ||
if let Some(conn_type) = &connection_settings.type_ { | ||
// VPN connections have type "vpn" or "wireguard" | ||
if conn_type == "vpn" || conn_type == "wireguard" { | ||
let name = connection_settings.id.clone().unwrap_or_else(|| "Unknown VPN".to_string()); | ||
let uuid = connection_settings.uuid.clone().unwrap_or_default(); | ||
|
||
vpn_connections.push(VpnConnection { name, uuid }); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Sort by name for consistent UI | ||
vpn_connections.sort_by(|a, b| a.name.cmp(&b.name)); | ||
|
||
Ok(vpn_connections) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
pub mod active_conns; | ||
pub mod available_vpns; | ||
pub mod available_wifi; | ||
pub mod current_networks; | ||
pub mod devices; | ||
|
@@ -34,6 +35,7 @@ use zbus::{ | |
}; | ||
|
||
use self::{ | ||
available_vpns::{VpnConnection, load_vpn_connections}, | ||
available_wifi::{AccessPoint, handle_wireless_device}, | ||
current_networks::{ActiveConnectionInfo, active_connections}, | ||
}; | ||
|
@@ -285,6 +287,123 @@ async fn start_listening( | |
}) | ||
.await; | ||
} | ||
Some(NetworkManagerRequest::ActivateVpn(uuid)) => { | ||
tracing::info!("Activating VPN with UUID: {}", uuid); | ||
let network_manager = match NetworkManager::new(&conn).await { | ||
Ok(n) => n, | ||
Err(e) => { | ||
tracing::error!("Failed to connect to NetworkManager: {:?}", e); | ||
_ = output | ||
.send(NetworkManagerEvent::RequestResponse { | ||
req: NetworkManagerRequest::ActivateVpn(uuid.clone()), | ||
|
||
success: false, | ||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(), | ||
}) | ||
.await; | ||
return State::Waiting(conn, rx); | ||
} | ||
}; | ||
|
||
let mut success = false; | ||
|
||
// Find the connection by UUID | ||
if let Ok(nm_settings) = NetworkManagerSettings::new(&conn).await { | ||
if let Ok(connections) = nm_settings.list_connections().await { | ||
for connection in connections { | ||
if let Ok(settings) = connection.get_settings().await { | ||
let settings = Settings::new(settings); | ||
if let Some(conn_settings) = &settings.connection { | ||
if conn_settings.uuid.as_ref() == Some(&uuid) { | ||
// Activate the VPN connection without a specific device | ||
// Call the D-Bus method directly since VPNs don't need a device | ||
use zbus::zvariant::ObjectPath; | ||
let empty_device = ObjectPath::try_from("/").unwrap(); | ||
|
||
match network_manager.inner() | ||
.call_method("ActivateConnection", &(connection.inner().path(), empty_device.clone(), empty_device)) | ||
.await | ||
{ | ||
Ok(_) => { | ||
tracing::info!("Successfully activated VPN: {}", uuid); | ||
success = true; | ||
} | ||
Err(e) => { | ||
tracing::error!("Failed to activate VPN {}: {:?}", uuid, e); | ||
} | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
if !success { | ||
tracing::warn!("VPN connection with UUID {} not found or failed to activate", uuid); | ||
} | ||
|
||
let state = NetworkManagerState::new(&conn).await.unwrap_or_default(); | ||
_ = output | ||
.send(NetworkManagerEvent::RequestResponse { | ||
req: NetworkManagerRequest::ActivateVpn(uuid), | ||
success, | ||
state, | ||
}) | ||
.await; | ||
} | ||
Some(NetworkManagerRequest::DeactivateVpn(name)) => { | ||
tracing::info!("Deactivating VPN: {}", name); | ||
let network_manager = match NetworkManager::new(&conn).await { | ||
Ok(n) => n, | ||
Err(e) => { | ||
tracing::error!("Failed to connect to NetworkManager: {:?}", e); | ||
_ = output | ||
.send(NetworkManagerEvent::RequestResponse { | ||
req: NetworkManagerRequest::DeactivateVpn(name.clone()), | ||
success: false, | ||
state: NetworkManagerState::new(&conn).await.unwrap_or_default(), | ||
}) | ||
.await; | ||
return State::Waiting(conn, rx); | ||
} | ||
}; | ||
|
||
let mut success = false; | ||
|
||
// Find and deactivate the active VPN connection by name | ||
if let Ok(active_connections) = network_manager.active_connections().await { | ||
for active_conn in active_connections { | ||
if let Ok(conn_id) = active_conn.id().await { | ||
if conn_id == name && active_conn.vpn().await.unwrap_or(false) { | ||
match network_manager.deactivate_connection(&active_conn).await { | ||
Ok(_) => { | ||
tracing::info!("Successfully deactivated VPN: {}", name); | ||
success = true; | ||
break; | ||
} | ||
Err(e) => { | ||
tracing::error!("Failed to deactivate VPN {}: {:?}", name, e); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
if !success { | ||
tracing::warn!("Active VPN connection '{}' not found or failed to deactivate", name); | ||
} | ||
|
||
let state = NetworkManagerState::new(&conn).await.unwrap_or_default(); | ||
_ = output | ||
.send(NetworkManagerEvent::RequestResponse { | ||
req: NetworkManagerRequest::DeactivateVpn(name), | ||
success, | ||
state, | ||
}) | ||
.await; | ||
} | ||
_ => { | ||
return State::Finished; | ||
} | ||
|
@@ -363,6 +482,8 @@ pub enum NetworkManagerRequest { | |
}, | ||
Forget(String, HwAddress), | ||
Reload, | ||
ActivateVpn(String), // UUID of VPN connection to activate | ||
DeactivateVpn(String), // Name of active VPN connection to deactivate | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
|
@@ -387,6 +508,7 @@ pub struct NetworkManagerState { | |
pub wireless_access_points: Vec<AccessPoint>, | ||
pub active_conns: Vec<ActiveConnectionInfo>, | ||
pub known_access_points: Vec<AccessPoint>, | ||
pub available_vpns: Vec<VpnConnection>, | ||
pub wifi_enabled: bool, | ||
pub airplane_mode: bool, | ||
pub connectivity: NmConnectivityState, | ||
|
@@ -398,6 +520,7 @@ impl Default for NetworkManagerState { | |
wireless_access_points: Vec::new(), | ||
active_conns: Vec::new(), | ||
known_access_points: Vec::new(), | ||
available_vpns: Vec::new(), | ||
wifi_enabled: false, | ||
airplane_mode: false, | ||
connectivity: NmConnectivityState::Unknown, | ||
|
@@ -500,6 +623,9 @@ impl NetworkManagerState { | |
self_.known_access_points = known_access_points; | ||
self_.connectivity = network_manager.connectivity().await?; | ||
|
||
// Load available VPN connections | ||
self_.available_vpns = load_vpn_connections(conn).await.unwrap_or_default(); | ||
|
||
Ok(self_) | ||
} | ||
|
||
|
@@ -508,6 +634,7 @@ impl NetworkManagerState { | |
self.active_conns = Vec::new(); | ||
self.known_access_points = Vec::new(); | ||
self.wireless_access_points = Vec::new(); | ||
self.available_vpns = Vec::new(); | ||
} | ||
|
||
async fn connect_wifi<'a>( | ||
|
Uh oh!
There was an error while loading. Please reload this page.