From 0c24922eaea5429e3a1fd0a60380b3bbc0cb5660 Mon Sep 17 00:00:00 2001 From: Justin Kilpatrick Date: Sun, 22 Sep 2024 14:37:44 -0400 Subject: [PATCH 1/4] Refactor kernel interface to remove mock and global var When a unit testing scheme was originally designed for the kernel interface module in 2017 it was turned into a stateful struct to accomodate a mock test environment that allowed the tests to call the real functions and specify what exact commands they expected to be run. This structure has not been maintained, new tests are not written in that style and instead use separate testing functions. But the original tests have not been modified much since that time. Leaving Rita with a KI global variable that in production doesn't actually do anything all to support mocking during tests. This commit removes the mock structure and turns kernel interface into just a grab bag of utility functions where tests functions are all split out internally rather than relying on mock. While we are touching all of these files it will also be renamed to system_interface rather than kernel interface as we now interface with a lot more than just linux kernel functions from this module. --- Cargo.lock | 2 - althea_kernel_interface/src/babel.rs | 8 +- althea_kernel_interface/src/bridge_tools.rs | 14 +- althea_kernel_interface/src/check_cron.rs | 25 +- althea_kernel_interface/src/counter.rs | 519 +++--------- althea_kernel_interface/src/create_wg_key.rs | 81 +- althea_kernel_interface/src/delete_tunnel.rs | 37 - althea_kernel_interface/src/dns.rs | 44 +- .../src/exit_client_tunnel.rs | 438 +++++----- .../src/exit_server_tunnel.rs | 530 ++++++------ althea_kernel_interface/src/fs_sync.rs | 16 +- althea_kernel_interface/src/get_neighbors.rs | 71 +- althea_kernel_interface/src/hardware_info.rs | 14 +- .../src/interface_tools.rs | 510 ++++++------ althea_kernel_interface/src/ip_addr.rs | 330 ++++---- althea_kernel_interface/src/ip_neigh.rs | 42 +- althea_kernel_interface/src/ip_route.rs | 436 +++------- althea_kernel_interface/src/iptables.rs | 70 +- althea_kernel_interface/src/is_openwrt.rs | 21 +- althea_kernel_interface/src/lib.rs | 180 ++--- .../src/link_local_tools.rs | 254 +++--- althea_kernel_interface/src/manipulate_uci.rs | 251 +++--- althea_kernel_interface/src/netfilter.rs | 591 +++++++------- althea_kernel_interface/src/netns.rs | 108 ++- althea_kernel_interface/src/open_tunnel.rs | 310 +++---- althea_kernel_interface/src/openwrt_ubus.rs | 26 +- althea_kernel_interface/src/ping_check.rs | 54 +- .../src/set_system_password.rs | 51 +- althea_kernel_interface/src/setup_wg_if.rs | 318 ++++---- althea_kernel_interface/src/time.rs | 36 +- .../src/traffic_control.rs | 756 +++++++++--------- .../src/udp_socket_table.rs | 37 +- althea_kernel_interface/src/upgrade.rs | 127 ++- .../src/wg_iface_counter.rs | 153 ++-- antenna_forwarding_client/Cargo.toml | 1 - antenna_forwarding_client/src/lib.rs | 20 +- clu/src/lib.rs | 20 +- integration_tests/src/mutli_exit.rs | 20 +- integration_tests/src/setup_utils/babel.rs | 4 +- integration_tests/src/setup_utils/database.rs | 53 +- .../src/setup_utils/namespaces.rs | 62 +- integration_tests/src/utils.rs | 74 +- rita_bin/Cargo.toml | 1 - rita_bin/src/client.rs | 15 +- rita_client/src/dashboard/bandwidth_limit.rs | 5 +- rita_client/src/dashboard/devices_on_lan.rs | 16 +- rita_client/src/dashboard/exits.rs | 17 +- rita_client/src/dashboard/router.rs | 8 +- rita_client/src/exit_manager/exit_loop.rs | 10 +- rita_client/src/exit_manager/time_sync.rs | 6 +- rita_client/src/exit_manager/utils.rs | 23 +- rita_client/src/heartbeat/mod.rs | 4 +- rita_client/src/operator_update/mod.rs | 8 +- rita_client/src/operator_update/tests.rs | 10 +- rita_client/src/operator_update/updater.rs | 18 +- rita_client/src/rita_loop/mod.rs | 51 +- rita_client/src/self_rescue.rs | 20 +- rita_client/src/traffic_watcher/mod.rs | 9 +- rita_common/src/dashboard/auth.rs | 11 +- rita_common/src/dashboard/interfaces.rs | 90 ++- rita_common/src/dashboard/logging.rs | 9 +- rita_common/src/dashboard/remote_access.rs | 11 +- rita_common/src/dashboard/wifi.rs | 53 +- rita_common/src/debt_keeper/mod.rs | 6 +- rita_common/src/lib.rs | 7 - rita_common/src/payment_validator/mod.rs | 6 +- rita_common/src/peer_listener/mod.rs | 7 +- rita_common/src/rita_loop/fast_loop.rs | 11 +- rita_common/src/rita_loop/slow_loop.rs | 10 +- .../src/simulated_txfee_manager/mod.rs | 6 +- rita_common/src/traffic_watcher/mod.rs | 23 +- .../src/tunnel_manager/contact_peers.rs | 4 +- rita_common/src/tunnel_manager/gc.rs | 4 +- rita_common/src/tunnel_manager/mod.rs | 33 +- rita_common/src/tunnel_manager/shaping.rs | 5 +- rita_exit/src/database/geoip.rs | 6 +- rita_exit/src/database/mod.rs | 61 +- rita_exit/src/operator_update/mod.rs | 4 +- rita_exit/src/rita_loop/mod.rs | 15 +- rita_exit/src/traffic_watcher/mod.rs | 11 +- rita_extender/src/lib.rs | 9 +- settings/src/lib.rs | 30 +- 82 files changed, 3186 insertions(+), 4181 deletions(-) delete mode 100644 althea_kernel_interface/src/delete_tunnel.rs diff --git a/Cargo.lock b/Cargo.lock index 379070ac9..300166b20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,7 +446,6 @@ dependencies = [ "crypto_box", "env_logger", "get_if_addrs", - "lazy_static", "log", "oping", "rand", @@ -3044,7 +3043,6 @@ dependencies = [ "hex-literal", "ipnetwork", "jemallocator", - "lazy_static", "log", "openssl", "openssl-probe", diff --git a/althea_kernel_interface/src/babel.rs b/althea_kernel_interface/src/babel.rs index 9cf877160..821a46532 100644 --- a/althea_kernel_interface/src/babel.rs +++ b/althea_kernel_interface/src/babel.rs @@ -1,7 +1,5 @@ -use crate::KernelInterface; +use crate::run_command; -impl dyn KernelInterface { - pub fn restart_babel(&self) { - let _res = self.run_command("/etc/init.d/babeld", &["restart"]); - } +pub fn restart_babel() { + let _res = run_command("/etc/init.d/babeld", &["restart"]); } diff --git a/althea_kernel_interface/src/bridge_tools.rs b/althea_kernel_interface/src/bridge_tools.rs index 1dd331d73..81ce463a2 100644 --- a/althea_kernel_interface/src/bridge_tools.rs +++ b/althea_kernel_interface/src/bridge_tools.rs @@ -1,15 +1,13 @@ //! Simple helper functions for brctl -use super::KernelInterface; +use crate::run_command; use crate::KernelInterfaceError as Error; use std::process::Output; -impl dyn KernelInterface { - pub fn add_if_to_bridge(&self, br: &str, iface: &str) -> Result { - self.run_command("brctl", &["addif", br, iface]) - } +pub fn add_if_to_bridge(br: &str, iface: &str) -> Result { + run_command("brctl", &["addif", br, iface]) +} - pub fn del_if_from_bridge(&self, br: &str, iface: &str) -> Result { - self.run_command("brctl", &["delif", br, iface]) - } +pub fn del_if_from_bridge(br: &str, iface: &str) -> Result { + run_command("brctl", &["delif", br, iface]) } diff --git a/althea_kernel_interface/src/check_cron.rs b/althea_kernel_interface/src/check_cron.rs index ddfc32579..889fb0019 100644 --- a/althea_kernel_interface/src/check_cron.rs +++ b/althea_kernel_interface/src/check_cron.rs @@ -1,19 +1,16 @@ -use super::KernelInterface; use crate::KernelInterfaceError as Error; use std::process::{Command, Stdio}; -impl dyn KernelInterface { - /// Checks if the cron service is running and starts it if it's not - pub fn check_cron(&self) -> Result<(), Error> { - Command::new("/etc/init.d/cron") - .args(["enable"]) - .stdout(Stdio::piped()) - .output()?; - Command::new("/etc/init.d/cron") - .args(["start"]) - .stdout(Stdio::piped()) - .output()?; +/// Checks if the cron service is running and starts it if it's not +pub fn check_cron() -> Result<(), Error> { + Command::new("/etc/init.d/cron") + .args(["enable"]) + .stdout(Stdio::piped()) + .output()?; + Command::new("/etc/init.d/cron") + .args(["start"]) + .stdout(Stdio::piped()) + .output()?; - Ok(()) - } + Ok(()) } diff --git a/althea_kernel_interface/src/counter.rs b/althea_kernel_interface/src/counter.rs index cb26b0a84..c3c6a2900 100644 --- a/althea_kernel_interface/src/counter.rs +++ b/althea_kernel_interface/src/counter.rs @@ -1,5 +1,6 @@ -use super::KernelInterface; -use crate::KernelInterfaceError as Error; +use crate::iptables::add_iptables_rule; +use crate::netfilter::{does_nftables_exist, nft_init_counters}; +use crate::{run_command, KernelInterfaceError as Error}; use regex::Regex; use std::collections::HashMap; use std::net::IpAddr; @@ -131,406 +132,132 @@ add zxcv 1234:5678:9801:2345:6789:0123:4567:8902,wg0 packets 123456789 bytes 987 } } -impl dyn KernelInterface { - pub fn init_counter(&self, target: &FilterTarget) -> Result<(), Error> { - if self.does_nftables_exist() { - info!("Trying to init a counter!"); - self.nft_init_counters(target.set_name(), target.chain(), target.nft_interface())?; - } else { - self.run_command( - "ipset", - &[ - "create", - target.set_name(), - "hash:net,iface", - "family", - "inet6", - "counters", - ], - )?; - self.add_iptables_rule( - "ip6tables", - &[ - "-w", - "-I", - target.table(), - "1", - "-m", - "set", - "!", - "--match-set", - target.set_name(), - &format!("dst,{}", target.interface()), - "-j", - "SET", - "--add-set", - target.set_name(), - &format!("dst,{}", target.interface()), - ], - )?; - } - - Ok(()) +pub fn init_counter(target: &FilterTarget) -> Result<(), Error> { + if does_nftables_exist() { + info!("Trying to init a counter!"); + nft_init_counters(target.set_name(), target.chain(), target.nft_interface())?; + } else { + run_command( + "ipset", + &[ + "create", + target.set_name(), + "hash:net,iface", + "family", + "inet6", + "counters", + ], + )?; + add_iptables_rule( + "ip6tables", + &[ + "-w", + "-I", + target.table(), + "1", + "-m", + "set", + "!", + "--match-set", + target.set_name(), + &format!("dst,{}", target.interface()), + "-j", + "SET", + "--add-set", + target.set_name(), + &format!("dst,{}", target.interface()), + ], + )?; } - fn parse_nft_set_counters( - &self, - set_name: &str, - ) -> Result, Error> { - let mut ret_map = HashMap::new(); - let out = self.run_command("nft", &["list", "set", "inet", "fw4", set_name])?; - // flush the list immediately to not missing accounting for any bytes - self.run_command("nft", &["flush", "set", "inet", "fw4", set_name])?; - - let out = out.stdout; - let out = String::from_utf8(out).expect("fix command"); - for line in out.lines() { - if line.contains("packets") { - let ret = line.replace("elements = { ", ""); - let mut ret = ret.split_ascii_whitespace(); - - // line is in the form: - // ff02::1:6 . \"wg0\" counter packets 3 bytes 204, - - let ip_addr = IpAddr::from_str(ret.next().unwrap_or(""))?; - - ret.next(); - - let iface = ret.next(); - let iface = if let Some(iface) = iface { - iface.replace('\"', "") - } else { - return Err(Error::ParseError(format!( - "No interface to parse for counter string {:?}", - line - ))); - }; - - ret.next(); - ret.next(); - let packets: u64 = ret.next().unwrap_or("").parse()?; - - ret.next(); - let bytes: u64 = ret.next().unwrap_or("").replace(',', "").parse()?; - - let total_bytes = (packets * 40) + bytes; - - trace!( - "ipaddr, iface, packets, bytes, total: {:?}, {:?} {:?}, {:?}, {:?}", - ip_addr, - iface, - packets, - bytes, - total_bytes - ); - - ret_map.insert((ip_addr, iface), total_bytes); - } - } + Ok(()) +} - Ok(ret_map) - } +fn parse_nft_set_counters(set_name: &str) -> Result, Error> { + let mut ret_map = HashMap::new(); + let out = run_command("nft", &["list", "set", "inet", "fw4", set_name])?; + // flush the list immediately to not missing accounting for any bytes + run_command("nft", &["flush", "set", "inet", "fw4", set_name])?; + + let out = out.stdout; + let out = String::from_utf8(out).expect("fix command"); + for line in out.lines() { + if line.contains("packets") { + let ret = line.replace("elements = { ", ""); + let mut ret = ret.split_ascii_whitespace(); + + // line is in the form: + // ff02::1:6 . \"wg0\" counter packets 3 bytes 204, + + let ip_addr = IpAddr::from_str(ret.next().unwrap_or(""))?; + + ret.next(); + + let iface = ret.next(); + let iface = if let Some(iface) = iface { + iface.replace('\"', "") + } else { + return Err(Error::ParseError(format!( + "No interface to parse for counter string {:?}", + line + ))); + }; + + ret.next(); + ret.next(); + let packets: u64 = ret.next().unwrap_or("").parse()?; + + ret.next(); + let bytes: u64 = ret.next().unwrap_or("").replace(',', "").parse()?; + + let total_bytes = (packets * 40) + bytes; + + trace!( + "ipaddr, iface, packets, bytes, total: {:?}, {:?} {:?}, {:?}, {:?}", + ip_addr, + iface, + packets, + bytes, + total_bytes + ); - pub fn read_counters( - &self, - target: &FilterTarget, - ) -> Result, Error> { - if self.does_nftables_exist() { - self.parse_nft_set_counters(target.set_name()) - } else { - self.run_command( - "ipset", - &[ - "create", - &format!("tmp_{}", target.set_name()), - "hash:net,iface", - "family", - "inet6", - "counters", - ], - )?; - - self.run_command( - "ipset", - &[ - "swap", - &format!("tmp_{}", target.set_name()), - target.set_name(), - ], - )?; - - let output = - self.run_command("ipset", &["save", &format!("tmp_{}", target.set_name())])?; - let res = parse_ipset(&String::from_utf8(output.stdout)?); - trace!("ipset parsed into {:?}", res); - - self.run_command("ipset", &["destroy", &format!("tmp_{}", target.set_name())])?; - res + ret_map.insert((ip_addr, iface), total_bytes); } } -} -#[test] -fn test_init_counter() { - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - - use crate::KI; - - let mut counter = 0; - - KI.set_mock(Box::new(move |program, args| { - counter += 1; - if KI.get_kernel_is_v4()? { - match counter { - 1 => { - assert_eq!(program, "ipset"); - assert_eq!( - args, - vec![ - "create", - "rita_input", - "hash:net,iface", - "family", - "inet6", - "counters", - ] - ); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 2 => { - assert_eq!(program, "ip6tables"); - assert_eq!( - args, - vec![ - "-w", - "-C", - "INPUT", - "-m", - "set", - "!", - "--match-set", - "rita_input", - "dst,src", - "-j", - "SET", - "--add-set", - "rita_input", - "dst,src", - ] - ); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - - _ => panic!("Unexpected call {} {:?} {:?}", counter, program, args), - } - } else { - match counter { - 1 => { - assert_eq!(program, "nft"); - assert_eq!(args, &["-v"]); - Ok(Output { - stdout: b"nftables v1.0.1 (Fearless Fosdick #3)".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 2 => { - assert_eq!(program, "nft"); - assert_eq!(args, &["list", "set", "inet", "fw4", "rita_input"]); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 3 => { - assert_eq!(program, "nft"); - assert_eq!( - args, - &[ - "add", - "set", - "inet", - "fw4", - "rita_input", - "{", - "type", - "ipv6_addr", - ".", - "ifname;", - "flags", - "dynamic;", - "counter;", - "size", - "65535;", - "}", - ] - ); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 4 => { - assert_eq!(program, "nft"); - assert_eq!( - args, - &[ - "insert", - "rule", - "inet", - "fw4", - "input", - "ip6", - "daddr", - ".", - "meta", - "iifname", - "@rita_input", - ] - ); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("Unexpected call {} {:?} {:?}", counter, program, args), - } - } - })); - KI.init_counter(&FilterTarget::Input) - .expect("Unable to init counter"); + Ok(ret_map) } -#[test] -fn test_read_counters() { - use std::net::Ipv6Addr; - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - - use crate::KI; - - let mut counter = 0; - - KI.set_mock(Box::new(move |program, args| { - counter += 1; - if KI.get_kernel_is_v4()? { - match counter { - 1 => { - assert_eq!(program, "ipset"); - assert_eq!( - args, - vec![ - "create", - "tmp_rita_input", - "hash:net,iface", - "family", - "inet6", - "counters", - ] - ); - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 2 => { - assert_eq!(program, "ipset"); - assert_eq!(args, vec!["swap", "tmp_rita_input", "rita_input"]); - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 3 => { - assert_eq!(program, "ipset"); - assert_eq!(args, vec!["save", "tmp_rita_input"]); - Ok(Output { - stdout: b" - add xxx fd00::dead:beef,wg42 packets 111 bytes 222 - " - .to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 4 => { - assert_eq!(program, "ipset"); - assert_eq!(args, vec!["destroy", "tmp_rita_input"]); - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("Unexpected call {} {:?} {:?}", counter, program, args), - } - } else { - match counter { - 1 => { - assert_eq!(program, "nft"); - assert_eq!(args, &["-v"]); - Ok(Output { - stdout: b"nftables v1.0.1 (Fearless Fosdick #3)".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 2 => { - assert_eq!(program, "nft"); - assert_eq!(args, &["list", "set", "inet", "fw4", "rita_input"]); - Ok(Output { - stdout: b" - fd00::dead:beef . \"wg42\" counter packets 111 bytes 222 - " - .to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 3 => { - assert_eq!(program, "nft"); - assert_eq!(args, &["flush", "set", "inet", "fw4", "rita_input"]); - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("Unexpected call {} {:?} {:?}", counter, program, args), - } - } - })); - - let result = KI - .read_counters(&FilterTarget::Input) - .expect("Unable to read values"); - assert_eq!(result.len(), 1); - - let value = result - .get(&( - IpAddr::V6(Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0xdead, 0xbeef)), - "wg42".into(), - )) - .expect("Unable to find key"); - assert_eq!(value, &(222u64 + 111u64 * 40)); + +pub fn read_counters(target: &FilterTarget) -> Result, Error> { + if does_nftables_exist() { + parse_nft_set_counters(target.set_name()) + } else { + run_command( + "ipset", + &[ + "create", + &format!("tmp_{}", target.set_name()), + "hash:net,iface", + "family", + "inet6", + "counters", + ], + )?; + + run_command( + "ipset", + &[ + "swap", + &format!("tmp_{}", target.set_name()), + target.set_name(), + ], + )?; + + let output = run_command("ipset", &["save", &format!("tmp_{}", target.set_name())])?; + let res = parse_ipset(&String::from_utf8(output.stdout)?); + trace!("ipset parsed into {:?}", res); + + run_command("ipset", &["destroy", &format!("tmp_{}", target.set_name())])?; + res + } } diff --git a/althea_kernel_interface/src/create_wg_key.rs b/althea_kernel_interface/src/create_wg_key.rs index ff107a8ca..a2cb1342b 100644 --- a/althea_kernel_interface/src/create_wg_key.rs +++ b/althea_kernel_interface/src/create_wg_key.rs @@ -1,4 +1,3 @@ -use super::KernelInterface; use crate::KernelInterfaceError as Error; use althea_types::wg_key::WgKey; use std::fs::File; @@ -13,45 +12,43 @@ pub struct WgKeypair { pub private: WgKey, } -impl dyn KernelInterface { - pub fn create_wg_key(&self, path: &Path, private_key: &WgKey) -> Result<(), Error> { - trace!("Overwriting old private key file"); - let mut priv_key_file = File::create(path)?; - write!(priv_key_file, "{private_key}")?; - Ok(()) - } - - pub fn create_wg_keypair(&self) -> Result { - let genkey = Command::new("wg") - .args(["genkey"]) - .stdout(Stdio::piped()) - .output() - .expect("Are you sure wireguard is installed on this device?"); - - let mut pubkey = Command::new("wg") - .args(["pubkey"]) - .stdout(Stdio::piped()) - .stdin(Stdio::piped()) - .spawn() - .expect("Are you sure wireguard is installed on this device?"); - - pubkey - .stdin - .as_mut() - .unwrap() - .write_all(&genkey.stdout) - .expect("Failure generating wg keypair!"); - let output = pubkey.wait_with_output().unwrap(); - - let mut privkey_str = String::from_utf8(genkey.stdout)?; - let mut pubkey_str = String::from_utf8(output.stdout)?; - - privkey_str.truncate(44); - pubkey_str.truncate(44); - - let private = WgKey::from_str(&privkey_str).unwrap(); - let public = WgKey::from_str(&pubkey_str).unwrap(); - - Ok(WgKeypair { public, private }) - } +pub fn create_wg_key(path: &Path, private_key: &WgKey) -> Result<(), Error> { + trace!("Overwriting old private key file"); + let mut priv_key_file = File::create(path)?; + write!(priv_key_file, "{private_key}")?; + Ok(()) +} + +pub fn create_wg_keypair() -> Result { + let genkey = Command::new("wg") + .args(["genkey"]) + .stdout(Stdio::piped()) + .output() + .expect("Are you sure wireguard is installed on this device?"); + + let mut pubkey = Command::new("wg") + .args(["pubkey"]) + .stdout(Stdio::piped()) + .stdin(Stdio::piped()) + .spawn() + .expect("Are you sure wireguard is installed on this device?"); + + pubkey + .stdin + .as_mut() + .unwrap() + .write_all(&genkey.stdout) + .expect("Failure generating wg keypair!"); + let output = pubkey.wait_with_output().unwrap(); + + let mut privkey_str = String::from_utf8(genkey.stdout)?; + let mut pubkey_str = String::from_utf8(output.stdout)?; + + privkey_str.truncate(44); + pubkey_str.truncate(44); + + let private = WgKey::from_str(&privkey_str).unwrap(); + let public = WgKey::from_str(&pubkey_str).unwrap(); + + Ok(WgKeypair { public, private }) } diff --git a/althea_kernel_interface/src/delete_tunnel.rs b/althea_kernel_interface/src/delete_tunnel.rs deleted file mode 100644 index 1607a0aad..000000000 --- a/althea_kernel_interface/src/delete_tunnel.rs +++ /dev/null @@ -1,37 +0,0 @@ -use super::KernelInterface; -use crate::KernelInterfaceError as Error; - -impl dyn KernelInterface { - pub fn delete_tunnel(&self, interface: &str) -> Result<(), Error> { - let output = self.run_command("ip", &["link", "del", interface])?; - if !output.stderr.is_empty() { - return Err(Error::RuntimeError(format!( - "received error deleting wireguard interface: {}", - String::from_utf8(output.stderr)? - ))); - } - Ok(()) - } -} - -#[test] -fn test_delete_tunnel_linux() { - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - - use crate::KI; - - let ip_args = &["link", "del", "wg1"]; - - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "ip"); - assert_eq!(args, ip_args); - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - })); - KI.delete_tunnel(&String::from("wg1")).unwrap(); -} diff --git a/althea_kernel_interface/src/dns.rs b/althea_kernel_interface/src/dns.rs index f73ccbeb4..b48d462b2 100644 --- a/althea_kernel_interface/src/dns.rs +++ b/althea_kernel_interface/src/dns.rs @@ -1,34 +1,30 @@ -use super::KernelInterface; - use std::fs::File; use std::io::Error; use std::io::Read; use std::net::IpAddr; -impl dyn KernelInterface { - /// Gets a list of IP addresses of nameservers from /etc/resolv.conf, may be v6, v4 or both - /// generally ignores malformed lines but produces IO errors - pub fn get_resolv_servers(&self) -> Result, Error> { - let mut f = File::open("/etc/resolv.conf")?; - let mut contents = String::new(); - f.read_to_string(&mut contents)?; +/// Gets a list of IP addresses of nameservers from /etc/resolv.conf, may be v6, v4 or both +/// generally ignores malformed lines but produces IO errors +pub fn get_resolv_servers() -> Result, Error> { + let mut f = File::open("/etc/resolv.conf")?; + let mut contents = String::new(); + f.read_to_string(&mut contents)?; - let mut res = Vec::new(); - for line in contents.lines() { - if line.starts_with("nameserver") { - let mut nameserver = line.split_whitespace(); - nameserver.next(); - match nameserver.next() { - Some(ip) => match ip.parse() { - Ok(addr) => res.push(addr), - Err(e) => { - warn!("Could not parse /etc/resolv.conf ip {:?} with {:?}", ip, e) - } - }, - None => warn!("Invalid /etc/resolv.conf!"), - } + let mut res = Vec::new(); + for line in contents.lines() { + if line.starts_with("nameserver") { + let mut nameserver = line.split_whitespace(); + nameserver.next(); + match nameserver.next() { + Some(ip) => match ip.parse() { + Ok(addr) => res.push(addr), + Err(e) => { + warn!("Could not parse /etc/resolv.conf ip {:?} with {:?}", ip, e) + } + }, + None => warn!("Invalid /etc/resolv.conf!"), } } - Ok(res) } + Ok(res) } diff --git a/althea_kernel_interface/src/exit_client_tunnel.rs b/althea_kernel_interface/src/exit_client_tunnel.rs index 2c63863a8..cf575daa3 100644 --- a/althea_kernel_interface/src/exit_client_tunnel.rs +++ b/althea_kernel_interface/src/exit_client_tunnel.rs @@ -1,5 +1,13 @@ -use super::KernelInterface; use crate::hardware_info::{get_kernel_version, parse_kernel_version}; +use crate::iptables::add_iptables_rule; +use crate::link_local_tools::{get_global_device_ip_v4, get_link_local_device_ip}; +use crate::netfilter::{ + delete_reject_rule, does_nftables_exist, init_nat_chain, insert_reject_rule, + set_nft_lan_fwd_rule, +}; +use crate::run_command; +use crate::setup_wg_if::get_peers; +use crate::traffic_control::set_codel_shaping; use crate::{open_tunnel::to_wg_local, KernelInterfaceError as Error}; use althea_types::WgKey; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; @@ -32,264 +40,260 @@ pub struct ClientExitTunnelConfig { pub user_specified_speed: Option, } -impl dyn KernelInterface { - pub fn get_kernel_is_v4(&self) -> Result { - let (_, system_kernel_version) = parse_kernel_version(get_kernel_version()?)?; - Ok(system_kernel_version.starts_with("4.")) - } +pub fn get_kernel_is_v4() -> Result { + let (_, system_kernel_version) = parse_kernel_version(get_kernel_version()?)?; + Ok(system_kernel_version.starts_with("4.")) +} - pub fn set_client_exit_tunnel_config( - &self, - args: ClientExitTunnelConfig, - local_mesh: Option, - ) -> Result<(), Error> { - self.run_command( - "wg", - &[ - "set", - "wg_exit", - "listen-port", - &args.listen_port.to_string(), - "private-key", - &args.private_key_path, - "peer", - &args.pubkey.to_string(), - "endpoint", - &format!("[{}]:{}", args.endpoint.ip(), args.endpoint.port()), - "allowed-ips", - "0.0.0.0/0, ::/0", - "persistent-keepalive", - "5", - ], - )?; +pub fn set_client_exit_tunnel_config( + args: ClientExitTunnelConfig, + local_mesh: Option, +) -> Result<(), Error> { + run_command( + "wg", + &[ + "set", + "wg_exit", + "listen-port", + &args.listen_port.to_string(), + "private-key", + &args.private_key_path, + "peer", + &args.pubkey.to_string(), + "endpoint", + &format!("[{}]:{}", args.endpoint.ip(), args.endpoint.port()), + "allowed-ips", + "0.0.0.0/0, ::/0", + "persistent-keepalive", + "5", + ], + )?; - // we only want one peer on this link, technically that one peer is multihomed - // via babel, but it has the same key so it's the same 'peer' from wireguard's - // perspective, if we don't do this we'll end up with multiple exits on the same - // tunnel - for i in self.get_peers("wg_exit")? { - if i != args.pubkey { - self.run_command("wg", &["set", "wg_exit", "peer", &format!("{i}"), "remove"])?; - } + // we only want one peer on this link, technically that one peer is multihomed + // via babel, but it has the same key so it's the same 'peer' from wireguard's + // perspective, if we don't do this we'll end up with multiple exits on the same + // tunnel + for i in get_peers("wg_exit")? { + if i != args.pubkey { + run_command("wg", &["set", "wg_exit", "peer", &format!("{i}"), "remove"])?; } + } - let prev_ip: Result = self.get_global_device_ip_v4("wg_exit"); - - match prev_ip { - Ok(prev_ip) => { - if prev_ip != args.local_ip { - self.run_command( - "ip", - &[ - "address", - "delete", - &format!("{}/{}", prev_ip, args.netmask), - "dev", - "wg_exit", - ], - )?; + let prev_ip: Result = get_global_device_ip_v4("wg_exit"); - self.run_command( - "ip", - &[ - "address", - "add", - &format!("{}/{}", args.local_ip, args.netmask), - "dev", - "wg_exit", - ], - )?; - } - } - Err(e) => { - warn!("Finding wg exit's current IP returned {}", e); - self.run_command( + match prev_ip { + Ok(prev_ip) => { + if prev_ip != args.local_ip { + run_command( "ip", &[ "address", - "add", - &format!("{}/{}", args.local_ip, args.netmask), + "delete", + &format!("{}/{}", prev_ip, args.netmask), "dev", "wg_exit", ], )?; - } - } - // If wg_exit does not have a link local addr, set one up - if self.get_link_local_device_ip("wg_exit").is_err() { - if let Some(mesh) = local_mesh { - if let Err(e) = self.run_command( + run_command( "ip", &[ "address", "add", - &format!("{}/64", to_wg_local(&mesh)), + &format!("{}/{}", args.local_ip, args.netmask), "dev", "wg_exit", ], - ) { - error!("IPV6 ERROR: Unable to set link local for wg_exit: {:?}", e); - } - } else { - error!("IPV6 ERRROR: No mesh ip, unable to set link local for wg_exit"); + )?; } } - - let output = self.run_command("ip", &["link", "set", "dev", "wg_exit", "mtu", "1340"])?; - if !output.stderr.is_empty() { - return Err(Error::RuntimeError(format!( - "received error adding wg link: {}", - String::from_utf8(output.stderr)? - ))); + Err(e) => { + warn!("Finding wg exit's current IP returned {}", e); + run_command( + "ip", + &[ + "address", + "add", + &format!("{}/{}", args.local_ip, args.netmask), + "dev", + "wg_exit", + ], + )?; } + } - let output = self.run_command("ip", &["link", "set", "dev", "wg_exit", "up"])?; - if !output.stderr.is_empty() { - return Err(Error::RuntimeError(format!( - "received error setting wg interface up: {}", - String::from_utf8(output.stderr)? - ))); + // If wg_exit does not have a link local addr, set one up + if get_link_local_device_ip("wg_exit").is_err() { + if let Some(mesh) = local_mesh { + if let Err(e) = run_command( + "ip", + &[ + "address", + "add", + &format!("{}/64", to_wg_local(&mesh)), + "dev", + "wg_exit", + ], + ) { + error!("IPV6 ERROR: Unable to set link local for wg_exit: {:?}", e); + } + } else { + error!("IPV6 ERRROR: No mesh ip, unable to set link local for wg_exit"); } + } - let _res = self.set_codel_shaping("br-lan", args.user_specified_speed); + let output = run_command("ip", &["link", "set", "dev", "wg_exit", "mtu", "1340"])?; + if !output.stderr.is_empty() { + return Err(Error::RuntimeError(format!( + "received error adding wg link: {}", + String::from_utf8(output.stderr)? + ))); + } - Ok(()) + let output = run_command("ip", &["link", "set", "dev", "wg_exit", "up"])?; + if !output.stderr.is_empty() { + return Err(Error::RuntimeError(format!( + "received error setting wg interface up: {}", + String::from_utf8(output.stderr)? + ))); } - pub fn set_route_to_tunnel(&self, gateway: &IpAddr) -> Result<(), Error> { - if let Err(e) = self.run_command("ip", &["route", "del", "default"]) { - warn!("Failed to delete default route {:?}", e); - } + let _res = set_codel_shaping("br-lan", args.user_specified_speed); - let output = self.run_command( - "ip", - &[ - "route", - "add", - "default", - "via", - &gateway.to_string(), - "dev", - "wg_exit", - ], - )?; - if !output.stderr.is_empty() { - return Err(Error::RuntimeError(format!( - "received error setting ip route: {}", - String::from_utf8(output.stderr)? - ))); - } + Ok(()) +} - Ok(()) +pub fn set_route_to_tunnel(gateway: &IpAddr) -> Result<(), Error> { + if let Err(e) = run_command("ip", &["route", "del", "default"]) { + warn!("Failed to delete default route {:?}", e); } - pub fn set_ipv6_route_to_tunnel(&self) -> Result<(), Error> { - // Remove current default route - if let Err(e) = self.run_command("ip", &["-6", "route", "del", "default"]) { - warn!("Failed to delete default ip6 route {:?}", e); - } - // Set new default route - let output = - self.run_command("ip", &["-6", "route", "add", "default", "dev", "wg_exit"])?; - if !output.stderr.is_empty() { - error!("IPV6 ERROR: Unable to set ip -6 default route"); - return Err(Error::RuntimeError(format!( - "received error setting ip -6 route: {}", - String::from_utf8(output.stderr)? - ))); - } - Ok(()) + let output = run_command( + "ip", + &[ + "route", + "add", + "default", + "via", + &gateway.to_string(), + "dev", + "wg_exit", + ], + )?; + if !output.stderr.is_empty() { + return Err(Error::RuntimeError(format!( + "received error setting ip route: {}", + String::from_utf8(output.stderr)? + ))); } - /// Adds nat rules for lan client, these act within the structure - /// of the openwrt rules which themselves create a few requirements - /// (such as saying that zone-lan-forward shoul jump to the accept table) - /// the nat rule here is very general, note the lack of restriction based - /// on incoming interface or ip, this is intentional, as it allows - /// the phone clients over in light_client_manager to function using these - /// same rules. It may be advisable in the future to split them up into - /// individual nat entires for each option - pub fn create_client_nat_rules(&self) -> Result<(), Error> { - let use_iptables = !self.does_nftables_exist(); + Ok(()) +} - if use_iptables { - self.add_iptables_rule( - "iptables", - &[ - "-t", - "nat", - "-A", - "POSTROUTING", - "-o", - "wg_exit", - "-j", - "MASQUERADE", - ], - )?; - self.add_iptables_rule("iptables", &["-A", "zone_lan_forward", "-j", "ACCEPT"])?; - } else { - self.init_nat_chain("wg_exit")?; - self.set_nft_lan_fwd_rule()?; - } +pub fn set_ipv6_route_to_tunnel() -> Result<(), Error> { + // Remove current default route + if let Err(e) = run_command("ip", &["-6", "route", "del", "default"]) { + warn!("Failed to delete default ip6 route {:?}", e); + } + // Set new default route + let output = run_command("ip", &["-6", "route", "add", "default", "dev", "wg_exit"])?; + if !output.stderr.is_empty() { + error!("IPV6 ERROR: Unable to set ip -6 default route"); + return Err(Error::RuntimeError(format!( + "received error setting ip -6 route: {}", + String::from_utf8(output.stderr)? + ))); + } + Ok(()) +} - // Set mtu - if use_iptables { - self.add_iptables_rule( - "iptables", - &[ - "-I", - "FORWARD", - "-p", - "tcp", - "--tcp-flags", - "SYN,RST", - "SYN", - "-j", - "TCPMSS", - "--clamp-mss-to-pmtu", //should be the same as --set-mss 1300 - ], - )?; +/// Adds nat rules for lan client, these act within the structure +/// of the openwrt rules which themselves create a few requirements +/// (such as saying that zone-lan-forward shoul jump to the accept table) +/// the nat rule here is very general, note the lack of restriction based +/// on incoming interface or ip, this is intentional, as it allows +/// the phone clients over in light_client_manager to function using these +/// same rules. It may be advisable in the future to split them up into +/// individual nat entires for each option +pub fn create_client_nat_rules() -> Result<(), Error> { + let use_iptables = !does_nftables_exist(); - //ipv6 support - self.add_iptables_rule( - "ip6tables", - &[ - "-I", - "FORWARD", - "-p", - "tcp", - "--tcp-flags", - "SYN,RST", - "SYN", - "-j", - "TCPMSS", - "--clamp-mss-to-pmtu", //should be the same as --set-mss 1300 - ], - )?; - } + if use_iptables { + add_iptables_rule( + "iptables", + &[ + "-t", + "nat", + "-A", + "POSTROUTING", + "-o", + "wg_exit", + "-j", + "MASQUERADE", + ], + )?; + add_iptables_rule("iptables", &["-A", "zone_lan_forward", "-j", "ACCEPT"])?; + } else { + init_nat_chain("wg_exit")?; + set_nft_lan_fwd_rule()?; + } - Ok(()) + // Set mtu + if use_iptables { + add_iptables_rule( + "iptables", + &[ + "-I", + "FORWARD", + "-p", + "tcp", + "--tcp-flags", + "SYN,RST", + "SYN", + "-j", + "TCPMSS", + "--clamp-mss-to-pmtu", //should be the same as --set-mss 1300 + ], + )?; + + //ipv6 support + add_iptables_rule( + "ip6tables", + &[ + "-I", + "FORWARD", + "-p", + "tcp", + "--tcp-flags", + "SYN,RST", + "SYN", + "-j", + "TCPMSS", + "--clamp-mss-to-pmtu", //should be the same as --set-mss 1300 + ], + )?; } - /// blocks the client nat by inserting a blocker in the start of the special lan forwarding - /// table created by openwrt. - pub fn block_client_nat(&self) -> Result<(), Error> { - if !self.does_nftables_exist() { - self.add_iptables_rule("iptables", &["-I", "zone_lan_forward", "-j", "REJECT"])?; - } else { - self.insert_reject_rule()?; - } - Ok(()) + Ok(()) +} + +/// blocks the client nat by inserting a blocker in the start of the special lan forwarding +/// table created by openwrt. +pub fn block_client_nat() -> Result<(), Error> { + if !does_nftables_exist() { + add_iptables_rule("iptables", &["-I", "zone_lan_forward", "-j", "REJECT"])?; + } else { + insert_reject_rule()?; } + Ok(()) +} - /// Removes the block created by block_client_nat() will fail if not run after that command - pub fn restore_client_nat(&self) -> Result<(), Error> { - if !self.does_nftables_exist() { - self.add_iptables_rule("iptables", &["-D", "zone_lan_forward", "-j", "REJECT"])?; - } else { - self.delete_reject_rule()?; - } - Ok(()) +/// Removes the block created by block_client_nat() will fail if not run after that command +pub fn restore_client_nat() -> Result<(), Error> { + if !does_nftables_exist() { + add_iptables_rule("iptables", &["-D", "zone_lan_forward", "-j", "REJECT"])?; + } else { + delete_reject_rule()?; } + Ok(()) } diff --git a/althea_kernel_interface/src/exit_server_tunnel.rs b/althea_kernel_interface/src/exit_server_tunnel.rs index 2f34c069f..4cff058ad 100644 --- a/althea_kernel_interface/src/exit_server_tunnel.rs +++ b/althea_kernel_interface/src/exit_server_tunnel.rs @@ -1,5 +1,10 @@ -use super::{KernelInterface, KernelInterfaceError}; +use super::KernelInterfaceError; +use crate::iptables::add_iptables_rule; +use crate::netfilter::{does_nftables_exist, init_nat_chain, insert_nft_exit_forward_rules}; use crate::open_tunnel::to_wg_local; +use crate::run_command; +use crate::setup_wg_if::get_peers; +use crate::traffic_control::{create_root_classful_limit, has_limit}; use althea_types::WgKey; use ipnetwork::IpNetwork; use std::collections::HashSet; @@ -15,316 +20,305 @@ pub struct ExitClient { pub port: u16, } -impl dyn KernelInterface { - // This function sets up the exit config and returns the updated list of tc filter handles - pub fn set_exit_wg_config( - &self, - clients: &HashSet, - listen_port: u16, - private_key_path: &str, - if_name: &str, - ) -> Result<(), Error> { - let command = "wg".to_string(); - - let mut args = vec![ - "set".into(), - if_name.into(), - "listen-port".into(), - format!("{listen_port}"), - "private-key".into(), - private_key_path.to_string(), - ]; - - let mut client_pubkeys = HashSet::new(); - - for c in clients.iter() { - // For the allowed IPs, we appends the clients internal ip as well - // as the client ipv6 assigned ip and add this to wireguards allowed ips - // internet_ipv6 is already in the form of ",.." - let i_ipv6 = &c.internet_ipv6; - let mut allowed_ips = c.internal_ip.to_string().to_owned(); - if let Some(i_ipv6) = i_ipv6 { - allowed_ips.push(','); - allowed_ips.push_str(&i_ipv6.to_string()); - } - - args.push("peer".into()); - args.push(format!("{}", c.public_key)); - args.push("endpoint".into()); - args.push(format!("[{}]:{}", c.mesh_ip, c.port)); - args.push("allowed-ips".into()); - args.push(allowed_ips); - - client_pubkeys.insert(c.public_key); +// This function sets up the exit config and returns the updated list of tc filter handles +pub fn set_exit_wg_config( + clients: &HashSet, + listen_port: u16, + private_key_path: &str, + if_name: &str, +) -> Result<(), Error> { + let command = "wg".to_string(); + + let mut args = vec![ + "set".into(), + if_name.into(), + "listen-port".into(), + format!("{listen_port}"), + "private-key".into(), + private_key_path.to_string(), + ]; + + let mut client_pubkeys = HashSet::new(); + + for c in clients.iter() { + // For the allowed IPs, we appends the clients internal ip as well + // as the client ipv6 assigned ip and add this to wireguards allowed ips + // internet_ipv6 is already in the form of ",.." + let i_ipv6 = &c.internet_ipv6; + let mut allowed_ips = c.internal_ip.to_string().to_owned(); + if let Some(i_ipv6) = i_ipv6 { + allowed_ips.push(','); + allowed_ips.push_str(&i_ipv6.to_string()); } - let arg_str: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); + args.push("peer".into()); + args.push(format!("{}", c.public_key)); + args.push("endpoint".into()); + args.push(format!("[{}]:{}", c.mesh_ip, c.port)); + args.push("allowed-ips".into()); + args.push(allowed_ips); - self.run_command(&command, &arg_str[..])?; + client_pubkeys.insert(c.public_key); + } - let wg_peers = self.get_peers(if_name)?; - info!("{} has {} peers", if_name, wg_peers.len()); - for i in wg_peers { - if !client_pubkeys.contains(&i) { - warn!("Removing no longer authorized peer {}", i); - self.run_command("wg", &["set", if_name, "peer", &format!("{i}"), "remove"])?; - } - } + let arg_str: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); - Ok(()) - } + run_command(&command, &arg_str[..])?; - /// This function adds a route for each client ipv4 subnet to the routing table - /// this works on the premise of smallest prefix first routing meaning that we can assign - /// ip route 172.168.0.1/16 to wg_exit_v2 and then individually add /32 routes to wg_exit_v1 - /// and this will produce the same routing outcomes with many less routes than adding individual routes - /// on both - pub fn setup_individual_client_routes( - &self, - client_internal_ip: IpAddr, - exit_internal_v4: IpAddr, - interface: &str, - ) { - let mut interface_cloned = interface.to_string(); - interface_cloned.push(' '); - // Setup ipv4 route - // 1.) Select all ipv4 routes with 'client_internal_ip'. This gives us all routes with wg_exit and wg_exit_v2 for the ip - // THere should be only one route - // 2.) Does the route contain 'interface'? Yes? we continue - // 3.) No? That means either no route exists or there is a route with the other interface name - // 4.) Delete route, add new route with the correct interface - let output = self - .run_command("ip", &["route", "show", &client_internal_ip.to_string()]) - .expect("Fix command"); - let route = String::from_utf8(output.stdout).unwrap(); - if !route.contains(&interface_cloned) { - self.run_command("ip", &["route", "del", &client_internal_ip.to_string()]) - .expect("Fix command"); - - self.run_command( - "ip", - &[ - "route", - "add", - &client_internal_ip.to_string(), - "dev", - interface, - "src", - &exit_internal_v4.to_string(), - ], - ) - .expect("Fix command"); + let wg_peers = get_peers(if_name)?; + info!("{} has {} peers", if_name, wg_peers.len()); + for i in wg_peers { + if !client_pubkeys.contains(&i) { + warn!("Removing no longer authorized peer {}", i); + run_command("wg", &["set", if_name, "peer", &format!("{i}"), "remove"])?; } } - /// this function performs the teardown step of setup_indvidual_client_routes, when a router upgrades - /// to beta20 or later this function checks for and deltes the rules - pub fn teardown_individual_client_routes(&self, client_internal_ip: IpAddr) { - let output = self - .run_command("ip", &["route", "show", &client_internal_ip.to_string()]) - .expect("Fix command"); - let route = String::from_utf8(output.stdout).unwrap(); - if !route.is_empty() { - self.run_command("ip", &["route", "del", &client_internal_ip.to_string()]) - .expect("Fix command"); - } - } + Ok(()) +} - /// Performs the one time startup tasks for the rita_exit clients loop - pub fn one_time_exit_setup( - &self, - local_v4: Option<(IpAddr, u8)>, - external_v6: Option<(IpAddr, u8)>, - exit_mesh: IpAddr, - interface: &str, - enable_enforcement: bool, - ) -> Result<(), Error> { - if let Some((local_ip_v4, netmask_v4)) = local_v4 { - // sanity checking - assert!(local_ip_v4.is_ipv4()); - assert!(netmask_v4 < 32); - - let _output = self.run_command( - "ip", - &[ - "address", - "add", - &format!("{local_ip_v4}/{netmask_v4}"), - "dev", - interface, - ], - )?; - } +/// This function adds a route for each client ipv4 subnet to the routing table +/// this works on the premise of smallest prefix first routing meaning that we can assign +/// ip route 172.168.0.1/16 to wg_exit_v2 and then individually add /32 routes to wg_exit_v1 +/// and this will produce the same routing outcomes with many less routes than adding individual routes +/// on both +pub fn setup_individual_client_routes( + client_internal_ip: IpAddr, + exit_internal_v4: IpAddr, + interface: &str, +) { + let mut interface_cloned = interface.to_string(); + interface_cloned.push(' '); + // Setup ipv4 route + // 1.) Select all ipv4 routes with 'client_internal_ip'. This gives us all routes with wg_exit and wg_exit_v2 for the ip + // THere should be only one route + // 2.) Does the route contain 'interface'? Yes? we continue + // 3.) No? That means either no route exists or there is a route with the other interface name + // 4.) Delete route, add new route with the correct interface + let output = run_command("ip", &["route", "show", &client_internal_ip.to_string()]) + .expect("Fix command"); + let route = String::from_utf8(output.stdout).unwrap(); + if !route.contains(&interface_cloned) { + run_command("ip", &["route", "del", &client_internal_ip.to_string()]).expect("Fix command"); + + run_command( + "ip", + &[ + "route", + "add", + &client_internal_ip.to_string(), + "dev", + interface, + "src", + &exit_internal_v4.to_string(), + ], + ) + .expect("Fix command"); + } +} - // setup ipv6 if provided2602:FBAD:10::/45 - if let Some((external_ip_v6, netmask_v6)) = external_v6 { - // sanity checking - assert!(external_ip_v6.is_ipv6()); - assert!(netmask_v6 < 128); +/// this function performs the teardown step of setup_indvidual_client_routes, when a router upgrades +/// to beta20 or later this function checks for and deltes the rules +pub fn teardown_individual_client_routes(client_internal_ip: IpAddr) { + let output = run_command("ip", &["route", "show", &client_internal_ip.to_string()]) + .expect("Fix command"); + let route = String::from_utf8(output.stdout).unwrap(); + if !route.is_empty() { + run_command("ip", &["route", "del", &client_internal_ip.to_string()]).expect("Fix command"); + } +} - let _output = self.run_command( - "ip", - &[ - "address", - "add", - &format!("{external_ip_v6}/{netmask_v6}"), - "dev", - interface, - ], - )?; - } +/// Performs the one time startup tasks for the rita_exit clients loop +pub fn one_time_exit_setup( + local_v4: Option<(IpAddr, u8)>, + external_v6: Option<(IpAddr, u8)>, + exit_mesh: IpAddr, + interface: &str, + enable_enforcement: bool, +) -> Result<(), Error> { + if let Some((local_ip_v4, netmask_v4)) = local_v4 { + // sanity checking + assert!(local_ip_v4.is_ipv4()); + assert!(netmask_v4 < 32); + + let _output = run_command( + "ip", + &[ + "address", + "add", + &format!("{local_ip_v4}/{netmask_v4}"), + "dev", + interface, + ], + )?; + } - // Set up link local mesh ip in wg_exit as fe80 + rest of mesh ip of exit - let local_link = to_wg_local(&exit_mesh); + // setup ipv6 if provided2602:FBAD:10::/45 + if let Some((external_ip_v6, netmask_v6)) = external_v6 { + // sanity checking + assert!(external_ip_v6.is_ipv6()); + assert!(netmask_v6 < 128); - let _output = self.run_command( + let _output = run_command( "ip", &[ "address", "add", - &format!("{local_link}/64"), + &format!("{external_ip_v6}/{netmask_v6}"), "dev", interface, ], )?; + } - let output = self.run_command("ip", &["link", "set", "dev", interface, "mtu", "1500"])?; - if !output.stderr.is_empty() { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error adding wg link: {}", - String::from_utf8(output.stderr)? - ))); - } + // Set up link local mesh ip in wg_exit as fe80 + rest of mesh ip of exit + let local_link = to_wg_local(&exit_mesh); + + let _output = run_command( + "ip", + &[ + "address", + "add", + &format!("{local_link}/64"), + "dev", + interface, + ], + )?; + + let output = run_command("ip", &["link", "set", "dev", interface, "mtu", "1500"])?; + if !output.stderr.is_empty() { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error adding wg link: {}", + String::from_utf8(output.stderr)? + ))); + } - let output = self.run_command("ip", &["link", "set", "dev", interface, "up"])?; - if !output.stderr.is_empty() { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error setting wg interface up: {}", - String::from_utf8(output.stderr)? - ))); - } + let output = run_command("ip", &["link", "set", "dev", interface, "up"])?; + if !output.stderr.is_empty() { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error setting wg interface up: {}", + String::from_utf8(output.stderr)? + ))); + } - // this creates the root classful htb limit for which we will make - // subclasses to enforce payment - if !self.has_limit(interface)? && enable_enforcement { - info!( - "Setting up root HTB qdisc for interface: {:?}, this should only run once", - interface - ); - self.create_root_classful_limit(interface) - .expect("Failed to setup root HTB qdisc!"); - } + // this creates the root classful htb limit for which we will make + // subclasses to enforce payment + if !has_limit(interface)? && enable_enforcement { + info!( + "Setting up root HTB qdisc for interface: {:?}, this should only run once", + interface + ); + create_root_classful_limit(interface).expect("Failed to setup root HTB qdisc!"); + } + + Ok(()) +} - Ok(()) +/// Sets up the natting rules for forwarding ipv4 and ipv6 traffic +pub fn setup_nat( + external_interface: &str, + interface: &str, + external_v6: Option<(IpAddr, u8)>, +) -> Result<(), Error> { + // nat masquerade on exit + if !does_nftables_exist() { + add_iptables_rule( + "iptables", + &[ + "-w", + "-t", + "nat", + "-A", + "POSTROUTING", + "-o", + external_interface, + "-j", + "MASQUERADE", + ], + )?; + } else { + init_nat_chain(external_interface)?; } - /// Sets up the natting rules for forwarding ipv4 and ipv6 traffic - pub fn setup_nat( - &self, - external_interface: &str, - interface: &str, - external_v6: Option<(IpAddr, u8)>, - ) -> Result<(), Error> { - // nat masquerade on exit - if !self.does_nftables_exist() { - self.add_iptables_rule( - "iptables", - &[ - "-w", - "-t", - "nat", - "-A", - "POSTROUTING", - "-o", - external_interface, - "-j", - "MASQUERADE", - ], - )?; - } else { - self.init_nat_chain(external_interface)?; - } + // Add v4 and v6 forward rules wg_exit <-> ex_nic + if !does_nftables_exist() { + // v4 wg_exit -> ex_nic + add_iptables_rule( + "iptables", + &[ + "-w", + "-t", + "filter", + "-A", + "FORWARD", + "-o", + external_interface, + "-i", + interface, + "-j", + "ACCEPT", + ], + )?; - // Add v4 and v6 forward rules wg_exit <-> ex_nic - if !self.does_nftables_exist() { - // v4 wg_exit -> ex_nic - self.add_iptables_rule( - "iptables", - &[ - "-w", - "-t", - "filter", - "-A", - "FORWARD", - "-o", - external_interface, - "-i", - interface, - "-j", - "ACCEPT", - ], - )?; + // v4 ex_nic -> interface + add_iptables_rule( + "iptables", + &[ + "-w", + "-t", + "filter", + "-A", + "FORWARD", + "-o", + interface, + "-i", + external_interface, + "-m", + "state", + "--state", + "RELATED,ESTABLISHED", + "-j", + "ACCEPT", + ], + )?; - // v4 ex_nic -> interface - self.add_iptables_rule( - "iptables", - &[ - "-w", - "-t", - "filter", - "-A", - "FORWARD", - "-o", - interface, - "-i", - external_interface, - "-m", - "state", - "--state", - "RELATED,ESTABLISHED", - "-j", - "ACCEPT", - ], - )?; + // Add iptable routes between wg_exit and the external nic + add_iptables_rule( + "ip6tables", + &[ + "-A", + "FORWARD", + "-i", + interface, + "-o", + external_interface, + "-j", + "ACCEPT", + ], + )?; - // Add iptable routes between wg_exit and the external nic - self.add_iptables_rule( + if let Some((external_ip_v6, netmask_v6)) = external_v6 { + add_iptables_rule( "ip6tables", &[ "-A", "FORWARD", + "-d", + &format!("{}/{}", external_ip_v6, netmask_v6), "-i", - interface, - "-o", external_interface, + "-o", + interface, "-j", "ACCEPT", ], )?; - - if let Some((external_ip_v6, netmask_v6)) = external_v6 { - self.add_iptables_rule( - "ip6tables", - &[ - "-A", - "FORWARD", - "-d", - &format!("{}/{}", external_ip_v6, netmask_v6), - "-i", - external_interface, - "-o", - interface, - "-j", - "ACCEPT", - ], - )?; - } - } else { - self.insert_nft_exit_forward_rules(interface, external_interface, external_v6)?; } - - Ok(()) + } else { + insert_nft_exit_forward_rules(interface, external_interface, external_v6)?; } + + Ok(()) } #[test] diff --git a/althea_kernel_interface/src/fs_sync.rs b/althea_kernel_interface/src/fs_sync.rs index 4f8e4c19b..170266127 100644 --- a/althea_kernel_interface/src/fs_sync.rs +++ b/althea_kernel_interface/src/fs_sync.rs @@ -1,12 +1,10 @@ -use super::KernelInterface; +use crate::run_command; use crate::KernelInterfaceError as Error; -impl dyn KernelInterface { - /// Performs a full filesystem sync by running the sync command. - /// If there are any outstanding writes they will be flushed to the disk - /// Currently used because UBIFS devices seem to have issues - pub fn fs_sync(&self) -> Result<(), Error> { - self.run_command("sync", &[])?; - Ok(()) - } +/// Performs a full filesystem sync by running the sync command. +/// If there are any outstanding writes they will be flushed to the disk +/// Currently used because UBIFS devices seem to have issues +pub fn fs_sync() -> Result<(), Error> { + run_command("sync", &[])?; + Ok(()) } diff --git a/althea_kernel_interface/src/get_neighbors.rs b/althea_kernel_interface/src/get_neighbors.rs index 1c71d9c21..df0726d13 100644 --- a/althea_kernel_interface/src/get_neighbors.rs +++ b/althea_kernel_interface/src/get_neighbors.rs @@ -1,68 +1,55 @@ -use crate::KernelInterface; -use crate::KernelInterfaceError as Error; +use crate::{run_command, KernelInterfaceError as Error}; use regex::Regex; -use std::collections::HashSet; use std::net::IpAddr; +use std::process::Output; use std::str::FromStr; -impl dyn KernelInterface { - /// Returns a vector of neighbors reachable over layer 2, giving IP address of each. - /// Implemented with `ip neighbor` on Linux. - pub fn get_neighbors(&self) -> Result, Error> { - let output = self.run_command("ip", &["neigh"])?; - trace!("Got {:?} from `ip neighbor`", output); +/// Internal testing function to parse the output of `ip neighbor` on Linux. +fn parse_neighbors_internal(output: Output) -> Result, Error> { + let mut vec = Vec::new(); - let mut vec = Vec::new(); - - lazy_static! { - static ref RE: Regex = - Regex::new(r"(\S*).*dev (\S*).*lladdr (\S*).*(REACHABLE|STALE|DELAY)") - .expect("Unable to compile regular expression"); - } - for caps in RE.captures_iter(&String::from_utf8(output.stdout)?) { - trace!("Regex captured {:?}", caps); - - vec.push((IpAddr::from_str(&caps[1])?, caps[2].to_string())); - } - trace!("Got neighbors {:?}", vec); - Ok(vec) + lazy_static! { + static ref RE: Regex = + Regex::new(r"(\S*).*dev (\S*).*lladdr (\S*).*(REACHABLE|STALE|DELAY)") + .expect("Unable to compile regular expression"); } + for caps in RE.captures_iter(&String::from_utf8(output.stdout)?) { + trace!("Regex captured {:?}", caps); - pub fn trigger_neighbor_disc(&self, interfaces: &HashSet) -> Result<(), Error> { - for interface in interfaces.iter() { - self.run_command("ping6", &["-c1", "-I", interface, "ff02::1"])?; - } - Ok(()) + vec.push((IpAddr::from_str(&caps[1])?, caps[2].to_string())); } + trace!("Got neighbors {:?}", vec); + Ok(vec) +} + +/// Returns a vector of neighbors reachable over layer 2, giving IP address of each. +/// Implemented with `ip neighbor` on Linux. +pub fn get_neighbors() -> Result, Error> { + let output = run_command("ip", &["neigh"])?; + trace!("Got {:?} from `ip neighbor`", output); + parse_neighbors_internal(output) } #[test] fn test_get_neighbors_linux() { - use crate::KI; - use std::os::unix::process::ExitStatusExt; use std::process::ExitStatus; use std::process::Output; - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "ip"); - assert_eq!(args, &["neigh"]); - - Ok(Output { - stdout: b"10.0.2.2 dev eth0 lladdr 00:00:00:aa:00:03 STALE + let output = Output { + stdout: b"10.0.2.2 dev eth0 lladdr 00:00:00:aa:00:03 STALE 10.0.0.2 dev eth0 FAILED 10.0.1.2 dev eth0 lladdr 00:00:00:aa:00:05 REACHABLE 2001::2 dev eth0 lladdr 00:00:00:aa:00:56 REACHABLE fe80::7459:8eff:fe98:81 dev eth0 lladdr 76:59:8e:98:00:81 STALE fe80::433:25ff:fe8c:e1ea dev eth0 lladdr 1a:32:06:78:05:0a STALE 2001::2 dev eth0 FAILED" - .to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - })); + .to_vec(), + stderr: b"".to_vec(), + status: ExitStatus::from_raw(0), + }; - let addresses = KI.get_neighbors().unwrap(); + let addresses = parse_neighbors_internal(output).unwrap(); //assert_eq!(format!("{}", addresses[0].0), "00-00-00-aa-00-03"); assert_eq!(addresses[0].0.to_string(), "10.0.2.2"); diff --git a/althea_kernel_interface/src/hardware_info.rs b/althea_kernel_interface/src/hardware_info.rs index 9c53c9ac1..79a0b2e81 100644 --- a/althea_kernel_interface/src/hardware_info.rs +++ b/althea_kernel_interface/src/hardware_info.rs @@ -1,6 +1,8 @@ use crate::file_io::get_lines; +use crate::is_openwrt::is_openwrt; +use crate::manipulate_uci::get_uci_var; +use crate::run_command; use crate::KernelInterfaceError as Error; -use crate::KI; use althea_types::extract_wifi_station_data; use althea_types::extract_wifi_survey_data; use althea_types::ConntrackInfo; @@ -404,10 +406,10 @@ fn get_conntrack_info() -> Option { /// Device names are in the form wlan0, wlan1 etc fn parse_wifi_device_names() -> Result, Error> { // We parse /etc/config/wireless which is an openwrt config. We return an error if not openwrt - if KI.is_openwrt() { + if is_openwrt() { let mut ret = Vec::new(); - let lines = KI.run_command("uci", &["show", "wireless"])?; + let lines = run_command("uci", &["show", "wireless"])?; let lines: Vec<&str> = from_utf8(&lines.stdout)?.lines().collect(); // trying to get lines 'wireless.default_radio1.ifname='wlan1'' @@ -490,7 +492,7 @@ fn get_wifi_station_info(dev: &str) -> Vec { pub fn get_radio_ssid(radio: &str) -> Option { let radio = radio.replace("wlan", "radio"); let path = format!("wireless.default_{radio}.ssid"); - match KI.get_uci_var(&path) { + match get_uci_var(&path) { Ok(a) => Some(a), Err(e) => { error!("Unable to get radio ssid for radio: {} with {:?}", radio, e); @@ -506,7 +508,7 @@ pub fn get_radio_ssid(radio: &str) -> Option { pub fn get_radio_enabled(radio: &str) -> Option { let radio = radio.replace("wlan", "radio"); let path = format!("wireless.{radio}.disabled"); - match KI.get_uci_var(&path) { + match get_uci_var(&path) { // if disabled flag is set to '0' in config, radio is enabled so we return true and vice versa Ok(a) => Some(a.contains('0')), Err(e) => { @@ -527,7 +529,7 @@ pub fn get_radio_enabled(radio: &str) -> Option { pub fn get_radio_channel(radio: &str) -> Option { let radio = radio.replace("wlan", "radio"); let path = format!("wireless.{radio}.channel"); - match KI.get_uci_var(&path) { + match get_uci_var(&path) { Ok(a) => match a.parse::() { Ok(a) => Some(a), Err(e) => { diff --git a/althea_kernel_interface/src/interface_tools.rs b/althea_kernel_interface/src/interface_tools.rs index c2e120c09..e4fd90329 100644 --- a/althea_kernel_interface/src/interface_tools.rs +++ b/althea_kernel_interface/src/interface_tools.rs @@ -1,5 +1,6 @@ use crate::file_io::get_lines; -use crate::KernelInterface; +use crate::netns::get_namespace; +use crate::run_command; use crate::KernelInterfaceError as Error; use althea_types::InterfaceUsageStats; use regex::Regex; @@ -19,102 +20,99 @@ fn get_helper(input: Option<&&str>) -> Result { } } -impl dyn KernelInterface { - /// Gets usage data from all interfaces from /proc/net/dev, note that for wireguard interfaces - /// updating the interface on the fly (like we do with wg_exit) will reset the usage - /// counter on the wireguard side, but not on in proc which this code pulls from - pub fn get_per_interface_usage(&self) -> Result, Error> { - let lines = get_lines("/proc/net/dev")?; - // all lines represent an interface, except the first line which is a header - let mut lines = lines.iter(); - // skip the first and second lines - lines.next(); - lines.next(); - let mut ret = Vec::new(); - for line in lines { - println!("line ins {}", line); - let parts: Vec<&str> = line.split_ascii_whitespace().collect(); - ret.push(InterfaceUsageStats { - interface_name: get_helper(parts.first())?.trim_end_matches(':').to_string(), - recieve_bytes: get_helper(parts.get(1))?.parse()?, - recieve_packets: get_helper(parts.get(2))?.parse()?, - recieve_errors: get_helper(parts.get(3))?.parse()?, - recieve_dropped: get_helper(parts.get(4))?.parse()?, - recieve_fifo_errors: get_helper(parts.get(5))?.parse()?, - recieve_frame_errors: get_helper(parts.get(6))?.parse()?, - recieve_multicast_erorrs: get_helper(parts.get(8))?.parse()?, - transmit_bytes: get_helper(parts.get(9))?.parse()?, - transmit_packets: get_helper(parts.get(10))?.parse()?, - transmit_errors: get_helper(parts.get(11))?.parse()?, - transmit_fifo_errors: get_helper(parts.get(12))?.parse()?, - transmit_collission_erorrs: get_helper(parts.get(13))?.parse()?, - tranmist_carrier_errors: get_helper(parts.get(14))?.parse()?, - }) - } - Ok(ret) +/// Gets usage data from all interfaces from /proc/net/dev, note that for wireguard interfaces +/// updating the interface on the fly (like we do with wg_exit) will reset the usage +/// counter on the wireguard side, but not on in proc which this code pulls from +pub fn get_per_interface_usage() -> Result, Error> { + let lines = get_lines("/proc/net/dev")?; + // all lines represent an interface, except the first line which is a header + let mut lines = lines.iter(); + // skip the first and second lines + lines.next(); + lines.next(); + let mut ret = Vec::new(); + for line in lines { + println!("line ins {}", line); + let parts: Vec<&str> = line.split_ascii_whitespace().collect(); + ret.push(InterfaceUsageStats { + interface_name: get_helper(parts.first())?.trim_end_matches(':').to_string(), + recieve_bytes: get_helper(parts.get(1))?.parse()?, + recieve_packets: get_helper(parts.get(2))?.parse()?, + recieve_errors: get_helper(parts.get(3))?.parse()?, + recieve_dropped: get_helper(parts.get(4))?.parse()?, + recieve_fifo_errors: get_helper(parts.get(5))?.parse()?, + recieve_frame_errors: get_helper(parts.get(6))?.parse()?, + recieve_multicast_erorrs: get_helper(parts.get(8))?.parse()?, + transmit_bytes: get_helper(parts.get(9))?.parse()?, + transmit_packets: get_helper(parts.get(10))?.parse()?, + transmit_errors: get_helper(parts.get(11))?.parse()?, + transmit_fifo_errors: get_helper(parts.get(12))?.parse()?, + transmit_collission_erorrs: get_helper(parts.get(13))?.parse()?, + tranmist_carrier_errors: get_helper(parts.get(14))?.parse()?, + }) } + Ok(ret) +} - /// Returns all existing interfaces - pub fn get_interfaces(&self) -> Result, Error> { - let links = read_dir("/sys/class/net/")?; - - let mut vec = Vec::new(); - for dir in links { - let dir = dir?; - if dir.path().is_dir() { - // this could fail if the interface contains any characters - // not allowed in unicode. I don't think the kernel allows - // this in the first place - vec.push(dir.file_name().to_str().unwrap().to_string()); - } +/// Returns all existing interfaces +pub fn get_interfaces() -> Result, Error> { + let links = read_dir("/sys/class/net/")?; + + let mut vec = Vec::new(); + for dir in links { + let dir = dir?; + if dir.path().is_dir() { + // this could fail if the interface contains any characters + // not allowed in unicode. I don't think the kernel allows + // this in the first place + vec.push(dir.file_name().to_str().unwrap().to_string()); } - - trace!("interfaces: {:?}", vec); - Ok(vec) } - pub fn ifindex_to_interface_name(&self, ifindex: usize) -> Result { - for interface in self.get_interfaces()? { - let found_ifindex = self.get_ifindex(&interface)?; - if ifindex == found_ifindex { - return Ok(interface); - } - } - Err(Error::NoInterfaceError(ifindex.to_string())) - } + trace!("interfaces: {:?}", vec); + Ok(vec) +} - /// Deletes an named interface - pub fn del_interface(&self, name: &str) -> Result<(), Error> { - self.run_command("ip", &["link", "del", "dev", name])?; - Ok(()) +pub fn ifindex_to_interface_name(ifindex: usize) -> Result { + for interface in get_interfaces()? { + let found_ifindex = get_ifindex(&interface)?; + if ifindex == found_ifindex { + return Ok(interface); + } } + Err(Error::NoInterfaceError(ifindex.to_string())) +} - pub fn iface_status(&self, iface: &str) -> Result { - // cat so we can mock - let output = self.run_command("cat", &[&format!("/sys/class/net/{iface}/operstate")])?; +/// Deletes an named interface +pub fn del_interface(name: &str) -> Result<(), Error> { + run_command("ip", &["link", "del", "dev", name])?; + Ok(()) +} - let output = from_utf8(&output.stdout)?; +pub fn iface_status(iface: &str) -> Result { + // cat so we can mock + let output = run_command("cat", &[&format!("/sys/class/net/{iface}/operstate")])?; - Ok(output.trim_end().to_string()) - } + let output = from_utf8(&output.stdout)?; - pub fn get_wg_remote_ip(&self, name: &str) -> Result { - let output = self.run_command("wg", &["show", name, "endpoints"])?; - let stdout = String::from_utf8(output.stdout)?; + Ok(output.trim_end().to_string()) +} - lazy_static! { - static ref RE: Regex = Regex::new(r"(?:([0-9a-f:]+)%)|(?:([0-9\.]+):)") - .expect("Unable to compile regular expression"); - } - let cap = RE.captures(&stdout); - - match cap { - Some(cap) => { - let ip_str = match cap.get(1) { - // ipv6 - Some(cap) => cap.as_str(), - None => { - match cap.get(2) { +/// Internal testing function for get_wg_remote_ip +fn get_wg_remote_ip_internal(stdout: String, name: String) -> Result { + lazy_static! { + static ref RE: Regex = Regex::new(r"(?:([0-9a-f:]+)%)|(?:([0-9\.]+):)") + .expect("Unable to compile regular expression"); + } + let cap = RE.captures(&stdout); + + match cap { + Some(cap) => { + let ip_str = match cap.get(1) { + // ipv6 + Some(cap) => cap.as_str(), + None => { + match cap.get(2) { Some(cap) => { // ipv4 cap.as_str() @@ -125,236 +123,204 @@ impl dyn KernelInterface { ))) } } - } - }; + } + }; - Ok(ip_str.parse()?) - } - None => Err(Error::RuntimeError(format!( - "Cannot parse `wg show {name} endpoints` output, got {stdout}, nothing captured" - ))), + Ok(ip_str.parse()?) } + None => Err(Error::RuntimeError(format!( + "Cannot parse `wg show {name} endpoints` output, got {stdout}, nothing captured" + ))), } +} - /// Gets all the IPv4 addresses from an interface and returns the address and it's netmask - /// as a tuple. - pub fn get_ip_from_iface(&self, name: &str) -> Result, Error> { - let output = self.run_command("ip", &["address", "show", "dev", name])?; - let stdout = String::from_utf8(output.stdout)?; +pub fn get_wg_remote_ip(name: &str) -> Result { + let output = run_command("wg", &["show", name, "endpoints"])?; + let stdout = String::from_utf8(output.stdout)?; + get_wg_remote_ip_internal(stdout, name.to_string()) +} - lazy_static! { - static ref RE: Regex = Regex::new(r"((\d){1,3}\.){3}(\d){1,3}/(\d){1,3}") - .expect("Unable to compile regular expression"); - } +/// Internal utility function for testing get_ip_from_iface +fn get_ip_from_iface_internal(stdout: String) -> Result, Error> { + lazy_static! { + static ref RE: Regex = Regex::new(r"((\d){1,3}\.){3}(\d){1,3}/(\d){1,3}") + .expect("Unable to compile regular expression"); + } - let mut ret = Vec::new(); - for line in stdout.lines() { - let cap = RE.captures(line); - // we captured something on this line - if let Some(cap) = cap { - // flatten drops the 'none' values in this array - for ip_cap in cap.iter().flatten() { - let mut split = ip_cap.as_str().split('/'); - let ip_str = split.next(); - let netmask = split.next(); - if let (Some(ip_str), Some(netmask)) = (ip_str, netmask) { - if let (Ok(parsed_ip), Ok(parsed_netmask)) = - (ip_str.parse(), netmask.parse()) - { - ret.push((parsed_ip, parsed_netmask)); - } + let mut ret = Vec::new(); + for line in stdout.lines() { + let cap = RE.captures(line); + // we captured something on this line + if let Some(cap) = cap { + // flatten drops the 'none' values in this array + for ip_cap in cap.iter().flatten() { + let mut split = ip_cap.as_str().split('/'); + let ip_str = split.next(); + let netmask = split.next(); + if let (Some(ip_str), Some(netmask)) = (ip_str, netmask) { + if let (Ok(parsed_ip), Ok(parsed_netmask)) = (ip_str.parse(), netmask.parse()) { + ret.push((parsed_ip, parsed_netmask)); } } } } - - Ok(ret) } - /// Gets all the IPv6 addresses from an interface and returns the address and it's netmask - /// as a tuple. - pub fn get_ipv6_from_iface(&self, name: &str) -> Result, Error> { - let output = self.run_command("ip", &["address", "show", "dev", name])?; - let stdout = String::from_utf8(output.stdout)?; + Ok(ret) +} - lazy_static! { - static ref RE: Regex = Regex::new(r"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/(\d){1,3}").expect("Unable to compile regular expression"); - } +/// Gets all the IPv4 addresses from an interface and returns the address and it's netmask +/// as a tuple. +pub fn get_ip_from_iface(name: &str) -> Result, Error> { + let output = run_command("ip", &["address", "show", "dev", name])?; + let stdout = String::from_utf8(output.stdout)?; + get_ip_from_iface_internal(stdout) +} - let mut ret = Vec::new(); - for line in stdout.lines() { - let cap = RE.captures(line); - // we captured something on this line - if let Some(cap) = cap { - // flatten drops the 'none' values in this array - for ip_cap in cap.iter().flatten() { - let mut split = ip_cap.as_str().split('/'); - let ip_str = split.next(); - let netmask = split.next(); - if let (Some(ip_str), Some(netmask)) = (ip_str, netmask) { - if let (Ok(parsed_ip), Ok(parsed_netmask)) = - (ip_str.parse(), netmask.parse()) - { - ret.push((parsed_ip, parsed_netmask)); - } +/// Gets all the IPv6 addresses from an interface and returns the address and it's netmask +/// as a tuple. +pub fn get_ipv6_from_iface(name: &str) -> Result, Error> { + let output = run_command("ip", &["address", "show", "dev", name])?; + let stdout = String::from_utf8(output.stdout)?; + + lazy_static! { + static ref RE: Regex = Regex::new(r"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/(\d){1,3}").expect("Unable to compile regular expression"); + } + + let mut ret = Vec::new(); + for line in stdout.lines() { + let cap = RE.captures(line); + // we captured something on this line + if let Some(cap) = cap { + // flatten drops the 'none' values in this array + for ip_cap in cap.iter().flatten() { + let mut split = ip_cap.as_str().split('/'); + let ip_str = split.next(); + let netmask = split.next(); + if let (Some(ip_str), Some(netmask)) = (ip_str, netmask) { + if let (Ok(parsed_ip), Ok(parsed_netmask)) = (ip_str.parse(), netmask.parse()) { + ret.push((parsed_ip, parsed_netmask)); } } } } + } + + Ok(ret) +} - Ok(ret) +/// calls iproute2 to set an interface up or down +pub fn set_if_up_down(if_name: &str, up_down: &str) -> Result<(), Error> { + let output = run_command("ip", &["link", "set", "dev", if_name, up_down])?; + if !output.stderr.is_empty() { + Err(Error::RuntimeError(format!( + "received error setting wg interface up: {}", + String::from_utf8(output.stderr)? + ))) + } else { + Ok(()) } +} - /// calls iproute2 to set an interface up or down - pub fn set_if_up_down(&self, if_name: &str, up_down: &str) -> Result<(), Error> { - let output = self.run_command("ip", &["link", "set", "dev", if_name, up_down])?; - if !output.stderr.is_empty() { - Err(Error::RuntimeError(format!( - "received error setting wg interface up: {}", - String::from_utf8(output.stderr)? - ))) - } else { - Ok(()) - } +/// Gets the mtu from an interface +pub fn get_mtu(if_name: &str) -> Result { + let lines = get_lines(&format!("/sys/class/net/{if_name}/mtu"))?; + if let Some(mtu) = lines.first() { + Ok(mtu.parse()?) + } else { + Err(Error::NoInterfaceError(if_name.to_string())) } +} - /// Gets the mtu from an interface - pub fn get_mtu(&self, if_name: &str) -> Result { - let lines = get_lines(&format!("/sys/class/net/{if_name}/mtu"))?; - if let Some(mtu) = lines.first() { - Ok(mtu.parse()?) +/// Gets the ifindex from an interface +pub fn get_ifindex(if_name: &str) -> Result { + if cfg!(feature = "integration_test") { + // ip netns exec n-1 cat /sys/class/net/veth-n-1-n-2/iflink + let ns = get_namespace().unwrap(); + let location = format!("/sys/class/net/{if_name}/ifindex"); + let index = run_command("ip", &["netns", "exec", &ns, "cat", &location])?; + + let index = match String::from_utf8(index.stdout) { + Ok(mut s) => { + //this outputs with an extra newline \n on the end which was messing up the next command + s.truncate(s.len() - 1); + s + } + Err(_) => panic!("Could not get index number!"), + }; + info!("location: {:?}, index {:?}", location, index); + + Ok(index.parse().unwrap()) + } else { + let lines = get_lines(&format!("/sys/class/net/{if_name}/ifindex"))?; + if let Some(ifindex) = lines.first() { + Ok(ifindex.parse()?) } else { Err(Error::NoInterfaceError(if_name.to_string())) } } +} - /// Gets the ifindex from an interface - pub fn get_ifindex(&self, if_name: &str) -> Result { - if cfg!(feature = "integration_test") { - // ip netns exec n-1 cat /sys/class/net/veth-n-1-n-2/iflink - let ns = self.get_namespace().unwrap(); - let location = format!("/sys/class/net/{if_name}/ifindex"); - let index = self - .run_command("ip", &["netns", "exec", &ns, "cat", &location]) - .unwrap(); - - let index = match String::from_utf8(index.stdout) { - Ok(mut s) => { - //this outputs with an extra newline \n on the end which was messing up the next command - s.truncate(s.len() - 1); - s - } - Err(_) => panic!("Could not get index number!"), - }; - info!("location: {:?}, index {:?}", location, index); - - Ok(index.parse().unwrap()) - } else { - let lines = get_lines(&format!("/sys/class/net/{if_name}/ifindex"))?; - if let Some(ifindex) = lines.first() { - Ok(ifindex.parse()?) - } else { - Err(Error::NoInterfaceError(if_name.to_string())) - } - } +/// Gets the iflink value from an interface. Physical interfaces have an ifindex and iflink that are +/// identical but if you have a virtual (say DSA) interface then this will be the physical interface name +pub fn get_iflink(if_name: &str) -> Result { + let lines = get_lines(&format!("/sys/class/net/{if_name}/iflink"))?; + if let Some(iflink) = lines.first() { + Ok(iflink.parse()?) + } else { + Err(Error::NoInterfaceError(if_name.to_string())) } +} - /// Gets the iflink value from an interface. Physical interfaces have an ifindex and iflink that are - /// identical but if you have a virtual (say DSA) interface then this will be the physical interface name - pub fn get_iflink(&self, if_name: &str) -> Result { - let lines = get_lines(&format!("/sys/class/net/{if_name}/iflink"))?; - if let Some(iflink) = lines.first() { - Ok(iflink.parse()?) - } else { - Err(Error::NoInterfaceError(if_name.to_string())) - } +/// Sets the mtu of an interface, if this interface is a DSA interface the +/// parent interface will be located and it's mtu increased as appropriate +pub fn set_mtu(if_name: &str, mtu: usize) -> Result<(), Error> { + let ifindex = get_ifindex(if_name)?; + let iflink = get_iflink(if_name)?; + // dsa interface detected, this is an interface controlled by an internal switch + // the parent interface (which has the ifindex value represented in iflink for the child interface) + // needs to have whatever the mtu is, plus room for VLAN headers + const DSA_VLAN_HEADER_SIZE: usize = 8; + if ifindex != iflink { + let parent_if_name = ifindex_to_interface_name(iflink)?; + set_mtu(&parent_if_name, mtu + DSA_VLAN_HEADER_SIZE)?; } - /// Sets the mtu of an interface, if this interface is a DSA interface the - /// parent interface will be located and it's mtu increased as appropriate - pub fn set_mtu(&self, if_name: &str, mtu: usize) -> Result<(), Error> { - let ifindex = self.get_ifindex(if_name)?; - let iflink = self.get_iflink(if_name)?; - // dsa interface detected, this is an interface controlled by an internal switch - // the parent interface (which has the ifindex value represented in iflink for the child interface) - // needs to have whatever the mtu is, plus room for VLAN headers - const DSA_VLAN_HEADER_SIZE: usize = 8; - if ifindex != iflink { - let parent_if_name = self.ifindex_to_interface_name(iflink)?; - self.set_mtu(&parent_if_name, mtu + DSA_VLAN_HEADER_SIZE)?; - } - - let output = self.run_command( - "ip", - &["link", "set", "dev", if_name, "mtu", &mtu.to_string()], - )?; - if !output.stderr.is_empty() { - Err(Error::RuntimeError(format!( - "received error setting interface mtu: {}", - String::from_utf8(output.stderr)? - ))) - } else { - Ok(()) - } + let output = run_command( + "ip", + &["link", "set", "dev", if_name, "mtu", &mtu.to_string()], + )?; + if !output.stderr.is_empty() { + Err(Error::RuntimeError(format!( + "received error setting interface mtu: {}", + String::from_utf8(output.stderr)? + ))) + } else { + Ok(()) } } #[test] fn test_get_wg_remote_ip() { - use crate::KI; - - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "wg"); - assert_eq!(args, &["show", "wg0", "endpoints"]); - Ok(Output { - stdout: b"fvLYbeMV+RYbzJEc4lNEPuK8ulva/5wcSJBz0W5t3hM= 71.8.186.226:60000\ -" - .to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - })); + let stdout = "fvLYbeMV+RYbzJEc4lNEPuK8ulva/5wcSJBz0W5t3hM= 71.8.186.226:60000"; assert_eq!( - KI.get_wg_remote_ip("wg0").unwrap(), + get_wg_remote_ip_internal(stdout.to_string(), "eth8".to_string()).unwrap(), "71.8.186.226".parse::().unwrap() ); - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "wg"); - assert_eq!(args, &["show", "wg0", "endpoints"]); - Ok(Output{ - stdout: b"v5yFYZVfl98N/LRVDK3hbyt5/dK/00VnEGHRBikHHXs= [fe80::78e4:1cff:fe61:560d%veth-1-6]:60000\ -".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - })); + let stdout = + "v5yFYZVfl98N/LRVDK3hbyt5/dK/00VnEGHRBikHHXs= [fe80::78e4:1cff:fe61:560d%veth-1-6]:60000"; assert_eq!( - KI.get_wg_remote_ip("wg0").unwrap(), + get_wg_remote_ip_internal(stdout.to_string(), "eth8".to_string()).unwrap(), "fe80::78e4:1cff:fe61:560d".parse::().unwrap() ); } #[test] fn test_get_ip_addresses_linux() { - use crate::KI; - - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "ip"); - assert_eq!(args, &["address", "show", "dev", "eth8"]); - - Ok(Output { - stdout: b" + let stdout =" 13: eth8: mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 00:e0:4c:67:a1:57 brd ff:ff:ff:ff:ff:ff inet 192.168.1.203/32 scope global eth8 @@ -379,20 +345,14 @@ fn test_get_ip_addresses_linux() { valid_lft forever preferred_lft forever inet6 fe80::2e0:4cff:fe67:a157/64 scope link valid_lft forever preferred_lft forever - " - .to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - })); - - let interfaces = KI.get_ip_from_iface("eth8").unwrap(); + "; + + let interfaces = get_ip_from_iface_internal(stdout.to_string()).unwrap(); let val = ("192.168.1.203".parse().unwrap(), 32); assert!(interfaces.contains(&val)) } #[test] fn test_get_interface_usage() { - use crate::KI; - let _ = KI.get_per_interface_usage().unwrap(); + let _ = get_per_interface_usage().unwrap(); } diff --git a/althea_kernel_interface/src/ip_addr.rs b/althea_kernel_interface/src/ip_addr.rs index 0cd011049..3ee07e4c0 100644 --- a/althea_kernel_interface/src/ip_addr.rs +++ b/althea_kernel_interface/src/ip_addr.rs @@ -1,223 +1,161 @@ use crate::hardware_info::maybe_get_single_line_string; +use crate::interface_tools::get_ipv6_from_iface; use crate::open_tunnel::is_link_local; -use crate::KernelInterface; +use crate::run_command; use crate::KernelInterfaceError as Error; use ipnetwork::IpNetwork; use std::net::IpAddr; use std::net::Ipv4Addr; -impl dyn KernelInterface { - /// Returns a bool based on device state, "UP" or "DOWN", "UNKNOWN" is - /// interpreted as DOWN - pub fn is_iface_up(&self, dev: &str) -> Option { - let path = format!("/sys/class/net/{dev}"); - if let Some(is_up) = maybe_get_single_line_string(&format!("{path}/operstate")) { - let is_up = is_up.contains("up"); - Some(is_up) - } else { - None - } +/// Returns a bool based on device state, "UP" or "DOWN", "UNKNOWN" is +/// interpreted as DOWN +pub fn is_iface_up(dev: &str) -> Option { + let path = format!("/sys/class/net/{dev}"); + if let Some(is_up) = maybe_get_single_line_string(&format!("{path}/operstate")) { + let is_up = is_up.contains("up"); + Some(is_up) + } else { + None } +} - /// Adds an ipv4 address to a given interface, true is returned when - /// the ip is added, false if it is already there and Error if the interface - /// does not exist or some other error has occured - pub fn add_ipv4(&self, ip: Ipv4Addr, dev: &str) -> Result { - // upwrap here because it's ok if we panic when the system does not have 'ip' installed - let output = self - .run_command("ip", &["addr", "add", &format!("{ip}/32"), "dev", dev]) - .unwrap(); - // Get the first line, check if it has "file exists" - match String::from_utf8(output.stderr) { - Ok(stdout) => match stdout.lines().next() { - Some(line) => { - if line.contains("File exists") { - Ok(false) - } else { - Err(Error::RuntimeError(format!("Error setting ip {line}"))) - } +/// Adds an ipv4 address to a given interface, true is returned when +/// the ip is added, false if it is already there and Error if the interface +/// does not exist or some other error has occured +pub fn add_ipv4(ip: Ipv4Addr, dev: &str) -> Result { + let output = run_command("ip", &["addr", "add", &format!("{ip}/32"), "dev", dev])?; + // Get the first line, check if it has "file exists" + match String::from_utf8(output.stderr) { + Ok(stdout) => match stdout.lines().next() { + Some(line) => { + if line.contains("File exists") { + Ok(false) + } else { + Err(Error::RuntimeError(format!("Error setting ip {line}"))) } - None => Ok(true), - }, - Err(e) => Err(Error::RuntimeError(format!( - "Could not decode stderr from ip with {e:?}" - ))), - } - } - - /// After receiving an ipv6 addr from the exit, this function adds that ip - /// to br-lan. SLAAC takes this ip and assigns a /64 to hosts that connect - /// to the router - /// We take a /128 of this ipv6 addr and assign it to ourselves in wg_exit as our own ipv6 addr - pub fn setup_ipv6_slaac(&self, router_ipv6_str: IpNetwork) { - // Get all the v6 addrs on interface - let v6_addrs = match self.get_ipv6_from_iface("br-lan") { - Ok(a) => { - trace!("Our ip list on brlan looks like {:?}", a); - a - } - Err(e) => { - error!("IPV6 ERROR: Unable to parse ips from interface br-lan, didnt not setup slaac: {:?}", e); - return; - } - }; - - for (addr, netmask) in v6_addrs { - let net = IpNetwork::new(IpAddr::V6(addr), netmask) - .expect("Why did we get an invalid addr from kernel?"); - // slaac addr is already set - if net == router_ipv6_str { - break; } + None => Ok(true), + }, + Err(e) => Err(Error::RuntimeError(format!( + "Could not decode stderr from ip with {e:?}" + ))), + } +} - // Remove all previously set ipv6 addrs - if !is_link_local(IpAddr::V6(addr)) { - if let Err(e) = - self.run_command("ip", &["addr", "del", &net.to_string(), "dev", "br-lan"]) - { - error!( - "IPV6 Error: Why are not able to delete the addr {:?} Error {:?}", - net, e - ); - } - } +/// After receiving an ipv6 addr from the exit, this function adds that ip +/// to br-lan. SLAAC takes this ip and assigns a /64 to hosts that connect +/// to the router +/// We take a /128 of this ipv6 addr and assign it to ourselves in wg_exit as our own ipv6 addr +pub fn setup_ipv6_slaac(router_ipv6_str: IpNetwork) { + // Get all the v6 addrs on interface + let v6_addrs = match get_ipv6_from_iface("br-lan") { + Ok(a) => { + trace!("Our ip list on brlan looks like {:?}", a); + a } - - // Add the new ipv6 addr - if let Err(e) = self.run_command( - "ip", - &["addr", "add", &router_ipv6_str.to_string(), "dev", "br-lan"], - ) { - error!( - "IPV6 ERROR: WHy are we unalbe to add the new subnet {:?} Error: {:?}", - router_ipv6_str, e - ); + Err(e) => { + error!("IPV6 ERROR: Unable to parse ips from interface br-lan, didnt not setup slaac: {:?}", e); + return; + } + }; + + for (addr, netmask) in v6_addrs { + let net = IpNetwork::new(IpAddr::V6(addr), netmask) + .expect("Why did we get an invalid addr from kernel?"); + // slaac addr is already set + if net == router_ipv6_str { + break; } - // Do the same thing for wg_exit - let v6_addrs = match self.get_ipv6_from_iface("wg_exit") { - Ok(a) => { - trace!("Our ip list on wg_exit looks like {:?}", a); - a - } - Err(e) => { - error!("IPV6 ERROR: Unable to parse ips from interface wg_exit, didnt not setup ipv6 correctly: {:?}", e); - return; + // Remove all previously set ipv6 addrs + if !is_link_local(IpAddr::V6(addr)) { + if let Err(e) = run_command("ip", &["addr", "del", &net.to_string(), "dev", "br-lan"]) { + error!( + "IPV6 Error: Why are not able to delete the addr {:?} Error {:?}", + net, e + ); } - }; + } + } - for (addr, _netmask) in v6_addrs { - // slaac addr is already set - if addr == router_ipv6_str.ip() { - return; - } + // Add the new ipv6 addr + if let Err(e) = run_command( + "ip", + &["addr", "add", &router_ipv6_str.to_string(), "dev", "br-lan"], + ) { + error!( + "IPV6 ERROR: WHy are we unalbe to add the new subnet {:?} Error: {:?}", + router_ipv6_str, e + ); + } - // Remove all previously set ipv6 addrs - if !is_link_local(IpAddr::V6(addr)) { - if let Err(e) = - self.run_command("ip", &["addr", "del", &addr.to_string(), "dev", "wg_exit"]) - { - error!( - "IPV6 Error: Why are not able to delete the addr {:?} Error {:?}", - addr, e - ); - } - } + // Do the same thing for wg_exit + let v6_addrs = match get_ipv6_from_iface("wg_exit") { + Ok(a) => { + trace!("Our ip list on wg_exit looks like {:?}", a); + a + } + Err(e) => { + error!("IPV6 ERROR: Unable to parse ips from interface wg_exit, didnt not setup ipv6 correctly: {:?}", e); + return; } + }; - // Add the new ipv6 addr - if let Err(e) = self.run_command( - "ip", - &[ - "addr", - "add", - &router_ipv6_str.ip().to_string(), - "dev", - "wg_exit", - ], - ) { - error!( - "IPV6 ERROR: WHy are we unalbe to add the new ip {:?} Error: {:?}", - router_ipv6_str, e - ); + for (addr, _netmask) in v6_addrs { + // slaac addr is already set + if addr == router_ipv6_str.ip() { + return; } - } - /// Adds an ipv4 address to a given interface, true is returned when - /// the ip is added, false if it is already there and Error if the interface - /// does not exist or some other error has occured - pub fn add_ipv4_mask(&self, ip: Ipv4Addr, mask: u32, dev: &str) -> Result { - // upwrap here because it's ok if we panic when the system does not have 'ip' installed - let output = self - .run_command("ip", &["addr", "add", &format!("{ip}/{mask}"), "dev", dev]) - .unwrap(); - // Get the first line, check if it has "file exists" - match String::from_utf8(output.stderr) { - Ok(stdout) => match stdout.lines().next() { - Some(line) => { - if line.contains("File exists") { - Ok(false) - } else { - Err(Error::RuntimeError(format!("Error setting ip {line}"))) - } - } - None => Ok(true), - }, - Err(e) => Err(Error::RuntimeError(format!( - "Could not decode stderr from ip with {e:?}" - ))), + // Remove all previously set ipv6 addrs + if !is_link_local(IpAddr::V6(addr)) { + if let Err(e) = run_command("ip", &["addr", "del", &addr.to_string(), "dev", "wg_exit"]) + { + error!( + "IPV6 Error: Why are not able to delete the addr {:?} Error {:?}", + addr, e + ); + } } } -} - -#[test] -fn test_add_ipv4() { - use crate::KI; - - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "ip"); - assert_eq!(args, &["addr", "add", "192.168.31.2/32", "dev", "eth0"]); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"RTNETLINK answers: File exists".to_vec(), - status: ExitStatus::from_raw(0), - }) - })); - let val = KI - .add_ipv4("192.168.31.2".parse().unwrap(), "eth0") - .expect("Failure to run ip test"); - assert!(!val); - - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "ip"); - assert_eq!(args, &["addr", "add", "192.168.31.2/32", "dev", "eth0"]); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"Cannot find device \"eth0\"".to_vec(), - status: ExitStatus::from_raw(0), - }) - })); - let val = KI.add_ipv4("192.168.31.2".parse().unwrap(), "eth0"); - assert!(val.is_err()); - - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "ip"); - assert_eq!(args, &["addr", "add", "192.168.31.2/32", "dev", "eth0"]); + // Add the new ipv6 addr + if let Err(e) = run_command( + "ip", + &[ + "addr", + "add", + &router_ipv6_str.ip().to_string(), + "dev", + "wg_exit", + ], + ) { + error!( + "IPV6 ERROR: WHy are we unalbe to add the new ip {:?} Error: {:?}", + router_ipv6_str, e + ); + } +} - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - })); - let val = KI - .add_ipv4("192.168.31.2".parse().unwrap(), "eth0") - .expect("Failure to run ip test"); - assert!(val); +/// Adds an ipv4 address to a given interface, true is returned when +/// the ip is added, false if it is already there and Error if the interface +/// does not exist or some other error has occured +pub fn add_ipv4_mask(ip: Ipv4Addr, mask: u32, dev: &str) -> Result { + let output = run_command("ip", &["addr", "add", &format!("{ip}/{mask}"), "dev", dev])?; + // Get the first line, check if it has "file exists" + match String::from_utf8(output.stderr) { + Ok(stdout) => match stdout.lines().next() { + Some(line) => { + if line.contains("File exists") { + Ok(false) + } else { + Err(Error::RuntimeError(format!("Error setting ip {line}"))) + } + } + None => Ok(true), + }, + Err(e) => Err(Error::RuntimeError(format!( + "Could not decode stderr from ip with {e:?}" + ))), + } } diff --git a/althea_kernel_interface/src/ip_neigh.rs b/althea_kernel_interface/src/ip_neigh.rs index e759396d5..991fb2a35 100644 --- a/althea_kernel_interface/src/ip_neigh.rs +++ b/althea_kernel_interface/src/ip_neigh.rs @@ -1,31 +1,29 @@ -use crate::KernelInterface; +use crate::run_command; use mac_address::MacAddress; use std::io::{Error, ErrorKind}; use std::net::IpAddr; -impl dyn KernelInterface { - /// Runs the ip neigh command via the Kernel interface lazy static and returns an error if it doesn't work - pub fn grab_ip_neigh(&self) -> Result, std::io::Error> { - info!("Sending ip neigh command to kernel"); - let res = self.run_command("ip", &["neigh"]); - match res { - Ok(output) => { - // Extra checking since output struct is ambigious on how it works - if !output.stdout.is_empty() { - let string_to_parse = String::from_utf8_lossy(&output.stdout).to_string(); - Ok(parse_ip_neigh(string_to_parse)) - } else { - Err(Error::new( - ErrorKind::Other, - "Empty ip neigh command. Failed".to_string(), - )) - } +/// Runs the ip neigh command via the Kernel interface lazy static and returns an error if it doesn't work +pub fn grab_ip_neigh() -> Result, std::io::Error> { + info!("Sending ip neigh command to kernel"); + let res = run_command("ip", &["neigh"]); + match res { + Ok(output) => { + // Extra checking since output struct is ambigious on how it works + if !output.stdout.is_empty() { + let string_to_parse = String::from_utf8_lossy(&output.stdout).to_string(); + Ok(parse_ip_neigh(string_to_parse)) + } else { + Err(Error::new( + ErrorKind::Other, + "Empty ip neigh command. Failed".to_string(), + )) } - Err(e) => Err(Error::new( - ErrorKind::Other, - format!("Unable to grab ip neigh from router. Failed with error {e:?}"), - )), } + Err(e) => Err(Error::new( + ErrorKind::Other, + format!("Unable to grab ip neigh from router. Failed with error {e:?}"), + )), } } diff --git a/althea_kernel_interface/src/ip_route.rs b/althea_kernel_interface/src/ip_route.rs index 8e14e00f4..ef8d11c32 100644 --- a/althea_kernel_interface/src/ip_route.rs +++ b/althea_kernel_interface/src/ip_route.rs @@ -1,4 +1,4 @@ -use crate::KernelInterface; +use crate::run_command; use crate::KernelInterfaceError as Error; use althea_types::FromStr; use std::fmt::Display; @@ -190,141 +190,118 @@ impl Display for IpRoute { } } -impl dyn KernelInterface { - /// Gets the default route, returns Error if the command fails and None - /// if no default route is set - pub fn get_default_route(&self) -> Result, Error> { - let output = self.run_command("ip", &["route", "list", "default"])?; - - let stdout = String::from_utf8(output.stdout).unwrap(); - // return the first valid default route that correctly parses into a route - // there can be multiple default routes with different metrics but ip is kind - // enough to always put the lowest metric (aka the 'best' one) to the top - for line in stdout.lines() { - match line.parse() { - Ok(route) => { - if let IpRoute::DefaultRoute(r) = route { - return Ok(Some(r)); - } +/// Internal unit testing function for get_default_route() +fn get_default_route_internal(stdout: String) -> Result, Error> { + // return the first valid default route that correctly parses into a route + // there can be multiple default routes with different metrics but ip is kind + // enough to always put the lowest metric (aka the 'best' one) to the top + for line in stdout.lines() { + match line.parse() { + Ok(route) => { + if let IpRoute::DefaultRoute(r) = route { + return Ok(Some(r)); } - Err(e) => error!("Failed to parse route! {:?}", e), } + Err(e) => error!("Failed to parse route! {:?}", e), } - Ok(None) } + Ok(None) +} - pub fn set_route(&self, to: &IpRoute) -> Result<(), Error> { - let to = to.to_string(); - let to: Vec<&str> = to.split_whitespace().collect(); - let mut def_route = vec!["route", "add"]; - def_route.extend(to); - self.run_command("ip", &def_route)?; - Ok(()) - } +/// Gets the default route, returns Error if the command fails and None +/// if no default route is set +pub fn get_default_route() -> Result, Error> { + let output = run_command("ip", &["route", "list", "default"])?; - /// Updates the settings default route, returns true if an edit to the settings has been performed - pub fn update_settings_route( - &self, - settings_default_route: &mut Option, - ) -> Result { - let def_route = match self.get_default_route()? { - Some(route) => route, - None => return Ok(false), - }; - if !def_route.is_althea_default_route() { - // update the default route if default route is not wg exit - *settings_default_route = Some(def_route); - Ok(true) - } else { - Ok(false) - } + let stdout = String::from_utf8(output.stdout).unwrap(); + get_default_route_internal(stdout) +} + +pub fn set_route(to: &IpRoute) -> Result<(), Error> { + let to = to.to_string(); + let to: Vec<&str> = to.split_whitespace().collect(); + let mut def_route = vec!["route", "add"]; + def_route.extend(to); + run_command("ip", &def_route)?; + Ok(()) +} + +/// Updates the settings default route, returns true if an edit to the settings has been performed +pub fn update_settings_route( + settings_default_route: &mut Option, +) -> Result { + let def_route = match get_default_route()? { + Some(route) => route, + None => return Ok(false), + }; + if !def_route.is_althea_default_route() { + // update the default route if default route is not wg exit + *settings_default_route = Some(def_route); + Ok(true) + } else { + Ok(false) } +} - /// sets the manual route for a peer using ip route, returns true if the settings - /// have been updated - pub fn manual_peers_route( - &self, - endpoint_ip: &IpAddr, - settings_default_route: &mut Option, - ) -> Result { - let changed = self.update_settings_route(settings_default_route)?; - match settings_default_route { - Some(d) => { - self.set_route(&IpRoute::ToSubnet(ToSubnet { - dst: *endpoint_ip, - subnet: 32, - via: Some(d.via), - nic: d.nic.to_string(), - proto: Some("static".to_string()), - metric: None, - src: None, - scope: None, - }))?; - Ok(changed) - } - // no default route, nothing to do - None => Ok(changed), +/// sets the manual route for a peer using ip route, returns true if the settings +/// have been updated +pub fn manual_peers_route( + endpoint_ip: &IpAddr, + settings_default_route: &mut Option, +) -> Result { + let changed = update_settings_route(settings_default_route)?; + match settings_default_route { + Some(d) => { + set_route(&IpRoute::ToSubnet(ToSubnet { + dst: *endpoint_ip, + subnet: 32, + via: Some(d.via), + nic: d.nic.to_string(), + proto: Some("static".to_string()), + metric: None, + src: None, + scope: None, + }))?; + Ok(changed) } + // no default route, nothing to do + None => Ok(changed), } +} - /// restore the default route, if we find a default route is already in place that is not - /// our wg_exit route we grab that one, save it off, and make no changes. - pub fn restore_default_route( - &self, - settings_default_route: &mut Option, - ) -> Result<(), Error> { - let current_route = self.get_default_route()?; - match current_route.clone() { - Some(d) => { - if d.is_althea_default_route() { - // if we didn't have a default route already it's restored by default - if let Some(route) = settings_default_route { - self.set_route(&IpRoute::DefaultRoute(route.clone()))?; - } - } else { - *settings_default_route = Some(current_route.unwrap()); - } - } - None => { +/// restore the default route, if we find a default route is already in place that is not +/// our wg_exit route we grab that one, save it off, and make no changes. +pub fn restore_default_route( + settings_default_route: &mut Option, +) -> Result<(), Error> { + let current_route = get_default_route()?; + match current_route.clone() { + Some(d) => { + if d.is_althea_default_route() { // if we didn't have a default route already it's restored by default if let Some(route) = settings_default_route { - self.set_route(&IpRoute::DefaultRoute(route.clone()))?; + set_route(&IpRoute::DefaultRoute(route.clone()))?; } + } else { + *settings_default_route = Some(current_route.unwrap()); } - }; - Ok(()) - } + } + None => { + // if we didn't have a default route already it's restored by default + if let Some(route) = settings_default_route { + set_route(&IpRoute::DefaultRoute(route.clone()))?; + } + } + }; + Ok(()) } #[test] //"default", "via", "64.146.145.5", "dev", "eth0", "proto", "static", "linkdown" fn test_get_default_route_invalid() { - use crate::KI; - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - let mut counter = 0; - - KI.set_mock(Box::new(move |program, args| { - counter += 1; - match counter { - 1 => { - assert_eq!(program, "ip"); - assert_eq!(args, vec!["route", "list", "default"]); - Ok(Output { - stdout: b"1.2.3.4/16 dev interface scope link metric 1000".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("Unexpected call {} {:?} {:?}", counter, program, args), - } - })); - - assert!( - KI.get_default_route().unwrap().is_none(), - "Invalid `ip route` unexpectedly returned a valid route" - ); + let route = "1.2.3.4/16 dev interface scope link metric 1000"; + let res = get_default_route_internal(route.to_string()); + assert!(res.unwrap().is_none()) } #[test] @@ -346,41 +323,16 @@ fn test_parse_routes() { #[test] fn test_get_default_route() { - use crate::KI; - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - let mut counter = 0; - - // This will mock `run_command` to run a real output of `ip route` - // with addition that there are additional spaces, more than one default - // route etc. - KI.set_mock(Box::new(move |program, args| { - counter += 1; - match counter { - 1 => { - assert_eq!(program, "ip"); - assert_eq!(args, vec!["route", "list", "default"]); - Ok(Output { - stdout: b" + let stdout = " 169.254.0.0/16 dev wifiinterface scope link metric 1000 172.16.82.0/24 dev vmnet1 proto kernel scope link src 172.16.82.1 default via 192.168.8.1 dev wifiinterface proto dhcp metric 600 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown 192.168.8.0/24 dev wifiinterface proto kernel scope link src 192.168.8.175 metric 600 default via 192.168.9.1 dev wifiinterface proto dhcp metric 1200 -192.168.36.0/24 dev vmnet8 proto kernel scope link src 192.168.36.1" - .to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("Unexpected call {} {:?} {:?}", counter, program, args), - } - })); +192.168.36.0/24 dev vmnet8 proto kernel scope link src 192.168.36.1"; - let result = KI - .get_default_route() + let result = get_default_route_internal(stdout.to_string()) .expect("Unable to get default route") .unwrap(); let correct = DefaultRoute { @@ -395,111 +347,6 @@ default via 192.168.9.1 dev wifiinterface proto dhcp metric 1200 #[test] fn test_set_route() { - use crate::KI; - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - let mut counter = 0; - - KI.set_mock(Box::new(move |program, args| { - counter += 1; - match counter { - 1 => { - assert_eq!(program, "ip"); - assert_eq!( - args, - vec![ - "route", - "add", - "127.0.0.1", - "via", - "127.0.0.2", - "dev", - "eno3p", - "proto", - "static" - ] - ); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 2 => { - assert_eq!(program, "ip"); - assert_eq!( - args, - vec![ - "route", - "add", - "127.0.0.1", - "via", - "127.0.0.2", - "dev", - "eno3p", - "proto", - "static" - ] - ); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 3 => { - assert_eq!(program, "ip"); - assert_eq!( - args, - vec![ - "route", - "add", - "127.0.0.1/24", - "dev", - "eno3p", - "proto", - "static" - ] - ); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 4 => { - assert_eq!(program, "ip"); - assert_eq!( - args, - vec![ - "route", - "add", - "127.0.0.1/24", - "via", - "127.0.0.2", - "dev", - "eno3p", - "proto", - "static", - "src", - "127.0.0.2" - ] - ); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("Unexpected call {} {:?} {:?}", counter, program, args), - } - })); - // as it would be normally parsed let route = IpRoute::ToSubnet(ToSubnet { dst: "127.0.0.1".parse().unwrap(), @@ -511,9 +358,10 @@ fn test_set_route() { metric: None, scope: None, }); - // without proto we insert proto static to prevent other - // programs from messing with our routes so it should still be there - KI.set_route(&route).expect("Unable to set route"); + assert_eq!( + route.to_string(), + "127.0.0.1 via 127.0.0.2 dev eno3p proto static " + ); let route = IpRoute::ToSubnet(ToSubnet { dst: "127.0.0.1".parse().unwrap(), subnet: 32, @@ -524,7 +372,10 @@ fn test_set_route() { metric: None, scope: None, }); - KI.set_route(&route).expect("Unable to set route"); + assert_eq!( + route.to_string(), + "127.0.0.1 via 127.0.0.2 dev eno3p proto static " + ); // without via, with subnet let route = IpRoute::ToSubnet(ToSubnet { dst: "127.0.0.1".parse().unwrap(), @@ -536,7 +387,7 @@ fn test_set_route() { metric: None, scope: None, }); - KI.set_route(&route).expect("Unable to set route"); + assert_eq!(route.to_string(), "127.0.0.1/24 dev eno3p proto static "); // without via, with subnet with other options let route = IpRoute::ToSubnet(ToSubnet { dst: "127.0.0.1".parse().unwrap(), @@ -550,71 +401,14 @@ fn test_set_route() { // will be ignored scope: Some("link".to_string()), }); - KI.set_route(&route).expect("Unable to set route"); + assert_eq!( + route.to_string(), + "127.0.0.1/24 via 127.0.0.2 dev eno3p proto static src 127.0.0.2 " + ); } #[test] fn test_set_default_route() { - use crate::KI; - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - let mut counter = 0; - - KI.set_mock(Box::new(move |program, args| { - counter += 1; - match counter { - 1 => { - assert_eq!(program, "ip"); - assert_eq!( - args, - vec![ - "route", - "add", - "default", - "via", - "192.168.8.1", - "dev", - "wifiinterface", - "proto", - "static" - ] - ); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 2 => { - assert_eq!(program, "ip"); - assert_eq!( - args, - vec![ - "route", - "add", - "default", - "via", - "192.168.8.1", - "dev", - "wifiinterface", - "proto", - "static", - "src", - "127.0.0.2" - ] - ); - - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("Unexpected call {} {:?} {:?}", counter, program, args), - } - })); let correct = IpRoute::DefaultRoute(DefaultRoute { via: "192.168.8.1".parse().unwrap(), nic: "wifiinterface".to_string(), @@ -622,8 +416,10 @@ fn test_set_default_route() { metric: Some(600), src: None, }); - - KI.set_route(&correct).expect("Unable to set default route"); + assert_eq!( + correct.to_string(), + "default via 192.168.8.1 dev wifiinterface proto static" + ); let correct = IpRoute::DefaultRoute(DefaultRoute { via: "192.168.8.1".parse().unwrap(), @@ -632,6 +428,8 @@ fn test_set_default_route() { metric: Some(600), src: Some("127.0.0.2".parse().unwrap()), }); - - KI.set_route(&correct).expect("Unable to set default route"); + assert_eq!( + correct.to_string(), + "default via 192.168.8.1 dev wifiinterface proto static src 127.0.0.2" + ); } diff --git a/althea_kernel_interface/src/iptables.rs b/althea_kernel_interface/src/iptables.rs index 80e284733..6b849bd66 100644 --- a/althea_kernel_interface/src/iptables.rs +++ b/althea_kernel_interface/src/iptables.rs @@ -1,54 +1,36 @@ -use crate::KernelInterface; -use crate::KernelInterfaceError; +use crate::{run_command, KernelInterfaceError}; -impl dyn KernelInterface { - /// -I and -A will be checked to see if they already exist before adding. - pub fn add_iptables_rule( - &self, - command: &str, - rule: &[&str], - ) -> Result<(), KernelInterfaceError> { - assert!(rule.contains(&"-A") || rule.contains(&"-I") || rule.contains(&"-D")); +/// -I and -A will be checked to see if they already exist before adding. +pub fn add_iptables_rule(command: &str, rule: &[&str]) -> Result<(), KernelInterfaceError> { + assert!(rule.contains(&"-A") || rule.contains(&"-I") || rule.contains(&"-D")); - // we replace the add or append commands with a check command so that we can see if the rule is actually present - // if it is then we don't need to do anything - let mut new_command = Vec::new(); - let mut i_pos_skip = None; + // we replace the add or append commands with a check command so that we can see if the rule is actually present + // if it is then we don't need to do anything + let mut new_command = Vec::new(); + let mut i_pos_skip = None; - for (i, rul) in rule.iter().enumerate() { - if i_pos_skip.is_some() && i == i_pos_skip.unwrap() { - continue; - } - if *rul == "-I" { - // if there's an insertion index, then skip it for the check statement - if rule[i + 2].parse::().is_ok() { - i_pos_skip = Some(i + 2); - } - new_command.push("-C"); - } else if *rul == "-A" { - new_command.push("-C"); - } else { - new_command.push(rul); - } + for (i, rul) in rule.iter().enumerate() { + if i_pos_skip.is_some() && i == i_pos_skip.unwrap() { + continue; } - - let check = self.run_command(command, &new_command)?; - - if !check.status.success() { - self.run_command(command, rule)?; + if *rul == "-I" { + // if there's an insertion index, then skip it for the check statement + if rule[i + 2].parse::().is_ok() { + i_pos_skip = Some(i + 2); + } + new_command.push("-C"); + } else if *rul == "-A" { + new_command.push("-C"); + } else { + new_command.push(rul); } - - Ok(()) } - pub fn check_iptable_rule( - &self, - command: &str, - rule: &[&str], - ) -> Result { - assert!(rule.contains(&"-C")); + let check = run_command(command, &new_command)?; - let check = self.run_command(command, rule)?; - Ok(check.status.success()) + if !check.status.success() { + run_command(command, rule)?; } + + Ok(()) } diff --git a/althea_kernel_interface/src/is_openwrt.rs b/althea_kernel_interface/src/is_openwrt.rs index ac6c6791c..29cf45d87 100644 --- a/althea_kernel_interface/src/is_openwrt.rs +++ b/althea_kernel_interface/src/is_openwrt.rs @@ -1,15 +1,12 @@ -use super::KernelInterface; use std::process::{Command, Stdio}; -impl dyn KernelInterface { - /// Checks if the local system is openwrt - pub fn is_openwrt(&self) -> bool { - let uname = Command::new("cat") - .args(["/etc/openwrt_release"]) - .stdout(Stdio::piped()) - .output() - .unwrap(); - let uname_results = String::from_utf8(uname.stdout).unwrap(); - uname_results.contains("OpenWrt") - } +/// Checks if the local system is openwrt +pub fn is_openwrt() -> bool { + let uname = Command::new("cat") + .args(["/etc/openwrt_release"]) + .stdout(Stdio::piped()) + .output() + .unwrap(); + let uname_results = String::from_utf8(uname.stdout).unwrap(); + uname_results.contains("OpenWrt") } diff --git a/althea_kernel_interface/src/lib.rs b/althea_kernel_interface/src/lib.rs index a292e9ab9..28b9c946f 100644 --- a/althea_kernel_interface/src/lib.rs +++ b/althea_kernel_interface/src/lib.rs @@ -5,6 +5,8 @@ extern crate log; #[macro_use] extern crate serde_derive; +use std::str; +use std::str::Utf8Error; use std::{env, fmt}; use std::{ error::Error, @@ -15,45 +17,38 @@ use std::{ num::ParseFloatError, process::{Command, Output}, }; -use std::{ - str::Utf8Error, - sync::{Arc, Mutex}, -}; - -use std::str; -mod babel; +pub mod babel; pub mod bridge_tools; -mod check_cron; -mod counter; -mod create_wg_key; -mod delete_tunnel; -mod dns; +pub mod check_cron; +pub mod counter; +pub mod create_wg_key; +pub mod dns; pub mod exit_client_tunnel; -mod exit_server_tunnel; +pub mod exit_server_tunnel; pub mod file_io; -mod fs_sync; +pub mod fs_sync; mod get_neighbors; pub mod hardware_info; -mod interface_tools; -mod ip_addr; +pub mod interface_tools; +pub mod ip_addr; pub mod ip_neigh; -mod ip_route; +pub mod ip_route; mod iptables; -mod is_openwrt; -mod link_local_tools; -mod manipulate_uci; +pub mod is_openwrt; +pub mod link_local_tools; +pub mod manipulate_uci; mod netfilter; pub mod netns; pub mod open_tunnel; -mod openwrt_ubus; +pub mod openwrt_ubus; pub mod opkg_feeds; -mod ping_check; -mod set_system_password; -mod setup_wg_if; +pub mod ping_check; +pub mod set_system_password; +pub mod setup_wg_if; pub mod time; -mod traffic_control; -mod udp_socket_table; +pub mod traffic_control; +pub mod udp_socket_table; pub mod upgrade; pub mod wg_iface_counter; @@ -72,9 +67,6 @@ use std::io::Error as IoError; use std::net::AddrParseError; use std::string::FromUtf8Error; -type CommandFunction = - Box) -> Result + Send>; - #[derive(Clone, Debug)] pub enum KernelInterfaceError { RuntimeError(String), @@ -194,25 +186,6 @@ impl From for KernelInterfaceError { } } -#[cfg(test)] -lazy_static! { - pub static ref KI: Box = Box::new(TestCommandRunner { - run_command: Arc::new(Mutex::new(Box::new(|_program, _args| { - panic!("kernel interface used before initialized"); - }))) - }); -} - -#[cfg(not(test))] -lazy_static! { - pub static ref KI: Box = Box::new(LinuxCommandRunner {}); -} - -pub trait CommandRunner { - fn run_command(&self, program: &str, args: &[&str]) -> Result; - fn set_mock(&self, mock: CommandFunction); -} - // a quick throwaway function to print arguments arrays so that they can be copy/pasted from logs fn print_str_array(input: &[&str]) -> String { let mut output = String::new(); @@ -222,86 +195,51 @@ fn print_str_array(input: &[&str]) -> String { output } -pub struct LinuxCommandRunner; - -impl CommandRunner for LinuxCommandRunner { - fn run_command(&self, program: &str, args: &[&str]) -> Result { - let start = Instant::now(); - let output = match Command::new(program).args(args).output() { - Ok(o) => o, - Err(e) => { - if e.kind() == ErrorKind::NotFound { - error!("The {:?} binary was not found. Please install a package that provides it. PATH={:?}", program, env::var("PATH")); - } - return Err(e.into()); +pub fn run_command(program: &str, args: &[&str]) -> Result { + let start = Instant::now(); + let output = match Command::new(program).args(args).output() { + Ok(o) => o, + Err(e) => { + if e.kind() == ErrorKind::NotFound { + error!("The {:?} binary was not found. Please install a package that provides it. PATH={:?}", program, env::var("PATH")); } - }; - + return Err(e.into()); + } + }; + + trace!( + "Command {} {} returned: {:?}", + program, + print_str_array(args), + output + ); + if !output.status.success() { trace!( - "Command {} {} returned: {:?}", + "Command {} {} returned: an error {:?}", program, print_str_array(args), output ); - if !output.status.success() { - trace!( - "Command {} {} returned: an error {:?}", - program, - print_str_array(args), - output - ); - } - trace!( - "command completed in {}s {}ms", - start.elapsed().as_secs(), - start.elapsed().subsec_millis() - ); - - if start.elapsed().as_secs() > 5 { - error!( - "Command {} {} took more than five seconds to complete!", - program, - print_str_array(args) - ); - } else if start.elapsed().as_secs() > 1 { - warn!( - "Command {} {} took more than one second to complete!", - program, - print_str_array(args) - ); - } - - Ok(output) - } - - fn set_mock( - &self, - _mock: Box) -> Result + Send>, - ) { - unimplemented!() } -} - -pub struct TestCommandRunner { - pub run_command: Arc>, -} - -impl CommandRunner for TestCommandRunner { - fn run_command(&self, program: &str, args: &[&str]) -> Result { - let mut args_owned = Vec::new(); - for a in args { - args_owned.push((*a).to_string()) - } - - (*self.run_command.lock().unwrap())(program.to_string(), args_owned) + trace!( + "command completed in {}s {}ms", + start.elapsed().as_secs(), + start.elapsed().subsec_millis() + ); + + if start.elapsed().as_secs() > 5 { + error!( + "Command {} {} took more than five seconds to complete!", + program, + print_str_array(args) + ); + } else if start.elapsed().as_secs() > 1 { + warn!( + "Command {} {} took more than one second to complete!", + program, + print_str_array(args) + ); } - fn set_mock(&self, mock: CommandFunction) { - *self.run_command.lock().unwrap() = mock - } + Ok(output) } - -pub trait KernelInterface: CommandRunner + Sync + Send {} - -impl KernelInterface for LinuxCommandRunner {} -impl KernelInterface for TestCommandRunner {} diff --git a/althea_kernel_interface/src/link_local_tools.rs b/althea_kernel_interface/src/link_local_tools.rs index 75113dbcf..abf941704 100644 --- a/althea_kernel_interface/src/link_local_tools.rs +++ b/althea_kernel_interface/src/link_local_tools.rs @@ -1,4 +1,4 @@ -use crate::{file_io::get_lines, KernelInterface, KernelInterfaceError}; +use crate::{file_io::get_lines, get_neighbors::get_neighbors, run_command, KernelInterfaceError}; use regex::Regex; use std::{ fs::read_dir, @@ -57,174 +57,138 @@ fn get_link_local_device_ip_internal( )) } -impl dyn KernelInterface { - fn get_proc_net_path(&self) -> String { - if cfg!(feature = "integration_test") { - // this is extremely overcomplicated and needs to be replaced by netlink at some point - // essentially we find the namespace file of a process spanwed in the namespace (babel) - // and then read it, only works in integration tests. +fn get_proc_net_path() -> String { + if cfg!(feature = "integration_test") { + // this is extremely overcomplicated and needs to be replaced by netlink at some point + // essentially we find the namespace file of a process spanwed in the namespace (babel) + // and then read it, only works in integration tests. - let ns = self.run_command("ip", &["netns", "identify"]).unwrap(); - let ns = match String::from_utf8(ns.stdout) { - Ok(s) => s, - Err(_) => panic!("Could not get netns name!"), - }; - let ns = ns.trim(); - - let links = read_dir("/proc/").unwrap(); - - // in the legacy test namespaces are netlab-1 but interfaces are veth-1-2 - // so this lets us do the conversion - let legacy_test_ns_number = ns.strip_prefix("netlab-"); - - for dir in links { - let dir = dir.unwrap(); - if dir.path().is_dir() { - let dir_name = dir.file_name().to_str().unwrap().to_string(); - // we're looking for a pid - if let Ok(number) = dir_name.trim().parse() { - let number: u32 = number; - let path = format!("/proc/{number}/net/if_inet6"); - if let Ok(lines) = get_lines(&path) { - for line in lines { - let line = line.split_ascii_whitespace().last().unwrap(); - let prefix = - if let Some(legacy_test_ns_number) = legacy_test_ns_number { - format!("veth-{legacy_test_ns_number}") - } else { - format!("veth-{ns}") - }; - if line.starts_with(&prefix) { - return path; - } + let ns = run_command("ip", &["netns", "identify"]).unwrap(); + let ns = match String::from_utf8(ns.stdout) { + Ok(s) => s, + Err(_) => panic!("Could not get netns name!"), + }; + let ns = ns.trim(); + + let links = read_dir("/proc/").unwrap(); + + // in the legacy test namespaces are netlab-1 but interfaces are veth-1-2 + // so this lets us do the conversion + let legacy_test_ns_number = ns.strip_prefix("netlab-"); + + for dir in links { + let dir = dir.unwrap(); + if dir.path().is_dir() { + let dir_name = dir.file_name().to_str().unwrap().to_string(); + // we're looking for a pid + if let Ok(number) = dir_name.trim().parse() { + let number: u32 = number; + let path = format!("/proc/{number}/net/if_inet6"); + if let Ok(lines) = get_lines(&path) { + for line in lines { + let line = line.split_ascii_whitespace().last().unwrap(); + let prefix = if let Some(legacy_test_ns_number) = legacy_test_ns_number + { + format!("veth-{legacy_test_ns_number}") + } else { + format!("veth-{ns}") + }; + if line.starts_with(&prefix) { + return path; } } } } } - panic!( - "We did not find the babel process to locate this rita threads namespace {}!", - ns - ); - } else { - // standard location - "/proc/net/if_inet6".to_string() } + panic!( + "We did not find the babel process to locate this rita threads namespace {}!", + ns + ); + } else { + // standard location + "/proc/net/if_inet6".to_string() } +} - /// This gets our link local ip for a given device - pub fn get_link_local_device_ip(&self, dev: &str) -> Result { - let path = self.get_proc_net_path(); - let lines = get_lines(&path)?; - get_link_local_device_ip_internal(lines, dev, true) - } - - /// This gets our global ip for a given device - pub fn get_global_device_ip(&self, dev: &str) -> Result { - let path = self.get_proc_net_path(); - let lines = get_lines(&path)?; - get_link_local_device_ip_internal(lines, dev, false) - } +/// This gets our link local ip for a given device +pub fn get_link_local_device_ip(dev: &str) -> Result { + let path = get_proc_net_path(); + let lines = get_lines(&path)?; + get_link_local_device_ip_internal(lines, dev, true) +} - pub fn get_global_device_ip_v4(&self, dev: &str) -> Result { - let output = self.run_command("ip", &["addr", "show", "dev", dev, "scope", "global"])?; - trace!("Got {:?} from `ip addr`", output); +/// This gets our global ip for a given device +pub fn get_global_device_ip(dev: &str) -> Result { + let path = get_proc_net_path(); + let lines = get_lines(&path)?; + get_link_local_device_ip_internal(lines, dev, false) +} - lazy_static! { - static ref RE: Regex = Regex::new(r"inet (\S*?)(/[0-9]+)? scope global") - .expect("Unable to compile regular expression"); - } +pub fn get_global_device_ip_v4(dev: &str) -> Result { + let output = run_command("ip", &["addr", "show", "dev", dev, "scope", "global"])?; + trace!("Got {:?} from `ip addr`", output); - let cap_str = String::from_utf8(output.stdout)?; - let cap = RE.captures(&cap_str); - if let Some(cap) = cap { - trace!("got global IP of {} from device {}", &cap[1], &dev); - Ok(cap[1].parse::()?) - } else { - Err(KernelInterfaceError::RuntimeError( - "No global found or no interface found".to_string(), - )) - } + lazy_static! { + static ref RE: Regex = Regex::new(r"inet (\S*?)(/[0-9]+)? scope global") + .expect("Unable to compile regular expression"); } - /// Given a neighboring link local ip, return the device name - pub fn get_device_name(&self, their_ip: IpAddr) -> Result { - let neigh = self.get_neighbors()?; - trace!("looking for {:?} in {:?} for device name", their_ip, neigh); - for (ip, dev) in neigh { - if ip == their_ip { - return Ok(dev); - } - } - + let cap_str = String::from_utf8(output.stdout)?; + let cap = RE.captures(&cap_str); + if let Some(cap) = cap { + trace!("got global IP of {} from device {}", &cap[1], &dev); + Ok(cap[1].parse::()?) + } else { Err(KernelInterfaceError::RuntimeError( - "Address not found in neighbors".to_string(), + "No global found or no interface found".to_string(), )) } +} - /// This gets our link local ip that can be reached by another node with link local ip - pub fn get_reply_ip( - &self, - their_ip: Ipv6Addr, - external_interface: Option, - ) -> Result { - let neigh = self.get_neighbors()?; - - trace!("Looking for {:?} in {:?} for reply ip", their_ip, neigh); - for (ip, dev) in neigh { - if ip == their_ip { - return self.get_link_local_device_ip(&dev); - } - } - - if let Some(external_interface) = external_interface { - let global_ip = self.get_global_device_ip(&external_interface)?; - trace!( - "Didn't find {:?} in neighbors, sending global ip {:?}", - their_ip, - global_ip - ); - Ok(global_ip) - } else { - trace!("Didn't find {:?} in neighbors, bailing out", their_ip); - Err(KernelInterfaceError::RuntimeError( - "Address not found in neighbors".to_string(), - )) +/// Given a neighboring link local ip, return the device name +pub fn get_device_name(their_ip: IpAddr) -> Result { + let neigh = get_neighbors()?; + trace!("looking for {:?} in {:?} for device name", their_ip, neigh); + for (ip, dev) in neigh { + if ip == their_ip { + return Ok(dev); } } -} -#[test] -fn test_get_device_name_linux() { - use crate::KI; - - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "ip"); - assert_eq!(args, &["neigh"]); + Err(KernelInterfaceError::RuntimeError( + "Address not found in neighbors".to_string(), + )) +} - Ok(Output { - stdout: b"10.0.2.2 dev eth0 lladdr 00:00:00:aa:00:03 STALE -10.0.0.2 dev eth0 FAILED -10.0.1.2 dev eth0 lladdr 00:00:00:aa:00:05 REACHABLE -2001::2 dev eth0 lladdr 00:00:00:aa:00:56 REACHABLE -fe80::7459:8eff:fe98:81 dev eth0 lladdr 76:59:8e:98:00:81 STALE -fe80::433:25ff:fe8c:e1ea dev eth2 lladdr 1a:32:06:78:05:0a STALE -2001::2 dev eth0 FAILED" - .to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - })); +/// This gets our link local ip that can be reached by another node with link local ip +pub fn get_reply_ip( + their_ip: Ipv6Addr, + external_interface: Option, +) -> Result { + let neigh = get_neighbors()?; - let dev = KI - .get_device_name("fe80::433:25ff:fe8c:e1ea".parse().unwrap()) - .unwrap(); + trace!("Looking for {:?} in {:?} for reply ip", their_ip, neigh); + for (ip, dev) in neigh { + if ip == their_ip { + return get_link_local_device_ip(&dev); + } + } - assert_eq!(dev, "eth2") + if let Some(external_interface) = external_interface { + let global_ip = get_global_device_ip(&external_interface)?; + trace!( + "Didn't find {:?} in neighbors, sending global ip {:?}", + their_ip, + global_ip + ); + Ok(global_ip) + } else { + trace!("Didn't find {:?} in neighbors, bailing out", their_ip); + Err(KernelInterfaceError::RuntimeError( + "Address not found in neighbors".to_string(), + )) + } } #[test] diff --git a/althea_kernel_interface/src/manipulate_uci.rs b/althea_kernel_interface/src/manipulate_uci.rs index d7cda525a..2db140d77 100644 --- a/althea_kernel_interface/src/manipulate_uci.rs +++ b/althea_kernel_interface/src/manipulate_uci.rs @@ -1,156 +1,151 @@ -use crate::{KernelInterface, KernelInterfaceError}; +use crate::{run_command, KernelInterfaceError}; use regex::Regex; use std::collections::HashMap; -impl dyn KernelInterface { - fn run_uci(&self, command: &str, args: &[&str]) -> Result<(), KernelInterfaceError> { - let output = self.run_command(command, args)?; - if !output.stderr.is_empty() { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error while setting UCI: {}", - String::from_utf8(output.stderr)? - ))); - } - Ok(()) +fn run_uci(command: &str, args: &[&str]) -> Result<(), KernelInterfaceError> { + let output = run_command(command, args)?; + if !output.stderr.is_empty() { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error while setting UCI: {}", + String::from_utf8(output.stderr)? + ))); } + Ok(()) +} + +/// Sets an arbitrary UCI variable on OpenWRT +pub fn set_uci_var(key: &str, value: &str) -> Result<(), KernelInterfaceError> { + run_uci("uci", &["set", &format!("{key}={value}")])?; + Ok(()) +} - /// Sets an arbitrary UCI variable on OpenWRT - pub fn set_uci_var(&self, key: &str, value: &str) -> Result<(), KernelInterfaceError> { - self.run_uci("uci", &["set", &format!("{key}={value}")])?; - Ok(()) +/// Adds an arbitrary UCI variable on OpenWRT +pub fn add_uci_var(key: &str, value: &str) -> Result<(), KernelInterfaceError> { + run_uci("uci", &["add", key, value])?; + Ok(()) +} + +/// Sets an arbitrary UCI list on OpenWRT +pub fn set_uci_list(key: &str, value: &[&str]) -> Result<(), KernelInterfaceError> { + if let Err(e) = del_uci_var(key) { + trace!("Delete uci var failed! {:?}", e); } - /// Adds an arbitrary UCI variable on OpenWRT - pub fn add_uci_var(&self, key: &str, value: &str) -> Result<(), KernelInterfaceError> { - self.run_uci("uci", &["add", key, value])?; - Ok(()) + for v in value { + run_uci("uci", &["add_list", &format!("{}={}", &key, &v)])?; } + Ok(()) +} - /// Sets an arbitrary UCI list on OpenWRT - pub fn set_uci_list(&self, key: &str, value: &[&str]) -> Result<(), KernelInterfaceError> { - if let Err(e) = self.del_uci_var(key) { - trace!("Delete uci var failed! {:?}", e); - } +/// Deletes an arbitrary UCI variable on OpenWRT +pub fn del_uci_var(key: &str) -> Result<(), KernelInterfaceError> { + run_uci("uci", &["delete", key])?; + Ok(()) +} - for v in value { - self.run_uci("uci", &["add_list", &format!("{}={}", &key, &v)])?; - } - Ok(()) +/// Retrieves the value of a given UCI path, could be one or multiple values +pub fn get_uci_var(key: &str) -> Result { + let output = run_command("uci", &["get", key])?; + if !output.stderr.is_empty() { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error while getting UCI: {}", + String::from_utf8(output.stderr)? + ))); } + let clean_string = String::from_utf8(output.stdout)?.trim().to_string(); + Ok(clean_string) +} - /// Deletes an arbitrary UCI variable on OpenWRT - pub fn del_uci_var(&self, key: &str) -> Result<(), KernelInterfaceError> { - self.run_uci("uci", &["delete", key])?; - Ok(()) +/// Commits changes to UCI, returns true if successful +pub fn uci_commit(subsection: &str) -> Result<(), KernelInterfaceError> { + let output = run_command("uci", &["commit", subsection])?; + if !output.status.success() { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error while commiting UCI: {}", + String::from_utf8(output.stderr)? + ))); } + Ok(()) +} - /// Retrieves the value of a given UCI path, could be one or multiple values - pub fn get_uci_var(&self, key: &str) -> Result { - let output = self.run_command("uci", &["get", key])?; - if !output.stderr.is_empty() { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error while getting UCI: {}", - String::from_utf8(output.stderr)? - ))); - } - let clean_string = String::from_utf8(output.stdout)?.trim().to_string(); - Ok(clean_string) +/// Resets unsaved changes to UCI +pub fn uci_revert(section: &str) -> Result<(), KernelInterfaceError> { + let output = run_command("uci", &["revert", section])?; + if !output.status.success() { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error while reverting UCI: {}", + String::from_utf8(output.stderr)? + ))); } + Ok(()) +} - /// Commits changes to UCI, returns true if successful - pub fn uci_commit(&self, subsection: &str) -> Result<(), KernelInterfaceError> { - let output = self.run_command("uci", &["commit", subsection])?; - if !output.status.success() { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error while commiting UCI: {}", - String::from_utf8(output.stderr)? - ))); - } - Ok(()) +pub fn refresh_initd(program: &str) -> Result<(), KernelInterfaceError> { + let output = run_command(&format!("/etc/init.d/{program}"), &["reload"])?; + if !output.status.success() { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error while refreshing {}: {}", + program, + String::from_utf8(output.stderr)? + ))); } + Ok(()) +} - /// Resets unsaved changes to UCI - pub fn uci_revert(&self, section: &str) -> Result<(), KernelInterfaceError> { - let output = self.run_command("uci", &["revert", section])?; - if !output.status.success() { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error while reverting UCI: {}", - String::from_utf8(output.stderr)? - ))); - } - Ok(()) +/// Obtain a HashMap of a UCI section's entries and their values. Whenever `section` is None +/// the function will shell out for just `uci show` which fetches all available config entries. +pub fn uci_show(section: Option<&str>) -> Result, KernelInterfaceError> { + lazy_static! { + static ref RE: Regex = Regex::new(r"(.+)=(.+)").unwrap(); } - pub fn refresh_initd(&self, program: &str) -> Result<(), KernelInterfaceError> { - let output = self.run_command(&format!("/etc/init.d/{program}"), &["reload"])?; - if !output.status.success() { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error while refreshing {}: {}", - program, - String::from_utf8(output.stderr)? - ))); - } - Ok(()) + let output = match section { + Some(s) => run_command("uci", &["show", s])?, + None => run_command("uci", &["show"])?, + }; + + if !output.status.success() { + return Err(KernelInterfaceError::RuntimeError(format!( + "`uci show` experienced a problem:\nstdout:\n{:?}\nstderr:\n{:?}", + String::from_utf8(output.stdout)?, + String::from_utf8(output.stderr)? + ))); } - /// Obtain a HashMap of a UCI section's entries and their values. Whenever `section` is None - /// the function will shell out for just `uci show` which fetches all available config entries. - pub fn uci_show( - &self, - section: Option<&str>, - ) -> Result, KernelInterfaceError> { - lazy_static! { - static ref RE: Regex = Regex::new(r"(.+)=(.+)").unwrap(); - } - - let output = match section { - Some(s) => self.run_command("uci", &["show", s])?, - None => self.run_command("uci", &["show"])?, - }; + let stdout = String::from_utf8(output.stdout)?; - if !output.status.success() { - return Err(KernelInterfaceError::RuntimeError(format!( - "`uci show` experienced a problem:\nstdout:\n{:?}\nstderr:\n{:?}", - String::from_utf8(output.stdout)?, - String::from_utf8(output.stderr)? - ))); - } - - let stdout = String::from_utf8(output.stdout)?; - - let mut retval: HashMap = HashMap::new(); - - for line in stdout.lines() { - let caps = match RE.captures(line) { - Some(c) => c, - None => { - return Err(KernelInterfaceError::RuntimeError(format!( - "uci_show: Could not match regex {:?} on line {:?}", - *RE, line - ))) - } - }; - retval.insert( - caps[1].to_owned(), - caps[2].to_owned().trim_matches('\'').to_string(), - ); - } - - Ok(retval) - } + let mut retval: HashMap = HashMap::new(); - pub fn openwrt_reset_wireless(&self) -> Result<(), KernelInterfaceError> { - self.run_command("wifi", &[])?; - Ok(()) + for line in stdout.lines() { + let caps = match RE.captures(line) { + Some(c) => c, + None => { + return Err(KernelInterfaceError::RuntimeError(format!( + "uci_show: Could not match regex {:?} on line {:?}", + *RE, line + ))) + } + }; + retval.insert( + caps[1].to_owned(), + caps[2].to_owned().trim_matches('\'').to_string(), + ); } - pub fn openwrt_reset_network(&self) -> Result<(), KernelInterfaceError> { - self.run_command("/etc/init.d/network", &["restart"])?; - Ok(()) - } + Ok(retval) +} - pub fn openwrt_reset_dnsmasq(&self) -> Result<(), KernelInterfaceError> { - self.run_command("/etc/init.d/dnsmasq", &["restart"])?; - Ok(()) - } +pub fn openwrt_reset_wireless() -> Result<(), KernelInterfaceError> { + run_command("wifi", &[])?; + Ok(()) +} + +pub fn openwrt_reset_network() -> Result<(), KernelInterfaceError> { + run_command("/etc/init.d/network", &["restart"])?; + Ok(()) +} + +pub fn openwrt_reset_dnsmasq() -> Result<(), KernelInterfaceError> { + run_command("/etc/init.d/dnsmasq", &["restart"])?; + Ok(()) } diff --git a/althea_kernel_interface/src/netfilter.rs b/althea_kernel_interface/src/netfilter.rs index ef8f15bb1..2d1be0732 100644 --- a/althea_kernel_interface/src/netfilter.rs +++ b/althea_kernel_interface/src/netfilter.rs @@ -1,235 +1,255 @@ +use crate::{run_command, KernelInterfaceError}; use std::net::IpAddr; -use crate::KernelInterface; -use crate::KernelInterfaceError; +pub fn does_nftables_exist() -> bool { + let output = match run_command("nft", &["-v"]) { + Ok(out) => out, + Err(e) => { + error!("Run command is failing with {}", e); + // Assume there is no nftables + return false; + } + }; -impl dyn KernelInterface { - pub fn does_nftables_exist(&self) -> bool { - let output = match self.run_command("nft", &["-v"]) { - Ok(out) => out, - Err(e) => { - error!("Run command is failing with {}", e); - // Assume there is no nftables - return false; - } - }; + let stdout = match String::from_utf8(output.stdout) { + Ok(a) => a, + Err(e) => { + error!("Cannot parse stdout with {}", e); + return false; + } + }; - let stdout = match String::from_utf8(output.stdout) { - Ok(a) => a, - Err(e) => { - error!("Cannot parse stdout with {}", e); - return false; - } - }; + stdout.contains("nftables") +} - stdout.contains("nftables") - } +fn create_fwd_rule() -> Result<(), KernelInterfaceError> { + run_command( + "nft", + &["insert", "rule", "inet", "fw4", "forward_lan", "accept"], + )?; - fn create_fwd_rule(&self) -> Result<(), KernelInterfaceError> { - self.run_command( - "nft", - &["insert", "rule", "inet", "fw4", "forward_lan", "accept"], - )?; + Ok(()) +} - Ok(()) +fn create_nft_set(set_name: &str) { + if let Err(e) = run_command( + "nft", + &[ + "add", + "set", + "inet", + "fw4", + set_name, + "{", + "type", + "ipv6_addr", + ".", + "ifname;", + "flags", + "dynamic;", + "counter;", + "size", + "65535;", + "}", + ], + ) { + error!("Unable to setup counter tables with {:?}", e); } +} - fn create_nft_set(&self, set_name: &str) { - if let Err(e) = self.run_command( - "nft", - &[ - "add", - "set", - "inet", - "fw4", - set_name, - "{", - "type", - "ipv6_addr", - ".", - "ifname;", - "flags", - "dynamic;", - "counter;", - "size", - "65535;", - "}", - ], - ) { - error!("Unable to setup counter tables with {:?}", e); - } - } +fn create_nat_table(ex_nic: &str) -> Result<(), KernelInterfaceError> { + // create the table + run_command("nft", &["create", "table", "ip", "nat"])?; - fn create_nat_table(&self, ex_nic: &str) -> Result<(), KernelInterfaceError> { - // create the table - self.run_command("nft", &["create", "table", "ip", "nat"])?; + // Add a chain to the table + run_command( + "nft", + &[ + "create", + "chain", + "ip", + "nat", + "postrouting", + "{", + "type", + "nat", + "hook", + "postrouting", + "priority", + "100", + ";", + "policy", + "accept", + ";", + "}", + ], + )?; - // Add a chain to the table - self.run_command( - "nft", - &[ - "create", - "chain", - "ip", - "nat", - "postrouting", - "{", - "type", - "nat", - "hook", - "postrouting", - "priority", - "100", - ";", - "policy", - "accept", - ";", - "}", - ], - )?; + // Add rule to chain + run_command( + "nft", + &[ + "add", + "rule", + "ip", + "nat", + "postrouting", + "oifname", + ex_nic, + "masquerade", + ], + )?; - // Add rule to chain - self.run_command( - "nft", - &[ - "add", - "rule", - "ip", - "nat", - "postrouting", - "oifname", - ex_nic, - "masquerade", - ], - )?; + Ok(()) +} - Ok(()) +fn is_lan_fwd_present() -> Result { + let out = run_command("nft", &["list", "chain", "inet", "fw4", "forward_lan"])?; + let out = out.stdout; + let out = String::from_utf8(out).expect("fix command"); + for line in out.lines() { + if line.trim() == "accept" { + return Ok(true); + } } - fn is_lan_fwd_present(&self) -> Result { - let out = self.run_command("nft", &["list", "chain", "inet", "fw4", "forward_lan"])?; - let out = out.stdout; - let out = String::from_utf8(out).expect("fix command"); - for line in out.lines() { - if line.trim() == "accept" { - return Ok(true); + Ok(false) +} + +fn get_reject_rule_handle() -> Result, KernelInterfaceError> { + let out = run_command( + "nft", + &["-a", "list", "chain", "inet", "fw4", "forward_lan"], + )?; + let out = out.stdout; + let out = String::from_utf8(out).expect("fix command"); + for line in out.lines() { + if line.contains("reject") { + let handle: Vec<&str> = line.split(' ').collect(); + match handle.last() { + Some(a) => match (*a).parse() { + Ok(b) => return Ok(Some(b)), + Err(_) => { + return Ok(None); + } + }, + None => return Ok(None), } } + } + Ok(None) +} - Ok(false) +fn is_nat_table_present() -> Result { + let out = run_command("nft", &["list", "table", "ip", "nat"])?; + if out.status.success() { + return Ok(true); } + Ok(false) +} - fn get_reject_rule_handle(&self) -> Result, KernelInterfaceError> { - let out = self.run_command( +pub fn insert_reject_rule() -> Result<(), KernelInterfaceError> { + if get_reject_rule_handle()?.is_none() { + run_command( "nft", - &["-a", "list", "chain", "inet", "fw4", "forward_lan"], + &["insert", "rule", "inet", "fw4", "forward_lan", "reject"], )?; - let out = out.stdout; - let out = String::from_utf8(out).expect("fix command"); - for line in out.lines() { - if line.contains("reject") { - let handle: Vec<&str> = line.split(' ').collect(); - match handle.last() { - Some(a) => match (*a).parse() { - Ok(b) => return Ok(Some(b)), - Err(_) => { - return Ok(None); - } - }, - None => return Ok(None), - } - } - } - Ok(None) } + Ok(()) +} - fn is_nat_table_present(&self) -> Result { - let out = self.run_command("nft", &["list", "table", "ip", "nat"])?; - if out.status.success() { - return Ok(true); - } - Ok(false) +pub fn delete_reject_rule() -> Result<(), KernelInterfaceError> { + if let Some(handle) = get_reject_rule_handle()? { + run_command( + "nft", + &[ + "delete", + "rule", + "inet", + "fw4", + "forward_lan", + "handle", + &handle.to_string(), + ], + )?; } + Ok(()) +} - pub fn insert_reject_rule(&self) -> Result<(), KernelInterfaceError> { - if self.get_reject_rule_handle()?.is_none() { - self.run_command( - "nft", - &["insert", "rule", "inet", "fw4", "forward_lan", "reject"], - )?; - } - Ok(()) +pub fn init_nat_chain(ex_nic: &str) -> Result<(), KernelInterfaceError> { + if !is_nat_table_present()? { + create_nat_table(ex_nic)?; } + Ok(()) +} - pub fn delete_reject_rule(&self) -> Result<(), KernelInterfaceError> { - if let Some(handle) = self.get_reject_rule_handle()? { - self.run_command( - "nft", - &[ - "delete", - "rule", - "inet", - "fw4", - "forward_lan", - "handle", - &handle.to_string(), - ], - )?; - } - Ok(()) +pub fn set_nft_lan_fwd_rule() -> Result<(), KernelInterfaceError> { + if !is_lan_fwd_present()? { + create_fwd_rule()?; } - pub fn init_nat_chain(&self, ex_nic: &str) -> Result<(), KernelInterfaceError> { - if !self.is_nat_table_present()? { - self.create_nat_table(ex_nic)?; - } - Ok(()) - } + Ok(()) +} - pub fn set_nft_lan_fwd_rule(&self) -> Result<(), KernelInterfaceError> { - if !self.is_lan_fwd_present()? { - self.create_fwd_rule()?; +fn are_nft_exit_forward_rules_present( + interface: &str, + ex_nic: &str, +) -> Result { + let out = run_command("nft", &["list", "chain", "inet", "fw4", "forward"])?; + let out = out.stdout; + let out = String::from_utf8(out).expect("fix command"); + for line in out.lines() { + // If one is present, they should all be present + if line.contains(&format!("iifname {interface} oifname {ex_nic}")) { + return Ok(true); } - - Ok(()) } + Ok(false) +} - fn are_nft_exit_forward_rules_present( - &self, - interface: &str, - ex_nic: &str, - ) -> Result { - let out = self.run_command("nft", &["list", "chain", "inet", "fw4", "forward"])?; - let out = out.stdout; - let out = String::from_utf8(out).expect("fix command"); - for line in out.lines() { - // If one is present, they should all be present - if line.contains(&format!("iifname {interface} oifname {ex_nic}")) { - return Ok(true); - } - } - Ok(false) - } +pub fn insert_nft_exit_forward_rules( + interface: &str, + ex_nic: &str, + external_v6: Option<(IpAddr, u8)>, +) -> Result<(), KernelInterfaceError> { + // Packets from wg_exit -> ex_nic. Add this rule for both v4 and v6 traffic + // nft add rule ip tests forward iifname "wg_exit" oifname "ex_nic" counter accept + if !are_nft_exit_forward_rules_present(interface, ex_nic)? { + run_command( + "nft", + &[ + "add", "rule", "inet", "fw4", "forward", "iifname", interface, "oifname", ex_nic, + "counter", "accept", + ], + )?; + // v4 packets from ex_nic -> wg_exit + // nft add rule ip tests forward iifname "ex_nic" oifname "wg_exit" ct state related,established counter accept + run_command( + "nft", + &[ + "add", + "rule", + "inet", + "fw4", + "forward", + "meta", + "nfproto", + "ipv4", + "iifname", + ex_nic, + "oifname", + interface, + "ct", + "state", + "related,established", + "counter", + "accept", + ], + )?; - pub fn insert_nft_exit_forward_rules( - &self, - interface: &str, - ex_nic: &str, - external_v6: Option<(IpAddr, u8)>, - ) -> Result<(), KernelInterfaceError> { - // Packets from wg_exit -> ex_nic. Add this rule for both v4 and v6 traffic - // nft add rule ip tests forward iifname "wg_exit" oifname "ex_nic" counter accept - if !self.are_nft_exit_forward_rules_present(interface, ex_nic)? { - self.run_command( - "nft", - &[ - "add", "rule", "inet", "fw4", "forward", "iifname", interface, "oifname", - ex_nic, "counter", "accept", - ], - )?; - // v4 packets from ex_nic -> wg_exit - // nft add rule ip tests forward iifname "ex_nic" oifname "wg_exit" ct state related,established counter accept - self.run_command( + // v6 packets from ex_nic -> wg_exit + if let Some((external_ip_v6, netmask_v6)) = external_v6 { + run_command( "nft", &[ "add", @@ -237,124 +257,95 @@ impl dyn KernelInterface { "inet", "fw4", "forward", - "meta", - "nfproto", - "ipv4", "iifname", ex_nic, "oifname", interface, - "ct", - "state", - "related,established", + "ip", + "daddr", + &format!("{}/{}", external_ip_v6, netmask_v6), "counter", "accept", ], )?; - - // v6 packets from ex_nic -> wg_exit - if let Some((external_ip_v6, netmask_v6)) = external_v6 { - self.run_command( - "nft", - &[ - "add", - "rule", - "inet", - "fw4", - "forward", - "iifname", - ex_nic, - "oifname", - interface, - "ip", - "daddr", - &format!("{}/{}", external_ip_v6, netmask_v6), - "counter", - "accept", - ], - )?; - } } - - Ok(()) } - pub fn nft_init_counters( - &self, - set_name: &str, - chain: &str, - interface: &str, - ) -> Result<(), KernelInterfaceError> { - if !self.is_nft_set_present(set_name) { - // add set - self.create_nft_set(set_name); - // add insert chain - self.create_nft_insert_chain(set_name, chain, interface); - } - Ok(()) + Ok(()) +} + +pub fn nft_init_counters( + set_name: &str, + chain: &str, + interface: &str, +) -> Result<(), KernelInterfaceError> { + if !is_nft_set_present(set_name) { + // add set + create_nft_set(set_name); + // add insert chain + create_nft_insert_chain(set_name, chain, interface); } + Ok(()) +} - fn is_nft_set_present(&self, set_name: &str) -> bool { - let out = self - .run_command("nft", &["list", "set", "inet", "fw4", set_name]) - .expect("Fix command!"); - if out.status.success() { - return true; - } - false +fn is_nft_set_present(set_name: &str) -> bool { + let out = run_command("nft", &["list", "set", "inet", "fw4", set_name]).expect("Fix command!"); + if out.status.success() { + return true; } + false +} - fn create_nft_insert_chain(&self, set_name: &str, chain: &str, interface: &str) { - // Add rule to match on a set element counter to increment it - if let Err(e) = self.run_command( - "nft", - &[ - "insert", - "rule", - "inet", - "fw4", - chain, - "ip6", - "daddr", - ".", - "meta", - interface, - &("@".to_owned() + set_name), - ], - ) { - error!("Unable to setup counters: {}", e); - } +fn create_nft_insert_chain(set_name: &str, chain: &str, interface: &str) { + // Add rule to match on a set element counter to increment it + if let Err(e) = run_command( + "nft", + &[ + "insert", + "rule", + "inet", + "fw4", + chain, + "ip6", + "daddr", + ".", + "meta", + interface, + &("@".to_owned() + set_name), + ], + ) { + error!("Unable to setup counters: {}", e); + } - // insert ip6addr . ifname when not present in set - // sudo nft -e -j insert rule filter input ip daddr . meta iifname != @myset3 set add ip saddr . meta iifname @myset3 - if let Err(e) = self.run_command( - "nft", - &vec![ - "insert", - "rule", - "inet", - "fw4", - chain, - "ip6", - "daddr", - ".", - "meta", - interface, - "!=", - &("@".to_owned() + set_name), - "add", - &("@".to_owned() + set_name), - "{", - "ip6", - "daddr", - ".", - "meta", - interface, - "counter", - "}", - ], - ) { - error!("Unable to setup counters: {}", e); - } + // insert ip6addr . ifname when not present in set + // sudo nft -e -j insert rule filter input ip daddr . meta iifname != @myset3 set add ip saddr . meta iifname @myset3 + if let Err(e) = run_command( + "nft", + &vec![ + "insert", + "rule", + "inet", + "fw4", + chain, + "ip6", + "daddr", + ".", + "meta", + interface, + "!=", + &("@".to_owned() + set_name), + "add", + &("@".to_owned() + set_name), + "{", + "ip6", + "daddr", + ".", + "meta", + interface, + "counter", + "}", + ], + ) { + error!("Unable to setup counters: {}", e); } } diff --git a/althea_kernel_interface/src/netns.rs b/althea_kernel_interface/src/netns.rs index 565f9a30f..e94a0e944 100644 --- a/althea_kernel_interface/src/netns.rs +++ b/althea_kernel_interface/src/netns.rs @@ -1,65 +1,63 @@ -use crate::KernelInterface; +use crate::run_command; use std::thread::sleep; -impl dyn KernelInterface { - /// Custom function for our integration test environment, returns a numbered netnamespace - /// this thread is currently operating in, allowing us to dispatch lazy static data - /// independent to each thread. Arch wise the fact that we have this problem at all indicates - /// that the lazy static for cross thread comms arch if a bit questionable by nature - pub fn check_integration_test_netns(&self) -> u32 { - if cfg!(feature = "integration_test") { - let mut ns = self.run_command("ip", &["netns", "identify"]); - while let Err(e) = ns { - warn!("Could not get netns name, retrying: {:?}", e); - sleep(std::time::Duration::from_secs(1)); - ns = self.run_command("ip", &["netns", "identify"]); - } - let ns = ns.unwrap(); - let ns = match String::from_utf8(ns.stdout) { - Ok(s) => s, - Err(_) => panic!("Could not get netns name!"), - }; - let ns = ns.trim(); - match ( - ns.split('-').last().unwrap().parse(), - ns.split('_').last().unwrap().parse(), - ) { - (Ok(a), _) => a, - (_, Ok(a)) => a, - (Err(_), Err(_)) => { - // for some reason it's not easily possible to tell if we're in a unit test - error!("Could not get netns name, maybe a unit test?"); - 0 - } +/// Custom function for our integration test environment, returns a numbered netnamespace +/// this thread is currently operating in, allowing us to dispatch lazy static data +/// independent to each thread. Arch wise the fact that we have this problem at all indicates +/// that the lazy static for cross thread comms arch if a bit questionable by nature +pub fn check_integration_test_netns() -> u32 { + if cfg!(feature = "integration_test") { + let mut ns = run_command("ip", &["netns", "identify"]); + while let Err(e) = ns { + warn!("Could not get netns name, retrying: {:?}", e); + sleep(std::time::Duration::from_secs(1)); + ns = run_command("ip", &["netns", "identify"]); + } + let ns = ns.unwrap(); + let ns = match String::from_utf8(ns.stdout) { + Ok(s) => s, + Err(_) => panic!("Could not get netns name!"), + }; + let ns = ns.trim(); + match ( + ns.split('-').last().unwrap().parse(), + ns.split('_').last().unwrap().parse(), + ) { + (Ok(a), _) => a, + (_, Ok(a)) => a, + (Err(_), Err(_)) => { + // for some reason it's not easily possible to tell if we're in a unit test + error!("Could not get netns name, maybe a unit test?"); + 0 } - } else { - 0 } + } else { + 0 } +} - /// Gets the network namespace name that holds the thread this function was called from. - /// If the calling thread was not inside a network namespace/in the default namespace, this - /// function returns a None - pub fn get_namespace(&self) -> Option { - let output = match self.run_command("ip", &["netns", "identify"]) { - Ok(output) => output, - Err(_) => { - warn!("Could not run ip netns- is ip netns installed?"); - return None; - } - }; - match String::from_utf8(output.stdout) { - Ok(mut s) => { - s.truncate(s.len() - 1); - if !s.is_empty() { - return Some(s); - } - None - } - Err(_) => { - warn!("Could not get ip netns name from stdout!"); - None +/// Gets the network namespace name that holds the thread this function was called from. +/// If the calling thread was not inside a network namespace/in the default namespace, this +/// function returns a None +pub fn get_namespace() -> Option { + let output = match run_command("ip", &["netns", "identify"]) { + Ok(output) => output, + Err(_) => { + warn!("Could not run ip netns- is ip netns installed?"); + return None; + } + }; + match String::from_utf8(output.stdout) { + Ok(mut s) => { + s.truncate(s.len() - 1); + if !s.is_empty() { + return Some(s); } + None + } + Err(_) => { + warn!("Could not get ip netns name from stdout!"); + None } } } diff --git a/althea_kernel_interface/src/open_tunnel.rs b/althea_kernel_interface/src/open_tunnel.rs index 1b4efc568..5d1b6b61f 100644 --- a/althea_kernel_interface/src/open_tunnel.rs +++ b/althea_kernel_interface/src/open_tunnel.rs @@ -1,4 +1,6 @@ -use crate::{DefaultRoute, KernelInterface, KernelInterfaceError}; +use crate::ip_route::manual_peers_route; +use crate::link_local_tools::get_device_name; +use crate::{run_command, DefaultRoute, KernelInterfaceError}; use althea_types::WgKey; use std::net::{IpAddr, Ipv6Addr, SocketAddr}; use std::path::Path; @@ -56,229 +58,101 @@ pub struct TunnelOpenArgs<'a> { pub settings_default_route: &'a mut Option, } -impl dyn KernelInterface { - pub fn open_tunnel(&self, args: TunnelOpenArgs) -> Result<(), KernelInterfaceError> { - let (setup_gateway_routes, socket_connect_str) = match args.endpoint { - SocketAddr::V4(sockv4) => ( - // if the ipv4 address is not private we are connecting to an exit - // and instead we are connecting to a local peer over ipv4 - !sockv4.ip().is_private(), - format!("{}:{}", sockv4.ip(), sockv4.port()), - ), - SocketAddr::V6(sockv6) => { - let is_link_local = is_link_local(IpAddr::V6(*sockv6.ip())); - if is_link_local { - // if the ipv6 address is link local we need to add the interface name - // to the endpoint - let interface_name = self.get_device_name((*sockv6.ip()).into())?; - ( - false, - format!("[{}%{}]:{}", sockv6.ip(), interface_name, sockv6.port()), - ) - } else { - // ipv6 is not link local, it's over the internet - (true, format!("[{}]:{}", sockv6.ip(), sockv6.port())) - } +pub fn open_tunnel(args: TunnelOpenArgs) -> Result<(), KernelInterfaceError> { + let (setup_gateway_routes, socket_connect_str) = match args.endpoint { + SocketAddr::V4(sockv4) => ( + // if the ipv4 address is not private we are connecting to an exit + // and instead we are connecting to a local peer over ipv4 + !sockv4.ip().is_private(), + format!("{}:{}", sockv4.ip(), sockv4.port()), + ), + SocketAddr::V6(sockv6) => { + let is_link_local = is_link_local(IpAddr::V6(*sockv6.ip())); + if is_link_local { + // if the ipv6 address is link local we need to add the interface name + // to the endpoint + let interface_name = get_device_name((*sockv6.ip()).into())?; + ( + false, + format!("[{}%{}]:{}", sockv6.ip(), interface_name, sockv6.port()), + ) + } else { + // ipv6 is not link local, it's over the internet + (true, format!("[{}]:{}", sockv6.ip(), sockv6.port())) } - }; - - let allowed_addresses = "::/0".to_string(); - - trace!("socket connect string: {}", socket_connect_str); - let output = self.run_command( - "wg", - &[ - "set", - &args.interface, - "listen-port", - &format!("{}", args.port), - "private-key", - args.private_key_path.to_str().unwrap(), - "peer", - &format!("{}", args.remote_pub_key), - "endpoint", - &socket_connect_str, - "allowed-ips", - &allowed_addresses, - "persistent-keepalive", - "5", - ], - )?; - if !output.stderr.is_empty() { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error from wg command: {}", - String::from_utf8(output.stderr)? - ))); - } - let _output = self.run_command( - "ip", - &[ - "address", - "add", - &args.own_ip.to_string(), - "dev", - &args.interface, - ], - )?; - - // Add second ip to tunnel used only by exits currently - if let Some(ip) = args.own_ip_v2 { - let _output = self.run_command( - "ip", - &["address", "add", &ip.to_string(), "dev", &args.interface], - )?; } + }; - // add ipv6 link local slacc address manually, this is required for peer discovery - self.run_command( + let allowed_addresses = "::/0".to_string(); + + trace!("socket connect string: {}", socket_connect_str); + let output = run_command( + "wg", + &[ + "set", + &args.interface, + "listen-port", + &format!("{}", args.port), + "private-key", + args.private_key_path.to_str().unwrap(), + "peer", + &format!("{}", args.remote_pub_key), + "endpoint", + &socket_connect_str, + "allowed-ips", + &allowed_addresses, + "persistent-keepalive", + "5", + ], + )?; + if !output.stderr.is_empty() { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error from wg command: {}", + String::from_utf8(output.stderr)? + ))); + } + let _output = run_command( + "ip", + &[ + "address", + "add", + &args.own_ip.to_string(), + "dev", + &args.interface, + ], + )?; + + // Add second ip to tunnel used only by exits currently + if let Some(ip) = args.own_ip_v2 { + let _output = run_command( "ip", - &[ - "address", - "add", - &format!("{}/64", to_wg_local(&args.own_ip)), - "dev", - &args.interface, - ], + &["address", "add", &ip.to_string(), "dev", &args.interface], )?; - - if setup_gateway_routes { - self.manual_peers_route(&args.endpoint.ip(), args.settings_default_route)?; - } - - let output = self.run_command("ip", &["link", "set", "dev", &args.interface, "up"])?; - if !output.stderr.is_empty() { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error setting wg interface {:?} up: {}", - args, - String::from_utf8(output.stderr)? - ))); - } - Ok(()) } -} - -#[test] -fn test_open_tunnel_linux() { - use crate::KI; - - use crate::ip_route::DefaultRoute; - use std::net::SocketAddrV6; - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - let interface = String::from("wg1"); - let endpoint_link_local_ip = Ipv6Addr::new(0xfe80, 0, 0, 0x12, 0x34, 0x56, 0x78, 0x90); - let own_mesh_ip = "fd00::1".parse::().unwrap(); - let endpoint = SocketAddr::V6(SocketAddrV6::new(endpoint_link_local_ip, 8088, 0, 123)); - let remote_pub_key = "x8AcR9wI4t97aowYFlis077BDBk9SLdq6khMiixuTsQ=" - .parse() - .unwrap(); - let private_key_path = Path::new("private_key"); - - let wg_args = &[ - "set", - "wg1", - "listen-port", - "8088", - "private-key", - "private_key", - "peer", - "x8AcR9wI4t97aowYFlis077BDBk9SLdq6khMiixuTsQ=", - "endpoint", - "[fe80::12:34:56:78:90%eth2]:8088", - "allowed-ips", - "::/0", - "persistent-keepalive", - "5", - ]; - - let mut counter = 0; - - KI.set_mock(Box::new(move |program, args| { - counter += 1; - match counter { - 1 => { - //get interfaces - assert_eq!(program, "ip"); - assert_eq!(args, &["neigh"]); - - Ok(Output { - stdout: b"10.0.2.2 dev eth0 lladdr 00:00:00:aa:00:03 STALE -10.0.0.2 dev eth0 FAILED -10.0.1.2 dev eth0 lladdr 00:00:00:aa:00:05 REACHABLE -2001::2 dev eth0 lladdr 00:00:00:aa:00:56 REACHABLE -fe80:0:0:12:34:56:78:90 dev eth2 lladdr 76:59:8e:98:00:81 STALE -fe80::433:25ff:fe8c:e1ea dev eth0 lladdr 1a:32:06:78:05:0a STALE -2001::2 dev eth0 FAILED" - .to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 2 => { - // setup wg interface - assert_eq!(program, "wg"); - assert_eq!(args, wg_args); - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 3 => { - // add global ip - assert_eq!(program, "ip"); - assert_eq!(args, ["address", "add", "fd00::1", "dev", "wg1"]); - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 4 => { - // add link local ip - assert_eq!(program, "ip"); - assert_eq!(args, ["address", "add", "fe80::1/64", "dev", "wg1"]); - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - 5 => { - // bring if up - assert_eq!(program, "ip"); - assert_eq!(args, ["link", "set", "dev", "wg1", "up"]); - Ok(Output { - stdout: b"".to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => unimplemented!(), - } - })); - - let def_route = DefaultRoute { - via: "192.168.8.1".parse().unwrap(), - nic: "wifiinterface".to_string(), - proto: Some("dhcp".to_string()), - metric: Some(600), - src: None, - }; - - let args = TunnelOpenArgs { - interface, - port: 8088, - endpoint, - remote_pub_key, - private_key_path, - own_ip: own_mesh_ip, - own_ip_v2: None, - external_nic: None, - settings_default_route: &mut Some(def_route), - }; + // add ipv6 link local slacc address manually, this is required for peer discovery + run_command( + "ip", + &[ + "address", + "add", + &format!("{}/64", to_wg_local(&args.own_ip)), + "dev", + &args.interface, + ], + )?; + + if setup_gateway_routes { + manual_peers_route(&args.endpoint.ip(), args.settings_default_route)?; + } - KI.open_tunnel(args).unwrap(); + let output = run_command("ip", &["link", "set", "dev", &args.interface, "up"])?; + if !output.stderr.is_empty() { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error setting wg interface {:?} up: {}", + args, + String::from_utf8(output.stderr)? + ))); + } + Ok(()) } diff --git a/althea_kernel_interface/src/openwrt_ubus.rs b/althea_kernel_interface/src/openwrt_ubus.rs index b798e53ae..1e4e030ee 100644 --- a/althea_kernel_interface/src/openwrt_ubus.rs +++ b/althea_kernel_interface/src/openwrt_ubus.rs @@ -1,18 +1,12 @@ -use crate::KernelInterface; -use crate::KernelInterfaceError; +use crate::{run_command, KernelInterfaceError}; -impl dyn KernelInterface { - /// calls a ubus rpc - pub fn ubus_call( - &self, - namespace: &str, - function: &str, - argument: &str, - ) -> Result { - let output = String::from_utf8( - self.run_command("ubus", &["call", namespace, function, argument])? - .stdout, - )?; - Ok(output) - } +/// calls a ubus rpc +pub fn ubus_call( + namespace: &str, + function: &str, + argument: &str, +) -> Result { + let output = + String::from_utf8(run_command("ubus", &["call", namespace, function, argument])?.stdout)?; + Ok(output) } diff --git a/althea_kernel_interface/src/ping_check.rs b/althea_kernel_interface/src/ping_check.rs index 3b717dee5..40d17f7eb 100644 --- a/althea_kernel_interface/src/ping_check.rs +++ b/althea_kernel_interface/src/ping_check.rs @@ -1,38 +1,34 @@ -use crate::KernelInterface; use crate::KernelInterfaceError; use oping::Ping; use std::net::IpAddr; use std::time::Duration; -impl dyn KernelInterface { - //Pings an address to determine if it's online - pub fn ping_check( - &self, - ip: &IpAddr, - timeout: Duration, - outgoing_device: Option<&str>, - ) -> Result { - trace!("starting ping"); - let mut ping = Ping::new(); - ping.add_host(&ip.to_string())?; +//Pings an address to determine if it's online +pub fn ping_check( + ip: &IpAddr, + timeout: Duration, + outgoing_device: Option<&str>, +) -> Result { + trace!("starting ping"); + let mut ping = Ping::new(); + ping.add_host(&ip.to_string())?; - // set outgoing device as needed - if let Some(device) = outgoing_device { - ping.set_device(device)?; - } + // set outgoing device as needed + if let Some(device) = outgoing_device { + ping.set_device(device)?; + } - ping.set_timeout(timeout.as_millis() as f64 / 1000f64)?; - trace!("sending ping"); - let mut response = ping.send()?; - trace!("sent ping"); - if let Some(res) = response.next() { - trace!("got ping response {:?}", res); - // we get dropped '1' to mean the packet is dropped - // because this create offers c bindings and doesn't do - // much of anything to adapt them - Ok(res.dropped == 0) - } else { - Ok(false) - } + ping.set_timeout(timeout.as_millis() as f64 / 1000f64)?; + trace!("sending ping"); + let mut response = ping.send()?; + trace!("sent ping"); + if let Some(res) = response.next() { + trace!("got ping response {:?}", res); + // we get dropped '1' to mean the packet is dropped + // because this create offers c bindings and doesn't do + // much of anything to adapt them + Ok(res.dropped == 0) + } else { + Ok(false) } } diff --git a/althea_kernel_interface/src/set_system_password.rs b/althea_kernel_interface/src/set_system_password.rs index 5d80b368b..f837a33d2 100644 --- a/althea_kernel_interface/src/set_system_password.rs +++ b/althea_kernel_interface/src/set_system_password.rs @@ -1,36 +1,33 @@ -use crate::KernelInterface; use crate::KernelInterfaceError; use std::io::Write; use std::process::{Command, Stdio}; -impl dyn KernelInterface { - /// Sets the system password on openwrt - pub fn set_system_password(&self, password: String) -> Result<(), KernelInterfaceError> { - trace!("Trying to set the system password to {}", password); - let mut password_with_newline = password.as_bytes().to_vec(); - password_with_newline.push(b'\n'); +/// Sets the system password on openwrt +pub fn set_system_password(password: String) -> Result<(), KernelInterfaceError> { + trace!("Trying to set the system password to {}", password); + let mut password_with_newline = password.as_bytes().to_vec(); + password_with_newline.push(b'\n'); - let mut passwd = Command::new("passwd") - .args(["-a sha512"]) - .stdout(Stdio::piped()) - .stdin(Stdio::piped()) - .spawn() - .unwrap(); + let mut passwd = Command::new("passwd") + .args(["-a sha512"]) + .stdout(Stdio::piped()) + .stdin(Stdio::piped()) + .spawn() + .unwrap(); - passwd - .stdin - .as_mut() - .unwrap() - .write_all(&password_with_newline)?; - passwd - .stdin - .as_mut() - .unwrap() - .write_all(&password_with_newline)?; + passwd + .stdin + .as_mut() + .unwrap() + .write_all(&password_with_newline)?; + passwd + .stdin + .as_mut() + .unwrap() + .write_all(&password_with_newline)?; - let output = passwd.wait_with_output()?; - trace!("Got {} from passwd", String::from_utf8(output.stdout)?); + let output = passwd.wait_with_output()?; + trace!("Got {} from passwd", String::from_utf8(output.stdout)?); - Ok(()) - } + Ok(()) } diff --git a/althea_kernel_interface/src/setup_wg_if.rs b/althea_kernel_interface/src/setup_wg_if.rs index 3aed3a7b0..cb5635cf3 100644 --- a/althea_kernel_interface/src/setup_wg_if.rs +++ b/althea_kernel_interface/src/setup_wg_if.rs @@ -1,155 +1,161 @@ -use crate::{KernelInterface, KernelInterfaceError, KernelInterfaceError as Error}; +use crate::interface_tools::get_interfaces; +use crate::{run_command, KernelInterfaceError, KernelInterfaceError as Error}; use althea_types::WgKey; use std::str::from_utf8; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -impl dyn KernelInterface { - pub fn get_peers(&self, iface_name: &str) -> Result, Error> { - let output = self.run_command("wg", &["show", iface_name, "peers"])?; +pub fn get_peers(iface_name: &str) -> Result, Error> { + let output = run_command("wg", &["show", iface_name, "peers"])?; - let output = from_utf8(&output.stdout)?; + let output = from_utf8(&output.stdout)?; - let mut peers = Vec::new(); + let mut peers = Vec::new(); - for l in output.lines() { - let parsed = l.parse(); - if let Ok(val) = parsed { - peers.push(val); - } else { - warn!("Could not parse peer! {}", l); - } + for l in output.lines() { + let parsed = l.parse(); + if let Ok(val) = parsed { + peers.push(val); + } else { + warn!("Could not parse peer! {}", l); } - - Ok(peers) } - /// checks the existing interfaces to find an interface name that isn't in use. - /// then calls iproute2 to set up a new interface with that name - pub fn create_blank_wg_numbered_wg_interface(&self) -> Result { - // this is the maximum allowed retries for when an interface is claimed to have already existed - // since we only setup interfaces once this can only happen if we have lost an interface or if - // the kernel is acting strange, either way it's better just to skip that interface and wait - // on a Rita restart to clean it up some day. - const MAX_RETRY: u8 = 5; - - //call "ip links" to get a list of currently set up links - let links = self.get_interfaces()?; - let mut if_num = 0; - //loop through the output of "ip links" until we find a wg suffix that isn't taken (e.g. "wg3") - while links.contains(&format!("wg{if_num}")) { - if_num += 1; - } - - let mut count = 0; - let mut interface = format!("wg{if_num}"); - let mut res = self.create_blank_wg_interface(&interface); - while let Err(KernelInterfaceError::WgExistsError) = res { - if_num += 1; - interface = format!("wg{if_num}"); - res = self.create_blank_wg_interface(&interface); - count += 1; - if count > MAX_RETRY { - break; - } - } + Ok(peers) +} - res?; - Ok(interface) +/// checks the existing interfaces to find an interface name that isn't in use. +/// then calls iproute2 to set up a new interface with that name +pub fn create_blank_wg_numbered_wg_interface() -> Result { + // this is the maximum allowed retries for when an interface is claimed to have already existed + // since we only setup interfaces once this can only happen if we have lost an interface or if + // the kernel is acting strange, either way it's better just to skip that interface and wait + // on a Rita restart to clean it up some day. + const MAX_RETRY: u8 = 5; + + //call "ip links" to get a list of currently set up links + let links = get_interfaces()?; + let mut if_num = 0; + //loop through the output of "ip links" until we find a wg suffix that isn't taken (e.g. "wg3") + while links.contains(&format!("wg{if_num}")) { + if_num += 1; } - /// calls iproute2 to set up a new interface with a given name. - pub fn create_blank_wg_interface(&self, name: &str) -> Result<(), KernelInterfaceError> { - let output = self.run_command("ip", &["link", "add", name, "type", "wireguard"])?; - let stderr = String::from_utf8(output.stderr)?; - if !stderr.is_empty() { - if stderr.contains("exists") { - return Err(KernelInterfaceError::WgExistsError); - } else { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error adding wg link: {stderr}" - ))); - } + let mut count = 0; + let mut interface = format!("wg{if_num}"); + let mut res = create_blank_wg_interface(&interface); + while let Err(KernelInterfaceError::WgExistsError) = res { + if_num += 1; + interface = format!("wg{if_num}"); + res = create_blank_wg_interface(&interface); + count += 1; + if count > MAX_RETRY { + break; } - - Ok(()) } - /// Returns the number of clients that are active on the wg_exit tunnel - pub fn get_wg_exit_clients_online(&self, interface: &str) -> Result { - let output = self.run_command("wg", &["show", interface, "latest-handshakes"])?; - let mut num: u32 = 0; - let out = String::from_utf8(output.stdout)?; - for line in out.lines() { - let content: Vec<&str> = line.split('\t').collect(); - let mut itr = content.iter(); - itr.next(); - let timestamp = itr.next().ok_or_else(|| { - KernelInterfaceError::RuntimeError("Option did not contain a value.".to_string()) - })?; - let d = UNIX_EPOCH + Duration::from_secs(timestamp.parse()?); - - if SystemTime::now().duration_since(d)? < Duration::new(600, 0) { - num += 1; - } + res?; + Ok(interface) +} + +/// calls iproute2 to set up a new interface with a given name. +pub fn create_blank_wg_interface(name: &str) -> Result<(), KernelInterfaceError> { + let output = run_command("ip", &["link", "add", name, "type", "wireguard"])?; + let stderr = String::from_utf8(output.stderr)?; + if !stderr.is_empty() { + if stderr.contains("exists") { + return Err(KernelInterfaceError::WgExistsError); + } else { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error adding wg link: {stderr}" + ))); } - Ok(num) } - /// Returns the last handshake time of every client on this tunnel. - pub fn get_last_handshake_time(&self, ifname: &str) -> Result, Error> { - let output = self.run_command("wg", &["show", ifname, "latest-handshakes"])?; - let out = String::from_utf8(output.stdout)?; - let mut timestamps = Vec::new(); - for line in out.lines() { - let content: Vec<&str> = line.split('\t').collect(); - let mut itr = content.iter(); - let wg_key: WgKey = match itr.next() { - Some(val) => val.parse()?, - None => { - return Err(KernelInterfaceError::RuntimeError( - "Invalid line!".to_string(), - )) - } - }; - let timestamp = match itr.next() { - Some(val) => val.parse()?, - None => { - return Err(KernelInterfaceError::RuntimeError( - "Invalid line!".to_string(), - )) - } - }; - let d = UNIX_EPOCH + Duration::from_secs(timestamp); - timestamps.push((wg_key, d)) + Ok(()) +} + +/// internal helper function for testing get_wg_exit_clients_online +fn get_wg_exit_clients_online_internal(out: String) -> Result { + let mut num: u32 = 0; + for line in out.lines() { + let content: Vec<&str> = line.split('\t').collect(); + let mut itr = content.iter(); + itr.next(); + let timestamp = itr.next().ok_or_else(|| { + KernelInterfaceError::RuntimeError("Option did not contain a value.".to_string()) + })?; + let d = UNIX_EPOCH + Duration::from_secs(timestamp.parse()?); + + if SystemTime::now().duration_since(d)? < Duration::new(600, 0) { + num += 1; } - Ok(timestamps) } + Ok(num) +} - /// Returns the last handshake time of every ACTIVE client on this tunnel. - /// An active handshake mean a wireguard tunnel that has a latest handshake value - /// When running wg show wg_exit latest-handshake, a entries with timestamp 0 are inactive - pub fn get_last_active_handshake_time( - &self, - ifname: &str, - ) -> Result, Error> { - let timestamps = self.get_last_handshake_time(ifname)?; - let timestamps = timestamps - .into_iter() - .filter(|(_, time)| *time != UNIX_EPOCH) - .collect(); - Ok(timestamps) +/// Returns the number of clients that are active on the wg_exit tunnel +pub fn get_wg_exit_clients_online(interface: &str) -> Result { + let output = run_command("wg", &["show", interface, "latest-handshakes"])?; + let out = String::from_utf8(output.stdout)?; + get_wg_exit_clients_online_internal(out) +} + +/// Internal helper function for ci testing get_last_handshake_time +fn get_last_active_handshake_time_internal(out: String) -> Result, Error> { + let mut timestamps = Vec::new(); + for line in out.lines() { + let content: Vec<&str> = line.split('\t').collect(); + let mut itr = content.iter(); + let wg_key: WgKey = match itr.next() { + Some(val) => val.parse()?, + None => { + return Err(KernelInterfaceError::RuntimeError( + "Invalid line!".to_string(), + )) + } + }; + let timestamp = match itr.next() { + Some(val) => val.parse()?, + None => { + return Err(KernelInterfaceError::RuntimeError( + "Invalid line!".to_string(), + )) + } + }; + let d = UNIX_EPOCH + Duration::from_secs(timestamp); + timestamps.push((wg_key, d)) } + Ok(timestamps) +} - /// Gets a list of all active wireguard interfaces on this device - pub fn get_list_of_wireguard_interfaces(&self) -> Result, Error> { - let output = self.run_command("wg", &["show", "interfaces"])?; - let out = String::from_utf8(output.stdout)?; - let mut interfaces = Vec::new(); - for interface in out.split_ascii_whitespace() { - interfaces.push(interface.to_string()) - } - Ok(interfaces) +/// Returns the last handshake time of every client on this tunnel. +pub fn get_last_handshake_time(ifname: &str) -> Result, Error> { + let output = run_command("wg", &["show", ifname, "latest-handshakes"])?; + let out = String::from_utf8(output.stdout)?; + get_last_active_handshake_time_internal(out) +} + +/// Returns the last handshake time of every ACTIVE client on this tunnel. +/// An active handshake mean a wireguard tunnel that has a latest handshake value +/// When running wg show wg_exit latest-handshake, a entries with timestamp 0 are inactive +pub fn get_last_active_handshake_time(ifname: &str) -> Result, Error> { + let timestamps = get_last_handshake_time(ifname)?; + let timestamps = timestamps + .into_iter() + .filter(|(_, time)| *time != UNIX_EPOCH) + .collect(); + Ok(timestamps) +} + +/// Gets a list of all active wireguard interfaces on this device +pub fn get_list_of_wireguard_interfaces() -> Result, Error> { + let output = run_command("wg", &["show", "interfaces"])?; + let out = String::from_utf8(output.stdout)?; + let mut interfaces = Vec::new(); + for interface in out.split_ascii_whitespace() { + interfaces.push(interface.to_string()) } + Ok(interfaces) } #[test] @@ -162,63 +168,13 @@ fn test_durations() { #[test] fn test_get_wg_exit_clients_online() { - use crate::KI; - - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - - let mut counter = 0; - - let link_args = &["show", "wg_exit", "latest-handshakes"]; - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "wg"); - counter += 1; - - match counter { - 1 => { - assert_eq!(args, link_args); - Ok(Output{ - stdout: format!("88gbNAZx7NoNK9hatYuDkeZOjQ8EBmJ8VBpcFhXPqHs= {}\nW1BwNSC9ulTutCg53KIlo+z2ihkXao3sXHaBBpaCXEw= 1536936247\n9jRr6euMHu3tBIsZyqxUmjbuKVVFZCBOYApOR2pLNkQ= 0", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()).as_bytes().to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("command called too many times"), - } - })); + let stdout = format!("88gbNAZx7NoNK9hatYuDkeZOjQ8EBmJ8VBpcFhXPqHs= {}\nW1BwNSC9ulTutCg53KIlo+z2ihkXao3sXHaBBpaCXEw= 1536936247\n9jRr6euMHu3tBIsZyqxUmjbuKVVFZCBOYApOR2pLNkQ= 0", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()); - assert_eq!(KI.get_wg_exit_clients_online("wg_exit").unwrap(), 1); + assert_eq!(get_wg_exit_clients_online_internal(stdout).unwrap(), 1); } #[test] fn test_get_last_handshake_time() { - use crate::KI; - - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - - let mut counter = 0; - - let link_args = &["show", "wg1", "latest-handshakes"]; - KI.set_mock(Box::new(move |program, args| { - assert_eq!(program, "wg"); - counter += 1; - - match counter { - 1 => { - assert_eq!(args, link_args); - Ok(Output{ - stdout: format!("88gbNAZx7NoNK9hatYuDkeZOjQ8EBmJ8VBpcFhXPqHs= {}\nbGkj7Z6bX1593G0pExfzxocWKhS3Un9uifIhZP9c5iM= 1536936247\n9jRr6euMHu3tBIsZyqxUmjbuKVVFZCBOYApOR2pLNkQ= 0", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()).as_bytes().to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("command called too many times"), - } - })); - let wgkey1: WgKey = "88gbNAZx7NoNK9hatYuDkeZOjQ8EBmJ8VBpcFhXPqHs=" .parse() .unwrap(); @@ -228,9 +184,9 @@ fn test_get_last_handshake_time() { let wgkey3: WgKey = "9jRr6euMHu3tBIsZyqxUmjbuKVVFZCBOYApOR2pLNkQ=" .parse() .unwrap(); + let stdout = format!("88gbNAZx7NoNK9hatYuDkeZOjQ8EBmJ8VBpcFhXPqHs= {}\nbGkj7Z6bX1593G0pExfzxocWKhS3Un9uifIhZP9c5iM= 1536936247\n9jRr6euMHu3tBIsZyqxUmjbuKVVFZCBOYApOR2pLNkQ= 0", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()); - let res = KI - .get_last_handshake_time("wg1") + let res = get_last_active_handshake_time_internal(stdout.to_string()) .expect("Failed to run get_last_handshake_time!"); assert!(res.contains(&(wgkey3, SystemTime::UNIX_EPOCH))); assert!(res.contains(&( diff --git a/althea_kernel_interface/src/time.rs b/althea_kernel_interface/src/time.rs index ff41fd8a3..470ff5b28 100644 --- a/althea_kernel_interface/src/time.rs +++ b/althea_kernel_interface/src/time.rs @@ -1,25 +1,21 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - -use super::KernelInterface; -use crate::KernelInterfaceError as Error; +use crate::{run_command, KernelInterfaceError as Error}; use std::process::Output; +use std::time::{SystemTime, UNIX_EPOCH}; -impl dyn KernelInterface { - /// Set the router's time using "date -s @seconds_since_unix_epoch" - pub fn set_local_time(&self, time: SystemTime) -> Result { - let time_secs = time.duration_since(UNIX_EPOCH).unwrap().as_secs(); - let t = format!("@{time_secs}"); - trace!("setting local time via 'date -s {}'", t); - match self.run_command("date", &["-s", t.as_str()]) { - Ok(output) => { - trace!("time was changed. New time is: {:?}", SystemTime::now()); - trace!("output: {:?}", output); - Ok(output) - } - Err(e) => { - error!("Failed to set local time: {:?}", e); - Err(e) - } +/// Set the router's time using "date -s @seconds_since_unix_epoch" +pub fn set_local_time(time: SystemTime) -> Result { + let time_secs = time.duration_since(UNIX_EPOCH).unwrap().as_secs(); + let t = format!("@{time_secs}"); + trace!("setting local time via 'date -s {}'", t); + match run_command("date", &["-s", t.as_str()]) { + Ok(output) => { + trace!("time was changed. New time is: {:?}", SystemTime::now()); + trace!("output: {:?}", output); + Ok(output) + } + Err(e) => { + error!("Failed to set local time: {:?}", e); + Err(e) } } } diff --git a/althea_kernel_interface/src/traffic_control.rs b/althea_kernel_interface/src/traffic_control.rs index 6adec46ee..c07d99f8d 100644 --- a/althea_kernel_interface/src/traffic_control.rs +++ b/althea_kernel_interface/src/traffic_control.rs @@ -4,456 +4,444 @@ //! Rita common in contrast is a simple limitation of the neighbors tunnel, which does not do //! any classful categorization. As a result one uses tbf and the other uses the clsssful htb +use crate::{run_command, KernelInterfaceError as Error}; use ipnetwork::IpNetwork; +use std::net::Ipv4Addr; -use crate::KernelInterface; -use crate::KernelInterfaceError as Error; +/// Determines if the provided interface has a configured qdisc +pub fn has_qdisc(iface_name: &str) -> Result { + let result = run_command("tc", &["qdisc", "show", "dev", iface_name])?; -use std::net::Ipv4Addr; + if !result.status.success() { + let res = String::from_utf8(result.stderr)?; + return Err(Error::TrafficControlError(format!( + "Failed to check qdisc for {iface_name}! {res:?}" + ))); + } -impl dyn KernelInterface { - /// Determines if the provided interface has a configured qdisc - pub fn has_qdisc(&self, iface_name: &str) -> Result { - let result = self.run_command("tc", &["qdisc", "show", "dev", iface_name])?; + let stdout = &String::from_utf8(result.stdout)?; - if !result.status.success() { - let res = String::from_utf8(result.stderr)?; - return Err(Error::TrafficControlError(format!( - "Failed to check qdisc for {iface_name}! {res:?}" - ))); - } + trace!("has_qdisc: {} {}", stdout, !stdout.contains("noqueue")); + Ok(!stdout.contains("noqueue")) +} - let stdout = &String::from_utf8(result.stdout)?; +/// Determines if the provided flow is assigned +pub fn has_flow(ip: Ipv4Addr, iface_name: &str) -> Result { + let class_id = get_class_id(ip); + let result = run_command("tc", &["filter", "show", "dev", iface_name])?; - trace!("has_qdisc: {} {}", stdout, !stdout.contains("noqueue")); - Ok(!stdout.contains("noqueue")) + if !result.status.success() { + let res = String::from_utf8(result.stderr)?; + return Err(Error::TrafficControlError(format!( + "Failed to check filter for {class_id}! {res:?}" + ))); } - /// Determines if the provided flow is assigned - pub fn has_flow(&self, ip: Ipv4Addr, iface_name: &str) -> Result { - let class_id = self.get_class_id(ip); - let result = self.run_command("tc", &["filter", "show", "dev", iface_name])?; + let stdout = &String::from_utf8(result.stdout)?; + Ok(stdout.contains(&format!("1:{class_id}"))) +} - if !result.status.success() { - let res = String::from_utf8(result.stderr)?; - return Err(Error::TrafficControlError(format!( - "Failed to check filter for {class_id}! {res:?}" - ))); - } +/// Gets the full flows list to pass to bulk functions +pub fn get_flows(iface_name: &str) -> Result { + let result = run_command("tc", &["filter", "show", "dev", iface_name])?; - let stdout = &String::from_utf8(result.stdout)?; - Ok(stdout.contains(&format!("1:{class_id}"))) + if !result.status.success() { + let res = String::from_utf8(result.stderr)?; + return Err(Error::TrafficControlError(format!( + "Failed to get flows {res:?}" + ))); } + Ok(String::from_utf8(result.stdout)?) +} - /// Gets the full flows list to pass to bulk functions - pub fn get_flows(&self, iface_name: &str) -> Result { - let result = self.run_command("tc", &["filter", "show", "dev", iface_name])?; +/// A version of the flows check designed to be run from the raw input, more efficient +/// in the exit setup loop than running the same command several hundred times +pub fn has_flow_bulk(ip: Ipv4Addr, tc_out: &str) -> bool { + let class_id = get_class_id(ip); + tc_out.contains(&format!("1:{class_id}")) +} - if !result.status.success() { - let res = String::from_utf8(result.stderr)?; - return Err(Error::TrafficControlError(format!( - "Failed to get flows {res:?}" - ))); - } - Ok(String::from_utf8(result.stdout)?) - } +/// Determines if the provided flow is assigned +pub fn has_class(ip: Ipv4Addr, iface_name: &str) -> Result { + let class_id = get_class_id(ip); + let result = run_command("tc", &["class", "show", "dev", iface_name])?; - /// A version of the flows check designed to be run from the raw input, more efficient - /// in the exit setup loop than running the same command several hundred times - pub fn has_flow_bulk(&self, ip: Ipv4Addr, tc_out: &str) -> bool { - let class_id = self.get_class_id(ip); - tc_out.contains(&format!("1:{class_id}")) + if !result.status.success() { + let res = String::from_utf8(result.stderr)?; + return Err(Error::TrafficControlError(format!( + "Failed to check filter for {class_id}! {res:?}" + ))); } - /// Determines if the provided flow is assigned - pub fn has_class(&self, ip: Ipv4Addr, iface_name: &str) -> Result { - let class_id = self.get_class_id(ip); - let result = self.run_command("tc", &["class", "show", "dev", iface_name])?; - - if !result.status.success() { - let res = String::from_utf8(result.stderr)?; - return Err(Error::TrafficControlError(format!( - "Failed to check filter for {class_id}! {res:?}" - ))); - } - - let stdout = &String::from_utf8(result.stdout)?; - for line in stdout.lines() { - let split = line.split_ascii_whitespace(); - for item in split { - if *item == format!("1:{class_id}") { - return Ok(true); - } + let stdout = &String::from_utf8(result.stdout)?; + for line in stdout.lines() { + let split = line.split_ascii_whitespace(); + for item in split { + if *item == format!("1:{class_id}") { + return Ok(true); } } - Ok(false) } + Ok(false) +} - /// Determines if the provided interface has a configured qdisc - pub fn has_limit(&self, iface_name: &str) -> Result { - let result = self.run_command("tc", &["qdisc", "show", "dev", iface_name])?; - - if !result.status.success() { - let res = String::from_utf8(result.stderr)?; - return Err(Error::TrafficControlError(format!( - "Failed to check limit for {iface_name}! {res:?}" - ))); - } +/// Determines if the provided interface has a configured qdisc +pub fn has_limit(iface_name: &str) -> Result { + let result = run_command("tc", &["qdisc", "show", "dev", iface_name])?; - let stdout = &String::from_utf8(result.stdout)?; - Ok((stdout.contains("htb") || stdout.contains("tbf")) - && !stdout.contains("codel") - && !stdout.contains("cake") - && !stdout.contains("noqueue")) + if !result.status.success() { + let res = String::from_utf8(result.stderr)?; + return Err(Error::TrafficControlError(format!( + "Failed to check limit for {iface_name}! {res:?}" + ))); } - /// Determines if the provided interface has a configured qdisc - pub fn has_cake(&self, iface_name: &str) -> Result { - let result = self.run_command("tc", &["qdisc", "show", "dev", iface_name])?; + let stdout = &String::from_utf8(result.stdout)?; + Ok((stdout.contains("htb") || stdout.contains("tbf")) + && !stdout.contains("codel") + && !stdout.contains("cake") + && !stdout.contains("noqueue")) +} - if !result.status.success() { - let res = String::from_utf8(result.stderr)?; - return Err(Error::TrafficControlError(format!( - "Failed to check limit for {iface_name}! {res:?}" - ))); - } +/// Determines if the provided interface has a configured qdisc +pub fn has_cake(iface_name: &str) -> Result { + let result = run_command("tc", &["qdisc", "show", "dev", iface_name])?; - let stdout = &String::from_utf8(result.stdout)?; - Ok((stdout.contains("codel") || stdout.contains("cake")) - && !stdout.contains("tbf") - && !stdout.contains("noqueue") - && !stdout.contains("htb")) + if !result.status.success() { + let res = String::from_utf8(result.stderr)?; + return Err(Error::TrafficControlError(format!( + "Failed to check limit for {iface_name}! {res:?}" + ))); } - /// This sets up latency protecting flow control, either cake on openwrt - /// or fq_codel on older devices/kernels, the Cake configuration sets several advanced parameters - /// that are not reflected if we fall back to codel - pub fn set_codel_shaping(&self, iface_name: &str, speed: Option) -> Result<(), Error> { - let operator = if self.has_qdisc(iface_name)? { - "change" - } else { - "add" - }; - let mut cake_args = vec![ - "qdisc", operator, "dev", iface_name, "root", "handle", "1:", "cake", - ]; - // declared here but used only in the match to provide a longer lifetime - let mbit; - - match speed { - Some(val) => { - mbit = format!("{val}mbit"); - cake_args.extend(["bandwidth", &mbit]) - } - None => cake_args.extend(["unlimited"]), - } + let stdout = &String::from_utf8(result.stdout)?; + Ok((stdout.contains("codel") || stdout.contains("cake")) + && !stdout.contains("tbf") + && !stdout.contains("noqueue") + && !stdout.contains("htb")) +} - // cake arguments for per hop tunnels only - cake_args.extend([ - // we want to use the 'internet' parameter here because the total rtt - // of the path from endpoint to endpoint is what this value cares about - // not neighbor to neighbor - "internet", - // diffserv4 allocates 50% of the connection to video streams and - // generally recognizes more traffic classes than the default diffserv3 - // there's some debate by cake maintainers internally if this is a good idea - "diffserv4", - ]); +/// This sets up latency protecting flow control, either cake on openwrt +/// or fq_codel on older devices/kernels, the Cake configuration sets several advanced parameters +/// that are not reflected if we fall back to codel +pub fn set_codel_shaping(iface_name: &str, speed: Option) -> Result<(), Error> { + let operator = if has_qdisc(iface_name)? { + "change" + } else { + "add" + }; + let mut cake_args = vec![ + "qdisc", operator, "dev", iface_name, "root", "handle", "1:", "cake", + ]; + // declared here but used only in the match to provide a longer lifetime + let mbit; + + match speed { + Some(val) => { + mbit = format!("{val}mbit"); + cake_args.extend(["bandwidth", &mbit]) + } + None => cake_args.extend(["unlimited"]), + } - let output = self.run_command("tc", &cake_args)?; + // cake arguments for per hop tunnels only + cake_args.extend([ + // we want to use the 'internet' parameter here because the total rtt + // of the path from endpoint to endpoint is what this value cares about + // not neighbor to neighbor + "internet", + // diffserv4 allocates 50% of the connection to video streams and + // generally recognizes more traffic classes than the default diffserv3 + // there's some debate by cake maintainers internally if this is a good idea + "diffserv4", + ]); + + let output = run_command("tc", &cake_args)?; + + if !output.status.success() { + trace!( + "No support for the Cake qdisc is detected, falling back to fq_codel. Error: {}", + String::from_utf8(output.stderr)? + ); + trace!("Command was tc {}", crate::print_str_array(&cake_args)); + warn!("sch_cake is strongly recommended, you should install it"); + let output = run_command( + "tc", + &[ + "qdisc", operator, "dev", iface_name, "root", "handle", "1:", "fq_codel", "target", + "100ms", + ], + )?; if !output.status.success() { + // final fallback on FIFO this is a very old machine indeed trace!( - "No support for the Cake qdisc is detected, falling back to fq_codel. Error: {}", + "No support for the fq codel qdisc is detected, falling back to fifo. Error: {}", String::from_utf8(output.stderr)? ); trace!("Command was tc {}", crate::print_str_array(&cake_args)); - warn!("sch_cake is strongly recommended, you should install it"); - let output = self.run_command( + let output = run_command( "tc", &[ - "qdisc", operator, "dev", iface_name, "root", "handle", "1:", "fq_codel", - "target", "100ms", + "qdisc", operator, "dev", iface_name, "root", "handle", "1:", "fifo", ], )?; - if !output.status.success() { - // final fallback on FIFO this is a very old machine indeed - trace!( - "No support for the fq codel qdisc is detected, falling back to fifo. Error: {}", - String::from_utf8(output.stderr)? - ); - trace!("Command was tc {}", crate::print_str_array(&cake_args)); - let output = self.run_command( - "tc", - &[ - "qdisc", operator, "dev", iface_name, "root", "handle", "1:", "fifo", - ], - )?; - if !output.status.success() { - let res = String::from_utf8(output.stderr)?; - error!("Cake, fq_codel and fallback FIFO have all failed!"); - return Err(Error::TrafficControlError(format!( - "Failed to create new qdisc limit! {res:?}" - ))); - } + let res = String::from_utf8(output.stderr)?; + error!("Cake, fq_codel and fallback FIFO have all failed!"); + return Err(Error::TrafficControlError(format!( + "Failed to create new qdisc limit! {res:?}" + ))); } } - - Ok(()) } - /// Creates a qdisc limit with the given bandwidth tuned for the correct rate - /// this limit uses tbf which is classless and faster since we leave prioritization - /// to the fq_codel on the ingress and egress interfaces - pub fn set_classless_limit(&self, iface_name: &str, bw: u32) -> Result<(), Error> { - if self.has_qdisc(iface_name)? { - self.delete_qdisc(iface_name)?; - } - - // we need 1kbyte of burst cache per mbit of bandwidth to actually keep things - // moving - let burst = bw * 1000u32; - // amount of time a packet can spend in the burst cache, 40ms - let latency = 40u32; - - let output = self.run_command( - "tc", - &[ - "qdisc", - "add", - "dev", - iface_name, - "root", - "handle", - "1:", - "tbf", - "latency", - &format!("{latency}ms"), - "burst", - &format!("{burst}"), - "rate", - &format!("{bw}kbit"), - ], - )?; + Ok(()) +} - if output.status.success() { - Ok(()) - } else { - let res = String::from_utf8(output.stderr)?; - Err(Error::TrafficControlError(format!( - "Failed to create new qdisc limit! {res:?}" - ))) - } +/// Creates a qdisc limit with the given bandwidth tuned for the correct rate +/// this limit uses tbf which is classless and faster since we leave prioritization +/// to the fq_codel on the ingress and egress interfaces +pub fn set_classless_limit(iface_name: &str, bw: u32) -> Result<(), Error> { + if has_qdisc(iface_name)? { + delete_qdisc(iface_name)?; } - /// Creates the root limit on the wg_exit tunnel for the exit, under which all other classes - /// operate - pub fn create_root_classful_limit(&self, iface_name: &str) -> Result<(), Error> { - let output = self.run_command( - "tc", - &[ - "qdisc", - "add", - "dev", - iface_name, - "root", - "handle", - "1:", - "htb", - "default", - "0", - "r2q", - "1", - "direct_qlen", - "1000000", - ], - )?; + // we need 1kbyte of burst cache per mbit of bandwidth to actually keep things + // moving + let burst = bw * 1000u32; + // amount of time a packet can spend in the burst cache, 40ms + let latency = 40u32; + + let output = run_command( + "tc", + &[ + "qdisc", + "add", + "dev", + iface_name, + "root", + "handle", + "1:", + "tbf", + "latency", + &format!("{latency}ms"), + "burst", + &format!("{burst}"), + "rate", + &format!("{bw}kbit"), + ], + )?; + + if output.status.success() { + Ok(()) + } else { + let res = String::from_utf8(output.stderr)?; + Err(Error::TrafficControlError(format!( + "Failed to create new qdisc limit! {res:?}" + ))) + } +} - if output.status.success() { - Ok(()) - } else { - let res = String::from_utf8(output.stderr)?; - Err(Error::TrafficControlError(format!( - "Failed to create new qdisc limit! {res:?}" - ))) - } +/// Creates the root limit on the wg_exit tunnel for the exit, under which all other classes +/// operate +pub fn create_root_classful_limit(iface_name: &str) -> Result<(), Error> { + let output = run_command( + "tc", + &[ + "qdisc", + "add", + "dev", + iface_name, + "root", + "handle", + "1:", + "htb", + "default", + "0", + "r2q", + "1", + "direct_qlen", + "1000000", + ], + )?; + + if output.status.success() { + Ok(()) + } else { + let res = String::from_utf8(output.stderr)?; + Err(Error::TrafficControlError(format!( + "Failed to create new qdisc limit! {res:?}" + ))) } +} - pub fn delete_class(&self, iface_name: &str, ip: Ipv4Addr) -> Result<(), Error> { - let class_id = self.get_class_id(ip); - match self.has_class(ip, iface_name) { - Ok(has_class) => { - if has_class { - self.run_command( - "tc", - &[ - "class", - "del", - "dev", - iface_name, - "parent", - "1:", - "classid", - &format!("1:{class_id}"), - ], - )?; - } - Ok(()) - } - Err(e) => { - error!( - "Unable to find class for ip {:?} on {:?} with {:?}", - ip, iface_name, e - ); - Err(e) +pub fn delete_class(iface_name: &str, ip: Ipv4Addr) -> Result<(), Error> { + let class_id = get_class_id(ip); + match has_class(ip, iface_name) { + Ok(has_class) => { + if has_class { + run_command( + "tc", + &[ + "class", + "del", + "dev", + iface_name, + "parent", + "1:", + "classid", + &format!("1:{class_id}"), + ], + )?; } + Ok(()) } - } - - pub fn set_class_limit( - &self, - iface_name: &str, - min_bw: u32, - max_bw: u32, - ip: Ipv4Addr, - ) -> Result<(), Error> { - let class_id = self.get_class_id(ip); - let modifier = if self.has_class(ip, iface_name)? { - "change" - } else { - "add" - }; - - let output = self.run_command( - "tc", - &[ - "class", - modifier, - "dev", - iface_name, - "parent", - "1:", - "classid", - &format!("1:{class_id}"), - "htb", - "rate", - &format!("{min_bw}kbit"), - "ceil", - &format!("{max_bw}kbit"), - ], - )?; - - if !output.status.success() { - let res = String::from_utf8(output.stderr)?; - return Err(Error::TrafficControlError(format!( - "Failed to update qdisc class limit! {res:?}" - ))); + Err(e) => { + error!( + "Unable to find class for ip {:?} on {:?} with {:?}", + ip, iface_name, e + ); + Err(e) } - - Ok(()) } +} - /// Generates a unique traffic class id for a exit user, essentially a really dumb hashing function - pub fn get_class_id(&self, ip: Ipv4Addr) -> u32 { - let num: u32 = ip.into(); - num % 9999 //9999 is the maximum flow id value allowed +pub fn set_class_limit( + iface_name: &str, + min_bw: u32, + max_bw: u32, + ip: Ipv4Addr, +) -> Result<(), Error> { + let class_id = get_class_id(ip); + let modifier = if has_class(ip, iface_name)? { + "change" + } else { + "add" + }; + + let output = run_command( + "tc", + &[ + "class", + modifier, + "dev", + iface_name, + "parent", + "1:", + "classid", + &format!("1:{class_id}"), + "htb", + "rate", + &format!("{min_bw}kbit"), + "ceil", + &format!("{max_bw}kbit"), + ], + )?; + + if !output.status.success() { + let res = String::from_utf8(output.stderr)?; + return Err(Error::TrafficControlError(format!( + "Failed to update qdisc class limit! {res:?}" + ))); } - /// Filters traffic from a given ipv6 address into the class that we are using - /// to shape that traffic on the exit side, uses the last two octets of the ip - /// to generate a class id. - pub fn create_flow_by_ipv6( - &self, - iface_name: &str, - ip_net: IpNetwork, - ip: Ipv4Addr, - ) -> Result<(), Error> { - let class_id = self.get_class_id(ip); - - let output = self.run_command( - "tc", - &[ - "filter", - "add", - "dev", - iface_name, - "parent", - "1:", - "protocol", - "ipv6", - "u32", - "match", - "ip6", - "dst", - &ip_net.to_string(), - "flowid", - &format!("1:{class_id}"), - ], - )?; - - if output.status.success() { - Ok(()) - } else { - let res = String::from_utf8(output.stderr)?; - Err(Error::TrafficControlError(format!( - "Failed to create limit by ip! {res:?}" - ))) - } - } + Ok(()) +} - /// Filters traffic from a given ipv4 address into the class that we are using - /// to shape that traffic on the exit side, uses the last two octets of the ip - /// to generate a class id. - pub fn create_flow_by_ip(&self, iface_name: &str, ip: Ipv4Addr) -> Result<(), Error> { - let class_id = self.get_class_id(ip); +/// Generates a unique traffic class id for a exit user, essentially a really dumb hashing function +pub fn get_class_id(ip: Ipv4Addr) -> u32 { + let num: u32 = ip.into(); + num % 9999 //9999 is the maximum flow id value allowed +} - let output = self.run_command( - "tc", - &[ - "filter", - "add", - "dev", - iface_name, - "parent", - "1:", - "protocol", - "ip", - "u32", - "match", - "ip", - "dst", - &format!("{ip}/32"), - "flowid", - &format!("1:{class_id}"), - ], - )?; +/// Filters traffic from a given ipv6 address into the class that we are using +/// to shape that traffic on the exit side, uses the last two octets of the ip +/// to generate a class id. +pub fn create_flow_by_ipv6(iface_name: &str, ip_net: IpNetwork, ip: Ipv4Addr) -> Result<(), Error> { + let class_id = get_class_id(ip); + + let output = run_command( + "tc", + &[ + "filter", + "add", + "dev", + iface_name, + "parent", + "1:", + "protocol", + "ipv6", + "u32", + "match", + "ip6", + "dst", + &ip_net.to_string(), + "flowid", + &format!("1:{class_id}"), + ], + )?; + + if output.status.success() { + Ok(()) + } else { + let res = String::from_utf8(output.stderr)?; + Err(Error::TrafficControlError(format!( + "Failed to create limit by ip! {res:?}" + ))) + } +} - if output.status.success() { - Ok(()) - } else { - let res = String::from_utf8(output.stderr)?; - Err(Error::TrafficControlError(format!( - "Failed to create limit by ip! {res:?}" - ))) - } +/// Filters traffic from a given ipv4 address into the class that we are using +/// to shape that traffic on the exit side, uses the last two octets of the ip +/// to generate a class id. +pub fn create_flow_by_ip(iface_name: &str, ip: Ipv4Addr) -> Result<(), Error> { + let class_id = get_class_id(ip); + + let output = run_command( + "tc", + &[ + "filter", + "add", + "dev", + iface_name, + "parent", + "1:", + "protocol", + "ip", + "u32", + "match", + "ip", + "dst", + &format!("{ip}/32"), + "flowid", + &format!("1:{class_id}"), + ], + )?; + + if output.status.success() { + Ok(()) + } else { + let res = String::from_utf8(output.stderr)?; + Err(Error::TrafficControlError(format!( + "Failed to create limit by ip! {res:?}" + ))) } +} - /// deletes the interface qdisc - pub fn delete_qdisc(&self, iface_name: &str) -> Result<(), Error> { - let output = self.run_command("tc", &["qdisc", "del", "dev", iface_name, "root"])?; - if output.status.success() { - Ok(()) - } else { - Err(Error::TrafficControlError( - "Failed to delete qdisc limit".to_string(), - )) - } +/// deletes the interface qdisc +pub fn delete_qdisc(iface_name: &str) -> Result<(), Error> { + let output = run_command("tc", &["qdisc", "del", "dev", iface_name, "root"])?; + if output.status.success() { + Ok(()) + } else { + Err(Error::TrafficControlError( + "Failed to delete qdisc limit".to_string(), + )) } } #[test] fn get_id() { - use crate::KI; - println!("{}", KI.get_class_id("172.168.4.121".parse().unwrap())); + println!("{}", get_class_id("172.168.4.121".parse().unwrap())); } diff --git a/althea_kernel_interface/src/udp_socket_table.rs b/althea_kernel_interface/src/udp_socket_table.rs index 1253398e8..9b47d68ce 100644 --- a/althea_kernel_interface/src/udp_socket_table.rs +++ b/althea_kernel_interface/src/udp_socket_table.rs @@ -1,4 +1,3 @@ -use crate::KernelInterface; use crate::KernelInterfaceError; use crate::KernelInterfaceError as Error; use std::collections::HashSet; @@ -29,31 +28,29 @@ fn parse_local_port(s: &str) -> Result { } } -impl dyn KernelInterface { - fn read_udp_socket_table(&self) -> Result { - let mut f = File::open("/proc/net/udp")?; - let mut contents = String::new(); +fn read_udp_socket_table() -> Result { + let mut f = File::open("/proc/net/udp")?; + let mut contents = String::new(); - f.read_to_string(&mut contents)?; + f.read_to_string(&mut contents)?; - Ok(contents) - } + Ok(contents) +} - /// Returns list of ports in use as seen in the UDP socket table (/proc/net/udp) - pub fn used_ports(&self) -> Result, Error> { - let udp_sockets_table = self.read_udp_socket_table()?; - let mut lines = udp_sockets_table.split('\n'); +/// Returns list of ports in use as seen in the UDP socket table (/proc/net/udp) +pub fn used_ports() -> Result, Error> { + let udp_sockets_table = read_udp_socket_table()?; + let mut lines = udp_sockets_table.split('\n'); - lines.next(); // advance iterator to skip header + lines.next(); // advance iterator to skip header - let ports: HashSet = lines - .take_while(|line| !line.is_empty()) // until end of the table is reached, - .map(parse_local_port) // parse each udp port, - .filter_map(Result::ok) // only taking those which parsed successfully - .collect(); + let ports: HashSet = lines + .take_while(|line| !line.is_empty()) // until end of the table is reached, + .map(parse_local_port) // parse each udp port, + .filter_map(Result::ok) // only taking those which parsed successfully + .collect(); - Ok(ports) - } + Ok(ports) } #[test] diff --git a/althea_kernel_interface/src/upgrade.rs b/althea_kernel_interface/src/upgrade.rs index 3c56a260f..2cdf540af 100644 --- a/althea_kernel_interface/src/upgrade.rs +++ b/althea_kernel_interface/src/upgrade.rs @@ -1,78 +1,75 @@ -use super::KernelInterface; use crate::{ opkg_feeds::{get_release_feed, set_release_feed, CUSTOMFEEDS}, - KernelInterfaceError as Error, + run_command, KernelInterfaceError as Error, }; use althea_types::{OpkgCommand, SysupgradeCommand}; use std::process::Output; -impl dyn KernelInterface { - pub fn perform_sysupgrade(&self, command: SysupgradeCommand) -> Result { - //If empty url, return error - if command.url.is_empty() { - info!("Empty url given to sysupgrade"); - return Err(Error::RuntimeError( - "Empty url given to sysupgrade".to_string(), - )); - } - - // append path to end of flags - let mut args = if command.flags.is_some() { - command.flags.unwrap() - } else { - Vec::new() - }; - args.push(command.url); - let args_ref: Vec<&str> = args.iter().map(std::ops::Deref::deref).collect(); - info!( - "Running the command /sbin/sysupgrade with args: {:?}", - args_ref - ); - self.run_command("/sbin/sysupgrade", &args_ref) +pub fn perform_sysupgrade(command: SysupgradeCommand) -> Result { + //If empty url, return error + if command.url.is_empty() { + info!("Empty url given to sysupgrade"); + return Err(Error::RuntimeError( + "Empty url given to sysupgrade".to_string(), + )); } - /// This function checks if the function provided is update or install. In case of install, for each of the packages - /// present, the arguments given are applied and opkg install is run - pub fn perform_opkg(&self, command: OpkgCommand) -> Result { - match command { - OpkgCommand::Install { - packages, - arguments, - } => { - let mut args = arguments; - args.insert(0, "install".to_string()); - for package in packages { - args.push(package); - } - info!("Running opkg install with args: {:?}", args); - let args_ref: Vec<&str> = args.iter().map(std::ops::Deref::deref).collect(); - self.run_command("opkg", &args_ref) - } - OpkgCommand::Remove { - packages, - arguments, - } => { - let mut args = arguments; - args.insert(0, "remove".to_string()); - for package in packages { - args.push(package); - } - info!("Running opkg remove with args: {:?}", args); - let args_ref: Vec<&str> = args.iter().map(std::ops::Deref::deref).collect(); - self.run_command("opkg", &args_ref) + // append path to end of flags + let mut args = if command.flags.is_some() { + command.flags.unwrap() + } else { + Vec::new() + }; + args.push(command.url); + let args_ref: Vec<&str> = args.iter().map(std::ops::Deref::deref).collect(); + info!( + "Running the command /sbin/sysupgrade with args: {:?}", + args_ref + ); + run_command("/sbin/sysupgrade", &args_ref) +} + +/// This function checks if the function provided is update or install. In case of install, for each of the packages +/// present, the arguments given are applied and opkg install is run +pub fn perform_opkg(command: OpkgCommand) -> Result { + match command { + OpkgCommand::Install { + packages, + arguments, + } => { + let mut args = arguments; + args.insert(0, "install".to_string()); + for package in packages { + args.push(package); } - OpkgCommand::Update { - feed, - feed_name, - arguments, - } => { - handle_release_feed_update(feed, feed_name)?; - let mut args = arguments; - args.insert(0, "update".to_string()); - info!("Running opkg update with args: {:?}", args); - let args_ref: Vec<&str> = args.iter().map(std::ops::Deref::deref).collect(); - self.run_command("opkg", &args_ref) + info!("Running opkg install with args: {:?}", args); + let args_ref: Vec<&str> = args.iter().map(std::ops::Deref::deref).collect(); + run_command("opkg", &args_ref) + } + OpkgCommand::Remove { + packages, + arguments, + } => { + let mut args = arguments; + args.insert(0, "remove".to_string()); + for package in packages { + args.push(package); } + info!("Running opkg remove with args: {:?}", args); + let args_ref: Vec<&str> = args.iter().map(std::ops::Deref::deref).collect(); + run_command("opkg", &args_ref) + } + OpkgCommand::Update { + feed, + feed_name, + arguments, + } => { + handle_release_feed_update(feed, feed_name)?; + let mut args = arguments; + args.insert(0, "update".to_string()); + info!("Running opkg update with args: {:?}", args); + let args_ref: Vec<&str> = args.iter().map(std::ops::Deref::deref).collect(); + run_command("opkg", &args_ref) } } } diff --git a/althea_kernel_interface/src/wg_iface_counter.rs b/althea_kernel_interface/src/wg_iface_counter.rs index db1e2427f..4f1d55ddf 100644 --- a/althea_kernel_interface/src/wg_iface_counter.rs +++ b/althea_kernel_interface/src/wg_iface_counter.rs @@ -2,8 +2,8 @@ //! this is mostly used in client and exit billing where we only have to concern ourselves with //! a single destination and a single price. -use crate::KernelInterfaceError as Error; -use crate::{KernelInterface, KernelInterfaceError}; +use crate::KernelInterfaceError; +use crate::{run_command, KernelInterfaceError as Error}; use althea_types::WgKey; use regex::Regex; use std::collections::HashMap; @@ -43,75 +43,54 @@ pub fn prepare_usage_history( } } -impl dyn KernelInterface { - /// Takes a wg interface name and provides upload and download since creation in bytes - /// in a hashmap indexed by peer WireGuard key - pub fn read_wg_counters(&self, wg_name: &str) -> Result, Error> { - let output = self.run_command("wg", &["show", wg_name, "transfer"])?; - if !output.stderr.is_empty() { - return Err(KernelInterfaceError::RuntimeError(format!( - "received error from wg command: {}", - String::from_utf8(output.stderr)? - ))); - } - - lazy_static! { - static ref RE: Regex = Regex::new( - r"(?P[+/=0-9a-zA-Z]+)\t(?P[0-9]+)\t(?P[0-9]+)\n*" - ) - .expect("Unable to compile regular expression"); - } +/// Internal function to parse the output of `wg show transfer` +fn read_wg_counters_internal(stdout: String) -> Result, Error> { + lazy_static! { + static ref RE: Regex = + Regex::new(r"(?P[+/=0-9a-zA-Z]+)\t(?P[0-9]+)\t(?P[0-9]+)\n*") + .expect("Unable to compile regular expression"); + } - let mut result = HashMap::new(); - for item in RE.captures_iter(&String::from_utf8(output.stdout)?) { - let usage = WgUsage { - upload: item["upload"].parse()?, - download: item["download"].parse()?, - }; - match item["key"].parse() { - Ok(key) => { - result.insert(key, usage); - } - Err(e) => warn!( - "Failed to parse WgKey {} with {:?}", - item["key"].to_string(), - e - ), + let mut result = HashMap::new(); + for item in RE.captures_iter(&stdout) { + let usage = WgUsage { + upload: item["upload"].parse()?, + download: item["download"].parse()?, + }; + match item["key"].parse() { + Ok(key) => { + result.insert(key, usage); } + Err(e) => warn!( + "Failed to parse WgKey {} with {:?}", + item["key"].to_string(), + e + ), } + } + + Ok(result) +} - Ok(result) +/// Takes a wg interface name and provides upload and download since creation in bytes +/// in a hashmap indexed by peer WireGuard key +pub fn read_wg_counters(wg_name: &str) -> Result, Error> { + let output = run_command("wg", &["show", wg_name, "transfer"])?; + if !output.stderr.is_empty() { + return Err(KernelInterfaceError::RuntimeError(format!( + "received error from wg command: {}", + String::from_utf8(output.stderr)? + ))); } + read_wg_counters_internal(String::from_utf8(output.stdout)?) } #[test] fn test_read_wg_counters() { - use crate::KI; - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - - let mut counter = 0; - - KI.set_mock(Box::new(move |program, args| { - counter += 1; - match counter { - 1 => { - assert_eq!(program, "wg"); - assert_eq!(args, vec!["show", "wg_exit", "transfer"]); - Ok(Output { - stdout: b"jkIodvXKgij/rAEQXFEPJpls6ooxXJEC5XlWA1uUPUg=\t821519724\t13592616000" - .to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("Unexpected call {} {:?} {:?}", counter, program, args), - } - })); - let wg_counter = KI - .read_wg_counters("wg_exit") - .expect("Unable to parse wg counters"); + let wg_counter = read_wg_counters_internal( + "jkIodvXKgij/rAEQXFEPJpls6ooxXJEC5XlWA1uUPUg=\t821519724\t13592616000".to_string(), + ) + .unwrap(); let test_key = "jkIodvXKgij/rAEQXFEPJpls6ooxXJEC5XlWA1uUPUg=" .parse() .unwrap(); @@ -124,43 +103,19 @@ fn test_read_wg_counters() { #[test] fn test_read_wg_exit_counters() { - use crate::KI; - use std::os::unix::process::ExitStatusExt; - use std::process::ExitStatus; - use std::process::Output; - - let mut counter = 0; - - KI.set_mock(Box::new(move |program, args| { - counter += 1; - match counter { - 1 => { - assert_eq!(program, "wg"); - assert_eq!(args, vec!["show", "wg_exit", "transfer"]); - Ok(Output { - stdout: b"7fYutmH8iHIcuKjnzcgaDBNpVRw8ly0XMYFr7PtirDI=\t0\t0 -fFGhz1faSAqNjTqT5rpBWLD/FLrP6P/P59Z2Eo3jQDo=\t0\t3318456 -oum4Nd5nngTjG5Hw+XFoLk18pTY8DA7bl2OIwWkc4wQ=\t0\t0 -TFG8LAio7MDd+i5tExNX/vxR1pqpgNqo+RiUkmBekmU=\t0\t0 -Iz668/X70eo/PF9C94cKAZjrSjU961V8xndxTtk0FRM=\t7088439728\t15281630160 -Hgu1A3JFol3D6TFsmnjX/PVvupl0W2wMee0wRVFb0Aw=\t122193456\t1120342792 -7dxulyk1UcCJ0zUDcGbV+CQRY0uGUnbY5exi6I8EeyE=\t351530232\t5424629680 -b6HGtuWLAIHyINOgL7euzrMsMfzHIie5kYDScSCT7Ds=\t67526804\t88741160 -RW1XPRn4nJQaDqqeDFRilPjtYOUBitXIuHwoKZAtKWw=\t480230868\t3531367092 -AJYeYn4R+I+Jc7xiKQ15ImruYFXybTiR6BB6Ip3/njs=\t1777907382\t2034640104 -jL+LlqHAM63Qd9/ynAuqqn4wrYO7Hp8cYMlnf2OoSH8=\t679969972\t6539417596 -" - .to_vec(), - stderr: b"".to_vec(), - status: ExitStatus::from_raw(0), - }) - } - _ => panic!("Unexpected call {} {:?} {:?}", counter, program, args), - } - })); - let wg_counter = KI - .read_wg_counters("wg_exit") - .expect("Unable to parse wg counters"); + let stdout = "7fYutmH8iHIcuKjnzcgaDBNpVRw8ly0XMYFr7PtirDI=\t0\t0 + fFGhz1faSAqNjTqT5rpBWLD/FLrP6P/P59Z2Eo3jQDo=\t0\t3318456 + oum4Nd5nngTjG5Hw+XFoLk18pTY8DA7bl2OIwWkc4wQ=\t0\t0 + TFG8LAio7MDd+i5tExNX/vxR1pqpgNqo+RiUkmBekmU=\t0\t0 + Iz668/X70eo/PF9C94cKAZjrSjU961V8xndxTtk0FRM=\t7088439728\t15281630160 + Hgu1A3JFol3D6TFsmnjX/PVvupl0W2wMee0wRVFb0Aw=\t122193456\t1120342792 + 7dxulyk1UcCJ0zUDcGbV+CQRY0uGUnbY5exi6I8EeyE=\t351530232\t5424629680 + b6HGtuWLAIHyINOgL7euzrMsMfzHIie5kYDScSCT7Ds=\t67526804\t88741160 + RW1XPRn4nJQaDqqeDFRilPjtYOUBitXIuHwoKZAtKWw=\t480230868\t3531367092 + AJYeYn4R+I+Jc7xiKQ15ImruYFXybTiR6BB6Ip3/njs=\t1777907382\t2034640104 + jL+LlqHAM63Qd9/ynAuqqn4wrYO7Hp8cYMlnf2OoSH8=\t679969972\t6539417596 + "; + let wg_counter = read_wg_counters_internal(stdout.to_string()).unwrap(); let test_key = "Iz668/X70eo/PF9C94cKAZjrSjU961V8xndxTtk0FRM=" .parse() diff --git a/antenna_forwarding_client/Cargo.toml b/antenna_forwarding_client/Cargo.toml index 8322ef2ac..393c49a97 100644 --- a/antenna_forwarding_client/Cargo.toml +++ b/antenna_forwarding_client/Cargo.toml @@ -15,5 +15,4 @@ antenna_forwarding_protocol = {path = "../antenna_forwarding_protocol"} serde_json = "1.0" serde = "1.0" get_if_addrs = "0.5" -lazy_static = "1.5" rand = "0.8" diff --git a/antenna_forwarding_client/src/lib.rs b/antenna_forwarding_client/src/lib.rs index 7154426f8..3fc61636e 100644 --- a/antenna_forwarding_client/src/lib.rs +++ b/antenna_forwarding_client/src/lib.rs @@ -4,11 +4,9 @@ #[macro_use] extern crate log; -#[macro_use] -extern crate lazy_static; -use althea_kernel_interface::KernelInterface; -use althea_kernel_interface::LinuxCommandRunner; +use althea_kernel_interface::interface_tools::get_ip_from_iface; +use althea_kernel_interface::run_command; use althea_types::Identity; use althea_types::WgKey; use antenna_forwarding_protocol::process_streams; @@ -34,10 +32,6 @@ use std::time::Instant; mod error; pub use error::AntennaForwardingError; -lazy_static! { - pub static ref KI: Box = Box::new(LinuxCommandRunner {}); -} - const SLEEP_TIME: Duration = Duration::from_secs(20); /// The timeout time for pinging a local antenna, 100ms is very /// very generous here as they should all respond really within 5ms @@ -312,7 +306,7 @@ fn find_antenna( trace!("Trying interface {}, with test ip {}", iface, our_ip); // this acts as a wildcard deletion across all interfaces, which is frankly really // dangerous if our default route overlaps, or if you enter an exit route ip - let _ = KI.run_command("ip", &["route", "del", &format!("{target_ip}/32")]); + let _ = run_command("ip", &["route", "del", &format!("{target_ip}/32")]); for iface in interfaces { // cleans up all previous forwarding ip's in some way this is more dangerous than the previous // solution, which only cleaned up the target and destination ip's. But the more through cleanup @@ -322,7 +316,7 @@ fn find_antenna( // not cause issues with failing the find antenna command cleanup_interface(iface)?; } - let res = KI.run_command( + let res = run_command( "ip", &["addr", "add", &format!("{our_ip}/32"), "dev", iface], ); @@ -330,7 +324,7 @@ fn find_antenna( // you need to use src here to disambiguate the sending address // otherwise the first available ipv4 address on the interface will // be used - match KI.run_command( + match run_command( "ip", &[ "route", @@ -433,12 +427,12 @@ fn send_error_message(server_stream: &mut TcpStream, message: String) { } fn cleanup_interface(iface: &str) -> Result<(), AntennaForwardingError> { - let values = KI.get_ip_from_iface(iface)?; + let values = get_ip_from_iface(iface)?; for (ip, netmask) in values { // we only clean up very specific routes, this doesn't prevent us from causing problems // but it does help prevent us from doing things like removing the default route. if netmask == 32 { - let _ = KI.run_command("ip", &["addr", "del", &format!("{ip}/32"), "dev", iface]); + let _ = run_command("ip", &["addr", "del", &format!("{ip}/32"), "dev", iface]); } } Ok(()) diff --git a/clu/src/lib.rs b/clu/src/lib.rs index 46619371e..cd57cd255 100644 --- a/clu/src/lib.rs +++ b/clu/src/lib.rs @@ -3,7 +3,9 @@ extern crate log; #[macro_use] extern crate lazy_static; -use althea_kernel_interface::KI; +use althea_kernel_interface::create_wg_key::{create_wg_key, create_wg_keypair}; +use althea_kernel_interface::interface_tools::{del_interface, get_interfaces}; +use althea_kernel_interface::ip_route::restore_default_route; use clarity::PrivateKey; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; @@ -72,7 +74,7 @@ pub fn cleanup() -> Result<(), NewCluError> { let mut interfaces: Vec = Vec::new(); - for i in KI.get_interfaces()? { + for i in get_interfaces()? { if RE.is_match(&i) { interfaces.push(i); } @@ -90,7 +92,7 @@ pub fn cleanup() -> Result<(), NewCluError> { /// are not deleted correctly with parallel deletion on some systems pub fn del_multiple_interfaces_sync(interfaces: Vec) -> Result<(), NewCluError> { for interface in interfaces { - KI.del_interface(&interface)?; + del_interface(&interface)?; } Ok(()) } @@ -119,7 +121,7 @@ pub fn del_multiple_interfaces(interfaces: Vec) { let mut threads = Vec::new(); // for lifetime reasons for name in batch { - threads.push(thread::spawn(move || KI.del_interface(&name.clone()))); + threads.push(thread::spawn(move || del_interface(&name.clone()))); } for thread in threads { // we want to wait for these to complete, we don't care that much if they fail @@ -133,7 +135,7 @@ fn linux_init(settings: RitaClientSettings) -> Result Result>(); for s in out { - KI.run_command("kill", &[s.trim()]).unwrap(); + run_command("kill", &[s.trim()]).unwrap(); } } fn get_current_exit(ns: Namespace, namespaces: NamespaceInfo) -> Namespace { - let out = KI - .run_command( - "ip", - &["netns", "exec", &ns.get_name(), "wg", "show", "wg_exit"], - ) - .unwrap(); + let out = run_command( + "ip", + &["netns", "exec", &ns.get_name(), "wg", "show", "wg_exit"], + ) + .unwrap(); let out = from_utf8(&out.stdout).unwrap(); let out = out.split('\n').collect::>(); diff --git a/integration_tests/src/setup_utils/babel.rs b/integration_tests/src/setup_utils/babel.rs index 22f41fa66..31b6afb4a 100644 --- a/integration_tests/src/setup_utils/babel.rs +++ b/integration_tests/src/setup_utils/babel.rs @@ -1,4 +1,4 @@ -use althea_kernel_interface::KI; +use althea_kernel_interface::run_command; use log::info; use std::{fs::remove_file, path::Path, thread, time::Duration}; @@ -15,7 +15,7 @@ pub fn spawn_babel(ns: String, babelconf_path: String, babeld_path: String) { let babeld_pid = pid_path; let babeld_log = format!("/var/log/babeld-{ns}.log"); // 1 here is for log - let res = KI.run_command( + let res = run_command( "ip", &[ "netns", diff --git a/integration_tests/src/setup_utils/database.rs b/integration_tests/src/setup_utils/database.rs index 4c1efee25..57375dad1 100644 --- a/integration_tests/src/setup_utils/database.rs +++ b/integration_tests/src/setup_utils/database.rs @@ -1,4 +1,4 @@ -use althea_kernel_interface::KI; +use althea_kernel_interface::run_command; use diesel::{Connection, PgConnection}; use log::warn; use std::io::Write; @@ -51,18 +51,17 @@ pub fn start_postgres() { // only init and launch if postgres has not already been started if !Path::new(&postgres_pid_path).exists() { // initialize the db datadir - let res = KI - .run_command( - "sudo", - &[ - "-u", - POSTGRES_USER, - initdb_bin, - "-D", - POSTGRES_DATABASE_LOCATION, - ], - ) - .unwrap(); + let res = run_command( + "sudo", + &[ + "-u", + POSTGRES_USER, + initdb_bin, + "-D", + POSTGRES_DATABASE_LOCATION, + ], + ) + .unwrap(); if !res.status.success() { panic!("Failed to init postgres {:?}", res); } @@ -84,18 +83,17 @@ pub fn start_postgres() { // start postgres in it's own thread, we kill it every time we startup // so it's spawned in this context thread::spawn(move || { - let res = KI - .run_command( - "sudo", - &[ - "-u", - POSTGRES_USER, - postgres_bin, - "-D", - POSTGRES_DATABASE_LOCATION, - ], - ) - .unwrap(); + let res = run_command( + "sudo", + &[ + "-u", + POSTGRES_USER, + postgres_bin, + "-D", + POSTGRES_DATABASE_LOCATION, + ], + ) + .unwrap(); panic!("Postgres has crashed {:?}", res); }); @@ -110,9 +108,8 @@ pub fn start_postgres() { } // reset database contents for every run, this is in the loop becuase it too must wait until the db has started - KI.run_command("psql", &["-c", "drop database test;", "-U", POSTGRES_USER]) - .unwrap(); - KI.run_command( + run_command("psql", &["-c", "drop database test;", "-U", POSTGRES_USER]).unwrap(); + run_command( "psql", &["-c", "create database test;", "-U", POSTGRES_USER], ) diff --git a/integration_tests/src/setup_utils/namespaces.rs b/integration_tests/src/setup_utils/namespaces.rs index 018b9f142..39c4106cf 100644 --- a/integration_tests/src/setup_utils/namespaces.rs +++ b/integration_tests/src/setup_utils/namespaces.rs @@ -1,4 +1,4 @@ -use althea_kernel_interface::{KernelInterfaceError, KI}; +use althea_kernel_interface::{run_command, KernelInterfaceError}; use nix::{ fcntl::{open, OFlag}, sys::stat::Mode, @@ -119,12 +119,12 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { // arbitrary number for the IP assignment let mut counter = 6; // clear namespaces - KI.run_command("ip", &["-all", "netns", "delete", "||", "true"])?; + run_command("ip", &["-all", "netns", "delete", "||", "true"])?; // add namespaces for ns in spaces.names.iter() { - KI.run_command("ip", &["netns", "add", &ns.get_name()])?; + run_command("ip", &["netns", "add", &ns.get_name()])?; // ip netns exec nB ip link set dev lo up - KI.run_command( + run_command( "ip", &[ "netns", @@ -139,7 +139,7 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { ], )?; // nft create table inet fw4 - KI.run_command( + run_command( "ip", &[ "netns", @@ -155,7 +155,7 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { // nft add chain inet fw4 input { type filter hook input priority filter; policy accept; } // nft add chain inet fw4 output { type filter hook output priority filter; policy accept; } // nft add chain inet fw4 forward { type filter hook forward priority filter; policy accept; } - KI.run_command( + run_command( "ip", &[ "netns", @@ -179,7 +179,7 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { "}", ], )?; - KI.run_command( + run_command( "ip", &[ "netns", @@ -203,7 +203,7 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { "}", ], )?; - KI.run_command( + run_command( "ip", &[ "netns", @@ -253,10 +253,10 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { // delete the old native namespace veth for repeated runs // this veth should go away on it's own when the veths are cleared but that // can take long enough to cause a race condition - KI.run_command("ip", &["link", "del", &veth_native_to_exit])?; + run_command("ip", &["link", "del", &veth_native_to_exit])?; // create link between exit and native namespace - KI.run_command( + run_command( "ip", &[ "link", @@ -270,7 +270,7 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { ], )?; // set the exit side of the link to the correct namespace the other side is native - KI.run_command( + run_command( "ip", &[ "link", @@ -280,8 +280,8 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { &name.get_name(), ], )?; - KI.run_command("ip", &["link", "set", "up", &veth_native_to_exit])?; - KI.run_command( + run_command("ip", &["link", "set", "up", &veth_native_to_exit])?; + run_command( "ip", &[ "netns", @@ -295,7 +295,7 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { ], )?; // add ip address for the exit - KI.run_command( + run_command( "ip", &[ "netns", @@ -310,7 +310,7 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { ], )?; // set default route - KI.run_command( + run_command( "ip", &[ "netns", @@ -331,14 +331,14 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { // now we need to loop the interfaces we created and build a single bridge interface // this will make our job of forwarding their traffic to the internet much easier - KI.run_command("ip", &["link", "add", BRIDGE_NAME, "type", "bridge"])?; + run_command("ip", &["link", "add", BRIDGE_NAME, "type", "bridge"])?; for iface in links_to_native_namespace { - KI.run_command("ip", &["link", "set", &iface, "master", BRIDGE_NAME])?; + run_command("ip", &["link", "set", &iface, "master", BRIDGE_NAME])?; } - KI.run_command("ip", &["link", "set", "up", BRIDGE_NAME])?; - KI.run_command("ip", &["addr", "add", BRIDGE_IP_PREFIX, "dev", BRIDGE_NAME])?; + run_command("ip", &["link", "set", "up", BRIDGE_NAME])?; + run_command("ip", &["addr", "add", BRIDGE_IP_PREFIX, "dev", BRIDGE_NAME])?; // finally we setup a nat between this bridge and the native namespace, allowing traffic to exit - KI.run_command( + run_command( "iptables", &[ "-A", @@ -355,7 +355,7 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { "ACCEPT", ], )?; - KI.run_command( + run_command( "iptables", &[ "-A", @@ -368,7 +368,7 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { "ACCEPT", ], )?; - KI.run_command( + run_command( "iptables", &[ "-t", @@ -395,24 +395,24 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { counter += 1; // create veth to link them - KI.run_command( + run_command( "ip", &[ "link", "add", &veth_ab, "type", "veth", "peer", "name", &veth_ba, ], )?; // assign each side of the veth to one of the nodes namespaces - KI.run_command("ip", &["link", "set", &veth_ab, "netns", &a_name])?; - KI.run_command("ip", &["link", "set", &veth_ba, "netns", &b_name])?; + run_command("ip", &["link", "set", &veth_ab, "netns", &a_name])?; + run_command("ip", &["link", "set", &veth_ba, "netns", &b_name])?; // add ip addresses on each side - KI.run_command( + run_command( "ip", &[ "netns", "exec", &a_name, "ip", "addr", "add", &ip_ab, "dev", &veth_ab, ], )?; - KI.run_command( + run_command( "ip", &[ "netns", "exec", &b_name, "ip", "addr", "add", &ip_ba, "dev", &veth_ba, @@ -420,14 +420,14 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { )?; // bring the interfaces up - KI.run_command( + run_command( "ip", &[ "netns", "exec", &a_name, "ip", "link", "set", "dev", &veth_ab, "up", ], )?; - KI.run_command( + run_command( "ip", &[ "netns", "exec", &b_name, "ip", "link", "set", "dev", &veth_ba, "up", @@ -436,13 +436,13 @@ pub fn setup_ns(spaces: NamespaceInfo) -> Result<(), KernelInterfaceError> { // ip netns exec nC ip route add 192.168.0.0/24 dev veth-nC-nA // add routes to each other's subnets - KI.run_command( + run_command( "ip", &[ "netns", "exec", &a_name, "ip", "route", "add", &subnet_b, "dev", &veth_ab, ], )?; - KI.run_command( + run_command( "ip", &[ "netns", "exec", &b_name, "ip", "route", "add", &subnet_a, "dev", &veth_ba, diff --git a/integration_tests/src/utils.rs b/integration_tests/src/utils.rs index 9f6310053..95c30959c 100644 --- a/integration_tests/src/utils.rs +++ b/integration_tests/src/utils.rs @@ -8,7 +8,7 @@ use crate::{ }; use actix_rt::time::sleep; use actix_rt::System; -use althea_kernel_interface::KI; +use althea_kernel_interface::run_command; use althea_proto::cosmos_sdk_proto::cosmos::gov::v1beta1::VoteOption; use althea_proto::{ canto::erc20::v1::RegisterCoinProposal, @@ -239,12 +239,11 @@ fn test_reach(from: Namespace, to: Namespace) -> bool { from.get_name(), to.get_name() ); - let output = KI - .run_command( - "ip", - &["netns", "exec", &from.get_name(), "ping6", &ip, "-c", "1"], - ) - .expect(&errormsg); + let output = run_command( + "ip", + &["netns", "exec", &from.get_name(), "ping6", &ip, "-c", "1"], + ) + .expect(&errormsg); let output = from_utf8(&output.stdout).expect("could not get output for ping6!"); trace!("ping output: {output:?} end"); output.contains("1 packets transmitted, 1 received, 0% packet loss") @@ -315,20 +314,19 @@ pub fn test_all_internet_connectivity(namespaces: NamespaceInfo) { for ns in namespaces.names { let start = Instant::now(); loop { - let out = KI - .run_command( - "ip", - &[ - "netns", - "exec", - &ns.get_name(), - "ping", - &NODE_IP.to_string(), - "-c", - "1", - ], - ) - .unwrap(); + let out = run_command( + "ip", + &[ + "netns", + "exec", + &ns.get_name(), + "ping", + &NODE_IP.to_string(), + "-c", + "1", + ], + ) + .unwrap(); if String::from_utf8(out.stdout) .unwrap() .contains("1 received") @@ -554,14 +552,13 @@ pub fn generate_traffic(from: Namespace, to: Option, data: String) { thread::spawn(move || { info!("In new thread about to start iperf server"); let output = if let Some(ns) = to { - KI.run_command( + run_command( "ip", &["netns", "exec", &ns.get_name(), "iperf3", "-s", "-1"], ) .expect("Could not setup iperf server") } else { - KI.run_command("iperf3", &["-s", "-1"]) - .expect("Could not setup iperf server") + run_command("iperf3", &["-s", "-1"]).expect("Could not setup iperf server") }; let stderr = from_utf8(&output.stderr).expect("Why is this failing"); @@ -580,21 +577,20 @@ pub fn generate_traffic(from: Namespace, to: Option, data: String) { info!("Going to setup client"); let ticker = Instant::now(); loop { - let output = KI - .run_command( - "ip", - &[ - "netns", - "exec", - &from.get_name(), - "iperf3", - "-c", - &ip, - "-n", - &data, - ], - ) - .expect("Could not setup iperf client"); + let output = run_command( + "ip", + &[ + "netns", + "exec", + &from.get_name(), + "iperf3", + "-c", + &ip, + "-n", + &data, + ], + ) + .expect("Could not setup iperf client"); let stderr = from_utf8(&output.stderr).expect("Why is this failing"); let std_output = from_utf8(&output.stdout).expect("could not get output for client setup!"); diff --git a/rita_bin/Cargo.toml b/rita_bin/Cargo.toml index 9895030cb..fbf8f5e17 100644 --- a/rita_bin/Cargo.toml +++ b/rita_bin/Cargo.toml @@ -40,7 +40,6 @@ arrayvec = { version = "0.7", features = ["serde"] } hex-literal = "0.4" openssl-probe = "0.1" env_logger = "0.11" -lazy_static = "1.5" phonenumber = "0.3.6" r2d2 = "0.8" ctrlc = { version = "3.4.5", features = ["termination"] } diff --git a/rita_bin/src/client.rs b/rita_bin/src/client.rs index 6069e152f..7a44f820e 100644 --- a/rita_bin/src/client.rs +++ b/rita_bin/src/client.rs @@ -11,19 +11,18 @@ #![allow(clippy::pedantic)] #![forbid(unsafe_code)] +use althea_kernel_interface::babel::restart_babel; +use althea_kernel_interface::check_cron::check_cron; +use althea_kernel_interface::is_openwrt::is_openwrt; #[cfg(feature = "jemalloc")] use jemallocator::Jemalloc; #[cfg(feature = "jemalloc")] #[global_allocator] static GLOBAL: Jemalloc = Jemalloc; -#[macro_use] -extern crate lazy_static; #[macro_use] extern crate log; -use althea_kernel_interface::KernelInterface; -use althea_kernel_interface::LinuxCommandRunner; use docopt::Docopt; use rita_client::dashboard::start_client_dashboard; use rita_client::get_client_usage; @@ -45,10 +44,6 @@ use settings::client::RitaClientSettings; use settings::save_settings_on_shutdown; use settings::FileWrite; -lazy_static! { - pub static ref KI: Box = Box::new(LinuxCommandRunner {}); -} - fn main() { //Setup a SIGTERM hadler ctrlc::set_handler(move || { @@ -130,7 +125,7 @@ fn main() { // the old tunnels is now in an incorrect state. We must either restart babel or empty it's interfaces list so that the newly // created wireguard tunnels can be re-added by this instance of Rita. Due to errors in babel (see git history there) // restarting is the way to go as removing dead interfaces often does not work - KI.restart_babel(); + restart_babel(); apply_babeld_settings_defaults( settings.network.babel_port, settings.network.babeld_settings, @@ -167,7 +162,7 @@ fn main() { } // If we are an an OpenWRT device try and rescue it from update issues - if KI.is_openwrt() && KI.check_cron().is_err() { + if is_openwrt() && check_cron().is_err() { error!("Failed to setup cron!"); } diff --git a/rita_client/src/dashboard/bandwidth_limit.rs b/rita_client/src/dashboard/bandwidth_limit.rs index a48639bd6..a5c7a1017 100644 --- a/rita_client/src/dashboard/bandwidth_limit.rs +++ b/rita_client/src/dashboard/bandwidth_limit.rs @@ -4,7 +4,8 @@ use actix_web_async::http::StatusCode; use actix_web_async::HttpResponse; use actix_web_async::{web::Path, HttpRequest}; -use rita_common::{RitaCommonError, KI}; +use althea_kernel_interface::traffic_control::set_codel_shaping; +use rita_common::RitaCommonError; pub async fn get_bandwidth_limit(_req: HttpRequest) -> HttpResponse { let val = settings::get_rita_client().network.user_bandwidth_limit; @@ -23,7 +24,7 @@ pub async fn set_bandwidth_limit(path: Path) -> HttpResponse { } else { return HttpResponse::BadRequest().finish(); } - let _res = KI.set_codel_shaping("br-lan", network.user_bandwidth_limit); + let _res = set_codel_shaping("br-lan", network.user_bandwidth_limit); rita_client.network = network; settings::set_rita_client(rita_client); diff --git a/rita_client/src/dashboard/devices_on_lan.rs b/rita_client/src/dashboard/devices_on_lan.rs index 61a54bbc9..673802c34 100644 --- a/rita_client/src/dashboard/devices_on_lan.rs +++ b/rita_client/src/dashboard/devices_on_lan.rs @@ -1,9 +1,13 @@ use crate::dashboard::extend_hardware_info; use actix_web_async::{HttpRequest, HttpResponse}; -use althea_kernel_interface::hardware_info::get_hardware_info; +use althea_kernel_interface::{ + hardware_info::get_hardware_info, + interface_tools::{get_ip_from_iface, get_ipv6_from_iface}, + ip_neigh::grab_ip_neigh, + run_command, +}; use althea_types::HardwareInfo; use mac_address::MacAddress; -use rita_common::KI; use serde::Serializer; use std::{ collections::{HashMap, HashSet}, @@ -27,8 +31,8 @@ fn consolidate_wlan_arp_table( /// Used to prune all devices not part of the lan by using the netmask to delete invalid ips fn prune_non_lan_entries(mut arp_table: Vec<(IpAddr, MacAddress)>) -> Vec<(IpAddr, MacAddress)> { - let ip4 = KI.get_ip_from_iface("br-lan"); - let ip6 = KI.get_ipv6_from_iface("br-lan"); + let ip4 = get_ip_from_iface("br-lan"); + let ip6 = get_ipv6_from_iface("br-lan"); // The long set of calls essentially grabs each ipaddress out of the arp table and checks if there is a singular // address match within each of the interfaces for ipv4 or ipv6. Otherwise, it prunes the ipaddress out of the arp @@ -163,7 +167,7 @@ pub fn generate_lan_device( /// if it isn't it simply returns its mac addresses to use fn resolve_name(ip_addresses: HashSet, mac_addr: MacAddress) -> String { info!("Sending cat command to kernel"); - let res = KI.run_command("cat", &["/tmp/dhcp.leases"]); + let res = run_command("cat", &["/tmp/dhcp.leases"]); match res { Ok(output) => { if !output.stdout.is_empty() { @@ -206,7 +210,7 @@ fn resolve_name(ip_addresses: HashSet, mac_addr: MacAddress) -> String { /// This is an endpoint to grab all the lan devices mapping to ip address and /// returns the json request populated with the related hardware information pub async fn get_devices_lan_endpoint(_req: HttpRequest) -> HttpResponse { - let command_response = KI.grab_ip_neigh(); + let command_response = grab_ip_neigh(); match command_response { Ok(output) => { let arp_table = output; diff --git a/rita_client/src/dashboard/exits.rs b/rita_client/src/dashboard/exits.rs index 2a720a5e7..563b28a3c 100644 --- a/rita_client/src/dashboard/exits.rs +++ b/rita_client/src/dashboard/exits.rs @@ -5,11 +5,11 @@ use crate::exit_manager::requests::exit_setup_request; use crate::RitaClientError; use actix_web_async::http::StatusCode; use actix_web_async::{web::Path, HttpRequest, HttpResponse}; +use althea_kernel_interface::ping_check::ping_check; use althea_types::{ExitIdentity, ExitState}; use babel_monitor::open_babel_stream; use babel_monitor::parse_routes; use babel_monitor::parsing::do_we_have_route; -use rita_common::KI; use std::collections::HashMap; use std::time::Duration; @@ -44,13 +44,12 @@ fn is_tunnel_working( ) -> bool { match (current_exit.clone(), is_selected(exit, current_exit)) { (Some(_exit), true) => match exit_status.general_details() { - Some(details) => KI - .ping_check( - &details.server_internal_ip, - EXIT_PING_TIMEOUT, - Some("wg_exit"), - ) - .unwrap_or(false), + Some(details) => ping_check( + &details.server_internal_ip, + EXIT_PING_TIMEOUT, + Some("wg_exit"), + ) + .unwrap_or(false), None => false, }, (_, _) => false, @@ -79,7 +78,7 @@ pub fn dashboard_get_exit_info() -> Result, RitaClientError> { // failed pings block for one second, so we should be sure it's at least reasonable // to expect the pings to work before issuing them. let reachable = if have_route { - KI.ping_check(&route_ip, EXIT_PING_TIMEOUT, None)? + ping_check(&route_ip, EXIT_PING_TIMEOUT, None)? } else { false }; diff --git a/rita_client/src/dashboard/router.rs b/rita_client/src/dashboard/router.rs index c26672bb4..d9c1c544f 100644 --- a/rita_client/src/dashboard/router.rs +++ b/rita_client/src/dashboard/router.rs @@ -1,7 +1,7 @@ use crate::operator_update::updater::update_system; use actix_web_async::{http::StatusCode, HttpRequest, HttpResponse}; +use althea_kernel_interface::{is_openwrt::is_openwrt, run_command}; use althea_types::UpdateType; -use rita_common::KI; use std::sync::{Arc, RwLock}; lazy_static! { @@ -10,8 +10,8 @@ lazy_static! { } pub async fn reboot_router(_req: HttpRequest) -> HttpResponse { - if KI.is_openwrt() { - if let Err(e) = KI.run_command("reboot", &[]) { + if is_openwrt() { + if let Err(e) = run_command("reboot", &[]) { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR) .json(format!("Cannot run reboot: {e}")); } @@ -25,7 +25,7 @@ pub async fn reboot_router(_req: HttpRequest) -> HttpResponse { /// the lazy static variable and use this to perform a sysupgrade. If device is not openwrt or no image /// link is available, do nothing pub async fn update_router(_req: HttpRequest) -> HttpResponse { - if KI.is_openwrt() { + if is_openwrt() { let reader = &*UPDATE_INSTRUCTION.read().unwrap(); if reader.is_none() { return HttpResponse::Ok().json("No update instructions set, doing nothing"); diff --git a/rita_client/src/exit_manager/exit_loop.rs b/rita_client/src/exit_manager/exit_loop.rs index da60bcd2c..7eb8cc0cf 100644 --- a/rita_client/src/exit_manager/exit_loop.rs +++ b/rita_client/src/exit_manager/exit_loop.rs @@ -12,10 +12,12 @@ use crate::exit_manager::utils::{ use crate::heartbeat::get_exit_registration_state; use crate::traffic_watcher::{query_exit_debts, QueryExitDebts}; use actix_async::System as AsyncSystem; +use althea_kernel_interface::ip_addr::setup_ipv6_slaac as setup_ipv6_slaac_ki; +use althea_kernel_interface::ip_route::get_default_route; +use althea_kernel_interface::run_command; use althea_types::{ExitDetails, ExitListV2}; use althea_types::{ExitIdentity, ExitState}; use rita_common::blockchain_oracle::low_balance; -use rita_common::KI; use std::net::IpAddr; use std::thread; use std::time::{Duration, Instant}; @@ -67,7 +69,7 @@ pub fn start_exit_manager_loop() { ); if Instant::now() - last_restart < Duration::from_secs(60) { error!("Restarting too quickly, rebooting instead!"); - let _res = KI.run_command("reboot", &[]); + let _res = run_command("reboot", &[]); } last_restart = Instant::now(); } @@ -171,7 +173,7 @@ fn setup_exit_tunnel( ); let signed_up_for_exit = registration_state.our_details(); - let default_route = match KI.get_default_route() { + let default_route = match get_default_route() { Ok(a) => a, Err(e) => { error!("Failed to get default route: {:?}", e); @@ -322,6 +324,6 @@ fn setup_ipv6_slaac() { trace!("Setting up ipv6 for slaac"); if let Some(ipv6_sub) = get_client_pub_ipv6() { trace!("setting up slaac with {:?}", ipv6_sub); - KI.setup_ipv6_slaac(ipv6_sub) + setup_ipv6_slaac_ki(ipv6_sub) } } diff --git a/rita_client/src/exit_manager/time_sync.rs b/rita_client/src/exit_manager/time_sync.rs index 7d7f3e679..c932fb306 100644 --- a/rita_client/src/exit_manager/time_sync.rs +++ b/rita_client/src/exit_manager/time_sync.rs @@ -1,4 +1,4 @@ -use althea_kernel_interface::KI; +use althea_kernel_interface::{setup_wg_if::get_last_handshake_time, time::set_local_time}; use althea_types::{ExitIdentity, ExitSystemTime}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -42,7 +42,7 @@ pub async fn get_exit_time(exit: ExitIdentity) -> Option { // try to get the latest handshake for the wg_exit tunnel pub fn get_latest_exit_handshake() -> Option { - match KI.get_last_handshake_time("wg_exit") { + match get_last_handshake_time("wg_exit") { Ok(results) => results.first().map(|(_, time)| { info!("last exit handshake {:?}", time); *time @@ -90,7 +90,7 @@ pub async fn maybe_set_local_to_exit_time(exit: ExitIdentity) { if let Ok(diff) = exit_time.duration_since(now) { if diff > MAX_DIFF_LOCAL_EXIT_TIME { // if we're here, then it's time to set our time - if KI.set_local_time(exit_time).is_ok() { + if set_local_time(exit_time).is_ok() { info!("Local time was reset to the exit's time: {:?}", exit_time); } } diff --git a/rita_client/src/exit_manager/utils.rs b/rita_client/src/exit_manager/utils.rs index e32322ee8..3f27535bb 100644 --- a/rita_client/src/exit_manager/utils.rs +++ b/rita_client/src/exit_manager/utils.rs @@ -4,6 +4,12 @@ use crate::heartbeat::get_exit_registration_state; use crate::rita_loop::CLIENT_LOOP_TIMEOUT; use crate::RitaClientError; use actix_web_async::Result; +use althea_kernel_interface::exit_client_tunnel::{ + block_client_nat, create_client_nat_rules, restore_client_nat, set_client_exit_tunnel_config, + set_ipv6_route_to_tunnel, set_route_to_tunnel, +}; +use althea_kernel_interface::ip_route::update_settings_route; +use althea_kernel_interface::setup_wg_if::create_blank_wg_interface; use althea_kernel_interface::{ exit_client_tunnel::ClientExitTunnelConfig, DefaultRoute, KernelInterfaceError, }; @@ -16,7 +22,6 @@ use babel_monitor::open_babel_stream; use babel_monitor::parse_routes; use babel_monitor::structs::Route; use ipnetwork::IpNetwork; -use rita_common::KI; use settings::set_rita_client; use std::net::SocketAddr; @@ -30,10 +35,10 @@ pub fn linux_setup_exit_tunnel( let local_mesh_ip = network.mesh_ip; // TODO this should be refactored to return a value - KI.update_settings_route(&mut network.last_default_route)?; + update_settings_route(&mut network.last_default_route)?; info!("Updated settings route"); - if let Err(KernelInterfaceError::RuntimeError(v)) = KI.create_blank_wg_interface("wg_exit") { + if let Err(KernelInterfaceError::RuntimeError(v)) = create_blank_wg_interface("wg_exit") { return Err(RitaClientError::MiscStringError(v)); } @@ -53,23 +58,23 @@ pub fn linux_setup_exit_tunnel( rita_client.network = network; settings::set_rita_client(rita_client); - KI.set_client_exit_tunnel_config(args, local_mesh_ip)?; - KI.set_route_to_tunnel(&general_details.server_internal_ip)?; - KI.set_ipv6_route_to_tunnel()?; + set_client_exit_tunnel_config(args, local_mesh_ip)?; + set_route_to_tunnel(&general_details.server_internal_ip)?; + set_ipv6_route_to_tunnel()?; - KI.create_client_nat_rules()?; + create_client_nat_rules()?; Ok(()) } pub fn restore_nat() { - if let Err(e) = KI.restore_client_nat() { + if let Err(e) = restore_client_nat() { error!("Failed to restore client nat! {:?}", e); } } pub fn remove_nat() { - if let Err(e) = KI.block_client_nat() { + if let Err(e) = block_client_nat() { error!("Failed to block client nat! {:?}", e); } } diff --git a/rita_client/src/heartbeat/mod.rs b/rita_client/src/heartbeat/mod.rs index 4abac405d..1d58b6ced 100644 --- a/rita_client/src/heartbeat/mod.rs +++ b/rita_client/src/heartbeat/mod.rs @@ -13,7 +13,7 @@ //! This packet is encrypted using the usual LibSodium box construction and sent to the heartbeat server in the following format //! WgKey, Nonce, Ciphertext for the HeartBeatMessage. This consumes 32 bytes, 24 bytes, and to the end of the message -use althea_kernel_interface::KI; +use althea_kernel_interface::run_command; use althea_types::ExitDetails; use althea_types::ExitState; use althea_types::HeartbeatMessage; @@ -110,7 +110,7 @@ pub fn send_heartbeat_loop() { error!("Heartbeat loop thread panicked! Respawning {:?}", e); if Instant::now() - last_restart < Duration::from_secs(60) { error!("Restarting too quickly, rebooting instead!"); - let _res = KI.run_command("reboot", &[]); + let _res = run_command("reboot", &[]); } last_restart = Instant::now(); } diff --git a/rita_client/src/operator_update/mod.rs b/rita_client/src/operator_update/mod.rs index fcfaeb99d..d57e5657b 100644 --- a/rita_client/src/operator_update/mod.rs +++ b/rita_client/src/operator_update/mod.rs @@ -10,6 +10,7 @@ use crate::heartbeat::HEARTBEAT_SERVER_KEY; use crate::rita_loop::is_gateway_client; use crate::RitaClientError; use althea_kernel_interface::hardware_info::get_hardware_info; +use althea_kernel_interface::run_command; use althea_types::websockets::{ OperatorAction, OperatorWebsocketMessage, OperatorWebsocketResponse, PaymentAndNetworkSettings, }; @@ -29,7 +30,6 @@ use rita_common::tunnel_manager::shaping::flag_reset_shaper; use rita_common::usage_tracker::structs::UsageType::{Client, Relay}; use rita_common::usage_tracker::{get_current_hour, get_current_throughput, get_usage_data_map}; use rita_common::DROPBEAR_AUTHORIZED_KEYS; -use rita_common::KI; use serde_json::Map; use serde_json::Value; use settings::client::RitaClientSettings; @@ -268,11 +268,11 @@ fn perform_operator_action(action: OperatorAction) { match action { OperatorAction::ResetShaper => flag_reset_shaper(), OperatorAction::Reboot => { - let _res = KI.run_command("reboot", &[]); + let _res = run_command("reboot", &[]); } OperatorAction::SoftReboot => { let args = vec!["restart"]; - if let Err(e) = KI.run_command("/etc/init.d/rita", &args) { + if let Err(e) = run_command("/etc/init.d/rita", &args) { error!("Unable to restart rita after opkg update: {}", e) } } @@ -365,7 +365,7 @@ fn update_authorized_keys( let mut existing = HashSet::new(); let auth_keys_file = File::open(keys_file); let mut write_data: Vec = vec![]; - let temp_key_file = String::from("temp_authorized_keys"); + let temp_key_file = format!("temp_authorized_keys_{}", keys_file); info!( "Authorized keys updates add {} remove {} pubkeys", diff --git a/rita_client/src/operator_update/tests.rs b/rita_client/src/operator_update/tests.rs index 617f19d35..97e6e87dd 100644 --- a/rita_client/src/operator_update/tests.rs +++ b/rita_client/src/operator_update/tests.rs @@ -59,7 +59,7 @@ mod test { let key_file: &str = "authorized_keys"; let operator_key = touch_temp_file(key_file); - let _update = update_authorized_keys(added_keys.clone(), removed_keys, key_file); + update_authorized_keys(added_keys.clone(), removed_keys, key_file).unwrap(); let result = parse_keys(key_file); assert_eq!(result.len(), 2); assert!(result.contains(&added_keys[0])); @@ -77,7 +77,7 @@ mod test { let operator_key = touch_temp_file(key_file); - let _update = update_authorized_keys(added_keys.clone(), removed_keys, key_file); + update_authorized_keys(added_keys.clone(), removed_keys, key_file).unwrap(); let result = parse_keys(key_file); assert!(result.contains(&added_keys[0])); assert!(result.contains(&added_keys[1])); @@ -97,7 +97,7 @@ mod test { let operator_key = touch_temp_file(key_file); - let _update = update_authorized_keys(added_keys, removed_keys, key_file); + update_authorized_keys(added_keys, removed_keys, key_file).unwrap(); let result = parse_keys(key_file); assert!(result.contains(&operator_key.to_string())); @@ -112,7 +112,7 @@ mod test { let operator_key = touch_temp_file(key_file); let removed_keys = vec![String::from(operator_key)]; - let _update = update_authorized_keys(added_keys, removed_keys.clone(), key_file); + update_authorized_keys(added_keys, removed_keys.clone(), key_file).unwrap(); let result = parse_keys(key_file); for item in result { @@ -128,7 +128,7 @@ mod test { ]; let removed_keys: Vec = vec![]; let key_file: &str = "create_keys_file"; - let _update = update_authorized_keys(added_keys, removed_keys, key_file); + update_authorized_keys(added_keys, removed_keys, key_file).unwrap(); assert!(Path::new(key_file).exists()); } #[test] diff --git a/rita_client/src/operator_update/updater.rs b/rita_client/src/operator_update/updater.rs index 536381fe9..ca84f29fb 100644 --- a/rita_client/src/operator_update/updater.rs +++ b/rita_client/src/operator_update/updater.rs @@ -2,21 +2,25 @@ //! versus updating operator tools on the status of this router which is the context of 'update' in the rest //! of this module -use althea_kernel_interface::KernelInterfaceError; +use althea_kernel_interface::{ + is_openwrt::is_openwrt, + run_command, + upgrade::{perform_opkg, perform_sysupgrade}, + KernelInterfaceError, +}; use althea_types::UpdateType; -use rita_common::KI; /// Updates the system, including Rita and other packages by performing either a sysupgrade or opkg install pub fn update_system(instruction: UpdateType) -> Result<(), KernelInterfaceError> { - if KI.is_openwrt() { + if is_openwrt() { match instruction { - UpdateType::Sysupgrade(command) => match KI.perform_sysupgrade(command) { + UpdateType::Sysupgrade(command) => match perform_sysupgrade(command) { Ok(_) => Ok(()), Err(e) => Err(e), }, UpdateType::Opkg(commands) => { for cmd in commands { - let res = KI.perform_opkg(cmd); + let res = perform_opkg(cmd); match res { Ok(o) => match o.status.code() { Some(0) => info!("opkg completed successfully! {:?}", o), @@ -35,10 +39,10 @@ pub fn update_system(instruction: UpdateType) -> Result<(), KernelInterfaceError // Restart rita after opkg let args = vec!["restart"]; - if let Err(e) = KI.run_command("/etc/init.d/rita", &args) { + if let Err(e) = run_command("/etc/init.d/rita", &args) { error!("Unable to restart rita after opkg update: {}", e) } - if let Err(e) = KI.run_command("/etc/init.d/rita_tower", &args) { + if let Err(e) = run_command("/etc/init.d/rita_tower", &args) { error!("Unable to restart rita tower after opkg update: {}", e) } diff --git a/rita_client/src/rita_loop/mod.rs b/rita_client/src/rita_loop/mod.rs index 40eb1d0e7..b9023689e 100644 --- a/rita_client/src/rita_loop/mod.rs +++ b/rita_client/src/rita_loop/mod.rs @@ -10,8 +10,18 @@ use crate::heartbeat::send_heartbeat_loop; use crate::heartbeat::HEARTBEAT_SERVER_KEY; use crate::operator_fee_manager::tick_operator_payments; use actix_async::System as AsyncSystem; +use althea_kernel_interface::dns::get_resolv_servers; +use althea_kernel_interface::ip_addr::is_iface_up; +use althea_kernel_interface::ip_route::manual_peers_route; +use althea_kernel_interface::is_openwrt::is_openwrt; +use althea_kernel_interface::manipulate_uci::get_uci_var; +use althea_kernel_interface::manipulate_uci::openwrt_reset_dnsmasq; +use althea_kernel_interface::manipulate_uci::set_uci_list; +use althea_kernel_interface::manipulate_uci::set_uci_var; +use althea_kernel_interface::manipulate_uci::uci_commit; +use althea_kernel_interface::netns::check_integration_test_netns; +use althea_kernel_interface::run_command; use althea_kernel_interface::KernelInterfaceError; -use althea_kernel_interface::KI; use althea_types::ExitState; use antenna_forwarding_client::start_antenna_forwarding_proxy; use rita_common::dashboard::interfaces::get_interfaces; @@ -48,7 +58,7 @@ lazy_static! { } pub fn is_gateway_client() -> bool { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); IS_GATEWAY_CLIENT .read() .unwrap() @@ -59,7 +69,7 @@ pub fn is_gateway_client() -> bool { } pub fn set_gateway_client(input: bool) { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); let gw_lock = &mut *IS_GATEWAY_CLIENT.write().unwrap(); // Clippy notation @@ -148,7 +158,7 @@ pub fn start_rita_client_loop() { error!("Rita client loop thread paniced! Respawning {:?}", e); if Instant::now() - last_restart < Duration::from_secs(60) { error!("Restarting too quickly, rebooting instead!"); - let _res = KI.run_command("reboot", &[]); + let _res = run_command("reboot", &[]); } last_restart = Instant::now(); } @@ -229,7 +239,7 @@ fn manage_gateway() { // the is_up detection is mostly useless because these ports reside on switches which mark // all ports as up all the time. if let Some(external_nic) = settings::get_rita_common().network.external_nic { - if KI.is_iface_up(&external_nic).unwrap_or(false) { + if is_iface_up(&external_nic).unwrap_or(false) { if let Ok(interfaces) = get_interfaces() { info!("We are a Gateway"); // this flag is used to handle billing around the corner case @@ -243,16 +253,13 @@ fn manage_gateway() { if let Some(mode) = interfaces.get(&external_nic) { if matches!(mode, InterfaceMode::Wan | InterfaceMode::StaticWan { .. }) { let mut common = settings::get_rita_common(); - match KI.get_resolv_servers() { + match get_resolv_servers() { Ok(s) => { for ip in s.iter() { trace!("Resolv route {:?}", ip); - KI.manual_peers_route( - ip, - &mut common.network.last_default_route, - ) - .unwrap(); + manual_peers_route(ip, &mut common.network.last_default_route) + .unwrap(); } settings::set_rita_common(common); } @@ -349,7 +356,7 @@ pub fn update_dns_conf() { }; // if we are on openwrt we can automatically configure the dhcp server, otherwise the user is on their own to santiy check their config - if KI.is_openwrt() { + if is_openwrt() { // the goal of this code is to make sure that the router is the only dns server offered during the dhcp negotiation process // not every device will take this dns server and use it, but many do, and some use it exclusively so it has to be correct @@ -360,8 +367,8 @@ pub fn update_dns_conf() { // if it does not start with the exit internal nameserver add it. An empty value is acceptable // since dnsmasq simply uses resolv.conf servers which we update above in that case. match ( - parse_list_to_ip(KI.get_uci_var(DHCP_DNS_LIST_KEY)), - maybe_parse_ip(KI.get_uci_var(LAN_IP_KEY)), + parse_list_to_ip(get_uci_var(DHCP_DNS_LIST_KEY)), + maybe_parse_ip(get_uci_var(LAN_IP_KEY)), ) { (Ok(dns_server_list), Ok(router_internal_ip)) => { // an empty list uses the system resolver, this is acceptable since we just set the system resolver to @@ -389,18 +396,18 @@ fn overwrite_dns_server_and_restart_dhcp(key: &str, ips: Vec) { let slice_of_strs: Vec<&str> = ips.iter().map(|s| s.as_str()).collect(); let reference_to_slice: &[&str] = &slice_of_strs; - let res = KI.set_uci_list(key, reference_to_slice); + let res = set_uci_list(key, reference_to_slice); if let Err(e) = res { error!("Failed to set dhcp server list via uci {:?}", e); return; } - let res = KI.uci_commit(key); + let res = uci_commit(key); if let Err(e) = res { error!("Failed to set dhcp server list via uci {:?}", e); return; } - let res = KI.openwrt_reset_dnsmasq(); + let res = openwrt_reset_dnsmasq(); if let Err(e) = res { error!("Failed to restart dhcp config with {:?}", e); } @@ -411,20 +418,20 @@ fn ensure_dhcp_resolvfile() { const DHCP_RESOLV_FILE_KEY: &str = "dhcp.@dnsmasq[0].resolvfile"; const DHCP_RESOLV_FILE_VALUE: &str = "/etc/resolv.conf"; - match KI.get_uci_var(DHCP_RESOLV_FILE_KEY) { + match get_uci_var(DHCP_RESOLV_FILE_KEY) { Ok(resolv_file) => { if resolv_file != DHCP_RESOLV_FILE_VALUE { - let res = KI.set_uci_var(DHCP_RESOLV_FILE_KEY, DHCP_RESOLV_FILE_VALUE); + let res = set_uci_var(DHCP_RESOLV_FILE_KEY, DHCP_RESOLV_FILE_VALUE); if let Err(e) = res { error!("Failed to set dhcp resolvfile {:?}", e); return; } - let res = KI.uci_commit(DHCP_RESOLV_FILE_KEY); + let res = uci_commit(DHCP_RESOLV_FILE_KEY); if let Err(e) = res { error!("Failed to set dhcp resolvfile {:?}", e); return; } - let res = KI.openwrt_reset_dnsmasq(); + let res = openwrt_reset_dnsmasq(); if let Err(e) = res { error!("Failed to restart dhcp config with {:?}", e); } @@ -456,7 +463,7 @@ pub fn update_system_time() { ); let seconds = last_saved * 3600; let formatted_seconds = format!("@{}", seconds); - match KI.run_command("date", &["-s", &formatted_seconds]) { + match run_command("date", &["-s", &formatted_seconds]) { Ok(_) => info!("System time updated!"), Err(e) => error!("{}", e), } diff --git a/rita_client/src/self_rescue.rs b/rita_client/src/self_rescue.rs index ce7f74624..919ec4a54 100644 --- a/rita_client/src/self_rescue.rs +++ b/rita_client/src/self_rescue.rs @@ -1,6 +1,10 @@ use althea_kernel_interface::hardware_info::get_hardware_info; -use althea_kernel_interface::KI; -use rand::prelude::SliceRandom; +use althea_kernel_interface::ping_check::ping_check; +use althea_kernel_interface::run_command; +use althea_kernel_interface::setup_wg_if::{ + get_last_active_handshake_time, get_list_of_wireguard_interfaces, +}; +use rand::seq::SliceRandom; use rand::Rng; use rita_common::TUNNEL_HANDSHAKE_TIMEOUT; use settings::get_rita_common; @@ -33,7 +37,7 @@ pub fn start_rita_client_rescue_loop() { } else { // If this router has been in a bad state for >10 mins, reboot if (Instant::now() - last_successful_ping) > REBOOT_TIMEOUT { - let _res = KI.run_command("reboot", &[]); + let _res = run_command("reboot", &[]); } } @@ -45,13 +49,13 @@ pub fn start_rita_client_rescue_loop() { // If you're coming back and looking at expanding/improving this in the future, consider moving this out of a separate thread // and into rita_client or rita_common loops using a shared Instant that exists only in the context of those two threads rather than // a global lazy static Instant. This would keep us from having to make guesses around the system time - let wg_interfaces = KI.get_list_of_wireguard_interfaces(); + let wg_interfaces = get_list_of_wireguard_interfaces(); trace!("interfaces {:?}", wg_interfaces); if let Ok(interfaces) = wg_interfaces { let mut rng = rand::thread_rng(); let sample = interfaces.choose_multiple(&mut rng, 3); for interface in sample { - if let Ok(times) = KI.get_last_active_handshake_time(interface) { + if let Ok(times) = get_last_active_handshake_time(interface) { // we grab only the first timestamps because none of these tunnels should have multiple timestamps if let Some((_, time)) = times.first() { if let Ok(elapsed) = time.elapsed() { @@ -60,7 +64,7 @@ pub fn start_rita_client_rescue_loop() { last_successful_handshake_check.elapsed() > REBOOT_TIMEOUT, ) { (true, true) => { - let _res = KI.run_command("reboot", &[]); + let _res = run_command("reboot", &[]); } // wait (true, false) => {} @@ -84,7 +88,7 @@ pub fn start_rita_client_rescue_loop() { (Some(mdl), Ok(info)) => { if mdl.contains("mikrotik_hap-ac2") && info.load_avg_fifteen_minute > 4.0 { info!("15 minute load average > 4, rebooting!"); - let _res = KI.run_command("reboot", &[]); + let _res = run_command("reboot", &[]); } } (Some(_), Err(_)) => error!("Could not get hardware info!"), @@ -122,7 +126,7 @@ pub fn run_ping_test() -> bool { let target_ip = PING_TEST_IPS[index]; let timeout = Duration::from_secs(5); - match KI.ping_check(&target_ip.into(), timeout, None) { + match ping_check(&target_ip.into(), timeout, None) { Ok(out) => out, Err(e) => { error!("ipv4 ping error: {:?}", e); diff --git a/rita_client/src/traffic_watcher/mod.rs b/rita_client/src/traffic_watcher/mod.rs index 22b0ca3f5..19f7bb1ae 100644 --- a/rita_client/src/traffic_watcher/mod.rs +++ b/rita_client/src/traffic_watcher/mod.rs @@ -23,6 +23,8 @@ use crate::rita_loop::is_gateway_client; use crate::RitaClientError; +use althea_kernel_interface::netns::check_integration_test_netns; +use althea_kernel_interface::wg_iface_counter::read_wg_counters; use althea_types::Identity; use babel_monitor::parsing::get_installed_route; use babel_monitor::structs::BabelMonitorError; @@ -33,7 +35,6 @@ use rita_common::debt_keeper::{gateway_traffic_update, traffic_replace, traffic_ use rita_common::usage_tracker::structs::UsageType; use rita_common::usage_tracker::update_usage_data; use rita_common::usage_tracker::UpdateUsage; -use rita_common::KI; use std::collections::HashMap; use std::net::IpAddr; use std::sync::Arc; @@ -48,7 +49,7 @@ lazy_static! { /// Gets Traffic watcher copy from the static ref, or default if no value has been set pub fn get_traffic_watcher() -> TrafficWatcher { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); TRAFFIC_WATCHER .read() .unwrap() @@ -64,7 +65,7 @@ pub fn get_traffic_watcher() -> TrafficWatcher { pub fn get_traffic_watcher_write_ref( input: &mut HashMap, ) -> &mut TrafficWatcher { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); input.entry(netns).or_default(); input.get_mut(&netns).unwrap() } @@ -230,7 +231,7 @@ pub fn local_traffic_calculation( let exit_route = find_exit_route_capped(exit.mesh_ip, routes)?; info!("Exit metric: {}", exit_route.metric); - let counter = match KI.read_wg_counters("wg_exit") { + let counter = match read_wg_counters("wg_exit") { Ok(res) => { if res.len() > 1 { warn!("wg_exit client tunnel has multiple peers!"); diff --git a/rita_common/src/dashboard/auth.rs b/rita_common/src/dashboard/auth.rs index 2040f162f..a201d454f 100644 --- a/rita_common/src/dashboard/auth.rs +++ b/rita_common/src/dashboard/auth.rs @@ -1,5 +1,8 @@ -use crate::{RitaCommonError, KI}; +use crate::RitaCommonError; use actix_web_async::{http::StatusCode, web::Json, HttpResponse}; +use althea_kernel_interface::{ + fs_sync::fs_sync, is_openwrt::is_openwrt, set_system_password::set_system_password, +}; use clarity::utils::bytes_to_hex_str; use settings::set_rita_common; use sha3::{Digest, Sha3_512}; @@ -28,13 +31,13 @@ pub async fn set_pass(router_pass: Json) -> HttpResponse { .json(format!("{}", RitaCommonError::SettingsError(e))); } - if KI.is_openwrt() { - if let Err(e) = KI.set_system_password(router_pass.password) { + if is_openwrt() { + if let Err(e) = set_system_password(router_pass.password) { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).json(format!("{e}")); } // We edited disk contents, force global sync - if let Err(e) = KI.fs_sync() { + if let Err(e) = fs_sync() { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).json(format!("{e}")); } } diff --git a/rita_common/src/dashboard/interfaces.rs b/rita_common/src/dashboard/interfaces.rs index 6c39bd38d..2d2ecb6b9 100644 --- a/rita_common/src/dashboard/interfaces.rs +++ b/rita_common/src/dashboard/interfaces.rs @@ -1,8 +1,14 @@ //! A generalized interface for modifying networking interface assignments using UCI -use crate::{RitaCommonError, KI}; +use crate::RitaCommonError; use actix_web_async::http::StatusCode; use actix_web_async::web::Path; use actix_web_async::{web::Json, HttpRequest, HttpResponse}; +use althea_kernel_interface::fs_sync::fs_sync; +use althea_kernel_interface::manipulate_uci::{ + del_uci_var, get_uci_var, openwrt_reset_network, openwrt_reset_wireless, set_uci_var, + uci_commit, uci_revert, uci_show, +}; +use althea_kernel_interface::{is_openwrt::is_openwrt, run_command}; use std::collections::HashMap; use std::fmt::Display; use std::net::Ipv4Addr; @@ -63,7 +69,7 @@ pub fn get_interfaces() -> Result, RitaCommonErro let mut retval = HashMap::new(); // Wired - for (setting_name, value) in KI.uci_show(Some("network"))? { + for (setting_name, value) in uci_show(Some("network"))? { // Only non-loopback non-bridge interface names should get past if setting_name.contains("ifname") && !value.contains("backhaul") && value != "lo" { // it's a list and we need to handle that @@ -108,7 +114,7 @@ pub fn ethernet2mode(ifname: &str, setting_name: &str) -> Result InterfaceMode::LTE, s if s.contains("backhaul") => { let prefix = "network.backhaul"; - let backhaul = KI.uci_show(Some(prefix))?; + let backhaul = uci_show(Some(prefix))?; trace!("{:?}", backhaul); let proto = if let Some(val) = backhaul.get(&format!("{prefix}.proto")) { val @@ -221,7 +227,7 @@ fn multiset_interfaces( trace!("Successfully transformed ethernet mode, rebooting"); // reboot has been moved here to avoid doing it after every interface, in theory we could do this without rebooting // and some attention has been paid to maintaining that possibility - KI.run_command("reboot", &[])?; + run_command("reboot", &[])?; Ok(()) } @@ -259,23 +265,23 @@ pub fn ethernet_transform_mode( InterfaceMode::Wan | InterfaceMode::StaticWan { .. } => { network.external_nic = None; - let ret = KI.del_uci_var("network.backhaul"); + let ret = del_uci_var("network.backhaul"); return_codes.push(ret); } // LTE is the same InterfaceMode::LTE => { network.external_nic = None; - let ret = KI.del_uci_var("network.lte"); + let ret = del_uci_var("network.lte"); return_codes.push(ret); } // LAN is a bridge and the lan bridge must always remain because things // like WiFi interfaces are attached to it. So we just remove the interface // from the list InterfaceMode::Lan => { - let list = KI.get_uci_var("network.lan.ifname")?; + let list = get_uci_var("network.lan.ifname")?; let new_list = list_remove(&list, ifname); - let ret = KI.set_uci_var("network.lan.ifname", &new_list); + let ret = set_uci_var("network.lan.ifname", &new_list); return_codes.push(ret); } // remove the section from the network and rita config, peer listener watches this setting @@ -283,7 +289,7 @@ pub fn ethernet_transform_mode( InterfaceMode::Mesh => { network.peer_interfaces.remove(ifname); - let ret = KI.del_uci_var(&filtered_ifname); + let ret = del_uci_var(&filtered_ifname); return_codes.push(ret); } InterfaceMode::Unknown => unimplemented!(), @@ -294,11 +300,11 @@ pub fn ethernet_transform_mode( InterfaceMode::Wan => { network.external_nic = Some(ifname.to_string()); - let ret = KI.set_uci_var("network.backhaul", "interface"); + let ret = set_uci_var("network.backhaul", "interface"); return_codes.push(ret); - let ret = KI.set_uci_var("network.backhaul.ifname", ifname); + let ret = set_uci_var("network.backhaul.ifname", ifname); return_codes.push(ret); - let ret = KI.set_uci_var("network.backhaul.proto", "dhcp"); + let ret = set_uci_var("network.backhaul.proto", "dhcp"); return_codes.push(ret); } InterfaceMode::StaticWan { @@ -308,45 +314,45 @@ pub fn ethernet_transform_mode( } => { network.external_nic = Some(ifname.to_string()); - let ret = KI.set_uci_var("network.backhaul", "interface"); + let ret = set_uci_var("network.backhaul", "interface"); return_codes.push(ret); - let ret = KI.set_uci_var("network.backhaul.ifname", ifname); + let ret = set_uci_var("network.backhaul.ifname", ifname); return_codes.push(ret); - let ret = KI.set_uci_var("network.backhaul.proto", "static"); + let ret = set_uci_var("network.backhaul.proto", "static"); return_codes.push(ret); - let ret = KI.set_uci_var("network.backhaul.netmask", &format!("{netmask}")); + let ret = set_uci_var("network.backhaul.netmask", &format!("{netmask}")); return_codes.push(ret); - let ret = KI.set_uci_var("network.backhaul.ipaddr", &format!("{ipaddr}")); + let ret = set_uci_var("network.backhaul.ipaddr", &format!("{ipaddr}")); return_codes.push(ret); - let ret = KI.set_uci_var("network.backhaul.gateway", &format!("{gateway}")); + let ret = set_uci_var("network.backhaul.gateway", &format!("{gateway}")); return_codes.push(ret); } InterfaceMode::LTE => { network.external_nic = Some(ifname.to_string()); - let ret = KI.set_uci_var("network.lte", "interface"); + let ret = set_uci_var("network.lte", "interface"); return_codes.push(ret); - let ret = KI.set_uci_var("network.lte.ifname", ifname); + let ret = set_uci_var("network.lte.ifname", ifname); return_codes.push(ret); - let ret = KI.set_uci_var("network.lte.proto", "dhcp"); + let ret = set_uci_var("network.lte.proto", "dhcp"); return_codes.push(ret); } // since we left lan mostly unmodified we just pop in the ifname InterfaceMode::Lan => { trace!("Converting interface to lan with ifname {:?}", ifname); - let ret = KI.get_uci_var("network.lan.ifname"); + let ret = get_uci_var("network.lan.ifname"); match ret { Ok(list) => { trace!("The existing LAN interfaces list is {:?}", list); let new_list = list_add(&list, ifname); trace!("Setting the new list {:?}", new_list); - let ret = KI.set_uci_var("network.lan.ifname", &new_list); + let ret = set_uci_var("network.lan.ifname", &new_list); return_codes.push(ret); } Err(e) => { if e.to_string().contains("Entry not found") { trace!("No LAN interfaces found, setting one now"); - let ret = KI.set_uci_var("network.lan.ifname", ifname); + let ret = set_uci_var("network.lan.ifname", ifname); return_codes.push(ret); } else { warn!("Trying to read lan ifname returned {:?}", e); @@ -358,11 +364,11 @@ pub fn ethernet_transform_mode( InterfaceMode::Mesh => { network.peer_interfaces.insert(ifname.to_string()); - let ret = KI.set_uci_var(&filtered_ifname, "interface"); + let ret = set_uci_var(&filtered_ifname, "interface"); return_codes.push(ret); - let ret = KI.set_uci_var(&format!("{filtered_ifname}.ifname"), ifname); + let ret = set_uci_var(&format!("{filtered_ifname}.ifname"), ifname); return_codes.push(ret); - let ret = KI.set_uci_var(&format!("{filtered_ifname}.proto"), "static"); + let ret = set_uci_var(&format!("{filtered_ifname}.proto"), "static"); return_codes.push(ret); } InterfaceMode::Unknown => unimplemented!(), @@ -377,7 +383,7 @@ pub fn ethernet_transform_mode( } let mut rita_client = settings::get_rita_client(); if !error_occured.is_empty() { - let res = KI.uci_revert("network"); + let res = uci_revert("network"); rita_client.network = old_network_settings; settings::set_rita_client(rita_client); //bail!("Error running UCI commands! Revert attempted: {:?}", res); @@ -394,8 +400,8 @@ pub fn ethernet_transform_mode( } } - KI.uci_commit("network")?; - KI.openwrt_reset_network()?; + uci_commit("network")?; + openwrt_reset_network()?; rita_client.network = network; settings::set_rita_client(rita_client); @@ -407,7 +413,7 @@ pub fn ethernet_transform_mode( trace!("Transforming ethernet"); // We edited disk contents, force global sync - KI.fs_sync()?; + fs_sync()?; Ok(()) } @@ -419,13 +425,13 @@ pub fn ethernet_transform_mode( /// to router wireless meshing'. fn wlan_toggle_get(uci_spec: &str) -> Result { - if !KI.is_openwrt() { + if !is_openwrt() { return Err(RitaCommonError::MiscStringError( "Not an OpenWRT device!".to_string(), )); } let bad_wireless = "Wireless config not correct"; - let current_state = KI.uci_show(Some(uci_spec))?; + let current_state = uci_show(Some(uci_spec))?; let current_state = match current_state.get(uci_spec) { Some(val) => val, None => return Err(RitaCommonError::MiscStringError(bad_wireless.to_string())), @@ -459,7 +465,7 @@ pub async fn wlan_mesh_get(_: HttpRequest) -> HttpResponse { } fn wlan_toggle_set(uci_spec: &str, enabled: bool) -> Result<(), RitaCommonError> { - if !KI.is_openwrt() { + if !is_openwrt() { return Err(RitaCommonError::MiscStringError( "Not an OpenWRT device!".to_string(), )); @@ -467,7 +473,7 @@ fn wlan_toggle_set(uci_spec: &str, enabled: bool) -> Result<(), RitaCommonError> let bad_wireless = "Wireless config not correct"; trace!("wlan toggle: uci_spec {}, enabled: {}", uci_spec, enabled,); - let current_state = KI.uci_show(Some(uci_spec))?; + let current_state = uci_show(Some(uci_spec))?; let current_state = match current_state.get(uci_spec) { Some(val) => val, None => return Err(RitaCommonError::MiscStringError(bad_wireless.to_string())), @@ -486,13 +492,13 @@ fn wlan_toggle_set(uci_spec: &str, enabled: bool) -> Result<(), RitaCommonError> // remember it's a 'disabled' toggle so we want to set it to zero to be 'enabled' let res = if enabled { - KI.set_uci_var(uci_spec, "0") + set_uci_var(uci_spec, "0") } else { - KI.set_uci_var(uci_spec, "1") + set_uci_var(uci_spec, "1") }; if let Err(e) = res { - let res_b = KI.uci_revert("wireless"); + let res_b = uci_revert("wireless"); if let Err(re) = res_b { return Err(RitaCommonError::InterfaceToggleError { main_error: vec![e], @@ -506,13 +512,13 @@ fn wlan_toggle_set(uci_spec: &str, enabled: bool) -> Result<(), RitaCommonError> } } - KI.uci_commit("wireless")?; - KI.openwrt_reset_wireless()?; + uci_commit("wireless")?; + openwrt_reset_wireless()?; // We edited disk contents, force global sync - KI.fs_sync()?; + fs_sync()?; - KI.run_command("reboot", &[])?; + run_command("reboot", &[])?; Ok(()) } diff --git a/rita_common/src/dashboard/logging.rs b/rita_common/src/dashboard/logging.rs index e1bd4b4b1..d8464a124 100644 --- a/rita_common/src/dashboard/logging.rs +++ b/rita_common/src/dashboard/logging.rs @@ -1,6 +1,7 @@ -use crate::KI; use actix_web_async::http::StatusCode; use actix_web_async::{web::Path, HttpRequest, HttpResponse}; +use althea_kernel_interface::is_openwrt::is_openwrt; +use althea_kernel_interface::run_command; use log::LevelFilter; pub async fn get_remote_logging(_req: HttpRequest) -> HttpResponse { @@ -25,8 +26,8 @@ pub async fn remote_logging(path: Path) -> HttpResponse { .json(format!("Failed to write config {e:?}")); } - if KI.is_openwrt() { - if let Err(e) = KI.run_command(service_path.as_str(), &["restart"]) { + if is_openwrt() { + if let Err(e) = run_command(service_path.as_str(), &["restart"]) { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR) .json(format!("Failed to restart service {e:?}")); } @@ -66,7 +67,7 @@ pub async fn remote_logging_level(path: Path) -> HttpResponse { .json(format!("Failed to write config {e:?}")); } - if let Err(e) = KI.run_command(service_path.as_str(), &["restart"]) { + if let Err(e) = run_command(service_path.as_str(), &["restart"]) { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR) .json(format!("Failed to restart service {e:?}")); } diff --git a/rita_common/src/dashboard/remote_access.rs b/rita_common/src/dashboard/remote_access.rs index eb18572a8..6186ebbec 100644 --- a/rita_common/src/dashboard/remote_access.rs +++ b/rita_common/src/dashboard/remote_access.rs @@ -1,16 +1,17 @@ use crate::RitaCommonError; use crate::DROPBEAR_CONFIG; -use crate::KI; use actix_web_async::http::StatusCode; use actix_web_async::web::Path; use actix_web_async::HttpRequest; use actix_web_async::HttpResponse; use althea_kernel_interface::file_io::get_lines; use althea_kernel_interface::file_io::write_out; +use althea_kernel_interface::is_openwrt::is_openwrt; +use althea_kernel_interface::run_command; static FIREWALL_CONFIG: &str = "/etc/config/firewall"; pub async fn get_remote_access_status(_req: HttpRequest) -> HttpResponse { - if !KI.is_openwrt() { + if !is_openwrt() { return HttpResponse::new(StatusCode::BAD_REQUEST); } HttpResponse::Ok().json(match check_dropbear_config() { @@ -25,7 +26,7 @@ pub async fn get_remote_access_status(_req: HttpRequest) -> HttpResponse { // the http responses at some point #[allow(dead_code)] pub fn get_remote_access_internal() -> Result { - if !KI.is_openwrt() { + if !is_openwrt() { return Err(RitaCommonError::ConversionError("Not Openwrt!".to_string())); } check_dropbear_config() @@ -81,7 +82,7 @@ pub fn set_remote_access_internal(remote_access: bool) -> Result<(), RitaCommonE lines.push(" option Interface 'lan'".to_string()); write_out(DROPBEAR_CONFIG, lines)?; - KI.run_command("/etc/init.d/dropbear", &["restart"])?; + run_command("/etc/init.d/dropbear", &["restart"])?; // this adds the updated rules to the firewall config, notice the versioning on the // firewall rules. The old ones will be left in place. @@ -114,7 +115,7 @@ pub fn set_remote_access_internal(remote_access: bool) -> Result<(), RitaCommonE } if needs_mesh_ssh || needs_wan_ssh { write_out(FIREWALL_CONFIG, firewall_lines)?; - KI.run_command("reboot", &[])?; + run_command("reboot", &[])?; Ok(()) } else { Ok(()) diff --git a/rita_common/src/dashboard/wifi.rs b/rita_common/src/dashboard/wifi.rs index bac052bd5..802536c31 100644 --- a/rita_common/src/dashboard/wifi.rs +++ b/rita_common/src/dashboard/wifi.rs @@ -1,10 +1,16 @@ //! These endpoints are used to modify mundane wireless settings use crate::dashboard::nickname::maybe_set_nickname; -use crate::{RitaCommonError, KI}; +use crate::RitaCommonError; use ::actix_web_async::http::StatusCode; use ::actix_web_async::web::Path; use ::actix_web_async::{web::Json, HttpRequest, HttpResponse}; +use althea_kernel_interface::exit_client_tunnel::create_client_nat_rules; +use althea_kernel_interface::fs_sync::fs_sync; +use althea_kernel_interface::manipulate_uci::{ + get_uci_var, openwrt_reset_wireless, set_uci_var, uci_commit, +}; +use althea_kernel_interface::openwrt_ubus::ubus_call; use althea_types::{ FromStr, WifiChannel, WifiDisabled, WifiPass, WifiSecurity, WifiSsid, WifiToken, }; @@ -206,12 +212,12 @@ pub fn set_ssid(wifi_ssid: &WifiSsid) -> Result<(), RitaCommonError> { let iface_name = wifi_ssid.radio.clone(); let ssid = wifi_ssid.ssid.clone(); let section_name = format!("default_{iface_name}"); - KI.set_uci_var(&format!("wireless.{section_name}.ssid"), &ssid)?; + set_uci_var(&format!("wireless.{section_name}.ssid"), &ssid)?; - KI.uci_commit("wireless")?; + uci_commit("wireless")?; // We edited disk contents, force global sync - KI.fs_sync()?; + fs_sync()?; // set the nickname with the first SSID change may fail // if the ssid is too long but don't block on that @@ -231,14 +237,14 @@ pub fn reset_wifi_pass() -> Result<(), RitaCommonError> { set_pass(&pass)?; } - KI.uci_commit("wireless")?; - KI.openwrt_reset_wireless()?; + uci_commit("wireless")?; + openwrt_reset_wireless()?; // We edited disk contents, force global sync - KI.fs_sync()?; + fs_sync()?; // we have invalidated the old nat rules, update them - KI.create_client_nat_rules()?; + create_client_nat_rules()?; Ok(()) } @@ -258,16 +264,15 @@ fn set_pass(wifi_pass: &WifiPass) -> Result<(), RitaCommonError> { let iface_name = wifi_pass.radio.clone(); let pass = wifi_pass.pass.clone(); let section_name = format!("default_{iface_name}"); - KI.set_uci_var(&format!("wireless.{section_name}.key"), &pass)?; + set_uci_var(&format!("wireless.{section_name}.key"), &pass)?; Ok(()) } fn set_channel(wifi_channel: &WifiChannel) -> Result<(), RitaCommonError> { - let current_channel: u16 = KI - .get_uci_var(&format!("wireless.{}.channel", wifi_channel.radio))? - .parse()?; - let channel_width = KI.get_uci_var(&format!("wireless.{}.htmode", wifi_channel.radio))?; + let current_channel: u16 = + get_uci_var(&format!("wireless.{}.channel", wifi_channel.radio))?.parse()?; + let channel_width = get_uci_var(&format!("wireless.{}.htmode", wifi_channel.radio))?; if let Err(e) = validate_channel( current_channel, @@ -279,7 +284,7 @@ fn set_channel(wifi_channel: &WifiChannel) -> Result<(), RitaCommonError> { return Err(e.into()); } - KI.set_uci_var( + set_uci_var( &format!("wireless.{}.channel", wifi_channel.radio), &wifi_channel.channel.to_string(), )?; @@ -294,7 +299,7 @@ fn set_security(wifi_security: &WifiSecurity) -> Result<(), RitaCommonError> { // think radio0, radio1 let iface_name = wifi_security.radio.clone(); let section_name = format!("default_{iface_name}"); - KI.set_uci_var( + set_uci_var( &format!("wireless.{section_name}.encryption"), &parsed.as_config_value(), )?; @@ -310,13 +315,13 @@ fn set_security(wifi_security: &WifiSecurity) -> Result<(), RitaCommonError> { /// Disables the wifi on the specified radio fn set_disabled(wifi_disabled: &WifiDisabled) -> Result<(), RitaCommonError> { let current_disabled: bool = - KI.get_uci_var(&format!("wireless.{}.disabled", wifi_disabled.radio))? == "1"; + get_uci_var(&format!("wireless.{}.disabled", wifi_disabled.radio))? == "1"; if current_disabled == wifi_disabled.disabled { return Ok(()); } - KI.set_uci_var( + set_uci_var( &format!("wireless.{}.disabled", wifi_disabled.radio), if wifi_disabled.disabled { "1" } else { "0" }, )?; @@ -392,25 +397,25 @@ pub fn set_wifi_multi_internal(wifi_changes: Vec) -> HttpResponse { }; } - if let Err(e) = KI.uci_commit("wireless") { + if let Err(e) = uci_commit("wireless") { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).json(ErrorJsonResponse { error: format!("{e}"), }); } - if let Err(e) = KI.openwrt_reset_wireless() { + if let Err(e) = openwrt_reset_wireless() { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).json(ErrorJsonResponse { error: format!("{e}"), }); } // We edited disk contents, force global sync - if let Err(e) = KI.fs_sync() { + if let Err(e) = fs_sync() { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).json(ErrorJsonResponse { error: format!("{e}"), }); } // we have invalidated the old nat rules, update them - if let Err(e) = KI.create_client_nat_rules() { + if let Err(e) = create_client_nat_rules() { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).json(ErrorJsonResponse { error: format!("{e}"), }); @@ -521,7 +526,7 @@ pub async fn get_allowed_wifi_channels(radio: Path) -> HttpResponse { debug!("/wifi_settings/get_channels hit with {:?}", radio); let radio = radio.into_inner(); - let current_channel: u16 = match KI.get_uci_var(&format!("wireless.{radio}.channel")) { + let current_channel: u16 = match get_uci_var(&format!("wireless.{radio}.channel")) { Ok(uci) => match uci.parse() { Ok(a) => a, Err(e) => { @@ -532,7 +537,7 @@ pub async fn get_allowed_wifi_channels(radio: Path) -> HttpResponse { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).json(format!("{e}")); } }; - let five_channel_width = match KI.get_uci_var(&format!("wireless.{radio}.htmode")) { + let five_channel_width = match get_uci_var(&format!("wireless.{radio}.htmode")) { Ok(a) => a, Err(e) => { return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).json(format!("{e}")); @@ -619,7 +624,7 @@ pub async fn get_wifi_config(_req: HttpRequest) -> HttpResponse { pub fn get_wifi_config_internal() -> Result, RitaCommonError> { let mut interfaces = Vec::new(); let mut devices = HashMap::new(); - let config = KI.ubus_call("uci", "get", "{ \"config\": \"wireless\"}")?; + let config = ubus_call("uci", "get", "{ \"config\": \"wireless\"}")?; let val: Value = serde_json::from_str(&config)?; let items = match val["values"].as_object() { Some(i) => i, diff --git a/rita_common/src/debt_keeper/mod.rs b/rita_common/src/debt_keeper/mod.rs index 71c17d8ca..1478fa0e9 100755 --- a/rita_common/src/debt_keeper/mod.rs +++ b/rita_common/src/debt_keeper/mod.rs @@ -15,7 +15,7 @@ use crate::tunnel_manager::tm_tunnel_state_change; use crate::tunnel_manager::TunnelAction; use crate::tunnel_manager::TunnelChange; use crate::RitaCommonError; -use crate::KI; +use althea_kernel_interface::netns::check_integration_test_netns; use althea_types::Denom; use althea_types::Identity; use althea_types::UnpublishedPaymentTx; @@ -60,7 +60,7 @@ pub fn wei_denom() -> Denom { /// Gets DebtKeeper copy from the static ref, or default if no value has been set pub fn get_debt_keeper() -> DebtKeeper { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); DEBT_DATA .read() .unwrap() @@ -74,7 +74,7 @@ pub fn get_debt_keeper() -> DebtKeeper { /// the lock will be held until you drop the return value, this lets the caller abstract the namespace handling /// but still hold the lock in the local thread to prevent parallel modification fn get_debt_keeper_write_ref(input: &mut HashMap) -> &mut DebtKeeper { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); input.entry(netns).or_default(); input.get_mut(&netns).unwrap() } diff --git a/rita_common/src/lib.rs b/rita_common/src/lib.rs index 2dfddde7d..5bc2a76e2 100644 --- a/rita_common/src/lib.rs +++ b/rita_common/src/lib.rs @@ -8,13 +8,6 @@ extern crate lazy_static; extern crate serde_derive; extern crate arrayvec; -use althea_kernel_interface::KernelInterface; - -use althea_kernel_interface::LinuxCommandRunner; - -lazy_static! { - pub static ref KI: Box = Box::new(LinuxCommandRunner {}); -} pub static DROPBEAR_CONFIG: &str = "/etc/config/dropbear"; pub static DROPBEAR_AUTHORIZED_KEYS: &str = "/etc/dropbear/authorized_keys"; diff --git a/rita_common/src/payment_validator/mod.rs b/rita_common/src/payment_validator/mod.rs index 3684c71ed..768945673 100644 --- a/rita_common/src/payment_validator/mod.rs +++ b/rita_common/src/payment_validator/mod.rs @@ -14,7 +14,7 @@ use crate::rita_loop::fast_loop::FAST_LOOP_TIMEOUT; use crate::rita_loop::get_web3_server; use crate::usage_tracker::update_payments; use crate::RitaCommonError; -use crate::KI; +use althea_kernel_interface::netns::check_integration_test_netns; use althea_proto::althea::microtx::v1::MsgMicrotx; use althea_types::Denom; use althea_types::Identity; @@ -82,7 +82,7 @@ lazy_static! { /// Adds an incoming transaction to the global incoming transaction queue abstracts netns handling /// to ensure that multiple instances can run in the same process without interfering with each other pub fn add_to_incoming_transaction_queue(tx: ToValidate) { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); INCOMING_TRANSACTIONS.push((netns, tx)); } @@ -90,7 +90,7 @@ pub fn add_to_incoming_transaction_queue(tx: ToValidate) { /// the queue while doing so. This abstracts netns handling to ensure that multiple instances /// can run in the same process without interfering with each other pub fn get_incoming_transaction_queue() -> Vec { - let our_netns = KI.check_integration_test_netns(); + let our_netns = check_integration_test_netns(); // this is a hack, in order to avoid any locks at all we iterate // over the entire queue and only take the items that are for our netns // it helps that this overhead will only ever occur in the integration tests diff --git a/rita_common/src/peer_listener/mod.rs b/rita_common/src/peer_listener/mod.rs index 74891b477..5e764c01c 100644 --- a/rita_common/src/peer_listener/mod.rs +++ b/rita_common/src/peer_listener/mod.rs @@ -15,7 +15,8 @@ use crate::peer_listener::structs::PeerListener; use crate::tm_identity_callback; use crate::IdentityCallback; use crate::RitaCommonError; -use crate::KI; +use althea_kernel_interface::interface_tools::get_ifindex; +use althea_kernel_interface::link_local_tools::get_link_local_device_ip; use althea_types::LocalIdentity; use std::collections::HashMap; use std::net::{IpAddr, Ipv6Addr, SocketAddr, SocketAddrV6, UdpSocket}; @@ -108,11 +109,11 @@ impl ListenInterface { let disc_ip = network.discovery_ip; trace!("Binding to {:?} for ListenInterface", ifname); // Lookup interface link local ip - let link_ip = KI.get_link_local_device_ip(ifname)?; + let link_ip = get_link_local_device_ip(ifname)?; trace!("Link ip is {:?}", link_ip); // Lookup interface index - let iface_index: u32 = KI.get_ifindex(ifname).unwrap_or(0) as u32; + let iface_index: u32 = get_ifindex(ifname).unwrap_or(0) as u32; // Bond to multicast discovery address on each listen port let multicast_socketaddr = SocketAddrV6::new(disc_ip, port, 0, iface_index); let multicast_socket = UdpSocket::bind(multicast_socketaddr)?; diff --git a/rita_common/src/rita_loop/fast_loop.rs b/rita_common/src/rita_loop/fast_loop.rs index 388d8c1d5..8fd79164a 100644 --- a/rita_common/src/rita_loop/fast_loop.rs +++ b/rita_common/src/rita_loop/fast_loop.rs @@ -9,8 +9,9 @@ use crate::peer_listener::structs::PeerListener; use crate::traffic_watcher::watch; use crate::tunnel_manager::contact_peers::tm_contact_peers; use crate::tunnel_manager::tm_get_neighbors; -use crate::KI; use actix_async::System as AsyncSystem; +use althea_kernel_interface::is_openwrt::is_openwrt; +use althea_kernel_interface::run_command; use babel_monitor::open_babel_stream; use babel_monitor::parse_neighs; use babel_monitor::parse_routes; @@ -124,8 +125,8 @@ pub fn start_rita_fast_loop() { if Instant::now() - last_restart < Duration::from_secs(60) { error!("Restarting too quickly, rebooting instead!"); // only reboot if we are on openwrt, otherwise we are probably on a datacenter server rebooting that is a bad idea - if KI.is_openwrt() { - let _res = KI.run_command("reboot", &[]); + if is_openwrt() { + let _res = run_command("reboot", &[]); } } last_restart = Instant::now(); @@ -188,8 +189,8 @@ pub fn peer_discovery_loop() { if Instant::now() - last_restart < Duration::from_secs(60) { error!("Restarting too quickly, rebooting instead!"); // only reboot if we are on openwrt, otherwise we are probably on a datacenter server rebooting that is a bad idea - if KI.is_openwrt() { - let _res = KI.run_command("reboot", &[]); + if is_openwrt() { + let _res = run_command("reboot", &[]); } } last_restart = Instant::now(); diff --git a/rita_common/src/rita_loop/slow_loop.rs b/rita_common/src/rita_loop/slow_loop.rs index b45d134c9..52840f3f4 100644 --- a/rita_common/src/rita_loop/slow_loop.rs +++ b/rita_common/src/rita_loop/slow_loop.rs @@ -2,8 +2,10 @@ use crate::handle_shaping; use crate::simulated_txfee_manager::tick_simulated_tx; use crate::token_bridge::tick_token_bridge; use crate::tunnel_manager::tm_common_slow_loop_helper; -use crate::KI; use actix_async::System as AsyncSystem; +use althea_kernel_interface::babel::restart_babel; +use althea_kernel_interface::is_openwrt::is_openwrt; +use althea_kernel_interface::run_command; use babel_monitor::open_babel_stream; use babel_monitor::parse_interfaces; use babel_monitor::set_local_fee; @@ -89,7 +91,7 @@ pub fn start_rita_slow_loop() { error!("We have not successfully talked to babel in {} loop iterations, restarting babel", num_babel_failures); // we restart babel here and then rely on the tm_monitor_check function to re-attach the tunnels in the next loop // iteration - KI.restart_babel(); + restart_babel(); } thread::sleep(SLOW_LOOP_SPEED); @@ -104,8 +106,8 @@ pub fn start_rita_slow_loop() { if Instant::now() - last_restart < Duration::from_secs(120) { error!("Restarting too quickly, rebooting instead!"); // only reboot if we are on openwrt, otherwise we are probably on a datacenter server rebooting that is a bad idea - if KI.is_openwrt() { - let _res = KI.run_command("reboot", &[]); + if is_openwrt() { + let _res = run_command("reboot", &[]); } } last_restart = Instant::now(); diff --git a/rita_common/src/simulated_txfee_manager/mod.rs b/rita_common/src/simulated_txfee_manager/mod.rs index 323a93b30..e21ab8e2d 100644 --- a/rita_common/src/simulated_txfee_manager/mod.rs +++ b/rita_common/src/simulated_txfee_manager/mod.rs @@ -4,7 +4,7 @@ use crate::blockchain_oracle::get_pay_thresh; use crate::payment_controller::TRANSACTION_SUBMISSION_TIMEOUT; use crate::rita_loop::get_web3_server; use crate::usage_tracker::update_payments; -use crate::KI; +use althea_kernel_interface::netns::check_integration_test_netns; use althea_types::Identity; use althea_types::PaymentTx; use num256::Uint256; @@ -21,7 +21,7 @@ lazy_static! { /// Gets Amount owed copy from the static ref, or default if no value has been set pub fn get_amount_owed() -> Uint256 { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); AMOUNT_OWED .read() .unwrap() @@ -35,7 +35,7 @@ pub fn get_amount_owed() -> Uint256 { /// the lock will be held until you drop the return value, this lets the caller abstract the namespace handling /// but still hold the lock in the local thread to prevent parallel modification pub fn get_amount_owed_write_ref(input: &mut HashMap) -> &mut Uint256 { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); input.entry(netns).or_insert_with(Uint256::zero); input.get_mut(&netns).unwrap() } diff --git a/rita_common/src/traffic_watcher/mod.rs b/rita_common/src/traffic_watcher/mod.rs index cfc3091e0..9c288c71f 100644 --- a/rita_common/src/traffic_watcher/mod.rs +++ b/rita_common/src/traffic_watcher/mod.rs @@ -9,7 +9,8 @@ use crate::usage_tracker::structs::UsageType; use crate::usage_tracker::update_usage_data; use crate::usage_tracker::UpdateUsage; use crate::RitaCommonError; -use crate::KI; +use althea_kernel_interface::counter::init_counter; +use althea_kernel_interface::counter::read_counters; use althea_kernel_interface::open_tunnel::is_link_local; use althea_kernel_interface::FilterTarget; use althea_types::Identity; @@ -35,14 +36,10 @@ impl Watch { } pub fn init_traffic_watcher() { - KI.init_counter(&FilterTarget::Input) - .expect("Is ipset installed?"); - KI.init_counter(&FilterTarget::Output) - .expect("Is ipset installed?"); - KI.init_counter(&FilterTarget::ForwardInput) - .expect("Is ipset installed?"); - KI.init_counter(&FilterTarget::ForwardOutput) - .expect("Is ipset installed?"); + init_counter(&FilterTarget::Input).expect("Is ipset installed?"); + init_counter(&FilterTarget::Output).expect("Is ipset installed?"); + init_counter(&FilterTarget::ForwardInput).expect("Is ipset installed?"); + init_counter(&FilterTarget::ForwardOutput).expect("Is ipset installed?"); info!("Traffic Watcher started"); } @@ -110,7 +107,7 @@ pub fn get_babel_info(routes: Vec) -> Result<(HashMap, u32) pub fn get_input_counters() -> Result, RitaCommonError> { let mut total_input_counters = HashMap::new(); trace!("Getting input counters"); - let input_counters = match KI.read_counters(&FilterTarget::Input) { + let input_counters = match read_counters(&FilterTarget::Input) { Ok(res) => res, Err(e) => { warn!( @@ -122,7 +119,7 @@ pub fn get_input_counters() -> Result, RitaCommon }; trace!("Got input counters: {:?}", input_counters); trace!("Getting fwd counters"); - let fwd_input_counters = match KI.read_counters(&FilterTarget::ForwardInput) { + let fwd_input_counters = match read_counters(&FilterTarget::ForwardInput) { Ok(res) => res, Err(e) => { warn!( @@ -164,7 +161,7 @@ pub fn get_input_counters() -> Result, RitaCommon pub fn get_output_counters() -> Result, RitaCommonError> { let mut total_output_counters = HashMap::new(); trace!("Getting ouput counters"); - let output_counters = match KI.read_counters(&FilterTarget::Output) { + let output_counters = match read_counters(&FilterTarget::Output) { Ok(res) => res, Err(e) => { warn!( @@ -176,7 +173,7 @@ pub fn get_output_counters() -> Result, RitaCommo }; trace!("Got output counters: {:?}", output_counters); - let fwd_output_counters = match KI.read_counters(&FilterTarget::ForwardOutput) { + let fwd_output_counters = match read_counters(&FilterTarget::ForwardOutput) { Ok(res) => res, Err(e) => { warn!( diff --git a/rita_common/src/tunnel_manager/contact_peers.rs b/rita_common/src/tunnel_manager/contact_peers.rs index 02125de8f..047234328 100644 --- a/rita_common/src/tunnel_manager/contact_peers.rs +++ b/rita_common/src/tunnel_manager/contact_peers.rs @@ -13,7 +13,7 @@ use crate::tm_identity_callback; use crate::tunnel_manager::get_tunnel_manager; use crate::IdentityCallback; use crate::RitaCommonError; -use crate::KI; +use althea_kernel_interface::ip_route::manual_peers_route; use althea_types::LocalIdentity; use futures::future::join_all; use std::net::ToSocketAddrs; @@ -73,7 +73,7 @@ pub async fn tm_neighbor_inquiry_manual_peer(peer: Peer) -> Result<(), RitaCommo trace!("TunnelManager neigh inquiry for {:?}", peer); let our_port = get_tunnel_manager().get_next_available_port()?; let mut settings = settings::get_rita_common(); - let changed = KI.manual_peers_route( + let changed = manual_peers_route( &peer.contact_socket.ip(), &mut settings.network.last_default_route, )?; diff --git a/rita_common/src/tunnel_manager/gc.rs b/rita_common/src/tunnel_manager/gc.rs index fcf5f58b4..38015f052 100644 --- a/rita_common/src/tunnel_manager/gc.rs +++ b/rita_common/src/tunnel_manager/gc.rs @@ -1,5 +1,5 @@ use super::{Tunnel, TunnelManager}; -use crate::KI; +use althea_kernel_interface::setup_wg_if::get_last_handshake_time; use althea_types::Identity; use babel_monitor::structs::Interface; use std::time::Duration; @@ -193,7 +193,7 @@ pub fn insert_into_tunnel_list(input: &Tunnel, tunnels_list: &mut HashMap bool { - let res = KI.get_last_handshake_time(ifname); + let res = get_last_handshake_time(ifname); match res { Ok(handshakes) => { for (_key, time) in handshakes { diff --git a/rita_common/src/tunnel_manager/mod.rs b/rita_common/src/tunnel_manager/mod.rs index 2f8ab367d..fcb087f01 100644 --- a/rita_common/src/tunnel_manager/mod.rs +++ b/rita_common/src/tunnel_manager/mod.rs @@ -18,10 +18,17 @@ use crate::tunnel_manager::error::TunnelManagerError; use crate::RitaCommonError; use crate::Shaper; use crate::FAST_LOOP_TIMEOUT; -use crate::KI; use crate::TUNNEL_HANDSHAKE_TIMEOUT; use crate::TUNNEL_TIMEOUT; +use althea_kernel_interface::interface_tools::del_interface; +use althea_kernel_interface::netns::check_integration_test_netns; +use althea_kernel_interface::open_tunnel::open_tunnel; use althea_kernel_interface::open_tunnel::TunnelOpenArgs; +use althea_kernel_interface::setup_wg_if::create_blank_wg_numbered_wg_interface; +use althea_kernel_interface::traffic_control::has_limit; +use althea_kernel_interface::traffic_control::set_classless_limit; +use althea_kernel_interface::traffic_control::set_codel_shaping; +use althea_kernel_interface::udp_socket_table::used_ports; use althea_types::Identity; use althea_types::LocalIdentity; use babel_monitor::monitor; @@ -47,7 +54,7 @@ lazy_static! { /// Gets TunnelManager copy from the static ref, or default if no value has been set pub fn get_tunnel_manager() -> TunnelManager { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); TUNNEL_MANAGER .read() .unwrap() @@ -61,7 +68,7 @@ pub fn get_tunnel_manager() -> TunnelManager { /// the lock will be held until you drop the return value, this lets the caller abstract the namespace handling /// but still hold the lock in the local thread to prevent parallel modification pub fn get_tunnel_manager_write_ref(input: &mut HashMap) -> &mut TunnelManager { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); input.entry(netns).or_default(); input.get_mut(&netns).unwrap() } @@ -163,7 +170,7 @@ impl Tunnel { } }; // after this step we have created a blank wg interface that we should clean up if we fail - let iface_name = KI.create_blank_wg_numbered_wg_interface()?; + let iface_name = create_blank_wg_numbered_wg_interface()?; let args = TunnelOpenArgs { interface: iface_name.clone(), @@ -177,14 +184,14 @@ impl Tunnel { settings_default_route: &mut network.last_default_route, }; - if let Err(e) = KI.open_tunnel(args) { + if let Err(e) = open_tunnel(args) { error!("Failed open tunnel! {:?}", e); // cleanup after our failed attempt - KI.del_interface(&iface_name)?; + del_interface(&iface_name)?; return Err(e.into()); } // a failure here isn't fatal, we just won't have traffic shaping - if let Err(e) = KI.set_codel_shaping(&iface_name, speed_limit) { + if let Err(e) = set_codel_shaping(&iface_name, speed_limit) { error!("Failed to setup codel shaping on tunnel! {:?}", e); } @@ -206,7 +213,7 @@ impl Tunnel { if let Err(e) = t.monitor() { error!("Failed to monitor tunnel! {:?}", e); // cleanup after our failed attempt - KI.del_interface(&t.iface_name)?; + del_interface(&t.iface_name)?; return Err(e.into()); } @@ -242,7 +249,7 @@ impl Tunnel { // We must wait until we have flushed the interface before deleting it // otherwise we will experience this error // https://github.com/sudomesh/bugs/issues/24 - if let Err(e) = KI.del_interface(&tunnel.iface_name) { + if let Err(e) = del_interface(&tunnel.iface_name) { error!("Failed to delete wg interface! {:?}", e); return Err(e.into()); } @@ -351,7 +358,7 @@ impl TunnelManager { /// Gets a port off of the internal port list after checking that said port is free /// with the operating system. fn get_next_available_port(&self) -> Result { - let udp_table = KI.used_ports()?; + let udp_table = used_ports()?; let used_ports = self.get_all_used_ports(); let start = settings::get_rita_common().network.wg_start_port; @@ -610,15 +617,15 @@ fn tunnel_bw_limit_update(tunnels: &[Tunnel]) -> Result<(), RitaCommonError> { for tunnel in tunnels { let payment_state = &tunnel.payment_state; let iface_name = &tunnel.iface_name; - let has_limit = KI.has_limit(iface_name)?; + let has_limit = has_limit(iface_name)?; if *payment_state == PaymentState::Overdue && !has_limit && !potential_payment_issues_detected() { - KI.set_classless_limit(iface_name, bw_per_iface)?; + set_classless_limit(iface_name, bw_per_iface)?; } else if *payment_state == PaymentState::Paid && has_limit { - KI.set_codel_shaping(iface_name, None)?; + set_codel_shaping(iface_name, None)?; } } Ok(()) diff --git a/rita_common/src/tunnel_manager/shaping.rs b/rita_common/src/tunnel_manager/shaping.rs index b0fddbe70..645d208c9 100644 --- a/rita_common/src/tunnel_manager/shaping.rs +++ b/rita_common/src/tunnel_manager/shaping.rs @@ -1,7 +1,8 @@ +use althea_kernel_interface::traffic_control::set_codel_shaping; + use super::get_tunnel_manager_write_ref; use super::TunnelManager; use super::TUNNEL_MANAGER; -use crate::KI; /// contains the state for the shaper #[derive(Debug, Default, Clone)] @@ -123,7 +124,7 @@ impl TunnelManager { /// tiny little helper function for GotBloat() limit is in mbps fn set_shaping_or_error(iface: &str, limit: Option) { - if let Err(e) = KI.set_codel_shaping(iface, limit) { + if let Err(e) = set_codel_shaping(iface, limit) { error!("Failed to shape tunnel for bloat! {}", e); } } diff --git a/rita_exit/src/database/geoip.rs b/rita_exit/src/database/geoip.rs index 21ec4670b..a8b7e9aab 100644 --- a/rita_exit/src/database/geoip.rs +++ b/rita_exit/src/database/geoip.rs @@ -1,9 +1,9 @@ +use althea_kernel_interface::interface_tools::get_wg_remote_ip; use althea_types::regions::Regions; use babel_monitor::open_babel_stream; use babel_monitor::parse_routes; use ipnetwork::IpNetwork; use rita_common::utils::ip_increment::is_unicast_link_local; -use rita_common::KI; use std::collections::HashMap; use std::net::IpAddr; use std::time::Duration; @@ -34,7 +34,7 @@ pub fn get_gateway_ip_single(mesh_ip: IpAddr) -> Result Ok(match KI.get_wg_remote_ip(&route.iface) { + Some(route) => Ok(match get_wg_remote_ip(&route.iface) { Ok(a) => a, Err(e) => return Err(Box::new(e.into())), }), @@ -91,7 +91,7 @@ pub fn get_gateway_ip_bulk( gateway_ip: *remote_ip, }); } else { - match KI.get_wg_remote_ip(&route.iface) { + match get_wg_remote_ip(&route.iface) { Ok(remote_ip) => { remote_ip_cache .insert(route.iface.clone(), remote_ip); diff --git a/rita_exit/src/database/mod.rs b/rita_exit/src/database/mod.rs index 3da32b9fa..d1c83cc29 100644 --- a/rita_exit/src/database/mod.rs +++ b/rita_exit/src/database/mod.rs @@ -14,6 +14,16 @@ use crate::rita_loop::EXIT_LOOP_TIMEOUT; use crate::rita_loop::LEGACY_INTERFACE; use crate::IpAssignmentMap; use crate::RitaExitError; +use althea_kernel_interface::exit_server_tunnel::set_exit_wg_config; +use althea_kernel_interface::exit_server_tunnel::setup_individual_client_routes; +use althea_kernel_interface::exit_server_tunnel::teardown_individual_client_routes; +use althea_kernel_interface::setup_wg_if::get_last_active_handshake_time; +use althea_kernel_interface::traffic_control::create_flow_by_ip; +use althea_kernel_interface::traffic_control::create_flow_by_ipv6; +use althea_kernel_interface::traffic_control::delete_class; +use althea_kernel_interface::traffic_control::has_class; +use althea_kernel_interface::traffic_control::has_flow; +use althea_kernel_interface::traffic_control::set_class_limit; use althea_kernel_interface::ExitClient; use althea_types::regions::Regions; use althea_types::Identity; @@ -25,7 +35,6 @@ use rita_client_registration::ExitSignupReturn; use rita_common::blockchain_oracle::calculate_close_thresh; use rita_common::debt_keeper::get_debts_list; use rita_common::debt_keeper::DebtAction; -use rita_common::KI; use settings::get_rita_exit; use std::collections::HashMap; use std::collections::HashSet; @@ -366,7 +375,7 @@ pub fn setup_clients( { info!("Setting up configs for wg_exit and wg_exit_v2"); // setup all the tunnels - let exit_status = KI.set_exit_wg_config( + let exit_status = set_exit_wg_config( &wg_clients, settings::get_rita_exit().exit_network.wg_tunnel_port, &settings::get_rita_exit().network.wg_private_key_path, @@ -386,7 +395,7 @@ pub fn setup_clients( } // Setup new tunnels - let exit_status_new = KI.set_exit_wg_config( + let exit_status_new = set_exit_wg_config( &wg_clients, settings::get_rita_exit().exit_network.wg_v2_tunnel_port, &settings::get_rita_exit().network.wg_private_key_path, @@ -419,16 +428,16 @@ pub fn setup_clients( // 2.) From these timestamps, determine if client is wg exit v1 or v2 // 3.) Compare this to our datastore of previous clients we set up routes for // 4.) Set up routes for v2 or v1 based on this - let new_wg_exit_clients_timestamps: HashMap = KI - .get_last_active_handshake_time(EXIT_INTERFACE) - .expect("There should be a new wg_exit interface") - .into_iter() - .collect(); - let wg_exit_clients_timestamps: HashMap = KI - .get_last_active_handshake_time(LEGACY_INTERFACE) - .expect("There should be a wg_exit interface") - .into_iter() - .collect(); + let new_wg_exit_clients_timestamps: HashMap = + get_last_active_handshake_time(EXIT_INTERFACE) + .expect("There should be a new wg_exit interface") + .into_iter() + .collect(); + let wg_exit_clients_timestamps: HashMap = + get_last_active_handshake_time(LEGACY_INTERFACE) + .expect("There should be a wg_exit interface") + .into_iter() + .collect(); let client_list_for_setup: Vec = key_to_client_map .clone() @@ -464,7 +473,7 @@ pub fn setup_clients( // all traffic will go over wg_exit_v2 for c_key in changed_clients_return.new_v1 { if let Some(c) = key_to_client_map.get(&c_key) { - KI.setup_individual_client_routes( + setup_individual_client_routes( match get_client_internal_ip( *c, get_rita_exit().exit_network.netmask, @@ -486,7 +495,7 @@ pub fn setup_clients( } for c_key in changed_clients_return.new_v2 { if let Some(c) = key_to_client_map.get(&c_key) { - KI.teardown_individual_client_routes( + teardown_individual_client_routes( match get_client_internal_ip( *c, get_rita_exit().exit_network.netmask, @@ -641,8 +650,8 @@ pub fn enforce_exit_clients( // setup flows this allows us to classify traffic we then limit the class, we delete the class as part of unenforcment but it's difficult to delete the flows // so a user who has been enforced and unenforced while the exit has been online may already have them setup let flow_setup_required = match ( - KI.has_flow(ip, EXIT_INTERFACE), - KI.has_flow(ip, LEGACY_INTERFACE), + has_flow(ip, EXIT_INTERFACE), + has_flow(ip, LEGACY_INTERFACE), ) { (Ok(true), Ok(true)) | (Ok(true), Ok(false)) @@ -661,10 +670,10 @@ pub fn enforce_exit_clients( }; if flow_setup_required { // create ipv4 and ipv6 flows, which are used to classify traffic, we can then limit the class specifically - if let Err(e) = KI.create_flow_by_ip(LEGACY_INTERFACE, ip) { + if let Err(e) = create_flow_by_ip(LEGACY_INTERFACE, ip) { error!("Failed to setup flow for wg_exit {:?}", e); } - if let Err(e) = KI.create_flow_by_ip(EXIT_INTERFACE, ip) { + if let Err(e) = create_flow_by_ip(EXIT_INTERFACE, ip) { error!("Failed to setup flow for wg_exit_v2 {:?}", e); } // gets the client ipv6 flow for this exit specifically @@ -677,7 +686,7 @@ pub fn enforce_exit_clients( ); if let Ok(Some(client_ipv6)) = client_ipv6 { if let Err(e) = - KI.create_flow_by_ipv6(EXIT_INTERFACE, client_ipv6, ip) + create_flow_by_ipv6(EXIT_INTERFACE, client_ipv6, ip) { error!("Failed to setup ipv6 flow for wg_exit_v2 {:?}", e); } @@ -688,7 +697,7 @@ pub fn enforce_exit_clients( ) } - if let Err(e) = KI.set_class_limit( + if let Err(e) = set_class_limit( LEGACY_INTERFACE, free_tier_limit, free_tier_limit, @@ -696,7 +705,7 @@ pub fn enforce_exit_clients( ) { error!("Unable to setup enforcement class on wg_exit: {:?}", e); } - if let Err(e) = KI.set_class_limit( + if let Err(e) = set_class_limit( EXIT_INTERFACE, free_tier_limit, free_tier_limit, @@ -706,8 +715,8 @@ pub fn enforce_exit_clients( } } else { let action_required = match ( - KI.has_class(ip, LEGACY_INTERFACE), - KI.has_class(ip, EXIT_INTERFACE), + has_class(ip, LEGACY_INTERFACE), + has_class(ip, EXIT_INTERFACE), ) { (Ok(a), Ok(b)) => a | b, (Ok(a), Err(_)) => a, @@ -721,11 +730,11 @@ pub fn enforce_exit_clients( // Delete exisiting enforcement class, users who are not enforced are unclassifed becuase // leaving the class in place reduces their speeds. info!("Deleting enforcement classes for {}", client.public_key); - if let Err(e) = KI.delete_class(LEGACY_INTERFACE, ip) { + if let Err(e) = delete_class(LEGACY_INTERFACE, ip) { error!("Unable to delete class on wg_exit, is {} still enforced when they shouldnt be? {:?}", ip, e); } - if let Err(e) = KI.delete_class(EXIT_INTERFACE, ip) { + if let Err(e) = delete_class(EXIT_INTERFACE, ip) { error!("Unable to delete class on wg_exit_v2, is {} still enforced when they shouldnt be? {:?}", ip, e); } } diff --git a/rita_exit/src/operator_update/mod.rs b/rita_exit/src/operator_update/mod.rs index d8671f8ef..3c04c64d7 100644 --- a/rita_exit/src/operator_update/mod.rs +++ b/rita_exit/src/operator_update/mod.rs @@ -1,7 +1,7 @@ //! This module is responsible for checking in with the operator server and getting updated local settings pub mod update_loop; +use althea_kernel_interface::setup_wg_if::get_wg_exit_clients_online; use althea_types::OperatorExitCheckinMessage; -use rita_common::KI; use std::time::{Duration, Instant}; use crate::rita_loop::EXIT_INTERFACE; @@ -47,7 +47,7 @@ pub async fn operator_update(rita_started: Instant) { id, exit_uptime: rita_started.elapsed(), // Since this checkin works only from b20, we only need to look on wg_exit_v2 - users_online: KI.get_wg_exit_clients_online(EXIT_INTERFACE).ok(), + users_online: get_wg_exit_clients_online(EXIT_INTERFACE).ok(), }) .await; match response { diff --git a/rita_exit/src/rita_loop/mod.rs b/rita_exit/src/rita_loop/mod.rs index 032fa6136..339149dbe 100644 --- a/rita_exit/src/rita_loop/mod.rs +++ b/rita_exit/src/rita_loop/mod.rs @@ -17,6 +17,8 @@ use crate::network_endpoints::*; use crate::traffic_watcher::watch_exit_traffic; use actix_async::System as AsyncSystem; use actix_web_async::{web, App, HttpServer}; +use althea_kernel_interface::exit_server_tunnel::{one_time_exit_setup, setup_nat}; +use althea_kernel_interface::setup_wg_if::create_blank_wg_interface; use althea_kernel_interface::wg_iface_counter::WgUsage; use althea_kernel_interface::ExitClient; use althea_types::{Identity, WgKey}; @@ -24,7 +26,6 @@ use babel_monitor::{open_babel_stream, parse_routes}; use rita_client_registration::client_db::get_all_regsitered_clients; use rita_common::debt_keeper::DebtAction; use rita_common::rita_loop::get_web3_server; -use rita_common::KI; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, RwLock}; use std::thread; @@ -278,11 +279,11 @@ fn check_regions(start: Instant, clients_list: Vec) -> Option) { } info!("Total exit income of {:?} Wei this round", total_income); - match KI.get_wg_exit_clients_online(LEGACY_INTERFACE) { + match get_wg_exit_clients_online(LEGACY_INTERFACE) { Ok(users) => info!("Total of {} wg_exit users online", users), Err(e) => warn!("Getting clients failed with {:?}", e), } - match KI.get_wg_exit_clients_online(EXIT_INTERFACE) { + match get_wg_exit_clients_online(EXIT_INTERFACE) { Ok(users) => info!("Total of {} wg_exit_v2 users online", users), Err(e) => warn!("Getting clients failed with {:?}", e), } @@ -180,7 +181,7 @@ pub fn watch_exit_traffic( let id_from_ip = ret.ip_to_id; let destinations = get_babel_info(routes, our_id, id_from_ip); - let counters = match KI.read_wg_counters(LEGACY_INTERFACE) { + let counters = match read_wg_counters(LEGACY_INTERFACE) { Ok(res) => res, Err(e) => { warn!( @@ -191,7 +192,7 @@ pub fn watch_exit_traffic( } }; - let new_counters = match KI.read_wg_counters(EXIT_INTERFACE) { + let new_counters = match read_wg_counters(EXIT_INTERFACE) { Ok(res) => res, Err(e) => { warn!( diff --git a/rita_extender/src/lib.rs b/rita_extender/src/lib.rs index 59b79d40d..73e2359a0 100644 --- a/rita_extender/src/lib.rs +++ b/rita_extender/src/lib.rs @@ -5,6 +5,8 @@ pub mod dashboard; mod error; use actix_async::System as AsyncSystem; +use althea_kernel_interface::run_command; +use althea_kernel_interface::upgrade::perform_opkg; use althea_types::OpkgCommand; use althea_types::WifiSsid; use error::RitaExtenderError; @@ -14,7 +16,6 @@ use rita_client::extender::ExtenderUpdate; use rita_common::dashboard::wifi::get_wifi_config_internal; use rita_common::dashboard::wifi::set_ssid; use rita_common::dashboard::wifi::WifiInterface; -use rita_common::KI; use std::thread; use std::time::Duration; use std::time::Instant; @@ -218,7 +219,7 @@ fn apply_opkg_update_if_needed(router_version: String, extender_version: String) feed_name: "althea_extender".to_string(), arguments: common_args, }; - let res = KI.perform_opkg(opkg_update); + let res = perform_opkg(opkg_update); match res { Ok(o) => match o.status.code() { Some(0) => info!("opkg update completed successfully! {:?}", o), @@ -240,7 +241,7 @@ fn apply_opkg_update_if_needed(router_version: String, extender_version: String) packages: vec!["rita_extender".to_string()], arguments: force_maintainer, }; - let res = KI.perform_opkg(opkg_install); + let res = perform_opkg(opkg_install); match res { Ok(o) => match o.status.code() { Some(0) => info!("opkg update completed successfully! {:?}", o), @@ -258,7 +259,7 @@ fn apply_opkg_update_if_needed(router_version: String, extender_version: String) } // Restart after opkg is complete - if let Err(e) = KI.run_command("/etc/init.d/rita_extender", &["restart"]) { + if let Err(e) = run_command("/etc/init.d/rita_extender", &["restart"]) { error!("Unable to restart rita extender after opkg update: {}", e) } } diff --git a/settings/src/lib.rs b/settings/src/lib.rs index ea3496820..34077df74 100644 --- a/settings/src/lib.rs +++ b/settings/src/lib.rs @@ -17,7 +17,7 @@ extern crate serde_derive; extern crate log; extern crate arrayvec; -use althea_kernel_interface::KI; +use althea_kernel_interface::netns::check_integration_test_netns; use althea_types::{BillingDetails, ContactType, Identity, InstallationDetails, SystemChain}; use clarity::Address; use logging::LoggingSettings; @@ -111,7 +111,7 @@ pub trait WrappedSettingsAdaptor { // Doing so will disable local reads/writes and instead call the adaptor's relevant fns // Can only be called once if no other settings exist in the SETTINGS global pub fn set_adaptor(adaptor: T) { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); let mut settings_ref = SETTINGS.write().unwrap(); match settings_ref.contains_key(&netns) { // make sure this only gets called once on start @@ -156,7 +156,7 @@ impl RitaSettings { /// write the current SETTINGS from memory to file pub fn write_config() -> Result<(), SettingsError> { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); match SETTINGS.read().unwrap().get(&netns) { Some(Settings::Adaptor(adapt)) => adapt.adaptor.write_config(), Some(Settings::Client(settings)) => { @@ -191,7 +191,7 @@ pub fn save_settings_on_shutdown() { /// get a JSON value of all settings pub fn get_config_json() -> Result { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); match SETTINGS.read().unwrap().get(&netns) { Some(Settings::Adaptor(adapt)) => adapt.adaptor.get_config_json(), Some(Settings::Client(settings)) => settings.get_all(), @@ -202,7 +202,7 @@ pub fn get_config_json() -> Result { /// merge a json of a subset of settings into global settings pub fn merge_config_json(changed_settings: serde_json::Value) -> Result<(), SettingsError> { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); let mut settings_ref = SETTINGS.write().unwrap(); let settings_ref = settings_ref.get_mut(&netns); match settings_ref { @@ -217,7 +217,7 @@ pub fn merge_config_json(changed_settings: serde_json::Value) -> Result<(), Sett /// Does not currently save the identity paramater, as we don't /// need to modify that in a generic context. pub fn set_rita_common(input: RitaSettings) { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); let mut settings_ref = SETTINGS.write().unwrap(); match settings_ref.get_mut(&netns) { Some(Settings::Adaptor(adapt)) => { @@ -249,7 +249,7 @@ pub fn set_rita_common(input: RitaSettings) { /// get the current settings and extract generic RitaSettings from it pub fn get_rita_common() -> RitaSettings { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); match SETTINGS.read().unwrap().get(&netns) { Some(Settings::Adaptor(adapt)) => { let settings = adapt.adaptor.get_client().unwrap(); @@ -284,19 +284,19 @@ pub fn get_git_hash() -> String { } pub fn set_flag_config(flag_config: PathBuf) { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); FLAG_CONFIG.write().unwrap().insert(netns, flag_config); } pub fn get_flag_config() -> PathBuf { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); FLAG_CONFIG.read().unwrap().get(&netns).unwrap().clone() } /// set client settings into local or adaptor memory /// panics if called on exit settings pub fn set_rita_client(client_setting: RitaClientSettings) { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); let mut settings_ref = SETTINGS.write().unwrap(); match settings_ref.get(&netns) { // if there's an adaptor already saved, then use it to set there @@ -317,7 +317,7 @@ pub fn set_rita_client(client_setting: RitaClientSettings) { /// get client settings from local or adaptor memory /// panics if called on exit settings pub fn get_rita_client() -> RitaClientSettings { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); match SETTINGS.read().unwrap().get(&netns) { Some(Settings::Adaptor(adapt)) => adapt.adaptor.get_client().unwrap(), Some(Settings::Client(settings)) => settings.clone(), @@ -357,7 +357,7 @@ pub fn get_system_chain() -> SystemChain { /// Set exit settings into memory pub fn set_rita_exit(exit_setting: RitaExitSettingsStruct) { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); SETTINGS .write() .unwrap() @@ -366,7 +366,7 @@ pub fn set_rita_exit(exit_setting: RitaExitSettingsStruct) { /// Retrieve exit settings from memory pub fn get_rita_exit() -> RitaExitSettingsStruct { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); let temp = SETTINGS.read().unwrap(); let temp = temp.get(&netns); if let Some(Settings::Exit(val)) = temp { @@ -378,7 +378,7 @@ pub fn get_rita_exit() -> RitaExitSettingsStruct { /// This code checks to see if the current device/setting is client or not pub fn check_if_client() -> bool { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); match SETTINGS.read().unwrap().get(&netns) { Some(Settings::Adaptor(_)) => false, Some(Settings::Client(_)) => true, @@ -389,7 +389,7 @@ pub fn check_if_client() -> bool { /// This code checks to see if the current device/setting is an exit or not pub fn check_if_exit() -> bool { - let netns = KI.check_integration_test_netns(); + let netns = check_integration_test_netns(); match SETTINGS.read().unwrap().get(&netns) { Some(Settings::Adaptor(_)) => false, Some(Settings::Client(_)) => false, From c5d79039b91bb337942a01728f6356bd6bc3a41e Mon Sep 17 00:00:00 2001 From: Justin Kilpatrick Date: Fri, 11 Oct 2024 23:25:06 -0400 Subject: [PATCH 2/4] Fix Identity decoding error Fuzzer actually found a problem, so that's nice. --- althea_types/src/identity.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/althea_types/src/identity.rs b/althea_types/src/identity.rs index 833da8362..155ba3c56 100644 --- a/althea_types/src/identity.rs +++ b/althea_types/src/identity.rs @@ -188,7 +188,13 @@ impl Identity { // 3rd entry is the eth address index += 1; let eth_address: Address = match Address::from_slice(match byte_chunks.get(index) { - Some(a) => &a[12..], + Some(a) => { + if a.len() < 32 { + return Err(AltheaTypesError::BadEthAbiInput(format!( + "Cant eth address with byte chunks {byte_chunks:?}" + ))); + } + &a[12..]}, None => { return Err(AltheaTypesError::BadEthAbiInput(format!( "Cant eth address with byte chunks {byte_chunks:?}" From 5da250d8687c4d4c0257851e4932c193539bd9b7 Mon Sep 17 00:00:00 2001 From: Justin Kilpatrick Date: Fri, 11 Oct 2024 23:48:13 -0400 Subject: [PATCH 3/4] Tag specific tests to run serially This commit adds a new test dependency that allows us to tag which tests need to be run serially versus in parallel. This lets us parallelize the rest of our test suite and run serially only those tests that must be run serially. This dramatically speeds up the tests because we have a number of fuzzing and other longer run tests that take a few minutes. --- .github/workflows/rust.yml | 2 +- Cargo.lock | 41 +++++++++++++++++ rita_common/Cargo.toml | 1 + rita_common/src/debt_keeper/mod.rs | 17 +++++++ rita_common/src/payment_validator/mod.rs | 2 + rita_common/src/usage_tracker/tests.rs | 58 ++---------------------- scripts/test.sh | 2 +- 7 files changed, 66 insertions(+), 57 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d061e910f..961825975 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v2 - uses: Swatinem/rust-cache@v2 - name: Run Rita and Rita Exit tests - run: RUST_TEST_THREADS=1 cargo test --verbose --all + run: cargo test --verbose --all rustfmt: needs: check runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 300166b20..066ad94a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3159,6 +3159,7 @@ dependencies = [ "serde_cbor", "serde_derive", "serde_json", + "serial_test", "settings", "sha3", "web30", @@ -3417,6 +3418,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scc" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553f8299af7450cda9a52d3a370199904e7a46b5ffd1bef187c4a6af3bb6db69" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.24" @@ -3451,6 +3461,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sdd" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" + [[package]] name = "seahash" version = "4.1.0" @@ -3567,6 +3583,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" +dependencies = [ + "futures 0.3.30", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "settings" version = "0.1.0" diff --git a/rita_common/Cargo.toml b/rita_common/Cargo.toml index 11c307f6a..69c2b3514 100644 --- a/rita_common/Cargo.toml +++ b/rita_common/Cargo.toml @@ -53,6 +53,7 @@ features = ["std"] [dev-dependencies] env_logger = "0.11" +serial_test = "3.1.1" [features] development = [] diff --git a/rita_common/src/debt_keeper/mod.rs b/rita_common/src/debt_keeper/mod.rs index 1478fa0e9..3b2772d9b 100755 --- a/rita_common/src/debt_keeper/mod.rs +++ b/rita_common/src/debt_keeper/mod.rs @@ -820,6 +820,7 @@ mod tests { use super::*; use rand::Rng; + use serial_test::serial; use settings::client::RitaClientSettings; fn get_test_identity() -> Identity { @@ -855,6 +856,7 @@ mod tests { } #[test] + #[serial] fn test_single_suspend() { settings::set_rita_client(RitaClientSettings::default()); let mut client = settings::get_rita_client(); @@ -874,6 +876,7 @@ mod tests { } #[test] + #[serial] fn test_single_overpay() { settings::set_rita_client(RitaClientSettings::default()); let mut client = settings::get_rita_client(); @@ -894,6 +897,7 @@ mod tests { } #[test] + #[serial] fn test_single_pay() { settings::set_rita_client(RitaClientSettings::default()); let mut common = settings::get_rita_common(); @@ -921,6 +925,7 @@ mod tests { } #[test] + #[serial] fn test_single_pay_limited() { settings::set_rita_client(RitaClientSettings::default()); let mut common = settings::get_rita_common(); @@ -944,6 +949,7 @@ mod tests { } #[test] + #[serial] fn test_single_reopen() { settings::set_rita_client(RitaClientSettings::default()); let mut client = settings::get_rita_client(); @@ -969,6 +975,7 @@ mod tests { } #[test] + #[serial] fn test_multi_pay() { settings::set_rita_client(RitaClientSettings::default()); let mut common = settings::get_rita_common(); @@ -994,6 +1001,7 @@ mod tests { } #[test] + #[serial] fn test_multi_pay_lmited() { settings::set_rita_client(RitaClientSettings::default()); let mut common = settings::get_rita_common(); @@ -1019,6 +1027,7 @@ mod tests { } #[test] + #[serial] fn test_multi_fail() { settings::set_rita_client(RitaClientSettings::default()); let mut client = settings::get_rita_client(); @@ -1042,6 +1051,7 @@ mod tests { } #[test] + #[serial] fn test_multi_reopen() { settings::set_rita_client(RitaClientSettings::default()); let mut client = settings::get_rita_client(); @@ -1071,6 +1081,7 @@ mod tests { } #[test] + #[serial] fn test_credit_reopen() { settings::set_rita_client(RitaClientSettings::default()); let mut common = settings::get_rita_common(); @@ -1109,6 +1120,7 @@ mod tests { } #[test] + #[serial] fn test_credit_reopen_limited() { let mut common = settings::get_rita_common(); common.payment.payment_threshold = 10.into(); @@ -1149,6 +1161,7 @@ mod tests { } #[test] + #[serial] fn test_payment_fail() { settings::set_rita_client(RitaClientSettings::default()); let mut common = settings::get_rita_common(); @@ -1245,6 +1258,7 @@ mod tests { } #[test] + #[serial] fn test_payment_fail_limited() { settings::set_rita_client(RitaClientSettings::default()); @@ -1343,6 +1357,7 @@ mod tests { } #[test] + #[serial] fn test_debts_saving() { settings::set_rita_client(RitaClientSettings::default()); let mut test_they_owe = NodeDebtData::new(); @@ -1394,6 +1409,7 @@ mod tests { } #[test] + #[serial] fn test_saving_debts_to_file() { let mut debt_data: DebtData = HashMap::new(); let id = Identity { @@ -1481,6 +1497,7 @@ mod tests { } #[test] + #[serial] fn test_normalize_payment_amount() { // this is $6 in a 6 decimal of precision token where 1 unit = $1 let start_amount = Uint256::from(6_000_000u64); diff --git a/rita_common/src/payment_validator/mod.rs b/rita_common/src/payment_validator/mod.rs index 768945673..5466b6a23 100644 --- a/rita_common/src/payment_validator/mod.rs +++ b/rita_common/src/payment_validator/mod.rs @@ -900,6 +900,7 @@ mod tests { use althea_types::identity::random_identity; use num256::Int256; use num_traits::Num; + use serial_test::serial; use settings::client::RitaClientSettings; fn generate_fake_payment(from_id: Identity) -> ToValidate { @@ -933,6 +934,7 @@ mod tests { /// tests the remove behaivor of payment validator, ensuring that transactions /// are successfully removed, and that they are moved to the correct list #[test] + #[serial] fn test_remove() { let mut validator = PaymentValidator::new(); let our_id = random_identity(); diff --git a/rita_common/src/usage_tracker/tests.rs b/rita_common/src/usage_tracker/tests.rs index d109e026f..d575ac967 100644 --- a/rita_common/src/usage_tracker/tests.rs +++ b/rita_common/src/usage_tracker/tests.rs @@ -1,5 +1,6 @@ #[warn(clippy::module_inception)] #[allow(unused)] +#[cfg(test)] pub mod test { use crate::usage_tracker::structs::{ @@ -18,34 +19,16 @@ pub mod test { use flate2::write::ZlibEncoder; use flate2::Compression; use rand::{thread_rng, Rng}; + use serial_test::serial; use settings::client::RitaClientSettings; use settings::{get_rita_common, set_rita_client, set_rita_common}; use std::collections::{HashMap, HashSet, VecDeque}; use std::convert::TryInto; use std::fs::File; use std::io::Write; - #[cfg(test)] - impl UsageTrackerStorage { - // previous implementation of save which uses the old struct to serialize - fn save2(&self) -> Result<(), RitaCommonError> { - let old_struct = UsageTrackerStorageOld { - last_save_hour: self.last_save_hour, - client_bandwidth: convert_map_to_flat_usage_data(self.client_bandwidth.clone()), - relay_bandwidth: convert_map_to_flat_usage_data(self.relay_bandwidth.clone()), - exit_bandwidth: convert_map_to_flat_usage_data(self.exit_bandwidth.clone()), - payments: convert_payment_set_to_payment_hour(self.payments.clone()), - }; - let serialized = bincode::serialize(&old_struct)?; - let mut file = File::create(settings::get_rita_common().network.usage_tracker_file)?; - let buffer: Vec = Vec::new(); - let mut encoder = ZlibEncoder::new(buffer, Compression::default()); - encoder.write_all(&serialized)?; - let compressed_bytes = encoder.finish()?; - Ok(file.write_all(&compressed_bytes)?) - } - } #[test] + #[serial] fn save_usage_tracker_bincode() { let rset = RitaClientSettings::new("../settings/test.toml").unwrap(); let rset = RitaClientSettings::default(); @@ -64,41 +47,6 @@ pub mod test { assert_eq!(dummy_usage_tracker, res2); } - #[test] - fn convert_legacy_usage_tracker() { - //env_logger::init(); - // make a dummy usage tracker instance - // save it as gzipped json ( pull code from the git history that you deleted and put it in this test) - // makes sure the file exists - // deserialize the file using the upgrade function - // make sure it's equal to the original dummy we made - let rset = RitaClientSettings::default(); - set_rita_client(rset); - let mut newrc = get_rita_common(); - newrc.network.usage_tracker_file = "/tmp/usage_tracker.bincode".to_string(); - set_rita_common(newrc); - info!("Generating large usage tracker history"); - let dummy_usage_tracker = generate_dummy_usage_tracker(); - - info!("Saving test data in old format"); - dummy_usage_tracker.save2().unwrap(); - - info!("Loading test data from oldformat"); - let mut res2 = UsageTrackerStorage::load_from_disk(); - - // Saving res2 with the new save() and updated usage_tracker_file in order to end with - // a .bincode file from the old format bincode file. - res2.save().unwrap(); - info!("Saving test data as bincode"); - let res4 = UsageTrackerStorage::load_from_disk(); - info!("Loading test data from bincode"); - - // use == to avoid printing out the compared data - // when it failed, as it is enormous - assert!(dummy_usage_tracker == res2); - assert!(res2 == res4); - } - /// tests that the flat conversion comes out in the right order (increasing usage hours) #[test] fn check_flat_conversion() { diff --git a/scripts/test.sh b/scripts/test.sh index b29514a6e..7206cadd5 100644 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -10,7 +10,7 @@ cargo clippy --all --all-targets --all-features -- -D warnings cargo fmt --all -- --check # test rita only on many architectures -CROSS_TEST_ARGS="--verbose --all -- --test-threads=1" +CROSS_TEST_ARGS="--verbose --all --" cross test --target x86_64-unknown-linux-musl $CROSS_TEST_ARGS cross test --target mips-unknown-linux-gnu $CROSS_TEST_ARGS cross test --target mipsel-unknown-linux-gnu $CROSS_TEST_ARGS From 11b7421e29915cb8907e4f3561fe1ec2d8bd02df Mon Sep 17 00:00:00 2001 From: Justin Kilpatrick Date: Fri, 11 Oct 2024 23:49:56 -0400 Subject: [PATCH 4/4] Rustfmt --- althea_types/src/identity.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/althea_types/src/identity.rs b/althea_types/src/identity.rs index 155ba3c56..93eb2b90a 100644 --- a/althea_types/src/identity.rs +++ b/althea_types/src/identity.rs @@ -194,7 +194,8 @@ impl Identity { "Cant eth address with byte chunks {byte_chunks:?}" ))); } - &a[12..]}, + &a[12..] + } None => { return Err(AltheaTypesError::BadEthAbiInput(format!( "Cant eth address with byte chunks {byte_chunks:?}"