#![cfg(all(feature = "local", feature = "composed"))]
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 alkcall::channels::operations::ChannelCore;
use alkcall::core::auth::{Identity, IdentityProvider};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream, UdpSocket};
use tokio::time::timeout;
use alksocks::composed::{bindfirst_accept, direct_egress, register_forwarded_serving};
use alksocks::consumer::{AssociateSession, BindSession, Socks5ChannelSession};
use alksocks::rfc1928::consts;
use alksocks::rfc1928::util::target_addr::{TargetAddr, ToTargetAddr};
use alksocks::{CredentialTable, DoorCredential, FrontDoor, UdpFrontDoor, SOCKS5_BIND_SCOPE};
use harness::{
composed_full_opener_identity, composed_push_accept, wire_bindfirst_composed,
wire_consumer_side_with, wire_consumer_side_with_provider, COMPOSED_BOUND_PORT,
COMPOSED_PEER_PORT, COMPOSED_RESOURCE,
};
const BOUND: Duration = Duration::from_secs(10);
async fn echo_tcp_server() -> SocketAddr {
let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind");
let addr = listener.local_addr().expect("local_addr");
tokio::spawn(async move {
loop {
let Ok((mut stream, _)) = listener.accept().await else {
return;
};
tokio::spawn(async move {
let mut buf = [0u8; 4096];
loop {
match stream.read(&mut buf).await {
Ok(0) | Err(_) => return,
Ok(n) => {
if stream.write_all(&buf[..n]).await.is_err() {
return;
}
}
}
}
});
}
});
addr
}
async fn echo_udp_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
}
async fn vanilla_cred_connect(door: SocketAddr, target: SocketAddr) -> (TcpStream, u8, [u8; 6]) {
let mut stream = vanilla_with_creds(door).await;
let mut req = vec![0x05, 0x01, 0x00, 0x01];
let SocketAddr::V4(v4) = target else {
panic!("v4 targets only")
};
req.extend_from_slice(&v4.ip().octets());
req.extend_from_slice(&v4.port().to_be_bytes());
stream.write_all(&req).await.expect("request");
stream.flush().await.expect("flush");
let mut head = [0u8; 4];
stream.read_exact(&mut head).await.expect("reply head");
assert_eq!(head[0], 0x05, "reply is v5");
assert_eq!(head[3], 0x01, "v4 BND.ADDR");
let mut addr = [0u8; 6];
stream.read_exact(&mut addr).await.expect("BND.ADDR");
(stream, head[1], addr)
}
async fn vanilla_no_auth_connect(door: SocketAddr, target: SocketAddr) -> (TcpStream, u8, [u8; 6]) {
let mut stream = TcpStream::connect(door).await.expect("door connect");
stream
.write_all(&[0x05, 0x01, 0x00])
.await
.expect("greeting");
let mut chosen = [0u8; 2];
stream
.read_exact(&mut chosen)
.await
.expect("method selection");
assert_eq!(chosen, [0x05, 0x00], "no-auth selected");
let mut req = vec![0x05, 0x01, 0x00, 0x01];
let SocketAddr::V4(v4) = target else {
panic!("v4 targets only")
};
req.extend_from_slice(&v4.ip().octets());
req.extend_from_slice(&v4.port().to_be_bytes());
stream.write_all(&req).await.expect("request");
stream.flush().await.expect("flush");
let mut head = [0u8; 4];
stream.read_exact(&mut head).await.expect("reply head");
assert_eq!(head[0], 0x05, "reply is v5");
assert_eq!(head[3], 0x01, "v4 BND.ADDR");
let mut addr = [0u8; 6];
stream.read_exact(&mut addr).await.expect("BND.ADDR");
(stream, head[1], 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..])
}
#[derive(Clone)]
struct DoorTokenProvider {
bind_scoped: Vec<String>,
}
impl IdentityProvider for DoorTokenProvider {
fn resolve_from_fingerprint(&self, _: &str) -> Option<Identity> {
None
}
fn resolve_from_token(&self, token: &alkcall::core::auth::AuthToken) -> Option<Identity> {
let id = String::from_utf8_lossy(&token.raw).into_owned();
if id.starts_with("door-") {
let mut scopes = vec![alksocks::SOCKS5_OPEN_SCOPE.to_string()];
if self.bind_scoped.contains(&id) {
scopes.push(SOCKS5_BIND_SCOPE.to_string());
}
return Some(Identity {
id,
scopes,
resources: Default::default(),
});
}
None
}
}
fn cred_table() -> CredentialTable {
CredentialTable::new(vec![DoorCredential {
username: "vanilla".to_string(),
password: "hunter2".to_string(),
identity: Identity {
id: "door-vanilla".to_string(),
scopes: vec![
alksocks::SOCKS5_OPEN_SCOPE.to_string(),
SOCKS5_BIND_SCOPE.to_string(),
],
resources: Default::default(),
},
}])
}
async fn vanilla_with_creds(door: SocketAddr) -> TcpStream {
let mut stream = TcpStream::connect(door).await.expect("door connect");
stream
.write_all(&[0x05, 0x01, 0x02])
.await
.expect("greeting");
let mut chosen = [0u8; 2];
stream
.read_exact(&mut chosen)
.await
.expect("method selected");
assert_eq!(chosen, [0x05, 0x02], "password auth selected");
let mut packet = vec![0x01, 7];
packet.extend_from_slice(b"vanilla");
packet.push(7);
packet.extend_from_slice(b"hunter2");
stream.write_all(&packet).await.expect("creds sent");
let mut status = [0u8; 2];
stream.read_exact(&mut status).await.expect("status");
assert_eq!(status, [0x01, 0x00], "RFC 1929 accepted");
stream
}
struct ComposedSuite {
topo: harness::BindFirstTopology,
queue: harness::ComposedAcceptQueue,
dial_count: Arc<std::sync::atomic::AtomicUsize>,
}
async fn wire_composed_suite() -> ComposedSuite {
init_trace();
let resource_registry = alktunnels::producer::ResourceRegistry::new();
resource_registry
.register(
COMPOSED_RESOURCE,
alktunnels::params::Substrate::Tcp,
"assembly-owned",
)
.await;
let queue = harness::composed_fresh_queue();
let dial_count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let dial = {
let dial_count = Arc::clone(&dial_count);
Arc::new(
move |substrate: alktunnels::params::Substrate,
backing: &str|
-> futures::future::BoxFuture<
'static,
Result<alktunnels::producer::TargetHandle, alktunnels::TunnelEstablishError>,
> {
let dial_count = Arc::clone(&dial_count);
let backing = backing.to_string();
Box::pin(async move {
use alktunnels::params::Substrate as S;
use alktunnels::producer::TargetHandle as TunnelTargetHandle;
use alktunnels::TunnelEstablishError;
dial_count.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
match substrate {
S::Tcp => {
let (host, port) = backing.rsplit_once(':').ok_or_else(|| {
TunnelEstablishError::DialFailed("bad target".into())
})?;
let addr: SocketAddr =
format!("{host}:{port}").parse().map_err(|e| {
TunnelEstablishError::DialFailed(format!("parse: {e}"))
})?;
let stream = TcpStream::connect(addr).await.map_err(|e| {
TunnelEstablishError::DialFailed(format!("connect: {e}"))
})?;
let (r, w) = stream.into_split();
Ok(TunnelTargetHandle {
read: Box::new(r),
write: Box::new(w),
})
}
S::Udp => {
let (consumer_side, target_side) = tokio::io::duplex(64 * 1024);
tokio::spawn(async move {
use tokio::io::AsyncReadExt;
let (mut t_read, mut t_write) = tokio::io::split(target_side);
let mut reader = alktunnels::wire::DatagramReader::new();
let mut buf = vec![0u8; 16 * 1024];
loop {
let n = match t_read.read(&mut buf).await {
Ok(0) | Err(_) => return,
Ok(n) => n,
};
for dg in reader.feed(&buf[..n]) {
let framed = match alktunnels::wire::frame_datagram(&dg) {
Ok(f) => f,
Err(_) => return,
};
if t_write.write_all(&framed).await.is_err() {
return;
}
let _ = t_write.flush().await;
}
}
});
let (c_read, c_write) = tokio::io::split(consumer_side);
Ok(TunnelTargetHandle {
read: Box::new(c_read),
write: Box::new(c_write),
})
}
other => Err(TunnelEstablishError::DialFailed(format!(
"substrate not wired: {other:?}"
))),
}
})
},
) as alktunnels::producer::DialFn
};
let topo = wire_bindfirst_composed(
resource_registry,
harness::composed_test_bind(),
harness::composed_queue_loop(queue.clone()),
None, composed_full_opener_identity(),
)
.await;
let registry = register_forwarded_serving(
&ChannelCore::new(
topo.opener.manager().clone(),
alkcall::channels::policy::default_policy(),
),
&topo.opener_registry,
alkcall::core::auth::AuthContext::anonymous(b"alk/tunnel"),
)
.expect("register forwarded openable");
let factory = bindfirst_accept(topo.opener.clone(), registry, COMPOSED_RESOURCE);
let core = ChannelCore::new(
topo.producer_manager.clone(),
alkcall::channels::policy::default_policy(),
);
alksocks::producer::Socks5Producer::builder()
.dial(alksocks::local::tcp_dial())
.egress(direct_egress(topo.opener.clone(), 8))
.accept(factory)
.build()
.register(
&core,
&topo.producer_registry,
alkcall::core::auth::AuthContext::anonymous(b"alk/socks5"),
None,
)
.expect("register socks5 openable (the composed suite's producer)");
let direct_core = ChannelCore::new(
topo.producer_manager.clone(),
alkcall::channels::policy::default_policy(),
);
alktunnels::producer::register_tunnel_direct_openable(
&direct_core,
&topo.producer_registry,
alkcall::core::auth::AuthContext::anonymous(b"alk/tunnel"),
dial,
None,
)
.expect("register the tunnel direct op (the composed suite)");
ComposedSuite {
topo,
queue,
dial_count,
}
}
#[tokio::test]
async fn full_dash_d_story_one_registration_three_commands_through_the_doors() {
init_trace();
let tcp_target = echo_tcp_server().await;
let udp_target = echo_udp_server().await;
let tcp_dial = alksocks::local::tcp_dial();
let tcp_topo = wire_consumer_side_with_provider(
tcp_dial,
Some(alksocks::local::udp_egress()),
Some(alksocks::local::bind_accept([127, 0, 0, 1])),
Some(harness::consumer_identity()),
Arc::new(DoorTokenProvider {
bind_scoped: vec!["door-vanilla".to_string()],
}),
)
.await;
let udp_dial = alksocks::local::tcp_dial();
let udp_topo = wire_consumer_side_with_provider(
udp_dial,
Some(alksocks::local::udp_egress()),
Some(alksocks::local::bind_accept([127, 0, 0, 1])),
Some(harness::consumer_identity()),
Arc::new(DoorTokenProvider {
bind_scoped: vec!["door-vanilla".to_string()],
}),
)
.await;
let tcp_listener = TcpListener::bind("127.0.0.1:0").await.expect("bind");
let door_addr = tcp_listener.local_addr().expect("door addr");
let door = FrontDoor::builder()
.credentials(cred_table())
.bind(door_addr)
.build()
.expect("the door builds");
let _tcp_serve = tokio::spawn({
let client = Arc::clone(&tcp_topo.consumer);
async move {
door.serve_on(tcp_listener, client)
.await
.expect("the door serves")
}
});
let udp_relay = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay");
let udp_relay_addr = udp_relay.local_addr().expect("relay addr");
let udp_door = UdpFrontDoor::builder()
.bind(udp_relay_addr)
.build()
.expect("the door builds");
let _udp_serve = tokio::spawn({
let client = Arc::clone(&udp_topo.consumer);
async move {
udp_door
.serve_on(udp_relay, client)
.await
.expect("the udp door serves")
}
});
let mut connect_stream = timeout(BOUND, vanilla_with_creds(door_addr))
.await
.expect("the cred conversation");
let mut req = vec![0x05, 0x01, 0x00, 0x01];
let SocketAddr::V4(v4) = tcp_target else {
panic!("v4 target")
};
req.extend_from_slice(&v4.ip().octets());
req.extend_from_slice(&v4.port().to_be_bytes());
connect_stream
.write_all(&req)
.await
.expect("connect request");
let mut head = [0u8; 4];
timeout(BOUND, connect_stream.read_exact(&mut head))
.await
.expect("reply#1")
.expect("read");
assert_eq!(
head[1],
consts::SOCKS5_REPLY_SUCCEEDED,
"CONNECT through the door"
);
let mut connect_bnd = [0u8; 6];
timeout(BOUND, connect_stream.read_exact(&mut connect_bnd))
.await
.expect("the CONNECT reply's BND.ADDR")
.expect("read");
let mut bind_stream = timeout(BOUND, vanilla_with_creds(door_addr))
.await
.expect("the second cred conversation");
bind_stream
.write_all(&[0x05, 0x02, 0x00, 0x01, 0, 0, 0, 0, 0, 0])
.await
.expect("bind request");
let mut head1 = [0u8; 4];
timeout(BOUND, bind_stream.read_exact(&mut head1))
.await
.expect("bind reply#1")
.expect("read");
assert_eq!(
head1[1],
consts::SOCKS5_REPLY_SUCCEEDED,
"BIND through the door"
);
let mut bnd = [0u8; 6];
timeout(BOUND, bind_stream.read_exact(&mut bnd))
.await
.expect("reply#1 BND.ADDR")
.expect("read");
let advertised = SocketAddr::from((
[bnd[0], bnd[1], bnd[2], bnd[3]],
u16::from_be_bytes([bnd[4], bnd[5]]),
));
assert!(
advertised.port() != 0,
"reply#1 carries the real listener: {advertised}"
);
let mut app = timeout(BOUND, TcpStream::connect(advertised))
.await
.expect("dial the advertised address")
.expect("app connected");
app.write_all(b"vanilla-bind").await.expect("app writes");
{
let mut head2 = [0u8; 4];
timeout(BOUND, bind_stream.read_exact(&mut head2))
.await
.expect("bind reply#2 head")
.expect("read");
assert_eq!(head2[1], consts::SOCKS5_REPLY_SUCCEEDED, "BIND reply#2");
let mut peer_addr = [0u8; 6];
timeout(BOUND, bind_stream.read_exact(&mut peer_addr))
.await
.expect("reply#2 DST.ADDR")
.expect("read");
let port = u16::from_be_bytes([peer_addr[4], peer_addr[5]]);
assert_eq!(
port,
app.local_addr().expect("app local").port(),
"reply#2 carries the real peer"
);
}
let client_sock = UdpSocket::bind("127.0.0.1:0").await.expect("client socket");
client_sock
.send_to(&rfc_datagram(udp_target, b"udp-door"), udp_relay_addr)
.await
.expect("vanilla sendto the relay");
let mut rx = vec![0u8; 65535];
let (n, from) = timeout(BOUND, client_sock.recv_from(&mut rx))
.await
.expect("udp reply within bound")
.expect("recv");
assert_eq!(
from, udp_relay_addr,
"the reply comes from the relay socket"
);
let (src, payload) = parse_rfc_datagram(&rx[..n]);
assert_eq!(src, udp_target, "the echo reports the target");
assert_eq!(payload, b"udp-door");
connect_stream
.write_all(b"tcp-live")
.await
.expect("tcp write");
let mut buf = [0u8; 8];
timeout(BOUND, connect_stream.read_exact(&mut buf))
.await
.expect("tcp echo")
.expect("read");
assert_eq!(&buf, b"tcp-live");
let mut app_buf = [0u8; 12];
timeout(BOUND, bind_stream.read_exact(&mut app_buf))
.await
.expect("bind data plane")
.expect("read");
assert_eq!(&app_buf, b"vanilla-bind");
client_sock
.send_to(&rfc_datagram(udp_target, b"udp-live"), udp_relay_addr)
.await
.expect("send");
let (n, _) = timeout(BOUND, client_sock.recv_from(&mut rx))
.await
.expect("udp echo")
.expect("recv");
assert_eq!(parse_rfc_datagram(&rx[..n]).1, b"udp-live");
drop(connect_stream);
drop(bind_stream);
drop(app);
client_sock
.send_to(&rfc_datagram(udp_target, b"still"), udp_relay_addr)
.await
.expect("send after the tcp teardown");
let (n, _) = timeout(BOUND, client_sock.recv_from(&mut rx))
.await
.expect("post-teardown echo")
.expect("recv");
assert_eq!(parse_rfc_datagram(&rx[..n]).1, b"still");
let (mut fresh, code, _) = timeout(BOUND, vanilla_cred_connect(door_addr, tcp_target))
.await
.expect("fresh CONNECT through the door");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
fresh.write_all(b"again").await.expect("write");
let mut buf = [0u8; 5];
timeout(BOUND, fresh.read_exact(&mut buf))
.await
.expect("echo")
.expect("read");
assert_eq!(&buf, b"again");
client_sock
.send_to(&rfc_datagram(udp_target, b"fresh"), udp_relay_addr)
.await
.expect("send fresh");
let (n, _) = timeout(BOUND, client_sock.recv_from(&mut rx))
.await
.expect("fresh udp echo")
.expect("recv");
assert_eq!(parse_rfc_datagram(&rx[..n]).1, b"fresh");
}
#[tokio::test]
async fn door_client_disconnect_tears_the_association_down_no_leaks() {
init_trace();
let udp_target = echo_udp_server().await;
let topo = wire_consumer_side_with(
alksocks::local::tcp_dial(),
Some(alksocks::local::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 socket");
client
.send_to(&rfc_datagram(udp_target, b"pre"), relay_addr)
.await
.expect("send");
let mut rx = vec![0u8; 65535];
let (n, _) = timeout(BOUND, client.recv_from(&mut rx))
.await
.expect("reply")
.expect("recv");
assert_eq!(parse_rfc_datagram(&rx[..n]).1, b"pre");
assert!(
topo.consumer
.manager()
.channel_ids()
.iter()
.any(|&id| id != 0),
"the association rode a channel"
);
drop(client);
timeout(Duration::from_secs(35), serve)
.await
.expect("the door's task ended on the idle bound")
.expect("clean exit");
tokio::time::sleep(Duration::from_millis(200)).await;
assert!(
topo.consumer
.manager()
.channel_ids()
.iter()
.all(|&id| id == 0),
"the teardown reaped the association's channel: {:?}",
topo.consumer.manager().channel_ids()
);
let relay2 = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay 2");
let relay2_addr = relay2.local_addr().expect("relay addr 2");
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 client2 = UdpSocket::bind("127.0.0.1:0").await.expect("client 2");
client2
.send_to(&rfc_datagram(udp_target, b"fresh"), relay2_addr)
.await
.expect("send");
let (n, _) = timeout(BOUND, client2.recv_from(&mut rx))
.await
.expect("fresh reply")
.expect("recv");
assert_eq!(parse_rfc_datagram(&rx[..n]).1, b"fresh");
}
#[tokio::test]
async fn composed_story_all_three_commands_on_one_registration() {
let suite = wire_composed_suite().await;
let tcp_target = echo_tcp_server().await;
let mut session = timeout(
BOUND,
Socks5ChannelSession::connect(suite.topo.opener.as_ref(), TargetAddr::Ip(tcp_target)),
)
.await
.expect("connect within bound")
.expect("the CONNECT session");
let (mut read, mut write) = session.split().expect("pipe");
write.write_all(b"composed-connect").await.expect("write");
write.flush().await.expect("flush");
let mut buf = [0u8; 16];
timeout(BOUND, read.read_exact(&mut buf))
.await
.expect("echo")
.expect("read");
assert_eq!(&buf, b"composed-connect");
let mut assoc = timeout(
BOUND,
AssociateSession::associate(suite.topo.opener.as_ref()),
)
.await
.expect("associate within bound")
.expect("the association");
let dest = TargetAddr::Ip(SocketAddr::from(([10, 9, 9, 9], 5353)));
assoc
.send_datagram(&dest, b"composed-udp")
.await
.expect("send");
let (src, payload) = timeout(BOUND, assoc.recv_datagram())
.await
.expect("recv within bound")
.expect("a datagram")
.expect("a datagram (not eof)");
assert_eq!(src, dest, "the echo reports the destination");
assert_eq!(&payload[..], b"composed-udp");
assert_eq!(
suite.dial_count.load(std::sync::atomic::Ordering::SeqCst),
1,
"one direct-op dial for the one destination"
);
let mut bind = timeout(
BOUND,
BindSession::bind(
suite.topo.opener.as_ref(),
("bind.test", 2121).to_target_addr().unwrap(),
),
)
.await
.expect("bind within bound")
.expect("the BIND session");
let bind_addr = bind.reply1().expect("reply#1").bind_addr;
assert_eq!(
bind_addr,
TargetAddr::Ip(SocketAddr::from(([127, 0, 0, 1], COMPOSED_BOUND_PORT))),
"reply#1 advertises the real OS-chosen bound"
);
composed_push_accept(&suite.queue, ("10.0.0.1", COMPOSED_PEER_PORT)).await;
let mut plane = timeout(BOUND, bind.await_inbound())
.await
.expect("reply#2")
.expect("await_inbound ok")
.expect("the plane");
assert_eq!(
plane.peer,
TargetAddr::Ip(SocketAddr::from(([10, 0, 0, 1], COMPOSED_PEER_PORT))),
"reply#2 carries the forwarded op's real peer"
);
plane.write_all(b"composed-bind").await.expect("write");
plane.flush().await.expect("flush");
let mut bbuf = [0u8; 13];
timeout(BOUND, plane.read_exact(&mut bbuf))
.await
.expect("round trip")
.expect("read");
assert_eq!(&bbuf, b"composed-bind");
drop(read);
drop(write);
drop(assoc);
drop(plane);
assert!(session.close().await, "the CONNECT channel reaped");
assert!(bind.close().await, "the BIND channel reaped");
}
#[tokio::test]
async fn mixed_assembly_channels_native_and_door_clients_concurrent() {
init_trace();
let tcp_target = echo_tcp_server().await;
let udp_target = echo_udp_server().await;
let topo = wire_consumer_side_with_provider(
alksocks::local::tcp_dial(),
Some(alksocks::local::udp_egress()),
Some(alksocks::local::bind_accept([127, 0, 0, 1])),
Some(harness::consumer_identity()),
Arc::new(DoorTokenProvider {
bind_scoped: vec!["door-mixed".to_string()],
}),
)
.await;
let door_topo = wire_consumer_side_with_provider(
alksocks::local::tcp_dial(),
Some(alksocks::local::udp_egress()),
Some(alksocks::local::bind_accept([127, 0, 0, 1])),
Some(harness::consumer_identity()),
Arc::new(DoorTokenProvider {
bind_scoped: vec!["door-mixed".to_string()],
}),
)
.await;
let udp_door_topo = wire_consumer_side_with_provider(
alksocks::local::tcp_dial(),
Some(alksocks::local::udp_egress()),
Some(alksocks::local::bind_accept([127, 0, 0, 1])),
Some(harness::consumer_identity()),
Arc::new(DoorTokenProvider {
bind_scoped: vec!["door-mixed".to_string()],
}),
)
.await;
let tcp_listener = TcpListener::bind("127.0.0.1:0").await.expect("bind");
let door_addr = tcp_listener.local_addr().expect("door addr");
let door = FrontDoor::builder()
.credentials(cred_table())
.bind(door_addr)
.build()
.expect("the door builds");
let _serve = tokio::spawn({
let client = Arc::clone(&door_topo.consumer);
async move {
door.serve_on(tcp_listener, client)
.await
.expect("the door serves")
}
});
let relay = UdpSocket::bind("127.0.0.1:0").await.expect("bind relay");
let relay_addr = relay.local_addr().expect("relay addr");
let udp_door = UdpFrontDoor::builder()
.bind(relay_addr)
.build()
.expect("the door builds");
let _udp_serve = tokio::spawn({
let client = Arc::clone(&udp_door_topo.consumer);
async move {
udp_door
.serve_on(relay, client)
.await
.expect("the udp door serves")
}
});
let native = async {
let mut s =
Socks5ChannelSession::connect(topo.consumer.as_ref(), TargetAddr::Ip(tcp_target))
.await
.map_err(|e| format!("native connect: {e}"))?;
let (mut read, mut write) = s.split().ok_or_else(|| "no pipe".to_string())?;
write
.write_all(b"native-tcp")
.await
.map_err(|e| e.to_string())?;
write.flush().await.map_err(|e| e.to_string())?;
let mut buf = [0u8; 10];
read.read_exact(&mut buf).await.map_err(|e| e.to_string())?;
if &buf != b"native-tcp" {
return Err("native connect echo mismatch".to_string());
}
drop(read);
drop(write);
s.close().await;
Ok(())
};
let native_udp = async {
let mut a = AssociateSession::associate(topo.consumer.as_ref())
.await
.map_err(|e| format!("native associate: {e}"))?;
let target = TargetAddr::Ip(udp_target);
a.send_datagram(&target, b"native-udp")
.await
.map_err(|e| e.to_string())?;
let (src, payload) = a
.recv_datagram()
.await
.map_err(|e| e.to_string())?
.ok_or_else(|| "eof before a datagram".to_string())?;
if src != target || &payload[..] != b"native-udp" {
return Err("native associate echo mismatch".to_string());
}
a.close().await;
Ok(())
};
let door_tcp = async {
let mut stream = timeout(BOUND, vanilla_with_creds(door_addr))
.await
.map_err(|e| format!("door connect: {e}"))?;
let mut req = vec![0x05, 0x01, 0x00, 0x01];
let SocketAddr::V4(v4) = tcp_target else {
panic!("v4")
};
req.extend_from_slice(&v4.ip().octets());
req.extend_from_slice(&v4.port().to_be_bytes());
stream.write_all(&req).await.map_err(|e| e.to_string())?;
let mut head = [0u8; 4];
timeout(BOUND, stream.read_exact(&mut head))
.await
.map_err(|e| e.to_string())?
.map_err(|e| e.to_string())?;
if head[1] != consts::SOCKS5_REPLY_SUCCEEDED {
return Err(format!("door connect reply {}", head[1]));
}
let mut bnd = [0u8; 6];
timeout(BOUND, stream.read_exact(&mut bnd))
.await
.map_err(|e| e.to_string())?
.map_err(|e| e.to_string())?;
stream
.write_all(b"door-tcp")
.await
.map_err(|e| e.to_string())?;
let mut buf = [0u8; 8];
timeout(BOUND, stream.read_exact(&mut buf))
.await
.map_err(|e| e.to_string())?
.map_err(|e| e.to_string())?;
if &buf != b"door-tcp" {
return Err("door connect echo mismatch".to_string());
}
Ok(())
};
let door_udp = async {
let sock = UdpSocket::bind("127.0.0.1:0")
.await
.map_err(|e| e.to_string())?;
sock.send_to(&rfc_datagram(udp_target, b"door-udp"), relay_addr)
.await
.map_err(|e| e.to_string())?;
let mut rx = vec![0u8; 65535];
let (n, _) = timeout(BOUND, sock.recv_from(&mut rx))
.await
.map_err(|e| format!("door udp recv: {e}"))?
.map_err(|e| e.to_string())?;
if parse_rfc_datagram(&rx[..n]).1 != b"door-udp" {
return Err("door udp echo mismatch".to_string());
}
Ok(())
};
let (r1, r2, r3, r4) = tokio::join!(native, native_udp, door_tcp, door_udp);
r1.expect("native connect");
r2.expect("native associate");
r3.expect("door connect");
r4.expect("door associate");
let (mut fresh, code, _) = timeout(BOUND, vanilla_cred_connect(door_addr, tcp_target))
.await
.expect("fresh door connect");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
fresh.write_all(b"again").await.expect("write");
let mut buf = [0u8; 5];
timeout(BOUND, fresh.read_exact(&mut buf))
.await
.expect("echo")
.expect("read");
assert_eq!(&buf, b"again");
let mut fresh_native =
Socks5ChannelSession::connect(topo.consumer.as_ref(), TargetAddr::Ip(tcp_target))
.await
.expect("fresh native connect");
let (mut read, mut write) = fresh_native.split().expect("pipe");
write.write_all(b"again2").await.expect("write");
let mut buf2 = [0u8; 6];
timeout(BOUND, read.read_exact(&mut buf2))
.await
.expect("echo")
.expect("read");
assert_eq!(&buf2, b"again2");
drop(read);
drop(write);
fresh_native.close().await;
}
#[tokio::test]
async fn mixed_assembly_composed_seams_beside_channels_native_sessions() {
let suite = wire_composed_suite().await;
let tcp_target = echo_tcp_server().await;
let mut connect = timeout(
BOUND,
Socks5ChannelSession::connect(suite.topo.opener.as_ref(), TargetAddr::Ip(tcp_target)),
)
.await
.expect("connect")
.expect("the CONNECT session");
let mut assoc = timeout(
BOUND,
AssociateSession::associate(suite.topo.opener.as_ref()),
)
.await
.expect("associate")
.expect("the association");
let mut bind = timeout(
BOUND,
BindSession::bind(
suite.topo.opener.as_ref(),
("bind.test", 2121).to_target_addr().unwrap(),
),
)
.await
.expect("bind")
.expect("the BIND session");
composed_push_accept(&suite.queue, ("10.0.0.1", COMPOSED_PEER_PORT)).await;
let mut plane = timeout(BOUND, bind.await_inbound())
.await
.expect("reply#2")
.expect("await_inbound ok")
.expect("the plane");
plane.write_all(b"mid").await.expect("write");
let mut bbuf = [0u8; 3];
timeout(BOUND, plane.read_exact(&mut bbuf))
.await
.expect("echo")
.expect("read");
assert_eq!(&bbuf, b"mid");
let (mut read, mut write) = connect.split().expect("pipe");
write.write_all(b"alive").await.expect("write");
let mut buf = [0u8; 5];
timeout(BOUND, read.read_exact(&mut buf))
.await
.expect("echo")
.expect("read");
assert_eq!(&buf, b"alive");
let dest = TargetAddr::Ip(SocketAddr::from(([10, 9, 9, 9], 5353)));
assoc.send_datagram(&dest, b"alive").await.expect("send");
let (src, payload) = timeout(BOUND, assoc.recv_datagram())
.await
.expect("recv")
.expect("a datagram")
.expect("a datagram (not eof)");
assert_eq!(src, dest);
assert_eq!(&payload[..], b"alive");
drop(read);
drop(write);
drop(assoc);
drop(plane);
assert!(connect.close().await);
assert!(bind.close().await);
let mut fresh = timeout(
BOUND,
Socks5ChannelSession::connect(suite.topo.opener.as_ref(), TargetAddr::Ip(tcp_target)),
)
.await
.expect("fresh connect")
.expect("the fresh session");
let (mut read, mut write) = fresh.split().expect("pipe");
write.write_all(b"again").await.expect("write");
let mut buf = [0u8; 5];
timeout(BOUND, read.read_exact(&mut buf))
.await
.expect("echo")
.expect("read");
assert_eq!(&buf, b"again");
drop(read);
drop(write);
fresh.close().await;
for _ in 0..50 {
tokio::time::sleep(Duration::from_millis(100)).await;
let ids = suite.topo.producer_manager.channel_ids();
if ids.iter().all(|&id| id == 0 || id % 2 == 1) {
break;
}
}
harness::assert_composed_channels(&suite.topo, &[0], &[]).await;
}
#[tokio::test]
async fn producer_door_one_registration_three_commands() {
init_trace();
let tcp_target = echo_tcp_server().await;
let udp_target = echo_udp_server().await;
let producer = alksocks::producer::Socks5Producer::builder()
.dial(alksocks::local::tcp_dial())
.egress(alksocks::local::udp_egress())
.accept(alksocks::local::bind_accept([127, 0, 0, 1]))
.build();
let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind");
let door_addr = listener.local_addr().expect("door addr");
let door = alksocks::local::ProducerDoor::builder()
.producer(producer)
.build()
.expect("the door builds");
let _guard = door.serve_on(listener).await;
let (mut connect_stream, code, _) =
timeout(BOUND, vanilla_no_auth_connect(door_addr, tcp_target))
.await
.expect("connect round trip");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
let mut bind_stream = TcpStream::connect(door_addr).await.expect("door connect");
bind_stream
.write_all(&[0x05, 0x01, 0x00])
.await
.expect("greeting");
let mut chosen = [0u8; 2];
timeout(BOUND, bind_stream.read_exact(&mut chosen))
.await
.expect("method selection")
.expect("read");
assert_eq!(chosen, [0x05, 0x00]);
bind_stream
.write_all(&[0x05, 0x02, 0x00, 0x01, 0, 0, 0, 0, 0, 0])
.await
.expect("bind request");
let mut head1 = [0u8; 4];
timeout(BOUND, bind_stream.read_exact(&mut head1))
.await
.expect("bind reply#1")
.expect("read");
assert_eq!(head1[1], consts::SOCKS5_REPLY_SUCCEEDED);
let mut bnd = [0u8; 6];
timeout(BOUND, bind_stream.read_exact(&mut bnd))
.await
.expect("reply#1 BND.ADDR")
.expect("read");
let advertised = SocketAddr::from((
[bnd[0], bnd[1], bnd[2], bnd[3]],
u16::from_be_bytes([bnd[4], bnd[5]]),
));
let mut app = timeout(BOUND, TcpStream::connect(advertised))
.await
.expect("dial the advertised address")
.expect("app connected");
app.write_all(b"prod-bind").await.expect("app writes");
let mut head2 = [0u8; 4];
timeout(BOUND, bind_stream.read_exact(&mut head2))
.await
.expect("bind reply#2")
.expect("read");
assert_eq!(head2[1], consts::SOCKS5_REPLY_SUCCEEDED, "BIND reply#2");
let mut peer_addr = [0u8; 6];
timeout(BOUND, bind_stream.read_exact(&mut peer_addr))
.await
.expect("reply#2 DST.ADDR")
.expect("read");
assert_eq!(
u16::from_be_bytes([peer_addr[4], peer_addr[5]]),
app.local_addr().expect("app local").port(),
"reply#2 carries the real peer"
);
let mut assoc_stream = TcpStream::connect(door_addr).await.expect("door connect");
assoc_stream
.write_all(&[0x05, 0x01, 0x00])
.await
.expect("greeting");
let mut chosen3 = [0u8; 2];
timeout(BOUND, assoc_stream.read_exact(&mut chosen3))
.await
.expect("method selection")
.expect("read");
assert_eq!(chosen3, [0x05, 0x00]);
assoc_stream
.write_all(&[0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0])
.await
.expect("associate request");
let mut head3 = [0u8; 4];
timeout(BOUND, assoc_stream.read_exact(&mut head3))
.await
.expect("associate reply")
.expect("read");
assert_eq!(
head3[1],
consts::SOCKS5_REPLY_SUCCEEDED,
"ASSOCIATE through the door"
);
let mut bnd3 = [0u8; 6];
timeout(BOUND, assoc_stream.read_exact(&mut bnd3))
.await
.expect("the sentinel BND.ADDR")
.expect("read");
assert_eq!(&bnd3, &[127, 0, 0, 1, 0, 0], "the sentinel");
connect_stream.write_all(b"prod-tcp").await.expect("write");
let mut buf = [0u8; 8];
timeout(BOUND, connect_stream.read_exact(&mut buf))
.await
.expect("tcp echo")
.expect("read");
assert_eq!(&buf, b"prod-tcp");
let mut app_buf = [0u8; 9];
timeout(BOUND, bind_stream.read_exact(&mut app_buf))
.await
.expect("bind data plane")
.expect("read");
assert_eq!(&app_buf, b"prod-bind");
let dst = TargetAddr::Ip(udp_target);
let frame = alksocks::wire::frame(&dst, b"prod-udp").expect("frame");
assoc_stream.write_all(&frame).await.expect("datagram");
let mut reply = vec![0u8; frame.len()];
timeout(BOUND, assoc_stream.read_exact(&mut reply))
.await
.expect("udp echo")
.expect("read");
let n = u16::from_be_bytes([reply[0], reply[1]]) as usize;
let inner = &reply[2..2 + n];
let atyp = inner[0];
let addr_len = match atyp {
0x01 => 1 + 4 + 2,
0x03 => 1 + 1 + inner[1] as usize + 2,
0x04 => 1 + 16 + 2,
other => panic!("unexpected ATYP {other}"),
};
assert_eq!(&inner[addr_len..], b"prod-udp", "the datagram echoed");
drop(connect_stream);
drop(app);
let (_, code, _) = timeout(BOUND, vanilla_no_auth_connect(door_addr, tcp_target))
.await
.expect("fresh connect after the teardown");
assert_eq!(
code,
consts::SOCKS5_REPLY_SUCCEEDED,
"no connection poisoning"
);
}
#[tokio::test]
async fn dash_d_domain_targets_resolve_at_the_producer_seam() {
init_trace();
let tcp_target = echo_tcp_server().await;
let topo = wire_consumer_side_with(
alksocks::local::tcp_dial(),
None,
None,
Some(harness::consumer_identity()),
)
.await;
let listener = TcpListener::bind("127.0.0.1:0").await.expect("bind");
let door_addr = listener.local_addr().expect("door addr");
let door = FrontDoor::builder()
.bind(door_addr)
.build()
.expect("the door builds");
let _serve = tokio::spawn({
let client = Arc::clone(&topo.consumer);
async move {
door.serve_on(listener, client)
.await
.expect("the door serves")
}
});
let mut stream = TcpStream::connect(door_addr).await.expect("door connect");
stream
.write_all(&[0x05, 0x01, 0x00])
.await
.expect("greeting");
let mut chosen = [0u8; 2];
stream
.read_exact(&mut chosen)
.await
.expect("method selection");
assert_eq!(chosen, [0x05, 0x00]);
let domain = "localhost".to_owned();
let mut req = vec![0x05, 0x01, 0x00, 0x03, domain.len() as u8];
req.extend_from_slice(domain.as_bytes());
req.extend_from_slice(&tcp_target.port().to_be_bytes());
stream.write_all(&req).await.expect("request");
let mut head = [0u8; 4];
timeout(BOUND, stream.read_exact(&mut head))
.await
.expect("reply")
.expect("read");
assert_eq!(
head[1],
consts::SOCKS5_REPLY_SUCCEEDED,
"the domain resolved"
);
let mut bnd = [0u8; 6];
timeout(BOUND, stream.read_exact(&mut bnd))
.await
.expect("BND.ADDR")
.expect("read");
stream.write_all(b"dom").await.expect("write");
let mut buf = [0u8; 3];
timeout(BOUND, stream.read_exact(&mut buf))
.await
.expect("echo")
.expect("read");
assert_eq!(&buf, b"dom");
}