Add Nat PMP method to P2P module (#11210)

* Add Nat PMP method to P2P module

* Add test fn

* Use closure

* print richer error messages.

* reformat long code line

* remove closures
This commit is contained in:
Cho 2019-12-19 17:01:39 +09:00 committed by David
parent 2b1d148ceb
commit e14d68e559
8 changed files with 140 additions and 15 deletions

10
Cargo.lock generated
View File

@ -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"

View File

@ -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<u16>,
/// 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<BasicNetworkConfiguration> 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,

View File

@ -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<NetworkConfiguration, String> {
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));

View File

@ -202,6 +202,7 @@ pub fn to_bootnodes(bootnodes: &Option<String>) -> Result<Vec<String>, 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,

View File

@ -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"

View File

@ -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

View File

@ -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<NodeEndpoint> {
fn search_upnp(local: &NodeEndpoint) -> Option<NodeEndpoint> {
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<NodeEndpoint> {
None
}
fn search_natpmp(local: &NodeEndpoint) -> Option<NodeEndpoint> {
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<NodeEndpoint> {
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]

View File

@ -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<u16>,
/// 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,