1use std::sync::Arc;
2use std::time::Duration;
34use anyhow::Result;
5use futures::StreamExt;
6use hyper::body::Incoming;
7use hyper::server::conn::http1;
8use hyper::service::service_fn;
9use hyper::{Request, Response, StatusCode};
10use hyper_util::rt::TokioIo;
11use tokio_util::sync::CancellationToken;
1213use arti_client::{TorClient, TorClientConfig};
14use safelog::{DisplayRedacted, sensitive};
15use tor_cell::relaycell::msg::Connected;
16use tor_hsservice::StreamRequest;
17use tor_hsservice::config::OnionServiceConfigBuilder;
18use tor_proto::stream::IncomingStreamRequest;
1920struct WebHandler {
21 shutdown: CancellationToken,
22}
2324impl WebHandler {
25async fn serve(&self, request: Request<Incoming>) -> Result<Response<String>> {
26println!("[+] Incoming request: {:?}", request);
2728let path = request.uri().path();
2930// Path to shutdown the service.
31 // TODO: Unauthenticated management. This route is accessible by anyone, and exists solely
32 // to demonstrate how to safely shutdown further incoming requests. You should probably
33 // move this elsewhere to ensure proper checks are in place!
34if path == "/shutdown" {
35self.shutdown.cancel();
36 }
3738// Default path.
39Ok(Response::builder().status(StatusCode::OK).body(format!(
40"You have succesfully reached your onion service served by Arti and hyper.\n\nYour request:\n\n{} {}",
41 request.method(),
42 path
43 ))?)
44 }
45}
4647#[tokio::main]
48async fn main() -> Result<()> {
49// Make sure you read doc/OnionService.md to extract your Onion service hostname
5051 // Arti uses the `tracing` crate for logging. Install a handler for this, to print Arti's logs.
52 // (You'll need to set RUST_LOG=info as an environment variable to actually see much; also try
53 // =debug for more detailed logging.)
54tracing_subscriber::fmt::init();
5556// Initialize web server data, if you need to
57let handler = Arc::new(WebHandler {
58 shutdown: CancellationToken::new(),
59 });
6061// The client config includes things like where to store persistent Tor network state.
62 // The defaults provided are the same as the Arti standalone application, and save data
63 // to a conventional place depending on operating system (for example, ~/.local/share/arti
64 // on Linux platforms)
65let config = TorClientConfig::default();
6667// We now let the Arti client start and bootstrap a connection to the network.
68 // (This takes a while to gather the necessary consensus state, etc.)
69let client = TorClient::create_bootstrapped(config).await.unwrap();
7071// Launch onion service.
72eprintln!("[+] Launching onion service...");
73let svc_cfg = OnionServiceConfigBuilder::default()
74 .nickname("allium-ampeloprasum".parse().unwrap())
75 .build()
76 .unwrap();
77let (service, request_stream) = client.launch_onion_service(svc_cfg)?;
78eprintln!(
79"[+] Onion address: {}",
80 service
81 .onion_address()
82 .expect("Onion address not found")
83 .display_unredacted()
84 );
8586// `is_fully_reachable` might remain false even if the service is reachable in practice;
87 // after a timeout, we stop waiting for that and try anyway.
88let timeout_seconds = 60;
89eprintln!(
90"[+] Waiting for onion service to be reachable. Please wait {} seconds...\r",
91 timeout_seconds
92 );
93let status_stream = service.status_events();
94let mut binding =
95 status_stream.filter(|status| futures::future::ready(status.state().is_fully_reachable()));
96match tokio::time::timeout(Duration::from_secs(timeout_seconds), binding.next()).await {
97Ok(Some(_)) => eprintln!("[+] Onion service is fully reachable."),
98Ok(None) => eprintln!("[-] Status stream ended unexpectedly."),
99Err(_) => eprintln!(
100"[-] Timeout waiting for service to become reachable. You can still attempt to visit the service."
101),
102 }
103104let stream_requests = tor_hsservice::handle_rend_requests(request_stream)
105 .take_until(handler.shutdown.cancelled());
106tokio::pin!(stream_requests);
107108while let Some(stream_request) = stream_requests.next().await {
109// Incoming connection.
110let handler = handler.clone();
111112 tokio::spawn(async move {
113let request = stream_request.request().clone();
114let result = handle_stream_request(stream_request, handler).await;
115116match result {
117Ok(()) => {}
118Err(err) => {
119eprintln!(
120"[-] Error serving connection {:?}: {}",
121 sensitive(request),
122 err
123 );
124 }
125 }
126 });
127 }
128129 drop(service);
130eprintln!("[+] Onion service exited cleanly.");
131132Ok(())
133}
134135async fn handle_stream_request(
136 stream_request: StreamRequest,
137 handler: Arc<WebHandler>,
138) -> Result<()> {
139match stream_request.request() {
140 IncomingStreamRequest::Begin(begin) if begin.port() == 80 => {
141let onion_service_stream = stream_request.accept(Connected::new_empty()).await?;
142let io = TokioIo::new(onion_service_stream);
143144 http1::Builder::new()
145 .serve_connection(io, service_fn(|request| handler.serve(request)))
146 .await?;
147 }
148_ => {
149 stream_request.shutdown_circuit()?;
150 }
151 }
152153Ok(())
154}