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:
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user