Skip to main content

rama_socks5/client/
bind.rs

1//! Contains [`bind-flow`] types such as the [`Binder`],
2//! used by a socks5 [`Client`] as part of the bind-handshake.
3//!
4//! [`bind-flow`]: crate::proto::Command::Bind
5//! [`Client`]: crate::Socks5Client
6
7use rama_core::io::Io;
8use rama_core::telemetry::tracing;
9use rama_net::address::{HostWithPort, SocketAddress};
10use std::fmt;
11
12use super::core::HandshakeError;
13use crate::proto::{ReplyKind, server};
14
15/// [`Binder`] is used to await for the socks5 server
16/// as it has to come back with a reply on whether or not
17/// the server has established a connection with the socks5 server.
18///
19/// [`Binder`] is provided by using the [`Client::handshake_bind`] method,
20/// and contains the [`requested_bind_address`] to be given to the server so it knows
21/// where to connect to. The one selected in the end will be [`selected_bind_address`]
22///
23/// [`Client::handshake_bind`]: crate::Socks5Client::handshake_bind
24/// [`requested_bind_address`]: Binder::requested_bind_address
25/// [`selected_bind_address`]: Binder::selected_bind_address
26pub struct Binder<S> {
27    stream: S,
28    requested_bind_address: Option<SocketAddress>,
29    selected_bind_address: SocketAddress,
30}
31
32/// Error that is returned in case the bind process while
33/// [waiting for the (2nd) success reply](Binder::connect)
34/// was not successfull.
35pub struct BindError<S> {
36    stream: S,
37    error: HandshakeError,
38}
39
40impl<S> BindError<S> {
41    #[inline]
42    /// [`ReplyKind::GeneralServerFailure`] is returned in case of an error
43    /// that is returned in case no reply was received from the (socks5) server.
44    pub fn reply(&self) -> ReplyKind {
45        self.error.reply()
46    }
47
48    /// Consume this error to take back ownership over the stream.
49    ///
50    /// NOTE that the stream is most likely in an unusable state,
51    /// so for most scenarios you probably just want to drop it.
52    pub fn into_stream(self) -> S {
53        self.stream
54    }
55}
56
57impl<S> fmt::Debug for BindError<S> {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        f.debug_struct("BindError")
60            .field("stream", &format_args!("{}", std::any::type_name::<S>()))
61            .field("error", &self.error)
62            .finish()
63    }
64}
65
66impl<S> fmt::Display for BindError<S> {
67    #[inline]
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        write!(f, "{}", self.error)
70    }
71}
72
73impl<S> std::error::Error for BindError<S> {
74    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
75        self.error.source()
76    }
77}
78
79/// Output that is returned in case the bind process while
80/// [waiting for the (2nd) success reply](Binder::connect)
81/// was successfull.
82pub struct BindOutput<S> {
83    /// Stream to transfer data via the socks5 server to the target server.
84    pub stream: S,
85    /// Possibly the address of the server as seen by the socks5 server.
86    pub server: HostWithPort,
87}
88
89impl<S: Io + Unpin> Binder<S> {
90    pub(crate) fn new(
91        stream: S,
92        requested_bind_address: Option<SocketAddress>,
93        selected_bind_address: SocketAddress,
94    ) -> Self {
95        Self {
96            stream,
97            requested_bind_address,
98            selected_bind_address,
99        }
100    }
101
102    /// Address of the address requested by
103    /// the socks5 (bind) client, if requested at all.
104    pub fn requested_bind_address(&self) -> Option<SocketAddress> {
105        self.requested_bind_address
106    }
107
108    /// Address of the socket that the socks5 server has opened
109    /// for the target server to connect to.
110    pub fn selected_bind_address(&self) -> SocketAddress {
111        self.selected_bind_address
112    }
113
114    /// Wait for the server to connect to the socks5 server
115    /// using the [selected bind address].
116    ///
117    /// [selected bind address]: Self::selected_bind_address
118    pub async fn connect(mut self) -> Result<BindOutput<S>, BindError<S>> {
119        let server = match server::Reply::read_from(&mut self.stream).await {
120            Ok(reply) => {
121                if reply.reply != ReplyKind::Succeeded {
122                    return Err(BindError {
123                        stream: self.stream,
124                        error: HandshakeError::reply_kind(reply.reply)
125                            .with_context("server responded with non-success reply"),
126                    });
127                }
128                reply.bind_address
129            }
130            Err(err) => {
131                return Err(BindError {
132                    stream: self.stream,
133                    error: HandshakeError::protocol(err).with_context("read server reply"),
134                });
135            }
136        };
137
138        tracing::trace!(
139            network.local.address = %self.selected_bind_address.ip_addr,
140            network.local.port = %self.selected_bind_address.port,
141            server.address = %server.host,
142            server.port = %server.port,
143            "socks5: bind handshake complete",
144        );
145
146        Ok(BindOutput {
147            stream: self.stream,
148            server,
149        })
150    }
151
152    /// Drop the [`Binder`] and return back ownership of the stream.
153    ///
154    /// Note that most likely this is not what you want to do as it
155    /// is in most cases not in a state useful to you.
156    pub fn into_stream(self) -> S {
157        self.stream
158    }
159}