durnitisp/src/main.rs

164 lines
5.8 KiB
Rust

// Copyright 2020 Jeremy Wall
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::convert::Into;
use std::sync::Arc;
use gflags;
use metrics_exporter_prometheus;
use nursery::thread;
use nursery::{Nursery, Waitable};
use tiny_http;
use tracing::{debug, error, info, instrument, Level};
use tracing_subscriber::FmtSubscriber;
mod icmp;
mod stun;
mod util;
gflags::define! {
/// Print this help text
-h, --help = false
}
gflags::define! {
/// Port to listen on for exporting variables prometheus style
--listenHost = "0.0.0.0:8080"
}
gflags::define! {
/// Enable debug logging
--debug = false
}
gflags::define! {
/// Enable trace logging
--trace = false
}
gflags::define! {
/// Comma separated list of hosts to ping
--pingHosts = "google.com"
}
gflags::define! {
/// Comma separated list of hosts to ping
--stunHosts = "stun.l.google.com:19302,stun.ekiga.net:3478,stun.xten.com:3478,stun.ideasip.com:3478,stun.rixtelecom.se:3478,stun.schlund.de:3478,stun.softjoys.com:3478,stun.stunprotocol.org:3478,stun.voiparound.com:3478,stun.voipbuster.com:3478,stun.voipstunt.com:3478,stun1.noc.ams-ix.net:3478"
}
#[instrument]
fn main() -> anyhow::Result<()> {
gflags::parse();
let stun_servers: Vec<&str> = STUNHOSTS.flag.split(",").collect();
if HELP.flag {
println!("durnitisp <options> <list of hostname:port>");
println!("");
println!("The hostname and port are expected to be for a valid stun server.");
println!("You can put as many of them as you want after the options.");
println!("");
println!("FLAGS:");
gflags::print_help_and_exit(0);
}
let subscriber_builder = if DEBUG.flag {
FmtSubscriber::builder()
// all spans/events with a level higher than debug
// will be written to stdout.
.with_max_level(Level::DEBUG)
} else if TRACE.flag {
FmtSubscriber::builder()
// all spans/events with a level will be written to stdout.
.with_max_level(Level::TRACE)
} else {
FmtSubscriber::builder()
// all spans/events with a level higher than info (e.g, error, info, warn, etc.)
// will be written to stdout.
.with_max_level(Level::INFO)
};
tracing::subscriber::set_global_default(subscriber_builder.finish())
.expect("setting default subscriber failed");
let ping_hosts: Vec<&str> = PINGHOSTS.flag.split(",").collect();
let builder = metrics_exporter_prometheus::PrometheusBuilder::new();
let prom_handle = builder
.install_recorder()
.expect("Failed to install prometheus exporter");
// Create a Registry and register metrics.
let stun_socket_addrs = util::resolve_socket_addrs(&stun_servers).unwrap();
let stun_servers = Arc::new(stun_servers);
let ping_hosts = Arc::new(ping_hosts);
let mut parent = Nursery::new();
// First we start the render thread.
{
// Introduce a new scope for our Arc to clone before moving it into the thread.
// thread::Handle starts the thread immediately so the render thread will usually start first.
let render_thread = thread::Handle::new(move || {
debug!(listenhost = LISTENHOST.flag, "attempting to start server");
let server = match tiny_http::Server::http(LISTENHOST.flag) {
Ok(server) => server,
Err(err) => {
error!(
?err,
"Error starting render thread. Shutting down all thread.",
);
std::process::exit(1);
}
};
info!(
listenthost = LISTENHOST.flag,
"Listening for metrics request on"
);
loop {
info!("Waiting for request");
match server.recv() {
Ok(req) => {
let response = tiny_http::Response::from_data(prom_handle.render())
.with_status_code(200);
if let Err(e) = req.respond(response) {
error!(err = ?e, "Error responding to request");
}
}
Err(e) => {
error!(err = ?e, "Invalid http request!");
}
}
}
});
parent.adopt(Box::new(render_thread));
}
{
icmp::schedule_echo_server(&ping_hosts, &mut parent);
}
// Then we attempt to start connections to each stun server.
for (i, s) in stun_socket_addrs.iter().enumerate() {
let stun_servers_copy = stun_servers.clone();
if let Some(s) = s.clone() {
let domain_name = *stun_servers_copy.get(i).unwrap();
let connect_thread =
thread::Pending::new(move || stun::start_listen_thread(domain_name, s.into()));
parent.schedule(Box::new(connect_thread));
// Spread the probe threads out so they're somewhat uniformly distributed.
std::thread::sleep(std::time::Duration::from_micros(
stun::delay_secs() * 1000000 / (stun_socket_addrs.len() as u64),
))
};
}
// Blocks forever
parent.wait();
Ok(())
}