Skip to main content

cuprate_p2p_transport/
socks.rs

1//! Socks Transport
2//!
3//! This module defines a transport method for the `ClearNet` network zone using a generic SOCKS5 proxy.
4//!
5
6//---------------------------------------------------------------------------------------------------- Imports
7
8use std::{
9    io::{self, ErrorKind},
10    net::SocketAddr,
11};
12
13use tokio::{
14    io::{AsyncReadExt, AsyncWriteExt},
15    net::{
16        tcp::{OwnedReadHalf, OwnedWriteHalf},
17        TcpStream,
18    },
19    time::{timeout, Duration},
20};
21use tokio_socks::tcp::Socks5Stream;
22use tokio_util::codec::{FramedRead, FramedWrite};
23
24use cuprate_p2p_core::{ClearNet, NetworkZone, Transport};
25use cuprate_wire::MoneroWireCodec;
26
27use crate::DisabledListener;
28
29/// Check if a Socks5 proxy is listening at `addr` via a protocol handshake.
30pub async fn is_socks5_proxy(addr: SocketAddr) -> bool {
31    let Ok(Ok(mut stream)) = timeout(Duration::from_secs(3), TcpStream::connect(addr)).await else {
32        return false;
33    };
34
35    // Socks5 greeting
36    if stream.write_all(&[0x05, 0x01, 0x00]).await.is_err() {
37        return false;
38    }
39
40    let mut buf = [0_u8; 2];
41    stream.read_exact(&mut buf).await.is_ok() && buf[0] == 0x05
42}
43
44//---------------------------------------------------------------------------------------------------- Configuration
45
46/// Socks5 proxied TCP transport.
47#[derive(Debug, Clone, Copy, Default)]
48pub struct Socks;
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct SocksClientConfig {
52    /// Proxy address
53    pub proxy: SocketAddr,
54
55    /// According to RFC 1929, if authentication is enabled, both username and password fields MUST NOT be empty.
56    pub authentication: Option<(String, String)>,
57}
58
59//---------------------------------------------------------------------------------------------------- Transport
60
61#[async_trait::async_trait]
62impl Transport<ClearNet> for Socks {
63    type ClientConfig = SocksClientConfig;
64    type ServerConfig = ();
65
66    type Stream = FramedRead<OwnedReadHalf, MoneroWireCodec>;
67    type Sink = FramedWrite<OwnedWriteHalf, MoneroWireCodec>;
68    type Listener = DisabledListener<ClearNet, OwnedReadHalf, OwnedWriteHalf>;
69
70    async fn connect_to_peer(
71        addr: <ClearNet as NetworkZone>::Addr,
72        config: &Self::ClientConfig,
73    ) -> Result<(Self::Stream, Self::Sink), io::Error> {
74        // Optional authentication
75        let proxy = if let Some((username, password)) = config.authentication.as_ref() {
76            Socks5Stream::connect_with_password(config.proxy, addr, username, password).await
77        } else {
78            Socks5Stream::connect(config.proxy, addr.to_string()).await
79        };
80
81        proxy
82            .map_err(|e| io::Error::new(ErrorKind::ConnectionAborted, e.to_string()))
83            .map(|stream| {
84                let (stream, sink) = stream.into_inner().into_split();
85                (
86                    FramedRead::new(stream, MoneroWireCodec::default()),
87                    FramedWrite::new(sink, MoneroWireCodec::default()),
88                )
89            })
90    }
91
92    async fn incoming_connection_listener(
93        _config: Self::ServerConfig,
94    ) -> Result<Self::Listener, io::Error> {
95        panic!("In proxy mode, inbound is disabled!");
96    }
97}