Skip to main content

ferogram_connect/
socks5.rs

1/*
2 * Copyright (c) 2026 Ankit Chaubey <ankitchaubey.dev@gmail.com>
3 * https://github.com/ankit-chaubey
4 *
5 * Project: ferogram
6 * Website: https://ferogram.dev
7 *
8 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
9 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
10 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
11 * This file may not be copied, modified, or distributed except according
12 * to those terms.
13 */
14
15use tokio::net::TcpStream;
16#[cfg(feature = "socks5")]
17use tokio_socks::tcp::Socks5Stream;
18
19use crate::error::ConnectError;
20
21/// SOCKS5 proxy configuration.
22#[derive(Clone, Debug)]
23pub struct Socks5Config {
24    /// Host:port of the SOCKS5 proxy server.
25    pub proxy_addr: String,
26    /// Optional username and password for proxy authentication.
27    pub auth: Option<(String, String)>,
28}
29
30impl Socks5Config {
31    /// Create an unauthenticated SOCKS5 config.
32    pub fn new(proxy_addr: impl Into<String>) -> Self {
33        Self {
34            proxy_addr: proxy_addr.into(),
35            auth: None,
36        }
37    }
38
39    /// Create a SOCKS5 config with username/password authentication.
40    pub fn with_auth(
41        proxy_addr: impl Into<String>,
42        username: impl Into<String>,
43        password: impl Into<String>,
44    ) -> Self {
45        Self {
46            proxy_addr: proxy_addr.into(),
47            auth: Some((username.into(), password.into())),
48        }
49    }
50
51    /// Establish a TCP connection through this SOCKS5 proxy.
52    #[cfg(feature = "socks5")]
53    pub async fn connect(&self, target: &str) -> Result<TcpStream, ConnectError> {
54        tracing::debug!(
55            "[ferogram::connect] SOCKS5: relaying through {} to {target}",
56            self.proxy_addr
57        );
58        let stream = match &self.auth {
59            None => Socks5Stream::connect(self.proxy_addr.as_str(), target)
60                .await
61                .map_err(|e| ConnectError::Io(std::io::Error::other(e)))?,
62            Some((user, pass)) => Socks5Stream::connect_with_password(
63                self.proxy_addr.as_str(),
64                target,
65                user.as_str(),
66                pass.as_str(),
67            )
68            .await
69            .map_err(|e| ConnectError::Io(std::io::Error::other(e)))?,
70        };
71        Ok(stream.into_inner())
72    }
73
74    /// Establish a TCP connection through this SOCKS5 proxy.
75    ///
76    /// Returns an error: the "socks5" feature is disabled, so no SOCKS5
77    /// client is compiled in.
78    #[cfg(not(feature = "socks5"))]
79    pub async fn connect(&self, _target: &str) -> Result<TcpStream, ConnectError> {
80        Err(ConnectError::Io(std::io::Error::new(
81            std::io::ErrorKind::Unsupported,
82            "SOCKS5 proxy requested but ferogram-connect was built without the \"socks5\" feature",
83        )))
84    }
85}