diff --git a/Cargo.lock b/Cargo.lock index 6e4b464..256d7bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,6 +342,7 @@ dependencies = [ "regex", "reqwest", "serde", + "serde_json", "tokio", ] @@ -758,9 +759,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" +checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" dependencies = [ "itoa 1.0.1", "ryu", diff --git a/Cargo.toml b/Cargo.toml index 54a9f09..25dfb9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ plist = { version = "1.3.1" } regex = { version = "1.5.4" } reqwest = { version = "0.11", features = ["json"] } serde = { version = "1.0.132", features = ["derive"] } +serde_json = "1.0.74" tokio = { version = "1", features = ["full"] } diff --git a/src/connectors/etcnet.rs b/src/connectors/etcnet.rs new file mode 100644 index 0000000..a3fb881 --- /dev/null +++ b/src/connectors/etcnet.rs @@ -0,0 +1,33 @@ +use crate::connectors::types::{ListsNetworkInterfaces, NetworkInterface}; +use crate::connectors::utils::call_exec; +use serde::Deserialize; +use serde_json; + +#[derive(Deserialize)] +struct ENInterface { + ifname: String, + flags: Vec, +} + +impl From for NetworkInterface { + fn from(inf: ENInterface) -> Self { + NetworkInterface { + enabled: inf.flags.contains(&String::from("UP")), + machine_name: inf.ifname.clone(), + ..Default::default() + } + } +} + +pub struct EtcNetConnector {} + +impl ListsNetworkInterfaces for EtcNetConnector { + fn list_network_interfaces(&self) -> Result, String> { + let output = call_exec("ip", "ip", vec!["--json", "address"]).expect("calling ip failed"); + let ifaces: Vec = serde_json::from_slice(&output).expect("ip returned shit"); + Ok(ifaces + .into_iter() + .map(|inf| NetworkInterface::from(inf)) + .collect()) + } +} diff --git a/src/connectors/macos.rs b/src/connectors/macos.rs index 3904d92..1bbb140 100644 --- a/src/connectors/macos.rs +++ b/src/connectors/macos.rs @@ -1,10 +1,12 @@ // shell.exec("{networksetup,airport}") goes brrrr -use crate::connectors::types::{Connector, Network, NetworkInterface}; -use std::process::Command; -extern crate plist; -use serde::{Deserialize}; -use regex::{Regex}; +use crate::connectors::types::{ + Connects, ListsNetworkInterfaces, ListsNetworks, Network, NetworkInterface, +}; +use crate::connectors::utils::call_exec; +use plist; +use regex::Regex; +use serde::Deserialize; #[derive(Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] @@ -32,34 +34,26 @@ pub struct MacOSConnector {} impl MacOSConnector { fn call_airport(&self, args: Vec<&str>) -> Result, String> { - let output = Command::new("/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport") - .args(&args) - .arg("--xml") - .output() - .expect("calling airport failed"); - if !output.status.success() { - panic!("airport returned {:} status", output.status); - } - Ok(output.stdout) + let mut args = args.clone(); + args.push("--xml"); + call_exec( + "airport", + "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport", + args) } fn call_networksetup(&self, args: Vec<&str>) -> Result, String> { - let output = Command::new("/usr/sbin/networksetup") - .args(&args) - .output() - .expect("calling networksetup failed"); - match output.status.success() { - true => Ok(output.stdout), - false => Err(match output.status.code() { - Some(code) => format!("networksetup exited with {:}: {:?}", code, output.stdout), - None => format!("networksetup exited by signal"), - }), - } + call_exec("networksetup", "/usr/sbin/networksetup", args) } } -impl Connector for MacOSConnector { - fn connect_to_network(&self, iface: &NetworkInterface, net: &Network, psk: Option<&str>) -> Result { +impl Connects for MacOSConnector { + fn connect_to_network( + &self, + iface: &NetworkInterface, + net: &Network, + psk: Option<&str>, + ) -> Result { let mut args = vec!["-setairportnetwork", &iface.machine_name, &net.ssid]; if let Some(p) = psk { args.push(p); @@ -69,30 +63,39 @@ impl Connector for MacOSConnector { Err(e) => Err(e), } } +} +impl ListsNetworks for MacOSConnector { fn list_networks(&self, _iface: &NetworkInterface) -> Result, String> { // this should use the specific network interface, but airport won't let us specify it - let output = self.call_airport(vec!["-s"]) + let output = self + .call_airport(vec!["-s"]) .expect("airport idk what actually"); let res: Vec = plist::from_bytes(&output).expect("airport returned shit"); let networks = res.into_iter().map(|net| Network::from(net)).collect(); Ok(networks) } +} +impl ListsNetworkInterfaces for MacOSConnector { fn list_network_interfaces(&self) -> Result, String> { // order returns both names let output = String::from_utf8( self.call_networksetup(vec!["-listnetworkserviceorder"]) - .expect("networksetup idk what actually")) - .expect("networksetup returned non-utf8 chars???"); + .expect("networksetup idk what actually"), + ) + .expect("networksetup returned non-utf8 chars???"); if output.len() == 0 { return Err("networksetup returned nothing. Is the Wi-Fi turned on?".to_string()); } lazy_static! { - static ref RE: Regex = Regex::new(r"(?x) - \((?P\d+|\*)\)\ (?P[^\n]+)\n - \(Hardware\ Port:\ .+?,\ Device:\ (?P[a-z]+\d+)\) - ").unwrap(); + static ref RE: Regex = Regex::new( + r"(?x) + \((?P\d+|\*)\)\ (?P[^\n]+)\n + \(Hardware\ Port:\ .+?,\ Device:\ (?P[a-z]+\d+)\) + " + ) + .unwrap(); } let mut ifaces: Vec = vec![]; for iface in RE.captures_iter(&output) { diff --git a/src/connectors/mod.rs b/src/connectors/mod.rs index aadfa2c..a094409 100644 --- a/src/connectors/mod.rs +++ b/src/connectors/mod.rs @@ -1,2 +1,5 @@ +pub mod etcnet; pub mod macos; +pub mod networkmanager; pub mod types; +pub mod utils; diff --git a/src/connectors/networkmanager.rs b/src/connectors/networkmanager.rs index e69de29..53a5c7f 100644 --- a/src/connectors/networkmanager.rs +++ b/src/connectors/networkmanager.rs @@ -0,0 +1,95 @@ +use crate::connectors::types::{ + Connects, ListsNetworkInterfaces, ListsNetworks, Network, NetworkInterface, +}; +use crate::connectors::utils::call_exec; +use std::borrow::BorrowMut; +use std::str::Split; + +pub struct NetworkManagerConnector {} + +impl NetworkManagerConnector { + fn call_nmcli(&self, add_args: Vec<&str>) -> Result, String> { + let args = vec!["-t", "-m", "tabular", "-c", "no", "-o", "-e", "yes"] + .into_iter() + .chain(add_args.into_iter()) + .collect(); + call_exec("networkmanager", "nmcli", args) + } + + fn get_nmcli(&self, add_args: Vec<&str>) -> Result>, String> { + let output = self + .call_nmcli(add_args) + .expect("calling networkmanager failed"); + let res = String::from_utf8(output).expect("networkmanager returned non-utf8"); + let lines: Vec<&str> = res.split("\n").filter(|ln| !ln.is_empty()).collect(); + let lines_with_fields: Vec> = lines.into_iter().map(|line| line.split(":")).collect(); + let mut parsed: Vec> = vec![]; + for old_fields_immut in lines_with_fields.clone() { + let mut old_fields = old_fields_immut; + let mut new_fields: Vec = vec![]; + let mut continuation = false; + loop { + match old_fields.next() { + Some(field) => { + let mut new_field = match continuation { + true => new_fields.pop().expect("new field").to_string(), + false => "".to_string(), + }; + if continuation { + new_field.push(':'); + } + new_field.push_str(match field.strip_suffix("\\") { + Some(stripped) => stripped, + None => field, + }); + new_fields.push(new_field); + continuation = field.ends_with("\\"); + }, + None => { + let parsed_line = new_fields; + parsed.push(parsed_line); + break + }, + } + } + } + Ok(parsed) + } +} + +impl ListsNetworkInterfaces for NetworkManagerConnector { + fn list_network_interfaces(&self) -> Result, String> { + let iface_list = match self.get_nmcli( + vec!["-f", "DEVICE,TYPE,STATE,CONNECTION", "device"], + ) { + Ok(list) => list, + Err(err) => return Err(err), + }; + let ifaces = iface_list.into_iter().map(|ifa| NetworkInterface { + machine_name: ifa[0].clone(), + enabled: ifa[2] != "unavailable", + ..Default::default() + }).collect(); + Ok(ifaces) + } +} + +impl ListsNetworks for NetworkManagerConnector { + fn list_networks(&self, iface: &NetworkInterface) -> Result, String> { + let net_list = match self.get_nmcli(vec![ + "-f", "SSID,BSSID,CHAN,SIGNAL,WPA-FLAGS,IN-USE", + "device", "wifi", "list", "ifname", &iface.machine_name], + ) { + Ok(list) => list, + Err(err) => return Err(err), + }; + let nets = net_list.into_iter().map(|net| Network { + ssid: net[0].to_string(), + bssid: Some(net[1].to_string()), + channel: Some(net[2].parse::().unwrap()), + rssi: Some(net[3].parse::().unwrap()), + ..Default::default() + }).collect(); + Ok(nets) + } +} diff --git a/src/connectors/types.rs b/src/connectors/types.rs index f656537..0a3d31e 100644 --- a/src/connectors/types.rs +++ b/src/connectors/types.rs @@ -1,8 +1,18 @@ -pub trait Connector { - fn connect_to_network(&self, iface: &NetworkInterface, net: &Network, psk: Option<&str>) -> Result; +pub trait Connects { + fn connect_to_network( + &self, + iface: &NetworkInterface, + net: &Network, + psk: Option<&str>, + ) -> Result; +} +pub trait ListsNetworks { fn list_networks(&self, iface: &NetworkInterface) -> Result, String>; +} +pub trait ListsNetworkInterfaces { fn list_network_interfaces(&self) -> Result, String>; } +pub trait Connector: Connects + ListsNetworks + ListsNetworkInterfaces {} #[derive(Debug, Default)] pub struct Network { diff --git a/src/connectors/utils.rs b/src/connectors/utils.rs new file mode 100644 index 0000000..a939a08 --- /dev/null +++ b/src/connectors/utils.rs @@ -0,0 +1,15 @@ +use std::process::Command; + +pub fn call_exec(exec_name: &str, exec_path: &str, args: Vec<&str>) -> Result, String> { + let output = Command::new(exec_path) + .args(&args) + .output() + .expect(&format!("calling {} failed smh", exec_name)); + match output.status.success() { + true => Ok(output.stdout), + false => Err(match output.status.code() { + Some(code) => format!("{} exited with {:}: {:?}", exec_name, code, output.stdout), + None => format!("{} exited by signal", exec_name), + }), + } +} diff --git a/src/main.rs b/src/main.rs index 6af97f7..f7736c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,34 +2,46 @@ extern crate lazy_static; mod connection_check; mod connectors; -use connectors::types::{Connector}; +use crate::connectors::types::{Connects, ListsNetworkInterfaces, ListsNetworks}; #[tokio::main] async fn main() -> Result<(), Box> { // let ip4 = connection_check::ipv4_info().await?; // println!("{:?}", ip4); - let connector = connectors::macos::MacOSConnector {}; + let connector = connectors::networkmanager::NetworkManagerConnector {}; - let ifaces = connector.list_network_interfaces().expect("network interface listing errored"); + let ifaces = connector + .list_network_interfaces() + .expect("network interface listing errored"); for iface in &ifaces { println!("{:?}", iface); } - let iface = ifaces.into_iter().find(|f| f.human_name == Some("Wi-Fi".to_string())) + let iface = ifaces + .into_iter() + .find(|f| f.machine_name == "wlp2s0") .expect("Wi-Fi interface not found"); - - let networks = connector.list_networks(&iface).expect("network listing errored"); + + let networks = connector + .list_networks(&iface) + .expect("network listing errored"); for net in &networks { println!("{:?}", net); } - let network = networks.into_iter().find(|net| net.ssid == "test") + /* + let network = networks + .into_iter() + .find(|net| net.ssid == "test") .expect("network not found"); - connector.connect_to_network(&iface, &network, None).expect("could not connect to network"); + connector + .connect_to_network(&iface, &network, None) + .expect("could not connect to network"); + */ Ok(()) }