summaryrefslogtreecommitdiff
path: root/examples/gsoc2023/pt-proxy/src/main.rs
blob: bdf342e097a72a897ea324ff3813b50022510ed9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
use anyhow::Result;
use clap::{Parser, Subcommand};
use fast_socks5::client::{Config, Socks5Stream};
use fast_socks5::server::{AcceptAuthentication, Socks5Server};
use std::str::FromStr;
use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::oneshot;
use tokio::time::Duration;
use tokio_stream::StreamExt;
use tor_chanmgr::transport::proxied::{Protocol, settings_to_protocol};
use tor_linkspec::PtTransportName;
use tor_ptmgr::ipc::{
    PluggableClientTransport, PluggableServerTransport, PluggableTransport, PtClientParameters,
    PtCommonParameters, PtServerParameters,
};
use tor_rtcompat::PreferredRuntime;
use tor_socksproto::{SocksAuth, SocksVersion};

/// The location where the obfs4 server will store its state
const SERVER_STATE_LOCATION: &str = "/tmp/arti-pt";
/// The location where the obfs4 client will store its state
const CLIENT_STATE_LOCATION: &str = "/tmp/arti-pt-client";

/// Error defined to denote a failure to get the bridge line
#[derive(Debug, thiserror::Error)]
#[error("Error while obtaining bridge line data")]
struct BridgeLineParseError;

/// Specify which mode we wish to use the program in
#[derive(Subcommand)]
enum Command {
    /// Enable client mode
    Client {
        /// The local port that programs will point traffic to
        #[arg(short, long, default_value = "9050")]
        client_port: u16,
        /// Remote IP that connections should go to, this is an
        /// obfs4 server
        #[arg(required = true)]
        remote_obfs4_ip: String,
        /// Remote port that connections should go to, this is an
        /// obfs4 server
        #[arg(required = true)]
        remote_obfs4_port: u16,
        /// Info about the server process that is required to connect
        /// successfully
        #[arg(required = true)]
        obfs4_auth_info: String,
    },
    /// Enable server mode
    Server {
        /// Address on which the obfs4 server should listen in for
        /// incoming connections
        #[arg(required = true)]
        listen_address: String,
        /// The local port the obfs4 server directs connections to
        ///
        /// Programs generally don't interact directly with it,
        /// so this doesn't need to be set
        #[arg(default_value = "4000")]
        final_socks5_port: u16,
    },
}

/// Tunnel SOCKS5 traffic through obfs4 connections
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
    #[command(subcommand)]
    command: Command,
    /// Binary to use to launch obfs4 client
    #[arg(required = true)]
    obfs4_path: String,
}

/// Store the data we need to connect to the obfs4 client
///
/// The obfs4 client in turn connects to the obfs4 server
#[derive(Clone)]
struct ForwardingCreds {
    username: String,
    password: String,
    forward_endpoint: String,
    obfs4_server_ip: String,
    obfs4_server_port: u16,
}

/// Create the config to launch an obfs4 server process
fn build_server_config(
    protocol: &str,
    bind_addr: &str,
    forwarding_server_addr: &str,
) -> Result<(PtCommonParameters, PtServerParameters)> {
    let bindaddr_formatted = format!("{}-{}", &protocol, bind_addr);
    let orport = forwarding_server_addr.to_string();
    Ok((
        PtCommonParameters::builder()
            .state_location(SERVER_STATE_LOCATION.into())
            .timeout(Some(Duration::from_secs(1)))
            .build()?,
        PtServerParameters::builder()
            .transports(vec![protocol.parse()?])
            .server_bindaddr(bindaddr_formatted)
            .server_orport(Some(orport))
            .build()?,
    ))
}

/// Read cert info and relay it to the user
fn read_cert_info() -> Result<String> {
    let file_path = format!("{SERVER_STATE_LOCATION}/obfs4_bridgeline.txt");
    match std::fs::read_to_string(file_path) {
        Ok(contents) => {
            let line = contents
                .lines()
                .find(|line| line.contains("Bridge obfs4"))
                .ok_or(BridgeLineParseError)?;
            let cert = line
                .split_whitespace()
                .find(|part| part.starts_with("cert="))
                .ok_or(BridgeLineParseError)?;
            let iat = line
                .split_whitespace()
                .find(|part| part.starts_with("iat-mode="))
                .ok_or(BridgeLineParseError)?;
            let complete_config = format!("{cert};{iat}");
            Ok(complete_config)
        }
        Err(e) => Err(e.into()),
    }
}

/// Create the config to launch an obfs4 client process
fn build_client_config(protocol: &str) -> Result<(PtCommonParameters, PtClientParameters)> {
    Ok((
        PtCommonParameters::builder()
            .state_location(CLIENT_STATE_LOCATION.into())
            .timeout(Some(Duration::from_secs(1)))
            .build()?,
        PtClientParameters::builder()
            .transports(vec![protocol.parse()?])
            .build()?,
    ))
}

/// Create a SOCKS5 connection to the obfs4 client
async fn connect_to_obfs4_client(
    forward_creds: ForwardingCreds,
) -> Result<Socks5Stream<TcpStream>> {
    let config = Config::default();
    Ok(Socks5Stream::connect_with_password(
        forward_creds.forward_endpoint,
        forward_creds.obfs4_server_ip,
        forward_creds.obfs4_server_port,
        forward_creds.username,
        forward_creds.password,
        config,
    )
    .await?)
}

