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}