1
//! The Tor directory mirror implementation.
2
//!
3
//! # Specifications
4
//!
5
//! * [Directory cache operation](https://spec.torproject.org/dir-spec/directory-cache-operation.html).
6
//!
7
//! # Rationale
8
//!
9
//! The network documents specified in the directory specification form a
10
//! fundamental part within the Tor protocol, namely the creation and distribution
11
//! of a canonical list, listing all relays present in the Tor network, thereby
12
//! giving all clients a unified view of the entire Tor network, a fact that
13
//! is very important for defending against partitioning attacks and other potential
14
//! attacks in the domain of distributed networks.
15
//!
16
//! These network documents are generated, signed, and served by so called
17
//! "directory authorities", a set of 10-ish highly trusted Tor relays more or
18
//! less governing the entirety of the Tor network.
19
//!
20
//! Now here comes the bottleneck: Tor has millions of active daily users but
21
//! only 10-ish relays responsible for these crucial documents.  Having all
22
//! clients download from those 10-ish relays would present an immense overload
23
//! to those, thereby potentially shutting the entire Tor network down, if the
24
//! amount of traffic to those relays is so high, that they are unable to
25
//! communicate and coordinate under themselves.
26
//!
27
//! Fortunately, all network documents are either directly or indirectly signed
28
//! by well-known keys of directory authorities, thereby making mirroring them
29
//! trivially possible, due the fact that authenticity can be established outside
30
//! the raw TLS connection thanks to cryptographic signatures.
31
//!
32
//! This is the place where directory mirrors come in hnady.  Directory mirrors
33
//! (previously known as "directory caches") are ordinary relays that mirror all
34
//! network documents from the authorities, by implementing the respective routes
35
//! for all HTTP GET endpoints from the relays.
36
//!
37
//! The network documents are usually served through ordinary Tor circuits,
38
//! by accepting incoming connections through `RELAY_BEGIN_DIR` cells.
39
//! In the past, this was done by some relays optionally enabling an additional
40
//! socket on the ordinary Internet through a dedicated SocketAddr, known as
41
//! "directory address".  Since about 2020, this is no longer done.  However,
42
//! the functionality continues to persist and this module is written fairly
43
//! agnostic on how it accepts such connections, as directory authorities continue
44
//! to advertise their directory address.
45

            
46
use std::{convert::Infallible, path::PathBuf};
47

            
48
use futures::Stream;
49
use tokio::io::{AsyncRead, AsyncWrite};
50
use tor_dircommon::{
51
    authority::AuthorityContacts,
52
    config::{DirTolerance, DownloadScheduleConfig},
53
};
54

            
55
#[cfg(feature = "dir-plugin-backend")]
56
use tor_dircommon::dir_plugin_backend::DirBackendPlugin;
57

            
58
mod operation;
59

            
60
/// Core data type of a directory mirror.
61
///
62
/// # External Notes
63
///
64
/// This structure serves as the entrence point to the [`mirror`](crate::mirror)
65
/// API.  It represents an instance that is launchable using [`DirMirror::serve`].
66
/// Calling this method consumes the instance, as this is the common behavior
67
/// for objects representing server-like things, in order to not imply that this
68
/// instance serves as a mere configuration template only.
69
///
70
/// # Internal Notes
71
///
72
/// For now, this data structure only holds configuration options as an ad-hoc
73
/// replacement for a yet missing hypothetical `DirMirrorConfig` structure.
74
///
75
/// I assume that in the future, regardless of the configuration, this might also
76
/// hold other fields such as access to the database pool, etc.  The question
77
/// is whether this structure will be passed around with locking mechanisms
78
/// or will just be used as a way to extract configuration options initially
79
/// in the consuming function, which then applies further wrapping or not.
80
#[derive(Debug)]
81
#[non_exhaustive]
82
pub struct DirMirror {
83
    /// The [`PathBuf`] where the [`database`](crate::database) is located.
84
    path: PathBuf,
85
    /// The [`AuthorityContacts`] data structure for contacting authorities.
86
    authorities: AuthorityContacts,
87
    /// The [`DownloadScheduleConfig`] used for properly retrying downloads.
88
    schedule: DownloadScheduleConfig,
89
    /// The [`DirTolerance`] to tolerate clock skews.
90
    tolerance: DirTolerance,
91
}
92

            
93
/// Insecure [`DirMirror`] abstraction providing a custom backend.
94
///
95
/// Intended for relay development as a medium-term abstraction.
96
#[cfg(feature = "dir-plugin-backend")]
97
#[non_exhaustive]
98
pub struct DirMirrorWithBackend<B> {
99
    /// The original [`DirMirror`].
100
    mirror: DirMirror,
101
    /// The backend to use for handling requests instead.
102
    backend: B,
103
}
104

            
105
#[cfg(feature = "dir-plugin-backend")]
106
impl<B: DirBackendPlugin> DirMirrorWithBackend<B> {
107
    /// Creates a new [`DirMirrorWithBackend`] from a given [`DirMirror`] and
108
    /// a given [`DirBackendPlugin`].
109
    pub fn new(mirror: DirMirror, backend: B) -> Self {
110
        Self { mirror, backend }
111
    }
112

            
113
    /// Consumes this [`DirMirror`] by running endlessly in the current task.
114
    ///
115
    /// Be aware of the limitations and also see [`DirMirror::serve()`].
116
    pub async fn serve<S, T, E>(self, listener: S) -> Result<(), Infallible>
117
    where
118
        S: Stream<Item = Result<T, E>> + Unpin,
119
        T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
120
        E: std::error::Error,
121
    {
122
        let res = crate::http::HttpServer::serve_backend(listener, self.backend).await;
123
        if let Err(e) = res {
124
            tracing::error!("HTTP backend failed unexpectedly: {e}");
125
        }
126
        Ok(())
127
    }
128
}
129

            
130
impl DirMirror {
131
    /// Creates a new [`DirMirror`] with a given set of configuration options.
132
    ///
133
    /// # Parameters
134
    ///
135
    /// * `path`: The [`PathBuf`] where the database is located.
136
    /// * `authorities`: The [`AuthorityContacts`] data structure for contacting authorities.
137
    /// * `schedule`: The [`DownloadScheduleConfig`] used for properly retrying downloads.
138
    /// * `tolerance`: The [`DirTolerance`] to tolerate clock skews.
139
    ///
140
    /// # Notes
141
    ///
142
    /// **Beware of [`DirTolerance::default()`]!**, as the default values are
143
    /// intended for clients, not directory mirrors.  Tolerances of several days
144
    /// are not recommended for directory mirrors.  Consider using something in
145
    /// the minute range instead, such as `60s`, which is what ctor uses.[^1]
146
    ///
147
    /// TODO DIRMIRROR: This is unacceptable for the actual release.  We **NEED**
148
    /// a proper way to configure this, such as with a `DirMirrorConfig` struct
149
    /// that can properly serialize from configuration files and such.  However,
150
    /// this task is not a trivial one and maybe one of the hardest parts of this
151
    /// entire development, as it would involve a radical change to many higher
152
    /// level crates.  The reason for this being, that we need a clean way to
153
    /// share "global" settings such as the list of authorities into various
154
    /// sub-configurations, such as the configuration for the directory mirror.
155
    /// We must not offer different configurations for the list of authorities
156
    /// for those different components, that would result in lots of boilerplate
157
    /// and potentially wrong execution given that those resources are affecting
158
    /// so many parts of the Tor protocol that a consistent view must be assumed
159
    /// in order to avoid surprising behavior.
160
    ///
161
    /// [^1]: <https://gitlab.torproject.org/tpo/core/tor/-/blob/0b20710/src/feature/nodelist/networkstatus.c#L1890>.
162
    pub fn new(
163
        path: PathBuf,
164
        authorities: AuthorityContacts,
165
        schedule: DownloadScheduleConfig,
166
        tolerance: DirTolerance,
167
    ) -> Self {
168
        Self {
169
            path,
170
            authorities,
171
            schedule,
172
            tolerance,
173
        }
174
    }
175

            
176
    /// Consumes the [`DirMirror`] by running endlessly in the current task.
177
    ///
178
    /// This method accepts a `listener`, which is a [`Stream`] yielding a
179
    /// [`Result`] in order to model a generic way of accepting incoming
180
    /// connections.  Think of `S` as the file descriptor you would call
181
    /// `accept(2)` upon if you were in C.  The idea behind this generic is,
182
    /// as outlined in the module documentation, that a [`DirMirror`] can
183
    /// handle incoming connections in multiple ways, such as by serving
184
    /// through an ordinary TCP socket or through a Tor circuit in combination
185
    /// with a `RELAY_BEGIN_DIR` cell.  How this is concretely done, is outside
186
    /// the scope of this crate; instead we provide the primitives making such
187
    /// flexibility possible.
188
    ///
189
    // TODO DIRMIRROR: can we change the listener to be a
190
    // Stream<Item = T> + Unpin instead of Stream<Item = Result<T, E>> + Unpin?
191
    // We expect the calling code to filter out any errors before handing
192
    // the stream over to DirMirror::serve().
193
    //
194
    // See https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/4222#note_3437135
195
    pub async fn serve<S, T, E>(self, mut listener: S) -> Result<(), Infallible>
196
    where
197
        S: Stream<Item = Result<T, E>> + Unpin,
198
        T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
199
        E: std::error::Error,
200
    {
201
        use futures::StreamExt as _;
202
        use tokio::io::AsyncWriteExt as _;
203

            
204
        // TODO DIRMIRROR: Replace this with the real implementation
205
        while let Some(s) = listener.next().await {
206
            let mut s = match s {
207
                Ok(s) => s,
208
                Err(e) => {
209
                    tracing::debug!("{e:?}");
210
                    continue;
211
                }
212
            };
213

            
214
            let serve_dummy_response = async {
215
                s.write_all(b"HTTP/1.1 500 Internal Server Error\r\n\r\n")
216
                    .await?;
217
                s.flush().await?;
218

            
219
                Ok::<(), std::io::Error>(())
220
            };
221

            
222
            if let Err(e) = serve_dummy_response.await {
223
                tracing::debug!(e=?e, "failed to serve dirmirror connection");
224
            }
225
        }
226

            
227
        Ok(())
228
    }
229
}