hyper_http_hs_example/
hyper-http-hs-example.rs

1use std::sync::Arc;
2use std::time::Duration;
3
4use 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;
12
13use 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;
19
20struct WebHandler {
21    shutdown: CancellationToken,
22}
23
24impl WebHandler {
25    async fn serve(&self, request: Request<Incoming>) -> Result<Response<String>> {
26        println!("[+] Incoming request: {:?}", request);
27
28        let path = request.uri().path();
29
30        // 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!
34        if path == "/shutdown" {
35            self.shutdown.cancel();
36        }
37
38        // Default path.
39        Ok(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}
46
47#[tokio::main]
48async fn main() -> Result<()> {
49    // Make sure you read doc/OnionService.md to extract your Onion service hostname
50
51    // 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.)
54    tracing_subscriber::fmt::init();
55
56    // Initialize web server data, if you need to
57    let handler = Arc::new(WebHandler {
58        shutdown: CancellationToken::new(),
59    });
60
61    // 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)
65    let config = TorClientConfig::default();
66
67    // 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.)
69    let client = TorClient::create_bootstrapped(config).await.unwrap();
70
71    // Launch onion service.
72    eprintln!("[+] Launching onion service...");
73    let svc_cfg = OnionServiceConfigBuilder::default()
74        .nickname("allium-ampeloprasum".parse().unwrap())
75        .build()
76        .unwrap();
77    let (service, request_stream) = client.launch_onion_service(svc_cfg)?;
78    eprintln!(
79        "[+] Onion address: {}",
80        service
81            .onion_address()
82            .expect("Onion address not found")
83            .display_unredacted()
84    );
85
86    // `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.
88    let timeout_seconds = 60;
89    eprintln!(
90        "[+] Waiting for onion service to be reachable. Please wait {} seconds...\r",
91        timeout_seconds
92    );
93    let status_stream = service.status_events();
94    let mut binding =
95        status_stream.filter(|status| futures::future::ready(status.state().is_fully_reachable()));
96    match tokio::time::timeout(Duration::from_secs(timeout_seconds), binding.next()).await {
97        Ok(Some(_)) => eprintln!("[+] Onion service is fully reachable."),
98        Ok(None) => eprintln!("[-] Status stream ended unexpectedly."),
99        Err(_) => eprintln!(
100            "[-] Timeout waiting for service to become reachable. You can still attempt to visit the service."
101        ),
102    }
103
104    let stream_requests = tor_hsservice::handle_rend_requests(request_stream)
105        .take_until(handler.shutdown.cancelled());
106    tokio::pin!(stream_requests);
107
108    while let Some(stream_request) = stream_requests.next().await {
109        // Incoming connection.
110        let handler = handler.clone();
111
112        tokio::spawn(async move {
113            let request = stream_request.request().clone();
114            let result = handle_stream_request(stream_request, handler).await;
115
116            match result {
117                Ok(()) => {}
118                Err(err) => {
119                    eprintln!(
120                        "[-] Error serving connection {:?}: {}",
121                        sensitive(request),
122                        err
123                    );
124                }
125            }
126        });
127    }
128
129    drop(service);
130    eprintln!("[+] Onion service exited cleanly.");
131
132    Ok(())
133}
134
135async fn handle_stream_request(
136    stream_request: StreamRequest,
137    handler: Arc<WebHandler>,
138) -> Result<()> {
139    match stream_request.request() {
140        IncomingStreamRequest::Begin(begin) if begin.port() == 80 => {
141            let onion_service_stream = stream_request.accept(Connected::new_empty()).await?;
142            let io = TokioIo::new(onion_service_stream);
143
144            http1::Builder::new()
145                .serve_connection(io, service_fn(|request| handler.serve(request)))
146                .await?;
147        }
148        _ => {
149            stream_request.shutdown_circuit()?;
150        }
151    }
152
153    Ok(())
154}