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
|
#![warn(clippy::missing_docs_in_private_items)]
//! # connection-checker
//! Use methods to test connections to Tor: directly or by using
//! pluggable transports snowflake, obfs4, and meek
//!
//! ### Intro
//! This project aims to illustrate how to make connections to Tor using
//! different methods, and uses those to create a tool that users can run
//! to see if they can connect to the Tor network in any way from their own
//! networks.
//!
//! For more info on pluggable transports, you can refer to
//! [these docs](https://tb-manual.torproject.org/circumvention/)
//!
//! ### Usage
//! Run the program:
//! `cargo run`
//!
//! By default only a direct Tor connection is tested. In order to test
//! other pluggable transports, we can pass the path to the PT binary to the
//! program.
//!
//! For example, if you wished to test an obfs4 and snowflake connection,
//! pass `--snowflake-path snowflake-client --obfs4-client lyrebird`,
//! where `lyrebird` is the path to the obfs4 pluggable transport binary
//! and `snowflake-client` is the Snowflake counterpart
//!
//! You can also optionally specify a different host:port than the default `torproject.org:80`
//! to be tested by passing the value using the `--connect-to` argument.
//!
//! For more information please refer to `cargo run -- --help`
//!
//! The program can test connections using snowflake, obfs4, and meek,
//! and thus requires the pluggable transports which are to be tested are already installed.
//! To install the pluggable transports, you can check your package manager
//! or build "lyrebird", "meek" and "snowflake" from source, obtainable
//! from the [corresponding Tor Project's GitLab repositories](https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/)
//!
//! ### Disclaimer
//! The connection-checker is experimental, not for production use. It's
//! intended for experimental purposes, providing insights into
//! connection methods.
use anyhow::Result;
use arti_client::config::pt::TransportConfigBuilder;
use arti_client::config::{BridgeConfigBuilder, CfgPath, Reconfigure};
use arti_client::{TorClient, TorClientConfig};
use clap::Parser;
use tor_error::ErrorReport;
use tor_rtcompat::PreferredRuntime;
use tracing::{error, info};
/// Test connections to the Tor network via different methods
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Opts {
/// Snowflake binary to use, implies Snowflake is to be tested
#[arg(long, required = false, default_value = None)]
snowflake_path: Option<String>,
/// obfs4 binary to use, implies obfs4 is to be tested
#[arg(long, required = false, default_value = None)]
obfs4_path: Option<String>,
/// meek binary to use, implies meek is to be tested
#[arg(long, required = false, default_value = None)]
meek_path: Option<String>,
/// Specify a custom host:port to connect to for testing purposes
#[arg(long, required = false, default_value = "torproject.org:80")]
connect_to: String,
}
/// Denotes the connection type
enum ConnType {
/// Snowflake
Snowflake,
/// obfs4
Obfs4,
/// Meek
Meek,
/// direct
Direct,
}
/// Test bridge we will use for validating obfs4 connections
const OBFS4_BRIDGE_LINE: &str = include_str!("../bridges/bridge_obfs4.txt");
/// Test bridge we will use for validating snowflake connections
const SNOWFLAKE_BRIDGE_LINE: &str = include_str!("../bridges/bridge_snowflake.txt");
/// Test bridge we will use for validating meek connections
const MEEK_BRIDGE_LINE: &str = include_str!("../bridges/bridge_meek.txt");
/// Connect to a sample host and print the path it used to get there.
/// Note that due to the way Tor works, other requests may use a different
/// path than the one we obtain using this function, so this is mostly
/// for demonstration purposes.
async fn build_circuit(tor_client: &TorClient<PreferredRuntime>, remote: &str) -> bool {
info!("Attempting to build circuit...");
match tor_client.connect(remote).await {
Ok(stream) => {
let circ = stream.circuit().path_ref();
for node in circ.iter() {
println!("Node: {}", node);
}
true
}
Err(e) => {
eprintln!("{}", e.report());
false
}
}
}
/// Attempts to build a pluggable transport-enabled [TorClientConfig] using
/// the supplied data
fn build_pt_config(
bridge_line: &str,
protocol_name: &str,
client_path: &str,
) -> Result<TorClientConfig> {
let mut builder = TorClientConfig::builder();
let bridge: BridgeConfigBuilder = bridge_line.parse()?;
builder.bridges().bridges().push(bridge);
let mut transport = TransportConfigBuilder::default();
transport
.protocols(vec![protocol_name.parse()?])
.path(CfgPath::new(client_path.into()))
.run_on_startup(true);
builder.bridges().transports().push(transport);
Ok(builder.build()?)
}
/// Reconfigure a given [TorClient] and try getting the circuit
async fn test_connection_via_config(
tor_client: &TorClient<PreferredRuntime>,
config: TorClientConfig,
msg: &str,
remote_url: &str,
) {
let isolated = tor_client.isolated_client();
println!("Testing {}...", msg);
match isolated.reconfigure(&config, Reconfigure::WarnOnFailures) {
Ok(_) => match build_circuit(&isolated, remote_url).await {
true => println!("{} successful!", msg),
false => println!("{} FAILED", msg),
},
Err(e) => {
error!("{}", e.report());
println!("{} FAILED", msg);
}
}
}
/// Main function ends up running most of the tests one by one
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let opts = Opts::parse();
let initialconfig = TorClientConfig::default();
let tor_client = TorClient::create_bootstrapped(initialconfig).await?;
let mut tests = Vec::with_capacity(4);
tests.push((ConnType::Direct, None));
if let Some(path) = opts.snowflake_path {
tests.push((ConnType::Snowflake, Some(path)));
}
if let Some(path) = opts.obfs4_path {
tests.push((ConnType::Obfs4, Some(path)));
}
if let Some(path) = opts.meek_path {
tests.push((ConnType::Meek, Some(path)));
}
for (connection_type, connection_bin_shared) in tests.iter() {
// This will only go to the "or" condition if we have a direct connection
// and that code doesn't use this variable anyway
let connection_bin = connection_bin_shared.to_owned().unwrap_or(String::new());
let (msg, config) = match connection_type {
ConnType::Obfs4 => {
let msg = "obfs4 Tor connection";
(
msg,
build_pt_config(OBFS4_BRIDGE_LINE, "obfs4", &connection_bin)?,
)
}
ConnType::Snowflake => {
let msg = "Snowflake Tor connection";
(
msg,
build_pt_config(SNOWFLAKE_BRIDGE_LINE, "snowflake", &connection_bin)?,
)
}
ConnType::Meek => {
let msg = "Meek Tor connection";
(
msg,
build_pt_config(MEEK_BRIDGE_LINE, "meek", &connection_bin)?,
)
}
ConnType::Direct => {
let msg = "direct Tor connection";
(msg, TorClientConfig::default())
}
};
test_connection_via_config(&tor_client, config, msg, &opts.connect_to).await;
}
Ok(())
}
|