Skip to main content

arti/proxy/
socks.rs

1//! SOCKS-specific proxy support.
2
3use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, BufReader};
4use safelog::sensitive;
5use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
6use std::sync::Arc;
7use tracing::{debug, instrument, warn};
8
9#[allow(unused)]
10use arti_client::HasKind;
11use arti_client::{ErrorKind, IntoTorAddr as _, StreamPrefs};
12#[cfg(feature = "rpc")]
13use tor_rpcbase::{self as rpc};
14use tor_rtcompat::Runtime;
15use tor_socksproto::{Handshake as _, SocksAddr, SocksAuth, SocksCmd, SocksRequest};
16
17use anyhow::{Context, Result, anyhow};
18
19use super::{
20    ListenerIsolation, ProvidedIsolation, ProxyContext, StreamIsolationKey, write_all_and_close,
21    write_all_and_flush,
22};
23cfg_if::cfg_if! {
24    if #[cfg(feature="rpc")] {
25        use crate::rpc::conntarget::ConnTarget;
26    } else {
27        use arti_client::TorClient;
28
29        /// A type returned by get_prefs_and_session,
30        /// and used to launch data streams or resolve attempts.
31        ///
32        /// TODO RPC: This is quite ugly; we should do something better.
33        /// At least, we should never expose this outside the socks module.
34        type ConnTarget<R> = Arc<TorClient<R>>;
35    }
36}
37
38/// Payload to return when an HTTP connection arrive on a Socks port
39/// without HTTP support.
40pub(super) const WRONG_PROTOCOL_PAYLOAD: &[u8] = br#"HTTP/1.0 501 Not running as an HTTP Proxy
41Content-Type: text/html; charset=utf-8
42
43<!DOCTYPE html>
44<html>
45<head>
46<title>This is a SOCKS Proxy, Not An HTTP Proxy</title>
47</head>
48<body>
49<h1>This is a SOCKS proxy, not an HTTP proxy.</h1>
50<p>
51It appears you have configured your web browser to use this Tor port as
52an HTTP proxy.
53</p>
54<p>
55This is not correct: This port is configured as a SOCKS proxy, not
56an HTTP proxy. If you need an HTTP proxy tunnel,
57build Arti with the <code>http-connect</code> feature enabled.
58</p>
59<p>
60See <a href="https://gitlab.torproject.org/tpo/core/arti/#todo-need-to-change-when-arti-get-a-user-documentation">https://gitlab.torproject.org/tpo/core/arti</a> for more information.
61</p>
62</body>
63</html>"#;
64
65/// Find out which kind of address family we can/should use for a
66/// given `SocksRequest`.
67#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
68fn stream_preference(req: &SocksRequest, addr: &str) -> StreamPrefs {
69    let mut prefs = StreamPrefs::new();
70    if addr.parse::<Ipv4Addr>().is_ok() {
71        // If they asked for an IPv4 address correctly, nothing else will do.
72        prefs.ipv4_only();
73    } else if addr.parse::<Ipv6Addr>().is_ok() {
74        // If they asked for an IPv6 address correctly, nothing else will do.
75        prefs.ipv6_only();
76    } else if req.version() == tor_socksproto::SocksVersion::V4 {
77        // SOCKS4 and SOCKS4a only support IPv4
78        prefs.ipv4_only();
79    } else {
80        // Otherwise, default to saying IPv4 is preferred.
81        prefs.ipv4_preferred();
82    }
83    prefs
84}
85
86/// The meaning of a SOCKS authentication field, according to our conventions.
87struct AuthInterpretation {
88    /// Associate this stream with a DataStream created by using a particular RPC object
89    /// as a Tor client.
90    #[cfg(feature = "rpc")]
91    rpc_object: Option<rpc::ObjectId>,
92
93    /// Isolate this stream from other streams that do not have the same
94    /// value.
95    isolation: ProvidedIsolation,
96}
97
98/// Given the authentication object from a socks connection, determine what it's telling
99/// us to do.
100///
101/// (In no case is it actually SOCKS authentication: it can either be a message
102/// to the stream isolation system or the RPC system.)
103fn interpret_socks_auth(auth: &SocksAuth) -> Result<AuthInterpretation> {
104    /// Interpretation of a SOCKS5 username according to
105    /// the [SOCKS extended authentication](https://spec.torproject.org/socks-extensions.html#extended-auth)
106    /// specification.
107    enum Uname<'a> {
108        /// This is a legacy username; it's just part of the
109        /// isolation information.
110        //
111        // Note: We're not actually throwing away the username here;
112        // instead we're going to use the whole SocksAuth
113        // in a `ProvidedAuthentication::Legacy``.
114        // TODO RPC: Find a more idiomatic way to express this data flow.
115        Legacy,
116        /// This is using the socks extension: contains the extension
117        /// format code and the remaining information from the username.
118        Extended(u8, &'a [u8]),
119    }
120    /// Helper: Try to interpret a SOCKS5 username field as indicating the start of a set of
121    /// extended socks authentication information.
122    ///
123    /// Implements [SOCKS extended authentication](https://spec.torproject.org/socks-extensions.html#extended-auth).
124    ///
125    /// If it does indicate that extensions are in use,
126    /// return a `Uname::Extended` containing
127    /// the extension format type and the remaining information from the username.
128    ///
129    /// If it indicates that no extensions are in use,
130    /// return `Uname::Legacy`.
131    ///
132    /// If it is badly formatted, return an error.
133    fn interpret_socks5_username(username: &[u8]) -> Result<Uname<'_>> {
134        /// 8-byte "magic" sequence from
135        /// [SOCKS extended authentication](https://spec.torproject.org/socks-extensions.html#extended-auth).
136        /// When it appears at the start of a username,
137        /// indicates that the username/password are to be interpreted as
138        /// as encoding SOCKS5 extended parameters,
139        /// but the format might not be one we recognize.
140        const SOCKS_EXT_CONST_ANY: &[u8] = b"<torS0X>";
141        let Some(remainder) = username.strip_prefix(SOCKS_EXT_CONST_ANY) else {
142            return Ok(Uname::Legacy);
143        };
144        let (format_code, remainder) = remainder
145            .split_at_checked(1)
146            .ok_or_else(|| anyhow!("Extended SOCKS information without format code."))?;
147        Ok(Uname::Extended(format_code[0], remainder))
148    }
149
150    let isolation = match auth {
151        SocksAuth::Username(user, pass) => match interpret_socks5_username(user)? {
152            Uname::Legacy => ProvidedIsolation::LegacySocks(auth.clone()),
153            Uname::Extended(b'1', b"") => {
154                return Err(anyhow!("Received empty RPC object ID"));
155            }
156            Uname::Extended(format_code @ b'1', remainder) => {
157                #[cfg(not(feature = "rpc"))]
158                return Err(anyhow!(
159                    "Received RPC object ID, but not built with support for RPC"
160                ));
161                #[cfg(feature = "rpc")]
162                return Ok(AuthInterpretation {
163                    rpc_object: Some(rpc::ObjectId::from(
164                        std::str::from_utf8(remainder).context("Rpc object ID was not utf-8")?,
165                    )),
166                    isolation: ProvidedIsolation::ExtendedSocks {
167                        format_code,
168                        isolation: pass.clone().into(),
169                    },
170                });
171            }
172            Uname::Extended(format_code @ b'0', b"") => ProvidedIsolation::ExtendedSocks {
173                format_code,
174                isolation: pass.clone().into(),
175            },
176            Uname::Extended(b'0', _) => {
177                return Err(anyhow!("Extraneous information in SOCKS username field."));
178            }
179            _ => return Err(anyhow!("Unrecognized SOCKS format code")),
180        },
181        _ => ProvidedIsolation::LegacySocks(auth.clone()),
182    };
183    tracing::debug!(
184        "socks auth {:?} -> isolation {:?}",
185        sensitive(&auth),
186        sensitive(&isolation)
187    );
188
189    Ok(AuthInterpretation {
190        #[cfg(feature = "rpc")]
191        rpc_object: None,
192        isolation,
193    })
194}
195
196impl<R: Runtime> super::ProxyContext<R> {
197    /// Interpret a SOCKS request and our input information to determine which
198    /// TorClient / ClientConnectionTarget object and StreamPrefs we should use.
199    ///
200    /// TODO RPC: The return type here is a bit ugly.
201    fn get_prefs_and_session(
202        &self,
203        request: &SocksRequest,
204        target_addr: &str,
205        conn_isolation: ListenerIsolation,
206    ) -> Result<(StreamPrefs, ConnTarget<R>)> {
207        // Determine whether we want to ask for IPv4/IPv6 addresses.
208        let mut prefs = stream_preference(request, target_addr);
209
210        // Interpret socks authentication to see whether we want to connect to an RPC connector.
211        let interp = interpret_socks_auth(request.auth())?;
212        prefs.set_isolation(StreamIsolationKey(conn_isolation, interp.isolation));
213
214        #[cfg(feature = "rpc")]
215        if let Some(session) = interp.rpc_object {
216            if let Some(mgr) = &self.rpc_mgr {
217                let (context, object) = mgr
218                    .lookup_object(&session)
219                    .context("no such session found")?;
220                let target = ConnTarget::Rpc { context, object };
221                return Ok((prefs, target));
222            } else {
223                return Err(anyhow!("no rpc manager found!?"));
224            }
225        }
226
227        let client = self.tor_client.clone();
228        #[cfg(feature = "rpc")]
229        let client = ConnTarget::Client(Arc::clone(&client));
230
231        Ok((prefs, client))
232    }
233}
234
235/// Given a just-received TCP connection `S` on a SOCKS port, handle the
236/// SOCKS handshake and relay the connection over the Tor network.
237///
238/// Uses `isolation_info` to decide which circuits this connection
239/// may use.  Requires that `isolation_info` is a pair listing the listener
240/// id and the source address for the socks request.
241#[instrument(skip_all, level = "trace")]
242pub(super) async fn handle_socks_conn<R, S>(
243    context: ProxyContext<R>,
244    mut socks_stream: BufReader<S>,
245    isolation_info: ListenerIsolation,
246) -> Result<()>
247where
248    R: Runtime,
249    S: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
250{
251    // Part 1: Perform the SOCKS handshake, to learn where we are
252    // being asked to connect, and what we're being asked to do once
253    // we connect there.
254    //
255    // The SOCKS handshake can require multiple round trips (SOCKS5
256    // always does) so we need to run this part of the process in a
257    // loop.
258    let mut handshake = tor_socksproto::SocksProxyHandshake::new();
259
260    let mut inbuf = tor_socksproto::Buffer::new();
261    let request = loop {
262        use tor_socksproto::NextStep as NS;
263
264        // Try to perform the next step in the handshake.
265        // (If there is an handshake error, don't reply with a Socks error, remote does not
266        // seems to speak Socks.)
267        let step = handshake.step(&mut inbuf)?;
268
269        match step {
270            NS::Recv(mut recv) => {
271                let n = socks_stream
272                    .read(recv.buf())
273                    .await
274                    .context("Error while reading SOCKS handshake")?;
275                recv.note_received(n)?;
276            }
277            NS::Send(data) => write_all_and_flush(&mut socks_stream, &data).await?,
278            NS::Finished(fin) => break fin.into_output_forbid_pipelining()?,
279        }
280    };
281
282    // Make sure there is no buffered data!
283    if !socks_stream.buffer().is_empty() {
284        let error = tor_socksproto::Error::ForbiddenPipelining;
285        return reply_error(&mut socks_stream, &request, error.kind()).await;
286    }
287
288    // Unpack the socks request and find out where we're connecting to.
289    let addr = request.addr().to_string();
290    let port = request.port();
291    debug!(
292        "Got a socks request: {} {}:{}",
293        request.command(),
294        sensitive(&addr),
295        port
296    );
297
298    let (prefs, tor_client) = context.get_prefs_and_session(&request, &addr, isolation_info)?;
299
300    match request.command() {
301        SocksCmd::CONNECT => {
302            // The SOCKS request wants us to connect to a given address.
303            // So, launch a connection over Tor.
304            let tor_addr = (addr.clone(), port).into_tor_addr()?;
305            let tor_stream = tor_client.connect_with_prefs(&tor_addr, &prefs).await;
306            let tor_stream = match tor_stream {
307                Ok(s) => s,
308                Err(e) => return reply_error(&mut socks_stream, &request, e.kind()).await,
309            };
310            // Okay, great! We have a connection over the Tor network.
311            debug!("Got a stream for {}:{}", sensitive(&addr), port);
312
313            // Send back a SOCKS response, telling the client that it
314            // successfully connected.
315            let reply = request
316                .reply(tor_socksproto::SocksStatus::SUCCEEDED, None)
317                .context("Encoding socks reply")?;
318            write_all_and_flush(&mut socks_stream, &reply[..]).await?;
319
320            let tor_stream = BufReader::with_capacity(super::APP_STREAM_BUF_LEN, tor_stream);
321
322            // Finally, relay traffic between
323            // the socks stream and the tor stream.
324            futures_copy::copy_buf_bidirectional(
325                socks_stream,
326                tor_stream,
327                futures_copy::eof::Close,
328                futures_copy::eof::Close,
329            )
330            .await?;
331        }
332        SocksCmd::RESOLVE => {
333            // We've been asked to perform a regular hostname lookup.
334            // (This is a tor-specific SOCKS extension.)
335
336            let addr = if let Ok(addr) = addr.parse() {
337                // if this is a valid ip address, just parse it and reply.
338                Ok(addr)
339            } else {
340                tor_client
341                    .resolve_with_prefs(&addr, &prefs)
342                    .await
343                    .map_err(|e| e.kind())
344                    .and_then(|addrs| addrs.first().copied().ok_or(ErrorKind::Other))
345            };
346            match addr {
347                Ok(addr) => {
348                    let reply = request
349                        .reply(
350                            tor_socksproto::SocksStatus::SUCCEEDED,
351                            Some(&SocksAddr::Ip(addr)),
352                        )
353                        .context("Encoding socks reply")?;
354                    write_all_and_close(&mut socks_stream, &reply[..]).await?;
355                }
356                Err(e) => return reply_error(&mut socks_stream, &request, e).await,
357            }
358        }
359        SocksCmd::RESOLVE_PTR => {
360            // We've been asked to perform a reverse hostname lookup.
361            // (This is a tor-specific SOCKS extension.)
362            let addr: IpAddr = match addr.parse() {
363                Ok(ip) => ip,
364                Err(e) => {
365                    let reply = request
366                        .reply(tor_socksproto::SocksStatus::ADDRTYPE_NOT_SUPPORTED, None)
367                        .context("Encoding socks reply")?;
368                    write_all_and_close(&mut socks_stream, &reply[..]).await?;
369                    return Err(anyhow!(e));
370                }
371            };
372            let hosts = match tor_client.resolve_ptr_with_prefs(addr, &prefs).await {
373                Ok(hosts) => hosts,
374                Err(e) => return reply_error(&mut socks_stream, &request, e.kind()).await,
375            };
376            if let Some(host) = hosts.into_iter().next() {
377                // this conversion should never fail, legal DNS names len must be <= 253 but Socks
378                // names can be up to 255 chars.
379                let hostname = SocksAddr::Hostname(host.try_into()?);
380                let reply = request
381                    .reply(tor_socksproto::SocksStatus::SUCCEEDED, Some(&hostname))
382                    .context("Encoding socks reply")?;
383                write_all_and_close(&mut socks_stream, &reply[..]).await?;
384            }
385        }
386        _ => {
387            // We don't support this SOCKS command.
388            warn!("Dropping request; {:?} is unsupported", request.command());
389            let reply = request
390                .reply(tor_socksproto::SocksStatus::COMMAND_NOT_SUPPORTED, None)
391                .context("Encoding socks reply")?;
392            write_all_and_close(&mut socks_stream, &reply[..]).await?;
393        }
394    };
395
396    // TODO: we should close the TCP stream if either task fails. Do we?
397    // See #211 and #190.
398
399    Ok(())
400}
401
402/// Reply a Socks error based on an arti-client Error and close the stream.
403/// Returns the error provided in parameter
404async fn reply_error<W>(
405    writer: &mut W,
406    request: &SocksRequest,
407    error: arti_client::ErrorKind,
408) -> Result<()>
409where
410    W: AsyncWrite + Unpin,
411{
412    use {ErrorKind as EK, tor_socksproto::SocksStatus as S};
413
414    // TODO: Currently we _always_ try to return extended SOCKS return values
415    // for onion service failures from proposal 304 when they are appropriate.
416    // But according to prop 304, this is something we should only do when it's
417    // requested, for compatibility with SOCKS implementations that can't handle
418    // unexpected REP codes.
419    //
420    // I suggest we make these extended error codes "always-on" for now, and
421    // later add a feature to disable them if it's needed. -nickm
422
423    // TODO: Perhaps we should map the extended SOCKS return values for onion
424    // service failures unconditionally, even if we haven't compiled in onion
425    // service client support.  We can make that change after the relevant
426    // ErrorKinds are no longer `experimental-api` in `tor-error`.
427
428    // We need to send an error. See what kind it is.
429    //
430    // TODO: Perhaps move this to tor-error, so it can be an exhaustive match.
431    let status = match error {
432        EK::RemoteNetworkFailed => S::TTL_EXPIRED,
433
434        #[cfg(feature = "onion-service-client")]
435        EK::OnionServiceNotFound => S::HS_DESC_NOT_FOUND,
436        #[cfg(feature = "onion-service-client")]
437        EK::OnionServiceAddressInvalid => S::HS_BAD_ADDRESS,
438        #[cfg(feature = "onion-service-client")]
439        EK::OnionServiceMissingClientAuth => S::HS_MISSING_CLIENT_AUTH,
440        #[cfg(feature = "onion-service-client")]
441        EK::OnionServiceWrongClientAuth => S::HS_WRONG_CLIENT_AUTH,
442
443        // NOTE: This is not a perfect correspondence from these ErrorKinds to
444        // the errors we're returning here. In the longer run, we'll want to
445        // encourage other ways to indicate failure to clients.  Those ways might
446        // include encouraging HTTP CONNECT, or the RPC system, both of which
447        // would give us more robust ways to report different kinds of failure.
448        #[cfg(feature = "onion-service-client")]
449        EK::OnionServiceNotRunning
450        | EK::OnionServiceConnectionFailed
451        | EK::OnionServiceProtocolViolation => S::HS_INTRO_FAILED,
452
453        _ => S::GENERAL_FAILURE,
454    };
455    let reply = request
456        .reply(status, None)
457        .context("Encoding socks reply")?;
458    // if writing back the error fail, still return the original error
459    let _ = write_all_and_close(writer, &reply[..]).await;
460
461    Err(anyhow!(error))
462}