diff --git a/Cargo.lock b/Cargo.lock index b67001df26..edcc430d88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1480,6 +1480,7 @@ dependencies = [ "juniper 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "multiaddr 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "network-core 0.1.0-dev", "network-grpc 0.1.0-dev", diff --git a/jormungandr/Cargo.toml b/jormungandr/Cargo.toml index 8008335fa5..3c83a05273 100644 --- a/jormungandr/Cargo.toml +++ b/jormungandr/Cargo.toml @@ -36,6 +36,7 @@ hyper = "0.12" jormungandr-lib = { path = "../jormungandr-lib" } lazy_static = "1.3" linked-hash-map = "0.5" +multiaddr = "0.3" native-tls = "0.2.2" network-core = { path = "../chain-deps/network-core" } network-grpc = { path = "../chain-deps/network-grpc" } diff --git a/jormungandr/src/network/mod.rs b/jormungandr/src/network/mod.rs index c5a2f9db37..2268b2337d 100644 --- a/jormungandr/src/network/mod.rs +++ b/jormungandr/src/network/mod.rs @@ -137,7 +137,7 @@ impl GlobalState { .map(|tp| { let mut builder = poldercast::NodeProfileBuilder::new(); builder.id(tp.id.into()); - builder.address(tp.address.into()); + builder.address(tp.address); builder.build() }) .map(p2p::Gossip::from) @@ -432,7 +432,8 @@ fn trusted_peers_shuffled(config: &Configuration) -> Vec { let mut peers = config .trusted_peers .iter() - .filter_map(|peer| peer.address.to_socketaddr()) + .map(|peer| peer.address.to_socketaddr().into_iter()) + .flatten() .collect::>(); let mut rng = rand::thread_rng(); peers.shuffle(&mut rng); diff --git a/jormungandr/src/settings/command_arguments.rs b/jormungandr/src/settings/command_arguments.rs index 7cd4be84a9..d1a6a620dc 100644 --- a/jormungandr/src/settings/command_arguments.rs +++ b/jormungandr/src/settings/command_arguments.rs @@ -1,4 +1,4 @@ -use crate::settings::{start::config::TrustedPeer, LOG_FILTER_LEVEL_POSSIBLE_VALUES}; +use crate::settings::{start::trusted_peer::*, LOG_FILTER_LEVEL_POSSIBLE_VALUES}; use slog::FilterLevel; use std::net::SocketAddr; use std::path::PathBuf; diff --git a/jormungandr/src/settings/start/config.rs b/jormungandr/src/settings/start/config.rs index a812585336..0518243b48 100644 --- a/jormungandr/src/settings/start/config.rs +++ b/jormungandr/src/settings/start/config.rs @@ -1,7 +1,10 @@ use crate::{ network::p2p::{topic, Id, PolicyConfig}, - settings::logging::{LogFormat, LogOutput}, - settings::LOG_FILTER_LEVEL_POSSIBLE_VALUES, + settings::{ + logging::{LogFormat, LogOutput}, + start::trusted_peer::*, + LOG_FILTER_LEVEL_POSSIBLE_VALUES, + }, }; use jormungandr_lib::{interfaces::Mempool, time::Duration}; use poldercast; @@ -110,13 +113,6 @@ pub struct P2pConfig { pub max_unreachable_nodes_to_connect_per_event: Option, } -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct TrustedPeer { - pub address: Address, - pub id: Id, -} - #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct Leadership { @@ -182,36 +178,19 @@ impl Default for Leadership { } } -impl std::str::FromStr for TrustedPeer { - type Err = String; - fn from_str(s: &str) -> Result { - let mut split = s.split('@'); - - let address = if let Some(address) = split.next() { - address - .parse::() - .map(Address) - .map_err(|e| e.to_string())? - } else { - return Err("Missing address component".to_owned()); - }; - - let id = if let Some(id) = split.next() { - id.parse::().map_err(|e| e.to_string())? - } else { - return Err("Missing id component".to_owned()); - }; - - Ok(TrustedPeer { address, id }) - } -} - impl Address { pub fn to_socketaddr(&self) -> Option { self.0.to_socketaddr() } } +custom_error! {pub AddressError + DnsLookupError { source: std::io::Error } = "failed to resolve DNS name {source}", + NoPortSpecified = "no TCP port specified", + NoAppropriateDNSFound = "the address was resolved, but it doesn't provide IPv4 or IPv6 addresses", + UnsupportedProtocol = "the provided protocol is unsupported, please use one of ip4/ip6/dns4/dns6", +} + impl std::fmt::Display for Address { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { self.0.fmt(f) @@ -226,6 +205,7 @@ impl Serialize for Address { serializer.serialize_str(&format!("{}", self.0)) } } + impl Serialize for Topic { fn serialize(&self, serializer: S) -> Result where diff --git a/jormungandr/src/settings/start/mod.rs b/jormungandr/src/settings/start/mod.rs index 80b4160562..ce52cf3c6a 100644 --- a/jormungandr/src/settings/start/mod.rs +++ b/jormungandr/src/settings/start/mod.rs @@ -1,5 +1,6 @@ pub mod config; pub mod network; +pub mod trusted_peer; use self::config::{Config, Leadership}; pub use self::config::{Cors, Rest}; @@ -181,6 +182,8 @@ fn generate_network( command_arguments: &StartArguments, config: &Option, ) -> Result { + use crate::settings::start::network::TrustedPeer; + let mut p2p = if let Some(cfg) = config { cfg.p2p.clone() } else { @@ -233,7 +236,15 @@ fn generate_network( .clone() .unwrap_or(vec![]) .into_iter() - .map(Into::into) + .filter_map(|tp| { + tp.address.to_addresses().ok().map(|addrs| { + addrs.into_iter().map(move |addr| TrustedPeer { + id: tp.id.clone(), + address: addr.0, + }) + }) + }) + .flatten() .collect(), protocol: Protocol::Grpc, policy: p2p.policy.clone(), diff --git a/jormungandr/src/settings/start/network.rs b/jormungandr/src/settings/start/network.rs index 7f0f27c91b..0c4a205d61 100644 --- a/jormungandr/src/settings/start/network.rs +++ b/jormungandr/src/settings/start/network.rs @@ -76,15 +76,6 @@ pub struct TrustedPeer { pub id: Id, } -impl From for TrustedPeer { - fn from(tp: super::config::TrustedPeer) -> Self { - TrustedPeer { - address: tp.address.0, - id: tp.id, - } - } -} - impl Peer { pub fn new(connection: SocketAddr, protocol: Protocol) -> Self { Peer { diff --git a/jormungandr/src/settings/start/trusted_peer.rs b/jormungandr/src/settings/start/trusted_peer.rs new file mode 100644 index 0000000000..32221d5241 --- /dev/null +++ b/jormungandr/src/settings/start/trusted_peer.rs @@ -0,0 +1,146 @@ +use crate::{network::p2p::Id, settings::start::config::Address}; +use multiaddr::AddrComponent; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + fmt, + iter::FromIterator, + net::{SocketAddr, ToSocketAddrs}, +}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TrustedPeer { + pub address: TrustedAddress, + pub id: Id, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TrustedAddress(pub multiaddr::Multiaddr); + +custom_error! {pub AddressError + DnsLookupError { source: std::io::Error } = "failed to resolve DNS name {source}", + NoPortSpecified = "no TCP port specified", + NoAppropriateDNSFound = "the address was resolved, but it doesn't provide IPv4 or IPv6 addresses", + UnsupportedProtocol = "the provided protocol is unsupported, please use one of ip4/ip6/dns4/dns6", +} + +impl TrustedAddress { + pub fn to_addresses(&self) -> Result, AddressError> { + let mut components = self.0.iter(); + let protocol = components.next(); + + if let Some(AddrComponent::IP4(_)) | Some(AddrComponent::IP6(_)) = protocol { + return Ok(vec![Address( + poldercast::Address::new(self.0.clone()).unwrap(), + )]); + } + + let port = match components.next() { + Some(AddrComponent::TCP(port)) => port, + _ => return Err(AddressError::NoPortSpecified), + }; + + let addresses: Vec = match protocol { + Some(AddrComponent::DNS4(fqdn)) => format!("{}:{}", fqdn, port) + .to_socket_addrs() + .map_err(|e| AddressError::DnsLookupError { source: e })? + .into_iter() + .filter_map(|r| match r { + SocketAddr::V4(addr) => Some(AddrComponent::IP4(*addr.ip())), + _ => None, + }) + .collect(), + Some(AddrComponent::DNS6(fqdn)) => format!("{}:{}", fqdn, port) + .to_socket_addrs() + .map_err(|e| AddressError::DnsLookupError { source: e })? + .into_iter() + .filter_map(|r| match r { + SocketAddr::V6(addr) => Some(AddrComponent::IP6(*addr.ip())), + _ => None, + }) + .collect(), + _ => return Err(AddressError::UnsupportedProtocol), + }; + + if addresses.is_empty() { + return Err(AddressError::NoAppropriateDNSFound); + } + + let addresses = addresses + .into_iter() + .map(|addr| { + let new_components = vec![addr, AddrComponent::TCP(port)]; + let new_multiaddr = multiaddr::Multiaddr::from_iter(new_components.into_iter()); + Address(poldercast::Address::new(new_multiaddr).unwrap()) + }) + .collect(); + + Ok(addresses) + } +} + +impl std::str::FromStr for TrustedPeer { + type Err = String; + fn from_str(s: &str) -> Result { + let mut split = s.split('@'); + + let address = if let Some(address) = split.next() { + multiaddr::Multiaddr::from_bytes(address.as_bytes().iter().cloned().collect()) + .map(TrustedAddress) + .map_err(|e| e.to_string())? + } else { + return Err("Missing address component".to_owned()); + }; + + let id = if let Some(id) = split.next() { + id.parse::().map_err(|e| e.to_string())? + } else { + return Err("Missing id component".to_owned()); + }; + + Ok(TrustedPeer { address, id }) + } +} + +impl Serialize for TrustedAddress { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("{}", self.0)) + } +} + +impl std::fmt::Display for TrustedAddress { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl<'de> Deserialize<'de> for TrustedAddress { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct TrustedAddressVisitor; + impl<'de> Visitor<'de> for TrustedAddressVisitor { + type Value = TrustedAddress; + + fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "Multiaddr (example: /ip4/192.168.0.1/tcp/443)") + } + + fn visit_str<'a, E>(self, v: &'a str) -> std::result::Result + where + E: serde::de::Error, + { + use serde::de::Unexpected; + match v.parse() { + Err(_err) => Err(E::invalid_value(Unexpected::Str(v), &self)), + Ok(addr) => Ok(TrustedAddress(addr)), + } + } + } + deserializer.deserialize_str(TrustedAddressVisitor) + } +}