Skip to main content

layer_client/
socks5.rs

1//! SOCKS5 proxy connector.
2//!
3//! Provides [`Socks5Config`] that can be attached to a [`crate::Config`]
4//! so every Telegram connection is routed through a SOCKS5 proxy.
5//!
6//! # Example
7//! ```rust,no_run
8//! use layer_client::{Config, proxy::Socks5Config};
9//! use std::sync::Arc;
10//! use layer_client::retry::AutoSleep;
11//!
12//! let cfg = Config {
13//!     socks5: Some(Socks5Config::new("127.0.0.1:1080")),
14//!     ..Default::default()
15//! };
16//! ```
17
18use tokio::net::TcpStream;
19use tokio_socks::tcp::Socks5Stream;
20use crate::InvocationError;
21
22/// SOCKS5 proxy configuration.
23#[derive(Clone, Debug)]
24pub struct Socks5Config {
25    /// Host:port of the SOCKS5 proxy server.
26    pub proxy_addr: String,
27    /// Optional username and password for proxy authentication.
28    pub auth: Option<(String, String)>,
29}
30
31impl Socks5Config {
32    /// Create an unauthenticated SOCKS5 config.
33    pub fn new(proxy_addr: impl Into<String>) -> Self {
34        Self { proxy_addr: proxy_addr.into(), auth: None }
35    }
36
37    /// Create a SOCKS5 config with username/password authentication.
38    pub fn with_auth(
39        proxy_addr: impl Into<String>,
40        username:   impl Into<String>,
41        password:   impl Into<String>,
42    ) -> Self {
43        Self {
44            proxy_addr: proxy_addr.into(),
45            auth: Some((username.into(), password.into())),
46        }
47    }
48
49    /// Establish a TCP connection through this SOCKS5 proxy.
50    ///
51    /// Returns a [`TcpStream`] tunnelled through the proxy to `target`.
52    pub async fn connect(&self, target: &str) -> Result<TcpStream, InvocationError> {
53        log::info!("[socks5] Connecting via {} → {target}", self.proxy_addr);
54        let stream = match &self.auth {
55            None => {
56                Socks5Stream::connect(self.proxy_addr.as_str(), target)
57                    .await
58                    .map_err(|e| InvocationError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?
59            }
60            Some((user, pass)) => {
61                Socks5Stream::connect_with_password(
62                    self.proxy_addr.as_str(),
63                    target,
64                    user.as_str(),
65                    pass.as_str(),
66                )
67                .await
68                .map_err(|e| InvocationError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?
69            }
70        };
71        log::info!("[socks5] Connected ✓");
72        Ok(stream.into_inner())
73    }
74}