#![cfg_attr(not(any(test, target_os = "macos")), allow(dead_code))]
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use tokio::task::AbortHandle;
use zlayer_spec::PortProtocol;
const UDP_SESSION_TIMEOUT: Duration = Duration::from_secs(60);
const MAX_UDP_DATAGRAM: usize = 65_535;
#[must_use]
pub fn spawn_overlay_forward(
bind: SocketAddr,
target: SocketAddr,
proto: PortProtocol,
) -> AbortHandle {
tokio::spawn(async move {
match proto {
PortProtocol::Udp => run_udp(bind, target).await,
PortProtocol::Tcp => run_tcp(bind, target).await,
}
})
.abort_handle()
}
async fn bind_tcp_retry(bind: SocketAddr) -> std::io::Result<tokio::net::TcpListener> {
let mut last_err = None;
for _ in 0..10 {
match tokio::net::TcpListener::bind(bind).await {
Ok(l) => return Ok(l),
Err(e) => {
last_err = Some(e);
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
}
Err(last_err.unwrap_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::AddrNotAvailable,
"tcp bind retries exhausted",
)
}))
}
async fn bind_udp_retry(bind: SocketAddr) -> std::io::Result<tokio::net::UdpSocket> {
let mut last_err = None;
for _ in 0..10 {
match tokio::net::UdpSocket::bind(bind).await {
Ok(s) => return Ok(s),
Err(e) => {
last_err = Some(e);
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
}
Err(last_err.unwrap_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::AddrNotAvailable,
"udp bind retries exhausted",
)
}))
}
async fn run_tcp(bind: SocketAddr, target: SocketAddr) {
let listener = match bind_tcp_retry(bind).await {
Ok(l) => l,
Err(e) => {
tracing::warn!(%bind, %target, error = %e, "overlay forwarder: TCP bind failed");
return;
}
};
tracing::info!(%bind, %target, "overlay forwarder: TCP listener up");
loop {
match listener.accept().await {
Ok((mut inbound, _peer)) => {
tokio::spawn(async move {
match tokio::net::TcpStream::connect(target).await {
Ok(mut outbound) => {
let _ =
tokio::io::copy_bidirectional(&mut inbound, &mut outbound).await;
}
Err(e) => tracing::debug!(
%target,
error = %e,
"overlay forwarder: connect to delivery target failed"
),
}
});
}
Err(e) => {
tracing::debug!(error = %e, "overlay forwarder: accept failed");
}
}
}
}
struct UdpSession {
upstream: Arc<tokio::net::UdpSocket>,
last_activity: tokio::time::Instant,
}
async fn run_udp(bind: SocketAddr, target: SocketAddr) {
let inbound = match bind_udp_retry(bind).await {
Ok(s) => Arc::new(s),
Err(e) => {
tracing::warn!(%bind, %target, error = %e, "overlay forwarder: UDP bind failed");
return;
}
};
tracing::info!(%bind, %target, "overlay forwarder: UDP relay up");
let sessions: Arc<Mutex<HashMap<SocketAddr, UdpSession>>> =
Arc::new(Mutex::new(HashMap::new()));
let mut sweep = tokio::time::interval(Duration::from_secs(10));
let mut buf = vec![0u8; MAX_UDP_DATAGRAM];
loop {
let (len, client_addr) = tokio::select! {
_ = sweep.tick() => {
let now = tokio::time::Instant::now();
let mut map = sessions.lock().await;
map.retain(|_, s| now.duration_since(s.last_activity) < UDP_SESSION_TIMEOUT);
continue;
}
r = inbound.recv_from(&mut buf) => match r {
Ok(v) => v,
Err(e) => {
tracing::debug!(error = %e, "overlay forwarder: UDP recv failed");
continue;
}
},
};
let datagram = &buf[..len];
{
let mut map = sessions.lock().await;
if let Some(s) = map.get_mut(&client_addr) {
s.last_activity = tokio::time::Instant::now();
if let Err(e) = s.upstream.send(datagram).await {
tracing::debug!(
client = %client_addr,
%target,
error = %e,
"overlay forwarder: upstream send failed; dropping session"
);
map.remove(&client_addr);
}
continue;
}
}
let upstream = match new_udp_upstream(target).await {
Ok(s) => Arc::new(s),
Err(e) => {
tracing::debug!(
client = %client_addr,
%target,
error = %e,
"overlay forwarder: failed to open upstream UDP socket"
);
continue;
}
};
spawn_udp_reader(
Arc::clone(&upstream),
Arc::clone(&inbound),
Arc::clone(&sessions),
client_addr,
);
if let Err(e) = upstream.send(datagram).await {
tracing::debug!(
client = %client_addr,
%target,
error = %e,
"overlay forwarder: first upstream send failed"
);
continue;
}
let mut map = sessions.lock().await;
map.insert(
client_addr,
UdpSession {
upstream,
last_activity: tokio::time::Instant::now(),
},
);
}
}
fn spawn_udp_reader(
upstream: Arc<tokio::net::UdpSocket>,
inbound: Arc<tokio::net::UdpSocket>,
sessions: Arc<Mutex<HashMap<SocketAddr, UdpSession>>>,
client_addr: SocketAddr,
) {
tokio::spawn(async move {
let mut rbuf = vec![0u8; MAX_UDP_DATAGRAM];
loop {
let Ok(Ok(n)) =
tokio::time::timeout(UDP_SESSION_TIMEOUT, upstream.recv(&mut rbuf)).await
else {
break;
};
if inbound.send_to(&rbuf[..n], client_addr).await.is_err() {
break;
}
let mut map = sessions.lock().await;
match map.get_mut(&client_addr) {
Some(s) => s.last_activity = tokio::time::Instant::now(),
None => break,
}
}
});
}
async fn new_udp_upstream(target: SocketAddr) -> std::io::Result<tokio::net::UdpSocket> {
let local: SocketAddr = if target.is_ipv6() {
(std::net::Ipv6Addr::UNSPECIFIED, 0).into()
} else {
(std::net::Ipv4Addr::UNSPECIFIED, 0).into()
};
let sock = tokio::net::UdpSocket::bind(local).await?;
sock.connect(target).await?;
Ok(sock)
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, SocketAddr};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
async fn spawn_tcp_echo() -> SocketAddr {
let listener = tokio::net::TcpListener::bind((Ipv4Addr::LOCALHOST, 0))
.await
.expect("bind echo");
let addr = listener.local_addr().expect("echo addr");
tokio::spawn(async move {
loop {
let Ok((mut sock, _)) = listener.accept().await else {
break;
};
tokio::spawn(async move {
let mut buf = [0u8; 1024];
loop {
match sock.read(&mut buf).await {
Ok(0) | Err(_) => break,
Ok(n) => {
if sock.write_all(&buf[..n]).await.is_err() {
break;
}
}
}
}
});
}
});
addr
}
#[tokio::test]
async fn tcp_forward_round_trips_bytes() {
let echo_addr = spawn_tcp_echo().await;
let probe = tokio::net::TcpListener::bind((Ipv4Addr::LOCALHOST, 0))
.await
.expect("probe bind");
let bind_addr = probe.local_addr().expect("probe addr");
drop(probe);
let handle = spawn_overlay_forward(bind_addr, echo_addr, PortProtocol::Tcp);
let mut client = None;
for _ in 0..50 {
if let Ok(c) = tokio::net::TcpStream::connect(bind_addr).await {
client = Some(c);
break;
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
let mut client = client.expect("connect to forwarder");
let payload = b"hello-overlay-forward";
client.write_all(payload).await.expect("write");
client.flush().await.expect("flush");
let mut got = vec![0u8; payload.len()];
client.read_exact(&mut got).await.expect("read echo");
assert_eq!(&got, payload);
handle.abort();
}
}