arti_relay/relay.rs
1//! Entry point of a Tor relay that is the [`TorRelay`] objects
2
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5
6use anyhow::Context;
7use tokio::task::JoinSet;
8use tracing::info;
9
10use fs_mistrust::Mistrust;
11use tor_chanmgr::Dormancy;
12use tor_config_path::CfgPathResolver;
13use tor_keymgr::{
14    ArtiEphemeralKeystore, ArtiNativeKeystore, KeyMgr, KeyMgrBuilder, KeystoreSelector,
15};
16use tor_memquota::MemoryQuotaTracker;
17use tor_netdir::params::NetParameters;
18use tor_persist::state_dir::StateDirectory;
19use tor_persist::{FsStateMgr, StateMgr};
20use tor_relay_crypto::pk::{RelayIdentityKeypair, RelayIdentityKeypairSpecifier};
21use tor_rtcompat::Runtime;
22
23use crate::config::TorRelayConfig;
24
25/// An initialized but unbootstrapped relay.
26///
27/// This intentionally does not have access to the runtime to prevent it from doing network io.
28///
29/// The idea is that we can build up the relay's components in an `InertTorRelay` without a runtime,
30/// and then call `bootstrap()` on it and provide a runtime to turn it into a network-capable relay.
31/// This gives us two advantages:
32///
33/// - We can initialize the internal data structures in the `InertTorRelay` (load the keystores,
34///   configure memquota, etc), which leaves `TorRelay` to just "running" the relay (bootstrapping,
35///   setting up listening sockets, etc). We don't need to combine the initialization and "running
36///   the relay" all within the same object.
37/// - We will likely want to share some of arti's key management subcommands in the future.
38///   arti-client has an `InertTorClient` which is used so that arti subcommands can access the
39///   keystore. If we do a similar thing here in arti-relay in the future, it might be nice to have
40///   an `InertTorRelay` which has these internal data structures, but doesn't need a runtime or
41///   have any networking capabilities.
42///
43/// Time will tell if this ends up being a bad design decision in practice, and we can always change
44/// it later.
45#[derive(Clone)]
46pub(crate) struct InertTorRelay {
47    /// The configuration options for the relay.
48    config: TorRelayConfig,
49
50    /// Path resolver for expanding variables in [`CfgPath`](tor_config_path::CfgPath)s.
51    #[expect(unused)] // TODO RELAY remove
52    path_resolver: CfgPathResolver,
53
54    /// State directory path.
55    ///
56    /// The [`StateDirectory`] stored in `state_dir` doesn't seem to have a way of getting the state
57    /// directory path, so we need to store a copy of the path here.
58    #[expect(unused)] // TODO RELAY remove
59    state_path: PathBuf,
60
61    /// Relay's state directory.
62    #[expect(unused)] // TODO RELAY remove
63    state_dir: StateDirectory,
64
65    /// Location on disk where we store persistent data.
66    #[expect(unused)] // TODO RELAY remove
67    state_mgr: FsStateMgr,
68
69    /// Key manager holding all relay keys and certificates.
70    keymgr: Arc<KeyMgr>,
71}
72
73impl InertTorRelay {
74    /// Create a new Tor relay with the given configuration.
75    pub(crate) fn new(
76        config: TorRelayConfig,
77        path_resolver: CfgPathResolver,
78    ) -> anyhow::Result<Self> {
79        let state_path = config.storage.state_dir(&path_resolver)?;
80        let state_dir = StateDirectory::new(&state_path, config.storage.permissions())
81            .context("Failed to create `StateDirectory`")?;
82        let state_mgr =
83            FsStateMgr::from_path_and_mistrust(&state_path, config.storage.permissions())
84                .context("Failed to create `FsStateMgr`")?;
85
86        // Try to take state ownership early, so we'll know if we have it.
87        // Note that this `try_lock()` may return `Ok` even if we can't acquire the lock.
88        // (At this point we don't yet care if we have it.)
89        let _ignore_status = state_mgr
90            .try_lock()
91            .context("Failed to try locking the state manager")?;
92
93        let keymgr = Self::create_keymgr(&state_path, config.storage.permissions())
94            .context("Failed to create key manager")?;
95
96        Ok(Self {
97            config,
98            path_resolver,
99            state_path,
100            state_dir,
101            state_mgr,
102            keymgr,
103        })
104    }
105
106    /// Connect the [`InertTorRelay`] to the Tor network.
107    pub(crate) async fn bootstrap<R: Runtime>(self, runtime: R) -> anyhow::Result<TorRelay<R>> {
108        // Attempt to generate any missing keys/cert from the KeyMgr.
109        Self::try_generate_keys(&self.keymgr).context("Failed to generate keys")?;
110
111        TorRelay::bootstrap(runtime, self).await
112    }
113
114    /// Create the [key manager](KeyMgr).
115    fn create_keymgr(state_path: &Path, mistrust: &Mistrust) -> anyhow::Result<Arc<KeyMgr>> {
116        let key_store_dir = state_path.join("keystore");
117
118        // Store for the short-term keys that we don't need to keep on disk. The store identifier
119        // is relay explicit because it can be used in other crates for channel and circuit.
120        let ephemeral_store = ArtiEphemeralKeystore::new("relay-ephemeral".into());
121        let persistent_store = ArtiNativeKeystore::from_path_and_mistrust(&key_store_dir, mistrust)
122            .context("Failed to construct the native keystore")?;
123        info!("Using relay keystore from {key_store_dir:?}");
124
125        let keymgr = KeyMgrBuilder::default()
126            .primary_store(Box::new(persistent_store))
127            .set_secondary_stores(vec![Box::new(ephemeral_store)])
128            .build()
129            .context("Failed to build the 'KeyMgr'")?;
130        let keymgr = Arc::new(keymgr);
131
132        Ok(keymgr)
133    }
134
135    /// Generate the relay keys.
136    fn try_generate_keys(keymgr: &KeyMgr) -> anyhow::Result<()> {
137        let mut rng = tor_llcrypto::rng::CautiousRng;
138
139        // Attempt to get the relay long-term identity key from the key manager. If not present,
140        // generate it. We need this key to sign the signing certificates.
141        let _kp_relay_id = keymgr
142            .get_or_generate::<RelayIdentityKeypair>(
143                &RelayIdentityKeypairSpecifier::new(),
144                KeystoreSelector::default(),
145                &mut rng,
146            )
147            .context("Failed to get or generate the long-term identity key")?;
148
149        // TODO #1598: We need to get_or_generate RSA keys here, but that currently fails because
150        // upstream ssh-key doesn't support 1024 bit keys. Once they do, we should add that here.
151
152        // TODO: Once certificate supports is added to the KeyMgr, we need to get/gen the
153        // RelaySigning (KP_relaysign_ed) certs from the native persistent store.
154        //
155        // If present, rotate it if expired. Else, generate it. Rotation or creation require the
156        // relay identity keypair (above) in order to sign the RelaySigning.
157        //
158        // We then need to generate the RelayLink (KP_link_ed) certificate which is in turn signed
159        // by the RelaySigning cert.
160
161        Ok(())
162    }
163}
164
165/// Represent an active Relay on the Tor network.
166#[derive(Clone)]
167pub(crate) struct TorRelay<R: Runtime> {
168    /// Asynchronous runtime object.
169    _runtime: R,
170
171    /// Memory quota tracker.
172    #[expect(unused)] // TODO RELAY remove
173    memquota: Arc<MemoryQuotaTracker>,
174
175    /// Channel manager, used by circuits etc.
176    chanmgr: Arc<tor_chanmgr::ChanMgr<R>>,
177
178    /// Key manager holding all relay keys and certificates.
179    #[expect(unused)] // TODO RELAY remove
180    keymgr: Arc<KeyMgr>,
181}
182
183impl<R: Runtime> TorRelay<R> {
184    /// Create a new Tor relay with the given [`runtime`][tor_rtcompat].
185    ///
186    /// Expected to be called from [`InertTorRelay::bootstrap()`].
187    async fn bootstrap(runtime: R, inert: InertTorRelay) -> anyhow::Result<Self> {
188        let memquota = MemoryQuotaTracker::new(&runtime, inert.config.system.memory.clone())
189            .context("Failed to initialize memquota tracker")?;
190
191        let chanmgr = Arc::new(tor_chanmgr::ChanMgr::new(
192            runtime.clone(),
193            &inert.config.channel,
194            Dormancy::Active,
195            &NetParameters::default(),
196            memquota.clone(),
197            Some(inert.keymgr.clone()),
198        ));
199
200        // TODO: missing the actual bootstrapping
201
202        Ok(Self {
203            _runtime: runtime,
204            memquota,
205            chanmgr,
206            keymgr: inert.keymgr,
207        })
208    }
209
210    /// Run the actual relay.
211    ///
212    /// This only returns if something has gone wrong.
213    /// Otherwise it runs forever.
214    pub(crate) async fn run(&self) -> anyhow::Result<void::Void> {
215        let mut task_handles = JoinSet::new();
216
217        // Channel housekeeping task.
218        let mut t = crate::tasks::ChannelHouseKeepingTask::new(&self.chanmgr);
219        task_handles.spawn(async move { t.start().await });
220
221        // TODO: More tasks will be spawned here.
222
223        // We block until facism is erradicated or a task ends which means the relay will shutdown
224        // and facism will have one more chance.
225        let void = task_handles
226            .join_next()
227            .await
228            .context("Task set is empty")?
229            .context("Task join failed")?
230            .context("Task stopped with error")?;
231
232        // We can never get here since a `Void` cannot be constructed.
233        void::unreachable(void);
234    }
235}