add hidden-service tests

This commit is contained in:
M Alghazwi 2025-10-28 16:22:00 +03:00
parent f8d9b989cf
commit 981304c3a5
No known key found for this signature in database
GPG Key ID: 646E567CAD7DB607
7 changed files with 147 additions and 9 deletions

View File

@ -13,4 +13,12 @@ arti-ureq = "0.35"
tor-rtcompat = "0.35"
tor-circmgr = "0.35"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util"] }
futures = "0.3.14"
futures = "0.3.14"
tor-hsservice = "0.35"
tor-proto = "0.35"
axum = { version = "0.8.1" }
tor-cell = "0.35"
safelog = { version = "0.6.0" }
hyper-util = { version = "0.1.1", features = ["tokio"] }
hyper = { version = "1", features = ["http1", "server"] }
tower = { version = "0.5.1", features = ["util", "tracing"] }

84
src/hidden_service.rs Normal file
View File

@ -0,0 +1,84 @@
use axum::Router;
use axum::routing::get;
use arti_client::TorClient;
use arti_ureq::ureq::http::Request;
use futures::StreamExt;
use hyper::body::Incoming;
use hyper_util::rt::{TokioExecutor, TokioIo};
use hyper_util::server;
use tor_hsservice::config::OnionServiceConfigBuilder;
use tor_proto::client::stream::IncomingStreamRequest;
use safelog::{DisplayRedacted as _, sensitive};
use tor_cell::relaycell::msg::Connected;
use tor_hsservice::StreamRequest;
use tower::Service;
/// returns the axum router for the hidden service
/// The router will serve the given string as a response to any GET request on the root path
pub fn get_service_router(msg: String) -> Router {
let router = Router::new().route("/", get(|| async { msg }));
router
}
/// launch the hidden service following the arti instructions.
/// The function uses both hyper and axum to serve.
/// this function launches the hidden service using the given client and router
/// then waits for requests.
pub async fn launch_hidden_service(tor_client: TorClient<tor_rtcompat::PreferredRuntime>, router: Router){
let svc_cfg = OnionServiceConfigBuilder::default()
.nickname("hello-world-8273649267405".parse().unwrap())
.build()
.unwrap();
let (service, request_stream) = tor_client.launch_onion_service(svc_cfg).unwrap();
println!("service is running on: {}", service.onion_address().unwrap().display_unredacted());
eprintln!("waiting for service to become fully reachable");
while let Some(status) = service.status_events().next().await {
if status.state().is_fully_reachable() {
break;
}
}
let stream_requests = tor_hsservice::handle_rend_requests(request_stream);
tokio::pin!(stream_requests);
eprintln!("ready to serve connections");
while let Some(stream_request) = stream_requests.next().await {
let router = router.clone();
tokio::spawn(async move {
let request = stream_request.request().clone();
if let Err(err) = handle_requests(stream_request, router).await {
eprintln!("error serving connection {:?}: {}", sensitive(request), err);
};
});
}
drop(service);
eprintln!("onion service exited cleanly");
}
/// this function serves the incoming requests to the hidden service using the given stream and router
async fn handle_requests(stream_request: StreamRequest, router: Router) -> anyhow::Result<()> {
match stream_request.request() {
IncomingStreamRequest::Begin(begin) if begin.port() == 80 => {
let onion_service_stream = stream_request.accept(Connected::new_empty()).await?;
let io = TokioIo::new(onion_service_stream);
let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| {
router.clone().call(request)
});
server::conn::auto::Builder::new(TokioExecutor::new())
.serve_connection(io, hyper_service)
.await
.map_err(|x| anyhow::anyhow!(x))?;
}
_ => {
stream_request.shutdown_circuit()?;
}
}
Ok(())
}

View File

@ -1,3 +1,4 @@
pub mod ureq_builder;
pub mod tests;
pub mod tor_client_builder;
pub mod tor_client_builder;
pub mod hidden_service;

View File

@ -1,5 +1,5 @@
#[cfg(test)]
mod tests {
pub mod tests {
use anyhow::Result;
use arti_client::TorClient;
use crate::ureq_builder::*;
@ -12,7 +12,7 @@ mod tests {
const TEST_URL_PATH_HTTP_PLAIN: &str = "/api/ip";
// We will use the following onion address with the tor client.
const TEST_ONION: &str = "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion";
const TEST_ONION_PATH: &str = "/";
pub const TEST_ONION_PATH: &str = "/";
/// Builds all components
fn build_ureq_agent() -> Result<Agent> {
let tls = choose_tls_provider();

View File

@ -1 +1,2 @@
pub mod get_request_tests;
pub mod get_request_tests;
pub mod test_hidden_service;

View File

@ -0,0 +1,44 @@
#[cfg(test)]
mod tests {
use crate::tests::get_request_tests::tests::TEST_ONION_PATH;
use anyhow::Result;
use arti_client::TorClient;
use crate::hidden_service::{get_service_router, launch_hidden_service};
use crate::tor_client_builder::{ fetch_with_client, make_tor_client_config};
// we will use this as the output of the hidden service
const HELLO_WORLD: &str = "Hello, world!";
/// Test: launch a `hello_world` hidden service
/// This test may take a little while to run
/// Warning: this will not terminate.
#[tokio::test(flavor = "multi_thread")]
async fn test_launch_hidden_service() -> Result<()> {
let config = make_tor_client_config();
let tor_client = TorClient::create_bootstrapped(config).await.unwrap();
let router = get_service_router(HELLO_WORLD.to_string());
launch_hidden_service(tor_client, router).await;
Ok(())
}
/// Test: send get request to onion address over Tor using the tor client.
/// again this is http and no tls
/// warning: this will print out an entire html page
async fn fetches_onion_url_over_tor_client(onion_url: &str) -> Result<()> {
let config = make_tor_client_config();
let tor_client = TorClient::builder()
.config(config)
.create_unbootstrapped()?;
let body = fetch_with_client(tor_client, onion_url, TEST_ONION_PATH).await?;
//sanity check:
assert!(!body.trim().is_empty(), "empty body from {}", onion_url);
Ok(())
}
}

View File

@ -5,10 +5,10 @@ use arti_ureq::ureq::tls::RootCerts;
/// Build a Connector from the Tor client and chosen TLS provider.
/// This is a ureq connector, and here we are giving it out already created tor client + tls
pub fn make_connector(
tor_client: arti_client::TorClient<arti_ureq::tor_rtcompat::PreferredRuntime>,
tor_client: arti_client::TorClient<tor_rtcompat::PreferredRuntime>,
tls_provider: ureq::tls::TlsProvider,
) -> Result<arti_ureq::Connector<arti_ureq::tor_rtcompat::PreferredRuntime>> {
let builder = arti_ureq::Connector::<arti_ureq::tor_rtcompat::PreferredRuntime>::builder()
) -> Result<arti_ureq::Connector<tor_rtcompat::PreferredRuntime>> {
let builder = arti_ureq::Connector::<tor_rtcompat::PreferredRuntime>::builder()
.context("Failed to create ConnectorBuilder")?
.tor_client(tor_client)
.tls_provider(tls_provider);
@ -18,7 +18,7 @@ pub fn make_connector(
/// Build a `ureq::Agent`
pub fn make_ureq_agent(
connector: arti_ureq::Connector<arti_ureq::tor_rtcompat::PreferredRuntime>,
connector: arti_ureq::Connector<tor_rtcompat::PreferredRuntime>,
tls_provider: ureq::tls::TlsProvider,
) -> Result<ureq::Agent> {
// Build ureq TLS config