#![cfg(feature = "local")]
mod harness;
fn init_trace() {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::level_filters::LevelFilter::DEBUG)
.with_test_writer()
.try_init();
}
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tokio::net::UdpSocket;
use tokio::time::timeout;
use alksocks::UdpFrontDoor;
use harness::echo_dial;
const BOUND: Duration = Duration::from_secs(10);
async fn udp_echo_server() -> SocketAddr {
let sock = UdpSocket::bind(("127.0.0.1", 0))
.await
.expect("bind udp echo");
let addr = sock.local_addr().expect("addr");
tokio::spawn(async move {
let mut buf = vec![0u8; 65535];
loop {
let Ok((n, peer)) = sock.recv_from(&mut buf).await else {
return;
};
if sock.send_to(&buf[..n], peer).await.is_err() {
return;
}
}
});
addr
}
fn rfc_datagram(target: SocketAddr, payload: &[u8]) -> Vec<u8> {
let SocketAddr::V4(v4) = target else {
panic!("v4 targets only");
};
let mut dg = vec![0u8, 0, 0, 0x01];
dg.extend_from_slice(&v4.ip().octets());
dg.extend_from_slice(&v4.port().to_be_bytes());
dg.extend_from_slice(payload);
dg
}
fn parse_rfc_datagram(wire: &[u8]) -> (SocketAddr, &[u8]) {
assert_eq!(&wire[..2], &[0, 0], "RSV zero");
assert_eq!(wire[3], 0x01, "v4 ATYP");
let ip = std::net::Ipv4Addr::new(wire[4], wire[5], wire[6], wire[7]);
let port = u16::from_be_bytes([wire[8], wire[9]]);
(SocketAddr::from((ip, port)), &wire[10..])
}
#[tokio::test]
async fn udp_door_vanilla_client_round_trip_and_empty_datagram() {
init_trace();
let echo_addr = udp_echo_server().await;
let topo = harness::wire_consumer_side_with(
echo_dial(),
Some(alksocks::udp_egress()),
None,
Some(harness::consumer_identity()),
)
.await;
let relay = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay");
let relay_addr = relay.local_addr().expect("relay addr");
let client = UdpSocket::bind("127.0.0.1:0").await.expect("client socket");
let door = UdpFrontDoor::builder()
.bind(relay_addr)
.build()
.expect("the door builds");
let _serve = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move { door.serve_on(relay, client).await.expect("the door serves") }
});
client
.send_to(&rfc_datagram(echo_addr, b"pre"), relay_addr)
.await
.expect("vanilla sendto the relay");
let mut rx = vec![0u8; 65535];
let (n, from) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("reply within bound")
.expect("recv");
assert_eq!(from, relay_addr, "the reply comes from the relay socket");
let (addr, payload) = parse_rfc_datagram(&rx[..n]);
assert_eq!(addr, echo_addr, "the echo reports the target");
assert_eq!(payload, b"pre");
client
.send_to(&rfc_datagram(echo_addr, b""), relay_addr)
.await
.expect("send the empty datagram");
let (n, _) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("empty-datagram reply within bound")
.expect("recv");
let (addr, payload) = parse_rfc_datagram(&rx[..n]);
assert_eq!(addr, echo_addr);
assert!(
payload.is_empty(),
"the empty datagram round-trips (never EOF)"
);
client
.send_to(&rfc_datagram(echo_addr, b"post"), relay_addr)
.await
.expect("send post");
let (n, _) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("post reply within bound")
.expect("recv");
let (_, payload) = parse_rfc_datagram(&rx[..n]);
assert_eq!(payload, b"post");
assert!(
topo.consumer
.manager()
.channel_ids()
.iter()
.any(|&id| id != 0),
"the door's association rode an in-band channel"
);
}
#[tokio::test]
async fn batched_in_band_replies_both_reach_the_vanilla_client() {
let echo_addr = udp_echo_server().await;
let topo = harness::wire_consumer_side_with(
echo_dial(),
Some(harness::doubled_echo_egress_factory()),
None,
Some(harness::consumer_identity()),
)
.await;
let relay = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay");
let relay_addr = relay.local_addr().expect("relay addr");
let client = UdpSocket::bind("127.0.0.1:0").await.expect("client socket");
let door = UdpFrontDoor::builder()
.bind(relay_addr)
.build()
.expect("the door builds");
let _serve = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move { door.serve_on(relay, client).await.expect("the door serves") }
});
client
.send_to(&rfc_datagram(echo_addr, b"once"), relay_addr)
.await
.expect("send");
let mut rx = vec![0u8; 65535];
for _ in 0..2 {
let (n, from) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("reply within bound")
.expect("recv");
assert_eq!(from, relay_addr, "the reply comes from the relay socket");
let (addr, payload) = parse_rfc_datagram(&rx[..n]);
assert_eq!(addr, echo_addr);
assert_eq!(payload, b"once");
}
let mut probe = vec![0u8; 65535];
assert!(
timeout(Duration::from_millis(300), client.recv_from(&mut probe))
.await
.is_err(),
"exactly two replies arrived"
);
}
#[tokio::test]
async fn frag_nonzero_refused_and_association_stays_healthy() {
let echo_addr = udp_echo_server().await;
let topo = harness::wire_consumer_side_with(
echo_dial(),
Some(alksocks::udp_egress()),
None,
Some(harness::consumer_identity()),
)
.await;
let relay = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay");
let relay_addr = relay.local_addr().expect("relay addr");
let client = UdpSocket::bind("127.0.0.1:0").await.expect("client socket");
let door = UdpFrontDoor::builder()
.bind(relay_addr)
.build()
.expect("the door builds");
let _serve = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move { door.serve_on(relay, client).await.expect("the door serves") }
});
let SocketAddr::V4(v4) = echo_addr else {
panic!("v4 target")
};
let mut frag_dg = vec![0u8, 0, 1, 0x01];
frag_dg.extend_from_slice(&v4.ip().octets());
frag_dg.extend_from_slice(&v4.port().to_be_bytes());
frag_dg.extend_from_slice(b"fragmented");
client
.send_to(&frag_dg, relay_addr)
.await
.expect("send frag");
client
.send_to(&rfc_datagram(echo_addr, b"healthy"), relay_addr)
.await
.expect("send after frag");
let mut rx = vec![0u8; 65535];
let (n, _) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("reply within bound")
.expect("recv");
let (_, payload) = parse_rfc_datagram(&rx[..n]);
assert_eq!(
payload, b"healthy",
"the association survived the FRAG drop"
);
let mut probe = vec![0u8; 65535];
assert!(
timeout(Duration::from_millis(300), client.recv_from(&mut probe))
.await
.is_err(),
"the FRAG ≠ 0 datagram was dropped (never egressed)"
);
}
#[tokio::test]
async fn idle_bound_closes_an_idle_association_and_a_fresh_one_succeeds() {
let echo_addr = udp_echo_server().await;
let topo = harness::wire_consumer_side_with(
echo_dial(),
Some(alksocks::udp_egress()),
None,
Some(harness::consumer_identity()),
)
.await;
let relay = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay");
let door = UdpFrontDoor::builder()
.bind(relay.local_addr().unwrap())
.idle(Duration::from_millis(300))
.build()
.expect("the door builds");
let serve = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move { door.serve_on(relay, client).await.expect("the door serves") }
});
let started = std::time::Instant::now();
timeout(Duration::from_secs(3), serve)
.await
.expect("the door's task ended")
.expect("serve returns cleanly on the idle exit");
let elapsed = started.elapsed();
assert!(
elapsed >= Duration::from_millis(250),
"the door waited the window before closing: {elapsed:?}"
);
tokio::time::sleep(Duration::from_millis(100)).await;
assert!(
topo.consumer
.manager()
.channel_ids()
.iter()
.all(|&id| id == 0),
"the idle teardown reaped the association's channel"
);
let relay2 = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay 2");
let relay2_addr = relay2.local_addr().unwrap();
let door2 = UdpFrontDoor::builder()
.bind(relay2_addr)
.build()
.expect("the door builds");
let _serve2 = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move {
door2
.serve_on(relay2, client)
.await
.expect("the fresh door serves")
}
});
let client = UdpSocket::bind("127.0.0.1:0").await.expect("client socket");
client
.send_to(&rfc_datagram(echo_addr, b"fresh"), relay2_addr)
.await
.expect("send through the fresh door");
let mut rx = vec![0u8; 65535];
let (n, _) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("fresh reply within bound")
.expect("recv");
let (addr, payload) = parse_rfc_datagram(&rx[..n]);
assert_eq!(addr, echo_addr);
assert_eq!(payload, b"fresh");
}
#[tokio::test]
async fn fresh_association_succeeds_after_teardown() {
let echo_addr = udp_echo_server().await;
let topo = harness::wire_consumer_side_with(
echo_dial(),
Some(alksocks::udp_egress()),
None,
Some(harness::consumer_identity()),
)
.await;
let relay = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay");
let relay_addr = relay.local_addr().expect("relay addr");
let door = UdpFrontDoor::builder()
.bind(relay_addr)
.idle(Duration::from_millis(200))
.build()
.expect("the door builds");
let serve = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move { door.serve_on(relay, client).await.expect("the door serves") }
});
let vanilla = UdpSocket::bind("127.0.0.1:0")
.await
.expect("vanilla socket");
vanilla
.send_to(&rfc_datagram(echo_addr, b"first"), relay_addr)
.await
.expect("send first");
let mut rx = vec![0u8; 65535];
let (n, _) = timeout(BOUND, vanilla.recv_from(&mut rx))
.await
.expect("first reply within bound")
.expect("recv");
assert_eq!(parse_rfc_datagram(&rx[..n]).1, b"first");
timeout(Duration::from_secs(3), serve)
.await
.expect("the first association ended (idle bound)")
.expect("clean exit");
let relay2 = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay 2");
let relay2_addr = relay2.local_addr().expect("relay2 addr");
let door2 = UdpFrontDoor::builder()
.bind(relay2_addr)
.build()
.expect("the door builds");
let serve2 = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move {
door2
.serve_on(relay2, client)
.await
.expect("the fresh door serves")
}
});
let client = UdpSocket::bind("127.0.0.1:0").await.expect("client socket");
client
.send_to(&rfc_datagram(echo_addr, b"fresh"), relay2_addr)
.await
.expect("send through the fresh door");
let (n, _) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("fresh reply within bound")
.expect("recv");
let (addr, payload) = parse_rfc_datagram(&rx[..n]);
assert_eq!(addr, echo_addr);
assert_eq!(payload, b"fresh");
serve2.abort();
}
#[tokio::test]
async fn udp_door_serve_wrapper_round_trips() {
init_trace();
let echo_addr = udp_echo_server().await;
let topo = harness::wire_consumer_side_with(
echo_dial(),
Some(alksocks::udp_egress()),
None,
Some(harness::consumer_identity()),
)
.await;
let probe = UdpSocket::bind("127.0.0.1:0").await.expect("probe bind");
let relay_addr = probe.local_addr().expect("probe addr");
drop(probe);
let client = UdpSocket::bind("127.0.0.1:0").await.expect("client socket");
let door = UdpFrontDoor::builder()
.bind(relay_addr)
.build()
.expect("the door builds");
let _serve = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move { door.serve(client).await.expect("the door serves") }
});
client
.send_to(&rfc_datagram(echo_addr, b"wrapped"), relay_addr)
.await
.expect("vanilla sendto the relay");
let mut rx = vec![0u8; 65535];
let (n, from) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("reply within bound")
.expect("recv");
assert_eq!(from, relay_addr, "the reply comes from the wrapper's bind");
let (_, payload) = parse_rfc_datagram(&rx[..n]);
assert_eq!(payload, b"wrapped");
}
#[tokio::test]
async fn nat_port_rebind_keeps_receiving_replies() {
let echo_addr = udp_echo_server().await;
let topo = harness::wire_consumer_side_with(
echo_dial(),
Some(alksocks::udp_egress()),
None,
Some(harness::consumer_identity()),
)
.await;
let relay = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay");
let relay_addr = relay.local_addr().expect("relay addr");
let door = UdpFrontDoor::builder()
.bind(relay_addr)
.build()
.expect("the door builds");
let _serve = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move { door.serve_on(relay, client).await.expect("the door serves") }
});
let client_a = UdpSocket::bind("127.0.0.1:0").await.expect("client A");
client_a
.send_to(&rfc_datagram(echo_addr, b"before"), relay_addr)
.await
.expect("send from A");
let mut rx = vec![0u8; 65535];
let (n, _) = timeout(BOUND, client_a.recv_from(&mut rx))
.await
.expect("reply within bound")
.expect("recv");
assert_eq!(parse_rfc_datagram(&rx[..n]).1, b"before");
let client_b = UdpSocket::bind("127.0.0.1:0").await.expect("client B");
client_b
.send_to(&rfc_datagram(echo_addr, b"after"), relay_addr)
.await
.expect("send from B");
let (n, _) = timeout(BOUND, client_b.recv_from(&mut rx))
.await
.expect("rebound reply within bound")
.expect("recv");
assert_eq!(parse_rfc_datagram(&rx[..n]).1, b"after");
let mut probe = vec![0u8; 65535];
assert!(
timeout(Duration::from_millis(300), client_a.recv_from(&mut probe))
.await
.is_err(),
"the reply followed the rebind (the old port stopped receiving)"
);
}
#[tokio::test]
async fn foreign_source_ip_datagrams_dropped_and_association_stays_healthy() {
init_trace();
let echo_addr = udp_echo_server().await;
let topo = harness::wire_consumer_side_with(
echo_dial(),
Some(alksocks::udp_egress()),
None,
Some(harness::consumer_identity()),
)
.await;
let relay = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay");
let relay_addr = relay.local_addr().expect("relay addr");
let door = UdpFrontDoor::builder()
.bind(relay_addr)
.build()
.expect("the door builds");
let _serve = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move { door.serve_on(relay, client).await.expect("the door serves") }
});
let client = UdpSocket::bind("127.0.0.1:0").await.expect("client");
client
.send_to(&rfc_datagram(echo_addr, b"pinned"), relay_addr)
.await
.expect("send pinned");
let mut rx = vec![0u8; 65535];
let (n, _) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("reply within bound")
.expect("recv");
assert_eq!(parse_rfc_datagram(&rx[..n]).1, b"pinned");
let foreign = UdpSocket::bind("127.0.0.2:0").await.expect("foreign bind");
foreign
.send_to(&rfc_datagram(echo_addr, b"hijack"), relay_addr)
.await
.expect("send foreign");
let mut probe = vec![0u8; 65535];
assert!(
timeout(Duration::from_millis(400), foreign.recv_from(&mut probe))
.await
.is_err(),
"the foreign-IP datagram was dropped (no reply to the spoofer)"
);
client
.send_to(&rfc_datagram(echo_addr, b"still"), relay_addr)
.await
.expect("send still");
let (n, _) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("still-reply within bound")
.expect("recv");
assert_eq!(
parse_rfc_datagram(&rx[..n]).1,
b"still",
"the association survived the foreign-IP drop"
);
}