/// Launch obfs4 client process
async fn launch_obfs4_client_process(
    obfs4_path: String,
) -> anyhow::Result<PluggableClientTransport> {
    let (common_params, client_params) = build_client_config("obfs4")?;
    let mut client_pt = PluggableClientTransport::new(
        obfs4_path.into(),
        vec![
            "-enableLogging".to_string(),
            "-logLevel".to_string(),
            "DEBUG".to_string(),
            "-unsafeLogging".to_string(),
        ],
        common_params,
        client_params,
    );
    client_pt.launch(PreferredRuntime::current()?).await?;
    Ok(client_pt)
}

/// Launch obfs4 server process
async fn launch_obfs4_server_process(
    obfs4_path: String,
    listen_address: String,
    final_socks5_endpoint: String,
) -> anyhow::Result<PluggableServerTransport> {
    let (common_params, server_params) =
        build_server_config("obfs4", &listen_address, &final_socks5_endpoint)?;

    let mut server_pt = PluggableServerTransport::new(
        obfs4_path.into(),
        vec![
            "-enableLogging".to_string(),
            "-logLevel".to_string(),
            "DEBUG".to_string(),
            "-unsafeLogging".to_string(),
        ],
        common_params,
        server_params,
    );
    server_pt.launch(PreferredRuntime::current()?).await?;
    Ok(server_pt)
}

/// Launch the dumb TCP pipe, whose only job is to abstract away the obfs4 client
/// and its complicated setup, and just forward bytes between the obfs4 client
/// and the client
async fn run_forwarding_server(endpoint: &str, forward_creds: ForwardingCreds) -> Result<()> {
    let listener = TcpListener::bind(endpoint).await?;
    while let Ok((mut client, _)) = listener.accept().await {
        let forward_creds_clone = forward_creds.clone();
        match connect_to_obfs4_client(forward_creds_clone).await {
            Ok(mut relay_stream) => {
                if let Err(e) = tokio::io::copy_bidirectional(&mut client, &mut relay_stream).await
                {
                    eprintln!("{e:#?}");
                }
            }
            Err(e) => {
                eprintln!("Couldn't connect to obfs4 client: \"{e}\"");
                // Report "No authentication method was acceptable" to user
                // For more info refer to RFC 1928
                client.write_all(&[5, 0xFF]).await.unwrap();
            }
        }
    }
    Ok(())
}

/// Run the final hop of the connection, which finally makes the actual
/// network request to the intended host and relays it back
async fn run_socks5_server(endpoint: &str) -> Result<oneshot::Receiver<bool>> {
    let listener = Socks5Server::<AcceptAuthentication>::bind(endpoint).await?;
    let (tx, rx) = oneshot::channel::<bool>();
    tokio::spawn(async move {
        while let Some(Ok(socks_socket)) = listener.incoming().next().await {
            tokio::spawn(async move {
                if let Err(e) = socks_socket.upgrade_to_socks5().await {
                    eprintln!("{e:#?}");
                }
            });
        }
        tx.send(true).unwrap()
    });
    Ok(rx)
}

/// Main function, ties everything together and parses arguments etc.
#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt::init();
    let args = Args::parse();
    let obfs4_path = args.obfs4_path;
    match args.command {
        Command::Client {
            client_port,
            remote_obfs4_ip,
            remote_obfs4_port,
            obfs4_auth_info: obfs4_server_conf,
        } => {
            let entry_addr = format!("127.0.0.1:{client_port}");

            let client_pt = launch_obfs4_client_process(obfs4_path).await?;
            let client_endpoint = client_pt
                .transport_methods()
                .get(&PtTransportName::from_str("obfs4")?)
                .unwrap()
                .endpoint()
                .to_string();

            let settings = settings_to_protocol(SocksVersion::V5, obfs4_server_conf)?;
            match settings {
                Protocol::Socks(_, auth) => match auth {
                    SocksAuth::Username(raw_username, raw_password) => {
                        let username = String::from_utf8(raw_username)?;
                        let password = match raw_password.is_empty() {
                            true => String::from("\0"),
                            false => String::from_utf8(raw_password)?,
                        };
                        let creds = ForwardingCreds {
                            username,
                            password,
                            forward_endpoint: client_endpoint,
                            obfs4_server_ip: remote_obfs4_ip,
                            obfs4_server_port: remote_obfs4_port,
                        };
                        println!();
                        println!("Listening on: {entry_addr}");
                        run_forwarding_server(&entry_addr, creds).await?;
                    }
                    _ => eprintln!("Unable to get credentials for obfs4 client process!"),
                },
                _ => eprintln!("Unexpected protocol"),
            }
        }
        Command::Server {
            listen_address,
            final_socks5_port,
        } => {
            let final_socks5_endpoint = format!("127.0.0.1:{final_socks5_port}");
            let exit_rx = run_socks5_server(&final_socks5_endpoint).await?;
            println!();
            println!("Listening on: {listen_address}");
            launch_obfs4_server_process(obfs4_path, listen_address, final_socks5_endpoint).await?;
            let auth_info = read_cert_info().unwrap();
            println!();
            println!("Authentication info is: {auth_info}");
            exit_rx.await.unwrap();
        }
    }
    Ok(())
}