Skip to content

Commit

Permalink
fix: use tcp tls backend for peer seed DNS resolution (#3544)
Browse files Browse the repository at this point in the history
Description
---
- Use TCP TLS backend for DNS
- Add TLS root certs from `webpki_roots`
- Don't crash node if DNS fails to resolve
- Update config

Motivation and Context
---
UDP stream does not work with seeds.weatherwax.tari.com

Dependencies:
`tari_p2p`:
- upgrade `trust-dns-client` to version "0.21.0-alpha.4"
- add `rustls = "0.19.1"` used for TLS support
- add `webpki = "0.21"` for root cert container struct used by trust-dns-client

How Has This Been Tested?
---
Existing tests
Manually on base node
  • Loading branch information
sdbondi committed Nov 8, 2021
1 parent 911b83b commit 5b38909
Show file tree
Hide file tree
Showing 24 changed files with 5,335 additions and 353 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions applications/tari_base_node/src/bootstrap.rs
Expand Up @@ -122,7 +122,7 @@ where B: BlockchainBackend + 'static
.parse()
.expect("Unable to parse application version. Not valid semver"),
AutoUpdateConfig {
name_server: config.dns_seeds_name_server,
name_server: config.dns_seeds_name_server.clone(),
update_uris: config.autoupdate_dns_hosts.clone(),
use_dnssec: config.dns_seeds_use_dnssec,
download_base_url: "https://tari-binaries.s3.amazonaws.com/latest".to_string(),
Expand Down Expand Up @@ -271,7 +271,7 @@ where B: BlockchainBackend + 'static
.chain(self.config.force_sync_peers.clone())
.collect(),
dns_seeds: self.config.dns_seeds.clone(),
dns_seeds_name_server: self.config.dns_seeds_name_server,
dns_seeds_name_server: self.config.dns_seeds_name_server.clone(),
dns_seeds_use_dnssec: self.config.dns_seeds_use_dnssec,
}
}
Expand Down
2 changes: 1 addition & 1 deletion applications/tari_console_wallet/src/init/mod.rs
Expand Up @@ -361,7 +361,7 @@ pub async fn init_wallet(
);

