1use std::collections::HashMap;
2use std::sync::mpsc;
3use tauri::{plugin::PluginApi, AppHandle, Runtime};
4use uuid::Uuid;
5use zbus::names::InterfaceName;
6use zbus::zvariant::Value;
7
8use crate::error::Result;
9use crate::models::*;
10use crate::nm_helpers::NetworkManagerHelpers;
11
12impl<R: Runtime> VSKNetworkManager<'static, R> {
13 fn vpn_type_from_service_type(service_type: &str) -> VpnType {
14 match service_type {
15 "org.freedesktop.NetworkManager.openvpn" => VpnType::OpenVpn,
16 "org.freedesktop.NetworkManager.wireguard" => VpnType::WireGuard,
17 "org.freedesktop.NetworkManager.l2tp" => VpnType::L2tp,
18 "org.freedesktop.NetworkManager.pptp" => VpnType::Pptp,
19 "org.freedesktop.NetworkManager.sstp" => VpnType::Sstp,
20 "org.freedesktop.NetworkManager.strongswan" => VpnType::Ikev2,
21 "org.freedesktop.NetworkManager.fortisslvpn" => VpnType::Fortisslvpn,
22 "org.freedesktop.NetworkManager.openconnect" => VpnType::OpenConnect,
23 _ => VpnType::Generic,
24 }
25 }
26
27 fn service_type_from_vpn_type(vpn_type: &VpnType) -> &'static str {
28 match vpn_type {
29 VpnType::OpenVpn => "org.freedesktop.NetworkManager.openvpn",
30 VpnType::WireGuard => "org.freedesktop.NetworkManager.wireguard",
31 VpnType::L2tp => "org.freedesktop.NetworkManager.l2tp",
32 VpnType::Pptp => "org.freedesktop.NetworkManager.pptp",
33 VpnType::Sstp => "org.freedesktop.NetworkManager.sstp",
34 VpnType::Ikev2 => "org.freedesktop.NetworkManager.strongswan",
35 VpnType::Fortisslvpn => "org.freedesktop.NetworkManager.fortisslvpn",
36 VpnType::OpenConnect => "org.freedesktop.NetworkManager.openconnect",
37 VpnType::Generic => "org.freedesktop.NetworkManager.vpnc",
38 }
39 }
40
41 fn vpn_state_from_active_state(state: u32) -> VpnConnectionState {
42 match state {
43 1 => VpnConnectionState::Connecting,
44 2 => VpnConnectionState::Connected,
45 3 => VpnConnectionState::Disconnecting,
46 4 => VpnConnectionState::Disconnected,
47 _ => VpnConnectionState::Unknown,
48 }
49 }
50
51 fn extract_string_from_dict(
52 dict: &HashMap<String, zbus::zvariant::OwnedValue>,
53 key: &str,
54 ) -> Option<String> {
55 let value = dict.get(key)?.to_owned();
56 <zbus::zvariant::Value<'_> as Clone>::clone(&value)
57 .downcast::<String>()
58 }
59
60 fn extract_bool_from_dict(
61 dict: &HashMap<String, zbus::zvariant::OwnedValue>,
62 key: &str,
63 ) -> Option<bool> {
64 let value = dict.get(key)?.to_owned();
65 <zbus::zvariant::Value<'_> as Clone>::clone(&value)
66 .downcast::<bool>()
67 }
68
69 fn string_map_from_section(
70 settings: &HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
71 section_name: &str,
72 ) -> HashMap<String, String> {
73 let mut out = HashMap::new();
74 let section = match settings.get(section_name) {
75 Some(v) => v,
76 None => return out,
77 };
78
79 for (k, v) in section {
80 let value = <zbus::zvariant::Value<'_> as Clone>::clone(v);
81 if let Some(s) = value.downcast::<String>() {
82 out.insert(k.clone(), s);
83 }
84 }
85 out
86 }
87
88 fn list_connection_paths(&self) -> Result<Vec<zbus::zvariant::OwnedObjectPath>> {
89 let settings_proxy = zbus::blocking::Proxy::new(
90 &self.connection,
91 "org.freedesktop.NetworkManager",
92 "/org/freedesktop/NetworkManager/Settings",
93 "org.freedesktop.NetworkManager.Settings",
94 )?;
95
96 let connections: Vec<zbus::zvariant::OwnedObjectPath> =
97 settings_proxy.call("ListConnections", &())?;
98 Ok(connections)
99 }
100
101 fn get_connection_settings(
102 &self,
103 conn_path: &zbus::zvariant::OwnedObjectPath,
104 ) -> Result<HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>> {
105 let conn_proxy = zbus::blocking::Proxy::new(
106 &self.connection,
107 "org.freedesktop.NetworkManager",
108 conn_path.as_str(),
109 "org.freedesktop.NetworkManager.Settings.Connection",
110 )?;
111
112 let settings: HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>> =
113 conn_proxy.call("GetSettings", &())?;
114 Ok(settings)
115 }
116
117 fn find_connection_path_by_uuid(
118 &self,
119 uuid: &str,
120 ) -> Result<zbus::zvariant::OwnedObjectPath> {
121 let connections = self.list_connection_paths()?;
122
123 for conn_path in connections {
124 let settings = self.get_connection_settings(&conn_path)?;
125 let dict = match settings.get("connection") {
126 Some(v) => v,
127 None => continue,
128 };
129
130 if let Some(conn_uuid) = Self::extract_string_from_dict(&dict, "uuid") {
131 if conn_uuid == uuid {
132 return Ok(conn_path);
133 }
134 }
135 }
136
137 Err(crate::error::NetworkError::VpnProfileNotFound(uuid.to_string()))
138 }
139
140 fn vpn_profile_from_settings(
141 &self,
142 settings: &HashMap<String, HashMap<String, zbus::zvariant::OwnedValue>>,
143 ) -> Option<VpnProfile> {
144 let connection_dict = settings.get("connection")?;
145
146 let conn_type = Self::extract_string_from_dict(connection_dict, "type")?;
147 if conn_type != "vpn" {
148 return None;
149 }
150
151 let uuid = Self::extract_string_from_dict(connection_dict, "uuid")?;
152 let id = Self::extract_string_from_dict(connection_dict, "id")
153 .unwrap_or_else(|| uuid.clone());
154 let interface_name = Self::extract_string_from_dict(connection_dict, "interface-name");
155 let autoconnect = Self::extract_bool_from_dict(connection_dict, "autoconnect")
156 .unwrap_or(false);
157
158 let vpn_settings = Self::string_map_from_section(settings, "vpn");
159 let service_type = vpn_settings
160 .get("service-type")
161 .map(|s| s.as_str())
162 .unwrap_or("unknown");
163
164 Some(VpnProfile {
165 id,
166 uuid,
167 vpn_type: Self::vpn_type_from_service_type(service_type),
168 interface_name,
169 autoconnect,
170 editable: true,
171 last_error: None,
172 })
173 }
174
175 fn get_wifi_icon(strength: u8) -> String {
177 match strength {
178 0..=25 => "network-wireless-signal-weak-symbolic".to_string(),
179 26..=50 => "network-wireless-signal-ok-symbolic".to_string(),
180 51..=75 => "network-wireless-signal-good-symbolic".to_string(),
181 76..=100 => "network-wireless-signal-excellent-symbolic".to_string(),
182 _ => "network-wireless-signal-none-symbolic".to_string(),
183 }
184 }
185
186 fn get_wired_icon(is_connected: bool) -> String {
187 if is_connected {
188 "network-wired-symbolic".to_string()
189 } else {
190 "network-offline-symbolic".to_string()
191 }
192 }
193
194 pub async fn new(app: AppHandle<R>) -> Result<Self> {
196 let connection = zbus::blocking::Connection::system()?;
197 let proxy = zbus::blocking::fdo::PropertiesProxy::builder(&connection)
198 .destination("org.freedesktop.NetworkManager")?
199 .path("/org/freedesktop/NetworkManager")?
200 .build()?;
201
202 Ok(Self {
203 connection,
204 proxy,
205 app,
206 })
207 }
208
209 pub fn get_current_network_state(&self) -> Result<NetworkInfo> {
210 let active_connections_variant = self.proxy.get(
212 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
213 "ActiveConnections",
214 )?;
215
216 match active_connections_variant.downcast_ref() {
218 Some(Value::Array(arr)) if !arr.is_empty() => {
219 match arr[0] {
221 zbus::zvariant::Value::ObjectPath(ref path) => {
222 let properties_proxy =
225 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
226 .destination("org.freedesktop.NetworkManager")?
227 .path(path)?
228 .build()?;
229
230 let devices_variant = properties_proxy.get(
231 InterfaceName::from_static_str_unchecked(
232 "org.freedesktop.NetworkManager.Connection.Active",
233 ),
234 "Devices",
235 )?;
236
237 let device_path = match devices_variant.downcast_ref() {
239 Some(Value::Array(device_arr)) if !device_arr.is_empty() => {
240 match device_arr[0] {
241 zbus::zvariant::Value::ObjectPath(ref dev_path) => {
242 dev_path.clone()
243 }
244 _ => return Ok(NetworkInfo::default()),
245 }
246 }
247 _ => return Ok(NetworkInfo::default()),
248 };
249
250 let device_properties_proxy =
253 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
254 .destination("org.freedesktop.NetworkManager")?
255 .path(&device_path)?
256 .build()?;
257
258 let connection_type = device_properties_proxy.get(
259 InterfaceName::from_static_str_unchecked(
260 "org.freedesktop.NetworkManager.Device",
261 ),
262 "DeviceType",
263 )?;
264
265 let state_variant = properties_proxy.get(
266 InterfaceName::from_static_str_unchecked(
267 "org.freedesktop.NetworkManager.Connection.Active",
268 ),
269 "State",
270 )?;
271
272 let is_connected = match state_variant.downcast_ref() {
273 Some(zbus::zvariant::Value::U32(state)) => *state == 2, _ => false,
275 };
276
277 let connection_type_str = match connection_type.downcast_ref() {
279 Some(zbus::zvariant::Value::U32(device_type)) => match device_type {
280 1 => "Ethernet".to_string(),
281 2 => "WiFi".to_string(),
282 _ => "Unknown".to_string(),
283 },
284 _ => "Unknown".to_string(),
285 };
286
287 let mut network_info = NetworkInfo {
289 name: "Unknown".to_string(),
290 ssid: "Unknown".to_string(),
291 connection_type: connection_type_str.clone(),
292 icon: "network-offline-symbolic".to_string(),
293 ip_address: "0.0.0.0".to_string(),
294 mac_address: "00:00:00:00:00:00".to_string(),
295 signal_strength: 0,
296 security_type: WiFiSecurityType::None,
297 is_connected: is_connected && NetworkManagerHelpers::has_internet_connectivity(&self.proxy)?,
298 };
299
300 let hw_address_variant = device_properties_proxy.get(
301 InterfaceName::from_static_str_unchecked(
302 "org.freedesktop.NetworkManager.Device",
303 ),
304 "HwAddress",
305 )?;
306
307 network_info.mac_address = match hw_address_variant.downcast_ref() {
308 Some(zbus::zvariant::Value::Str(s)) => s.to_string(),
309 _ => "00:00:00:00:00:00".to_string(),
310 };
311
312 if connection_type_str == "WiFi" {
314 let wireless_properties_proxy =
317 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
318 .destination("org.freedesktop.NetworkManager")?
319 .path(&device_path)?
320 .build()?;
321
322 let active_ap_path = wireless_properties_proxy.get(
323 InterfaceName::from_static_str_unchecked(
324 "org.freedesktop.NetworkManager.Device.Wireless",
325 ),
326 "ActiveAccessPoint",
327 )?;
328
329 if let Some(zbus::zvariant::Value::ObjectPath(ap_path)) =
330 active_ap_path.downcast_ref()
331 {
332 let _ap_proxy = zbus::blocking::Proxy::new(
333 &self.connection,
334 "org.freedesktop.NetworkManager",
335 ap_path,
336 "org.freedesktop.NetworkManager.AccessPoint",
337 )?;
338
339 let ap_properties_proxy =
342 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
343 .destination("org.freedesktop.NetworkManager")?
344 .path(ap_path)?
345 .build()?;
346
347 let ssid_variant = ap_properties_proxy.get(
348 InterfaceName::from_static_str_unchecked(
349 "org.freedesktop.NetworkManager.AccessPoint",
350 ),
351 "Ssid",
352 )?;
353
354 network_info.ssid = match ssid_variant.downcast_ref() {
355 Some(zbus::zvariant::Value::Array(ssid_bytes)) => {
356 let bytes: Vec<u8> = ssid_bytes
358 .iter()
359 .filter_map(|v| {
360 if let zbus::zvariant::Value::U8(b) = v {
361 Some(*b)
362 } else {
363 None
364 }
365 })
366 .collect();
367
368 String::from_utf8_lossy(&bytes).to_string()
369 }
370 _ => "Unknown".to_string(),
371 };
372 network_info.name = network_info.ssid.clone();
373
374 let strength_variant = ap_properties_proxy.get(
376 InterfaceName::from_static_str_unchecked(
377 "org.freedesktop.NetworkManager.AccessPoint",
378 ),
379 "Strength",
380 )?;
381
382 network_info.signal_strength = match strength_variant.downcast_ref()
383 {
384 Some(zbus::zvariant::Value::U8(s)) => *s,
385 _ => 0,
386 };
387
388 network_info.icon =
390 Self::get_wifi_icon(network_info.signal_strength);
391
392 network_info.security_type = NetworkManagerHelpers::detect_security_type(&ap_properties_proxy)?;
394 }
395 } else {
396 network_info.icon = Self::get_wired_icon(network_info.is_connected);
398 }
399 let ip4_config_path = device_properties_proxy.get(
401 InterfaceName::from_static_str_unchecked(
402 "org.freedesktop.NetworkManager.Device",
403 ),
404 "Ip4Config",
405 )?;
406
407 if let Some(zbus::zvariant::Value::ObjectPath(config_path)) =
409 ip4_config_path.downcast_ref()
410 {
411 let ip_config_properties_proxy =
413 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
414 .destination("org.freedesktop.NetworkManager")?
415 .path(config_path)?
416 .build()?;
417
418 let addresses_variant = ip_config_properties_proxy.get(
419 InterfaceName::from_static_str_unchecked(
420 "org.freedesktop.NetworkManager.IP4Config",
421 ),
422 "Addresses",
423 )?;
424
425 if let Some(Value::Array(addr_arr)) = addresses_variant.downcast_ref() {
426 if let Some(Value::Array(ip_tuple)) = addr_arr.first() {
427 if ip_tuple.len() >= 1 {
428 if let Value::U32(ip_int) = &ip_tuple[0] {
429 use std::net::Ipv4Addr;
430 network_info.ip_address =
431 Ipv4Addr::from((*ip_int).to_be()).to_string();
432 }
433 }
434 }
435 }
436 }
437
438 Ok(network_info)
439 }
440 _ => Ok(NetworkInfo::default()),
441 }
442 }
443 _ => Ok(NetworkInfo::default()),
444 }
445 }
446
447 pub fn list_wifi_networks(&self) -> Result<Vec<NetworkInfo>> {
449 let devices_variant = self.proxy.get(
451 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
452 "Devices",
453 )?;
454
455 let mut networks = Vec::new();
456 let current_network = self.get_current_network_state()?;
457
458 if let Some(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
459 let device_values = devices.get();
461 for device in device_values {
462 if let zbus::zvariant::Value::ObjectPath(ref device_path) = device {
463 let device_props =
465 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
466 .destination("org.freedesktop.NetworkManager")?
467 .path(device_path)?
468 .build()?;
469
470 let device_type_variant = device_props.get(
472 InterfaceName::from_static_str_unchecked(
473 "org.freedesktop.NetworkManager.Device",
474 ),
475 "DeviceType",
476 )?;
477
478 if let Some(zbus::zvariant::Value::U32(device_type)) =
480 device_type_variant.downcast_ref()
481 {
482 if device_type == &2u32 {
483 let mac_address = match device_props
484 .get(
485 InterfaceName::from_static_str_unchecked(
486 "org.freedesktop.NetworkManager.Device",
487 ),
488 "HwAddress",
489 )?
490 .downcast_ref()
491 {
492 Some(zbus::zvariant::Value::Str(s)) => s.to_string(),
493 _ => "00:00:00:00:00:00".to_string(),
494 };
495
496 let wireless_props =
498 zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
499 .destination("org.freedesktop.NetworkManager")?
500 .path(device_path)?
501 .build()?;
502
503 let access_points_variant = wireless_props.get(
504 InterfaceName::from_static_str_unchecked(
505 "org.freedesktop.NetworkManager.Device.Wireless",
506 ),
507 "AccessPoints",
508 )?;
509
510 if let Some(zbus::zvariant::Value::Array(aps)) =
511 access_points_variant.downcast_ref()
512 {
513 let ap_values = aps.get();
515 for ap in ap_values {
516 if let zbus::zvariant::Value::ObjectPath(ref ap_path) = ap {
517 let ap_props = zbus::blocking::fdo::PropertiesProxy::builder(
518 &self.connection,
519 )
520 .destination("org.freedesktop.NetworkManager")?
521 .path(ap_path)?
522 .build()?;
523
524 let ssid_variant = ap_props.get(
526 InterfaceName::from_static_str_unchecked(
527 "org.freedesktop.NetworkManager.AccessPoint",
528 ),
529 "Ssid",
530 )?;
531
532 let ssid = match ssid_variant.downcast_ref() {
533 Some(zbus::zvariant::Value::Array(ssid_bytes)) => {
534 let bytes: Vec<u8> = ssid_bytes
536 .iter()
537 .filter_map(|v| {
538 if let zbus::zvariant::Value::U8(b) = v {
539 Some(*b)
540 } else {
541 None
542 }
543 })
544 .collect();
545
546 String::from_utf8_lossy(&bytes).to_string()
547 }
548 _ => "Unknown".to_string(),
549 };
550
551 let strength_variant = ap_props.get(
553 InterfaceName::from_static_str_unchecked(
554 "org.freedesktop.NetworkManager.AccessPoint",
555 ),
556 "Strength",
557 )?;
558
559 let strength = match strength_variant.downcast_ref() {
560 Some(zbus::zvariant::Value::U8(s)) => *s,
561 _ => 0,
562 };
563
564 let security_type = NetworkManagerHelpers::detect_security_type(&ap_props)?;
566
567 let is_connected = current_network.ssid == ssid;
568
569 let network_info = NetworkInfo {
570 name: ssid.clone(),
571 ssid,
572 connection_type: "wifi".to_string(),
573 icon: Self::get_wifi_icon(strength),
574 ip_address: if is_connected {
575 current_network.ip_address.clone()
576 } else {
577 "0.0.0.0".to_string()
578 },
579 mac_address: mac_address.clone(),
580 signal_strength: strength,
581 security_type,
582 is_connected,
583 };
584
585 if !networks.iter().any(|n: &NetworkInfo| n.ssid == network_info.ssid) {
586 networks.push(network_info);
587 }
588 }
589 }
590 }
591 }
592 }
593 }
594 }
595 }
596
597 networks.sort_by(|a, b| b.signal_strength.cmp(&a.signal_strength));
599
600 Ok(networks)
601 }
602
603 pub fn rescan_wifi(&self) -> Result<Vec<NetworkInfo>> {
605 let devices_variant = self.proxy.get(
606 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
607 "Devices",
608 )?;
609
610 let mut wifi_device_found = false;
611 let mut requested_scan = false;
612
613 if let Some(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
614 for device in devices.get() {
615 if let zbus::zvariant::Value::ObjectPath(ref device_path) = device {
616 let device_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
617 .destination("org.freedesktop.NetworkManager")?
618 .path(device_path)?
619 .build()?;
620
621 let device_type_variant = device_props.get(
622 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager.Device"),
623 "DeviceType",
624 )?;
625
626 if let Some(zbus::zvariant::Value::U32(device_type)) = device_type_variant.downcast_ref() {
627 if *device_type == 2 {
628 wifi_device_found = true;
629
630 let wireless_proxy = zbus::blocking::Proxy::new(
631 &self.connection,
632 "org.freedesktop.NetworkManager",
633 device_path.as_str(),
634 "org.freedesktop.NetworkManager.Device.Wireless",
635 )?;
636
637 let options: HashMap<String, zbus::zvariant::OwnedValue> = HashMap::new();
638 if wireless_proxy.call::<_, _, ()>("RequestScan", &(options,)).is_ok() {
639 requested_scan = true;
640 }
641 }
642 }
643 }
644 }
645 }
646
647 if !wifi_device_found {
648 return Err(crate::error::NetworkError::OperationError(
649 "No wireless device available for scanning".to_string(),
650 ));
651 }
652
653 if !requested_scan {
654 return Err(crate::error::NetworkError::OperationError(
655 "Failed to request WiFi scan on available wireless devices".to_string(),
656 ));
657 }
658
659 self.list_wifi_networks()
660 }
661
662 pub fn connect_to_wifi(&self, config: WiFiConnectionConfig) -> Result<()> {
664 log::debug!("connect_to_wifi called: ssid='{}' security={:?} username={:?}",
666 config.ssid, config.security_type, config.username);
667
668 let mut connection_settings = HashMap::new();
670 let mut wifi_settings = HashMap::new();
671 let mut security_settings = HashMap::new();
672
673 let mut connection = HashMap::new();
675 connection.insert("id".to_string(), Value::from(config.ssid.clone()));
676 connection.insert("type".to_string(), Value::from("802-11-wireless"));
677 connection_settings.insert("connection".to_string(), connection);
678
679 wifi_settings.insert("ssid".to_string(), Value::from(config.ssid.clone()));
681 wifi_settings.insert("mode".to_string(), Value::from("infrastructure"));
682
683 match config.security_type {
685 WiFiSecurityType::None => {
686 }
688 WiFiSecurityType::Wep => {
689 security_settings.insert("key-mgmt".to_string(), Value::from("none"));
690 if let Some(password) = config.password.clone() {
691 security_settings.insert("wep-key0".to_string(), Value::from(password));
692 }
693 }
694 WiFiSecurityType::WpaPsk => {
695 security_settings.insert("key-mgmt".to_string(), Value::from("wpa-psk"));
696 if let Some(password) = config.password.clone() {
697 security_settings.insert("psk".to_string(), Value::from(password));
698 }
699 }
700 WiFiSecurityType::WpaEap => {
701 security_settings.insert("key-mgmt".to_string(), Value::from("wpa-eap"));
702 if let Some(password) = config.password.clone() {
703 security_settings.insert("password".to_string(), Value::from(password));
704 }
705 if let Some(username) = config.username.clone() {
706 security_settings.insert("identity".to_string(), Value::from(username));
707 }
708 }
709 WiFiSecurityType::Wpa2Psk => {
710 security_settings.insert("key-mgmt".to_string(), Value::from("wpa-psk"));
711 security_settings.insert("proto".to_string(), Value::from("rsn"));
712 if let Some(password) = config.password.clone() {
713 security_settings.insert("psk".to_string(), Value::from(password));
714 }
715 }
716 WiFiSecurityType::Wpa3Psk => {
717 security_settings.insert("key-mgmt".to_string(), Value::from("sae"));
718 if let Some(password) = config.password.clone() {
719 security_settings.insert("psk".to_string(), Value::from(password));
720 }
721 }
722 }
723
724 connection_settings.insert("802-11-wireless".to_string(), wifi_settings);
725 connection_settings.insert("802-11-wireless-security".to_string(), security_settings);
726
727 log::trace!("connection_settings: {:#?}", connection_settings);
729
730 let nm_proxy = zbus::blocking::Proxy::new(
732 &self.connection,
733 "org.freedesktop.NetworkManager",
734 "/org/freedesktop/NetworkManager",
735 "org.freedesktop.NetworkManager",
736 )?;
737
738 let call_result: zbus::Result<(zbus::zvariant::OwnedObjectPath, zbus::zvariant::OwnedObjectPath)> = nm_proxy.call("AddAndActivateConnection", &(connection_settings, "/", "/"));
740
741 match call_result {
742 Ok((conn_path, active_path)) => {
743 log::info!(
744 "AddAndActivateConnection succeeded for ssid='{}' conn='{}' active='{}'",
745 config.ssid,
746 conn_path.as_str(),
747 active_path.as_str()
748 );
749 }
750 Err(e) => {
751 log::error!(
752 "AddAndActivateConnection failed for ssid='{}': {:?}",
753 config.ssid,
754 e
755 );
756 return Err(e.into());
757 }
758 }
759
760 log::debug!("connect_to_wifi finished for ssid='{}'", config.ssid);
761
762 Ok(())
763 }
764
765 pub fn toggle_network_state(&self, enabled: bool) -> Result<bool> {
767 let nm_proxy = zbus::blocking::Proxy::new(
768 &self.connection,
769 "org.freedesktop.NetworkManager",
770 "/org/freedesktop/NetworkManager",
771 "org.freedesktop.NetworkManager",
772 )?;
773
774 nm_proxy.set_property("NetworkingEnabled", enabled)?;
775
776 let current_state: bool = nm_proxy.get_property("NetworkingEnabled")?;
777 Ok(current_state)
778 }
779
780 pub fn get_wireless_enabled(&self) -> Result<bool> {
782 let nm_proxy = zbus::blocking::Proxy::new(
783 &self.connection,
784 "org.freedesktop.NetworkManager",
785 "/org/freedesktop/NetworkManager",
786 "org.freedesktop.NetworkManager",
787 )?;
788 Ok(nm_proxy.get_property("WirelessEnabled")?)
789 }
790
791 pub fn set_wireless_enabled(&self, enabled: bool) -> Result<()> {
793 let nm_proxy = zbus::blocking::Proxy::new(
794 &self.connection,
795 "org.freedesktop.NetworkManager",
796 "/org/freedesktop/NetworkManager",
797 "org.freedesktop.NetworkManager",
798 )?;
799 nm_proxy.set_property("WirelessEnabled", enabled)?;
800 Ok(())
801 }
802
803 pub fn is_wireless_available(&self) -> Result<bool> {
805 let devices_variant = self.proxy.get(
807 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
808 "Devices",
809 )?;
810
811 if let Some(zbus::zvariant::Value::Array(devices)) = devices_variant.downcast_ref() {
812 let device_values = devices.get();
813 for device in device_values {
814 if let zbus::zvariant::Value::ObjectPath(ref device_path) = device {
815 let device_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
816 .destination("org.freedesktop.NetworkManager")?
817 .path(device_path)?
818 .build()?;
819
820 let device_type_variant = device_props.get(
821 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager.Device"),
822 "DeviceType",
823 )?;
824
825 if let Some(zbus::zvariant::Value::U32(device_type)) = device_type_variant.downcast_ref() {
826 if device_type == &2u32 { return Ok(true);
828 }
829 }
830 }
831 }
832 }
833 Ok(false)
834 }
835
836 pub fn listen_network_changes(&self) -> Result<mpsc::Receiver<NetworkInfo>> {
838 let (tx, rx) = mpsc::channel();
839 let connection_clone = self.connection.clone();
840 let app_handle = self.app.clone();
841
842 std::thread::spawn(move || {
844 match zbus::blocking::Connection::system() {
845 Ok(conn) => {
846 if let Ok(proxy) = zbus::blocking::Proxy::new(
848 &conn,
849 "org.freedesktop.NetworkManager",
850 "/org/freedesktop/NetworkManager",
851 "org.freedesktop.NetworkManager",
852 ) {
853 if let Ok(mut signal) = proxy.receive_signal("StateChanged") {
854 while let Some(_msg) = signal.next() {
855 let network_manager = VSKNetworkManager {
856 connection: connection_clone.clone(),
857 proxy: zbus::blocking::fdo::PropertiesProxy::builder(
858 &connection_clone,
859 )
860 .destination("org.freedesktop.NetworkManager")
861 .unwrap()
862 .path("/org/freedesktop/NetworkManager")
863 .unwrap()
864 .build()
865 .unwrap(),
866 app: app_handle.clone(),
867 };
868
869 if let Ok(network_info) =
870 network_manager.get_current_network_state()
871 {
872 if tx.send(network_info).is_err() {
873 break;
874 }
875 }
876 }
877 }
878 }
879 }
880 Err(e) => {
881 eprintln!(
882 "Error al conectar con D-Bus para escuchar cambios de red: {:?}",
883 e
884 );
885 }
886 }
887 });
888
889 Ok(rx)
890 }
891
892 pub fn disconnect_from_wifi(&self) -> Result<()> {
894 let _current_state = self.get_current_network_state()?;
896
897 let nm_proxy = zbus::blocking::Proxy::new(
899 &self.connection,
900 "org.freedesktop.NetworkManager",
901 "/org/freedesktop/NetworkManager",
902 "org.freedesktop.NetworkManager",
903 )?;
904
905 let active_connections_variant: zbus::zvariant::OwnedValue = self.proxy.get(
907 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
908 "ActiveConnections",
909 )?;
910
911 let active_connections = match active_connections_variant.downcast_ref() {
913 Some(zbus::zvariant::Value::Array(arr)) => arr
914 .iter()
915 .filter_map(|v| match v {
916 zbus::zvariant::Value::ObjectPath(path) => {
917 Some(zbus::zvariant::OwnedObjectPath::from(path.to_owned()))
918 }
919 _ => None,
920 })
921 .collect::<Vec<zbus::zvariant::OwnedObjectPath>>(),
922 _ => Vec::new(),
923 };
924
925 if !active_connections.is_empty() {
926 nm_proxy.call::<_, _, ()>("DeactivateConnection", &(active_connections[0].as_str()))?;
927 Ok(())
928 } else {
929 Ok(())
930 }
931 }
932
933 pub fn get_saved_wifi_networks(&self) -> Result<Vec<NetworkInfo>> {
935 let settings_proxy = zbus::blocking::Proxy::new(
937 &self.connection,
938 "org.freedesktop.NetworkManager",
939 "/org/freedesktop/NetworkManager/Settings",
940 "org.freedesktop.NetworkManager.Settings",
941 )?;
942
943 let connections: Vec<zbus::zvariant::OwnedObjectPath> =
945 settings_proxy.call("ListConnections", &())?;
946 let mut saved_networks = Vec::new();
947
948 for conn_path in connections {
950 let conn_proxy = zbus::blocking::Proxy::new(
952 &self.connection,
953 "org.freedesktop.NetworkManager",
954 conn_path.as_str(),
955 "org.freedesktop.NetworkManager.Settings.Connection",
956 )?;
957
958 let settings: std::collections::HashMap<String, zbus::zvariant::OwnedValue> =
960 conn_proxy.call("GetSettings", &())?;
961
962 if let Some(connection) = settings.get("connection") {
964 let connection_value = connection.to_owned();
965 let connection_dict =
966 match <zbus::zvariant::Value<'_> as Clone>::clone(&connection_value)
967 .downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>(
968 ) {
969 Some(dict) => dict,
970 _ => continue,
971 };
972
973 if let Some(conn_type) = connection_dict.get("type") {
975 let conn_type_value = conn_type.to_owned();
976 let conn_type_str =
977 match <zbus::zvariant::Value<'_> as Clone>::clone(&conn_type_value)
978 .downcast::<String>()
979 {
980 Some(s) => s,
981 _ => continue,
982 };
983
984 if conn_type_str == "802-11-wireless" {
986 let mut network_info = NetworkInfo::default();
987 network_info.connection_type = "wifi".to_string();
988
989 if let Some(id) = connection_dict.get("id") {
991 let id_value = id.to_owned();
992 if let Some(name) =
993 <zbus::zvariant::Value<'_> as Clone>::clone(&id_value)
994 .downcast::<String>()
995 {
996 network_info.name = name;
997 }
998 }
999
1000 if let Some(wireless) = settings.get("802-11-wireless") {
1002 let wireless_value = wireless.to_owned();
1003 let wireless_dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&wireless_value).downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>() {
1004 Some(dict) => dict,
1005 _ => continue,
1006 };
1007
1008 if let Some(ssid) = wireless_dict.get("ssid") {
1009 let ssid_value = ssid.to_owned();
1010 if let Some(ssid_bytes) =
1011 <zbus::zvariant::Value<'_> as Clone>::clone(&ssid_value)
1012 .downcast::<Vec<u8>>()
1013 {
1014 if let Ok(ssid_str) = String::from_utf8(ssid_bytes) {
1015 network_info.ssid = ssid_str;
1016 }
1017 }
1018 }
1019 }
1020
1021 if let Some(security) = settings.get("802-11-wireless-security") {
1023 let security_value = security.to_owned();
1024 let security_dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&security_value).downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>() {
1025 Some(dict) => dict,
1026 _ => {
1027 network_info.security_type = WiFiSecurityType::None;
1028 saved_networks.push(network_info);
1029 continue;
1030 },
1031 };
1032
1033 if let Some(key_mgmt) = security_dict.get("key-mgmt") {
1034 let key_mgmt_value = key_mgmt.to_owned();
1035 if let Some(key_mgmt_str) =
1036 <zbus::zvariant::Value<'_> as Clone>::clone(&key_mgmt_value)
1037 .downcast::<String>()
1038 {
1039 match key_mgmt_str.as_str() {
1040 "none" => {
1041 network_info.security_type = WiFiSecurityType::None
1042 }
1043 "wpa-psk" => {
1044 network_info.security_type = WiFiSecurityType::WpaPsk
1045 }
1046 "wpa-eap" => {
1047 network_info.security_type = WiFiSecurityType::WpaEap
1048 }
1049 _ => network_info.security_type = WiFiSecurityType::None,
1050 }
1051 }
1052 }
1053 } else {
1054 network_info.security_type = WiFiSecurityType::None;
1055 }
1056
1057 saved_networks.push(network_info);
1059 }
1060 }
1061 }
1062 }
1063
1064 Ok(saved_networks)
1065 }
1066
1067 pub fn delete_wifi_connection(&self, ssid: &str) -> Result<bool> {
1069 let settings_proxy = zbus::blocking::Proxy::new(
1071 &self.connection,
1072 "org.freedesktop.NetworkManager",
1073 "/org/freedesktop/NetworkManager/Settings",
1074 "org.freedesktop.NetworkManager.Settings",
1075 )?;
1076
1077 let connections: Vec<zbus::zvariant::OwnedObjectPath> =
1079 settings_proxy.call("ListConnections", &())?;
1080
1081 for conn_path in connections {
1083 let conn_proxy = zbus::blocking::Proxy::new(
1085 &self.connection,
1086 "org.freedesktop.NetworkManager",
1087 conn_path.as_str(),
1088 "org.freedesktop.NetworkManager.Settings.Connection",
1089 )?;
1090
1091 let settings: std::collections::HashMap<String, zbus::zvariant::OwnedValue> =
1093 conn_proxy.call("GetSettings", &())?;
1094
1095 if let Some(connection) = settings.get("connection") {
1097 let connection_value = connection.to_owned();
1098 let connection_dict =
1099 match <zbus::zvariant::Value<'_> as Clone>::clone(&connection_value)
1100 .downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>(
1101 ) {
1102 Some(dict) => dict,
1103 _ => continue,
1104 };
1105
1106 if let Some(conn_type) = connection_dict.get("type") {
1108 let conn_type_value = conn_type.to_owned();
1109 let conn_type_str =
1110 match <zbus::zvariant::Value<'_> as Clone>::clone(&conn_type_value)
1111 .downcast::<String>()
1112 {
1113 Some(s) => s,
1114 _ => continue,
1115 };
1116
1117 if conn_type_str == "802-11-wireless" {
1119 if let Some(wireless) = settings.get("802-11-wireless") {
1120 let wireless_value = wireless.to_owned();
1121 let wireless_dict = match <zbus::zvariant::Value<'_> as Clone>::clone(&wireless_value).downcast::<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>() {
1122 Some(dict) => dict,
1123 _ => continue,
1124 };
1125
1126 if let Some(ssid_value) = wireless_dict.get("ssid") {
1127 let ssid_owned = ssid_value.to_owned();
1128 if let Some(ssid_bytes) =
1129 <zbus::zvariant::Value<'_> as Clone>::clone(&ssid_owned)
1130 .downcast::<Vec<u8>>()
1131 {
1132 if let Ok(conn_ssid_str) = String::from_utf8(ssid_bytes) {
1133 if conn_ssid_str == ssid {
1135 conn_proxy.call::<_, _, ()>("Delete", &())?;
1136 return Ok(true);
1137 }
1138 }
1139 }
1140 }
1141 }
1142 }
1143 }
1144 }
1145 }
1146
1147 Ok(false)
1149 }
1150
1151 pub fn list_vpn_profiles(&self) -> Result<Vec<VpnProfile>> {
1153 let connections = self.list_connection_paths()?;
1154 let mut profiles = Vec::new();
1155
1156 for conn_path in connections {
1157 let settings = self.get_connection_settings(&conn_path)?;
1158 if let Some(profile) = self.vpn_profile_from_settings(&settings) {
1159 profiles.push(profile);
1160 }
1161 }
1162
1163 profiles.sort_by(|a, b| a.id.cmp(&b.id));
1164 Ok(profiles)
1165 }
1166
1167 pub fn get_vpn_status(&self) -> Result<VpnStatus> {
1169 let active_connections_variant = self.proxy.get(
1170 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
1171 "ActiveConnections",
1172 )?;
1173
1174 let mut status = VpnStatus::default();
1175
1176 if let Some(zbus::zvariant::Value::Array(arr)) = active_connections_variant.downcast_ref() {
1177 for value in arr.iter() {
1178 let active_path = match value {
1179 zbus::zvariant::Value::ObjectPath(path) => path,
1180 _ => continue,
1181 };
1182
1183 let active_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1184 .destination("org.freedesktop.NetworkManager")?
1185 .path(active_path)?
1186 .build()?;
1187
1188 let conn_type_variant = active_props.get(
1189 InterfaceName::from_static_str_unchecked(
1190 "org.freedesktop.NetworkManager.Connection.Active",
1191 ),
1192 "Type",
1193 )?;
1194
1195 let conn_type = match conn_type_variant.downcast_ref() {
1196 Some(zbus::zvariant::Value::Str(v)) => v.to_string(),
1197 _ => continue,
1198 };
1199
1200 if conn_type != "vpn" {
1201 continue;
1202 }
1203
1204 let state_variant = active_props.get(
1205 InterfaceName::from_static_str_unchecked(
1206 "org.freedesktop.NetworkManager.Connection.Active",
1207 ),
1208 "State",
1209 )?;
1210 let state = match state_variant.downcast_ref() {
1211 Some(zbus::zvariant::Value::U32(v)) => *v,
1212 _ => 0,
1213 };
1214
1215 let id_variant = active_props.get(
1216 InterfaceName::from_static_str_unchecked(
1217 "org.freedesktop.NetworkManager.Connection.Active",
1218 ),
1219 "Id",
1220 )?;
1221 let uuid_variant = active_props.get(
1222 InterfaceName::from_static_str_unchecked(
1223 "org.freedesktop.NetworkManager.Connection.Active",
1224 ),
1225 "Uuid",
1226 )?;
1227
1228 status.state = Self::vpn_state_from_active_state(state);
1229 status.active_profile_name = match id_variant.downcast_ref() {
1230 Some(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1231 _ => None,
1232 };
1233 status.active_profile_id = status.active_profile_name.clone();
1234 status.active_profile_uuid = match uuid_variant.downcast_ref() {
1235 Some(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1236 _ => None,
1237 };
1238
1239 let ip4_config_variant = active_props.get(
1240 InterfaceName::from_static_str_unchecked(
1241 "org.freedesktop.NetworkManager.Connection.Active",
1242 ),
1243 "Ip4Config",
1244 )?;
1245
1246 if let Some(zbus::zvariant::Value::ObjectPath(ip4_path)) =
1247 ip4_config_variant.downcast_ref()
1248 {
1249 if ip4_path.as_str() != "/" {
1250 let ip4_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1251 .destination("org.freedesktop.NetworkManager")?
1252 .path(ip4_path)?
1253 .build()?;
1254
1255 if let Ok(gateway_variant) = ip4_props.get(
1256 InterfaceName::from_static_str_unchecked(
1257 "org.freedesktop.NetworkManager.IP4Config",
1258 ),
1259 "Gateway",
1260 ) {
1261 status.gateway = match gateway_variant.downcast_ref() {
1262 Some(zbus::zvariant::Value::Str(v)) => Some(v.to_string()),
1263 _ => None,
1264 };
1265 }
1266 }
1267 }
1268
1269 return Ok(status);
1270 }
1271 }
1272
1273 Ok(status)
1274 }
1275
1276 pub fn connect_vpn(&self, uuid: String) -> Result<()> {
1278 let current_status = self.get_vpn_status()?;
1279 if current_status.state == VpnConnectionState::Connected
1280 && current_status.active_profile_uuid.as_deref() == Some(uuid.as_str())
1281 {
1282 return Err(crate::error::NetworkError::VpnAlreadyConnected(uuid));
1283 }
1284
1285 let conn_path = self.find_connection_path_by_uuid(&uuid)?;
1286
1287 let nm_proxy = zbus::blocking::Proxy::new(
1288 &self.connection,
1289 "org.freedesktop.NetworkManager",
1290 "/org/freedesktop/NetworkManager",
1291 "org.freedesktop.NetworkManager",
1292 )?;
1293
1294 let activate_result: zbus::Result<zbus::zvariant::OwnedObjectPath> =
1295 nm_proxy.call("ActivateConnection", &(conn_path.as_str(), "/", "/"));
1296
1297 match activate_result {
1298 Ok(_) => Ok(()),
1299 Err(e) => {
1300 let msg = e.to_string().to_lowercase();
1301 if msg.contains("secret") || msg.contains("authentication") {
1302 Err(crate::error::NetworkError::VpnAuthFailed(e.to_string()))
1303 } else {
1304 Err(crate::error::NetworkError::VpnActivationFailed(e.to_string()))
1305 }
1306 }
1307 }
1308 }
1309
1310 pub fn disconnect_vpn(&self, uuid: Option<String>) -> Result<()> {
1312 let active_connections_variant = self.proxy.get(
1313 InterfaceName::from_static_str_unchecked("org.freedesktop.NetworkManager"),
1314 "ActiveConnections",
1315 )?;
1316
1317 let mut target_active_connection: Option<zbus::zvariant::OwnedObjectPath> = None;
1318
1319 if let Some(zbus::zvariant::Value::Array(arr)) = active_connections_variant.downcast_ref() {
1320 for value in arr.iter() {
1321 let active_path = match value {
1322 zbus::zvariant::Value::ObjectPath(path) => {
1323 zbus::zvariant::OwnedObjectPath::from(path.to_owned())
1324 }
1325 _ => continue,
1326 };
1327
1328 let active_props = zbus::blocking::fdo::PropertiesProxy::builder(&self.connection)
1329 .destination("org.freedesktop.NetworkManager")?
1330 .path(active_path.as_str())?
1331 .build()?;
1332
1333 let conn_type_variant = active_props.get(
1334 InterfaceName::from_static_str_unchecked(
1335 "org.freedesktop.NetworkManager.Connection.Active",
1336 ),
1337 "Type",
1338 )?;
1339 let conn_type = match conn_type_variant.downcast_ref() {
1340 Some(zbus::zvariant::Value::Str(v)) => v.to_string(),
1341 _ => continue,
1342 };
1343
1344 if conn_type != "vpn" {
1345 continue;
1346 }
1347
1348 if let Some(target_uuid) = uuid.as_deref() {
1349 let uuid_variant = active_props.get(
1350 InterfaceName::from_static_str_unchecked(
1351 "org.freedesktop.NetworkManager.Connection.Active",
1352 ),
1353 "Uuid",
1354 )?;
1355 let active_uuid = match uuid_variant.downcast_ref() {
1356 Some(zbus::zvariant::Value::Str(v)) => v.to_string(),
1357 _ => continue,
1358 };
1359
1360 if active_uuid == target_uuid {
1361 target_active_connection = Some(active_path.clone());
1362 break;
1363 }
1364 } else {
1365 target_active_connection = Some(active_path.clone());
1366 break;
1367 }
1368 }
1369 }
1370
1371 let target_active_connection = match target_active_connection {
1372 Some(path) => path,
1373 None => {
1374 if let Some(target_uuid) = uuid {
1375 return Err(crate::error::NetworkError::VpnProfileNotFound(target_uuid));
1376 }
1377 return Err(crate::error::NetworkError::VpnNotActive);
1378 }
1379 };
1380
1381 let nm_proxy = zbus::blocking::Proxy::new(
1382 &self.connection,
1383 "org.freedesktop.NetworkManager",
1384 "/org/freedesktop/NetworkManager",
1385 "org.freedesktop.NetworkManager",
1386 )?;
1387
1388 nm_proxy.call::<_, _, ()>(
1389 "DeactivateConnection",
1390 &(target_active_connection.as_str(),),
1391 )?;
1392 Ok(())
1393 }
1394
1395 pub fn create_vpn_profile(&self, config: VpnCreateConfig) -> Result<VpnProfile> {
1397 if config.id.trim().is_empty() {
1398 return Err(crate::error::NetworkError::VpnInvalidConfig(
1399 "id is required".to_string(),
1400 ));
1401 }
1402
1403 let uuid = Uuid::new_v4().to_string();
1404 let mut connection_section = HashMap::new();
1405 connection_section.insert("id".to_string(), Value::from(config.id.clone()));
1406 connection_section.insert("uuid".to_string(), Value::from(uuid.clone()));
1407 connection_section.insert("type".to_string(), Value::from("vpn"));
1408 connection_section.insert(
1409 "autoconnect".to_string(),
1410 Value::from(config.autoconnect.unwrap_or(false)),
1411 );
1412
1413 let mut vpn_section = HashMap::new();
1414 vpn_section.insert(
1415 "service-type".to_string(),
1416 Value::from(Self::service_type_from_vpn_type(&config.vpn_type)),
1417 );
1418
1419 if let Some(username) = config.username {
1420 vpn_section.insert("user-name".to_string(), Value::from(username));
1421 }
1422 if let Some(gateway) = config.gateway {
1423 vpn_section.insert("remote".to_string(), Value::from(gateway));
1424 }
1425 if let Some(ca_cert_path) = config.ca_cert_path {
1426 vpn_section.insert("ca".to_string(), Value::from(ca_cert_path));
1427 }
1428 if let Some(user_cert_path) = config.user_cert_path {
1429 vpn_section.insert("cert".to_string(), Value::from(user_cert_path));
1430 }
1431 if let Some(private_key_path) = config.private_key_path {
1432 vpn_section.insert("key".to_string(), Value::from(private_key_path));
1433 }
1434 if let Some(private_key_password) = config.private_key_password {
1435 vpn_section.insert("key-password".to_string(), Value::from(private_key_password));
1436 }
1437 if let Some(custom_settings) = config.settings {
1438 for (k, v) in custom_settings {
1439 vpn_section.insert(k, Value::from(v));
1440 }
1441 }
1442
1443 let mut vpn_secrets_section = HashMap::new();
1444 if let Some(password) = config.password {
1445 vpn_secrets_section.insert("password".to_string(), Value::from(password));
1446 }
1447 if let Some(custom_secrets) = config.secrets {
1448 for (k, v) in custom_secrets {
1449 vpn_secrets_section.insert(k, Value::from(v));
1450 }
1451 }
1452
1453 let mut settings: HashMap<String, HashMap<String, Value>> = HashMap::new();
1454 settings.insert("connection".to_string(), connection_section);
1455 settings.insert("vpn".to_string(), vpn_section);
1456 if !vpn_secrets_section.is_empty() {
1457 settings.insert("vpn-secrets".to_string(), vpn_secrets_section);
1458 }
1459
1460 let settings_proxy = zbus::blocking::Proxy::new(
1461 &self.connection,
1462 "org.freedesktop.NetworkManager",
1463 "/org/freedesktop/NetworkManager/Settings",
1464 "org.freedesktop.NetworkManager.Settings",
1465 )?;
1466
1467 let _created_path: zbus::zvariant::OwnedObjectPath =
1468 settings_proxy.call("AddConnection", &(settings,))?;
1469
1470 Ok(VpnProfile {
1471 id: config.id,
1472 uuid,
1473 vpn_type: config.vpn_type,
1474 interface_name: None,
1475 autoconnect: config.autoconnect.unwrap_or(false),
1476 editable: true,
1477 last_error: None,
1478 })
1479 }
1480
1481 pub fn update_vpn_profile(&self, config: VpnUpdateConfig) -> Result<VpnProfile> {
1483 let conn_path = self.find_connection_path_by_uuid(&config.uuid)?;
1484 let existing_settings = self.get_connection_settings(&conn_path)?;
1485
1486 let existing_profile = self
1487 .vpn_profile_from_settings(&existing_settings)
1488 .ok_or_else(|| crate::error::NetworkError::VpnProfileNotFound(config.uuid.clone()))?;
1489
1490 let existing_vpn_settings = Self::string_map_from_section(&existing_settings, "vpn");
1491 let existing_vpn_secrets = Self::string_map_from_section(&existing_settings, "vpn-secrets");
1492
1493 let mut settings: HashMap<String, HashMap<String, Value>> = HashMap::new();
1496 for (section_name, dict) in &existing_settings {
1497 let mut section_map: HashMap<String, Value> = HashMap::new();
1498 for (k, v) in dict {
1499 section_map.insert(k.clone(), <zbus::zvariant::Value<'_> as Clone>::clone(v));
1500 }
1501 settings.insert(section_name.clone(), section_map);
1502 }
1503
1504 let connection_section = settings
1505 .entry("connection".to_string())
1506 .or_insert_with(HashMap::new);
1507 connection_section.insert(
1508 "id".to_string(),
1509 Value::from(config.id.clone().unwrap_or(existing_profile.id.clone())),
1510 );
1511 connection_section.insert("uuid".to_string(), Value::from(config.uuid.clone()));
1512 connection_section.insert("type".to_string(), Value::from("vpn"));
1513 connection_section.insert(
1514 "autoconnect".to_string(),
1515 Value::from(config.autoconnect.unwrap_or(existing_profile.autoconnect)),
1516 );
1517
1518 let service_type = existing_vpn_settings
1519 .get("service-type")
1520 .cloned()
1521 .unwrap_or_else(|| {
1522 Self::service_type_from_vpn_type(&existing_profile.vpn_type).to_string()
1523 });
1524
1525 let vpn_section = settings
1526 .entry("vpn".to_string())
1527 .or_insert_with(HashMap::new);
1528 vpn_section.insert("service-type".to_string(), Value::from(service_type));
1529
1530 let mut merged_settings = existing_vpn_settings;
1531 if let Some(username) = config.username {
1532 merged_settings.insert("user-name".to_string(), username);
1533 }
1534 if let Some(gateway) = config.gateway {
1535 merged_settings.insert("remote".to_string(), gateway);
1536 }
1537 if let Some(ca_cert_path) = config.ca_cert_path {
1538 merged_settings.insert("ca".to_string(), ca_cert_path);
1539 }
1540 if let Some(user_cert_path) = config.user_cert_path {
1541 merged_settings.insert("cert".to_string(), user_cert_path);
1542 }
1543 if let Some(private_key_path) = config.private_key_path {
1544 merged_settings.insert("key".to_string(), private_key_path);
1545 }
1546 if let Some(private_key_password) = config.private_key_password {
1547 merged_settings.insert("key-password".to_string(), private_key_password);
1548 }
1549 if let Some(custom_settings) = config.settings {
1550 for (k, v) in custom_settings {
1551 merged_settings.insert(k, v);
1552 }
1553 }
1554
1555 for (k, v) in merged_settings {
1556 vpn_section.insert(k, Value::from(v));
1557 }
1558
1559 let mut merged_secrets = existing_vpn_secrets;
1560 if let Some(password) = config.password {
1561 merged_secrets.insert("password".to_string(), password);
1562 }
1563 if let Some(custom_secrets) = config.secrets {
1564 for (k, v) in custom_secrets {
1565 merged_secrets.insert(k, v);
1566 }
1567 }
1568
1569 if merged_secrets.is_empty() {
1570 settings.remove("vpn-secrets");
1571 } else {
1572 let vpn_secrets_section = settings
1573 .entry("vpn-secrets".to_string())
1574 .or_insert_with(HashMap::new);
1575 vpn_secrets_section.clear();
1576 for (k, v) in merged_secrets {
1577 vpn_secrets_section.insert(k, Value::from(v));
1578 }
1579 }
1580
1581 let conn_proxy = zbus::blocking::Proxy::new(
1582 &self.connection,
1583 "org.freedesktop.NetworkManager",
1584 conn_path.as_str(),
1585 "org.freedesktop.NetworkManager.Settings.Connection",
1586 )?;
1587 conn_proxy.call::<_, _, ()>("Update", &(settings,))?;
1588
1589 let updated_settings = self.get_connection_settings(&conn_path)?;
1590 self.vpn_profile_from_settings(&updated_settings)
1591 .ok_or_else(|| crate::error::NetworkError::VpnProfileNotFound(config.uuid))
1592 }
1593
1594 pub fn delete_vpn_profile(&self, uuid: String) -> Result<()> {
1596 let conn_path = self.find_connection_path_by_uuid(&uuid)?;
1597
1598 let conn_proxy = zbus::blocking::Proxy::new(
1599 &self.connection,
1600 "org.freedesktop.NetworkManager",
1601 conn_path.as_str(),
1602 "org.freedesktop.NetworkManager.Settings.Connection",
1603 )?;
1604
1605 conn_proxy.call::<_, _, ()>("Delete", &())?;
1606 Ok(())
1607 }
1608}
1609
1610#[cfg(test)]
1611mod tests {
1612 use super::*;
1613
1614 #[test]
1615 fn vpn_state_deactivated_maps_to_disconnected() {
1616 assert_eq!(
1617 VSKNetworkManager::<tauri::Wry>::vpn_state_from_active_state(4),
1618 VpnConnectionState::Disconnected
1619 );
1620 }
1621}
1622
1623pub async fn init(
1625 app: &AppHandle<tauri::Wry>,
1626 _api: PluginApi<tauri::Wry, ()>,
1627) -> Result<VSKNetworkManager<'static, tauri::Wry>> {
1628 Ok(VSKNetworkManager::new(app.clone()).await?)
1629}