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