Skip to content

Commit

Permalink
feat(peerseeds/autoupdate): hickory-dns upgrade, default to system DN…
Browse files Browse the repository at this point in the history
…S settings
  • Loading branch information
sdbondi committed Oct 3, 2024
1 parent 54eb6d7 commit 1e7a4b7
Show file tree
Hide file tree
Showing 15 changed files with 576 additions and 5,391 deletions.
525 changes: 400 additions & 125 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions base_layer/chat_ffi/src/application_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use tari_chat_client::{
config::{ApplicationConfig, ChatClientConfig},
networking::Multiaddr,
};
use tari_common::configuration::{name_server::DEFAULT_DNS_NAME_SERVER, MultiaddrList, Network, StringList};
use tari_common::configuration::{MultiaddrList, Network, StringList};
use tari_p2p::{PeerSeedsConfig, TransportConfig, TransportType};

use crate::error::{InterfaceError, LibChatError};
Expand Down Expand Up @@ -199,7 +199,6 @@ pub unsafe extern "C" fn create_chat_config(
chat_client: chat_client_config,
peer_seeds: PeerSeedsConfig {
dns_seeds_use_dnssec: false,
dns_seeds_name_server: DEFAULT_DNS_NAME_SERVER.parse().unwrap(),
dns_seeds: StringList::from(vec![format!("seeds.{}.tari.com", network.as_key_str())]),
..PeerSeedsConfig::default()
},
Expand Down
15 changes: 10 additions & 5 deletions base_layer/p2p/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ pgp = { version = "0.10", optional = true }
prost = "0.11.9"
rand = "0.8"
reqwest = { version = "0.11", optional = true, default-features = false }
rustls = "0.20.2"
rustls = { version = "0.23.13", default-features = false, features = ["aws-lc-rs", "logging", "std", "tls12"] }
# Needed by rusttls
aws-lc-rs = "1.9.0"
semver = { version = "1.0.1", optional = true }
serde = "1.0.90"
thiserror = "1.0.26"
Expand All @@ -37,10 +39,9 @@ tokio-stream = { version = "0.1.9", default-features = false, features = [
"time",
] }
tower = "0.4.11"
trust-dns-client = { version = "=0.21.0-alpha.5", features = [
"dns-over-rustls",
] }
webpki = "0.22"
hickory-client = { version = "0.25.0-alpha.2", features = ["dns-over-rustls"] }
hickory-resolver = "0.25.0-alpha.2"
webpki-roots = "0.26.6"

[dev-dependencies]
tari_test_utils = { path = "../../infrastructure/test_utils" }
Expand All @@ -58,3 +59,7 @@ tari_common = { path = "../../common", features = [
[features]
test-mocks = []
auto-update = ["reqwest/default", "pgp", "semver"]

[package.metadata.cargo-machete]
# Ignored because rusttls requires it
ignored = ["aws-lc-rs"]
22 changes: 10 additions & 12 deletions base_layer/p2p/src/auto_update/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ impl Display for UpdateSpec {

#[cfg(test)]
mod test {
use trust_dns_client::{
use hickory_client::{
op::Query,
proto::{
rr::{rdata, Name, RData, RecordType},
Expand All @@ -218,14 +218,14 @@ mod test {

fn create_txt_record(contents: Vec<&str>) -> DnsResponse {
let resp_query = Query::query(Name::from_str("test.local.").unwrap(), RecordType::A);
let mut record = Record::new();
record
.set_record_type(RecordType::TXT)
.set_data(Some(RData::TXT(rdata::TXT::new(
contents.into_iter().map(ToString::to_string).collect(),
))));

mock::message(resp_query, vec![record], vec![], vec![]).into()

let origin = Name::parse("example.com.", None).unwrap();
let record = Record::from_rdata(
origin,
86300,
RData::TXT(rdata::TXT::new(contents.into_iter().map(ToString::to_string).collect())),
);
DnsResponse::from_message(mock::message(resp_query, vec![record], vec![], vec![])).unwrap()
}

mod update_spec {
Expand All @@ -244,15 +244,13 @@ mod test {
mod dns_software_update {
use std::time::Duration;

use tari_common::configuration::name_server::DEFAULT_DNS_NAME_SERVER;

use super::*;

impl AutoUpdateConfig {
fn get_test_defaults() -> Self {
Self {
override_from: None,
name_server: DEFAULT_DNS_NAME_SERVER.parse().unwrap(),
name_server: Default::default(),
update_uris: vec!["test.local".to_string()].into(),
use_dnssec: true,
download_base_url: "https://tari-binaries.s3.amazonaws.com/latest".to_string(),
Expand Down
1 change: 0 additions & 1 deletion base_layer/p2p/src/auto_update/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ use tari_common::{
SubConfigPath,
};
use tari_utilities::hex::Hex;
pub use trust_dns_client::rr::dnssec::TrustAnchor;

use crate::auto_update::{dns::UpdateSpec, signature::SignedMessageVerifier};

Expand Down
22 changes: 15 additions & 7 deletions base_layer/p2p/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ use std::{
use serde::{Deserialize, Serialize};
use tari_common::{
configuration::{
name_server::DEFAULT_DNS_NAME_SERVER,
serializers,
utils::{deserialize_string_or_struct, serialize_string},
MultiaddrList,
Expand Down Expand Up @@ -77,7 +76,7 @@ impl Default for PeerSeedsConfig {
Network::get_current_or_user_setting_or_default().as_key_str()
)]
.into(),
dns_seeds_name_server: DEFAULT_DNS_NAME_SERVER.parse().unwrap(),
dns_seeds_name_server: DnsNameServer::default(),
dns_seeds_use_dnssec: false,
}
}
Expand Down Expand Up @@ -181,6 +180,8 @@ impl P2pConfig {

#[cfg(test)]
mod test {
use tari_common::DnsNameServer;

use crate::PeerSeedsConfig;

#[test]
Expand Down Expand Up @@ -215,7 +216,7 @@ mod test {
Ok(_) => panic!("Should fail"),
Err(e) => assert_eq!(
e.to_string(),
"failed to parse DNS name server 'dns_name' for key `dns_seeds_name_server` at line 4 column 37"
"invalid socket address syntax for key `dns_seeds_name_server` at line 4 column 37"
),
}

Expand Down Expand Up @@ -245,10 +246,17 @@ mod test {
let config = toml::from_str::<PeerSeedsConfig>(config_str).unwrap();
assert_eq!(config.dns_seeds.into_vec(), Vec::<String>::new());
assert_eq!(config.peer_seeds.into_vec(), Vec::<String>::new());
assert_eq!(
config.dns_seeds_name_server.to_string(),
"1.1.1.1:853/cloudflare-dns.com".to_string()
);
assert!(matches!(config.dns_seeds_name_server, DnsNameServer::System));
assert!(!config.dns_seeds_use_dnssec);

// System
let config_str = r#"
#dns_seeds = []
#peer_seeds = []
dns_seeds_name_server = "system"
#dns_seeds_use_dnssec = false
"#;
let config = toml::from_str::<PeerSeedsConfig>(config_str).unwrap();
assert!(matches!(config.dns_seeds_name_server, DnsNameServer::System));
}
}
111 changes: 70 additions & 41 deletions base_layer/p2p/src/dns/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,50 @@
// 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.

use std::{sync::Arc, time::Duration};
use std::{net::SocketAddr, sync::Arc, time::Duration};

use futures::{future, FutureExt};
use rustls::{ClientConfig, RootCertStore};
use tari_common::DnsNameServer;
use tari_shutdown::Shutdown;
use tokio::task;
use trust_dns_client::{
#[cfg(test)]
use hickory_client::proto::error::ProtoError;
use hickory_client::{
client::{AsyncClient, AsyncDnssecClient, ClientHandle},
op::Query,
proto::{
error::ProtoError,
iocompat::AsyncIoTokioAsStd,
rr::dnssec::TrustAnchor,
rr::dnssec::{SigSigner, TrustAnchor},
rustls::tls_client_connect,
xfer::DnsResponse,
DnsHandle,
DnsMultiplexer,
},
rr::{dnssec::SigSigner, DNSClass, IntoName, RecordType},
serialize::binary::BinEncoder,
rr::{DNSClass, IntoName, RecordType},
serialize::binary::{BinEncodable, BinEncoder},
tcp::TcpClientStream,
};
use hickory_resolver::system_conf;
use rustls::{ClientConfig, RootCertStore};
use tari_common::DnsNameServer;
use tari_shutdown::Shutdown;
use tokio::task;

use super::DnsClientError;
#[cfg(test)]
use crate::dns::mock::{DefaultOnSend, MockClientHandle};
use crate::dns::roots;

const TIMEOUT: Duration = Duration::from_secs(5);

#[derive(Clone)]
pub enum DnsClient {
Secure(Client<AsyncDnssecClient>),
Normal(Client<AsyncClient>),
#[cfg(test)]
Mock(Client<MockClientHandle<DefaultOnSend, ProtoError>>),
Mock(Client<MockClientHandle<DefaultOnSend>>),
}

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

Expand Down Expand Up @@ -98,14 +103,9 @@ impl DnsClient {
.answers()
.iter()
.map(|answer| {
let data = if let Some(d) = answer.data() {
d
} else {
return Err(DnsClientError::NoRecordDataPresent);
};
let mut buf = Vec::new();
let mut decoder = BinEncoder::new(&mut buf);
data.emit(&mut decoder).unwrap();
answer.data().emit(&mut decoder).unwrap();
Ok(buf)
})
.collect::<Result<Vec<Vec<u8>>, DnsClientError>>()?
Expand All @@ -130,22 +130,25 @@ pub struct Client<C> {
}

impl Client<AsyncDnssecClient> {
pub async fn connect_secure(name_server: DnsNameServer, trust_anchor: TrustAnchor) -> Result<Self, DnsClientError> {
pub async fn connect_dnssec(name_server: DnsNameServer, trust_anchor: TrustAnchor) -> Result<Self, DnsClientError> {
let shutdown = Shutdown::new();
let timeout = Duration::from_secs(5);
let (socket_addr, dns_name) = socket_addr_and_dns_name(name_server)?;

let dns_name = dns_name.ok_or_else(|| DnsClientError::DnsNameRequiredForDnsSec)?;
let (stream, handle) = tls_client_connect::<AsyncIoTokioAsStd<tokio::net::TcpStream>>(
name_server.addr,
name_server.dns_name,
default_client_config(),
socket_addr,
dns_name,
default_tls_client_config(),
);

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

let (client, bg) = AsyncDnssecClient::builder(dns_muxer)
.trust_anchor(trust_anchor)
.build()
.await?;

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

Ok(Self {
inner: client,
Expand All @@ -158,15 +161,28 @@ impl Client<AsyncClient> {
pub async fn connect(name_server: DnsNameServer) -> Result<Self, DnsClientError> {
let shutdown = Shutdown::new();

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()));
let (socket_addr, dns_name) = socket_addr_and_dns_name(name_server)?;

let client = match dns_name {
Some(dns_name) => {
let (stream, handle) = tls_client_connect::<AsyncIoTokioAsStd<tokio::net::TcpStream>>(
socket_addr,
dns_name,
default_tls_client_config(),
);

let (client, bg) = AsyncClient::with_timeout(stream, handle, TIMEOUT, None).await?;
task::spawn(future::select(shutdown.to_signal(), bg.fuse()));
client
},
None => {
let (stream, handle) =
TcpClientStream::<AsyncIoTokioAsStd<tokio::net::TcpStream>>::new(([8, 8, 8, 8], 53).into());
let (client, bg) = AsyncClient::with_timeout(stream, handle, TIMEOUT, None).await?;
task::spawn(future::select(shutdown.to_signal(), bg.fuse()));
client
},
};

Ok(Self {
inner: client,
Expand All @@ -176,7 +192,7 @@ impl Client<AsyncClient> {
}

impl<C> Client<C>
where C: DnsHandle<Error = ProtoError>
where C: DnsHandle
{
pub async fn lookup(&mut self, query: Query) -> Result<DnsResponse, DnsClientError> {
let client_resp = self
Expand All @@ -188,25 +204,38 @@ where C: DnsHandle<Error = ProtoError>
}
}

fn default_client_config() -> Arc<ClientConfig> {
fn default_tls_client_config() -> Arc<ClientConfig> {
let mut root_store = RootCertStore::empty();
root_store.add_server_trust_anchors(roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(ta.subject, ta.spki, ta.name_constraints)
}));
root_store.extend(roots::TLS_SERVER_ROOTS.iter().cloned());

let client_config = ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth();

Arc::new(client_config)
}

fn socket_addr_and_dns_name(server: DnsNameServer) -> Result<(SocketAddr, Option<String>), DnsClientError> {
match server {
DnsNameServer::System => {
let (conf, _opts) = system_conf::read_system_conf()?;
let found = conf
.name_servers()
.iter()
.find(|ns| ns.tls_dns_name.is_some())
.or_else(|| conf.name_servers().first())
.ok_or_else(|| DnsClientError::SystemHasNoDnsServers)?;
Ok((found.socket_addr, found.tls_dns_name.clone()))
},
DnsNameServer::Custom { addr, dns_name } => Ok((addr, dns_name)),
}
}

#[cfg(test)]
mod mock {
use super::*;

impl Client<MockClientHandle<DefaultOnSend, ProtoError>> {
impl Client<MockClientHandle<DefaultOnSend>> {
pub async fn connect_mock(messages: Vec<Result<DnsResponse, ProtoError>>) -> Result<Self, ProtoError> {
let client = MockClientHandle::mock(messages);
Ok(Self {
Expand Down
15 changes: 12 additions & 3 deletions base_layer/p2p/src/dns/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
// 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.

use trust_dns_client::{error::ClientError, proto::error::ProtoError};
use std::io;

use hickory_client::{error::ClientError, proto::error::ProtoError};
use hickory_resolver::error::ResolveError;

#[derive(Debug, thiserror::Error)]
pub enum DnsClientError {
Expand All @@ -32,6 +35,12 @@ pub enum DnsClientError {
Timeout,
#[error("Failed to parse name server string")]
NameServerParseFailed,
#[error("No record data present")]
NoRecordDataPresent,
#[error("Resolve error: {0}")]
ResolveError(#[from] ResolveError),
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("System config has no DNS servers configured")]
SystemHasNoDnsServers,
#[error("DNS server name was not provided for DNSSEC connection e.g.1.1.1.1:853/cloudflare-dns.com")]
DnsNameRequiredForDnsSec,
}
Loading

0 comments on commit 1e7a4b7

Please sign in to comment.