Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Add Nat PMP method to P2P module (#11210)
Browse files Browse the repository at this point in the history
* Add Nat PMP method to P2P module

* Add test fn

* Use closure

* print richer error messages.

* reformat long code line

* remove closures
  • Loading branch information
NamsooCho authored and dvdplm committed Dec 19, 2019
1 parent 2b1d148 commit e14d68e
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 15 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion ethcore/sync/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
10 changes: 8 additions & 2 deletions parity/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
2 changes: 2 additions & 0 deletions parity/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions util/network-devp2p/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion util/network-devp2p/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
112 changes: 101 additions & 11 deletions util/network-devp2p/src/ip_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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);
Expand All @@ -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]
Expand Down
12 changes: 12 additions & 0 deletions util/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit e14d68e

Please sign in to comment.