#![allow(dead_code)]
use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
use bytes::Bytes;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use alkcall::channels::adapter::{ChannelsAdapter, InstallChannelZero};
use alkcall::channels::client::{ChannelClient, ServingConfig};
use alkcall::channels::manager::ChannelManager;
use alkcall::channels::operations::{ChannelCore, ChannelOperations};
use alkcall::channels::pump::pump_bidi;
use alkcall::core::auth::{AuthContext, AuthToken, Identity, IdentityProvider};
use alkcall::core::types::Connection as CoreConnection;
use alkcall::protocol::connection::{split_single_stream, CallConnection};
use alkcall::protocol::dispatch::Dispatcher;
use alkcall::registry::registration::OperationRegistry;
use alksocks::egress::{
BindAccept, BindAcceptError, BindAcceptFactory, BindTargetHandle, DialFn, EgressError,
EgressFactory, SocksDialError,
};
use alksocks::params::{
socks5_open_spec, OP_SOCKS5_OPEN, SOCKS5_ALPN, SOCKS5_BIND_SCOPE, SOCKS5_OPEN_SCOPE,
};
use alksocks::rfc1928::consts;
use alksocks::rfc1928::server::{new_reply, Socks5ServerProtocol, SocksServerError};
use alksocks::rfc1928::util::target_addr::TargetAddr;
use alksocks::rfc1928::{ReplyError, Socks5Command, SocksError};
use alksocks::Socks5Params;
#[path = "common/mod.rs"]
#[allow(unused_imports)]
pub mod common;
#[allow(unused_imports)]
pub use common::{
datagram_frame as udp_frame, decode_datagram as udp_unframe, doubled_echo_egress_factory,
echo_dial, echo_dial_witnessed as witnessing_echo_dial, echo_egress_factory as echo_egress,
parked_accept_factory, raw_greet, raw_read_reply, raw_request, read_reply_v4, refusing_dial,
refusing_egress, write_request, QueueEgress,
};
pub fn consumer_identity() -> Identity {
Identity {
id: "consumer".to_string(),
scopes: vec![SOCKS5_OPEN_SCOPE.to_string()],
resources: HashMap::new(),
}
}
pub fn bind_identity() -> Identity {
Identity {
id: "consumer-bind".to_string(),
scopes: vec![SOCKS5_BIND_SCOPE.to_string()],
resources: HashMap::new(),
}
}
pub fn both_scopes_identity() -> Identity {
Identity {
id: "consumer-both".to_string(),
scopes: vec![SOCKS5_OPEN_SCOPE.to_string(), SOCKS5_BIND_SCOPE.to_string()],
resources: HashMap::new(),
}
}
pub fn bind_scoped_identity() -> Identity {
both_scopes_identity()
}
pub const TEST_AUTH_TOKEN: &str = "test-token-with-socks5-open-scope";
pub struct TestIdProvider;
impl IdentityProvider for TestIdProvider {
fn resolve_from_fingerprint(&self, _: &str) -> Option<Identity> {
None
}
fn resolve_from_token(&self, token: &AuthToken) -> Option<Identity> {
if token.raw == TEST_AUTH_TOKEN.as_bytes() {
Some(consumer_identity())
} else {
None
}
}
}
pub const ECHO_BIND_PORT: u16 = 2121;
pub fn echo_accept() -> BindAcceptFactory {
Arc::new(move |_target: TargetAddr| {
Box::pin(async move {
let bind_addr =
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), ECHO_BIND_PORT);
let accept_future = async move {
let (mut echo_end, target_end) = tokio::io::duplex(64 * 1024);
tokio::spawn(async move {
loop {
let mut buf = vec![0u8; 4096];
match echo_end.read(&mut buf).await {
Ok(0) | Err(_) => return,
Ok(n) => {
if echo_end.write_all(&buf[..n]).await.is_err() {
return;
}
}
}
}
});
let (read_half, write_half) = tokio::io::split(target_end);
Ok(BindTargetHandle {
peer: TargetAddr::Ip(SocketAddr::from(([10, 0, 0, 7], 44444))),
read: Box::new(read_half) as Box<dyn AsyncRead + Send + Sync + Unpin>,
write: Box::new(write_half) as Box<dyn AsyncWrite + Send + Sync + Unpin>,
})
};
Ok(BindAccept::new(bind_addr, accept_future))
})
})
}
pub fn refusing_accept_factory(err: alksocks::egress::BindAcceptFactoryError) -> BindAcceptFactory {
Arc::new(move |_target: TargetAddr| {
use alksocks::egress::BindAcceptFactoryError as E;
let err = match &err {
E::Refused(m) => E::Refused(m.clone()),
E::NotAllowed(m) => E::NotAllowed(m.clone()),
E::Shortage(m) => E::Shortage(m.clone()),
E::General(m) => E::General(m.clone()),
};
Box::pin(async move { Err(err) })
})
}
pub fn refusing_accept(err: BindAcceptError) -> BindAcceptFactory {
Arc::new(move |_target: TargetAddr| {
use BindAcceptError as E;
let err = match &err {
E::Refused(m) => E::Refused(m.clone()),
E::NotAllowed(m) => E::NotAllowed(m.clone()),
E::General(m) => E::General(m.clone()),
};
Box::pin(async move {
let bind_addr =
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), ECHO_BIND_PORT);
Ok(BindAccept::new(bind_addr, async move { Err(err) }))
})
})
}
pub type IdentityWitness = Arc<tokio::sync::Mutex<Option<String>>>;
pub fn socks5_session_establisher(
witness: IdentityWitness,
) -> alkcall::channels::operations::OpenEstablisher {
Arc::new(move |_input: serde_json::Value, auth: AuthContext| {
let witness = Arc::clone(&witness);
Box::pin(async move {
*witness.lock().await = auth.identity.as_ref().map(|i| i.id.clone());
Ok(alkcall::channels::operations::Establishment::default())
})
})
}
async fn serve_connect_minimal<T>(stream: T, dial: DialFn) -> Result<(), SocksError>
where
T: AsyncRead + AsyncWrite + Unpin,
{
let (proto, cmd, target) = Socks5ServerProtocol::accept_no_auth(stream)
.await
.map_err(SocksError::ServerError)?
.read_command()
.await
.map_err(SocksError::ServerError)?;
if cmd != Socks5Command::TCPConnect {
proto
.reply_error(&ReplyError::CommandNotSupported)
.await
.map_err(SocksError::ServerError)?;
return Err(SocksError::ServerError(SocksServerError::UnknownCommand(
cmd.as_u8(),
)));
}
let handle = match dial(target).await {
Ok(handle) => handle,
Err(err) => {
proto
.reply_error(&err.to_reply_error())
.await
.map_err(SocksError::ServerError)?;
return Err(SocksError::ServerError(SocksServerError::Refused(
"dial refused in-stream",
)));
}
};
let inner = proto
.reply_success(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0))
.await
.map_err(SocksError::ServerError)?;
pump_bidi(inner, handle.read, handle.write).await;
Ok(())
}
fn make_minimal_connect_handler(dial: DialFn) -> alkcall::channels::operations::OpenHandler {
Arc::new(
move |_input: serde_json::Value,
_plan: Option<alkcall::channels::operations::ChannelPlan>,
conn: CoreConnection,
_auth: AuthContext| {
let dial = Arc::clone(&dial);
tokio::spawn(async move {
let Some(bidi) = conn.accept_bi().await.ok() else {
tracing::error!("socks5 pump: accept_bi failed");
return;
};
if let Err(err) = serve_connect_minimal(bidi, dial).await {
tracing::warn!("socks5 session ended: {err:#}");
}
})
},
)
}
#[derive(Clone)]
pub enum RegisterMode {
MinimalConnect(DialFn),
Producer {
dial: DialFn,
egress: Option<EgressFactory>,
accept: Option<BindAcceptFactory>,
},
ProducerBuilder {
dial: DialFn,
egress: Option<EgressFactory>,
accept: Option<BindAcceptFactory>,
bind_policy: Option<bool>,
},
NoSocks5,
}
pub fn register_socks5_openable_minimal(
core: &ChannelCore,
on_registry: &Arc<OperationRegistry>,
auth: AuthContext,
dial: DialFn,
witness: IdentityWitness,
) -> Result<(), String> {
core.register_openable_with_establisher(
socks5_open_spec(),
Some(socks5_session_establisher(witness)),
make_minimal_connect_handler(dial),
on_registry,
auth,
None,
)
}
pub struct Topology {
pub producer: Arc<ChannelClient>,
pub consumer_manager: ChannelManager,
pub consumer_call: Arc<CallConnection>,
pub producer_registry: Arc<OperationRegistry>,
pub consumer_registry: Arc<OperationRegistry>,
pub identity_witness: IdentityWitness,
}
pub async fn wire_with(
mode: RegisterMode,
consumer_identity: Option<Identity>,
serving_identity: Option<Identity>,
provider: Arc<dyn IdentityProvider>,
) -> Topology {
let producer_op_registry = Arc::new(OperationRegistry::new());
let consumer_serving_registry = Arc::new(OperationRegistry::new());
let (consumer_call_tx, mut consumer_call_rx) =
tokio::sync::mpsc::channel::<Arc<CallConnection>>(1);
let (consumer_manager_tx, mut consumer_manager_rx) =
tokio::sync::mpsc::channel::<ChannelManager>(1);
let identity_witness: IdentityWitness = Arc::new(tokio::sync::Mutex::new(None));
let fallback = Identity {
id: "consumer-fallback".to_string(),
scopes: vec![SOCKS5_OPEN_SCOPE.to_string()],
resources: HashMap::new(),
};
let resolved = Arc::new(consumer_identity.unwrap_or(fallback));
let resolved_for_hook = Arc::clone(&resolved);
let serving_registry_for_hook = Arc::clone(&consumer_serving_registry);
let install_hook: InstallChannelZero = Arc::new(
move |manager: ChannelManager, channel0_conn: CoreConnection, _auth: AuthContext| {
let consumer_call_tx = consumer_call_tx.clone();
let consumer_manager_tx = consumer_manager_tx.clone();
let serving_registry = Arc::clone(&serving_registry_for_hook);
let resolved = Arc::clone(&resolved_for_hook);
tokio::spawn(async move {
let channel0_bidi = match channel0_conn.accept_bi().await {
Ok(s) => s,
Err(_) => return,
};
let (writer, reader) = split_single_stream(channel0_bidi);
let _ = channel0_conn.set_identity((*resolved).clone());
let call_connection = Arc::new(CallConnection::new_single_stream(
channel0_conn,
Arc::clone(&writer),
));
let _ = consumer_call_tx.send(Arc::clone(&call_connection)).await;
let _ = consumer_manager_tx.send(manager).await;
let dp = Dispatcher::new(
serving_registry,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
);
dp.serve_single_stream(call_connection, reader, writer)
.await;
})
},
);
let (producer_end, consumer_end) = tokio::io::duplex(64 * 1024);
let producer_conn = CoreConnection::from_bidi(producer_end, b"alk/channels".to_vec(), None);
let consumer_conn = CoreConnection::from_bidi(consumer_end, b"alk/channels".to_vec(), None);
let identity_for_transport = (*resolved).clone();
producer_conn
.set_identity(identity_for_transport)
.expect("transport identity set once");
let adapter = ChannelsAdapter::new(install_hook, Arc::new(alkcall::channels::policy::NoCap));
let producer_transport_auth = AuthContext::anonymous(b"alk/channels");
let consumer_conn_for_adapter = consumer_conn;
let _adapter_task = tokio::spawn(async move {
let _ = alkcall::core::types::ProtocolHandler::handle(
&adapter,
consumer_conn_for_adapter,
&producer_transport_auth,
)
.await;
});
let producer_client = ChannelClient::from_connection_with_serving(
producer_conn,
Some(ServingConfig {
registry: Arc::clone(&producer_op_registry),
identity_provider: provider,
identity: serving_identity,
}),
)
.await
.expect("producer channel client init");
let producer_client = Arc::new(producer_client);
let ops = ChannelOperations::with_default_policy(producer_client.manager().clone());
ops.register_on(&producer_op_registry)
.expect("register channel ops on producer");
if let RegisterMode::MinimalConnect(dial) = &mode {
let core = ChannelCore::new(
producer_client.manager().clone(),
alkcall::channels::policy::default_policy(),
);
register_socks5_openable_minimal(
&core,
&producer_op_registry,
AuthContext::anonymous(b"alk/socks5"),
Arc::clone(dial),
Arc::clone(&identity_witness),
)
.expect("register socks5 openable (minimal CONNECT stand-in)");
}
if let RegisterMode::Producer {
dial,
egress,
accept,
} = &mode
{
let core = ChannelCore::new(
producer_client.manager().clone(),
alkcall::channels::policy::default_policy(),
);
alksocks::producer::register_socks5_openable(
&core,
&producer_op_registry,
AuthContext::anonymous(b"alk/socks5"),
Some(Arc::clone(dial)),
egress.clone(),
accept.clone(),
Some(Arc::clone(&identity_witness)),
)
.expect("register socks5 openable (the real producer)");
}
if let RegisterMode::ProducerBuilder {
dial,
egress,
accept,
bind_policy,
} = &mode
{
let core = ChannelCore::new(
producer_client.manager().clone(),
alkcall::channels::policy::default_policy(),
);
let mut producer = alksocks::producer::Socks5Producer::builder().dial(Arc::clone(dial));
if let Some(egress) = egress {
producer = producer.egress(egress.clone());
}
if let Some(accept) = accept {
producer = producer.accept(accept.clone());
}
if let Some(can_bind) = bind_policy {
producer = producer.bind_policy(*can_bind);
}
producer
.build()
.register(
&core,
&producer_op_registry,
AuthContext::anonymous(b"alk/socks5"),
Some(Arc::clone(&identity_witness)),
)
.expect("register socks5 openable (the spec'd builder shape)");
}
let consumer_call = consumer_call_rx.recv().await.expect("consumer call conn");
let consumer_manager = consumer_manager_rx.recv().await.expect("consumer manager");
Topology {
producer: producer_client,
consumer_manager,
consumer_call,
producer_registry: Arc::clone(&producer_op_registry),
consumer_registry: consumer_serving_registry,
identity_witness,
}
}
pub async fn wire(dial: DialFn) -> Topology {
wire_with(
RegisterMode::Producer {
dial,
egress: None,
accept: None,
},
Some(consumer_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await
}
pub struct ConsumerSideTopology {
pub consumer: Arc<ChannelClient>,
pub producer_manager: ChannelManager,
pub identity_witness: IdentityWitness,
}
pub async fn wire_consumer_side_with(
dial: DialFn,
egress: Option<EgressFactory>,
accept: Option<BindAcceptFactory>,
identity: Option<Identity>,
) -> ConsumerSideTopology {
wire_consumer_side_with_provider(
dial,
egress,
accept,
identity,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await
}
pub async fn wire_consumer_side_with_provider(
dial: DialFn,
egress: Option<EgressFactory>,
accept: Option<BindAcceptFactory>,
identity: Option<Identity>,
provider: Arc<dyn IdentityProvider>,
) -> ConsumerSideTopology {
let producer_op_registry = Arc::new(OperationRegistry::new());
let (producer_manager_tx, mut producer_manager_rx) =
tokio::sync::mpsc::channel::<ChannelManager>(1);
let identity_witness: IdentityWitness = Arc::new(tokio::sync::Mutex::new(None));
let witness_for_hook = Arc::clone(&identity_witness);
let resolved = Arc::new(identity.unwrap_or_else(consumer_identity));
let resolved_for_hook = Arc::clone(&resolved);
let provider_for_hook = Arc::clone(&provider);
let dial_for_hook = Arc::clone(&dial);
let install_hook: InstallChannelZero = Arc::new(
move |manager: ChannelManager, channel0_conn: CoreConnection, _auth: AuthContext| {
let registry = Arc::clone(&producer_op_registry);
let manager_tx = producer_manager_tx.clone();
let witness = Arc::clone(&witness_for_hook);
let provider = Arc::clone(&provider_for_hook);
let resolved = Arc::clone(&resolved_for_hook);
let dial = Arc::clone(&dial_for_hook);
let egress = egress.clone();
let accept = accept.clone();
tokio::spawn(async move {
let channel0_bidi = match channel0_conn.accept_bi().await {
Ok(s) => s,
Err(_) => return,
};
let (writer, reader) = split_single_stream(channel0_bidi);
let _ = channel0_conn.set_identity((*resolved).clone());
let call_connection = Arc::new(CallConnection::new_single_stream(
channel0_conn,
Arc::clone(&writer),
));
let core =
ChannelCore::new(manager.clone(), alkcall::channels::policy::default_policy());
let ops = ChannelOperations::with_default_policy(manager.clone());
ops.register_on(®istry)
.expect("register channel ops on producer");
alksocks::producer::register_socks5_openable(
&core,
®istry,
AuthContext::anonymous(b"alk/socks5"),
Some(dial),
egress,
accept,
Some(witness),
)
.expect("register socks5 openable (the consumer-side topology)");
let _ = manager_tx.send(manager).await;
let dp = Dispatcher::new(registry, provider);
dp.serve_single_stream(call_connection, reader, writer)
.await;
})
},
);
let (consumer_end, producer_end) = tokio::io::duplex(64 * 1024);
let consumer_conn = CoreConnection::from_bidi(consumer_end, b"alk/channels".to_vec(), None);
let producer_conn = CoreConnection::from_bidi(producer_end, b"alk/channels".to_vec(), None);
consumer_conn
.set_identity((*resolved).clone())
.expect("transport identity set once");
let adapter = ChannelsAdapter::new(install_hook, Arc::new(alkcall::channels::policy::NoCap));
let producer_transport_auth = AuthContext::anonymous(b"alk/channels");
let _adapter_task = tokio::spawn(async move {
let _ = alkcall::core::types::ProtocolHandler::handle(
&adapter,
producer_conn,
&producer_transport_auth,
)
.await;
});
let consumer_client = ChannelClient::from_connection(consumer_conn)
.await
.expect("consumer channel client init");
let producer_manager = producer_manager_rx
.recv()
.await
.expect("producer manager captured");
ConsumerSideTopology {
consumer: Arc::new(consumer_client),
producer_manager,
identity_witness,
}
}
pub async fn wire_consumer_side(dial: DialFn) -> ConsumerSideTopology {
wire_consumer_side_with(dial, None, None, Some(consumer_identity())).await
}
pub struct TunnelDirectTopology {
pub consumer: Arc<ChannelClient>,
pub producer_manager: ChannelManager,
}
pub async fn wire_tunnel_direct(dial: alktunnels::producer::DialFn) -> TunnelDirectTopology {
let producer_op_registry = Arc::new(OperationRegistry::new());
let (producer_manager_tx, mut producer_manager_rx) =
tokio::sync::mpsc::channel::<ChannelManager>(1);
let resolved = Identity {
id: "composed-egress-consumer".to_string(),
scopes: vec![alktunnels::params::TUNNEL_DIRECT_SCOPE.to_string()],
resources: HashMap::new(),
};
let resolved = Arc::new(resolved);
let resolved_for_hook = Arc::clone(&resolved);
let dial_for_hook = Arc::clone(&dial);
let install_hook: InstallChannelZero = Arc::new(
move |manager: ChannelManager, channel0_conn: CoreConnection, _auth: AuthContext| {
let registry = Arc::clone(&producer_op_registry);
let manager_tx = producer_manager_tx.clone();
let resolved = Arc::clone(&resolved_for_hook);
let dial = Arc::clone(&dial_for_hook);
tokio::spawn(async move {
let channel0_bidi = match channel0_conn.accept_bi().await {
Ok(s) => s,
Err(_) => return,
};
let (writer, reader) = split_single_stream(channel0_bidi);
let _ = channel0_conn.set_identity((*resolved).clone());
let call_connection = Arc::new(CallConnection::new_single_stream(
channel0_conn,
Arc::clone(&writer),
));
let core =
ChannelCore::new(manager.clone(), alkcall::channels::policy::default_policy());
alktunnels::producer::register_tunnel_direct_openable(
&core,
®istry,
AuthContext::anonymous(b"alk/tunnel"),
dial,
None,
)
.expect("register tunnel direct openable (the composed topology)");
let _ = manager_tx.send(manager).await;
let dp = Dispatcher::new(
registry,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
);
dp.serve_single_stream(call_connection, reader, writer)
.await;
})
},
);
let (consumer_end, producer_end) = tokio::io::duplex(64 * 1024);
let consumer_conn = CoreConnection::from_bidi(consumer_end, b"alk/channels".to_vec(), None);
let producer_conn = CoreConnection::from_bidi(producer_end, b"alk/channels".to_vec(), None);
consumer_conn
.set_identity((*resolved).clone())
.expect("transport identity set once");
let adapter = ChannelsAdapter::new(install_hook, Arc::new(alkcall::channels::policy::NoCap));
let producer_transport_auth = AuthContext::anonymous(b"alk/channels");
let _adapter_task = tokio::spawn(async move {
let _ = alkcall::core::types::ProtocolHandler::handle(
&adapter,
producer_conn,
&producer_transport_auth,
)
.await;
});
let consumer_client = ChannelClient::from_connection(consumer_conn)
.await
.expect("consumer channel client init");
let producer_manager = producer_manager_rx
.recv()
.await
.expect("producer manager captured");
TunnelDirectTopology {
consumer: Arc::new(consumer_client),
producer_manager,
}
}
pub struct RawRfcClient<S> {
pub stream: S,
}
impl<S> RawRfcClient<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
pub fn new(stream: S) -> Self {
Self { stream }
}
pub async fn greet(&mut self) -> Result<u8, std::io::Error> {
raw_greet(&mut self.stream).await
}
pub async fn request(
&mut self,
cmd: Socks5Command,
target: &TargetAddr,
) -> Result<(), std::io::Error> {
raw_request(&mut self.stream, &cmd, target).await
}
pub async fn read_reply(&mut self) -> Result<(u8, Vec<u8>), std::io::Error> {
let (code, _atyp, addr) = raw_read_reply(&mut self.stream).await?;
Ok((code, addr))
}
pub async fn connect(&mut self, target: &TargetAddr) -> Result<u8, std::io::Error> {
let method = self.greet().await?;
assert_eq!(
method,
consts::SOCKS5_AUTH_METHOD_NONE,
"harness uses no-auth"
);
self.request(Socks5Command::TCPConnect, target).await?;
Ok(self.read_reply().await?.0)
}
}
pub struct BindFirstTopology {
pub opener: Arc<ChannelClient>,
pub opener_registry: Arc<OperationRegistry>,
pub producer_manager: ChannelManager,
pub producer_registry: Arc<OperationRegistry>,
pub identity_witness: Arc<tokio::sync::Mutex<Option<String>>>,
}
pub async fn wire_bindfirst_composed(
registry: alktunnels::producer::ResourceRegistry,
bind: alktunnels::producer::BindFn,
accept_loop: alktunnels::producer::BindFirstAcceptLoop,
accept_factory: Option<BindAcceptFactory>,
opener_identity: Identity,
) -> BindFirstTopology {
let producer_op_registry = Arc::new(OperationRegistry::new());
let opener_op_registry = Arc::new(OperationRegistry::new());
let registry_for_hook = Arc::clone(&producer_op_registry);
let opener_registry_for_struct = Arc::clone(&opener_op_registry);
let (producer_manager_tx, mut producer_manager_rx) =
tokio::sync::mpsc::channel::<ChannelManager>(1);
let identity_witness: Arc<tokio::sync::Mutex<Option<String>>> =
Arc::new(tokio::sync::Mutex::new(None));
let bind_witness = Arc::clone(&identity_witness);
let opener_identity_for_hook = opener_identity.clone();
let install_hook: InstallChannelZero = Arc::new(move |manager, channel0_conn, _auth| {
let registry_arc = Arc::clone(®istry_for_hook);
let resource_registry = registry.clone();
let bind = Arc::clone(&bind);
let accept_loop = Arc::clone(&accept_loop);
let accept = accept_factory.clone();
let witness = Arc::clone(&bind_witness);
let manager_tx = producer_manager_tx.clone();
let caller = opener_identity_for_hook.clone();
tokio::spawn(async move {
let channel0_bidi = match channel0_conn.accept_bi().await {
Ok(s) => s,
Err(_) => return,
};
let (writer, reader) = split_single_stream(channel0_bidi);
let _ = channel0_conn.set_identity(caller);
let call_connection = Arc::new(CallConnection::new_single_stream(
channel0_conn,
Arc::clone(&writer),
));
let core =
ChannelCore::new(manager.clone(), alkcall::channels::policy::default_policy());
let ops = ChannelOperations::with_default_policy(manager.clone());
ops.register_on(®istry_arc)
.expect("register channel ops on producer");
let call_for_opener = Arc::clone(&call_connection);
let manager_for_opener = manager.clone();
let open_forwarded: alktunnels::producer::ForwardedOpener =
Arc::new(move |forwarded: alktunnels::TunnelForwardedParams| {
let call = Arc::clone(&call_for_opener);
let manager = manager_for_opener.clone();
Box::pin(async move {
let input = serde_json::to_value(&forwarded).map_err(|e| {
alktunnels::TunnelEstablishError::HandlerError(format!(
"forwarded params serialize: {e}"
))
})?;
let response = call.call(alktunnels::OP_TUNNEL_FORWARDED, input).await;
let out = response.result.map_err(|e| {
alktunnels::TunnelEstablishError::HandlerError(format!(
"forwarded open: {}",
e.message
))
})?;
let channel_id =
out.get("channel_id")
.and_then(|v| v.as_u64())
.ok_or_else(|| {
alktunnels::TunnelEstablishError::HandlerError(
"forwarded open reply missing channel_id".to_string(),
)
})? as u32;
let (send, recv) = manager
.adopt_channel(channel_id, alktunnels::TUNNEL_ALPN, None)
.await
.map_err(|e| {
alktunnels::TunnelEstablishError::HandlerError(format!(
"forwarded adopt: {e}"
))
})?;
Ok((send, recv))
})
});
alktunnels::producer::register_tunnel_bindfirst_openable(
&core,
&resource_registry,
®istry_arc,
AuthContext::anonymous(b"alk/tunnel"),
alktunnels::producer::BindFirstInjections {
bind,
accept_loop,
open_forwarded,
},
Some(witness),
)
.expect("register tunnel bindfirst openable");
if let Some(accept) = accept {
let producer = alksocks::producer::Socks5Producer::builder()
.accept(accept)
.build();
producer
.register(
&core,
®istry_arc,
AuthContext::anonymous(b"alk/socks5"),
None,
)
.expect("register socks5 openable (the composed accept topology)");
}
let _ = manager_tx.send(manager).await;
let dp = Dispatcher::new(
registry_arc,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
);
dp.serve_single_stream(call_connection, reader, writer)
.await;
})
});
let (opener_end, producer_end) = tokio::io::duplex(64 * 1024);
let opener_conn = CoreConnection::from_bidi(opener_end, b"alk/channels".to_vec(), None);
let producer_conn = CoreConnection::from_bidi(producer_end, b"alk/channels".to_vec(), None);
opener_conn
.set_identity(opener_identity.clone())
.expect("transport identity set once");
let adapter = ChannelsAdapter::new(install_hook, Arc::new(alkcall::channels::policy::NoCap));
let producer_transport_auth = AuthContext::anonymous(b"alk/channels");
let _adapter_task = tokio::spawn(async move {
let _ = alkcall::core::types::ProtocolHandler::handle(
&adapter,
producer_conn,
&producer_transport_auth,
)
.await;
});
let opener_client = ChannelClient::from_connection_with_serving(
opener_conn,
Some(ServingConfig {
registry: Arc::clone(&opener_op_registry),
identity_provider: Arc::new(alkcall::core::auth::NoopIdentityProvider),
identity: None,
}),
)
.await
.expect("opener channel client init");
let producer_manager = producer_manager_rx
.recv()
.await
.expect("producer manager captured");
BindFirstTopology {
opener: Arc::new(opener_client),
opener_registry: opener_registry_for_struct,
producer_manager,
producer_registry: producer_op_registry,
identity_witness,
}
}
pub fn composed_opener_identity() -> Identity {
Identity {
id: "composed-opener".to_string(),
scopes: vec![
alktunnels::TUNNEL_OPEN_SCOPE.to_string(),
alktunnels::TUNNEL_FORWARDED_SCOPE.to_string(),
SOCKS5_OPEN_SCOPE.to_string(),
SOCKS5_BIND_SCOPE.to_string(),
],
resources: HashMap::new(),
}
}
pub fn composed_scopeless_opener_identity() -> Identity {
Identity {
id: "composed-opener-scopeless".to_string(),
scopes: vec![
alktunnels::TUNNEL_OPEN_SCOPE.to_string(),
SOCKS5_OPEN_SCOPE.to_string(),
SOCKS5_BIND_SCOPE.to_string(),
],
resources: HashMap::new(),
}
}
pub fn composed_full_opener_identity() -> Identity {
Identity {
id: "composed-opener-full".to_string(),
scopes: vec![
alktunnels::TUNNEL_OPEN_SCOPE.to_string(),
alktunnels::TUNNEL_DIRECT_SCOPE.to_string(),
alktunnels::TUNNEL_FORWARDED_SCOPE.to_string(),
SOCKS5_OPEN_SCOPE.to_string(),
SOCKS5_BIND_SCOPE.to_string(),
],
resources: HashMap::new(),
}
}
pub const COMPOSED_RESOURCE: &str = "bind";
pub const COMPOSED_BOUND_PORT: u16 = 42113;
pub const COMPOSED_PEER_PORT: u16 = 55231;
pub fn composed_test_bind() -> alktunnels::producer::BindFn {
Arc::new(|_substrate, _backing| {
Box::pin(async {
Ok(alktunnels::producer::BoundListener {
listener: Arc::new(()) as Arc<dyn std::any::Any + Send + Sync>,
bound: alktunnels::params::SubstrateAddr::HostPort {
host: "127.0.0.1".to_string(),
port: COMPOSED_BOUND_PORT,
},
})
})
})
}
pub type ComposedAcceptQueue = (
Arc<
tokio::sync::Mutex<
std::collections::VecDeque<(
alktunnels::params::SubstrateAddr,
alktunnels::producer::TargetHandle,
)>,
>,
>,
Arc<tokio::sync::Notify>,
);
pub fn composed_fresh_queue() -> ComposedAcceptQueue {
(
Arc::new(tokio::sync::Mutex::new(std::collections::VecDeque::new())),
Arc::new(tokio::sync::Notify::new()),
)
}
fn composed_accepted_halves(
peer_addr: (&str, u16),
) -> (
alktunnels::params::SubstrateAddr,
alktunnels::producer::TargetHandle,
) {
let (consumer_side, target_side) = tokio::io::duplex(64 * 1024);
let (mut t_read, mut t_write) = tokio::io::split(target_side);
tokio::spawn(async move {
let mut buf = vec![0u8; 8192];
loop {
match t_read.read(&mut buf).await {
Ok(0) | Err(_) => return,
Ok(n) => {
if t_write.write_all(&buf[..n]).await.is_err() {
return;
}
}
}
}
});
let (c_read, c_write) = tokio::io::split(consumer_side);
(
alktunnels::params::SubstrateAddr::HostPort {
host: peer_addr.0.to_string(),
port: peer_addr.1,
},
alktunnels::producer::TargetHandle {
read: Box::new(c_read),
write: Box::new(c_write),
},
)
}
pub fn composed_queue_loop(
queue: ComposedAcceptQueue,
) -> alktunnels::producer::BindFirstAcceptLoop {
let (queue, notify) = queue;
Arc::new(move |_listener| {
let queue = Arc::clone(&queue);
let notify = Arc::clone(¬ify);
Box::pin(async move {
let popped = {
let notified = std::pin::pin!(notify.notified());
let mut q = queue.lock().await;
if let Some(pair) = q.pop_front() {
Some(pair)
} else {
drop(q);
notified.await;
let mut q = queue.lock().await;
q.pop_front()
}
};
popped.ok_or(alktunnels::TunnelEstablishError::DialFailed(
"listener closed".to_string(),
))
})
})
}
pub async fn composed_push_accept(queue: &ComposedAcceptQueue, peer: (&str, u16)) {
let (peer_addr, handle) = composed_accepted_halves(peer);
queue.0.lock().await.push_back((peer_addr, handle));
queue.1.notify_waiters();
}
pub async fn assert_composed_channels(
topo: &BindFirstTopology,
opener_extra: &[u32],
producer_extra: &[u32],
) {
let opener_ids = topo.opener.manager().channel_ids();
let producer_ids = topo.producer_manager.channel_ids();
let opener_ok = opener_ids
.iter()
.all(|id| *id == 0 || opener_extra.contains(id));
let producer_ok = producer_ids
.iter()
.all(|id| *id == 0 || *id % 2 == 1 || producer_extra.contains(id));
assert!(
opener_ok && producer_ok,
"channel sets: opener={opener_ids:?} producer={producer_ids:?}"
);
}
#[cfg(test)]
mod tests {
use super::*;
use alksocks::egress::{BindAcceptFactoryError, EgressFactory};
fn assert_send_sync<T: Send + Sync>() {}
fn assert_send<T: Send>() {}
fn init_trace() {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::level_filters::LevelFilter::TRACE)
.with_test_writer()
.try_init();
}
#[test]
fn identities_carry_the_intended_scopes() {
let open = consumer_identity();
assert_eq!(open.scopes, vec![SOCKS5_OPEN_SCOPE.to_string()]);
assert_eq!(open.id, "consumer");
let bind = bind_identity();
assert_eq!(bind.scopes, vec![SOCKS5_BIND_SCOPE.to_string()]);
assert!(!bind.scopes.contains(&SOCKS5_OPEN_SCOPE.to_string()));
let both = both_scopes_identity();
assert!(both.scopes.contains(&SOCKS5_OPEN_SCOPE.to_string()));
assert!(both.scopes.contains(&SOCKS5_BIND_SCOPE.to_string()));
assert_eq!(both.scopes.len(), 2, "both grants, explicitly");
}
#[test]
fn test_id_provider_resolves_the_scoped_token() {
let provider = TestIdProvider;
let token = AuthToken {
raw: TEST_AUTH_TOKEN.as_bytes().to_vec(),
};
assert_eq!(
provider.resolve_from_token(&token).map(|i| i.id),
Some("consumer".to_string())
);
assert_eq!(
provider
.resolve_from_token(&token)
.and_then(|i| i.scopes.into_iter().next()),
Some(SOCKS5_OPEN_SCOPE.to_string())
);
assert!(provider
.resolve_from_token(&AuthToken {
raw: b"other".to_vec(),
})
.is_none());
assert!(provider.resolve_from_fingerprint("any").is_none());
}
#[tokio::test]
async fn echo_dial_round_trips_through_pump() {
let handle = echo_dial()(TargetAddr::Domain("example.com".into(), 443))
.await
.expect("dial resolves");
let (mut probe_far, probe_near) = tokio::io::duplex(64 * 1024);
let pump = tokio::spawn(pump_bidi(probe_near, handle.read, handle.write));
probe_far.write_all(b"ping").await.expect("write");
let mut buf = [0u8; 4];
probe_far.read_exact(&mut buf).await.expect("echoed back");
assert_eq!(&buf, b"ping");
drop(probe_far);
let (c2p, p2c) = pump.await.expect("pump completes");
assert_eq!((c2p, p2c), (4, 4), "both pumps moved the probe bytes");
}
#[tokio::test]
async fn refusing_dial_maps_to_its_reply_code() {
let dial = refusing_dial(SocksDialError::NotAllowed("policy".into()));
let err = dial(TargetAddr::Domain("example.com".into(), 443))
.await
.expect_err("dial refuses");
assert_eq!(
err.to_reply_error().as_u8(),
ReplyError::ConnectionNotAllowed.as_u8()
);
}
#[tokio::test]
async fn echo_egress_echoes_inner_bytes_and_parks_when_idle() {
let mut egress = echo_egress()().await.expect("egress builds");
let payload = Bytes::from_static(b"query");
egress
.send(
TargetAddr::Ip("10.0.0.1:53".parse().unwrap()),
payload.clone(),
)
.await
.expect("send succeeds");
let reply = egress
.next_reply()
.await
.expect("next_reply")
.expect("a reply is ready");
let expected = {
let mut v = TargetAddr::Ip("10.0.0.1:53".parse().unwrap())
.to_be_bytes()
.expect("addr encodes");
v.extend_from_slice(&payload);
Bytes::from(v)
};
assert_eq!(reply, expected);
let mut poll = egress.next_reply();
tokio::time::timeout(std::time::Duration::from_millis(50), &mut poll)
.await
.expect_err("next_reply stays Pending when nothing is ready");
}
#[tokio::test]
async fn echo_accept_resolves_with_halves_and_echoes() {
let accept = echo_accept()(TargetAddr::Domain("example.com".into(), 2121))
.await
.expect("factory resolves");
assert_eq!(
accept.bind_addr(),
SocketAddr::from(([127, 0, 0, 1], ECHO_BIND_PORT))
);
let mut handle = accept.accept().await.expect("accept resolves");
assert_eq!(
handle.peer,
TargetAddr::Ip(SocketAddr::from(([10, 0, 0, 7], 44444)))
);
handle.write.write_all(b"ping").await.expect("write");
let mut buf = [0u8; 4];
handle.read.read_exact(&mut buf).await.expect("echoed");
assert_eq!(&buf, b"ping");
}
#[tokio::test]
async fn failing_accept_variants_refuse_at_their_stages() {
let factory =
refusing_accept_factory(BindAcceptFactoryError::Refused("no listener".into()));
let err = factory(TargetAddr::Domain("example.com".into(), 2121))
.await
.expect_err("factory refuses");
assert_eq!(
err.to_reply_error().as_u8(),
ReplyError::ConnectionRefused.as_u8()
);
let factory = refusing_accept(BindAcceptError::NotAllowed("policy".into()));
let accept = factory(TargetAddr::Domain("example.com".into(), 2121))
.await
.expect("factory resolves; the accept future refuses");
let err = accept.accept().await.expect_err("accept refuses");
assert_eq!(
err.to_reply_error().as_u8(),
ReplyError::ConnectionNotAllowed.as_u8()
);
}
#[tokio::test]
async fn refusing_egress_refuses_at_associate_setup() {
let factory = refusing_egress(EgressError::Unreachable("no egress".into()));
let err = match factory().await {
Err(err) => err,
Ok(_) => panic!("the factory must refuse"),
};
assert_eq!(
err.to_reply_error().as_u8(),
ReplyError::NetworkUnreachable.as_u8()
);
}
#[test]
fn seam_closures_have_the_poc_compile_shapes() {
assert_send_sync::<DialFn>();
assert_send_sync::<EgressFactory>();
assert_send_sync::<BindAcceptFactory>();
assert_send::<BindAccept>();
}
async fn hand_rolled_server(mut stream: tokio::io::DuplexStream, reply_code: u8) {
let mut greeting = [0u8; 3];
stream.read_exact(&mut greeting).await.expect("greeting");
assert_eq!(greeting, [0x05, 0x01, 0x00]);
stream
.write_all(&[consts::SOCKS5_VERSION, consts::SOCKS5_AUTH_METHOD_NONE])
.await
.expect("method selection");
let mut head = [0u8; 4];
stream.read_exact(&mut head).await.expect("command head");
assert_eq!(head[1], consts::SOCKS5_CMD_TCP_CONNECT);
match head[3] {
consts::SOCKS5_ADDR_TYPE_DOMAIN_NAME => {
let mut len = [0u8; 1];
stream.read_exact(&mut len).await.expect("domain len");
let mut rest = vec![0u8; len[0] as usize + 2];
stream.read_exact(&mut rest).await.expect("domain + port");
}
consts::SOCKS5_ADDR_TYPE_IPV4 => {
let mut rest = [0u8; 6];
stream.read_exact(&mut rest).await.expect("v4 + port");
}
other => panic!("test wrote an unexpected address type {other}"),
}
let addr = SocketAddr::from(([127, 0, 0, 1], 1080));
stream
.write_all(&new_reply(&ReplyError::from_u8(reply_code), addr))
.await
.expect("reply written");
}
#[tokio::test]
async fn raw_rfc_client_round_trips_against_a_hand_rolled_server() {
let (client_end, server_end) = tokio::io::duplex(1024);
let _server = tokio::spawn(hand_rolled_server(
server_end,
consts::SOCKS5_REPLY_SUCCEEDED,
));
let mut raw = RawRfcClient::new(client_end);
let code = tokio::time::timeout(
std::time::Duration::from_secs(5),
raw.connect(&TargetAddr::Domain("echo.test".into(), 443)),
)
.await
.expect("round-trip timed out")
.expect("the raw client completes");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
let (client_end, server_end) = tokio::io::duplex(1024);
let server = tokio::spawn(hand_rolled_server(
server_end,
consts::SOCKS5_REPLY_CONNECTION_REFUSED,
));
let mut raw = RawRfcClient::new(client_end);
let code = raw
.connect(&TargetAddr::Ip("127.0.0.1:1".parse().unwrap()))
.await
.expect("round-trip completes");
assert_eq!(code, consts::SOCKS5_REPLY_CONNECTION_REFUSED);
let _ = server.await;
}
#[tokio::test]
async fn smoke_connect_echo_round_trip_over_the_harness() {
let topo = wire(echo_dial()).await;
let response = topo
.consumer_call
.call(OP_SOCKS5_OPEN, Socks5Params::as_json())
.await;
let out = response.result.expect("the socks5 channel opens");
let channel_id = out
.get("channel_id")
.and_then(|v| v.as_u64())
.expect("the reply carries channel_id") as u32;
assert!(channel_id >= 1);
let (send, recv) = topo
.consumer_manager
.adopt_channel(channel_id, SOCKS5_ALPN, None)
.await
.expect("adopt the producer-allocated id");
let (stream_peer, stream_self) = tokio::io::duplex(64 * 1024);
tokio::spawn(pump_bidi(
stream_self,
Box::new(recv) as Box<dyn AsyncRead + Send + Unpin>,
Box::new(send) as Box<dyn AsyncWrite + Send + Unpin>,
));
let mut raw = RawRfcClient::new(stream_peer);
let code = tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.connect(&TargetAddr::Domain("echo.test".into(), 443)),
)
.await
.expect("CONNECT round-trip timed out")
.expect("the raw client completes");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
raw.stream
.write_all(b"ping")
.await
.expect("write the probe");
let mut buf = [0u8; 4];
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("echo timed out")
.expect("read the echo");
assert_eq!(&buf, b"ping");
assert_eq!(
topo.identity_witness.lock().await.as_deref(),
Some("consumer")
);
}
#[tokio::test]
async fn scopeless_identity_fails_closed_with_forbidden() {
let bare = Identity {
id: "scopeless".to_string(),
scopes: vec![],
resources: std::collections::HashMap::new(),
};
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: None,
accept: None,
},
Some(bare),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let response = topo
.consumer_call
.call(OP_SOCKS5_OPEN, Socks5Params::as_json())
.await;
let err = response
.result
.expect_err("a scope-less identity fails closed");
assert_eq!(err.code, "FORBIDDEN");
assert!(topo
.consumer_manager
.channel_ids()
.into_iter()
.all(|id| id == 0));
assert!(topo
.producer
.manager()
.channel_ids()
.into_iter()
.all(|id| id == 0));
}
#[tokio::test]
async fn establisher_witnesses_the_opener_identity() {
let topo = wire(echo_dial()).await;
let response = topo
.consumer_call
.call(OP_SOCKS5_OPEN, Socks5Params::as_json())
.await;
assert!(response.result.is_ok(), "the channel opens");
tokio::time::timeout(std::time::Duration::from_secs(5), async {
loop {
let seen = topo.identity_witness.lock().await.clone();
if seen.is_some() {
assert_eq!(seen.as_deref(), Some("consumer"));
return;
}
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
})
.await
.expect("the establisher ran and witnessed the opener");
}
#[tokio::test]
async fn producer_allocates_consumer_adopts() {
let topo = wire(echo_dial()).await;
let response = topo
.consumer_call
.call(OP_SOCKS5_OPEN, Socks5Params::as_json())
.await;
let out = response.result.expect("open");
let channel_id = out.get("channel_id").and_then(|v| v.as_u64()).expect("id") as u32;
assert_eq!(channel_id % 2, 1, "the connect side allocates odd ids");
assert!(!topo.consumer_manager.has_channel(channel_id));
assert!(topo.producer.manager().has_channel(channel_id));
let (_send, _recv) = topo
.consumer_manager
.adopt_channel(channel_id, SOCKS5_ALPN, None)
.await
.expect("adopt");
assert!(topo.consumer_manager.has_channel(channel_id));
}
async fn open_channel_and_pump(
topo: &Topology,
) -> (
RawRfcClient<tokio::io::DuplexStream>,
tokio::task::JoinHandle<(u64, u64)>,
) {
let response = topo
.consumer_call
.call(OP_SOCKS5_OPEN, Socks5Params::as_json())
.await;
let out = response.result.expect("the socks5 channel opens");
let channel_id = out
.get("channel_id")
.and_then(|v| v.as_u64())
.expect("the reply carries channel_id") as u32;
let (send, recv) = topo
.consumer_manager
.adopt_channel(channel_id, SOCKS5_ALPN, None)
.await
.expect("adopt the producer-allocated id");
let (stream_peer, stream_self) = tokio::io::duplex(64 * 1024);
let pump = tokio::spawn(pump_bidi(
stream_self,
Box::new(recv) as Box<dyn AsyncRead + Send + Unpin>,
Box::new(send) as Box<dyn AsyncWrite + Send + Unpin>,
));
(RawRfcClient::new(stream_peer), pump)
}
#[tokio::test]
async fn producer_connect_echo_witnesses_identity_and_tears_down_cleanly() {
let topo = wire(echo_dial()).await;
let (mut raw, pump) = open_channel_and_pump(&topo).await;
let code = tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.connect(&TargetAddr::Domain("echo.test".into(), 443)),
)
.await
.expect("CONNECT round-trip timed out")
.expect("the raw client completes");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
raw.stream.write_all(b"ping").await.expect("write");
let mut buf = [0u8; 4];
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("echo timed out")
.expect("read the echo");
assert_eq!(&buf, b"ping");
assert_eq!(
topo.identity_witness.lock().await.as_deref(),
Some("consumer")
);
drop(raw);
tokio::time::timeout(std::time::Duration::from_secs(10), pump)
.await
.expect("the data plane pump ends on client EOF")
.expect("join");
}
#[tokio::test]
async fn producer_dial_refusal_is_0x05_and_no_phantom_data_plane() {
let topo = wire(refusing_dial(SocksDialError::Refused("target down".into()))).await;
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
let method = raw.greet().await.expect("greet");
assert_eq!(method, consts::SOCKS5_AUTH_METHOD_NONE);
raw.request(
Socks5Command::TCPConnect,
&TargetAddr::Domain("down.test".into(), 80),
)
.await
.expect("request written");
let (code, _addr) = raw.read_reply().await.expect("reply read");
assert_eq!(code, consts::SOCKS5_REPLY_CONNECTION_REFUSED);
drop(_pump);
let topo2 = wire(echo_dial()).await;
let (mut raw2, _pump2) = open_channel_and_pump(&topo2).await;
let code = raw2
.connect(&TargetAddr::Domain("echo.test".into(), 443))
.await
.expect("fresh connect");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
}
#[tokio::test]
async fn producer_scope_class_refusal_is_0x02() {
let topo = wire(refusing_dial(SocksDialError::NotAllowed("policy".into()))).await;
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::TCPConnect,
&TargetAddr::Domain("policy.test".into(), 80),
)
.await
.expect("request written");
let (code, _addr) = raw.read_reply().await.expect("reply read");
assert_eq!(code, consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED);
}
#[tokio::test]
async fn producer_associate_refuses_0x07_connect_still_serves() {
let topo = wire(echo_dial()).await;
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::UDPAssociate,
&TargetAddr::Domain("relay.test".into(), 1080),
)
.await
.expect("request written");
let (code, _addr) = raw.read_reply().await.expect("reply read");
assert_eq!(code, consts::SOCKS5_REPLY_COMMAND_NOT_SUPPORTED);
drop(raw);
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
let code = raw
.connect(&TargetAddr::Domain("echo.test".into(), 443))
.await
.expect("connect");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
raw.stream.write_all(b"still").await.expect("write");
let mut buf = [0u8; 5];
raw.stream.read_exact(&mut buf).await.expect("echo");
assert_eq!(&buf, b"still");
}
async fn raw_associate(
topo: &Topology,
) -> (
RawRfcClient<tokio::io::DuplexStream>,
tokio::task::JoinHandle<(u64, u64)>,
) {
let (mut raw, pump) = open_channel_and_pump(topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::UDPAssociate,
&TargetAddr::Domain("relay.test".into(), 1080),
)
.await
.expect("request written");
let (code, addr) = raw.read_reply().await.expect("reply read");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED, "the associate reply");
assert_eq!(
(addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]),
(127, 0, 0, 1, 0, 0),
"the reply carries the sentinel BND.ADDR"
);
(raw, pump)
}
#[tokio::test]
async fn producer_udp_associate_datagram_stage_over_channels() {
init_trace();
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: Some(echo_egress()),
accept: None,
},
Some(consumer_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, pump) = raw_associate(&topo).await;
let target = TargetAddr::Ip("10.0.0.1:53".parse().unwrap());
for i in 0..5u8 {
raw.stream
.write_all(&udp_frame(&target, &[i; 10]))
.await
.expect("datagram sent");
}
for i in 0..5u8 {
let mut buf = vec![0u8; 2 + 7 + 10];
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("echo timed out")
.expect("reply frame read");
let (src, payload) = udp_unframe(&buf);
assert_eq!(src, target, "the echo reports the destination");
assert_eq!(&payload[..], &vec![i; 10][..], "in-order echo");
}
raw.stream
.write_all(&udp_frame(&target, b""))
.await
.expect("empty datagram sent");
let mut buf = vec![0u8; 2 + 7];
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("empty echo timed out")
.expect("reply frame read");
let (_, payload) = udp_unframe(&buf);
assert!(payload.is_empty(), "the empty datagram round-trips");
raw.stream
.write_all(&[0x00, 0x04, 0x7f, 1, 2, 3])
.await
.expect("garbage frame sent");
let mut buf = [0u8; 16];
let outcome = tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read(&mut buf),
)
.await
.expect("read resolves");
match outcome {
Ok(0) | Err(_) => {}
Ok(n) => panic!("EOF expected after an undecodable frame — got {n} bytes"),
}
drop(raw);
tokio::time::timeout(std::time::Duration::from_secs(10), pump)
.await
.expect("the stage pump ends")
.expect("join");
}
#[tokio::test]
async fn producer_udp_associate_eof_teardown_connection_stays_healthy() {
init_trace();
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: Some(echo_egress()),
accept: None,
},
Some(consumer_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (raw, pump) = raw_associate(&topo).await;
drop(raw);
tokio::time::timeout(std::time::Duration::from_secs(10), pump)
.await
.expect("the stage pump ends on channel EOF")
.expect("join");
let (mut raw, _pump) = raw_associate(&topo).await;
let target = TargetAddr::Ip("10.0.0.2:53".parse().unwrap());
raw.stream
.write_all(&udp_frame(&target, b"fresh"))
.await
.expect("datagram sent");
let mut buf = vec![0u8; 2 + 7 + 5];
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("fresh echo timed out")
.expect("reply frame read");
let (_, payload) = udp_unframe(&buf);
assert_eq!(&payload[..], b"fresh");
}
#[tokio::test]
async fn producer_udp_associate_factory_refusal_maps_to_reply_code() {
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: Some(refusing_egress(EgressError::Unreachable(
"no egress".into(),
))),
accept: None,
},
Some(consumer_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::UDPAssociate,
&TargetAddr::Domain("relay.test".into(), 1080),
)
.await
.expect("request written");
let (code, _addr) = raw.read_reply().await.expect("reply read");
assert_eq!(
code,
consts::SOCKS5_REPLY_NETWORK_UNREACHABLE,
"the factory refusal maps to reply#1's code"
);
}
#[tokio::test]
async fn producer_connect_and_associate_on_the_same_registration() {
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: Some(echo_egress()),
accept: None,
},
Some(consumer_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
let code = raw
.connect(&TargetAddr::Domain("echo.test".into(), 443))
.await
.expect("connect");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
raw.stream.write_all(b"tcp").await.expect("write");
let mut buf = [0u8; 3];
raw.stream.read_exact(&mut buf).await.expect("echo");
assert_eq!(&buf, b"tcp");
drop(raw);
let (mut raw, _pump) = raw_associate(&topo).await;
let target = TargetAddr::Ip("10.0.0.3:53".parse().unwrap());
raw.stream
.write_all(&udp_frame(&target, b"udp"))
.await
.expect("datagram sent");
let mut buf = vec![0u8; 2 + 7 + 3];
raw.stream.read_exact(&mut buf).await.expect("echo");
let (_, payload) = udp_unframe(&buf);
assert_eq!(&payload[..], b"udp");
}
#[tokio::test]
async fn producer_bind_round_trip_over_channels() {
init_trace();
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: None,
accept: Some(echo_accept()),
},
Some(bind_scoped_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::TCPBind,
&TargetAddr::Domain("bind.test".into(), ECHO_BIND_PORT),
)
.await
.expect("request written");
let (code1, addr1) = raw.read_reply().await.expect("reply#1");
assert_eq!(code1, consts::SOCKS5_REPLY_SUCCEEDED, "reply#1 success");
assert_eq!(
u16::from_be_bytes([addr1[addr1.len() - 2], addr1[addr1.len() - 1]]),
ECHO_BIND_PORT,
"reply#1 carries the factory's advertised port"
);
let (code2, addr2) = raw.read_reply().await.expect("reply#2");
assert_eq!(code2, consts::SOCKS5_REPLY_SUCCEEDED, "reply#2 success");
let peer_port = u16::from_be_bytes([addr2[addr2.len() - 2], addr2[addr2.len() - 1]]);
assert_eq!(peer_port, 44444, "reply#2 carries the accepted peer");
raw.stream.write_all(b"ping").await.expect("write");
let mut buf = [0u8; 4];
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("echo timed out")
.expect("read the echo");
assert_eq!(&buf, b"ping");
raw.stream.write_all(b"pong").await.expect("write");
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("echo timed out")
.expect("read the echo");
assert_eq!(&buf, b"pong");
drop(raw);
tokio::time::timeout(std::time::Duration::from_secs(10), pump)
.await
.expect("the data plane pump ends on client EOF")
.expect("join");
}
#[tokio::test]
async fn producer_bind_command_gate_refuses_0x02_connect_unaffected() {
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: None,
accept: Some(echo_accept()),
},
Some(consumer_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::TCPBind,
&TargetAddr::Domain("gate.test".into(), ECHO_BIND_PORT),
)
.await
.expect("request written");
let (code, _addr) = raw.read_reply().await.expect("reply read");
assert_eq!(code, consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED);
drop(raw);
drop(pump);
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
let code = raw
.connect(&TargetAddr::Domain("echo.test".into(), 443))
.await
.expect("connect still allowed");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
raw.stream.write_all(b"egress").await.expect("write");
let mut buf = [0u8; 6];
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("echo timed out")
.expect("read the echo");
assert_eq!(&buf, b"egress");
}
#[tokio::test]
async fn producer_bind_serves_a_both_scopes_identity() {
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: None,
accept: Some(echo_accept()),
},
Some(both_scopes_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::TCPBind,
&TargetAddr::Domain("bind.test".into(), ECHO_BIND_PORT),
)
.await
.expect("request written");
let (code1, _addr) = raw.read_reply().await.expect("reply#1");
assert_eq!(code1, consts::SOCKS5_REPLY_SUCCEEDED);
let (code2, _addr) = raw.read_reply().await.expect("reply#2");
assert_eq!(code2, consts::SOCKS5_REPLY_SUCCEEDED);
raw.stream.write_all(b"scoped").await.expect("write");
let mut buf = [0u8; 6];
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("echo timed out")
.expect("read the echo");
assert_eq!(&buf, b"scoped");
}
#[tokio::test]
async fn producer_bind_quiet_wait_garbage_over_channels() {
init_trace();
let parked: BindAcceptFactory = Arc::new(move |_target: TargetAddr| {
Box::pin(async move {
let bind_addr =
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), ECHO_BIND_PORT);
Ok(BindAccept::new(
bind_addr,
std::future::pending::<Result<BindTargetHandle, BindAcceptError>>(),
))
})
});
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: None,
accept: Some(parked),
},
Some(bind_scoped_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::TCPBind,
&TargetAddr::Domain("quiet.test".into(), ECHO_BIND_PORT),
)
.await
.expect("request written");
let (code1, _addr) = raw.read_reply().await.expect("reply#1");
assert_eq!(code1, consts::SOCKS5_REPLY_SUCCEEDED);
raw.stream.write_all(&[0x05, 0x02]).await.expect("garbage");
let mut buf = [0u8; 10];
let outcome = tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read(&mut buf),
)
.await
.expect("read resolves");
match outcome {
Ok(0) | Err(_) => {}
Ok(n) => panic!("no reply#2 after garbage — got {n} bytes"),
}
drop(raw);
tokio::time::timeout(std::time::Duration::from_secs(10), pump)
.await
.expect("the session pump ends on the garbage refusal")
.expect("join");
}
#[tokio::test]
async fn producer_three_command_regression_guard() {
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: Some(echo_egress()),
accept: Some(echo_accept()),
},
Some(both_scopes_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
let code = raw
.connect(&TargetAddr::Domain("echo.test".into(), 443))
.await
.expect("connect");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
raw.stream.write_all(b"c").await.expect("write");
let mut buf = [0u8; 1];
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("echo timed out")
.expect("read the echo");
assert_eq!(&buf, b"c");
drop(raw);
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::TCPBind,
&TargetAddr::Domain("bind.test".into(), ECHO_BIND_PORT),
)
.await
.expect("request written");
let (code1, _addr) = raw.read_reply().await.expect("reply#1");
assert_eq!(code1, consts::SOCKS5_REPLY_SUCCEEDED);
let (code2, _addr) = raw.read_reply().await.expect("reply#2");
assert_eq!(code2, consts::SOCKS5_REPLY_SUCCEEDED);
drop(raw);
let (mut raw, _pump) = raw_associate(&topo).await;
let target = TargetAddr::Ip("10.0.0.9:53".parse().unwrap());
raw.stream
.write_all(&udp_frame(&target, b"all3"))
.await
.expect("datagram sent");
let mut buf = vec![0u8; 2 + 7 + 4];
tokio::time::timeout(
std::time::Duration::from_secs(10),
raw.stream.read_exact(&mut buf),
)
.await
.expect("echo timed out")
.expect("reply frame read");
let (_, payload) = udp_unframe(&buf);
assert_eq!(&payload[..], b"all3");
}
#[tokio::test]
async fn producer_bind_failure_mapping_over_channels() {
use alksocks::egress::BindAcceptFactoryError;
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: None,
accept: Some(refusing_accept_factory(BindAcceptFactoryError::Refused(
"no listener".into(),
))),
},
Some(bind_scoped_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::TCPBind,
&TargetAddr::Domain("factory.fail".into(), ECHO_BIND_PORT),
)
.await
.expect("request written");
let (code, _addr) = raw.read_reply().await.expect("reply read");
assert_eq!(code, consts::SOCKS5_REPLY_CONNECTION_REFUSED);
drop(raw);
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: None,
accept: Some(refusing_accept(BindAcceptError::NotAllowed(
"policy".into(),
))),
},
Some(bind_scoped_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::TCPBind,
&TargetAddr::Domain("accept.fail".into(), ECHO_BIND_PORT),
)
.await
.expect("request written");
let (code1, _addr) = raw.read_reply().await.expect("reply#1");
assert_eq!(
code1,
consts::SOCKS5_REPLY_SUCCEEDED,
"the factory resolved"
);
let (code2, _addr) = raw.read_reply().await.expect("reply#2");
assert_eq!(code2, consts::SOCKS5_REPLY_CONNECTION_NOT_ALLOWED);
}
#[tokio::test]
async fn producer_bind_no_factory_serves_variant_3_over_channels() {
let topo = wire_with(
RegisterMode::Producer {
dial: echo_dial(),
egress: None,
accept: None,
},
Some(bind_scoped_identity()),
None,
Arc::new(alkcall::core::auth::NoopIdentityProvider),
)
.await;
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
raw.greet().await.expect("greet");
raw.request(
Socks5Command::TCPBind,
&TargetAddr::Domain("variant3.test".into(), 2121),
)
.await
.expect("request written");
let (code1, addr1) = raw.read_reply().await.expect("reply#1");
assert_eq!(code1, consts::SOCKS5_REPLY_SUCCEEDED);
assert!(
addr1.iter().all(|b| *b == 0),
"reply#1 carries the wrapper-aware sentinel"
);
let (code2, addr2) = raw.read_reply().await.expect("reply#2");
assert_eq!(code2, consts::SOCKS5_REPLY_SUCCEEDED);
assert!(
addr2.iter().all(|b| *b == 0),
"reply#2 carries the wrapper-aware sentinel peer"
);
}
#[tokio::test]
async fn producer_serves_repeated_connects_on_one_registration() {
let topo = wire(echo_dial()).await;
for i in 0..3 {
let (mut raw, _pump) = open_channel_and_pump(&topo).await;
let code = raw
.connect(&TargetAddr::Domain(format!("t{i}.test"), 443))
.await
.expect("connect");
assert_eq!(code, consts::SOCKS5_REPLY_SUCCEEDED);
raw.stream.write_all(b"ping").await.expect("write");
let mut buf = [0u8; 4];
raw.stream.read_exact(&mut buf).await.expect("echo");
assert_eq!(&buf, b"ping");
drop(raw);
}
}
}