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