feat: allow DNS names in peers/seeds list and resolve them (#3125)

* feat: allow DNS names in peers/seeds list and resolve them

* tests: add mod for peer tests

* refactor: rename some variables

* chore: use Serde desrialize to resolve DNS names into PeerAddrs

* fix: compile

* fix: add back code to remove duplicate ip addresses from resolved DNS seeds
This commit is contained in:
Joseph Goulden 2020-02-14 14:58:57 +00:00 committed by GitHub
parent 053415ddf8
commit 0d2e58e90e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 102 additions and 40 deletions

View file

@ -260,6 +260,7 @@ fn comments() -> HashMap<String, String> {
retval.insert( retval.insert(
"seeding_type".to_string(), "seeding_type".to_string(),
" "
#All seeds/peers can be either IP address or DNS names. Port number must always be specified
#how to seed this server, can be None, List or DNSSeed #how to seed this server, can be None, List or DNSSeed
" "
.to_string(), .to_string(),

View file

@ -466,7 +466,7 @@ impl Readable for GetPeerAddrs {
/// Peer addresses we know of that are fresh enough, in response to /// Peer addresses we know of that are fresh enough, in response to
/// GetPeerAddrs. /// GetPeerAddrs.
#[derive(Debug)] #[derive(Debug, Clone, Serialize, PartialEq)]
pub struct PeerAddrs { pub struct PeerAddrs {
pub peers: Vec<PeerAddr>, pub peers: Vec<PeerAddr>,
} }
@ -493,7 +493,7 @@ impl Readable for PeerAddrs {
for _ in 0..peer_count { for _ in 0..peer_count {
peers.push(PeerAddr::read(reader)?); peers.push(PeerAddr::read(reader)?);
} }
Ok(PeerAddrs { peers: peers }) Ok(PeerAddrs { peers })
} }
} }

View file

@ -152,7 +152,7 @@ impl Peer {
pub fn is_denied(config: &P2PConfig, peer_addr: PeerAddr) -> bool { pub fn is_denied(config: &P2PConfig, peer_addr: PeerAddr) -> bool {
if let Some(ref denied) = config.peers_deny { if let Some(ref denied) = config.peers_deny {
if denied.contains(&peer_addr) { if denied.peers.contains(&peer_addr) {
debug!( debug!(
"checking peer allowed/denied: {:?} explicitly denied", "checking peer allowed/denied: {:?} explicitly denied",
peer_addr peer_addr
@ -161,7 +161,7 @@ impl Peer {
} }
} }
if let Some(ref allowed) = config.peers_allow { if let Some(ref allowed) = config.peers_allow {
if allowed.contains(&peer_addr) { if allowed.peers.contains(&peer_addr) {
debug!( debug!(
"checking peer allowed/denied: {:?} explicitly allowed", "checking peer allowed/denied: {:?} explicitly allowed",
peer_addr peer_addr

View file

@ -12,15 +12,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::util::RwLock;
use std::convert::From; use std::convert::From;
use std::fmt;
use std::fs::File; use std::fs::File;
use std::io::{self, Read}; use std::io::{self, Read};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use chrono::prelude::*; use chrono::prelude::*;
use serde::de::{SeqAccess, Visitor};
use serde::{Deserialize, Deserializer};
use grin_store;
use crate::chain; use crate::chain;
use crate::core::core; use crate::core::core;
@ -28,7 +33,8 @@ use crate::core::core::hash::Hash;
use crate::core::global; use crate::core::global;
use crate::core::pow::Difficulty; use crate::core::pow::Difficulty;
use crate::core::ser::{self, ProtocolVersion, Readable, Reader, Writeable, Writer}; use crate::core::ser::{self, ProtocolVersion, Readable, Reader, Writeable, Writer};
use grin_store; use crate::msg::PeerAddrs;
use crate::util::RwLock;
/// Maximum number of block headers a peer should ever send /// Maximum number of block headers a peer should ever send
pub const MAX_BLOCK_HEADERS: u32 = 512; pub const MAX_BLOCK_HEADERS: u32 = 512;
@ -156,6 +162,45 @@ impl Readable for PeerAddr {
} }
} }
impl<'de> Visitor<'de> for PeerAddrs {
type Value = PeerAddrs;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an array of dns names or IP addresses")
}
fn visit_seq<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: SeqAccess<'de>,
{
let mut peers = Vec::with_capacity(access.size_hint().unwrap_or(0));
while let Some(entry) = access.next_element::<&str>()? {
match SocketAddr::from_str(entry) {
// Try to parse IP address first
Ok(ip) => peers.push(PeerAddr(ip)),
// If that fails it's probably a DNS record
Err(_) => {
let socket_addrs = entry
.to_socket_addrs()
.expect(format!("Unable to resolve DNS: {}", entry).as_str());
peers.append(&mut socket_addrs.map(|addr| PeerAddr(addr)).collect());
}
}
}
Ok(PeerAddrs { peers })
}
}
impl<'de> Deserialize<'de> for PeerAddrs {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(PeerAddrs { peers: vec![] })
}
}
impl std::hash::Hash for PeerAddr { impl std::hash::Hash for PeerAddr {
/// If loopback address then we care about ip and port. /// If loopback address then we care about ip and port.
/// If regular address then we only care about the ip and ignore the port. /// If regular address then we only care about the ip and ignore the port.
@ -218,18 +263,18 @@ pub struct P2PConfig {
pub seeding_type: Seeding, pub seeding_type: Seeding,
/// The list of seed nodes, if using Seeding as a seed type /// The list of seed nodes, if using Seeding as a seed type
pub seeds: Option<Vec<PeerAddr>>, pub seeds: Option<PeerAddrs>,
/// Capabilities expose by this node, also conditions which other peers this /// Capabilities expose by this node, also conditions which other peers this
/// node will have an affinity toward when connection. /// node will have an affinity toward when connection.
pub capabilities: Capabilities, pub capabilities: Capabilities,
pub peers_allow: Option<Vec<PeerAddr>>, pub peers_allow: Option<PeerAddrs>,
pub peers_deny: Option<Vec<PeerAddr>>, pub peers_deny: Option<PeerAddrs>,
/// The list of preferred peers that we will try to connect to /// The list of preferred peers that we will try to connect to
pub peers_preferred: Option<Vec<PeerAddr>>, pub peers_preferred: Option<PeerAddrs>,
pub ban_window: Option<i64>, pub ban_window: Option<i64>,
@ -316,7 +361,7 @@ impl P2PConfig {
pub enum Seeding { pub enum Seeding {
/// No seeding, mostly for tests that programmatically connect /// No seeding, mostly for tests that programmatically connect
None, None,
/// A list of seed addresses provided to the server /// A list of seeds provided to the server (can be addresses or DNS names)
List, List,
/// Automatically get a list of seeds from multiple DNS /// Automatically get a list of seeds from multiple DNS
DNSSeed, DNSSeed,

View file

@ -361,35 +361,47 @@ fn listen_for_addrs(
} }
} }
pub fn dns_seeds() -> Box<dyn Fn() -> Vec<PeerAddr> + Send> { pub fn default_dns_seeds() -> Box<dyn Fn() -> Vec<PeerAddr> + Send> {
Box::new(|| { Box::new(|| {
let mut addresses: Vec<PeerAddr> = vec![];
let net_seeds = if global::is_floonet() { let net_seeds = if global::is_floonet() {
FLOONET_DNS_SEEDS FLOONET_DNS_SEEDS
} else { } else {
MAINNET_DNS_SEEDS MAINNET_DNS_SEEDS
}; };
for dns_seed in net_seeds { resolve_dns_to_addrs(
let temp_addresses = addresses.clone(); &net_seeds
debug!("Retrieving seed nodes from dns {}", dns_seed); .iter()
match (dns_seed.to_owned(), 0).to_socket_addrs() { .map(|s| {
Ok(addrs) => addresses.append( s.to_string()
&mut (addrs + if global::is_floonet() {
.map(|mut addr| { ":13414"
addr.set_port(if global::is_floonet() { 13414 } else { 3414 }); } else {
PeerAddr(addr) ":3414"
}) }
.filter(|addr| !temp_addresses.contains(addr)) })
.collect()), .collect(),
), )
Err(e) => debug!("Failed to resolve seed {:?} got error {:?}", dns_seed, e),
}
}
debug!("Retrieved seed addresses: {:?}", addresses);
addresses
}) })
} }
fn resolve_dns_to_addrs(dns_records: &Vec<String>) -> Vec<PeerAddr> {
let mut addresses: Vec<PeerAddr> = vec![];
for dns in dns_records {
debug!("Retrieving addresses from dns {}", dns);
match dns.to_socket_addrs() {
Ok(addrs) => addresses.append(
&mut addrs
.map(|addr| PeerAddr(addr))
.filter(|addr| !addresses.contains(addr))
.collect(),
),
Err(e) => debug!("Failed to resolve dns {:?} got error {:?}", dns, e),
};
}
debug!("Resolved addresses: {:?}", addresses);
addresses
}
/// Convenience function when the seed list is immediately known. Mostly used /// Convenience function when the seed list is immediately known. Mostly used
/// for tests. /// for tests.
pub fn predefined_seeds(addrs: Vec<PeerAddr>) -> Box<dyn Fn() -> Vec<PeerAddr> + Send> { pub fn predefined_seeds(addrs: Vec<PeerAddr>) -> Box<dyn Fn() -> Vec<PeerAddr> + Send> {

View file

@ -231,22 +231,24 @@ impl Server {
seed::predefined_seeds(vec![]) seed::predefined_seeds(vec![])
} }
p2p::Seeding::List => match &config.p2p_config.seeds { p2p::Seeding::List => match &config.p2p_config.seeds {
Some(seeds) => seed::predefined_seeds(seeds.clone()), Some(seeds) => seed::predefined_seeds(seeds.peers.clone()),
None => { None => {
return Err(Error::Configuration( return Err(Error::Configuration(
"Seeds must be configured for seeding type List".to_owned(), "Seeds must be configured for seeding type List".to_owned(),
)); ));
} }
}, },
p2p::Seeding::DNSSeed => seed::dns_seeds(), p2p::Seeding::DNSSeed => seed::default_dns_seeds(),
_ => unreachable!(), _ => unreachable!(),
}; };
let preferred_peers = config.p2p_config.peers_preferred.clone().map(|p| p.peers);
connect_thread = Some(seed::connect_and_monitor( connect_thread = Some(seed::connect_and_monitor(
p2p_server.clone(), p2p_server.clone(),
config.p2p_config.capabilities, config.p2p_config.capabilities,
seeder, seeder,
config.p2p_config.peers_preferred.clone(), preferred_peers,
stop_state.clone(), stop_state.clone(),
)?); )?);
} }

View file

@ -24,9 +24,11 @@ use ctrlc;
use crate::config::GlobalConfig; use crate::config::GlobalConfig;
use crate::core::global; use crate::core::global;
use crate::p2p::{PeerAddr, Seeding}; use crate::p2p::Seeding;
use crate::servers; use crate::servers;
use crate::tui::ui; use crate::tui::ui;
use grin_p2p::msg::PeerAddrs;
use grin_p2p::PeerAddr;
use grin_util::logger::LogEntry; use grin_util::logger::LogEntry;
use std::sync::mpsc; use std::sync::mpsc;
@ -119,12 +121,12 @@ pub fn server_command(
} }
if let Some(seeds) = a.values_of("seed") { if let Some(seeds) = a.values_of("seed") {
let seed_addrs = seeds let peers = seeds
.filter_map(|x| x.parse().ok()) .filter_map(|s| s.parse().ok())
.map(|x| PeerAddr(x)) .map(|sa| PeerAddr(sa))
.collect(); .collect();
server_config.p2p_config.seeding_type = Seeding::List; server_config.p2p_config.seeding_type = Seeding::List;
server_config.p2p_config.seeds = Some(seed_addrs); server_config.p2p_config.seeds = Some(PeerAddrs { peers });
} }
} }