summaryrefslogtreecommitdiff
path: root/examples/hyper-examples/src/bin/hyper-http-hs-example.rs
blob: b788b43e039013a8dbf2f9e8d3b1b01e1fdafb6d (plain)
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
use std::sync::Arc;
use std::time::Duration;

use anyhow::Result;
use futures::StreamExt;
use hyper::body::Incoming;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use tokio_util::sync::CancellationToken;

use arti_client::{TorClient, TorClientConfig};
use safelog::{DisplayRedacted, sensitive};
use tor_cell::relaycell::msg::Connected;
use tor_hsservice::StreamRequest;
use tor_hsservice::config::OnionServiceConfigBuilder;
use tor_proto::stream::IncomingStreamRequest;

struct WebHandler {
    shutdown: CancellationToken,
}

impl WebHandler {
    async fn serve(&self, request: Request<Incoming>) -> Result<Response<String>> {
        println!("[+] Incoming request: {:?}", request);

        let path = request.uri().path();

        // Path to shutdown the service.
        // TODO: Unauthenticated management. This route is accessible by anyone, and exists solely
        //  to demonstrate how to safely shutdown further incoming requests. You should probably
        //  move this elsewhere to ensure proper checks are in place!
        if path == "/shutdown" {
            self.shutdown.cancel();
        }

        // Default path.
        Ok(Response::builder().status(StatusCode::OK).body(format!(
            "You have successfully reached your onion service served by Arti and hyper.\n\nYour request:\n\n{} {}",
            request.method(),
            path
        ))?)
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    // Make sure you read doc/OnionService.md to extract your Onion service hostname

    // Arti uses the `tracing` crate for logging. Install a handler for this, to print Arti's logs.
    // (You'll need to set RUST_LOG=info as an environment variable to actually see much; also try
    // =debug for more detailed logging.)
    tracing_subscriber::fmt::init();

    // Initialize web server data, if you need to
    let handler = Arc::new(WebHandler {
        shutdown: CancellationToken::new(),
    });

    // The client config includes things like where to store persistent Tor network state.
    // The defaults provided are the same as the Arti standalone application, and save data
    // to a conventional place depending on operating system (for example, ~/.local/share/arti
    // on Linux platforms)
    let config = TorClientConfig::default();

    // We now let the Arti client start and bootstrap a connection to the network.
    // (This takes a while to gather the necessary consensus state, etc.)
    let client = TorClient::create_bootstrapped(config).await.unwrap();

    // Launch onion service.
    eprintln!("[+] Launching onion service...");
    let svc_cfg = OnionServiceConfigBuilder::default()
        .nickname("allium-ampeloprasum".parse().unwrap())
        .build()
        .unwrap();
    let (service, request_stream) = match client.launch_onion_service(svc_cfg)? {
        Some(running_service) => running_service,
        None => {
            eprintln!("[+] Onion service not launched due to being disabled in config.");
            return Ok(());
        }
    };
    eprintln!(
        "[+] Onion address: {}",
        service
            .onion_address()
            .expect("Onion address not found")
            .display_unredacted()
    );

    // `is_fully_reachable` might remain false even if the service is reachable in practice;
    // after a timeout, we stop waiting for that and try anyway.
    let timeout_seconds = 60;
    eprintln!(
        "[+] Waiting for onion service to be reachable. Please wait {} seconds...\r",
        timeout_seconds
    );
    let status_stream = service.status_events();
    let mut binding =
        status_stream.filter(|status| futures::future::ready(status.state().is_fully_reachable()));
    match tokio::time::timeout(Duration::from_secs(timeout_seconds), binding.next()).await {
        Ok(Some(_)) => eprintln!("[+] Onion service is fully reachable."),
        Ok(None) => eprintln!("[-] Status stream ended unexpectedly."),
        Err(_) => eprintln!(
            "[-] Timeout waiting for service to become reachable. You can still attempt to visit the service."
        ),
    }

    let stream_requests = tor_hsservice::handle_rend_requests(request_stream)
        .take_until(handler.shutdown.cancelled());
    tokio::pin!(stream_requests);

    while let Some(stream_request) = stream_requests.next().await {
        // Incoming connection.
        let handler = handler.clone();

        tokio::spawn(async move {
            let request = stream_request.request().clone();
            let result = handle_stream_request(stream_request, handler).await;

            match result {
                Ok(()) => {}
                Err(err) => {
                    eprintln!(
                        "[-] Error serving connection {:?}: {}",
                        sensitive(request),
                        err
                    );
                }
            }
        });
    }

    drop(service);
    eprintln!("[+] Onion service exited cleanly.");

    Ok(())
}

async fn handle_stream_request(
    stream_request: StreamRequest,
    handler: Arc<WebHandler>,
) -> 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);

            http1::Builder::new()
                .serve_connection(io, service_fn(|request| handler.serve(request)))
                .await?;
        }
        _ => {
            stream_request.shutdown_circuit()?;
        }
    }

    Ok(())
}