rustocat/src/main.rs

157 lines
5.1 KiB
Rust

mod config;
mod listener;
mod shutdown;
mod tcp;
mod udp;
#[cfg(feature = "consul")]
mod consul;
use log::{debug, error, info, warn};
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::sync::Arc;
use tokio::sync::{broadcast, RwLock};
pub type Result<T> = std::result::Result<T, Box<dyn Error>>;
// #[derive(Debug)]
struct ActiveListener {
notify_shutdown: broadcast::Sender<()>,
targets: Arc<RwLock<Vec<String>>>,
}
#[tokio::main]
async fn main() -> Result<()> {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
record.level(),
message
))
})
// Add blanket level filter -
.level(log::LevelFilter::Info)
.level_for("rustocat", log::LevelFilter::Debug)
.chain(std::io::stdout())
.apply()?;
let mut listeners: HashMap<String, ActiveListener> = HashMap::new();
let text_config_provider = config::FileConfigProvider::new();
let mut target_provider: Box<dyn config::ConfigProvider> = {
#[cfg(feature = "consul")]
{
let cfg = text_config_provider.load_config()?;
if cfg.consul.is_some() && cfg.consul.unwrap() {
let consul_config_provider = consul::ConsulConfigProvider::new();
Box::new(consul_config_provider)
} else {
Box::new(text_config_provider)
}
}
#[cfg(not(feature = "consul"))]
Box::new(text_config_provider)
};
loop {
let mappings = target_provider.get_targets().await?;
let mut required_listeners: HashSet<String> = HashSet::new();
for target in mappings {
let mut source_str = "".to_owned();
if target.udp == None || target.udp == Some(false) {
source_str.push_str("udp:");
} else {
source_str.push_str("tcp:");
}
source_str.push_str(&target.source);
required_listeners.insert(source_str.clone());
if let Some(listener) = listeners.get(&source_str) {
let mut invalid = false;
let targets = listener.targets.read().await;
for t in &target.targets {
if !targets.iter().any(|e| *e == *t) {
invalid = true;
break;
}
}
if !invalid {
for t in targets.iter() {
if !target.targets.iter().any(|e| *e == *t) {
invalid = true;
break;
}
}
}
if invalid {
warn!("Found invalid targets! Adjusting!");
let mut w = listener.targets.write().await;
*w = target.targets;
}
} else {
let (notify_shutdown, _) = broadcast::channel(1);
let listener = ActiveListener {
notify_shutdown: notify_shutdown,
targets: Arc::new(RwLock::new(target.targets)),
};
let l = listener::Listener {
shutdown: shutdown::ShutdownReceiver::new(listener.notify_shutdown.subscribe()),
source: target.source.clone(),
targets: listener.targets.clone(),
};
tokio::spawn(async move {
if target.udp == None || target.udp == Some(false) {
if let Err(err) = tcp::start_tcp_listener(l).await {
error!("tcp listener error: {}", err);
}
} else {
if let Err(err) = udp::start_udp_listener(l).await {
error!("udp listener error {}: {}", target.source, err);
}
}
});
listeners.insert(source_str, listener);
}
}
let to_delete: Vec<_> = listeners
.keys()
.filter(|x| required_listeners.get(*x).is_none())
.cloned() // Clone the result to make listeners mutable again!
.collect();
for del_key in to_delete {
if let Some(listener) = listeners.get(&del_key) {
let res = listener.notify_shutdown.send(()); //Errors are irrelevant here. I guess....
match res {
Ok(_) => {
info!("Sent shutdown signal!");
}
Err(_) => {
warn!("Failed to send shutdown signal!");
}
}
debug!("Removing listener!");
listeners.remove(&del_key);
}
}
target_provider.wait_for_change().await?;
info!("Recevied SIGHUP, reloading config!");
}
}