diff --git a/veilid-core/src/lib.rs b/veilid-core/src/lib.rs index ce93567c..6c707b24 100644 --- a/veilid-core/src/lib.rs +++ b/veilid-core/src/lib.rs @@ -90,7 +90,7 @@ pub static DEFAULT_LOG_IGNORE_LIST: [&str; 23] = [ use cfg_if::*; use enumset::*; 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 schemars::{schema_for, JsonSchema}; use serde::*; diff --git a/veilid-core/src/network_manager/native/network_class_discovery.rs b/veilid-core/src/network_manager/native/network_class_discovery.rs index 88389406..7faa1fef 100644 --- a/veilid-core/src/network_manager/native/network_class_discovery.rs +++ b/veilid-core/src/network_manager/native/network_class_discovery.rs @@ -27,12 +27,20 @@ struct DiscoveryContextInner { detected_public_dial_info: Option, } +#[derive(Clone)] pub struct DiscoveryContext { routing_table: RoutingTable, net: Network, inner: Arc>, } +#[derive(Clone, Debug)] +struct DetectedDialInfo { + dial_info: DialInfo, + dial_info_class: DialInfoClass, + network_class: NetworkClass, +} + impl DiscoveryContext { pub fn new(routing_table: RoutingTable, net: Network) -> Self { Self { @@ -412,53 +420,74 @@ impl DiscoveryContext { // If we know we are not behind NAT, check our firewall status #[instrument(level = "trace", skip(self), err)] pub async fn protocol_process_no_nat(&self) -> EyreResult<()> { - let (node_1, external_1_dial_info) = { - let inner = self.inner.lock(); - ( - inner.node_1.as_ref().unwrap().clone(), - inner.external_1_dial_info.as_ref().unwrap().clone(), - ) - }; + // Do these detections in parallel, but with ordering preference + let mut ord = FuturesOrdered::new(); - // 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 - else if self - .validate_dial_info(node_1.clone(), external_1_dial_info.clone(), true) - .await - { - // Add public dial info with Direct dialinfo class - self.set_detected_public_dial_info(external_1_dial_info, DialInfoClass::Direct); - self.set_detected_network_class(NetworkClass::InboundCapable); - } else { - // Add public dial info with Blocked dialinfo class - self.set_detected_public_dial_info(external_1_dial_info, DialInfoClass::Blocked); - self.set_detected_network_class(NetworkClass::InboundCapable); + // UPNP Automatic Mapping + /////////// + let this = self.clone(); + let do_mapped_fut: SendPinBoxFuture> = 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> = Box::pin(async move { + let (node_1, external_1_dial_info) = { + let inner = this.inner.lock(); + ( + inner.node_1.as_ref().unwrap().clone(), + inner.external_1_dial_info.as_ref().unwrap().clone(), + ) + }; + // Do a validate_dial_info on the external address from a redirected node + if this + .validate_dial_info(node_1.clone(), external_1_dial_info.clone(), true) + .await + { + // Add public dial info with Direct dialinfo class + Some(DetectedDialInfo { + dial_info: external_1_dial_info.clone(), + dial_info_class: DialInfoClass::Direct, + network_class: NetworkClass::InboundCapable, + }) + } else { + // Add public dial info with Blocked dialinfo class + Some(DetectedDialInfo { + 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(()) } // If we know we are behind NAT check what kind #[instrument(level = "trace", skip(self), ret, err)] pub async fn protocol_process_nat(&self) -> EyreResult { - // 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 let (node_1, external_1_dial_info, external_1_address, protocol_type, address_type) = { let inner = self.inner.lock(); @@ -471,51 +500,111 @@ impl DiscoveryContext { ) }; - // 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 - if let Some(local_port) = self.net.get_local_port(protocol_type) { - if external_1_dial_info.port() != local_port { - 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); + // Do these detections in parallel, but with ordering preference + let mut ord = FuturesOrdered::new(); - if self - .validate_dial_info( - node_1.clone(), - external_1_dial_info_with_local_port.clone(), - true, - ) - .await - { - // Add public dial info with Direct dialinfo class - self.set_detected_public_dial_info( - external_1_dial_info_with_local_port, - DialInfoClass::Direct, - ); - self.set_detected_network_class(NetworkClass::InboundCapable); - return Ok(true); - } + // UPNP Automatic Mapping + /////////// + let this = self.clone(); + let do_mapped_fut: SendPinBoxFuture> = 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> = + 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 + // This test is to see if a node had manual port forwarding done with the same port number as the local listener + let mut external_1_dial_info_with_local_port = + c_external_1_dial_info.clone(); + external_1_dial_info_with_local_port.set_port(local_port); + + if this + .validate_dial_info( + c_node_1.clone(), + external_1_dial_info_with_local_port.clone(), + true, + ) + .await + { + // Add public dial info with Direct dialinfo class + return Some(DetectedDialInfo { + dial_info: external_1_dial_info_with_local_port, + dial_info_class: DialInfoClass::Direct, + network_class: NetworkClass::InboundCapable, + }); + } + + None + }); + ord.push_back(do_manual_map_fut); } } - // Port mapping was not possible, and things aren't accessible directly. - // Let's see what kind of NAT we have + // 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> = Box::pin(async move { + // 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? + if this + .validate_dial_info(c_node_1.clone(), c_external_1_dial_info.clone(), true) + .await + { + // Yes, another machine can use the dial info directly, so Full Cone + // Add public dial info with full cone NAT network class - // Does a redirected dial info validation from a different address and a random port find us? - if self - .validate_dial_info(node_1.clone(), external_1_dial_info.clone(), true) - .await - { - // Yes, another machine can use the dial info directly, so Full Cone - // 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); + 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); - // No more retries - return Ok(true); + // 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); + } } - // 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 let (external_2_address, node_2) = match self .discover_external_address(protocol_type, address_type, Some(node_1.node_ids())) @@ -589,7 +678,7 @@ impl Network { // Loop for restricted NAT retries loop { log_net!(debug - "=== update_protocol_dialino {:?} {:?} tries_left={} ===", + "=== update_protocol_dialinfo {:?} {:?} tries_left={} ===", address_type, protocol_type, retry_count