Unclear how to configure `arti-client` to use obfs4 bridge
I apologize if this is already explained somewhere or I'm missing some information regarding obfs4 support in arti but I'm having issues configuring arti to use my bridge and I could use some pointers regarding where the issue in my implementation/understanding lies.
Specifically I get this runtime error when trying to configure arti-client to use a bridge with obfs4 as a pluggable-transport:
2025-01-18T13:19:47.942637Z DEBUG Using tor bridge bridge_line=obfs4 194.164.170.39:19223 CB40FE9E4DC6780F6FBCD67A2566B3537095585D cert=z6iYiqrjLsgW6gSy3P8wp51bYqqcsiclOqNu9woiJyVExUCHybURkiGZ/bkd85T26SShUg iat-mode=0
thread 'tokio-runtime-worker' panicked at /Users/mohan/Development/core/swap/src/common/tor.rs:38:10:
We initialized the Tor client with all required attributes: Inconsistent { fields: ["bridges.bridges.bridges", "bridges.bridges.transports"], problem: "Bridges configured, but all bridges unusable due to lack of corresponding pluggable transport in `[bridges.transports]`" }
The bridge is requested from the moat API. I have digged a bit on the code and I believe I need to construct a TransportConfig and append it to builder.bridges().transports(). However looking into the code it seems I need to bundle a obfs4-proxy binary which will then be launched by arti? Does this mean there is no native support for obsf4 yet? We'd like to support mobile later on and launching external processes is probably a no-go on iOS.
I have these cargo features enabled:
arti-client = { version = "^0.25.0", features = [ "static-sqlite", "tokio", "rustls", "pt-client", "bridge-client" ], default-features = false }
I'm initializing the client like this (relevant PR in our repo is here)
pub async fn init_tor_client(
data_dir: &Path,
bridges: Vec<String>,
) -> Result<Arc<TorClient<TokioRustlsRuntime>>, Error> {
// We store the Tor state in the data directory
let data_dir = data_dir.join("tor");
let state_dir = data_dir.join("state");
let cache_dir = data_dir.join("cache");
// The client configuration describes how to connect to the Tor network,
// and what directories to use for storing persistent state.
let mut builder = TorClientConfigBuilder::from_directories(state_dir, cache_dir);
// Append all known bridges to the configuration
for bridge_line in bridges {
match bridge_line.parse::<BridgeConfigBuilder>() {
Ok(bridge) => {
tracing::debug!(%bridge_line, "Using tor bridge");
builder.bridges().bridges().push(bridge);
}
Err(err) => {
tracing::error!(%err, %bridge_line, "Could not use tor bridge because we could not parse it")
}
}
}
let config = builder
.build()
.expect("We initialized the Tor client with all required attributes");
// Start the Arti client, and let it bootstrap a connection to the Tor network.
// (This takes a while to gather the necessary directory information.
// It uses cached information when possible.)
let runtime = TokioRustlsRuntime::current().expect("We are always running with tokio");
tracing::debug!("Bootstrapping Tor client");
let tor_client = TorClient::with_runtime(runtime)
.config(config)
.create_bootstrapped()
.await?;
Ok(Arc::new(tor_client))
}
I'd be very thankful for some help / clarifications :)