From 221512c7330554fb7950f56cdbbfb81814a2306b Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 2 Feb 2021 23:20:26 -0300 Subject: [PATCH] Async DNS seeder lookups (#1662) * replace to_socket_addrs * refactor `resolve()` into `resolve_host()` * use `resolve_host()` to resolve config peers * add DNS_LOOKUP_TIMEOUT constant * don't block the main thread in initialize --- zebra-network/src/config.rs | 59 ++++++++++++++++++------ zebra-network/src/constants.rs | 9 ++++ zebra-network/src/peer_set/initialize.rs | 15 +++--- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index 3bcab18d..56a4293a 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -1,9 +1,4 @@ -use std::{ - collections::HashSet, - net::{SocketAddr, ToSocketAddrs}, - string::String, - time::Duration, -}; +use std::{collections::HashSet, net::SocketAddr, string::String, time::Duration}; use zebra_chain::parameters::Network; @@ -37,19 +32,55 @@ pub struct Config { } impl Config { - fn parse_peers(peers: HashSet) -> HashSet { - peers + /// Concurrently resolves `peers` into zero or more IP addresses, with a timeout + /// of a few seconds on each DNS request. + /// + /// If DNS resolution fails or times out for all peers, returns an empty list. + async fn parse_peers(peers: &HashSet) -> HashSet { + use futures::stream::StreamExt; + let peer_addresses = peers .iter() - .flat_map(|s| s.to_socket_addrs()) - .flatten() - .collect() + .map(|s| Config::resolve_host(s)) + .collect::>() + .concat() + .await; + + if peer_addresses.is_empty() { + tracing::warn!( + ?peers, + ?peer_addresses, + "empty peer list after DNS resolution" + ); + }; + peer_addresses } /// Get the initial seed peers based on the configured network. - pub fn initial_peers(&self) -> HashSet { + pub async fn initial_peers(&self) -> HashSet { match self.network { - Network::Mainnet => Config::parse_peers(self.initial_mainnet_peers.clone()), - Network::Testnet => Config::parse_peers(self.initial_testnet_peers.clone()), + Network::Mainnet => Config::parse_peers(&self.initial_mainnet_peers).await, + Network::Testnet => Config::parse_peers(&self.initial_testnet_peers).await, + } + } + + /// Resolves `host` into zero or more IP addresses. + /// + /// If `host` is a DNS name, performs DNS resolution with a timeout of a few seconds. + /// If DNS resolution fails or times out, returns an empty list. + async fn resolve_host(host: &str) -> HashSet { + let fut = tokio::net::lookup_host(host); + let fut = tokio::time::timeout(crate::constants::DNS_LOOKUP_TIMEOUT, fut); + + match fut.await { + Ok(Ok(ips)) => ips.collect(), + Ok(Err(e)) => { + tracing::info!(?host, ?e, "DNS error resolving peer IP address"); + HashSet::new() + } + Err(e) => { + tracing::info!(?host, ?e, "DNS timeout resolving peer IP address"); + HashSet::new() + } } } } diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs index 0dc36603..3fbeac2e 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -108,6 +108,15 @@ lazy_static! { }.expect("regex is valid"); } +/// The timeout for DNS lookups. +/// +/// [6.1.3.3 Efficient Resource Usage] from [RFC 1123: Requirements for Internet Hosts] +/// suggest no less than 5 seconds for resolving timeout. +/// +/// [RFC 1123: Requirements for Internet Hosts] https://tools.ietf.org/rfcmarkup?doc=1123 +/// [6.1.3.3 Efficient Resource Usage] https://tools.ietf.org/rfcmarkup?doc=1123#page-77 +pub const DNS_LOOKUP_TIMEOUT: Duration = Duration::from_secs(5); + /// Magic numbers used to identify different Zcash networks. pub mod magics { use super::*; diff --git a/zebra-network/src/peer_set/initialize.rs b/zebra-network/src/peer_set/initialize.rs index e6ad0cf0..0d5e8afa 100644 --- a/zebra-network/src/peer_set/initialize.rs +++ b/zebra-network/src/peer_set/initialize.rs @@ -135,16 +135,19 @@ where let listen_guard = tokio::spawn(listen(config.listen_addr, listener, peerset_tx.clone())); + // 2. Initial peers, specified in the config. let initial_peers_fut = { - let initial_peers = config.initial_peers(); + let config = config.clone(); let connector = connector.clone(); - let tx = peerset_tx.clone(); - - // Connect the tx end to the 3 peer sources: - add_initial_peers(initial_peers, connector, tx) + let peerset_tx = peerset_tx.clone(); + async move { + let initial_peers = config.initial_peers().await; + // Connect the tx end to the 3 peer sources: + add_initial_peers(initial_peers, connector, peerset_tx).await + } + .boxed() }; - // 2. Initial peers, specified in the config. let add_guard = tokio::spawn(initial_peers_fut); // 3. Outgoing peers we connect to in response to load.