1use futures::io::BufReader;
4use futures::{
5 AsyncRead, AsyncWrite, Future, FutureExt as _, Stream, StreamExt as _, select_biased,
6};
7use itertools::iproduct;
8use oneshot_fused_workaround as oneshot;
9use safelog::sensitive as sv;
10use std::collections::HashMap;
11use std::io::Error as IoError;
12use std::sync::{Arc, Mutex};
13use strum::IntoEnumIterator;
14use tor_cell::relaycell::msg as relaymsg;
15use tor_error::{ErrorKind, HasKind, debug_report};
16use tor_hsservice::{HsNickname, RendRequest, StreamRequest};
17use tor_log_ratelim::log_ratelim;
18use tor_proto::client::stream::DataStream;
19use tor_proto::stream::IncomingStreamRequest;
20use tor_rtcompat::{Runtime, SpawnExt as _};
21
22use crate::config::{
23 Encapsulation, ProxyAction, ProxyActionDiscriminants, ProxyConfig, TargetAddr,
24};
25
26#[derive(Debug)]
29pub struct OnionServiceReverseProxy {
30 state: Mutex<State>,
32}
33
34#[derive(Debug)]
36struct State {
37 config: ProxyConfig,
39 shutdown_tx: Option<oneshot::Sender<void::Void>>,
41 shutdown_rx: futures::future::Shared<oneshot::Receiver<void::Void>>,
43}
44
45#[derive(Clone, Debug, thiserror::Error)]
47#[non_exhaustive]
48pub enum HandleRequestsError {
49 #[error("Unable to spawn a task")]
51 Spawn(#[source] Arc<futures::task::SpawnError>),
52}
53
54impl HasKind for HandleRequestsError {
55 fn kind(&self) -> ErrorKind {
56 match self {
57 HandleRequestsError::Spawn(e) => e.kind(),
58 }
59 }
60}
61
62impl OnionServiceReverseProxy {
63 pub fn new(config: ProxyConfig) -> Arc<Self> {
65 let (shutdown_tx, shutdown_rx) = oneshot::channel();
66 Arc::new(Self {
67 state: Mutex::new(State {
68 config,
69 shutdown_tx: Some(shutdown_tx),
70 shutdown_rx: shutdown_rx.shared(),
71 }),
72 })
73 }
74
75 pub fn reconfigure(
80 &self,
81 config: ProxyConfig,
82 how: tor_config::Reconfigure,
83 ) -> Result<(), tor_config::ReconfigureError> {
84 if how == tor_config::Reconfigure::CheckAllOrNothing {
85 return Ok(());
87 }
88 let mut state = self.state.lock().expect("poisoned lock");
89 state.config = config;
90 Ok(())
95 }
96
97 pub fn shutdown(&self) {
99 let mut state = self.state.lock().expect("poisoned lock");
100 let _ = state.shutdown_tx.take();
101 }
102
103 pub async fn handle_requests<R, S>(
110 &self,
111 runtime: R,
112 nickname: HsNickname,
113 requests: S,
114 ) -> Result<(), HandleRequestsError>
115 where
116 R: Runtime,
117 S: Stream<Item = RendRequest> + Unpin,
118 {
119 let mut stream_requests = tor_hsservice::handle_rend_requests(requests).fuse();
120 let mut shutdown_rx = self
121 .state
122 .lock()
123 .expect("poisoned lock")
124 .shutdown_rx
125 .clone()
126 .fuse();
127 let nickname = Arc::new(nickname);
128
129 #[cfg(feature = "metrics")]
131 #[derive(Clone, Copy, Eq, PartialEq, Hash)]
132 enum CounterSelector {
133 Ret(Result<(), ()>),
135 Total,
137 }
138
139 #[cfg(feature = "metrics")]
140 let metrics_counters = {
141 use CounterSelector as CS;
142
143 let counters = iproduct!(
144 ProxyActionDiscriminants::iter(),
145 [
146 (CS::Total, "arti_hss_proxy_connections_total"),
147 (CS::Ret(Ok(())), "arti_hss_proxy_connections_ok_total"),
148 (CS::Ret(Err(())), "arti_hss_proxy_connections_failed_total"),
149 ],
150 )
151 .map(|(action, (outcome, name))| {
152 let k = (action, outcome);
153 let nickname = nickname.to_string();
154 let action: &str = action.into();
155 let v = metrics::counter!(name, "nickname" => nickname, "action" => action);
156 (k, v)
157 })
158 .collect::<HashMap<(ProxyActionDiscriminants, CounterSelector), _>>();
159
160 Arc::new(counters)
161 };
162
163 loop {
164 let stream_request = select_biased! {
165 _ = shutdown_rx => return Ok(()),
166 stream_request = stream_requests.next() => match stream_request {
167 None => return Ok(()),
168 Some(s) => s,
169 }
170 };
171
172 runtime.spawn({
173 let action = self.choose_action(stream_request.request());
174 let runtime = runtime.clone();
175 let nickname = nickname.clone();
176 let req = stream_request.request().clone();
177
178 #[cfg(feature = "metrics")]
179 let metrics_counters = metrics_counters.clone();
180
181 async move {
182 let outcome =
183 run_action(runtime, nickname.as_ref(), action.clone(), stream_request).await;
184
185 #[cfg(feature = "metrics")]
186 {
187 use CounterSelector as CS;
188
189 let action = ProxyActionDiscriminants::from(&action);
190 let outcome = outcome.as_ref().map(|_|()).map_err(|_|());
191 for outcome in [CS::Total, CS::Ret(outcome)] {
192 if let Some(counter) = metrics_counters.get(&(action, outcome)) {
193 counter.increment(1);
194 } else {
195 }
197 }
198 }
199
200 log_ratelim!(
201 "Performing action on {}", nickname;
202 outcome;
203 Err(_) => WARN, "Unable to take action {:?} for request {:?}", sv(action), sv(req)
204 );
205 }
206 })
207 .map_err(|e| HandleRequestsError::Spawn(Arc::new(e)))?;
208 }
209 }
210
211 fn choose_action(&self, stream_request: &IncomingStreamRequest) -> ProxyAction {
214 let port: u16 = match stream_request {
215 IncomingStreamRequest::Begin(begin) => {
216 begin.port()
219 }
220 other => {
221 tracing::warn!(
222 "Rejecting onion service request for invalid command {:?}. Internal error.",
223 other
224 );
225 return ProxyAction::DestroyCircuit;
226 }
227 };
228
229 self.state
230 .lock()
231 .expect("poisoned lock")
232 .config
233 .resolve_port_for_begin(port)
234 .cloned()
235 .unwrap_or(ProxyAction::DestroyCircuit)
237 }
238}
239
240async fn run_action<R: Runtime>(
242 runtime: R,
243 nickname: &HsNickname,
244 action: ProxyAction,
245 request: StreamRequest,
246) -> Result<(), RequestFailed> {
247 match action {
248 ProxyAction::DestroyCircuit => {
249 request
250 .shutdown_circuit()
251 .map_err(RequestFailed::CantDestroy)?;
252 }
253 ProxyAction::Forward(encap, target) => match (encap, target) {
254 (Encapsulation::Simple, ref addr @ TargetAddr::Inet(a)) => {
255 let rt_clone = runtime.clone();
256
257 let connect_options = Default::default();
259 let stream = runtime.connect(&a, &connect_options);
260
261 forward_connection(rt_clone, request, stream, nickname, addr).await?;
262 }
263 #[cfg(unix)]
267 (Encapsulation::Simple, TargetAddr::Unix(unix_addr)) => {
268 let rt_clone = runtime.clone();
269 let connect_options = Default::default();
271 let unix_addr_clone = unix_addr.clone();
272 let stream = runtime.connect(&unix_addr_clone, &connect_options);
273 forward_connection(
274 rt_clone,
275 request,
276 stream,
277 nickname,
278 &TargetAddr::Unix(unix_addr),
279 )
280 .await?;
281 }
282 },
283 ProxyAction::RejectStream => {
284 let end = relaymsg::End::new_with_reason(relaymsg::EndReason::DONE);
286
287 request
288 .reject(end)
289 .await
290 .map_err(RequestFailed::CantReject)?;
291 }
292 ProxyAction::IgnoreStream => drop(request),
293 };
294 Ok(())
295}
296
297#[derive(thiserror::Error, Debug, Clone)]
299enum RequestFailed {
300 #[error("Unable to destroy onion service circuit")]
302 CantDestroy(#[source] tor_error::Bug),
303
304 #[error("Unable to reject onion service request")]
306 CantReject(#[source] tor_hsservice::ClientError),
307
308 #[error("Unable to accept onion service connection")]
311 AcceptRemote(#[source] tor_hsservice::ClientError),
312
313 #[error("Unable to spawn task")]
315 Spawn(#[source] Arc<futures::task::SpawnError>),
316}
317
318impl HasKind for RequestFailed {
319 fn kind(&self) -> ErrorKind {
320 match self {
321 RequestFailed::CantDestroy(e) => e.kind(),
322 RequestFailed::CantReject(e) => e.kind(),
323 RequestFailed::AcceptRemote(e) => e.kind(),
324 RequestFailed::Spawn(e) => e.kind(),
325 }
326 }
327}
328
329const STREAM_BUF_LEN: usize = 4096;
338
339async fn forward_connection<R, FUT, TS>(
347 runtime: R,
348 request: StreamRequest,
349 target_stream_future: FUT,
350 nickname: &HsNickname,
351 addr: &TargetAddr,
352) -> Result<(), RequestFailed>
353where
354 R: Runtime,
355 FUT: Future<Output = Result<TS, IoError>>,
356 TS: AsyncRead + AsyncWrite + Send + 'static,
357{
358 let local_stream = target_stream_future.await.map_err(Arc::new);
359
360 log_ratelim!(
363 "Connecting to {} for onion service {}", sv(addr), nickname;
364 local_stream
365 );
366
367 let local_stream = match local_stream {
368 Ok(s) => s,
369 Err(_) => {
370 let end = relaymsg::End::new_with_reason(relaymsg::EndReason::DONE);
371 if let Err(e_rejecting) = request.reject(end).await {
372 debug_report!(
373 &e_rejecting,
374 "Unable to reject onion service request from client"
375 );
376 return Err(RequestFailed::CantReject(e_rejecting));
377 }
378 return Ok(());
381 }
382 };
383
384 let onion_service_stream: DataStream = {
385 let connected = relaymsg::Connected::new_empty();
386 request
387 .accept(connected)
388 .await
389 .map_err(RequestFailed::AcceptRemote)?
390 };
391
392 let onion_service_stream = BufReader::with_capacity(STREAM_BUF_LEN, onion_service_stream);
393 let local_stream = BufReader::with_capacity(STREAM_BUF_LEN, local_stream);
394
395 runtime
396 .spawn(
397 futures_copy::copy_buf_bidirectional(
398 onion_service_stream,
399 local_stream,
400 futures_copy::eof::Close,
401 futures_copy::eof::Close,
402 )
403 .map(|_| ()),
404 )
405 .map_err(|e| RequestFailed::Spawn(Arc::new(e)))?;
406
407 Ok(())
408}