tor_dircommon/
fallback.rs

1//! List of directories that ships with Tor, for initial directory
2//! operations.
3//!
4//! When a client doesn't have directory information yet, it uses a
5//! "Fallback Directory" to retrieve its initial information about the
6//! network.
7//!
8//! # Semver note
9//!
10//! The types in this module are re-exported from `arti-client` and
11//! `tor-dirmgr`: any changes here must be reflected there.
12
13use base64ct::{Base64Unpadded, Encoding as _};
14use derive_builder::Builder;
15use tor_config::{ConfigBuildError, define_list_builder_helper};
16use tor_config::{define_list_builder_accessors, impl_standard_builder, list_builder::VecBuilder};
17use tor_llcrypto::pk::ed25519::Ed25519Identity;
18use tor_llcrypto::pk::rsa::RsaIdentity;
19
20use serde::{Deserialize, Serialize};
21use std::net::SocketAddr;
22
23/// A directory whose location ships with Tor (or arti), and which we
24/// can use for bootstrapping when we don't know anything else about
25/// the network.
26//
27// Note that we do *not* set serde(deny_unknown_fields) on this
28// structure: we want our fallback directory configuration format to
29// be future-proof against adding new info about each fallback.
30#[derive(Debug, Clone, Builder, Eq, PartialEq)]
31#[builder(build_fn(private, name = "build_unvalidated", error = "ConfigBuildError"))]
32#[builder(derive(Debug, Serialize, Deserialize))]
33pub struct FallbackDir {
34    /// RSA identity for the directory relay
35    rsa_identity: RsaIdentity,
36    /// Ed25519 identity for the directory relay
37    ed_identity: Ed25519Identity,
38    /// List of ORPorts for the directory relay
39    #[builder(sub_builder(fn_name = "build"), setter(custom))]
40    orports: Vec<SocketAddr>,
41}
42
43impl_standard_builder! { FallbackDir: !Default }
44
45define_list_builder_accessors! {
46    struct FallbackDirBuilder {
47        pub orports: [SocketAddr],
48    }
49}
50
51impl FallbackDirBuilder {
52    /// Make a new FallbackDirBuilder.
53    ///
54    /// You only need to use this if you're using a non-default set of
55    /// fallback directories.
56    pub fn new() -> Self {
57        Self::default()
58    }
59    /// Builds a new `FallbackDir`.
60    ///
61    /// ### Errors
62    ///
63    /// Errors unless both of `rsa_identity`, `ed_identity`, and at least one `orport`,
64    /// have been provided.
65    pub fn build(&self) -> std::result::Result<FallbackDir, ConfigBuildError> {
66        let built = self.build_unvalidated()?;
67        if built.orports.is_empty() {
68            return Err(ConfigBuildError::Invalid {
69                field: "orport".to_string(),
70                problem: "list was empty".to_string(),
71            });
72        }
73        Ok(built)
74    }
75}
76
77/// A list of fallback directories.
78///
79/// Fallback directories (represented by [`FallbackDir`]) are used by Tor
80/// clients when they don't already have enough other directory information to
81/// contact the network.
82#[derive(Debug, Clone, Default, PartialEq, Eq)]
83pub struct FallbackList {
84    /// The underlying fallbacks in this set.
85    fallbacks: Vec<FallbackDir>,
86}
87
88impl<T: IntoIterator<Item = FallbackDir>> From<T> for FallbackList {
89    fn from(fallbacks: T) -> Self {
90        FallbackList {
91            fallbacks: fallbacks.into_iter().collect(),
92        }
93    }
94}
95
96define_list_builder_helper! {
97    // pub because tor-dirmgr needs it for NetworkConfig.fallback_caches
98    pub struct FallbackListBuilder {
99        pub(crate) fallbacks: [FallbackDirBuilder],
100    }
101    built: FallbackList = FallbackList { fallbacks };
102    default = default_fallbacks();
103}
104
105impl FallbackList {
106    /// Return the number of fallbacks in this list.
107    pub fn len(&self) -> usize {
108        self.fallbacks.len()
109    }
110    /// Return true if there are no fallbacks in this list.
111    pub fn is_empty(&self) -> bool {
112        self.fallbacks.is_empty()
113    }
114    /// Returns an iterator over the [`FallbackDir`] items.
115    pub fn iter(&self) -> std::slice::Iter<'_, FallbackDir> {
116        self.fallbacks.iter()
117    }
118}
119
120/// Return a list of the default fallback directories shipped with
121/// arti.
122pub(crate) fn default_fallbacks() -> Vec<FallbackDirBuilder> {
123    /// Build a fallback directory; panic if input is bad.
124    fn fallback(rsa: &str, ed: &str, ports: &[&str]) -> FallbackDirBuilder {
125        let rsa = RsaIdentity::from_hex(rsa).expect("Bad hex in built-in fallback list");
126        let ed = Base64Unpadded::decode_vec(ed).expect("Bad hex in built-in fallback list");
127        let ed = Ed25519Identity::from_bytes(&ed).expect("Wrong length in built-in fallback list");
128        let mut bld = FallbackDir::builder();
129        bld.rsa_identity(rsa).ed_identity(ed);
130
131        ports
132            .iter()
133            .map(|s| s.parse().expect("Bad socket address in fallbacklist"))
134            .for_each(|p| {
135                bld.orports().push(p);
136            });
137
138        bld
139    }
140    include!("../data/fallback_dirs.rs")
141}
142
143impl tor_linkspec::HasAddrs for FallbackDir {
144    fn addrs(&self) -> impl Iterator<Item = SocketAddr> {
145        self.orports.iter().copied()
146    }
147}
148impl tor_linkspec::HasRelayIdsLegacy for FallbackDir {
149    fn ed_identity(&self) -> &Ed25519Identity {
150        &self.ed_identity
151    }
152    fn rsa_identity(&self) -> &RsaIdentity {
153        &self.rsa_identity
154    }
155}
156
157impl tor_linkspec::DirectChanMethodsHelper for FallbackDir {}
158
159impl tor_linkspec::ChanTarget for FallbackDir {}