mirror of
https://github.com/pradt2/always-online-stun.git
synced 2024-05-03 09:24:56 +08:00
120 lines
4.9 KiB
Rust
120 lines
4.9 KiB
Rust
use std::cell::RefCell;
|
|
use std::io;
|
|
use std::rc::Rc;
|
|
use std::time::Duration;
|
|
use futures::StreamExt;
|
|
use tokio::time::Instant;
|
|
use crate::utils::join_all_with_semaphore;
|
|
use crate::outputs::{ValidHosts, ValidIpV4s, ValidIpV6s};
|
|
use crate::servers::StunServer;
|
|
use crate::stun::{StunServerTestResult, StunSocketResponse};
|
|
|
|
extern crate pretty_env_logger;
|
|
#[macro_use] extern crate log;
|
|
|
|
mod servers;
|
|
mod stun;
|
|
mod utils;
|
|
mod outputs;
|
|
mod geoip;
|
|
|
|
const CONCURRENT_SOCKETS_USED_LIMIT: usize = 64;
|
|
|
|
#[tokio::main(flavor = "current_thread")]
|
|
async fn main() -> io::Result<()> {
|
|
pretty_env_logger::init();
|
|
|
|
let client = Rc::new(RefCell::new(geoip::CachedIpGeolocationIpClient::default().await?));
|
|
|
|
let stun_servers = servers::get_stun_servers().await?;
|
|
|
|
let stun_servers_count = stun_servers.len();
|
|
info!("Loaded {} stun server hosts", stun_servers.len());
|
|
|
|
let stun_server_test_results = stun_servers.into_iter()
|
|
.map(|candidate| {
|
|
async move {
|
|
let test_result = stun::test_udp_stun_server(candidate).await;
|
|
print_stun_server_status(&test_result);
|
|
test_result
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let timestamp = Instant::now();
|
|
let stun_server_test_results = join_all_with_semaphore(stun_server_test_results.into_iter(), CONCURRENT_SOCKETS_USED_LIMIT).await;
|
|
|
|
ValidHosts::default(&stun_server_test_results).save().await?;
|
|
ValidIpV4s::default(&stun_server_test_results).save().await?;
|
|
ValidIpV6s::default(&stun_server_test_results).save().await?;
|
|
|
|
write_stun_server_summary(stun_servers_count, &stun_server_test_results,timestamp.elapsed());
|
|
|
|
futures::stream::iter(stun_server_test_results.iter())
|
|
.filter_map(|test_result| async move { if test_result.is_healthy() { Some(test_result) } else { None } })
|
|
.map(|test_result| futures::stream::iter(test_result.socket_tests.iter()))
|
|
.flatten()
|
|
.map(|test_result| test_result.socket)
|
|
.for_each(|socket| {
|
|
let client = client.clone();
|
|
async move {
|
|
client.borrow_mut().get_ip_geoip_info(socket.ip()).await.expect("GeoIP IP info must be available");
|
|
}
|
|
}).await;
|
|
|
|
client.borrow_mut().save().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn print_stun_server_status(test_result: &StunServerTestResult) {
|
|
if test_result.is_healthy() {
|
|
info!("{:<25} -> Host is healthy", test_result.server.hostname);
|
|
} else if !test_result.is_resolvable() {
|
|
info!("{:<25} -> Host is not resolvable", test_result.server.hostname);
|
|
} else if test_result.is_partial_timeout() {
|
|
info!("{:<25} -> Host times out on some sockets", test_result.server.hostname);
|
|
} else if test_result.is_timeout() {
|
|
info!("{:<25} -> Host times out on all sockets", test_result.server.hostname);
|
|
} else {
|
|
info!("{:<25} -> Host behaves in an unexpected way. Run with RUST_LOG=DEBUG for more info", test_result.server.hostname);
|
|
for socket_test in &test_result.socket_tests {
|
|
match &socket_test.result {
|
|
StunSocketResponse::HealthyResponse { .. } => { debug!("{:<25} -> Socket {:<21} returned a healthy response", test_result.server.hostname, socket_test.socket) }
|
|
StunSocketResponse::InvalidMappingResponse { expected, actual, rtt } => { debug!("{:<25} -> Socket {:<21} returned an invalid mapping: expected={} actual={}", test_result.server.hostname, socket_test.socket, expected, actual) }
|
|
StunSocketResponse::Timeout { deadline } => { debug!("{:<25} -> Socket {:<21} timed out after {:?}", test_result.server.hostname, socket_test.socket, deadline) }
|
|
StunSocketResponse::UnexpectedError { err } => { debug!("{:<25} -> Socket {:<21} returned an unexpected error: {}", test_result.server.hostname, socket_test.socket, err) }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_stun_server_summary(candidate_hosts_count: usize, results: &Vec<StunServerTestResult>, time_taken: Duration) {
|
|
let mut healthy = 0;
|
|
let mut dns_unresolved = 0;
|
|
let mut partial_timeout = 0;
|
|
let mut timeout = 0;
|
|
let mut unexpected_err = 0;
|
|
results.iter().for_each(|server_test_result| {
|
|
if server_test_result.is_healthy() {
|
|
healthy += 1;
|
|
} else if !server_test_result.is_resolvable() {
|
|
dns_unresolved += 1;
|
|
} else if server_test_result.is_partial_timeout() {
|
|
partial_timeout += 1;
|
|
} else if server_test_result.is_timeout() {
|
|
timeout += 1;
|
|
} else {
|
|
unexpected_err += 1;
|
|
}
|
|
});
|
|
info!(
|
|
"Statistics -> Tested={}, Healthy={}, DNS failure={}, partial Timeout={}, Timeout={}, Unexpected err={}. Finished in {:?}",
|
|
candidate_hosts_count, healthy, dns_unresolved, partial_timeout, timeout, unexpected_err, time_taken
|
|
);
|
|
|
|
if healthy == 0 {
|
|
warn!("No healthy hosts found! Are you behind NAT?")
|
|
}
|
|
}
|