Skip to main content

hashiverse_lib/transport/bootstrap_provider/
dnssec_bootstrap_provider.rs

1//! # DNSSEC-validated bootstrap provider
2//!
3//! Resolves the domains in [`crate::tools::config::BOOTSTRAP_DOMAINS`] via a
4//! `hickory-resolver` configured to require DNSSEC authentication. Successfully-resolved
5//! addresses are returned as the seed peer list.
6//!
7//! DNSSEC matters here because bootstrapping is the one moment a client hasn't yet
8//! authenticated anyone on the network — a hijacked A record from an unauthenticated
9//! resolver could redirect every new node to an attacker-controlled sybil cluster.
10//! Requiring DNSSEC-validated lookups pushes that attack surface back to the root
11//! trust anchor.
12
13use crate::tools::{config, tools};
14use crate::transport::bootstrap_provider::bootstrap_provider::BootstrapProvider;
15use hickory_resolver::{
16    config::{LookupIpStrategy, ResolverConfig},
17    name_server::TokioConnectionProvider,
18    Resolver, TokioResolver,
19};
20use log::warn;
21use std::collections::HashSet;
22
23pub struct DnssecBootstrapProvider {
24    resolvers: Vec<TokioResolver>,
25}
26
27impl Default for DnssecBootstrapProvider {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl DnssecBootstrapProvider {
34    pub fn new() -> Self {
35        let resolver_configs = [
36            ResolverConfig::cloudflare_https(),
37            ResolverConfig::google_https(),
38            // ResolverConfig::quad9_https(),   // seems to be too busy - many errors / timeouts
39        ];
40
41        let resolvers = resolver_configs
42            .into_iter()
43            .map(|resolver_config| {
44                let mut builder = Resolver::builder_with_config(resolver_config, TokioConnectionProvider::default());
45                builder.options_mut().validate = true;
46                builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
47                builder.build()
48            })
49            .collect();
50
51        Self { resolvers }
52    }
53}
54
55#[async_trait::async_trait]
56impl BootstrapProvider for DnssecBootstrapProvider {
57    async fn get_bootstrap_addresses(&self) -> Vec<String> {
58        let mut addresses = HashSet::new();
59        for domain in config::BOOTSTRAP_DOMAINS {
60            for resolver in &self.resolvers {
61                match resolver.lookup_ip(*domain).await {
62                    Ok(response) => {
63                        for ip in response.iter() {
64                            addresses.insert(format!("{}:443", ip));
65                        }
66                    }
67                    Err(e) => {
68                        warn!("DNSSEC bootstrap lookup failed for {}: {}", domain, e);
69                    }
70                }
71            }
72        }
73        let mut addresses: Vec<String> = addresses.drain().collect();
74        tools::shuffle(&mut addresses);
75        addresses
76    }
77}