use std::fmt::Debug;
use std::io::{self, IoSliceMut};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::pin::Pin;
use std::sync::Arc;
use std::task::{ready, Context, Poll};
use quinn::udp::{RecvMeta, Transmit};
use quinn::{AsyncUdpSocket, UdpPoller};
use crate::network::NetworkUdpSocket;
const ADDR_HEADER_SIZE: usize = 6;
pub struct TsnetUdpSocket {
inner: NetworkUdpSocket,
tsnet_addr: SocketAddr,
}
impl TsnetUdpSocket {
pub fn new(inner: NetworkUdpSocket, tsnet_addr: SocketAddr) -> Self {
Self { inner, tsnet_addr }
}
fn encode_header(dest: SocketAddr) -> io::Result<[u8; ADDR_HEADER_SIZE]> {
let ip = match dest.ip() {
IpAddr::V4(v4) => v4,
IpAddr::V6(_) => {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"TsnetUdpSocket: IPv6 not supported in relay framing",
));
}
};
let port = dest.port();
let mut header = [0u8; ADDR_HEADER_SIZE];
header[..4].copy_from_slice(&ip.octets());
header[4..6].copy_from_slice(&port.to_be_bytes());
Ok(header)
}
fn decode_header(buf: &[u8]) -> io::Result<(SocketAddr, usize)> {
if buf.len() < ADDR_HEADER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"TsnetUdpSocket: packet too short for address header ({} < {})",
buf.len(),
ADDR_HEADER_SIZE
),
));
}
let ip = Ipv4Addr::new(buf[0], buf[1], buf[2], buf[3]);
let port = u16::from_be_bytes([buf[4], buf[5]]);
Ok((SocketAddr::new(IpAddr::V4(ip), port), ADDR_HEADER_SIZE))
}
}
impl Debug for TsnetUdpSocket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TsnetUdpSocket")
.field("tsnet_addr", &self.tsnet_addr)
.field("relay_local_addr", &self.inner.inner().local_addr().ok())
.finish()
}
}
impl AsyncUdpSocket for TsnetUdpSocket {
fn create_io_poller(self: Arc<Self>) -> Pin<Box<dyn UdpPoller>> {
Box::pin(TsnetUdpPoller {
socket: self,
fut: None,
})
}
fn try_send(&self, transmit: &Transmit) -> io::Result<()> {
let header = Self::encode_header(transmit.destination)?;
let payload = transmit.contents;
let mut framed = Vec::with_capacity(ADDR_HEADER_SIZE + payload.len());
framed.extend_from_slice(&header);
framed.extend_from_slice(payload);
self.inner.inner().try_send(&framed)?;
Ok(())
}
fn poll_recv(
&self,
cx: &mut Context,
bufs: &mut [IoSliceMut<'_>],
meta: &mut [RecvMeta],
) -> Poll<io::Result<usize>> {
debug_assert!(!bufs.is_empty());
debug_assert!(!meta.is_empty());
let buf_len = bufs[0].len();
let tmp_size = ADDR_HEADER_SIZE + buf_len;
let mut tmp = vec![0u8; tmp_size];
loop {
ready!(self.inner.inner().poll_recv_ready(cx))?;
match self.inner.inner().try_recv(&mut tmp) {
Ok(n) => {
if n < ADDR_HEADER_SIZE {
tracing::warn!(
bytes = n,
"TsnetUdpSocket: runt packet (< {} bytes), skipping",
ADDR_HEADER_SIZE
);
continue;
}
let (src_addr, hdr_len) = Self::decode_header(&tmp[..n])?;
let payload_len = n - hdr_len;
let copy_len = payload_len.min(buf_len);
bufs[0][..copy_len].copy_from_slice(&tmp[hdr_len..hdr_len + copy_len]);
meta[0] = RecvMeta {
addr: src_addr,
len: copy_len,
stride: copy_len,
ecn: None, dst_ip: Some(self.tsnet_addr.ip()),
};
return Poll::Ready(Ok(1));
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => return Poll::Ready(Err(e)),
}
}
}
fn local_addr(&self) -> io::Result<SocketAddr> {
Ok(self.tsnet_addr)
}
fn max_transmit_segments(&self) -> usize {
1
}
fn max_receive_segments(&self) -> usize {
1
}
fn may_fragment(&self) -> bool {
true
}
}
struct TsnetUdpPoller {
socket: Arc<TsnetUdpSocket>,
fut: Option<Pin<Box<dyn std::future::Future<Output = io::Result<()>> + Send + Sync>>>,
}
impl UdpPoller for TsnetUdpPoller {
fn poll_writable(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
if self.fut.is_none() {
let socket = self.socket.clone();
self.fut = Some(Box::pin(async move {
socket.inner.inner().writable().await
}));
}
let result = self.fut.as_mut().unwrap().as_mut().poll(cx);
if result.is_ready() {
self.fut = None;
}
result
}
}
impl Debug for TsnetUdpPoller {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TsnetUdpPoller").finish_non_exhaustive()
}
}
unsafe impl Send for TsnetUdpPoller {}
unsafe impl Sync for TsnetUdpPoller {}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, SocketAddr};
#[test]
fn encode_header_ipv4() {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 64, 1, 2)), 9420);
let header = TsnetUdpSocket::encode_header(addr).unwrap();
assert_eq!(header[0], 100);
assert_eq!(header[1], 64);
assert_eq!(header[2], 1);
assert_eq!(header[3], 2);
assert_eq!(u16::from_be_bytes([header[4], header[5]]), 9420);
}
#[test]
fn encode_header_ipv6_rejected() {
let addr = SocketAddr::new(IpAddr::V6("::1".parse().unwrap()), 9420);
let result = TsnetUdpSocket::encode_header(addr);
assert!(result.is_err());
}
#[test]
fn decode_header_roundtrip() {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 100, 50, 3)), 12345);
let header = TsnetUdpSocket::encode_header(addr).unwrap();
let (decoded_addr, hdr_len) = TsnetUdpSocket::decode_header(&header).unwrap();
assert_eq!(decoded_addr, addr);
assert_eq!(hdr_len, ADDR_HEADER_SIZE);
}
#[test]
fn decode_header_too_short() {
let result = TsnetUdpSocket::decode_header(&[0, 1, 2]);
assert!(result.is_err());
}
#[test]
fn decode_header_with_payload() {
let mut buf = Vec::new();
buf.extend_from_slice(&[10, 0, 0, 1]); buf.extend_from_slice(&8080u16.to_be_bytes()); buf.extend_from_slice(b"hello"); let (addr, hdr_len) = TsnetUdpSocket::decode_header(&buf).unwrap();
assert_eq!(addr, SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 8080));
assert_eq!(hdr_len, 6);
assert_eq!(&buf[hdr_len..], b"hello");
}
#[tokio::test]
async fn tsnet_socket_send_recv_via_loopback_relay() {
let relay = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap();
let relay_addr = relay.local_addr().unwrap();
let inner = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap();
inner.connect(relay_addr).await.unwrap();
let inner_addr = inner.local_addr().unwrap();
relay.connect(inner_addr).await.unwrap();
let tsnet_port = 9420;
let net_socket = NetworkUdpSocket::new(inner, tsnet_port);
let tsnet_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 64, 1, 1)), tsnet_port);
let tsnet_socket = Arc::new(TsnetUdpSocket::new(net_socket, tsnet_addr));
let dest = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 64, 1, 2)), 9420);
let payload = b"test quic datagram";
tsnet_socket.inner.inner().writable().await.unwrap();
let transmit = Transmit {
destination: dest,
ecn: None,
contents: payload,
segment_size: None,
src_ip: None,
};
tsnet_socket.try_send(&transmit).unwrap();
let mut buf = [0u8; 2048];
let n = relay.recv(&mut buf).await.unwrap();
assert_eq!(n, ADDR_HEADER_SIZE + payload.len());
assert_eq!(buf[0], 100);
assert_eq!(buf[1], 64);
assert_eq!(buf[2], 1);
assert_eq!(buf[3], 2);
assert_eq!(u16::from_be_bytes([buf[4], buf[5]]), 9420);
assert_eq!(&buf[ADDR_HEADER_SIZE..n], payload);
let reply_src = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(100, 64, 1, 2)), 9420);
let reply_payload = b"reply from peer";
let mut framed = Vec::new();
if let IpAddr::V4(v4) = reply_src.ip() {
framed.extend_from_slice(&v4.octets());
}
framed.extend_from_slice(&reply_src.port().to_be_bytes());
framed.extend_from_slice(reply_payload);
relay.send(&framed).await.unwrap();
let mut recv_buf = [0u8; 2048];
let mut ioslice = [IoSliceMut::new(&mut recv_buf)];
let mut recv_meta = [RecvMeta::default()];
let count = tokio::time::timeout(
std::time::Duration::from_secs(2),
poll_recv_helper(&tsnet_socket, &mut ioslice, &mut recv_meta),
)
.await
.expect("recv timed out");
assert_eq!(count, 1);
assert_eq!(recv_meta[0].addr, reply_src);
assert_eq!(recv_meta[0].len, reply_payload.len());
assert_eq!(&recv_buf[..recv_meta[0].len], reply_payload);
}
async fn poll_recv_helper(
socket: &Arc<TsnetUdpSocket>,
bufs: &mut [IoSliceMut<'_>],
meta: &mut [RecvMeta],
) -> usize {
std::future::poll_fn(|cx| socket.poll_recv(cx, bufs, meta)).await.unwrap()
}
}