always-online-stun/src/stun.rs

188 lines
6.1 KiB
Rust
Raw Normal View History

2022-04-10 22:28:37 +08:00
use std::net::{SocketAddr};
2021-11-17 20:40:23 +08:00
use std::time::{Duration, Instant};
2022-04-10 22:28:37 +08:00
use crate::utils::join_all_with_semaphore;
use crate::StunServer;
#[derive(Clone, Debug)]
pub(crate) struct StunServerTestResult {
pub(crate) server: StunServer,
pub(crate) socket_tests: Vec<StunSocketTestResult>,
2021-11-17 20:40:23 +08:00
}
2022-04-10 22:28:37 +08:00
impl StunServerTestResult {
pub(crate) fn is_resolvable(&self) -> bool {
return self.socket_tests.len() > 0;
}
pub(crate) fn is_healthy(&self) -> bool {
self.is_resolvable() && self.socket_tests.iter()
.all(StunSocketTestResult::is_ok)
}
pub(crate) fn is_partial_timeout(&self) -> bool {
self.is_resolvable()
&& self.is_any_healthy()
&& self.is_any_timeout()
&&! self.is_any_invalid_mapping()
&&! self.is_any_unexpected_response()
}
pub(crate) fn is_timeout(&self) -> bool {
self.is_resolvable()
&&! self.is_any_healthy()
&& self.is_any_timeout()
&&! self.is_any_invalid_mapping()
&&! self.is_any_unexpected_response()
}
fn is_any_healthy(&self) -> bool {
self.is_resolvable() && self.socket_tests.iter()
.any(|result| match result.result {
StunSocketResponse::HealthyResponse {..} => true,
_ => false,
})
}
fn is_any_timeout(&self) -> bool {
self.is_resolvable() && self.socket_tests.iter()
.any(|result| match result.result {
StunSocketResponse::Timeout {..} => true,
_ => false,
})
}
fn is_any_invalid_mapping(&self) -> bool {
self.is_resolvable() && self.socket_tests.iter()
.any(|result| match result.result {
StunSocketResponse::InvalidMappingResponse {..} => true,
_ => false,
})
}
fn is_any_unexpected_response(&self) -> bool {
self.is_resolvable() && self.socket_tests.iter()
.any(|result| match result.result {
StunSocketResponse::UnexpectedError {..} => true,
_ => false,
})
}
2021-11-17 20:40:23 +08:00
}
2022-04-10 22:28:37 +08:00
#[derive(Clone, Debug)]
pub(crate) struct StunSocketTestResult {
pub(crate) socket: SocketAddr,
pub(crate) result: StunSocketResponse
}
2021-11-17 20:40:23 +08:00
2022-04-10 22:28:37 +08:00
impl StunSocketTestResult {
pub(crate) fn is_ok(&self) -> bool {
self.result.is_ok()
}
}
#[derive(Clone, Debug)]
pub(crate) enum StunSocketResponse {
HealthyResponse { rtt: Duration },
InvalidMappingResponse { expected: SocketAddr, actual: SocketAddr, rtt: Duration },
Timeout { deadline: Duration },
UnexpectedError { err: String }
}
impl StunSocketResponse {
fn is_ok(&self) -> bool {
match &self {
StunSocketResponse::HealthyResponse { .. } => true,
_ => false
2021-11-19 04:52:05 +08:00
}
2021-11-17 20:40:23 +08:00
}
2022-04-10 22:28:37 +08:00
}
2021-11-17 20:40:23 +08:00
2022-04-10 22:28:37 +08:00
pub(crate) async fn test_udp_stun_server(
server: StunServer
) -> StunServerTestResult {
let socket_addrs = tokio::net::lookup_host(format!("{}:{}", server.hostname, server.port)).await;
2021-11-17 20:40:23 +08:00
2022-04-10 22:28:37 +08:00
if socket_addrs.is_err() {
if socket_addrs.as_ref().err().unwrap().to_string() != "failed to lookup address information: Name or service not known" {
warn!("{:<21} -> Unexpected DNS failure: {}", server.hostname, socket_addrs.as_ref().err().unwrap().to_string());
}
return StunServerTestResult {
server,
socket_tests: vec![],
}
2021-11-19 04:52:05 +08:00
}
2022-04-10 22:28:37 +08:00
let results = socket_addrs.unwrap()
.map(|addr| addr.ip())
.map(|addr| {
let port = server.port;
let hostname = server.hostname.as_str();
async move {
let stun_socket = SocketAddr::new(addr, port);
let res = test_socket_addr(hostname, stun_socket).await;
res
}
})
.collect::<Vec<_>>();
2021-11-19 04:52:05 +08:00
2022-04-10 22:28:37 +08:00
let results = join_all_with_semaphore(results.into_iter(), 1).await;
StunServerTestResult {
server,
socket_tests: results
2021-11-19 04:52:05 +08:00
}
2022-04-10 22:28:37 +08:00
}
2021-11-19 04:52:05 +08:00
2022-04-10 22:28:37 +08:00
async fn test_socket_addr(
hostname: &str,
socket_addr: SocketAddr
) -> StunSocketTestResult {
let local_socket = tokio::net::UdpSocket::bind(
match socket_addr {
SocketAddr::V4(..) => { "0.0.0.0:0" }
SocketAddr::V6(..) => { "[::]:0" }
}
).await.unwrap();
let mut client = stunclient::StunClient::new(socket_addr);
let deadline = Duration::from_secs(1);
client.set_timeout(deadline);
let start = Instant::now();
let result = client.query_external_address_async(&local_socket).await;
let request_duration = Instant::now() - start;
return match result {
Ok(return_addr) => if return_addr.port() == local_socket.local_addr().unwrap().port() {
debug!("{:<25} -> Socket {:<21} returned a healthy response", hostname, &socket_addr);
StunSocketTestResult {
socket: socket_addr,
result: StunSocketResponse::HealthyResponse { rtt: request_duration },
}
} else {
debug!("{:<25} -> Socket {:<21} returned an invalid mapping: expected={}, actual={}", hostname, &socket_addr, local_socket.local_addr().unwrap(), return_addr);
StunSocketTestResult {
socket: socket_addr,
result: StunSocketResponse::InvalidMappingResponse { expected: local_socket.local_addr().unwrap(), actual: return_addr, rtt: request_duration },
}
},
Err(err) => {
if err.to_string() == "Timed out waiting for STUN server reply" {
debug!("{:<25} -> Socket {:<21} timed out after {:?}", hostname, &socket_addr, deadline);
StunSocketTestResult {
socket: socket_addr,
result: StunSocketResponse::Timeout { deadline },
}
} else {
debug!("{:<25} -> Socket {:<21} returned an unexpected error: {:?}", hostname, &socket_addr, err.to_string());
StunSocketTestResult {
socket: socket_addr,
result: StunSocketResponse::UnexpectedError { err: err.to_string() },
}
}
},
}
2021-11-19 04:52:05 +08:00
}