1
//! Entry point of a Tor relay that is the [`TorRelay`] objects
2

            
3
use std::net::SocketAddr;
4
use std::path::{Path, PathBuf};
5
use std::sync::{Arc, Weak};
6

            
7
use anyhow::Context;
8
use hickory_net::runtime::TokioRuntimeProvider;
9
use hickory_resolver::{Resolver, ResolverBuilder};
10
use tokio::task::JoinSet;
11
use tracing::debug;
12
#[cfg(unix)]
13
use tracing::warn;
14

            
15
use fs_mistrust::Mistrust;
16
use tor_basic_utils::iter_join;
17
use tor_cell::relaycell::RelayCmd;
18
use tor_chanmgr::{ChanMgr, ChanMgrConfig, Dormancy};
19
use tor_config_path::CfgPathResolver;
20
use tor_dircommon::authority::AuthorityContacts;
21
use tor_dircommon::config::{DirTolerance, DownloadScheduleConfig};
22
use tor_dirmgr::{DirMgrConfig, DirPlugin};
23
use tor_dirserver::mirror::{DirMirror, DirMirrorWithBackend};
24
use tor_keymgr::{ArtiNativeKeystore, KeyMgr, KeyMgrBuilder};
25
use tor_memquota::MemoryQuotaTracker;
26
use tor_netdir::params::NetParameters;
27
use tor_persist::state_dir::StateDirectory;
28
use tor_persist::{FsStateMgr, StateMgr};
29
use tor_proto::relay::{CircuitIncomingStreamReceiver, CreateRequestHandler};
30
use tor_rtcompat::{DynTimeProvider, NetStreamProvider, Runtime};
31

            
32
use crate::client::RelayClient;
33
use crate::config::{DnsConfig, TorRelayConfig};
34
use crate::stream::RequestFilter;
35
use crate::stream::dns::resolver::DnsResolverReactor;
36
use crate::tasks::channel::build_circ_net_params;
37
use crate::tasks::crypto::InitKeyMaterial;
38

            
39
use futures::channel::mpsc;
40

            
41
// TODO(relay): this is in the client module, but not client-specific
42
use tor_proto::client::stream::DataStream;
43

            
44
/// An initialized but unbootstrapped relay.
45
///
46
/// This intentionally does not have access to the runtime to prevent it from doing network io.
47
///
48
/// The idea is that we can build up the relay's components in an `InertTorRelay` without a runtime,
49
/// and then call `init()` on it and provide a runtime to turn it into a network-capable relay.
50
/// This gives us two advantages:
51
///
52
/// - We can initialize the internal data structures in the `InertTorRelay` (load the keystores,
53
///   configure memquota, etc), which leaves `TorRelay` to just "running" the relay (bootstrapping,
54
///   setting up listening sockets, etc). We don't need to combine the initialization and "running
55
///   the relay" all within the same object.
56
/// - We will likely want to share some of arti's key management subcommands in the future.
57
///   arti-client has an `InertTorClient` which is used so that arti subcommands can access the
58
///   keystore. If we do a similar thing here in arti-relay in the future, it might be nice to have
59
///   an `InertTorRelay` which has these internal data structures, but doesn't need a runtime or
60
///   have any networking capabilities.
61
///
62
/// Time will tell if this ends up being a bad design decision in practice, and we can always change
63
/// it later.
64
pub(crate) struct InertTorRelay {
65
    /// The configuration options for the relay.
66
    config: TorRelayConfig,
67

            
68
    /// The configuration options for the client's directory manager.
69
    dirmgr_config: DirMgrConfig,
70

            
71
    /// Path resolver for expanding variables in [`CfgPath`](tor_config_path::CfgPath)s.
72
    #[expect(unused)] // TODO RELAY remove
73
    path_resolver: CfgPathResolver,
74

            
75
    /// State directory path.
76
    ///
77
    /// The [`StateDirectory`] stored in `state_dir` doesn't seem to have a way of getting the state
78
    /// directory path, so we need to store a copy of the path here.
79
    #[expect(unused)] // TODO RELAY remove
80
    state_path: PathBuf,
81

            
82
    /// Relay's state directory.
83
    #[expect(unused)] // TODO RELAY remove
84
    state_dir: StateDirectory,
85

            
86
    /// Location on disk where we store persistent data.
87
    state_mgr: FsStateMgr,
88

            
89
    /// Key manager.
90
    keymgr: KeyMgr,
91
}
92

            
93
impl InertTorRelay {
94
    /// Create a new Tor relay with the given configuration.
95
    pub(crate) fn new(
96
        config: TorRelayConfig,
97
        path_resolver: CfgPathResolver,
98
    ) -> anyhow::Result<Self> {
99
        let state_path = config.storage.state_dir(&path_resolver)?;
100
        let cache_path = config.storage.cache_dir(&path_resolver)?;
101

            
102
        let state_dir = StateDirectory::new(&state_path, config.storage.permissions())
103
            .context("Failed to create `StateDirectory`")?;
104
        let state_mgr =
105
            FsStateMgr::from_path_and_mistrust(&state_path, config.storage.permissions())
106
                .context("Failed to create `FsStateMgr`")?;
107

            
108
        // Try to take state ownership early, so we'll know if we have it.
109
        // Note that this `try_lock()` may return `Ok` even if we can't acquire the lock.
110
        // (At this point we don't yet care if we have it.)
111
        let _ignore_status = state_mgr
112
            .try_lock()
113
            .context("Failed to try locking the state manager")?;
114

            
115
        let keymgr = Self::create_keymgr(&state_path, config.storage.permissions())
116
            .context("Failed to create key manager")?;
117

            
118
        let dirmgr_config = DirMgrConfig {
119
            cache_dir: cache_path,
120
            cache_trust: config.storage.permissions().clone(),
121
            network: config.tor_network.clone(),
122
            schedule: Default::default(),
123
            tolerance: Default::default(),
124
            override_net_params: Default::default(),
125
            extensions: Default::default(),
126
        };
127

            
128
        Ok(Self {
129
            config,
130
            dirmgr_config,
131
            path_resolver,
132
            state_path,
133
            state_dir,
134
            state_mgr,
135
            keymgr,
136
        })
137
    }
138

            
139
    /// Connect the [`InertTorRelay`] to the Tor network.
140
    pub(crate) async fn init<R: Runtime>(self, runtime: R) -> anyhow::Result<TorRelay<R>> {
141
        // Attempt to generate any missing keys/cert from the KeyMgr.
142
        let init_key_material = crate::tasks::crypto::init_keys(&runtime, &self.keymgr)
143
            .context("Failed to generate keys")?;
144

            
145
        TorRelay::init(runtime, self, init_key_material).await
146
    }
147

            
148
    /// Create the [key manager](KeyMgr).
149
    fn create_keymgr(state_path: &Path, mistrust: &Mistrust) -> anyhow::Result<KeyMgr> {
150
        let key_store_dir = state_path.join("keystore");
151

            
152
        let persistent_store = ArtiNativeKeystore::from_path_and_mistrust(&key_store_dir, mistrust)
153
            .context("Failed to construct the native keystore")?;
154

            
155
        // Should only log fs paths at debug level or lower,
156
        // unless they're part of a diagnostic message.
157
        debug!("Using relay keystore from {key_store_dir:?}");
158

            
159
        let keymgr = KeyMgrBuilder::default()
160
            .primary_store(Box::new(persistent_store))
161
            .build()
162
            .context("Failed to build the 'KeyMgr'")?;
163

            
164
        // TODO: support C-tor keystore
165

            
166
        Ok(keymgr)
167
    }
168
}
169

            
170
/// Represent an active Relay on the Tor network.
171
pub(crate) struct TorRelay<R: Runtime> {
172
    /// Asynchronous runtime object.
173
    runtime: R,
174

            
175
    /// Memory quota tracker.
176
    #[expect(unused)] // TODO RELAY remove
177
    memquota: Arc<MemoryQuotaTracker>,
178

            
179
    /// A "client" used by relays to construct circuits.
180
    client: RelayClient<R>,
181

            
182
    /// The directory authorities that were either configured or the compiled-in defaults.
183
    ///
184
    /// We keep a copy here so we can pass it to the descriptor publisher task. These are not
185
    /// exposed by a [`tor_dirmgr::DirProvider`] hence why we keep that copy from the config.
186
    authorities: AuthorityContacts,
187

            
188
    /// The directory mirror object, used for handling BEGIN_DIR.
189
    dir_mirror: DirMirrorWithBackend<DirPlugin>,
190

            
191
    /// Channel manager, used by circuits etc.
192
    chanmgr: Arc<ChanMgr<R>>,
193

            
194
    /// Handles CREATE* requests on channels.
195
    ///
196
    /// Given to the [`ChanMgr`],
197
    /// which gives it to each channel.
198
    /// We can access this handler directly to update consensus parameters or keys.
199
    create_request_handler: Arc<CreateRequestHandler>,
200

            
201
    /// The receiver for the [`Stream`](futures::Stream)s of `IncomingStream` of all circuits.
202
    ///
203
    /// Receives one [`Stream`](futures::Stream) (of tor streams) per circuit.
204
    /// Each of these is handled in a new task.
205
    circuit_stream_rx: CircuitIncomingStreamReceiver,
206

            
207
    /// See [`InertTorRelay::keymgr`].
208
    keymgr: KeyMgr,
209

            
210
    /// Listening OR ports.
211
    or_listeners: Vec<<R as NetStreamProvider<SocketAddr>>::Listener>,
212

            
213
    /// The hickory stub resolver, used for building the DNS resolver reactor.
214
    //
215
    // TODO(relay): this should be optional, and only set to Some
216
    // if we're configured to run as an exit.
217
    //
218
    // Right now, because there is no exit config,
219
    // arti-relay unconditionally spawns the DNS resolver task,
220
    // and unconditionally accepts all RESOLVE.
221
    resolver: Arc<Resolver<TokioRuntimeProvider>>,
222
}
223

            
224
impl<R: Runtime> TorRelay<R> {
225
    /// Create a new Tor relay with the given [`runtime`][tor_rtcompat].
226
    ///
227
    /// We use this to initialize components, open sockets, etc.
228
    /// Doing work with these components should happen in [`TorRelay::run()`].
229
    ///
230
    /// Expected to be called from [`InertTorRelay::init()`].
231
    async fn init(
232
        runtime: R,
233
        inert: InertTorRelay,
234
        init_key_material: InitKeyMaterial,
235
    ) -> anyhow::Result<Self> {
236
        let memquota = MemoryQuotaTracker::new(&runtime, inert.config.system.memory.clone())
237
            .context("Failed to initialize memquota tracker")?;
238

            
239
        // Init the channel manager.
240
        let config = ChanMgrConfig::new(inert.config.channel.clone())
241
            .with_my_addrs(inert.config.relay.advertise.all_addr())
242
            .with_auth_material(Arc::new(init_key_material.chan_auth_keys));
243
        let chanmgr = Arc::new(
244
            ChanMgr::new(
245
                runtime.clone(),
246
                config,
247
                Dormancy::Active,
248
                // TODO: It seems wrong to start with the compiled-in defaults when we might have
249
                // a newer network status on disk that would provide a better initial value,
250
                // but `TorClient` does this too so let's not worry about it.
251
                &NetParameters::default(),
252
                memquota.clone(),
253
            )
254
            .context("Failed to build chan manager")?,
255
        );
256

            
257
        let authorities = inert.dirmgr_config.authorities().clone();
258

            
259
        // Init the relay's client.
260
        let client = RelayClient::new(
261
            runtime.clone(),
262
            Arc::clone(&chanmgr),
263
            &inert.config,
264
            &inert.config,
265
            inert.dirmgr_config,
266
            inert.state_mgr,
267
        )
268
        .context("Failed to construct the relay's client")?;
269

            
270
        // Circuit-related network status parameters.
271
        let circ_net_params = build_circ_net_params(client.dirmgr().params().as_ref().as_ref())
272
            .context("Failed to build circuit parameters for CREATE* request handler")?;
273

            
274
        // TODO(relay): add exit configuration, and update this to reject BEGIN and RESOLVE
275
        // if we are not configured to run as an exit
276
        let allow_incoming = &[RelayCmd::BEGIN, RelayCmd::BEGIN_DIR, RelayCmd::RESOLVE];
277

            
278
        // A handler that will process CREATE* requests on channels.
279
        let (create_request_handler, circuit_stream_rx) = CreateRequestHandler::new(
280
            Arc::downgrade(&chanmgr) as Weak<_>,
281
            circ_net_params,
282
            init_key_material.ntor_keys,
283
            Box::new(|| Box::new(RequestFilter::default()) as Box<_>),
284
            allow_incoming,
285
        );
286
        let create_request_handler = Arc::new(create_request_handler);
287

            
288
        // Configure the channel manager to handle CREATE* requests.
289
        //
290
        // We do this once, and can later update its network parameters and keys using the
291
        // `Arc` handle that we store.
292
        // The `ChanMgr` will hold an `Arc<CreateRequestHandler>` and
293
        // the `CreateRequestHandler` will hold a `Weak<ChanMgr>`.
294
        //
295
        // We could technically do something fancier by creating the `ChanMgr` and handler
296
        // inside an `Arc::new_cyclic()` and pass the handler as part of the `ChanMgrConfig`,
297
        // but the code becomes a mess.
298
        chanmgr
299
            .set_create_request_handler(Arc::clone(&create_request_handler))
300
            .context("Failed to set the CREATE* request handler")?;
301

            
302
        // We don't use any custom options on the listening socket.
303
        let listen_options = Default::default();
304

            
305
        // An iterator of `listen()` futures with some extra error handling.
306
        let or_listeners = inert.config.relay.listen.addrs().map(async |addr| {
307
            match runtime.listen(addr, &listen_options).await {
308
                Ok(x) => Some(Ok(x)),
309
                // If we don't support the address family (typically IPv6), only warn.
310
                #[cfg(unix)]
311
                Err(ref e) if e.raw_os_error() == Some(libc::EAFNOSUPPORT) => {
312
                    let message =
313
                        format!("Could not listen at {addr}: address family not supported");
314
                    if addr.is_ipv6() {
315
                        warn!("{message}");
316
                    } else {
317
                        // If we got `EAFNOSUPPORT` for a non-IPv6 address, then warn louder.
318
                        tor_error::warn_report!(e, "{message}");
319
                    }
320
                    None
321
                }
322
                Err(e) => {
323
                    Some(Err(e).with_context(|| format!("Failed to listen at address {addr}")))
324
                }
325
            }
326
        });
327

            
328
        // We await the futures sequentially rather than with something like `join_all` to make
329
        // errors more reproducible.
330
        let or_listeners = {
331
            let mut awaited_listeners = vec![];
332
            for listener in or_listeners {
333
                match listener.await {
334
                    Some(Ok(x)) => awaited_listeners.push(x),
335
                    Some(Err(e)) => return Err(e),
336
                    None => {}
337
                };
338
            }
339
            awaited_listeners
340
        };
341

            
342
        // Typically we would have returned with an error if we failed to listen on an address,
343
        // but we ignore `EAFNOSUPPORT` errors above, so it's possible that all failed with
344
        // `EAFNOSUPPORT` and we ended up here.
345
        if or_listeners.is_empty() {
346
            return Err(anyhow::anyhow!(
347
                "Could not listen at any OR port addresses: {}",
348
                iter_join(", ", inert.config.relay.listen.addrs()),
349
            ));
350
        }
351

            
352
        // TODO DIRMIRROR: Need a config for the DirMirror and should be same as our
353
        // TorRelay one.
354
        let path: PathBuf = PathBuf::from("/dev/null");
355
        let dir_mirror_authorities: AuthorityContacts = Default::default();
356
        let schedule: DownloadScheduleConfig = Default::default();
357
        let tolerance: DirTolerance = Default::default();
358

            
359
        let dir_mirror = DirMirror::new(path, dir_mirror_authorities, schedule, tolerance);
360

            
361
        // TODO: This is temporary until the directory mirror has support for downloading documents.
362
        let dir_mirror = DirMirrorWithBackend::new(dir_mirror, client.dirmgr_plugin().clone());
363

            
364
        let resolver = Arc::new(build_hickory_resolver(&inert.config)?);
365

            
366
        Ok(Self {
367
            runtime,
368
            memquota,
369
            client,
370
            authorities,
371
            dir_mirror,
372
            chanmgr,
373
            create_request_handler,
374
            keymgr: inert.keymgr,
375
            or_listeners,
376
            circuit_stream_rx,
377
            resolver,
378
        })
379
    }
380

            
381
    /// Run the actual relay.
382
    ///
383
    /// This only returns if something has gone wrong.
384
    /// Otherwise it runs forever.
385
    pub(crate) async fn run(self) -> anyhow::Result<void::Void> {
386
        let mut task_handles = JoinSet::new();
387

            
388
        // Channel housekeeping task.
389
        task_handles.spawn({
390
            let mut t = crate::tasks::ChannelHouseKeepingTask::new(&self.chanmgr);
391
            async move {
392
                t.start()
393
                    .await
394
                    .context("Failed to run channel house keeping task")
395
            }
396
        });
397

            
398
        // Update the CREATE* request handler when there are new network parameters.
399
        task_handles.spawn({
400
            let create_request_handler = Arc::clone(&self.create_request_handler);
401
            let dir_provider = Arc::clone(self.client.dirmgr());
402
            async {
403
                crate::tasks::channel::update_create_request_handler_netparams(
404
                    create_request_handler,
405
                    dir_provider as Arc<_>,
406
                )
407
                .await
408
                .context("Failed to run create request handler update task")
409
            }
410
        });
411

            
412
        // Listen for new Tor (OR) connections.
413
        task_handles.spawn({
414
            let runtime = self.runtime.clone();
415
            let chanmgr = Arc::clone(&self.chanmgr);
416
            async {
417
                // TODO: Should we give all tasks a `start` method?
418
                crate::tasks::listeners::or_listener(runtime, chanmgr, self.or_listeners)
419
                    .await
420
                    .context("Failed to run OR listener task")
421
            }
422
        });
423

            
424
        // TODO DIRMIRROR: The buffer size here was picked mostly arbitrarily
425
        // (on my new and not-very-busy relay, I noticed bursts of ~5000 BEGIN_DIR requests per second,
426
        // but I'm not sure how representative this is).
427
        //
428
        // We may be able to make this buffer even smaller, assuming the consumer (i.e. DirMirror)
429
        // reads from it quickly enough (presumably it will read from this in a loop,
430
        // dispatching each request to a new task?)
431
        #[allow(clippy::disallowed_methods)]
432
        let (begin_dir_tx, begin_dir_rx) = mpsc::channel::<tor_proto::Result<DataStream>>(4096);
433

            
434
        // Spawn a directory mirror server task, if we are a dir cache.
435
        task_handles.spawn(async {
436
            // TODO DIRMIRROR: it would be nicer if serve() returned
437
            // Result<Void, _> to statically prove that it indeed never
438
            // returns with a non-error.
439
            // Plus, if we do that, we can simplify this invocation,
440
            // because we won't need the anyhow! error below.
441
            self.dir_mirror.serve(begin_dir_rx).await?;
442
            Err(anyhow::anyhow!("dir mirror exited"))
443
        });
444

            
445
        // TODO(relay): enable DoT
446
        //
447
        // TODO(relay): disable hickory's internal cache
448
        // (this will involve patching hickory to make its moka dependency optional)
449
        //
450
        // Note: the hickory resolver spawns various background tasks
451

            
452
        let (reactor, resolver) =
453
            DnsResolverReactor::new(DynTimeProvider::new(self.runtime.clone()), self.resolver);
454
        // Spawn the DNS reactor
455
        //
456
        // TODO(relay): only spawn this if we're configured to run as an exit
457
        task_handles.spawn(async move {
458
            reactor.run().await?;
459
            Err(anyhow::anyhow!("DNS reactor exited"))
460
        });
461

            
462
        let runtime = self.runtime.clone();
463
        // Listen for new Tor streams
464
        task_handles.spawn(
465
            // TODO: Should we give all tasks a `start` method?
466
            crate::stream::handle_incoming_streams(
467
                runtime,
468
                begin_dir_tx,
469
                self.circuit_stream_rx,
470
                resolver,
471
            ),
472
        );
473

            
474
        // Channel used to ask the descriptor publisher to rebuild and re-publish the descriptor.
475
        let (desc_command_tx, desc_command_rx) = crate::tasks::descriptor::new_command_channel();
476
        let (crypto_command_tx, crypto_command_rx) = crate::tasks::crypto::new_command_channel();
477

            
478
        // Start the crypto task.
479
        task_handles.spawn({
480
            let reactor = crate::tasks::crypto::Reactor::new(
481
                self.runtime.clone(),
482
                self.chanmgr.clone(),
483
                self.create_request_handler.clone(),
484
                self.keymgr,
485
                self.client.dirmgr().clone(),
486
                desc_command_tx,
487
                crypto_command_rx,
488
            )?;
489
            async {
490
                reactor
491
                    .run()
492
                    .await
493
                    .context("Failed to run key rotation task")
494
            }
495
        });
496

            
497
        // Build and publish the relay's own descriptor.
498
        task_handles.spawn({
499
            let netdir = Arc::clone(self.client.dirmgr()) as Arc<_>;
500
            let authorities = self.authorities;
501
            async move {
502
                crate::tasks::RelayDescriptorPublisherTask::new(
503
                    &self.runtime,
504
                    netdir,
505
                    authorities,
506
                    crypto_command_tx,
507
                    desc_command_rx,
508
                )
509
                .context("Failed to create descriptor publisher task")?
510
                .start()
511
                .await
512
                .context("Failed to run descriptor publisher task")
513
            }
514
        });
515

            
516
        // Launch client tasks.
517
        //
518
        // We need to hold on to these handles until the relay stops, otherwise dropping these
519
        // handles would stop the background tasks.
520
        //
521
        // These are `tor_rtcompat::scheduler::TaskHandle`s, which don't notify us if they
522
        // stop/crash.
523
        //
524
        // TODO: Whose responsibility is it to ensure that these background tasks don't crash?
525
        // Should we have a way of monitoring these tasks? Or should the circuit manager re-launch
526
        // crashed tasks?
527
        let _client_task_handles = self.client.launch_background_tasks();
528

            
529
        // TODO: More tasks will be spawned here.
530

            
531
        // Now that background tasks are started, bootstrap the client.
532
        self.client
533
            .bootstrap()
534
            .await
535
            .context("Failed to bootstrap the relay's client")?;
536

            
537
        // We block until facism is erradicated or a task ends which means the relay will shutdown
538
        // and facism will have one more chance.
539
        let void = task_handles
540
            .join_next()
541
            .await
542
            .context("Relay task set is empty")?
543
            .context("Relay task join failed")?
544
            .context("Relay task stopped unexpectedly")?;
545

            
546
        // We can never get here since a `Void` cannot be constructed.
547
        void::unreachable(void);
548
    }
549
}
550

            
551
/// Build the hickory resolver, configuring it using
552
/// the settings from our [`DnsConfig`].
553
fn build_hickory_resolver(
554
    config: &TorRelayConfig,
555
) -> anyhow::Result<Resolver<TokioRuntimeProvider>> {
556
    let mut builder = prepare_hickory_builder(config)?;
557

            
558
    // Set the hickory's internal cache size to 0
559
    // (we will be implementing our own cache separately)
560
    builder.options_mut().cache_size = 0;
561

            
562
    // Unconditionally enable case randomization,
563
    // equivalent to C Tor's ServerDNSRandomizeCase.
564
    builder.options_mut().case_randomization = true;
565

            
566
    Ok(builder.build()?)
567
}
568

            
569
/// Prepare the hickory resolver builder, using the settings from
570
/// our [`DnsConfig`].
571
#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))]
572
fn prepare_hickory_builder(
573
    config: &TorRelayConfig,
574
) -> anyhow::Result<ResolverBuilder<TokioRuntimeProvider>> {
575
    use hickory_resolver::system_conf::parse_resolv_conf;
576
    let DnsConfig { resolv_conf } = &config.relay.dns;
577
    let builder = if let Some(resolv_conf) = resolv_conf {
578
        let mistrust = config.storage.permissions();
579
        let resolv_conf = mistrust.verifier().file_access().read(resolv_conf)?;
580
        let (config, opts) = parse_resolv_conf(resolv_conf)?;
581

            
582
        let rt_provider = TokioRuntimeProvider::default();
583
        let mut builder = Resolver::builder_with_config(config, rt_provider);
584
        // Setting the options like this is a bit funky,
585
        // but hickory doesn't have any other API for setting them
586
        *builder.options_mut() = opts;
587

            
588
        builder
589
    } else {
590
        Resolver::builder_tokio()?
591
    };
592

            
593
    Ok(builder)
594
}
595

            
596
/// Prepare the hickory resolver builder, using the settings from
597
/// our [`DnsConfig`](crate::config::DnsConfig).
598
///
599
// Note: currently this returns a default-initialized hickory resolver,
600
// because all of the DnsConfig options we have today are unavailable
601
// on these platforms.
602
#[cfg(not(all(unix, not(any(target_os = "android", target_vendor = "apple")))))]
603
fn prepare_hickory_builder(
604
    config: &TorRelayConfig,
605
) -> anyhow::Result<ResolverBuilder<TokioRuntimeProvider>> {
606
    // We can't use resolv_conf on this platform,
607
    // but we deconstruct DnsConfig here to make sure that
608
    // if we add new fields, this will fail to compile,
609
    // reminding us to handle them here
610
    let DnsConfig { resolv_conf: _ } = &config.relay.dns;
611
    Ok(Resolver::builder_tokio()?)
612
}