use super::{spawn_server_task, QOS_PORT};
use log::debug;
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
sync::Arc,
time::{Duration, SystemTime},
};
use tokio::{net::UdpSocket, sync::RwLock};
pub async fn start_qos_server() -> std::io::Result<()> {
let socket: UdpSocket = UdpSocket::bind((Ipv4Addr::LOCALHOST, QOS_PORT)).await?;
let socket: Arc<UdpSocket> = Arc::new(socket);
let mut buffer: [u8; 4096] = [0u8; 4096];
loop {
let (count, addr) = socket.recv_from(&mut buffer).await?;
let buffer: Box<[u8]> = Box::from(&buffer[..count]);
spawn_server_task(handle(socket.clone(), addr, buffer));
}
}
async fn handle(socket: Arc<UdpSocket>, socket_addr: SocketAddr, buffer: Box<[u8]>) {
let socket_ip = match socket_addr {
SocketAddr::V4(addr) => *addr.ip(),
_ => Ipv4Addr::UNSPECIFIED,
};
let address = public_address().await.unwrap_or(socket_ip);
debug!("QoS: From: {} Resolved: {}", socket_addr, address);
let mut output = Vec::with_capacity(
buffer.len() + 4 + 2 + 4,
);
output.extend_from_slice(&buffer);
output.extend_from_slice(&address.octets());
output.extend_from_slice(&socket_addr.port().to_be_bytes());
output.extend_from_slice(&[0, 0, 0, 0]);
let _ = socket.send_to(&output, socket_addr).await;
}
enum PublicAddrCache {
Unset,
Set {
value: Ipv4Addr,
expires: SystemTime,
},
}
static PUBLIC_ADDR_CACHE: RwLock<PublicAddrCache> = RwLock::const_new(PublicAddrCache::Unset);
const ADDR_CACHE_TIME: Duration = Duration::from_secs(60 * 30);
async fn public_address() -> Option<Ipv4Addr> {
{
let cached = &*PUBLIC_ADDR_CACHE.read().await;
if let PublicAddrCache::Set { value, expires } = cached {
let time = SystemTime::now();
if time.lt(expires) {
return Some(*value);
}
}
}
let cached = &mut *PUBLIC_ADDR_CACHE.write().await;
let addresses = ["https://api.ipify.org/", "https://ipv4.icanhazip.com/"];
let mut value: Option<Ipv4Addr> = None;
for address in addresses {
let Ok(response) = reqwest::get(address).await else {
continue;
};
let Ok(response) = response.text().await else {
continue;
};
let ip = response.trim().replace('\n', "");
if let Ok(parsed) = ip.parse() {
value = Some(parsed);
break;
}
}
if value.is_none() {
if let Ok(IpAddr::V4(addr)) = local_ip_address::local_ip() {
value = Some(addr);
}
}
let value = value?;
*cached = PublicAddrCache::Set {
value,
expires: SystemTime::now() + ADDR_CACHE_TIME,
};
Some(value)
}