use anytls::AsyncReadWrite;
use anytls::core::PaddingFactory;
use anytls::proxy::session::Client;
use anytls::runtime::DefaultPaddingFactory;
use anytls::{BoxError, PROGRAM_VERSION_NAME};
use clap::Parser;
use rustls::ClientConfig;
use sha2::{Digest, Sha256};
use socks5_impl::server::auth::NoAuth;
use socks5_impl::server::{IncomingConnection, Server};
use std::fs::File;
use std::io::BufReader;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio_rustls::TlsConnector;
#[derive(Parser)]
#[command(version, author, name = "anytls-client", about = "AnyTLS Client")]
struct Args {
#[arg(short = 'l', long, default_value = "127.0.0.1:1080", help = "SOCKS5 listen port")]
listen: SocketAddr,
#[arg(short = 's', long, help = "Server address")]
server: SocketAddr,
#[arg(long, help = "SNI")]
sni: Option<String>,
#[arg(short = 'p', long, help = "Password")]
password: String,
#[arg(long, help = "Root CA certificate PEM file to verify server (optional)")]
root_cert: Option<PathBuf>,
#[arg(long, default_value = "info", help = "Log level (off, error, warn, info, debug, trace)")]
log: log::LevelFilter,
}
#[tokio::main]
async fn main() -> Result<(), BoxError> {
let args = Args::parse();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(args.log.to_string())).init();
if args.password.is_empty() {
log::error!("Please set password");
std::process::exit(1);
}
let password_sha256: [u8; 32] = Sha256::digest(args.password.as_bytes()).into();
log::info!("[Client] {}", PROGRAM_VERSION_NAME);
log::info!("[Client] SOCKS5 {} => {}", args.listen, args.server);
let server = Server::bind(args.listen, Arc::new(NoAuth)).await?;
let tls_config = create_tls_config(args.root_cert.as_deref())?;
let padding = DefaultPaddingFactory::load();
let padding_clone = padding.clone();
let client = Arc::new(Client::new(
Box::new(move || {
Box::pin(dail_out_callback(
args.server,
args.sni.clone(),
tls_config.clone(),
padding_clone.clone(),
password_sha256,
))
}),
padding,
std::time::Duration::from_secs(30),
std::time::Duration::from_secs(30),
5,
));
loop {
let (stream, _addr) = server.accept().await?;
let client = client.clone();
tokio::spawn(async move {
if let Err(e) = handle_connection(stream, client).await {
log::error!("Connection error: {}", e);
}
});
}
}
async fn dail_out_callback(
server: SocketAddr,
sni: Option<String>,
tls_config: Arc<ClientConfig>,
padding: Arc<tokio::sync::RwLock<PaddingFactory>>,
password_sha256: [u8; 32],
) -> std::io::Result<Box<dyn AsyncReadWrite>> {
let sni = sni.clone();
let stream = TcpStream::connect(&server).await?;
stream.set_nodelay(true)?;
let ka = socket2::TcpKeepalive::new()
.with_time(std::time::Duration::from_secs(60))
.with_interval(std::time::Duration::from_secs(10));
socket2::SockRef::from(&stream).set_tcp_keepalive(&ka)?;
use rustls::pki_types::ServerName;
let server_name = if let Some(sni) = sni {
if let Ok(ip) = sni.parse::<std::net::IpAddr>() {
ServerName::IpAddress(ip.into())
} else {
use std::io::{Error, ErrorKind::InvalidInput};
ServerName::try_from(sni).map_err(|e| Error::new(InvalidInput, e))?
}
} else {
ServerName::IpAddress(server.ip().into())
};
let connector = TlsConnector::from(tls_config);
let mut tls_stream = connector.connect(server_name, stream).await?;
let mut auth_data = Vec::new();
auth_data.extend_from_slice(&password_sha256);
let padding_factory = padding.read().await;
let padding_sizes = padding_factory.generate_record_payload_sizes(0);
let padding_len = if !padding_sizes.is_empty() { padding_sizes[0] as u16 } else { 0 };
auth_data.extend_from_slice(&padding_len.to_be_bytes());
if padding_len > 0 {
auth_data.resize(auth_data.len() + padding_len as usize, 0);
}
tls_stream.write_all(&auth_data).await?;
Ok(Box::new(tls_stream) as Box<dyn AsyncReadWrite>)
}
fn create_tls_config(root_cert: Option<&Path>) -> Result<Arc<ClientConfig>, BoxError> {
if let Some(path) = root_cert {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let certs_iter = rustls_pemfile::certs(&mut reader);
let certs: Vec<rustls::pki_types::CertificateDer<'static>> = certs_iter.collect::<Result<_, _>>()?;
let mut root_store = rustls::RootCertStore::empty();
for cert in certs {
root_store.add(cert)?;
}
let config = ClientConfig::builder().with_root_certificates(root_store).with_no_client_auth();
return Ok(Arc::new(config));
}
let mut config = ClientConfig::builder()
.with_root_certificates(rustls::RootCertStore::empty())
.with_no_client_auth();
config.dangerous().set_certificate_verifier(Arc::new(AllowAnyCertVerifier));
Ok(Arc::new(config))
}
#[derive(Debug)]
struct AllowAnyCertVerifier;
impl rustls::client::danger::ServerCertVerifier for AllowAnyCertVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
vec![
rustls::SignatureScheme::RSA_PKCS1_SHA1,
rustls::SignatureScheme::ECDSA_SHA1_Legacy,
rustls::SignatureScheme::RSA_PKCS1_SHA256,
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
rustls::SignatureScheme::RSA_PKCS1_SHA384,
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
rustls::SignatureScheme::RSA_PKCS1_SHA512,
rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
rustls::SignatureScheme::RSA_PSS_SHA256,
rustls::SignatureScheme::RSA_PSS_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA512,
rustls::SignatureScheme::ED25519,
rustls::SignatureScheme::ED448,
]
}
}
async fn handle_connection(incoming: IncomingConnection<()>, client: Arc<Client>) -> Result<(), BoxError> {
let (authenticated, _out) = incoming.authenticate().await?;
let client_conn = authenticated.wait_request().await?;
use socks5_impl::protocol::Reply;
use socks5_impl::server::connection::ClientConnection;
let (conn_ready, target_addr) = match client_conn {
ClientConnection::Connect(conn_need_reply, addr) => {
let conn_ready = conn_need_reply.reply(Reply::Succeeded, addr.clone()).await?;
(conn_ready, addr)
}
other => {
log::warn!("Unsupported SOCKS5 command: {:?}", other);
return Err("Unsupported SOCKS5 command".into());
}
};
log::info!("Connecting to target via proxy: {}", target_addr);
let proxy_stream = client.create_stream().await?;
let addr_data: Vec<u8> = target_addr.into();
proxy_stream.write(&addr_data).await?;
let (mut client_read, mut client_write) = conn_ready.into_split();
let proxy_stream_read = proxy_stream.clone();
let proxy_stream_write = proxy_stream.clone();
let c2p = tokio::spawn(async move {
let mut buf = vec![0u8; 4096];
let mut err = None;
loop {
match client_read.read(&mut buf).await {
Ok(0) => break,
Ok(n) => {
if let Err(e) = proxy_stream_write.write(&buf[..n]).await {
err = Some(e);
break;
}
}
Err(e) => {
err = Some(e);
break;
}
}
}
let _ = proxy_stream_write.close().await;
if let Some(e) = err {
log::debug!("Client to Proxy error: {e}");
}
});
let p2c = tokio::spawn(async move {
let mut buf = vec![0u8; 4096];
let mut err = None;
loop {
match proxy_stream_read.read(&mut buf).await {
Ok(0) => break,
Ok(n) => {
if let Err(e) = client_write.write_all(&buf[..n]).await {
err = Some(e);
break;
}
}
Err(e) => {
err = Some(e);
break;
}
}
}
let _ = client_write.shutdown().await;
if let Some(e) = err {
log::debug!("Proxy to Client error: {e}");
}
});
let _ = tokio::join!(c2p, p2c);
Ok(())
}