use anyhow::{Context, Result};
use std::net::{IpAddr, SocketAddr};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tracing::{debug, error, info};
pub async fn bind_listener(addr: SocketAddr) -> Result<TcpListener> {
TcpListener::bind(addr)
.await
.with_context(|| format!("failed to bind {}", addr))
}
pub async fn serve_listener<P>(listener: TcpListener, port: u16, peer: &P) -> Result<()>
where
P: PeerConnection + Send + Sync + 'static,
{
match listener.local_addr() {
Ok(addr) => info!("listening on {}", addr),
Err(_) => info!("listening on port {}", port),
}
loop {
let (stream, client_addr) = listener.accept().await?;
stream.set_nodelay(true).ok();
debug!("accepted connection from {} on port {}", client_addr, port);
match peer.open_tunnel(port).await {
Ok((send, recv)) => {
tokio::spawn(async move {
if let Err(e) = forward_bidirectional(stream, send, recv).await {
error!("tunnel error: {}", e);
}
});
}
Err(e) => {
error!("failed to open tunnel to peer for port {}: {}", port, e);
}
}
}
}
pub async fn handle_tunnel(
host: IpAddr,
port: u16,
send: iroh::endpoint::SendStream,
recv: iroh::endpoint::RecvStream,
) -> Result<()> {
let addr = SocketAddr::from((host, port));
let stream = TcpStream::connect(addr)
.await
.with_context(|| format!("failed to connect to {}", addr))?;
stream.set_nodelay(true).ok();
forward_bidirectional(stream, send, recv).await
}
async fn forward_bidirectional(
tcp: TcpStream,
mut quic_send: iroh::endpoint::SendStream,
mut quic_recv: iroh::endpoint::RecvStream,
) -> Result<()> {
let (mut tcp_read, mut tcp_write) = tcp.into_split();
let tcp_to_quic = async {
let result = copy_flush(&mut tcp_read, &mut quic_send).await;
let _ = quic_send.finish();
result
};
let quic_to_tcp = async { copy_flush(&mut quic_recv, &mut tcp_write).await };
tokio::select! {
r = tcp_to_quic => { debug!("tcp->quic ended: {:?}", r); }
r = quic_to_tcp => { debug!("quic->tcp ended: {:?}", r); }
}
Ok(())
}
pub async fn copy_flush<R, W>(reader: &mut R, writer: &mut W) -> std::io::Result<u64>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
let mut buf = [0u8; 16 * 1024];
let mut total = 0u64;
loop {
let n = reader.read(&mut buf).await?;
if n == 0 {
return Ok(total);
}
writer.write_all(&buf[..n]).await?;
writer.flush().await?;
total += n as u64;
}
}
pub trait PeerConnection: Send + Sync {
fn open_tunnel(
&self,
port: u16,
) -> impl std::future::Future<
Output = Result<(iroh::endpoint::SendStream, iroh::endpoint::RecvStream)>,
> + Send;
}