connection_checker/
main.rs

1#![warn(clippy::missing_docs_in_private_items)]
2//! # connection-checker
3//! Use methods to test connections to Tor: directly or by using
4//! pluggable transports snowflake, obfs4, and meek
5//!
6//! ### Intro
7//! This project aims to illustrate how to make connections to Tor using
8//! different methods, and uses those to create a tool that users can run
9//! to see if they can connect to the Tor network in any way from their own
10//! networks.
11//!
12//! For more info on pluggable transports, you can refer to
13//! [these docs](https://tb-manual.torproject.org/circumvention/)
14//!
15//! ### Usage
16//! Run the program:
17//! `cargo run`
18//!
19//! By default only a direct Tor connection is tested. In order to test
20//! other pluggable transports, we can pass the path to the PT binary to the
21//! program.
22//!
23//! For example, if you wished to test an obfs4 and snowflake connection,
24//! pass `--snowflake-path snowflake-client --obfs4-client lyrebird`,
25//! where `lyrebird` is the path to the obfs4 pluggable transport binary
26//! and `snowflake-client` is the Snowflake counterpart
27//!
28//! You can also optionally specify a different host:port than the default `torproject.org:80`
29//! to be tested by passing the value using the `--connect-to` argument.
30//!
31//! For more information please refer to `cargo run -- --help`
32//!
33//! The program can test connections using snowflake, obfs4, and meek,
34//! and thus requires the pluggable transports which are to be tested are already installed.
35//! To install the pluggable transports, you can check your package manager
36//! or build "lyrebird", "meek" and "snowflake" from source, obtainable
37//! from the [corresponding Tor Project's GitLab repositories](https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/)
38//!
39//! ### Disclaimer
40//! The connection-checker is experimental, not for production use. It's
41//! intended for experimental purposes, providing insights into
42//! connection methods.
43use anyhow::{Error, Result};
44use arti_client::config::pt::TransportConfigBuilder;
45use arti_client::config::{BridgeConfigBuilder, CfgPath, Reconfigure};
46use arti_client::{TorClient, TorClientConfig};
47use clap::Parser;
48use tor_error::ErrorReport;
49use tor_proto::stream::ClientStreamCtrl as _;
50use tor_rtcompat::PreferredRuntime;
51use tracing::{error, info};
52
53/// Test connections to the Tor network via different methods
54#[derive(Parser)]
55#[command(author, version, about, long_about = None)]
56struct Opts {
57    /// Snowflake binary to use, implies Snowflake is to be tested
58    #[arg(long, required = false, default_value = None)]
59    snowflake_path: Option<String>,
60    /// obfs4 binary to use, implies obfs4 is to be tested
61    #[arg(long, required = false, default_value = None)]
62    obfs4_path: Option<String>,
63    /// meek binary to use, implies meek is to be tested
64    #[arg(long, required = false, default_value = None)]
65    meek_path: Option<String>,
66
67    /// Specify a custom host:port to connect to for testing purposes
68    #[arg(long, required = false, default_value = "torproject.org:80")]
69    connect_to: String,
70}
71
72/// Denotes the connection type
73enum ConnType {
74    /// Snowflake
75    Snowflake,
76    /// obfs4
77    Obfs4,
78    /// Meek
79    Meek,
80    /// direct
81    Direct,
82}
83
84/// Test bridge we will use for validating obfs4 connections
85const OBFS4_BRIDGE_LINE: &str = include_str!("../bridges/bridge_obfs4.txt");
86/// Test bridge we will use for validating snowflake connections
87const SNOWFLAKE_BRIDGE_LINE: &str = include_str!("../bridges/bridge_snowflake.txt");
88/// Test bridge we will use for validating meek connections
89const MEEK_BRIDGE_LINE: &str = include_str!("../bridges/bridge_meek.txt");
90
91/// Connect to a sample host and print the path it used to get there.
92/// Note that due to the way Tor works, other requests may use a different
93/// path than the one we obtain using this function, so this is mostly
94/// for demonstration purposes.
95async fn build_circuit(tor_client: &TorClient<PreferredRuntime>, remote: &str) -> Result<()> {
96    info!("Attempting to build circuit...");
97    match tor_client.connect(remote).await {
98        Ok(stream) => {
99            let circuit = stream
100                .client_stream_ctrl()
101                .ok_or_else(|| Error::msg("failed to get client stream ctrl?!"))?
102                .circuit()
103                .ok_or_else(|| Error::msg("failed to get client circuit?!"))?;
104            let circ = circuit.path_ref()?;
105            for node in circ.iter() {
106                println!("Node: {}", node);
107            }
108            Ok(())
109        }
110        Err(e) => {
111            eprintln!("{}", e.report());
112            Err(e.into())
113        }
114    }
115}
116
117/// Attempts to build a pluggable transport-enabled [TorClientConfig] using
118/// the supplied data
119fn build_pt_config(
120    bridge_line: &str,
121    protocol_name: &str,
122    client_path: &str,
123) -> Result<TorClientConfig> {
124    let mut builder = TorClientConfig::builder();
125    let bridge: BridgeConfigBuilder = bridge_line.parse()?;
126    builder.bridges().bridges().push(bridge);
127    let mut transport = TransportConfigBuilder::default();
128    transport
129        .protocols(vec![protocol_name.parse()?])
130        .path(CfgPath::new(client_path.into()))
131        .run_on_startup(true);
132    builder.bridges().transports().push(transport);
133    Ok(builder.build()?)
134}
135
136/// Reconfigure a given [TorClient] and try getting the circuit
137async fn test_connection_via_config(
138    tor_client: &TorClient<PreferredRuntime>,
139    config: TorClientConfig,
140    msg: &str,
141    remote_url: &str,
142) {
143    let isolated = tor_client.isolated_client();
144    println!("Testing {}...", msg);
145    match isolated.reconfigure(&config, Reconfigure::WarnOnFailures) {
146        Ok(_) => match build_circuit(&isolated, remote_url).await {
147            Ok(_) => println!("{} successful!", msg),
148            Err(_) => println!("{} FAILED", msg),
149        },
150        Err(e) => {
151            error!("{}", e.report());
152            println!("{} FAILED", msg);
153        }
154    }
155}
156
157/// Main function ends up running most of the tests one by one
158#[tokio::main]
159async fn main() -> Result<()> {
160    tracing_subscriber::fmt::init();
161
162    let opts = Opts::parse();
163    let initialconfig = TorClientConfig::default();
164    let tor_client = TorClient::create_bootstrapped(initialconfig).await?;
165    let mut tests = Vec::with_capacity(4);
166    tests.push((ConnType::Direct, None));
167    if let Some(path) = opts.snowflake_path {
168        tests.push((ConnType::Snowflake, Some(path)));
169    }
170    if let Some(path) = opts.obfs4_path {
171        tests.push((ConnType::Obfs4, Some(path)));
172    }
173    if let Some(path) = opts.meek_path {
174        tests.push((ConnType::Meek, Some(path)));
175    }
176    for (connection_type, connection_bin_shared) in tests.iter() {
177        // This will only go to the "or" condition if we have a direct connection
178        // and that code doesn't use this variable anyway
179        let connection_bin = connection_bin_shared.to_owned().unwrap_or(String::new());
180        let (msg, config) = match connection_type {
181            ConnType::Obfs4 => {
182                let msg = "obfs4 Tor connection";
183                (
184                    msg,
185                    build_pt_config(OBFS4_BRIDGE_LINE, "obfs4", &connection_bin)?,
186                )
187            }
188            ConnType::Snowflake => {
189                let msg = "Snowflake Tor connection";
190                (
191                    msg,
192                    build_pt_config(SNOWFLAKE_BRIDGE_LINE, "snowflake", &connection_bin)?,
193                )
194            }
195            ConnType::Meek => {
196                let msg = "Meek Tor connection";
197                (
198                    msg,
199                    build_pt_config(MEEK_BRIDGE_LINE, "meek", &connection_bin)?,
200                )
201            }
202            ConnType::Direct => {
203                let msg = "direct Tor connection";
204                (msg, TorClientConfig::default())
205            }
206        };
207        test_connection_via_config(&tor_client, config, msg, &opts.connect_to).await;
208    }
209    Ok(())
210}