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(
"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
"
.to_string(),

View file

@ -466,7 +466,7 @@ impl Readable for GetPeerAddrs {
/// Peer addresses we know of that are fresh enough, in response to
/// GetPeerAddrs.
#[derive(Debug)]
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct PeerAddrs {
pub peers: Vec<PeerAddr>,
}
@ -493,7 +493,7 @@ impl Readable for PeerAddrs {
for _ in 0..peer_count {
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 {
if let Some(ref denied) = config.peers_deny {
if denied.contains(&peer_addr) {
if denied.peers.contains(&peer_addr) {
debug!(
"checking peer allowed/denied: {:?} explicitly denied",
peer_addr
@ -161,7 +161,7 @@ impl Peer {
}
}
if let Some(ref allowed) = config.peers_allow {
if allowed.contains(&peer_addr) {
if allowed.peers.contains(&peer_addr) {
debug!(
"checking peer allowed/denied: {:?} explicitly allowed",
peer_addr

View file

@ -12,15 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::util::RwLock;
use std::convert::From;
use std::fmt;
use std::fs::File;
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::str::FromStr;
use std::sync::Arc;
use chrono::prelude::*;
use serde::de::{SeqAccess, Visitor};
use serde::{Deserialize, Deserializer};
use grin_store;
use crate::chain;
use crate::core::core;
@ -28,7 +33,8 @@ use crate::core::core::hash::Hash;
use crate::core::global;
use crate::core::pow::Difficulty;
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
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 {
/// If loopback address then we care about ip and 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,
/// 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
/// node will have an affinity toward when connection.
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
pub peers_preferred: Option<Vec<PeerAddr>>,
pub peers_preferred: Option<PeerAddrs>,
pub ban_window: Option<i64>,
@ -316,7 +361,7 @@ impl P2PConfig {
pub enum Seeding {
/// No seeding, mostly for tests that programmatically connect
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,
/// Automatically get a list of seeds from multiple DNS
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(|| {
let mut addresses: Vec<PeerAddr> = vec![];
let net_seeds = if global::is_floonet() {
FLOONET_DNS_SEEDS
} else {
MAINNET_DNS_SEEDS
};
for dns_seed in net_seeds {
let temp_addresses = addresses.clone();
debug!("Retrieving seed nodes from dns {}", dns_seed);
match (dns_seed.to_owned(), 0).to_socket_addrs() {
Ok(addrs) => addresses.append(
&mut (addrs
.map(|mut addr| {
addr.set_port(if global::is_floonet() { 13414 } else { 3414 });
PeerAddr(addr)
})
.filter(|addr| !temp_addresses.contains(addr))
.collect()),
),
Err(e) => debug!("Failed to resolve seed {:?} got error {:?}", dns_seed, e),
}
}
debug!("Retrieved seed addresses: {:?}", addresses);
addresses
resolve_dns_to_addrs(
&net_seeds
.iter()
.map(|s| {
s.to_string()
+ if global::is_floonet() {
":13414"
} else {
":3414"
}
})
.collect(),
)
})
}
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
/// for tests.
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![])
}
p2p::Seeding::List => match &config.p2p_config.seeds {
Some(seeds) => seed::predefined_seeds(seeds.clone()),
Some(seeds) => seed::predefined_seeds(seeds.peers.clone()),
None => {
return Err(Error::Configuration(
"Seeds must be configured for seeding type List".to_owned(),
));
}
},
p2p::Seeding::DNSSeed => seed::dns_seeds(),
p2p::Seeding::DNSSeed => seed::default_dns_seeds(),
_ => unreachable!(),
};
let preferred_peers = config.p2p_config.peers_preferred.clone().map(|p| p.peers);
connect_thread = Some(seed::connect_and_monitor(
p2p_server.clone(),
config.p2p_config.capabilities,
seeder,
config.p2p_config.peers_preferred.clone(),
preferred_peers,
stop_state.clone(),
)?);
}

View file

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