#![expect(
clippy::expect_used,
reason = "example/test/bench: panic-on-error and print-for-output are the standard patterns for demos and harnesses"
)]
use rama::{
extensions::Extensions,
net::{address::SocketAddress, user::credentials::basic},
proxy::socks5::{
Socks5Acceptor, Socks5Client, client::bind::BindOutput, server::DefaultBinder,
},
rt::Executor,
tcp::{client::default_tcp_connect, server::TcpListener},
telemetry::tracing::{
self,
level_filters::LevelFilter,
subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt},
},
};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() {
tracing::subscriber::registry()
.with(fmt::layer())
.with(
EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into())
.from_env_lossy(),
)
.init();
let socks5_socket_addr = spawn_socks5_server().await;
let ext = Extensions::default();
let (proxy_client_stream, _) = default_tcp_connect(&ext, socks5_socket_addr.into())
.await
.expect("establish connection to socks5 server (from client)");
let socks5_client = Socks5Client::new().with_auth(basic!("john", "secret"));
let binder = socks5_client
.handshake_bind(proxy_client_stream, None)
.await
.expect("initiate socks5 bind handshake");
let bind_addr = binder.selected_bind_address();
tokio::spawn(async move {
let (mut stream, _) = default_tcp_connect(&ext, bind_addr.into())
.await
.expect("establish connection to socks5 server (from server)");
tracing::info!("server: read ping via socks5 proxy from client");
let mut buf = [0u8; 4];
stream
.read_exact(&mut buf)
.await
.expect("server read 'ping'");
assert_eq!(b"ping", &buf[..]);
tracing::info!("server: write pong via socks5 proxy to client");
stream
.write_all(b"pong")
.await
.expect("server write 'pong'");
_ = stream.read_u8().await;
});
let BindOutput { mut stream, .. } = binder.connect().await.expect("server to be connected");
tracing::info!("client: write ping via socks5 proxy to server");
stream
.write_all(b"ping")
.await
.expect("client write 'ping'");
tracing::info!("client: read pong via socks5 proxy from server");
let mut buf = [0u8; 4];
stream
.read_exact(&mut buf)
.await
.expect("client read 'pong'");
assert_eq!(b"pong", &buf[..]);
tracing::info!("ping-pong succeeded, bye now!")
}
async fn spawn_socks5_server() -> SocketAddress {
let tcp_service =
TcpListener::bind_address(SocketAddress::local_ipv4(63010), Executor::default())
.await
.expect("bind socks5 BIND proxy on open port");
let bind_addr = tcp_service
.local_addr()
.expect("get bind address of socks5 proxy server")
.into();
let socks5_acceptor = Socks5Acceptor::new(Executor::default())
.with_authorizer(basic!("john", "secret").into_authorizer())
.with_binder(DefaultBinder::default().with_bind_address(SocketAddress::local_ipv4(0)));
tokio::spawn(tcp_service.serve(socks5_acceptor));
bind_addr
}