tor_interface/
mock_tor_client.rs

1// standard
2use std::collections::BTreeMap;
3use std::net::{SocketAddr, TcpListener, TcpStream};
4use std::sync::{atomic, Arc, Mutex};
5
6// internal crates
7use crate::tor_crypto::*;
8use crate::tor_provider;
9use crate::tor_provider::*;
10
11
12/// [`MockTorClient`]-specific error type
13#[derive(thiserror::Error, Debug)]
14pub enum Error {
15    #[error("client not bootstrapped")]
16    ClientNotBootstrapped(),
17
18    #[error("client already bootstrapped")]
19    ClientAlreadyBootstrapped(),
20
21    #[error("onion service not found: {}", .0)]
22    OnionServiceNotFound(OnionAddr),
23
24    #[error("onion service not published: {}", .0)]
25    OnionServiceNotPublished(OnionAddr),
26
27    #[error("onion service requires onion auth")]
28    OnionServiceRequiresOnionAuth(),
29
30    #[error("provided onion auth key invalid")]
31    OnionServiceAuthInvalid(),
32
33    #[error("unable to bind TCP listener")]
34    TcpListenerBindFailed(#[source] std::io::Error),
35
36    #[error("unable to get TCP listener's local adress")]
37    TcpListenerLocalAddrFailed(#[source] std::io::Error),
38
39    #[error("unable to connect to {}", .0)]
40    ConnectFailed(TargetAddr),
41
42    #[error("not implemented")]
43    NotImplemented(),
44}
45
46impl From<Error> for crate::tor_provider::Error {
47    fn from(error: Error) -> Self {
48        crate::tor_provider::Error::Generic(error.to_string())
49    }
50}
51
52struct MockTorNetwork {
53    onion_services: Option<BTreeMap<OnionAddr, (Vec<X25519PublicKey>, SocketAddr)>>,
54}
55
56impl MockTorNetwork {
57    const fn new() -> MockTorNetwork {
58        MockTorNetwork {
59            onion_services: None,
60        }
61    }
62
63    fn connect_to_onion(
64        &mut self,
65        service_id: &V3OnionServiceId,
66        virt_port: u16,
67        client_auth: Option<&X25519PublicKey>,
68    ) -> Result<OnionStream, Error> {
69        let onion_addr = OnionAddr::V3(OnionAddrV3::new(service_id.clone(), virt_port));
70
71        match &mut self.onion_services {
72            Some(onion_services) => {
73                if let Some((client_auth_keys, socket_addr)) = onion_services.get(&onion_addr) {
74                    match (client_auth_keys.len(), client_auth) {
75                        (0, None) => (),
76                        (_, None) => return Err(Error::OnionServiceRequiresOnionAuth()),
77                        (0, Some(_)) => return Err(Error::OnionServiceAuthInvalid()),
78                        (_, Some(client_auth)) => {
79                            if !client_auth_keys.contains(client_auth) {
80                                return Err(Error::OnionServiceAuthInvalid());
81                            }
82                        }
83                    }
84
85                    if let Ok(stream) = TcpStream::connect(socket_addr) {
86                        Ok(OnionStream {
87                            stream,
88                            local_addr: None,
89                            peer_addr: Some(TargetAddr::OnionService(onion_addr)),
90                        })
91                    } else {
92                        Err(Error::OnionServiceNotFound(onion_addr))
93                    }
94                } else {
95                    Err(Error::OnionServiceNotPublished(onion_addr))
96                }
97            }
98            None => Err(Error::OnionServiceNotPublished(onion_addr)),
99        }
100    }
101
102    fn start_onion(
103        &mut self,
104        service_id: V3OnionServiceId,
105        virt_port: u16,
106        client_auth_keys: Vec<X25519PublicKey>,
107        address: SocketAddr,
108    ) {
109        let onion_addr = OnionAddr::V3(OnionAddrV3::new(service_id, virt_port));
110        match &mut self.onion_services {
111            Some(onion_services) => {
112                onion_services.insert(onion_addr, (client_auth_keys, address));
113            }
114            None => {
115                let mut onion_services = BTreeMap::new();
116                onion_services.insert(onion_addr, (client_auth_keys, address));
117                self.onion_services = Some(onion_services);
118            }
119        }
120    }
121
122    fn stop_onion(&mut self, onion_addr: &OnionAddr) {
123        if let Some(onion_services) = &mut self.onion_services {
124            onion_services.remove(onion_addr);
125        }
126    }
127}
128
129static MOCK_TOR_NETWORK: Mutex<MockTorNetwork> = Mutex::new(MockTorNetwork::new());
130
131/// A mock `TorProvider` implementation for testing.
132///
133/// `MockTorClient` implements the [`TorProvider`] trait. It creates a fake, in-process Tor Network using local socekts and listeners. No actual traffic ever leaves the local host.
134///
135/// Mock onion-services can be created, connected to, and communiccated with. Connecting to clearnet targets always succeeds by connecting to single local endpoint, but will never send any traffic to connecting clients.
136pub struct MockTorClient {
137    events: Vec<TorEvent>,
138    bootstrapped: bool,
139    client_auth_keys: BTreeMap<V3OnionServiceId, X25519PublicKey>,
140    onion_services: Vec<(OnionAddr, Arc<atomic::AtomicBool>)>,
141    loopback: TcpListener,
142    next_connect_handle: ConnectHandle,
143}
144
145impl MockTorClient {
146    /// Construct a new `MockTorClient`.
147    pub fn new() -> MockTorClient {
148        let mut events: Vec<TorEvent> = Default::default();
149        let line = "[notice] MockTorClient running".to_string();
150        events.push(TorEvent::LogReceived { line });
151
152        let socket_addr = SocketAddr::from(([127, 0, 0, 1], 0u16));
153        let listener = TcpListener::bind(socket_addr).expect("tcplistener bind failed");
154
155        MockTorClient {
156            events,
157            bootstrapped: false,
158            client_auth_keys: Default::default(),
159            onion_services: Default::default(),
160            loopback: listener,
161            next_connect_handle: Default::default(),
162        }
163    }
164}
165
166impl Default for MockTorClient {
167    fn default() -> Self {
168        Self::new()
169    }
170}
171
172impl TorProvider for MockTorClient {
173    fn update(&mut self) -> Result<Vec<TorEvent>, tor_provider::Error> {
174        match MOCK_TOR_NETWORK.lock() {
175            Ok(mut mock_tor_network) => {
176                let mut i = 0;
177                while i < self.onion_services.len() {
178                    // remove onion services with no active listeners
179                    if !self.onion_services[i].1.load(atomic::Ordering::Relaxed) {
180                        let entry = self.onion_services.swap_remove(i);
181                        let onion_addr = entry.0;
182                        mock_tor_network.stop_onion(&onion_addr);
183                    } else {
184                        i += 1;
185                    }
186                }
187            }
188            Err(_) => unreachable!("another thread panicked while holding mock tor network's lock"),
189        }
190
191        Ok(std::mem::take(&mut self.events))
192    }
193
194    fn bootstrap(&mut self) -> Result<(), tor_provider::Error> {
195        if self.bootstrapped {
196            Err(Error::ClientAlreadyBootstrapped())?
197        } else {
198            self.events.push(TorEvent::BootstrapStatus {
199                progress: 0u32,
200                tag: "start".to_string(),
201                summary: "bootstrapping started".to_string(),
202            });
203            self.events.push(TorEvent::BootstrapStatus {
204                progress: 50u32,
205                tag: "middle".to_string(),
206                summary: "bootstrapping continues".to_string(),
207            });
208            self.events.push(TorEvent::BootstrapStatus {
209                progress: 100u32,
210                tag: "finished".to_string(),
211                summary: "bootstrapping completed".to_string(),
212            });
213            self.events.push(TorEvent::BootstrapComplete);
214            self.bootstrapped = true;
215            Ok(())
216        }
217    }
218
219    fn add_client_auth(
220        &mut self,
221        service_id: &V3OnionServiceId,
222        client_auth: &X25519PrivateKey,
223    ) -> Result<(), tor_provider::Error> {
224        let client_auth_public = X25519PublicKey::from_private_key(client_auth);
225        if let Some(key) = self.client_auth_keys.get_mut(service_id) {
226            *key = client_auth_public;
227        } else {
228            self.client_auth_keys
229                .insert(service_id.clone(), client_auth_public);
230        }
231        Ok(())
232    }
233
234    fn remove_client_auth(
235        &mut self,
236        service_id: &V3OnionServiceId,
237    ) -> Result<(), tor_provider::Error> {
238        self.client_auth_keys.remove(service_id);
239        Ok(())
240    }
241
242    fn connect(
243        &mut self,
244        target: TargetAddr,
245        _circuit: Option<CircuitToken>,
246    ) -> Result<OnionStream, tor_provider::Error> {
247        let (service_id, virt_port) = match target {
248            TargetAddr::OnionService(OnionAddr::V3(OnionAddrV3 {
249                service_id,
250                virt_port,
251            })) => (service_id, virt_port),
252            target_address => {
253                if let Ok(stream) = TcpStream::connect(
254                    self.loopback
255                        .local_addr()
256                        .expect("loopback local_addr failed"),
257                ) {
258                    return Ok(OnionStream {
259                        stream,
260                        local_addr: None,
261                        peer_addr: Some(target_address),
262                    });
263                } else {
264                    return Err(Error::ConnectFailed(target_address).into());
265                }
266            }
267        };
268        let client_auth = self.client_auth_keys.get(&service_id);
269
270        match MOCK_TOR_NETWORK.lock() {
271            Ok(mut mock_tor_network) => {
272                Ok(mock_tor_network.connect_to_onion(&service_id, virt_port, client_auth)?)
273            }
274            Err(_) => unreachable!("another thread panicked while holding mock tor network's lock"),
275        }
276    }
277
278    fn connect_async(
279        &mut self,
280        target: TargetAddr,
281        circuit: Option<CircuitToken>,
282    ) -> Result<ConnectHandle, tor_provider::Error> {
283
284        let handle = self.next_connect_handle;
285        self.next_connect_handle += 1usize;
286
287        let event = match self.connect(target, circuit) {
288            Ok(stream) => TorEvent::ConnectComplete{
289                handle,
290                stream,
291            },
292            Err(error) => TorEvent::ConnectFailed{
293                handle,
294                error,
295            },
296        };
297        self.events.push(event);
298
299        Ok(handle)
300    }
301
302    fn listener(
303        &mut self,
304        private_key: &Ed25519PrivateKey,
305        virt_port: u16,
306        authorized_clients: Option<&[X25519PublicKey]>,
307    ) -> Result<OnionListener, tor_provider::Error> {
308        // convert inputs to relevant types
309        let service_id = V3OnionServiceId::from_private_key(private_key);
310        let onion_addr = OnionAddr::V3(OnionAddrV3::new(service_id.clone(), virt_port));
311        let authorized_clients: Vec<X25519PublicKey> = match authorized_clients {
312            Some(keys) => keys.into(),
313            None => Default::default(),
314        };
315
316        // try to bind to a local address, let OS pick our port
317        let socket_addr = SocketAddr::from(([127, 0, 0, 1], 0u16));
318        let listener = TcpListener::bind(socket_addr).map_err(Error::TcpListenerBindFailed)?;
319        let socket_addr = listener
320            .local_addr()
321            .map_err(Error::TcpListenerLocalAddrFailed)?;
322
323        // register the onion service with the mock tor network
324        match MOCK_TOR_NETWORK.lock() {
325            Ok(mut mock_tor_network) => mock_tor_network.start_onion(
326                service_id.clone(),
327                virt_port,
328                authorized_clients,
329                socket_addr,
330            ),
331            Err(_) => unreachable!("another thread panicked while holding mock tor network's lock"),
332        }
333
334        // init flag for signaling when listener goes out of scope so we can tear down onion service
335        let is_active = Arc::new(atomic::AtomicBool::new(true));
336        self.onion_services
337            .push((onion_addr.clone(), Arc::clone(&is_active)));
338
339        // onion service published event
340        self.events
341            .push(TorEvent::OnionServicePublished { service_id });
342
343
344        Ok(OnionListener::new(listener, onion_addr, is_active, |is_active| {
345            is_active.store(false, atomic::Ordering::Relaxed);
346        }))
347    }
348
349    fn generate_token(&mut self) -> CircuitToken {
350        0usize
351    }
352
353    fn release_token(&mut self, _token: CircuitToken) {}
354}
355
356impl Drop for MockTorClient {
357    fn drop(&mut self) {
358        // remove all our onion services
359        match MOCK_TOR_NETWORK.lock() {
360            Ok(mut mock_tor_network) => {
361                for entry in self.onion_services.iter() {
362                    let onion_addr = &entry.0;
363                    mock_tor_network.stop_onion(onion_addr);
364                }
365            }
366            Err(_) => unreachable!("another thread panicked while holding mock tor network's lock"),
367        }
368    }
369}