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};
5253/// 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)]
59snowflake_path: Option<String>,
60/// obfs4 binary to use, implies obfs4 is to be tested
61#[arg(long, required = false, default_value = None)]
62obfs4_path: Option<String>,
63/// meek binary to use, implies meek is to be tested
64#[arg(long, required = false, default_value = None)]
65meek_path: Option<String>,
6667/// Specify a custom host:port to connect to for testing purposes
68#[arg(long, required = false, default_value = "torproject.org:80")]
69connect_to: String,
70}
7172/// Denotes the connection type
73enum ConnType {
74/// Snowflake
75Snowflake,
76/// obfs4
77Obfs4,
78/// Meek
79Meek,
80/// direct
81Direct,
82}
8384/// 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");
9091/// 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<()> {
96info!("Attempting to build circuit...");
97match tor_client.connect(remote).await {
98Ok(stream) => {
99let 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?!"))?;
104let circ = circuit.path_ref()?;
105for node in circ.iter() {
106println!("Node: {}", node);
107 }
108Ok(())
109 }
110Err(e) => {
111eprintln!("{}", e.report());
112Err(e.into())
113 }
114 }
115}
116117/// 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> {
124let mut builder = TorClientConfig::builder();
125let bridge: BridgeConfigBuilder = bridge_line.parse()?;
126 builder.bridges().bridges().push(bridge);
127let 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);
133Ok(builder.build()?)
134}
135136/// 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) {
143let isolated = tor_client.isolated_client();
144println!("Testing {}...", msg);
145match isolated.reconfigure(&config, Reconfigure::WarnOnFailures) {
146Ok(_) => match build_circuit(&isolated, remote_url).await {
147Ok(_) => println!("{} successful!", msg),
148Err(_) => println!("{} FAILED", msg),
149 },
150Err(e) => {
151error!("{}", e.report());
152println!("{} FAILED", msg);
153 }
154 }
155}
156157/// Main function ends up running most of the tests one by one
158#[tokio::main]
159async fn main() -> Result<()> {
160 tracing_subscriber::fmt::init();
161162let opts = Opts::parse();
163let initialconfig = TorClientConfig::default();
164let tor_client = TorClient::create_bootstrapped(initialconfig).await?;
165let mut tests = Vec::with_capacity(4);
166 tests.push((ConnType::Direct, None));
167if let Some(path) = opts.snowflake_path {
168 tests.push((ConnType::Snowflake, Some(path)));
169 }
170if let Some(path) = opts.obfs4_path {
171 tests.push((ConnType::Obfs4, Some(path)));
172 }
173if let Some(path) = opts.meek_path {
174 tests.push((ConnType::Meek, Some(path)));
175 }
176for (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
179let connection_bin = connection_bin_shared.to_owned().unwrap_or(String::new());
180let (msg, config) = match connection_type {
181 ConnType::Obfs4 => {
182let msg = "obfs4 Tor connection";
183 (
184 msg,
185 build_pt_config(OBFS4_BRIDGE_LINE, "obfs4", &connection_bin)?,
186 )
187 }
188 ConnType::Snowflake => {
189let msg = "Snowflake Tor connection";
190 (
191 msg,
192 build_pt_config(SNOWFLAKE_BRIDGE_LINE, "snowflake", &connection_bin)?,
193 )
194 }
195 ConnType::Meek => {
196let msg = "Meek Tor connection";
197 (
198 msg,
199 build_pt_config(MEEK_BRIDGE_LINE, "meek", &connection_bin)?,
200 )
201 }
202 ConnType::Direct => {
203let msg = "direct Tor connection";
204 (msg, TorClientConfig::default())
205 }
206 };
207 test_connection_via_config(&tor_client, config, msg, &opts.connect_to).await;
208 }
209Ok(())
210}