use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::net::UdpSocket;
use tokio::task::JoinHandle;
pub const MAX_DATAGRAM: usize = 512;
#[derive(Debug, Clone)]
pub struct UdpForwardConfig {
pub listen: SocketAddr,
pub dest: SocketAddr,
pub allowed_source: Option<IpAddr>,
}
pub struct UdpForwarder {
local: SocketAddr,
forwarded: Arc<AtomicU64>,
dropped: Arc<AtomicU64>,
task: JoinHandle<()>,
}
impl UdpForwarder {
pub async fn spawn(cfg: UdpForwardConfig) -> std::io::Result<Self> {
let sock = UdpSocket::bind(cfg.listen).await?;
let local = sock.local_addr()?;
let out = UdpSocket::bind(if cfg.dest.is_ipv4() { "0.0.0.0:0" } else { "[::]:0" }).await?;
let forwarded = Arc::new(AtomicU64::new(0));
let dropped = Arc::new(AtomicU64::new(0));
let (f, d) = (forwarded.clone(), dropped.clone());
let task = tokio::spawn(async move {
let mut buf = [0u8; MAX_DATAGRAM + 1];
loop {
let Ok((n, src)) = sock.recv_from(&mut buf).await else {
return;
};
let source_ok = cfg.allowed_source.is_none_or(|a| a == src.ip());
if n > MAX_DATAGRAM || n == 0 || !source_ok {
d.fetch_add(1, Ordering::Relaxed);
continue;
}
if out.send_to(&buf[..n], cfg.dest).await.is_ok() {
f.fetch_add(1, Ordering::Relaxed);
}
}
});
Ok(Self {
local,
forwarded,
dropped,
task,
})
}
pub fn local_addr(&self) -> SocketAddr {
self.local
}
pub fn forwarded(&self) -> u64 {
self.forwarded.load(Ordering::Relaxed)
}
pub fn dropped(&self) -> u64 {
self.dropped.load(Ordering::Relaxed)
}
}
impl Drop for UdpForwarder {
fn drop(&mut self) {
self.task.abort();
}
}