wifi_config/
lib.rs

1use dbus::{
2    arg::Variant,
3    blocking::{stdintf::org_freedesktop_dbus::Properties, Connection},
4};
5use std::{collections::HashMap, time::Duration};
6
7/// Helper function to convert IP string to u32 in network byte order
8fn ip_to_u32(ip: &str) -> Result<u32, String> {
9    let parts: Result<Vec<u32>, _> = ip.split('.').map(|s| s.parse::<u32>()).collect();
10
11    match parts {
12        Ok(parts) if parts.len() == 4 && parts.iter().all(|&x| x <= 255) => {
13            Ok(((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]).to_be())
14        }
15        _ => Err(format!("Invalid IP address format: {}", ip)),
16    }
17}
18
19/// Sends Wi-Fi configuration to **NetworkManager** via the D-Bus system bus.
20///
21/// # Arguments
22///
23/// * `ssid` - The name of the Wi-Fi network (SSID).
24/// * `password` - The password for the Wi-Fi network.
25///
26/// # Behavior
27///
28/// - Connects to the system D-Bus and queries NetworkManager for available devices.
29/// - Searches for the first device of type `2` (which corresponds to Wi-Fi).
30/// - Builds a connection settings dictionary compatible with NetworkManager:
31///   - `802-11-wireless` (SSID, mode)
32///   - `802-11-wireless-security` (WPA-PSK with the given password)
33/// - Calls `AddAndActivateConnection` to tell NetworkManager to connect.
34///
35/// # Errors
36///
37/// - If no Wi-Fi device is found, the function prints an error to stderr and returns.
38/// - If D-Bus calls fail, the error is printed to stderr.
39///
40/// # Example
41///
42/// ```no_run
43/// use wifi_config::send_wifi_to_network_manager;
44///
45/// fn main() {
46///     send_wifi_to_network_manager("MyHomeWiFi", "supersecret123");
47/// }
48/// ```
49
50/// Sends Wi-Fi parameters to NetworkManager for connection
51pub fn send_wifi_to_network_manager(ssid: &str, password: &str) {
52    let conn = Connection::new_system().unwrap();
53    let proxy = conn.with_proxy(
54        "org.freedesktop.NetworkManager",
55        "/org/freedesktop/NetworkManager",
56        Duration::from_secs(10),
57    );
58    let connection_path = dbus::Path::new("/").unwrap();
59    let (devices,): (Vec<dbus::Path>,) = proxy
60        .method_call("org.freedesktop.NetworkManager", "GetDevices", ())
61        .unwrap();
62
63    let mut wifi_device_path: Option<dbus::Path> = None;
64
65    for device in devices {
66        let device_proxy = conn.with_proxy(
67            "org.freedesktop.NetworkManager",
68            &device,
69            Duration::from_secs(10),
70        );
71        let device_type: u32 = device_proxy
72            .get("org.freedesktop.NetworkManager.Device", "DeviceType")
73            .unwrap();
74
75        // type 2 means Wi-Fi
76        if device_type == 2 {
77            wifi_device_path = Some(device);
78            break;
79        }
80    }
81
82    if wifi_device_path.is_none() {
83        eprintln!("Wi-Fi device not found.");
84        return;
85    }
86
87    let device_path = wifi_device_path.unwrap();
88    // Wi-Fi configuration structure
89    let mut connection_settings: HashMap<&str, HashMap<&str, Variant<Box<dyn dbus::arg::RefArg>>>> =
90        HashMap::new();
91
92    // Wi-Fi settings
93    let mut wifi_settings: HashMap<&str, Variant<Box<dyn dbus::arg::RefArg>>> = HashMap::new();
94    wifi_settings.insert("ssid", Variant(Box::new(ssid.as_bytes().to_vec())));
95    wifi_settings.insert("mode", Variant(Box::new(String::from("infrastructure"))));
96    connection_settings.insert("802-11-wireless", wifi_settings);
97
98    // Wi-Fi security settings
99    let mut wifi_security: HashMap<&str, Variant<Box<dyn dbus::arg::RefArg>>> = HashMap::new();
100    wifi_security.insert("key-mgmt", Variant(Box::new(String::from("wpa-psk"))));
101    wifi_security.insert("psk", Variant(Box::new(String::from(password))));
102    connection_settings.insert("802-11-wireless-security", wifi_security);
103
104    let result: Result<(), _> = proxy.method_call(
105        "org.freedesktop.NetworkManager",
106        "AddAndActivateConnection",
107        (connection_settings, device_path, connection_path),
108    );
109
110    match result {
111        Ok(_) => println!("Wi-Fi configuration successfully sent."),
112        Err(e) => eprintln!("Failed to configure Wi-Fi: {}", e),
113    }
114}
115
116/// Sends Wi-Fi configuration with **static IPv4 settings** to **NetworkManager** via the D-Bus system bus.
117///
118/// # Arguments
119///
120/// * `ssid` - The name of the Wi-Fi network (SSID).
121/// * `password` - The password for the Wi-Fi network.
122/// * `ip` - The static IPv4 address to assign to the device (e.g. `"192.168.1.100"`).
123/// * `prefix` - The network prefix length (e.g. `24` for `255.255.255.0`).
124/// * `gateway` - The default gateway IPv4 address.
125///
126/// # Behavior
127///
128/// - Connects to the system D-Bus and queries NetworkManager for available devices.
129/// - Searches for the first device of type `2` (Wi-Fi).
130/// - Builds a connection settings dictionary compatible with NetworkManager:
131///   - `connection`: includes UUID, ID, and type `802-11-wireless`.
132///   - `802-11-wireless`: sets SSID and mode to `infrastructure`.
133///   - `802-11-wireless-security`: configures WPA-PSK using the provided password.
134///   - `ipv4`: sets `method` to `manual` and defines a static IP configuration via the `addresses` array `[ip, prefix, gateway]`.
135/// - Calls `AddAndActivateConnection` to instruct NetworkManager to add and activate the connection immediately.
136///
137/// # Errors
138///
139/// - If no Wi-Fi device is found, the function prints an error to stderr and returns.
140/// - If any IP address cannot be parsed, an error is printed and the function returns early.
141/// - If the D-Bus call to `AddAndActivateConnection` fails, the error is printed to stderr.
142///
143/// # Example
144///
145/// ```no_run
146/// use wifi_config::send_static_wifi_to_network_manager;
147///
148/// fn main() {
149///     send_static_wifi_to_network_manager(
150///         "MyOfficeWiFi",
151///         "topsecretpass",
152///         "192.168.10.50",
153///         24,
154///         "192.168.10.1",
155///     );
156/// }
157/// ```
158pub fn send_static_wifi_to_network_manager(
159    ssid: &str,
160    password: &str,
161    ip: &str,
162    prefix: u32,
163    gateway: &str,
164) {
165    // connect to the system bus
166    let conn = Connection::new_system().unwrap();
167    let proxy = conn.with_proxy(
168        "org.freedesktop.NetworkManager",
169        "/org/freedesktop/NetworkManager",
170        Duration::from_secs(10),
171    );
172
173    // find Wi-Fi device
174    let (devices,): (Vec<dbus::Path<'static>>,) = proxy
175        .method_call("org.freedesktop.NetworkManager", "GetDevices", ())
176        .unwrap();
177
178    let mut wifi_device_path: Option<dbus::Path<'static>> = None;
179    for device in devices {
180        let device_proxy = conn.with_proxy(
181            "org.freedesktop.NetworkManager",
182            &device,
183            Duration::from_secs(10),
184        );
185        let device_type: u32 = device_proxy
186            .get("org.freedesktop.NetworkManager.Device", "DeviceType")
187            .unwrap();
188
189        if device_type == 2 {
190            wifi_device_path = Some(device);
191            break;
192        }
193    }
194
195    if wifi_device_path.is_none() {
196        eprintln!("Wi-Fi device not found.");
197        return;
198    }
199
200    let device_path = wifi_device_path.unwrap();
201    let connection_path = dbus::Path::new("/").unwrap();
202
203    // Wi-Fi setting
204    let mut connection_settings: HashMap<&str, HashMap<&str, Variant<Box<dyn dbus::arg::RefArg>>>> =
205        HashMap::new();
206
207    let mut wifi_settings: HashMap<&str, Variant<Box<dyn dbus::arg::RefArg>>> = HashMap::new();
208    wifi_settings.insert("ssid", Variant(Box::new(ssid.as_bytes().to_vec())));
209    wifi_settings.insert("mode", Variant(Box::new(String::from("infrastructure"))));
210    connection_settings.insert("802-11-wireless", wifi_settings);
211
212    // Wi-Fi security
213    let mut wifi_security: HashMap<&str, Variant<Box<dyn dbus::arg::RefArg>>> = HashMap::new();
214    wifi_security.insert("key-mgmt", Variant(Box::new(String::from("wpa-psk"))));
215    wifi_security.insert("psk", Variant(Box::new(String::from(password))));
216    connection_settings.insert("802-11-wireless-security", wifi_security);
217
218    // IPv4 static configuration
219    let mut ipv4_settings: HashMap<&str, Variant<Box<dyn dbus::arg::RefArg>>> = HashMap::new();
220    ipv4_settings.insert("method", Variant(Box::new(String::from("manual"))));
221    // Convert IP addresses to u32 format
222    let ip_u32 = match ip_to_u32(ip) {
223        Ok(addr) => addr,
224        Err(e) => {
225            eprintln!("❌ {}", e);
226            return;
227        }
228    };
229    let gateway_u32 = match ip_to_u32(gateway) {
230        Ok(addr) => addr,
231        Err(e) => {
232            eprintln!("❌ {}", e);
233            return;
234        }
235    };
236
237    // Create address array: [ip, prefix, gateway]
238    let address_array: Vec<u32> = vec![ip_u32, prefix, gateway_u32];
239    let addresses: Vec<Vec<u32>> = vec![address_array];
240
241    ipv4_settings.insert("addresses", Variant(Box::new(addresses)));
242
243    connection_settings.insert("ipv4", ipv4_settings);
244
245    // send to NM
246    let result: Result<(), _> = proxy.method_call(
247        "org.freedesktop.NetworkManager",
248        "AddAndActivateConnection",
249        (connection_settings, device_path, connection_path),
250    );
251
252    match result {
253        Ok(_) => println!("Wi-Fi configuration with static IP sent."),
254        Err(e) => eprintln!("❌ Failed to connect to Wi-Fi: {}", e),
255    }
256}
257
258//test
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn test_check() {
265        assert!(true);
266    }
267}