speed up public address detection

This commit is contained in:
John Smith 2023-08-24 16:15:51 -04:00 committed by Christien Rioux
parent 4eca53fd9b
commit 945215aba1
2 changed files with 167 additions and 78 deletions

View File

@ -90,7 +90,7 @@ pub static DEFAULT_LOG_IGNORE_LIST: [&str; 23] = [
use cfg_if::*; use cfg_if::*;
use enumset::*; use enumset::*;
use eyre::{bail, eyre, Report as EyreReport, Result as EyreResult, WrapErr}; use eyre::{bail, eyre, Report as EyreReport, Result as EyreResult, WrapErr};
use futures_util::stream::FuturesUnordered; use futures_util::stream::{FuturesOrdered, FuturesUnordered};
use parking_lot::*; use parking_lot::*;
use schemars::{schema_for, JsonSchema}; use schemars::{schema_for, JsonSchema};
use serde::*; use serde::*;

View File

@ -27,12 +27,20 @@ struct DiscoveryContextInner {
detected_public_dial_info: Option<DetectedPublicDialInfo>, detected_public_dial_info: Option<DetectedPublicDialInfo>,
} }
#[derive(Clone)]
pub struct DiscoveryContext { pub struct DiscoveryContext {
routing_table: RoutingTable, routing_table: RoutingTable,
net: Network, net: Network,
inner: Arc<Mutex<DiscoveryContextInner>>, inner: Arc<Mutex<DiscoveryContextInner>>,
} }
#[derive(Clone, Debug)]
struct DetectedDialInfo {
dial_info: DialInfo,
dial_info_class: DialInfoClass,
network_class: NetworkClass,
}
impl DiscoveryContext { impl DiscoveryContext {
pub fn new(routing_table: RoutingTable, net: Network) -> Self { pub fn new(routing_table: RoutingTable, net: Network) -> Self {
Self { Self {
@ -412,53 +420,74 @@ impl DiscoveryContext {
// If we know we are not behind NAT, check our firewall status // If we know we are not behind NAT, check our firewall status
#[instrument(level = "trace", skip(self), err)] #[instrument(level = "trace", skip(self), err)]
pub async fn protocol_process_no_nat(&self) -> EyreResult<()> { pub async fn protocol_process_no_nat(&self) -> EyreResult<()> {
// Do these detections in parallel, but with ordering preference
let mut ord = FuturesOrdered::new();
// UPNP Automatic Mapping
///////////
let this = self.clone();
let do_mapped_fut: SendPinBoxFuture<Option<DetectedDialInfo>> = Box::pin(async move {
// Attempt a port mapping via all available and enabled mechanisms
// Try this before the direct mapping in the event that we are restarting
// and may not have recorded a mapping created the last time
if let Some(external_mapped_dial_info) = this.try_port_mapping().await {
// Got a port mapping, let's use it
return Some(DetectedDialInfo {
dial_info: external_mapped_dial_info.clone(),
dial_info_class: DialInfoClass::Mapped,
network_class: NetworkClass::InboundCapable,
});
}
None
});
ord.push_back(do_mapped_fut);
let this = self.clone();
let do_direct_fut: SendPinBoxFuture<Option<DetectedDialInfo>> = Box::pin(async move {
let (node_1, external_1_dial_info) = { let (node_1, external_1_dial_info) = {
let inner = self.inner.lock(); let inner = this.inner.lock();
( (
inner.node_1.as_ref().unwrap().clone(), inner.node_1.as_ref().unwrap().clone(),
inner.external_1_dial_info.as_ref().unwrap().clone(), inner.external_1_dial_info.as_ref().unwrap().clone(),
) )
}; };
// Attempt a port mapping via all available and enabled mechanisms
// Try this before the direct mapping in the event that we are restarting
// and may not have recorded a mapping created the last time
if let Some(external_mapped_dial_info) = self.try_port_mapping().await {
// Got a port mapping, let's use it
self.set_detected_public_dial_info(external_mapped_dial_info, DialInfoClass::Mapped);
self.set_detected_network_class(NetworkClass::InboundCapable);
}
// Do a validate_dial_info on the external address from a redirected node // Do a validate_dial_info on the external address from a redirected node
else if self if this
.validate_dial_info(node_1.clone(), external_1_dial_info.clone(), true) .validate_dial_info(node_1.clone(), external_1_dial_info.clone(), true)
.await .await
{ {
// Add public dial info with Direct dialinfo class // Add public dial info with Direct dialinfo class
self.set_detected_public_dial_info(external_1_dial_info, DialInfoClass::Direct); Some(DetectedDialInfo {
self.set_detected_network_class(NetworkClass::InboundCapable); dial_info: external_1_dial_info.clone(),
dial_info_class: DialInfoClass::Direct,
network_class: NetworkClass::InboundCapable,
})
} else { } else {
// Add public dial info with Blocked dialinfo class // Add public dial info with Blocked dialinfo class
self.set_detected_public_dial_info(external_1_dial_info, DialInfoClass::Blocked); Some(DetectedDialInfo {
self.set_detected_network_class(NetworkClass::InboundCapable); dial_info: external_1_dial_info.clone(),
dial_info_class: DialInfoClass::Blocked,
network_class: NetworkClass::InboundCapable,
})
} }
});
ord.push_back(do_direct_fut);
while let Some(res) = ord.next().await {
if let Some(ddi) = res {
self.set_detected_public_dial_info(ddi.dial_info, ddi.dial_info_class);
self.set_detected_network_class(ddi.network_class);
break;
}
}
Ok(()) Ok(())
} }
// If we know we are behind NAT check what kind // If we know we are behind NAT check what kind
#[instrument(level = "trace", skip(self), ret, err)] #[instrument(level = "trace", skip(self), ret, err)]
pub async fn protocol_process_nat(&self) -> EyreResult<bool> { pub async fn protocol_process_nat(&self) -> EyreResult<bool> {
// Attempt a port mapping via all available and enabled mechanisms
// Try this before the direct mapping in the event that we are restarting
// and may not have recorded a mapping created the last time
if let Some(external_mapped_dial_info) = self.try_port_mapping().await {
// Got a port mapping, let's use it
self.set_detected_public_dial_info(external_mapped_dial_info, DialInfoClass::Mapped);
self.set_detected_network_class(NetworkClass::InboundCapable);
// No more retries
return Ok(true);
}
// Get the external dial info for our use here // Get the external dial info for our use here
let (node_1, external_1_dial_info, external_1_address, protocol_type, address_type) = { let (node_1, external_1_dial_info, external_1_address, protocol_type, address_type) = {
let inner = self.inner.lock(); let inner = self.inner.lock();
@ -471,51 +500,111 @@ impl DiscoveryContext {
) )
}; };
// Do these detections in parallel, but with ordering preference
let mut ord = FuturesOrdered::new();
// UPNP Automatic Mapping
///////////
let this = self.clone();
let do_mapped_fut: SendPinBoxFuture<Option<DetectedDialInfo>> = Box::pin(async move {
// Attempt a port mapping via all available and enabled mechanisms
// Try this before the direct mapping in the event that we are restarting
// and may not have recorded a mapping created the last time
if let Some(external_mapped_dial_info) = this.try_port_mapping().await {
// Got a port mapping, let's use it
return Some(DetectedDialInfo {
dial_info: external_mapped_dial_info.clone(),
dial_info_class: DialInfoClass::Mapped,
network_class: NetworkClass::InboundCapable,
});
}
None
});
ord.push_back(do_mapped_fut);
// Manual Mapping Detection
///////////
let this = self.clone();
if let Some(local_port) = this.net.get_local_port(protocol_type) {
if external_1_dial_info.port() != local_port {
let c_external_1_dial_info = external_1_dial_info.clone();
let c_node_1 = node_1.clone();
let do_manual_map_fut: SendPinBoxFuture<Option<DetectedDialInfo>> =
Box::pin(async move {
// Do a validate_dial_info on the external address, but with the same port as the local port of local interface, from a redirected node // Do a validate_dial_info on the external address, but with the same port as the local port of local interface, from a redirected node
// This test is to see if a node had manual port forwarding done with the same port number as the local listener // This test is to see if a node had manual port forwarding done with the same port number as the local listener
if let Some(local_port) = self.net.get_local_port(protocol_type) { let mut external_1_dial_info_with_local_port =
if external_1_dial_info.port() != local_port { c_external_1_dial_info.clone();
let mut external_1_dial_info_with_local_port = external_1_dial_info.clone();
external_1_dial_info_with_local_port.set_port(local_port); external_1_dial_info_with_local_port.set_port(local_port);
if self if this
.validate_dial_info( .validate_dial_info(
node_1.clone(), c_node_1.clone(),
external_1_dial_info_with_local_port.clone(), external_1_dial_info_with_local_port.clone(),
true, true,
) )
.await .await
{ {
// Add public dial info with Direct dialinfo class // Add public dial info with Direct dialinfo class
self.set_detected_public_dial_info( return Some(DetectedDialInfo {
external_1_dial_info_with_local_port, dial_info: external_1_dial_info_with_local_port,
DialInfoClass::Direct, dial_info_class: DialInfoClass::Direct,
); network_class: NetworkClass::InboundCapable,
self.set_detected_network_class(NetworkClass::InboundCapable); });
return Ok(true);
} }
None
});
ord.push_back(do_manual_map_fut);
} }
} }
// Port mapping was not possible, and things aren't accessible directly. // Full Cone NAT Detection
///////////
let this = self.clone();
let c_node_1 = node_1.clone();
let c_external_1_dial_info = external_1_dial_info.clone();
let do_full_cone_fut: SendPinBoxFuture<Option<DetectedDialInfo>> = Box::pin(async move {
// Let's see what kind of NAT we have // Let's see what kind of NAT we have
// Does a redirected dial info validation from a different address and a random port find us? // Does a redirected dial info validation from a different address and a random port find us?
if self if this
.validate_dial_info(node_1.clone(), external_1_dial_info.clone(), true) .validate_dial_info(c_node_1.clone(), c_external_1_dial_info.clone(), true)
.await .await
{ {
// Yes, another machine can use the dial info directly, so Full Cone // Yes, another machine can use the dial info directly, so Full Cone
// Add public dial info with full cone NAT network class // Add public dial info with full cone NAT network class
self.set_detected_public_dial_info(external_1_dial_info, DialInfoClass::FullConeNAT);
self.set_detected_network_class(NetworkClass::InboundCapable);
// No more retries return Some(DetectedDialInfo {
dial_info: c_external_1_dial_info,
dial_info_class: DialInfoClass::FullConeNAT,
network_class: NetworkClass::InboundCapable,
});
}
None
});
ord.push_back(do_full_cone_fut);
// Run detections in parallel and take the first one, ordered by preference, that returns a result
while let Some(res) = ord.next().await {
if let Some(ddi) = res {
self.set_detected_public_dial_info(ddi.dial_info, ddi.dial_info_class);
self.set_detected_network_class(ddi.network_class);
return Ok(true); return Ok(true);
} }
}
// No, we are restricted, determine what kind of restriction // We are restricted, determine what kind of restriction
// Get the external dial info for our use here
let (node_1, external_1_dial_info, external_1_address, protocol_type, address_type) = {
let inner = self.inner.lock();
(
inner.node_1.as_ref().unwrap().clone(),
inner.external_1_dial_info.as_ref().unwrap().clone(),
inner.external_1_address.unwrap(),
inner.protocol_type.unwrap(),
inner.address_type.unwrap(),
)
};
// Get our external address from some fast node, that is not node 1, call it node 2 // Get our external address from some fast node, that is not node 1, call it node 2
let (external_2_address, node_2) = match self let (external_2_address, node_2) = match self
.discover_external_address(protocol_type, address_type, Some(node_1.node_ids())) .discover_external_address(protocol_type, address_type, Some(node_1.node_ids()))
@ -589,7 +678,7 @@ impl Network {
// Loop for restricted NAT retries // Loop for restricted NAT retries
loop { loop {
log_net!(debug log_net!(debug
"=== update_protocol_dialino {:?} {:?} tries_left={} ===", "=== update_protocol_dialinfo {:?} {:?} tries_left={} ===",
address_type, address_type,
protocol_type, protocol_type,
retry_count retry_count