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
This commit is contained in:
Alfredo Garcia 2021-02-02 23:20:26 -03:00 committed by GitHub
parent 6679a124e3
commit 221512c733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 63 additions and 20 deletions

View File

@ -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<S: ToSocketAddrs>(peers: HashSet<S>) -> HashSet<SocketAddr> {
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<String>) -> HashSet<SocketAddr> {
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::<futures::stream::FuturesUnordered<_>>()
.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<SocketAddr> {
pub async fn initial_peers(&self) -> HashSet<SocketAddr> {
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<SocketAddr> {
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()
}
}
}
}

View File

@ -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::*;

View File

@ -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.