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 DnssecBootstrapProvider {
28    pub fn new() -> Self {
29        let resolver_configs = [
30            ResolverConfig::cloudflare_https(),
31            ResolverConfig::google_https(),
32            // ResolverConfig::quad9_https(),   // seems to be too busy - many errors / timeouts
33        ];
34
35        let resolvers = resolver_configs
36            .into_iter()
37            .map(|resolver_config| {
38                let mut builder = Resolver::builder_with_config(resolver_config, TokioConnectionProvider::default());
39                builder.options_mut().validate = true;
40                builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only;
41                builder.build()
42            })
43            .collect();
44
45        Self { resolvers }
46    }
47}
48
49#[async_trait::async_trait]
50impl BootstrapProvider for DnssecBootstrapProvider {
51    async fn get_bootstrap_addresses(&self) -> Vec<String> {
52        let mut addresses = HashSet::new();
53        for domain in config::BOOTSTRAP_DOMAINS {
54            for resolver in &self.resolvers {
55                match resolver.lookup_ip(*domain).await {
56                    Ok(response) => {
57                        for ip in response.iter() {
58                            addresses.insert(format!("{}:443", ip));
59                        }
60                    }
61                    Err(e) => {
62                        warn!("DNSSEC bootstrap lookup failed for {}: {}", domain, e);
63                    }
64                }
65            }
66        }
67        let mut addresses: Vec<String> = addresses.drain().collect();
68        tools::shuffle(&mut addresses);
69        addresses
70    }
71}