etcnet, networkmanager connectors

master
Lauren Liberda 2022-01-07 20:28:09 +01:00
parent c3732ba4bc
commit 77379b9a2e
No known key found for this signature in database
GPG Key ID: 734C629FD04BD319
9 changed files with 219 additions and 46 deletions

5
Cargo.lock generated
View File

@ -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",

View File

@ -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"] }

33
src/connectors/etcnet.rs Normal file
View File

@ -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<String>,
}
impl From<ENInterface> 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<Vec<NetworkInterface>, String> {
let output = call_exec("ip", "ip", vec!["--json", "address"]).expect("calling ip failed");
let ifaces: Vec<ENInterface> = serde_json::from_slice(&output).expect("ip returned shit");
Ok(ifaces
.into_iter()
.map(|inf| NetworkInterface::from(inf))
.collect())
}
}

View File

@ -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<Vec<u8>, 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<Vec<u8>, 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<bool, String> {
impl Connects for MacOSConnector {
fn connect_to_network(
&self,
iface: &NetworkInterface,
net: &Network,
psk: Option<&str>,
) -> Result<bool, String> {
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<Vec<Network>, 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<AirportNetwork> = 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<Vec<NetworkInterface>, 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<order>\d+|\*)\)\ (?P<human>[^\n]+)\n
\(Hardware\ Port:\ .+?,\ Device:\ (?P<unix>[a-z]+\d+)\)
").unwrap();
static ref RE: Regex = Regex::new(
r"(?x)
\((?P<order>\d+|\*)\)\ (?P<human>[^\n]+)\n
\(Hardware\ Port:\ .+?,\ Device:\ (?P<unix>[a-z]+\d+)\)
"
)
.unwrap();
}
let mut ifaces: Vec<NetworkInterface> = vec![];
for iface in RE.captures_iter(&output) {

View File

@ -1,2 +1,5 @@
pub mod etcnet;
pub mod macos;
pub mod networkmanager;
pub mod types;
pub mod utils;

View File

@ -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<Vec<u8>, 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<Vec<Vec<String>>, 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<Split<&str>> = lines.into_iter().map(|line| line.split(":")).collect();
let mut parsed: Vec<Vec<String>> = vec![];
for old_fields_immut in lines_with_fields.clone() {
let mut old_fields = old_fields_immut;
let mut new_fields: Vec<String> = 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<Vec<NetworkInterface>, 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<Vec<Network>, 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::<u8>().unwrap()),
rssi: Some(net[3].parse::<i8>().unwrap()),
..Default::default()
}).collect();
Ok(nets)
}
}

View File

@ -1,8 +1,18 @@
pub trait Connector {
fn connect_to_network(&self, iface: &NetworkInterface, net: &Network, psk: Option<&str>) -> Result<bool, String>;
pub trait Connects {
fn connect_to_network(
&self,
iface: &NetworkInterface,
net: &Network,
psk: Option<&str>,
) -> Result<bool, String>;
}
pub trait ListsNetworks {
fn list_networks(&self, iface: &NetworkInterface) -> Result<Vec<Network>, String>;
}
pub trait ListsNetworkInterfaces {
fn list_network_interfaces(&self) -> Result<Vec<NetworkInterface>, String>;
}
pub trait Connector: Connects + ListsNetworks + ListsNetworkInterfaces {}
#[derive(Debug, Default)]
pub struct Network {

15
src/connectors/utils.rs Normal file
View File

@ -0,0 +1,15 @@
use std::process::Command;
pub fn call_exec(exec_name: &str, exec_path: &str, args: Vec<&str>) -> Result<Vec<u8>, 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),
}),
}
}

View File

@ -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<dyn std::error::Error>> {
// 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(())
}