Skip to main content

trojan_client/
lib.rs

1//! Trojan client with SOCKS5 proxy.
2//!
3//! This crate provides a local SOCKS5 proxy that forwards connections through
4//! the Trojan protocol over TLS to a remote trojan server.
5
6pub mod cli;
7pub mod config;
8mod connector;
9mod error;
10mod handler;
11pub mod socks5;
12
13pub use cli::ClientArgs;
14pub use config::{ClientConfig, load_client_config};
15pub use connector::ClientState;
16pub use error::ClientError;
17
18use std::sync::Arc;
19use std::time::Duration;
20
21use tokio::net::TcpListener;
22use tokio_rustls::TlsConnector;
23use tokio_util::sync::CancellationToken;
24use tracing::{error, info};
25use trojan_auth::sha224_hex;
26use trojan_core::defaults::DEFAULT_TLS_HANDSHAKE_TIMEOUT_SECS;
27use trojan_dns::DnsResolver;
28
29/// Run the trojan client with the given configuration.
30pub async fn run(config: ClientConfig, shutdown: CancellationToken) -> Result<(), ClientError> {
31    // Compute password hash
32    let hash_hex = sha224_hex(&config.client.password);
33
34    // Build DNS resolver from config
35    let dns_resolver = DnsResolver::new(&config.client.dns)
36        .map_err(|e| ClientError::Config(format!("dns resolver: {e}")))?;
37    info!(dns = ?config.client.dns.strategy, "dns resolver initialized");
38
39    // Build TLS config
40    let tls_config = connector::build_tls_config(&config.client.tls)?;
41    let tls_connector = TlsConnector::from(Arc::new(tls_config));
42    let sni = connector::resolve_sni(&config.client.tls, &config.client.remote)?;
43
44    let state = Arc::new(ClientState {
45        hash_hex,
46        remote_addr: config.client.remote.clone(),
47        tls_connector,
48        sni,
49        tcp_config: config.client.tcp.clone(),
50        tls_handshake_timeout: Duration::from_secs(DEFAULT_TLS_HANDSHAKE_TIMEOUT_SECS),
51        dns_resolver,
52    });
53
54    // Bind SOCKS5 listener
55    let listener = TcpListener::bind(&config.client.listen).await?;
56    info!(listen = %config.client.listen, remote = %config.client.remote, "trojan client started");
57
58    loop {
59        tokio::select! {
60            result = listener.accept() => {
61                match result {
62                    Ok((stream, peer)) => {
63                        let state = state.clone();
64                        tokio::spawn(async move {
65                            handler::handle_socks5_conn(stream, peer, state).await;
66                        });
67                    }
68                    Err(e) => {
69                        error!(error = %e, "failed to accept connection");
70                    }
71                }
72            }
73            _ = shutdown.cancelled() => {
74                info!("shutting down client");
75                break;
76            }
77        }
78    }
79
80    Ok(())
81}