diff --git a/.gitignore b/.gitignore index ea8c4bf..4a3b37d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +config.toml diff --git a/Cargo.lock b/Cargo.lock index 8cc6f3e..b9bb0c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + [[package]] name = "bytes" version = "1.12.0" @@ -162,7 +168,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "defmt-macros", ] @@ -217,6 +223,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -309,12 +325,27 @@ dependencies = [ "syn", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.33" @@ -384,6 +415,29 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -451,6 +505,15 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.0", +] + [[package]] name = "regex" version = "1.12.4" @@ -527,6 +590,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.228" @@ -572,6 +641,22 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "socket2" version = "0.6.4" @@ -634,7 +719,9 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", @@ -735,9 +822,12 @@ dependencies = [ "camino", "clap", "env_logger", + "lazy_static", "log", "mikrotik-rs", + "regex", "serde", + "tokio", "toml", ] diff --git a/Cargo.toml b/Cargo.toml index 34d110c..9a5db20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,10 @@ anyhow = "1.0.103" camino = { version = "1.2.4", features = ["serde1"] } clap = { version = "4.6.1", features = ["derive"] } env_logger = "0.11.11" +lazy_static = "1.5.0" log = "0.4.33" mikrotik-rs = { version = "0.8.0", features = ["tokio-tls"] } +regex = "1.12.4" serde = { version = "1.0.228", features = ["derive"] } +tokio = { version = "1.52.3", features = ["full"] } toml = "1.1.2" diff --git a/src/config/mod.rs b/src/config/mod.rs index d156062..2db1162 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -25,7 +25,7 @@ pub struct ConfigDefaultLogins { pub password: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct ConfigHost { pub name: String, pub ip: IpAddr, diff --git a/src/mt_commander/mod.rs b/src/mt_commander/mod.rs index e69de29..01509f5 100644 --- a/src/mt_commander/mod.rs +++ b/src/mt_commander/mod.rs @@ -0,0 +1,126 @@ +use core::panic; +use std::{ + collections::HashMap, + hash::Hash, + sync::{Arc, Mutex}, +}; + +use mikrotik_rs::{Command, CommandBuilder, Event, MikrotikDevice}; +use regex::Regex; + +use crate::config::{Config, ConfigHost}; + +lazy_static::lazy_static! { + static ref MTC: Arc> = MtCommanderInternal::new_mutex(); +} + +pub struct MtCommander; +struct MtCommanderInternal { + cfg: Option, +} + +impl MtCommander { + pub fn init(cfg: Config) -> anyhow::Result<()> { + let mut mtc = MTC.lock().map_err(|e| anyhow::anyhow!(e.to_string()))?; + mtc.cfg = Some(cfg); + Ok(()) + } + + pub async fn run_command_on_hosts( + hosts: &[ConfigHost], + command: &str, + ) -> anyhow::Result> { + let mut res_map = HashMap::new(); + for host in hosts { + let res = Self::run_command_on_host(host, command).await?; + res_map.insert(host.clone(), res); + } + Ok(res_map) + } + pub async fn run_command_on_host(host: &ConfigHost, command: &str) -> anyhow::Result { + let mtc = MTC.lock().map_err(|e| anyhow::anyhow!(e.to_string()))?; + mtc.run_command(host, &command).await + } + + pub async fn run_command_on_hosts_and_parse_re( + hosts: &[ConfigHost], + command: &str, + parse_re: &Regex, + ) -> anyhow::Result>> { + let mut res_map = HashMap::new(); + for host in hosts { + let res = Self::run_command_on_host_and_parse_re(host, command, parse_re).await?; + res_map.insert(host.clone(), res); + } + Ok(res_map) + } + + pub async fn run_command_on_host_and_parse_re( + host: &ConfigHost, + command: &str, + parse_re: &Regex, + ) -> anyhow::Result> { + let mtc = MTC.lock().map_err(|e| anyhow::anyhow!(e.to_string()))?; + let res = mtc.run_command(host, &command).await?; + let out = parse_re + .find_iter(&res) + .map(|s| s.as_str().to_string()) + .collect(); + Ok(out) + } +} + +impl MtCommanderInternal { + fn new(cfg: Config) -> Self { + Self { cfg: Some(cfg) } + } + fn new_mutex() -> Arc> { + Arc::new(Mutex::new(Self { cfg: None })) + } + fn init(&mut self, cfg: Config) -> anyhow::Result<()> { + self.cfg = Some(cfg); + self.spawn_watcher_thread()?; + Ok(()) + } + fn cfg(&self) -> &Config { + if let Some(cfg) = &self.cfg { + cfg + } else { + panic!(); + } + } + fn spawn_watcher_thread(&mut self) -> anyhow::Result<()> { + async fn watcher() {} + + tokio::spawn(watcher()); + Ok(()) + } + async fn run_command(&self, host: &ConfigHost, cmd: &str) -> anyhow::Result { + let dev = MikrotikDevice::connect( + format!("{}", &host.ip), + &host.username(self.cfg()), + Some(&host.password(self.cfg())), + ) + .await?; + + let mut rx = dev + .send_command(CommandBuilder::new().command(cmd).build()) + .await?; + + let mut res = String::new(); + + while let Some(event) = rx.recv().await { + match event { + Event::Reply { response, .. } => { + log::debug!("({}) {:?}", &host.ip, response.attributes); + } + Event::Done { .. } => break, + Event::Trap { response, .. } => { + log::error!("({}) {:?}", &host.ip, response.message); + } + other => println!("{other:?}"), + } + } + Ok(res) + } +}