diff --git a/Cargo.lock b/Cargo.lock index 644fb8654..89fb466d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1379,6 +1379,7 @@ dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "natpmp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-bytes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-crypto 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "parity-path 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2812,6 +2813,14 @@ name = "nan-preserving-float" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "natpmp" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "net2" version = "0.2.33" @@ -5612,6 +5621,7 @@ dependencies = [ "checksum multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9c35dac080fd6e16a99924c8dfdef0af89d797dd851adab25feaffacf7850d6" "checksum multihash 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c62469025f45dee2464ef9fc845f4683c543993792c1993e7d903c17a4546b74" "checksum nan-preserving-float 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34d4f00fcc2f4c9efa8cc971db0da9e28290e28e97af47585e48691ef10ff31f" +"checksum natpmp 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d85b74917d95eab8b26ab6fe28e21d3fede3a614411ca4d3b01265c05bf86a12" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" diff --git a/ethcore/sync/src/api.rs b/ethcore/sync/src/api.rs index a8a7a99fc..3565ac073 100644 --- a/ethcore/sync/src/api.rs +++ b/ethcore/sync/src/api.rs @@ -54,7 +54,7 @@ use network::{ client_version::ClientVersion, NetworkProtocolHandler, NetworkContext, PeerId, ProtocolId, NetworkConfiguration as BasicNetworkConfiguration, NonReservedPeerMode, Error, - ConnectionFilter, IpFilter + ConnectionFilter, IpFilter, NatType }; use snapshot::SnapshotService; use parking_lot::{RwLock, Mutex}; @@ -742,6 +742,8 @@ pub struct NetworkConfiguration { pub udp_port: Option, /// Enable NAT configuration pub nat_enabled: bool, + /// Nat type + pub nat_type: NatType, /// Enable discovery pub discovery_enabled: bool, /// List of initial node addresses @@ -786,6 +788,7 @@ impl NetworkConfiguration { public_address: match self.public_address { None => None, Some(addr) => Some(SocketAddr::from_str(&addr)?) }, udp_port: self.udp_port, nat_enabled: self.nat_enabled, + nat_type: self.nat_type, discovery_enabled: self.discovery_enabled, boot_nodes: self.boot_nodes, use_secret: self.use_secret, @@ -810,6 +813,7 @@ impl From for NetworkConfiguration { public_address: other.public_address.and_then(|addr| Some(format!("{}", addr))), udp_port: other.udp_port, nat_enabled: other.nat_enabled, + nat_type: other.nat_type, discovery_enabled: other.discovery_enabled, boot_nodes: other.boot_nodes, use_secret: other.use_secret, diff --git a/parity/configuration.rs b/parity/configuration.rs index 69987fc69..d6e6931fb 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -53,7 +53,7 @@ use export_hardcoded_sync::ExportHsyncCmd; use presale::ImportWallet; use account::{AccountCmd, NewAccount, ListAccounts, ImportAccounts, ImportFromGethAccounts}; use snapshot_cmd::{self, SnapshotCommand}; -use network::{IpFilter}; +use network::{IpFilter, NatType}; const DEFAULT_MAX_PEERS: u16 = 50; const DEFAULT_MIN_PEERS: u16 = 25; @@ -743,7 +743,13 @@ impl Configuration { fn net_config(&self) -> Result { let mut ret = NetworkConfiguration::new(); - ret.nat_enabled = self.args.arg_nat == "any" || self.args.arg_nat == "upnp"; + ret.nat_enabled = self.args.arg_nat == "any" || self.args.arg_nat == "upnp" || self.args.arg_nat == "natpmp"; + ret.nat_type = match &self.args.arg_nat[..] { + "any" => NatType::Any, + "upnp" => NatType::UPnP, + "natpmp" => NatType::NatPMP, + _ => NatType::Nothing, + }; ret.boot_nodes = to_bootnodes(&self.args.arg_bootnodes)?; let (listen, public) = self.net_addresses()?; ret.listen_address = Some(format!("{}", listen)); diff --git a/parity/helpers.rs b/parity/helpers.rs index 67ae0ed09..f987fede4 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -202,6 +202,7 @@ pub fn to_bootnodes(bootnodes: &Option) -> Result, String> { #[cfg(test)] pub fn default_network_config() -> ::sync::NetworkConfiguration { + use network::NatType; use sync::{NetworkConfiguration}; use super::network::IpFilter; NetworkConfiguration { @@ -211,6 +212,7 @@ pub fn default_network_config() -> ::sync::NetworkConfiguration { public_address: None, udp_port: None, nat_enabled: true, + nat_type: NatType::Any, discovery_enabled: true, boot_nodes: Vec::new(), use_secret: None, diff --git a/util/network-devp2p/Cargo.toml b/util/network-devp2p/Cargo.toml index f019334b9..e5d492178 100644 --- a/util/network-devp2p/Cargo.toml +++ b/util/network-devp2p/Cargo.toml @@ -32,6 +32,7 @@ parity-snappy = "0.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" lru-cache = "0.1" +natpmp = "0.2" [dev-dependencies] env_logger = "0.5" diff --git a/util/network-devp2p/src/host.rs b/util/network-devp2p/src/host.rs index 47c2d5321..bc8242ca0 100644 --- a/util/network-devp2p/src/host.rs +++ b/util/network-devp2p/src/host.rs @@ -461,7 +461,7 @@ impl Host { let public_address = select_public_address(local_endpoint.address.port()); let public_endpoint = NodeEndpoint { address: public_address, udp_port: local_endpoint.udp_port }; if self.info.read().config.nat_enabled { - match map_external_address(&local_endpoint) { + match map_external_address(&local_endpoint, &self.info.read().config.nat_type) { Some(endpoint) => { info!("NAT mapped to external address {}", endpoint.address); endpoint diff --git a/util/network-devp2p/src/ip_utils.rs b/util/network-devp2p/src/ip_utils.rs index 0a599155d..dbed72787 100644 --- a/util/network-devp2p/src/ip_utils.rs +++ b/util/network-devp2p/src/ip_utils.rs @@ -22,10 +22,17 @@ use std::time::Duration; use igd::{PortMappingProtocol, search_gateway, SearchOptions}; use ipnetwork::IpNetwork; -use log::debug; +use log::{trace, debug}; +use natpmp::{Natpmp, Protocol, Response}; +use network::NatType; use crate::node_table::NodeEndpoint; +const NAT_PMP_PORT_MAPPING_LIFETIME: u32 = 30; +// Waiting duration in milliseconds for response from router after sending port mapping request. +// 50 milliseconds might be enough for low RTT. +const NAT_PMP_PORT_MAPPING_WAITING_DURATION: u64 = 50; + /// Socket address extension for rustc beta. To be replaces with now unstable API pub trait SocketAddrExt { /// Returns true if the address appears to be globally routable. @@ -310,20 +317,20 @@ pub fn select_public_address(port: u16) -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port)) } -pub fn map_external_address(local: &NodeEndpoint) -> Option { +fn search_upnp(local: &NodeEndpoint) -> Option { if let SocketAddr::V4(ref local_addr) = local.address { let local_ip = *local_addr.ip(); let local_port = local_addr.port(); let local_udp_port = local.udp_port; + let search_options = SearchOptions { + timeout: Some(Duration::new(5, 0)), + // igd 0.7 used port 0 by default. + // Let's not change this behaviour + bind_addr: SocketAddr::V4(SocketAddrV4::new(local_ip, 0)), + ..Default::default() + }; let search_gateway_child = ::std::thread::spawn(move || { - let search_options = SearchOptions { - timeout: Some(Duration::new(5, 0)), - // igd 0.7 used port 0 by default. - // Let's not change this behaviour - bind_addr: SocketAddr::V4(SocketAddrV4::new(local_ip, 0)), - ..Default::default() - }; match search_gateway(search_options) { Err(ref err) => debug!("Gateway search error: {}", err), Ok(gateway) => { @@ -358,6 +365,82 @@ pub fn map_external_address(local: &NodeEndpoint) -> Option { None } +fn search_natpmp(local: &NodeEndpoint) -> Option { + if let SocketAddr::V4(ref local_addr) = local.address { + let local_port = local_addr.port(); + let local_udp_port = local.udp_port; + + let search_gateway_child = ::std::thread::spawn(move || { + let mut n = Natpmp::new()?; + + // this function call want to receive `Response::Gateway` response from router, if other then it is an Error. + n.send_public_address_request()?; + ::std::thread::sleep(Duration::from_millis(NAT_PMP_PORT_MAPPING_WAITING_DURATION)); + let gw = match n.read_response_or_retry() { + Ok(Response::Gateway(gw)) => Ok(gw), + Err(e) => { + debug!(target: "network", "IP request error: {}", e); + Err(e) + }, + _ => Err(natpmp::Error::NATPMP_ERR_UNDEFINEDERROR.into()) + }?; + + // this function call want to receive `Response::TCP` response from router, if other then it is an Error. + n.send_port_mapping_request(Protocol::TCP, local_port, local_port, NAT_PMP_PORT_MAPPING_LIFETIME)?; + ::std::thread::sleep(Duration::from_millis(NAT_PMP_PORT_MAPPING_WAITING_DURATION)); + let tcp_r = match n.read_response_or_retry() { + Ok(Response::TCP(tcp)) => Ok(tcp), + Err(e) => { + debug!(target: "network", "Port mapping for TCP error: {}", e); + Err(e) + }, + _ => Err(natpmp::Error::NATPMP_ERR_UNDEFINEDERROR.into()) + }?; + + // this function call want to receive `Response::UDP` response from router, if other then it is an Error. + n.send_port_mapping_request(Protocol::UDP, local_udp_port, local_udp_port, NAT_PMP_PORT_MAPPING_LIFETIME)?; + ::std::thread::sleep(Duration::from_millis(NAT_PMP_PORT_MAPPING_WAITING_DURATION)); + let udp_r = match n.read_response_or_retry() { + Ok(Response::UDP(udp)) => Ok(udp), + Err(e) => { + debug!(target: "network", "Port mapping for UDP error: {}", e); + Err(e) + }, + _ => Err(natpmp::Error::NATPMP_ERR_UNDEFINEDERROR.into()) + }?; + + Ok(NodeEndpoint { + address: SocketAddr::V4(SocketAddrV4::new(*gw.public_address(), tcp_r.public_port())), + udp_port: udp_r.public_port() + }) + }); + + return search_gateway_child.join().ok()? + .map_err(|e: natpmp::Error| debug!(target: "network", "NAT PMP port mapping error: {:?}", e)) + .ok(); + } + None +} + +/// Port mapping using ether UPnP or Nat-PMP. +/// NAT PMP has higher priority than UPnP. +pub fn map_external_address(local: &NodeEndpoint, nat_type: &NatType) -> Option { + match *nat_type { + NatType::Any => { + match search_natpmp(local) { + Some(end_point) => Some(end_point), + None => search_upnp(local), + } + }, + NatType::NatPMP => search_natpmp(local), + NatType::UPnP => search_upnp(local), + _ => { + trace!(target: "network", "Can't map external address using NAT"); + None + } + } +} + #[test] fn can_select_public_address() { let pub_address = select_public_address(40477); @@ -366,9 +449,16 @@ fn can_select_public_address() { #[ignore] #[test] -fn can_map_external_address_or_fail() { +fn can_map_external_address_upnp_or_fail() { let pub_address = select_public_address(40478); - let _ = map_external_address(&NodeEndpoint { address: pub_address, udp_port: 40478 }); + let _ = map_external_address(&NodeEndpoint { address: pub_address, udp_port: 40478 }, &NatType::UPnP); +} + +#[ignore] +#[test] +fn can_map_external_address_natpmp_or_fail() { + let pub_address = select_public_address(40479); + let _ = map_external_address(&NodeEndpoint { address: pub_address, udp_port: 40479 }, &NatType::NatPMP); } #[test] diff --git a/util/network/src/lib.rs b/util/network/src/lib.rs index e8ec1cf75..67ab4f581 100644 --- a/util/network/src/lib.rs +++ b/util/network/src/lib.rs @@ -174,6 +174,15 @@ impl Ord for SessionCapabilityInfo { } } +/// Type of NAT resolving method +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum NatType { + Nothing, + Any, + UPnP, + NatPMP, +} + /// Network service configuration #[derive(Debug, PartialEq, Clone)] pub struct NetworkConfiguration { @@ -189,6 +198,8 @@ pub struct NetworkConfiguration { pub udp_port: Option, /// Enable NAT configuration pub nat_enabled: bool, + /// Nat type + pub nat_type: NatType, /// Enable discovery pub discovery_enabled: bool, /// List of initial node addresses @@ -229,6 +240,7 @@ impl NetworkConfiguration { public_address: None, udp_port: None, nat_enabled: true, + nat_type: NatType::Any, discovery_enabled: true, boot_nodes: Vec::new(), use_secret: None,