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

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]