let updater_config = AutoUpdateConfig {
name_server: config.dns_seeds_name_server,
name_server: config.dns_seeds_name_server.clone(),
update_uris: config.autoupdate_dns_hosts.clone(),
use_dnssec: config.dns_seeds_use_dnssec,
download_base_url: "https://tari-binaries.s3.amazonaws.com/latest".to_string(),
Expand Down
4 changes: 3 additions & 1 deletion base_layer/p2p/Cargo.toml
Expand Up @@ -38,7 +38,9 @@ tokio = { version = "1.11", features = ["macros"] }
tokio-stream = { version = "0.1.7", default-features = false, features = ["time"] }
tower = "0.3.0-alpha.2"
tower-service = { version = "0.3.0-alpha.2" }
trust-dns-client = { version = "0.21.0-alpha.2", features = ["dns-over-rustls"] }
trust-dns-client = { version = "0.21.0-alpha.4", features = ["dns-over-rustls"] }
rustls = "0.19.1"
webpki = "0.21"

[dev-dependencies]
tari_test_utils = { version = "^0.13", path = "../../infrastructure/test_utils" }
Expand Down
5 changes: 3 additions & 2 deletions base_layer/p2p/src/auto_update/dns.rs
Expand Up @@ -42,10 +42,11 @@ pub struct DnsSoftwareUpdate {
impl DnsSoftwareUpdate {
/// Connect to DNS host according to the given config
pub async fn connect(config: AutoUpdateConfig) -> Result<Self, AutoUpdateError> {
let name_server = config.name_server.clone();
let client = if config.use_dnssec {
DnsClient::connect_secure(config.name_server, default_trust_anchor()).await?
DnsClient::connect_secure(name_server, default_trust_anchor()).await?
} else {
DnsClient::connect(config.name_server).await?
DnsClient::connect(name_server).await?
};

Ok(Self { client, config })
Expand Down
5 changes: 2 additions & 3 deletions base_layer/p2p/src/auto_update/mod.rs
Expand Up @@ -41,16 +41,15 @@ use std::{
fmt,
fmt::{Display, Formatter},
io,
net::SocketAddr,
};
use tari_common::configuration::bootstrap::ApplicationType;
use tari_common::{configuration::bootstrap::ApplicationType, DnsNameServer};
use tari_utilities::hex::Hex;

const LOG_TARGET: &str = "p2p::auto_update";

#[derive(Debug, Clone)]
pub struct AutoUpdateConfig {
pub name_server: SocketAddr,
pub name_server: DnsNameServer,
pub update_uris: Vec<String>,
pub use_dnssec: bool,
pub download_base_url: String,
Expand Down
67 changes: 55 additions & 12 deletions base_layer/p2p/src/dns/client.rs
Expand Up @@ -24,15 +24,26 @@
use crate::dns::mock::{DefaultOnSend, MockClientHandle};

use super::DnsClientError;
use crate::dns::roots;
use futures::{future, FutureExt};
use std::{net::SocketAddr, sync::Arc};
use rustls::{ClientConfig, ProtocolVersion, RootCertStore};
use std::{sync::Arc, time::Duration};
use tari_common::DnsNameServer;
use tari_shutdown::Shutdown;
use tokio::{net::UdpSocket, task};
use tokio::task;
use trust_dns_client::{
client::{AsyncClient, AsyncDnssecClient, ClientHandle},
op::Query,
proto::{error::ProtoError, rr::dnssec::TrustAnchor, udp::UdpClientStream, xfer::DnsResponse, DnsHandle},
rr::{DNSClass, IntoName, RecordType},
proto::{
error::ProtoError,
iocompat::AsyncIoTokioAsStd,
rr::dnssec::TrustAnchor,
rustls::tls_client_connect,
xfer::DnsResponse,
DnsHandle,
DnsMultiplexer,
},
rr::{dnssec::SigSigner, DNSClass, IntoName, RecordType},
serialize::binary::BinEncoder,
};

Expand All @@ -45,12 +56,12 @@ pub enum DnsClient {
}

impl DnsClient {
pub async fn connect_secure(name_server: SocketAddr, trust_anchor: TrustAnchor) -> Result<Self, DnsClientError> {
pub async fn connect_secure(name_server: DnsNameServer, trust_anchor: TrustAnchor) -> Result<Self, DnsClientError> {
let client = Client::connect_secure(name_server, trust_anchor).await?;
Ok(DnsClient::Secure(client))
}

pub async fn connect(name_server: SocketAddr) -> Result<Self, DnsClientError> {
pub async fn connect(name_server: DnsNameServer) -> Result<Self, DnsClientError> {
let client = Client::connect(name_server).await?;
Ok(DnsClient::Normal(client))
}
Expand Down Expand Up @@ -110,13 +121,23 @@ pub struct Client<C> {
}

impl Client<AsyncDnssecClient> {
pub async fn connect_secure(name_server: SocketAddr, trust_anchor: TrustAnchor) -> Result<Self, DnsClientError> {
pub async fn connect_secure(name_server: DnsNameServer, trust_anchor: TrustAnchor) -> Result<Self, DnsClientError> {
let shutdown = Shutdown::new();
let stream = UdpClientStream::<UdpSocket>::new(name_server);
let (client, background) = AsyncDnssecClient::builder(stream)

// TODO: make configurable
let timeout = Duration::from_secs(5);
let (stream, handle) = tls_client_connect::<AsyncIoTokioAsStd<tokio::net::TcpStream>>(
name_server.addr,
name_server.dns_name,
default_client_config(),
);

let dns_muxer = DnsMultiplexer::<_, SigSigner>::with_timeout(stream, handle, timeout, None);
let (client, background) = AsyncDnssecClient::builder(dns_muxer)
.trust_anchor(trust_anchor)
.build()
.await?;

task::spawn(future::select(shutdown.to_signal(), background.fuse()));

Ok(Self {
Expand All @@ -127,10 +148,18 @@ impl Client<AsyncDnssecClient> {
}

impl Client<AsyncClient> {
pub async fn connect(name_server: SocketAddr) -> Result<Self, DnsClientError> {
pub async fn connect(name_server: DnsNameServer) -> Result<Self, DnsClientError> {
let shutdown = Shutdown::new();
let stream = UdpClientStream::<UdpSocket>::new(name_server);
let (client, background) = AsyncClient::connect(stream).await?;

// TODO: make configurable
let timeout = Duration::from_secs(5);
let (stream, handle) = tls_client_connect::<AsyncIoTokioAsStd<tokio::net::TcpStream>>(
name_server.addr,
name_server.dns_name,
default_client_config(),
);

let (client, background) = AsyncClient::with_timeout(stream, handle, timeout, None).await?;
task::spawn(future::select(shutdown.to_signal(), background.fuse()));

Ok(Self {
Expand All @@ -148,10 +177,24 @@ where C: DnsHandle<Error = ProtoError>
.inner
.query(query.name().clone(), query.query_class(), query.query_type())
.await?;

Ok(client_resp)
}
}

fn default_client_config() -> Arc<ClientConfig> {
let mut root_store = RootCertStore::empty();
root_store.add_server_trust_anchors(&roots::TLS_SERVER_ROOTS);
let versions = vec![ProtocolVersion::TLSv1_2];

let mut client_config = ClientConfig::new();
client_config.root_store = root_store;
client_config.versions = versions;
client_config.alpn_protocols.push("h2".as_bytes().to_vec());

Arc::new(client_config)
}

#[cfg(test)]
mod mock {
use super::*;
Expand Down
4 changes: 4 additions & 0 deletions base_layer/p2p/src/dns/error.rs
Expand Up @@ -28,4 +28,8 @@ pub enum DnsClientError {
ClientError(#[from] ClientError),
#[error("DNS Protocol error: {0}")]
ProtoError(#[from] ProtoError),
#[error("DNS timeout error")]
Timeout,
#[error("Failed to parse name server string")]
NameServerParseFailed,
}
18 changes: 3 additions & 15 deletions base_layer/p2p/src/dns/mod.rs
Expand Up @@ -4,20 +4,8 @@ pub use client::DnsClient;
mod error;
pub use error::DnsClientError;

mod roots;
pub(crate) use roots::default_trust_anchor;

#[cfg(test)]
pub(crate) mod mock;

use trust_dns_client::proto::rr::dnssec::{public_key::Rsa, TrustAnchor};

#[inline]
pub(crate) fn default_trust_anchor() -> TrustAnchor {
// This was copied from the trust-dns crate.
const ROOT_ANCHOR_ORIG: &[u8] = include_bytes!("roots/19036.rsa");
// This was generated from the `.` root domain in 10/2020.
const ROOT_ANCHOR_CURRENT: &[u8] = include_bytes!("roots/20326.rsa");

let mut anchor = TrustAnchor::new();
anchor.insert_trust_anchor(&Rsa::from_public_bytes(ROOT_ANCHOR_ORIG).expect("Invalid ROOT_ANCHOR_ORIG"));
anchor.insert_trust_anchor(&Rsa::from_public_bytes(ROOT_ANCHOR_CURRENT).expect("Invalid ROOT_ANCHOR_CURRENT"));
anchor
}
39 changes: 39 additions & 0 deletions base_layer/p2p/src/dns/roots/mod.rs
@@ -0,0 +1,39 @@
// Copyright 2021, The Tari Project
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
// disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
// products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

mod tls;
pub(super) use tls::TLS_SERVER_ROOTS;

use trust_dns_client::proto::rr::dnssec::{public_key::Rsa, TrustAnchor};

#[inline]
pub fn default_trust_anchor() -> TrustAnchor {
// This was copied from the trust-dns crate.
const ROOT_ANCHOR_ORIG: &[u8] = include_bytes!("19036.rsa");
// This was generated from the `.` root domain in 10/2020.
const ROOT_ANCHOR_CURRENT: &[u8] = include_bytes!("20326.rsa");

let mut anchor = TrustAnchor::new();
anchor.insert_trust_anchor(&Rsa::from_public_bytes(ROOT_ANCHOR_ORIG).expect("Invalid ROOT_ANCHOR_ORIG"));
anchor.insert_trust_anchor(&Rsa::from_public_bytes(ROOT_ANCHOR_CURRENT).expect("Invalid ROOT_ANCHOR_CURRENT"));
anchor
}

0 comments on commit 5b38909

Please sign in to comment.