pt_proxy/
main.rs

1use anyhow::Result;
2use clap::{Parser, Subcommand};
3use fast_socks5::client::{Config, Socks5Stream};
4use fast_socks5::server::{AcceptAuthentication, Socks5Server};
5use std::str::FromStr;
6use tokio::io::AsyncWriteExt;
7use tokio::net::{TcpListener, TcpStream};
8use tokio::sync::oneshot;
9use tokio::time::Duration;
10use tokio_stream::StreamExt;
11use tor_chanmgr::transport::proxied::{settings_to_protocol, Protocol};
12use tor_linkspec::PtTransportName;
13use tor_ptmgr::ipc::{
14    PluggableClientTransport, PluggableServerTransport, PluggableTransport, PtClientParameters,
15    PtCommonParameters, PtServerParameters,
16};
17use tor_rtcompat::PreferredRuntime;
18use tor_socksproto::{SocksAuth, SocksVersion};
19
20/// The location where the obfs4 server will store its state
21const SERVER_STATE_LOCATION: &str = "/tmp/arti-pt";
22/// The location where the obfs4 client will store its state
23const CLIENT_STATE_LOCATION: &str = "/tmp/arti-pt-client";
24
25/// Error defined to denote a failure to get the bridge line
26#[derive(Debug, thiserror::Error)]
27#[error("Error while obtaining bridge line data")]
28struct BridgeLineParseError;
29
30/// Specify which mode we wish to use the program in
31#[derive(Subcommand)]
32enum Command {
33    /// Enable client mode
34    Client {
35        /// The local port that programs will point traffic to
36        #[arg(short, long, default_value = "9050")]
37        client_port: u16,
38        /// Remote IP that connections should go to, this is an
39        /// obfs4 server
40        #[arg(required = true)]
41        remote_obfs4_ip: String,
42        /// Remote port that connections should go to, this is an
43        /// obfs4 server
44        #[arg(required = true)]
45        remote_obfs4_port: u16,
46        /// Info about the server process that is required to connect
47        /// successfully
48        #[arg(required = true)]
49        obfs4_auth_info: String,
50    },
51    /// Enable server mode
52    Server {
53        /// Address on which the obfs4 server should listen in for
54        /// incoming connections
55        #[arg(required = true)]
56        listen_address: String,
57        /// The local port the obfs4 server directs connections to
58        ///
59        /// Programs generally don't interact directly with it,
60        /// so this doesn't need to be set
61        #[arg(default_value = "4000")]
62        final_socks5_port: u16,
63    },
64}
65
66/// Tunnel SOCKS5 traffic through obfs4 connections
67#[derive(Parser)]
68#[command(author, version, about, long_about = None)]
69struct Args {
70    #[command(subcommand)]
71    command: Command,
72    /// Binary to use to launch obfs4 client
73    #[arg(required = true)]
74    obfs4_path: String,
75}
76
77/// Store the data we need to connect to the obfs4 client
78///
79/// The obfs4 client in turn connects to the obfs4 server
80#[derive(Clone)]
81struct ForwardingCreds {
82    username: String,
83    password: String,
84    forward_endpoint: String,
85    obfs4_server_ip: String,
86    obfs4_server_port: u16,
87}
88
89/// Create the config to launch an obfs4 server process
90fn build_server_config(
91    protocol: &str,
92    bind_addr: &str,
93    forwarding_server_addr: &str,
94) -> Result<(PtCommonParameters, PtServerParameters)> {
95    let bindaddr_formatted = format!("{}-{}", &protocol, bind_addr);
96    let orport = forwarding_server_addr.to_string();
97    Ok((
98        PtCommonParameters::builder()
99            .state_location(SERVER_STATE_LOCATION.into())
100            .timeout(Some(Duration::from_secs(1)))
101            .build()?,
102        PtServerParameters::builder()
103            .transports(vec![protocol.parse()?])
104            .server_bindaddr(bindaddr_formatted)
105            .server_orport(Some(orport))
106            .build()?,
107    ))
108}
109
110/// Read cert info and relay it to the user
111fn read_cert_info() -> Result<String> {
112    let file_path = format!("{}/obfs4_bridgeline.txt", SERVER_STATE_LOCATION);
113    match std::fs::read_to_string(file_path) {
114        Ok(contents) => {
115            let line = contents
116                .lines()
117                .find(|line| line.contains("Bridge obfs4"))
118                .ok_or(BridgeLineParseError)?;
119            let cert = line
120                .split_whitespace()
121                .find(|part| part.starts_with("cert="))
122                .ok_or(BridgeLineParseError)?;
123            let iat = line
124                .split_whitespace()
125                .find(|part| part.starts_with("iat-mode="))
126                .ok_or(BridgeLineParseError)?;
127            let complete_config = format!("{};{}", cert, iat);
128            Ok(complete_config)
129        }
130        Err(e) => Err(e.into()),
131    }
132}
133
134/// Create the config to launch an obfs4 client process
135fn build_client_config(protocol: &str) -> Result<(PtCommonParameters, PtClientParameters)> {
136    Ok((
137        PtCommonParameters::builder()
138            .state_location(CLIENT_STATE_LOCATION.into())
139            .timeout(Some(Duration::from_secs(1)))
140            .build()?,
141        PtClientParameters::builder()
142            .transports(vec![protocol.parse()?])
143            .build()?,
144    ))
145}
146
147/// Create a SOCKS5 connection to the obfs4 client
148async fn connect_to_obfs4_client(
149    forward_creds: ForwardingCreds,
150) -> Result<Socks5Stream<TcpStream>> {
151    let config = Config::default();
152    Ok(Socks5Stream::connect_with_password(
153        forward_creds.forward_endpoint,
154        forward_creds.obfs4_server_ip,
155        forward_creds.obfs4_server_port,
156        forward_creds.username,
157        forward_creds.password,
158        config,
159    )
160    .await?)
161}
162
163/// Launch obfs4 client process
164async fn launch_obfs4_client_process(
165    obfs4_path: String,
166) -> anyhow::Result<PluggableClientTransport> {
167    let (common_params, client_params) = build_client_config("obfs4")?;
168    let mut client_pt = PluggableClientTransport::new(
169        obfs4_path.into(),
170        vec![
171            "-enableLogging".to_string(),
172            "-logLevel".to_string(),
173            "DEBUG".to_string(),
174            "-unsafeLogging".to_string(),
175        ],
176        common_params,
177        client_params,
178    );
179    client_pt.launch(PreferredRuntime::current()?).await?;
180    Ok(client_pt)
181}
182
183/// Launch obfs4 server process
184async fn launch_obfs4_server_process(
185    obfs4_path: String,
186    listen_address: String,
187    final_socks5_endpoint: String,
188) -> anyhow::Result<PluggableServerTransport> {
189    let (common_params, server_params) =
190        build_server_config("obfs4", &listen_address, &final_socks5_endpoint)?;
191
192    let mut server_pt = PluggableServerTransport::new(
193        obfs4_path.into(),
194        vec![
195            "-enableLogging".to_string(),
196            "-logLevel".to_string(),
197            "DEBUG".to_string(),
198            "-unsafeLogging".to_string(),
199        ],
200        common_params,
201        server_params,
202    );
203    server_pt.launch(PreferredRuntime::current()?).await?;
204    Ok(server_pt)
205}
206
207/// Launch the dumb TCP pipe, whose only job is to abstract away the obfs4 client
208/// and its complicated setup, and just forward bytes between the obfs4 client
209/// and the client
210async fn run_forwarding_server(endpoint: &str, forward_creds: ForwardingCreds) -> Result<()> {
211    let listener = TcpListener::bind(endpoint).await?;
212    while let Ok((mut client, _)) = listener.accept().await {
213        let forward_creds_clone = forward_creds.clone();
214        match connect_to_obfs4_client(forward_creds_clone).await {
215            Ok(mut relay_stream) => {
216                if let Err(e) = tokio::io::copy_bidirectional(&mut client, &mut relay_stream).await
217                {
218                    eprintln!("{:#?}", e);
219                }
220            }
221            Err(e) => {
222                eprintln!("Couldn't connect to obfs4 client: \"{}\"", e);
223                // Report "No authentication method was acceptable" to user
224                // For more info refer to RFC 1928
225                client.write_all(&[5, 0xFF]).await.unwrap();
226            }
227        }
228    }
229    Ok(())
230}
231
232/// Run the final hop of the connection, which finally makes the actual
233/// network request to the intended host and relays it back
234async fn run_socks5_server(endpoint: &str) -> Result<oneshot::Receiver<bool>> {
235    let listener = Socks5Server::<AcceptAuthentication>::bind(endpoint).await?;
236    let (tx, rx) = oneshot::channel::<bool>();
237    tokio::spawn(async move {
238        while let Some(Ok(socks_socket)) = listener.incoming().next().await {
239            tokio::spawn(async move {
240                if let Err(e) = socks_socket.upgrade_to_socks5().await {
241                    eprintln!("{:#?}", e);
242                }
243            });
244        }
245        tx.send(true).unwrap()
246    });
247    Ok(rx)
248}
249
250/// Main function, ties everything together and parses arguments etc.
251#[tokio::main]
252async fn main() -> Result<()> {
253    tracing_subscriber::fmt::init();
254    let args = Args::parse();
255    let obfs4_path = args.obfs4_path;
256    match args.command {
257        Command::Client {
258            client_port,
259            remote_obfs4_ip,
260            remote_obfs4_port,
261            obfs4_auth_info: obfs4_server_conf,
262        } => {
263            let entry_addr = format!("127.0.0.1:{}", client_port);
264
265            let client_pt = launch_obfs4_client_process(obfs4_path).await?;
266            let client_endpoint = client_pt
267                .transport_methods()
268                .get(&PtTransportName::from_str("obfs4")?)
269                .unwrap()
270                .endpoint()
271                .to_string();
272
273            let settings = settings_to_protocol(SocksVersion::V5, obfs4_server_conf)?;
274            match settings {
275                Protocol::Socks(_, auth) => match auth {
276                    SocksAuth::Username(raw_username, raw_password) => {
277                        let username = String::from_utf8(raw_username)?;
278                        let password = match raw_password.is_empty() {
279                            true => String::from("\0"),
280                            false => String::from_utf8(raw_password)?,
281                        };
282                        let creds = ForwardingCreds {
283                            username,
284                            password,
285                            forward_endpoint: client_endpoint,
286                            obfs4_server_ip: remote_obfs4_ip,
287                            obfs4_server_port: remote_obfs4_port,
288                        };
289                        println!();
290                        println!("Listening on: {}", entry_addr);
291                        run_forwarding_server(&entry_addr, creds).await?;
292                    }
293                    _ => eprintln!("Unable to get credentials for obfs4 client process!"),
294                },
295                _ => eprintln!("Unexpected protocol"),
296            }
297        }
298        Command::Server {
299            listen_address,
300            final_socks5_port,
301        } => {
302            let final_socks5_endpoint = format!("127.0.0.1:{}", final_socks5_port);
303            let exit_rx = run_socks5_server(&final_socks5_endpoint).await?;
304            println!();
305            println!("Listening on: {}", listen_address);
306            launch_obfs4_server_process(obfs4_path, listen_address, final_socks5_endpoint).await?;
307            let auth_info = read_cert_info().unwrap();
308            println!();
309            println!("Authentication info is: {}", auth_info);
310            exit_rx.await.unwrap();
311        }
312    }
313    Ok(())
314}