157 lines
5.1 KiB
Rust
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!");
|
|
}
|
|
}
|