public address detection refactoring
This commit is contained in:
		| @@ -1,3 +1,4 @@ | ||||
| mod discovery_context; | ||||
| mod igd_manager; | ||||
| mod network_class_discovery; | ||||
| mod network_tcp; | ||||
| @@ -8,6 +9,7 @@ mod start_protocols; | ||||
| use super::*; | ||||
| use crate::routing_table::*; | ||||
| use connection_manager::*; | ||||
| use discovery_context::*; | ||||
| use network_interfaces::*; | ||||
| use network_tcp::*; | ||||
| use protocol::tcp::RawTcpProtocolHandler; | ||||
|   | ||||
| @@ -1,659 +1,9 @@ | ||||
| /// Detect NetworkClass and DialInfo for the PublicInternet RoutingDomain | ||||
| /// Also performs UPNP/IGD mapping if enabled and possible | ||||
| /// Detect NetworkClass and DialInfo for the DialInfo for the PublicInternet RoutingDomain | ||||
| use super::*; | ||||
| use futures_util::stream::FuturesUnordered; | ||||
| use futures_util::FutureExt; | ||||
| use stop_token::future::FutureExt as StopTokenFutureExt; | ||||
|  | ||||
| const PORT_MAP_VALIDATE_TRY_COUNT: usize = 3; | ||||
| const PORT_MAP_VALIDATE_DELAY_MS: u32 = 500; | ||||
| const PORT_MAP_TRY_COUNT: usize = 3; | ||||
|  | ||||
| struct DetectedPublicDialInfo { | ||||
|     dial_info: DialInfo, | ||||
|     class: DialInfoClass, | ||||
| } | ||||
| struct DiscoveryContextInner { | ||||
|     // per-protocol | ||||
|     intf_addrs: Option<Vec<SocketAddress>>, | ||||
|     protocol_type: Option<ProtocolType>, | ||||
|     address_type: Option<AddressType>, | ||||
|     // first node contacted | ||||
|     external_1_dial_info: Option<DialInfo>, | ||||
|     external_1_address: Option<SocketAddress>, | ||||
|     node_1: Option<NodeRef>, | ||||
|     // detected public dialinfo | ||||
|     detected_network_class: Option<NetworkClass>, | ||||
|     detected_public_dial_info: Option<DetectedPublicDialInfo>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone)] | ||||
| pub struct DiscoveryContext { | ||||
|     routing_table: RoutingTable, | ||||
|     net: Network, | ||||
|     inner: Arc<Mutex<DiscoveryContextInner>>, | ||||
| } | ||||
|  | ||||
| #[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 { | ||||
|             routing_table, | ||||
|             net, | ||||
|             inner: Arc::new(Mutex::new(DiscoveryContextInner { | ||||
|                 // per-protocol | ||||
|                 intf_addrs: None, | ||||
|                 protocol_type: None, | ||||
|                 address_type: None, | ||||
|                 external_1_dial_info: None, | ||||
|                 external_1_address: None, | ||||
|                 node_1: None, | ||||
|                 detected_network_class: None, | ||||
|                 detected_public_dial_info: None, | ||||
|             })), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /////// | ||||
|     // Utilities | ||||
|  | ||||
|     // Pick the best network class we have seen so far | ||||
|     pub fn set_detected_network_class(&self, network_class: NetworkClass) { | ||||
|         let mut inner = self.inner.lock(); | ||||
|         debug!(target: "net", | ||||
|             protocol_type=?inner.protocol_type, | ||||
|             address_type=?inner.address_type, | ||||
|             ?network_class, | ||||
|             "set_detected_network_class" | ||||
|         ); | ||||
|         inner.detected_network_class = Some(network_class); | ||||
|     } | ||||
|  | ||||
|     pub fn set_detected_public_dial_info(&self, dial_info: DialInfo, class: DialInfoClass) { | ||||
|         let mut inner = self.inner.lock(); | ||||
|         debug!(target: "net", | ||||
|             protocol_type=?inner.protocol_type, | ||||
|             address_type=?inner.address_type, | ||||
|             ?dial_info, | ||||
|             ?class, | ||||
|             "set_detected_public_dial_info" | ||||
|         ); | ||||
|         inner.detected_public_dial_info = Some(DetectedPublicDialInfo { dial_info, class }); | ||||
|     } | ||||
|  | ||||
|     // Ask for a public address check from a particular noderef | ||||
|     // This is done over the normal port using RPC | ||||
|     #[instrument(level = "trace", skip(self), ret)] | ||||
|     async fn request_public_address(&self, node_ref: NodeRef) -> Option<SocketAddress> { | ||||
|         let rpc = self.routing_table.rpc_processor(); | ||||
|  | ||||
|         let res = network_result_value_or_log!(match rpc.rpc_call_status(Destination::direct(node_ref.clone())).await { | ||||
|                 Ok(v) => v, | ||||
|                 Err(e) => { | ||||
|                     log_net!(error | ||||
|                         "failed to get status answer from {:?}: {}", | ||||
|                         node_ref, e | ||||
|                     ); | ||||
|                     return None; | ||||
|                 } | ||||
|             } => [ format!(": node_ref={}", node_ref) ] { | ||||
|                 return None; | ||||
|             } | ||||
|         ); | ||||
|  | ||||
|         log_net!( | ||||
|             "request_public_address {:?}: Value({:?})", | ||||
|             node_ref, | ||||
|             res.answer | ||||
|         ); | ||||
|         res.answer.map(|si| si.socket_address) | ||||
|     } | ||||
|  | ||||
|     // find fast peers with a particular address type, and ask them to tell us what our external address is | ||||
|     // This is done over the normal port using RPC | ||||
|     #[instrument(level = "trace", skip(self), ret)] | ||||
|     async fn discover_external_address( | ||||
|         &self, | ||||
|         protocol_type: ProtocolType, | ||||
|         address_type: AddressType, | ||||
|         ignore_node_ids: Option<TypedKeyGroup>, | ||||
|     ) -> Option<(SocketAddress, NodeRef)> { | ||||
|         let node_count = { | ||||
|             let config = self.routing_table.network_manager().config(); | ||||
|             let c = config.get(); | ||||
|             c.network.dht.max_find_node_count as usize | ||||
|         }; | ||||
|         let routing_domain = RoutingDomain::PublicInternet; | ||||
|  | ||||
|         // Build an filter that matches our protocol and address type | ||||
|         // and excludes relayed nodes so we can get an accurate external address | ||||
|         let dial_info_filter = DialInfoFilter::all() | ||||
|             .with_protocol_type(protocol_type) | ||||
|             .with_address_type(address_type); | ||||
|         let inbound_dial_info_entry_filter = RoutingTable::make_inbound_dial_info_entry_filter( | ||||
|             routing_domain, | ||||
|             dial_info_filter.clone(), | ||||
|         ); | ||||
|         let disallow_relays_filter = Box::new( | ||||
|             move |rti: &RoutingTableInner, v: Option<Arc<BucketEntry>>| { | ||||
|                 let v = v.unwrap(); | ||||
|                 v.with(rti, |_rti, e| { | ||||
|                     if let Some(n) = e.signed_node_info(routing_domain) { | ||||
|                         n.relay_ids().is_empty() | ||||
|                     } else { | ||||
|                         false | ||||
|                     } | ||||
|                 }) | ||||
|             }, | ||||
|         ) as RoutingTableEntryFilter; | ||||
|         let will_validate_dial_info_filter = Box::new( | ||||
|             move |rti: &RoutingTableInner, v: Option<Arc<BucketEntry>>| { | ||||
|                 let entry = v.unwrap(); | ||||
|                 entry.with(rti, move |_rti, e| { | ||||
|                     e.node_info(routing_domain) | ||||
|                         .map(|ni| { | ||||
|                             ni.has_capability(CAP_VALIDATE_DIAL_INFO) | ||||
|                                 && ni.is_fully_direct_inbound() | ||||
|                         }) | ||||
|                         .unwrap_or(false) | ||||
|                 }) | ||||
|             }, | ||||
|         ) as RoutingTableEntryFilter; | ||||
|  | ||||
|         let mut filters = VecDeque::from([ | ||||
|             inbound_dial_info_entry_filter, | ||||
|             disallow_relays_filter, | ||||
|             will_validate_dial_info_filter, | ||||
|         ]); | ||||
|         if let Some(ignore_node_ids) = ignore_node_ids { | ||||
|             let ignore_nodes_filter = Box::new( | ||||
|                 move |rti: &RoutingTableInner, v: Option<Arc<BucketEntry>>| { | ||||
|                     let v = v.unwrap(); | ||||
|                     v.with(rti, |_rti, e| !e.node_ids().contains_any(&ignore_node_ids)) | ||||
|                 }, | ||||
|             ) as RoutingTableEntryFilter; | ||||
|             filters.push_back(ignore_nodes_filter); | ||||
|         } | ||||
|  | ||||
|         // Find public nodes matching this filter | ||||
|         let peers = self | ||||
|             .routing_table | ||||
|             .find_fast_public_nodes_filtered(node_count, filters); | ||||
|         if peers.is_empty() { | ||||
|             log_net!(debug | ||||
|                 "no external address detection peers of type {:?}:{:?}", | ||||
|                 protocol_type, | ||||
|                 address_type | ||||
|             ); | ||||
|             return None; | ||||
|         } | ||||
|  | ||||
|         // For each peer, ask them for our public address, filtering on desired dial info | ||||
|         for mut peer in peers { | ||||
|             peer.set_filter(Some( | ||||
|                 NodeRefFilter::new() | ||||
|                     .with_routing_domain(routing_domain) | ||||
|                     .with_dial_info_filter(dial_info_filter.clone()), | ||||
|             )); | ||||
|             if let Some(sa) = self.request_public_address(peer.clone()).await { | ||||
|                 return Some((sa, peer)); | ||||
|             } | ||||
|         } | ||||
|         log_net!(debug "no peers responded with an external address"); | ||||
|         None | ||||
|     } | ||||
|  | ||||
|     // This pulls the already-detected local interface dial info from the routing table | ||||
|     #[instrument(level = "trace", skip(self), ret)] | ||||
|     fn get_local_addresses( | ||||
|         &self, | ||||
|         protocol_type: ProtocolType, | ||||
|         address_type: AddressType, | ||||
|     ) -> Vec<SocketAddress> { | ||||
|         let filter = DialInfoFilter::all() | ||||
|             .with_protocol_type(protocol_type) | ||||
|             .with_address_type(address_type); | ||||
|         self.routing_table | ||||
|             .dial_info_details(RoutingDomain::LocalNetwork) | ||||
|             .iter() | ||||
|             .filter_map(|did| { | ||||
|                 if did.dial_info.matches_filter(&filter) { | ||||
|                     Some(did.dial_info.socket_address()) | ||||
|                 } else { | ||||
|                     None | ||||
|                 } | ||||
|             }) | ||||
|             .collect() | ||||
|     } | ||||
|  | ||||
|     #[instrument(level = "trace", skip(self), ret)] | ||||
|     async fn validate_dial_info( | ||||
|         &self, | ||||
|         node_ref: NodeRef, | ||||
|         dial_info: DialInfo, | ||||
|         redirect: bool, | ||||
|     ) -> bool { | ||||
|         let rpc = self.routing_table.rpc_processor(); | ||||
|  | ||||
|         // asking for node validation doesn't have to use the dial info filter of the dial info we are validating | ||||
|         let mut node_ref = node_ref.clone(); | ||||
|         node_ref.set_filter(None); | ||||
|  | ||||
|         // ask the node to send us a dial info validation receipt | ||||
|         let out = rpc | ||||
|             .rpc_call_validate_dial_info(node_ref.clone(), dial_info, redirect) | ||||
|             .await | ||||
|             .map_err(logthru_net!( | ||||
|                 "failed to send validate_dial_info to {:?}", | ||||
|                 node_ref | ||||
|             )) | ||||
|             .unwrap_or(false); | ||||
|         out | ||||
|     } | ||||
|  | ||||
|     #[instrument(level = "trace", skip(self), ret)] | ||||
|     async fn try_upnp_port_mapping(&self) -> Option<DialInfo> { | ||||
|         let (pt, llpt, at, external_address_1, node_1, local_port) = { | ||||
|             let inner = self.inner.lock(); | ||||
|             let pt = inner.protocol_type.unwrap(); | ||||
|             let llpt = pt.low_level_protocol_type(); | ||||
|             let at = inner.address_type.unwrap(); | ||||
|             let external_address_1 = inner.external_1_address.unwrap(); | ||||
|             let node_1 = inner.node_1.as_ref().unwrap().clone(); | ||||
|             let local_port = self.net.get_local_port(pt).unwrap(); | ||||
|             (pt, llpt, at, external_address_1, node_1, local_port) | ||||
|         }; | ||||
|  | ||||
|         let mut tries = 0; | ||||
|         loop { | ||||
|             tries += 1; | ||||
|  | ||||
|             // Attempt a port mapping. If this doesn't succeed, it's not going to | ||||
|             let Some(mapped_external_address) = self | ||||
|                 .net | ||||
|                 .unlocked_inner | ||||
|                 .igd_manager | ||||
|                 .map_any_port(llpt, at, local_port, Some(external_address_1.to_ip_addr())) | ||||
|                 .await else | ||||
|             { | ||||
|                 return None; | ||||
|             }; | ||||
|  | ||||
|             // Make dial info from the port mapping | ||||
|             let external_mapped_dial_info = | ||||
|                 self.make_dial_info(SocketAddress::from_socket_addr(mapped_external_address), pt); | ||||
|  | ||||
|             // Attempt to validate the port mapping | ||||
|             let mut validate_tries = 0; | ||||
|             loop { | ||||
|                 validate_tries += 1; | ||||
|  | ||||
|                 // Ensure people can reach us. If we're firewalled off, this is useless | ||||
|                 if self | ||||
|                     .validate_dial_info(node_1.clone(), external_mapped_dial_info.clone(), false) | ||||
|                     .await | ||||
|                 { | ||||
|                     return Some(external_mapped_dial_info); | ||||
|                 } | ||||
|  | ||||
|                 if validate_tries == PORT_MAP_VALIDATE_TRY_COUNT { | ||||
|                     log_net!(debug "UPNP port mapping succeeded but port {}/{} is still unreachable.\nretrying\n", | ||||
|                     local_port, match llpt { | ||||
|                         LowLevelProtocolType::UDP => "udp", | ||||
|                         LowLevelProtocolType::TCP => "tcp", | ||||
|                     }); | ||||
|                     sleep(PORT_MAP_VALIDATE_DELAY_MS).await | ||||
|                 } else { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Release the mapping if we're still unreachable | ||||
|             let _ = self | ||||
|                 .net | ||||
|                 .unlocked_inner | ||||
|                 .igd_manager | ||||
|                 .unmap_port(llpt, at, external_address_1.port()) | ||||
|                 .await; | ||||
|  | ||||
|             if tries == PORT_MAP_TRY_COUNT { | ||||
|                 warn!("UPNP port mapping succeeded but port {}/{} is still unreachable.\nYou may need to add a local firewall allowed port on this machine.\n", | ||||
|                     local_port, match llpt { | ||||
|                         LowLevelProtocolType::UDP => "udp", | ||||
|                         LowLevelProtocolType::TCP => "tcp", | ||||
|                     } | ||||
|                 ); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         None | ||||
|     } | ||||
|  | ||||
|     #[instrument(level = "trace", skip(self), ret)] | ||||
|     async fn try_port_mapping(&self) -> Option<DialInfo> { | ||||
|         let enable_upnp = { | ||||
|             let c = self.net.config.get(); | ||||
|             c.network.upnp | ||||
|         }; | ||||
|  | ||||
|         if enable_upnp { | ||||
|             return self.try_upnp_port_mapping().await; | ||||
|         } | ||||
|  | ||||
|         None | ||||
|     } | ||||
|  | ||||
|     fn make_dial_info(&self, addr: SocketAddress, protocol_type: ProtocolType) -> DialInfo { | ||||
|         match protocol_type { | ||||
|             ProtocolType::UDP => DialInfo::udp(addr), | ||||
|             ProtocolType::TCP => DialInfo::tcp(addr), | ||||
|             ProtocolType::WS => { | ||||
|                 let c = self.net.config.get(); | ||||
|                 DialInfo::try_ws( | ||||
|                     addr, | ||||
|                     format!("ws://{}/{}", addr, c.network.protocol.ws.path), | ||||
|                 ) | ||||
|                 .unwrap() | ||||
|             } | ||||
|             ProtocolType::WSS => panic!("none of the discovery functions are used for wss"), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /////// | ||||
|     // Per-protocol discovery routines | ||||
|  | ||||
|     #[instrument(level = "trace", skip(self))] | ||||
|     pub fn protocol_begin(&self, protocol_type: ProtocolType, address_type: AddressType) { | ||||
|         // Get our interface addresses | ||||
|         let intf_addrs = self.get_local_addresses(protocol_type, address_type); | ||||
|  | ||||
|         let mut inner = self.inner.lock(); | ||||
|         inner.intf_addrs = Some(intf_addrs); | ||||
|         inner.protocol_type = Some(protocol_type); | ||||
|         inner.address_type = Some(address_type); | ||||
|         inner.external_1_dial_info = None; | ||||
|         inner.external_1_address = None; | ||||
|         inner.node_1 = None; | ||||
|     } | ||||
|  | ||||
|     // Get our first node's view of our external IP address via normal RPC | ||||
|     #[instrument(level = "trace", skip(self), ret)] | ||||
|     pub async fn protocol_get_external_address_1(&self) -> bool { | ||||
|         let (protocol_type, address_type) = { | ||||
|             let inner = self.inner.lock(); | ||||
|             (inner.protocol_type.unwrap(), inner.address_type.unwrap()) | ||||
|         }; | ||||
|  | ||||
|         // Get our external address from some fast node, call it node 1 | ||||
|         let (external_1, node_1) = match self | ||||
|             .discover_external_address(protocol_type, address_type, None) | ||||
|             .await | ||||
|         { | ||||
|             None => { | ||||
|                 // If we can't get an external address, exit but don't throw an error so we can try again later | ||||
|                 log_net!(debug "couldn't get external address 1 for {:?} {:?}", protocol_type, address_type); | ||||
|                 return false; | ||||
|             } | ||||
|             Some(v) => v, | ||||
|         }; | ||||
|         let external_1_dial_info = self.make_dial_info(external_1, protocol_type); | ||||
|  | ||||
|         let mut inner = self.inner.lock(); | ||||
|         inner.external_1_dial_info = Some(external_1_dial_info); | ||||
|         inner.external_1_address = Some(external_1); | ||||
|         inner.node_1 = Some(node_1); | ||||
|  | ||||
|         log_net!(debug | ||||
|             "external_1_dial_info: {:?}\nexternal_1_address: {:?}\nnode_1: {:?}", | ||||
|             inner.external_1_dial_info, inner.external_1_address, inner.node_1 | ||||
|         ); | ||||
|  | ||||
|         true | ||||
|     } | ||||
|  | ||||
|     // 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<()> { | ||||
|         // 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 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<bool> { | ||||
|         // Get the external dial info for our use here | ||||
|         let (node_1, external_1_dial_info, protocol_type) = { | ||||
|             let inner = self.inner.lock(); | ||||
|             ( | ||||
|                 inner.node_1.as_ref().unwrap().clone(), | ||||
|                 inner.external_1_dial_info.as_ref().unwrap().clone(), | ||||
|                 inner.protocol_type.unwrap(), | ||||
|             ) | ||||
|         }; | ||||
|  | ||||
|         // 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 | ||||
|                         // 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); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 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 | ||||
|             // 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 | ||||
|  | ||||
|                 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); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // We are restricted, determine what kind of restriction | ||||
|         // Get the external dial info for our use here | ||||
|         let (external_1_address, address_type) = { | ||||
|             let inner = self.inner.lock(); | ||||
|             ( | ||||
|                 inner.external_1_address.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())) | ||||
|             .await | ||||
|         { | ||||
|             None => { | ||||
|                 // If we can't get an external address, allow retry | ||||
|                 log_net!(debug "failed to discover external address 2 for {:?}:{:?}, skipping node {:?}", protocol_type, address_type, node_1); | ||||
|                 return Ok(false); | ||||
|             } | ||||
|             Some(v) => v, | ||||
|         }; | ||||
|  | ||||
|         log_net!(debug | ||||
|             "external_2_address: {:?}\nnode_2: {:?}", | ||||
|             external_2_address, node_2 | ||||
|         ); | ||||
|  | ||||
|         // If we have two different external addresses, then this is a symmetric NAT | ||||
|         if external_2_address != external_1_address { | ||||
|             // Symmetric NAT is outbound only, no public dial info will work | ||||
|             self.set_detected_network_class(NetworkClass::OutboundOnly); | ||||
|  | ||||
|             // No more retries | ||||
|             return Ok(true); | ||||
|         } | ||||
|  | ||||
|         // If we're going to end up as a restricted NAT of some sort | ||||
|         // Address is the same, so it's address or port restricted | ||||
|  | ||||
|         // Do a validate_dial_info on the external address from a random port | ||||
|         if self | ||||
|             .validate_dial_info(node_2.clone(), external_1_dial_info.clone(), false) | ||||
|             .await | ||||
|         { | ||||
|             // Got a reply from a non-default port, which means we're only address restricted | ||||
|             self.set_detected_public_dial_info( | ||||
|                 external_1_dial_info, | ||||
|                 DialInfoClass::AddressRestrictedNAT, | ||||
|             ); | ||||
|         } else { | ||||
|             // Didn't get a reply from a non-default port, which means we are also port restricted | ||||
|             self.set_detected_public_dial_info( | ||||
|                 external_1_dial_info, | ||||
|                 DialInfoClass::PortRestrictedNAT, | ||||
|             ); | ||||
|         } | ||||
|         self.set_detected_network_class(NetworkClass::InboundCapable); | ||||
|  | ||||
|         // Allow another retry because sometimes trying again will get us Full Cone NAT instead | ||||
|         Ok(false) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Network { | ||||
|     #[instrument(level = "trace", skip(self, context), err)] | ||||
|     pub async fn update_protocol_dialinfo( | ||||
| @@ -670,6 +20,25 @@ impl Network { | ||||
|         // Start doing protocol | ||||
|         context.protocol_begin(protocol_type, address_type); | ||||
|  | ||||
|         // 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); | ||||
|  | ||||
|         // Loop for restricted NAT retries | ||||
|         loop { | ||||
|             log_net!(debug | ||||
| @@ -685,22 +54,21 @@ impl Network { | ||||
|             } | ||||
|  | ||||
|             // If our local interface list contains external_1 then there is no NAT in place | ||||
|             { | ||||
|                 let res = { | ||||
|                     let inner = context.inner.lock(); | ||||
|                     inner | ||||
|                         .intf_addrs | ||||
|                         .as_ref() | ||||
|                         .unwrap() | ||||
|                         .contains(inner.external_1_address.as_ref().unwrap()) | ||||
|                 }; | ||||
|                 if res { | ||||
|                     // No NAT | ||||
|                     context.protocol_process_no_nat().await?; | ||||
|  | ||||
|                     // No more retries | ||||
|                     break; | ||||
|                 } | ||||
|             let res = { | ||||
|                 let inner = context.inner.lock(); | ||||
|                 inner | ||||
|                     .intf_addrs | ||||
|                     .as_ref() | ||||
|                     .unwrap() | ||||
|                     .contains(inner.external_1_address.as_ref().unwrap()) | ||||
|             }; | ||||
|             if res { | ||||
|                 // No NAT | ||||
|                 context.protocol_process_no_nat().await?; | ||||
|  | ||||
|                 // No more retries | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             // There is -some NAT- | ||||
| @@ -719,6 +87,11 @@ impl Network { | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     #[instrument(level = "trace", skip(self), err)] | ||||
|     pub async fn update_with_discovery_context(&self, ctx: DiscoveryContext) -> EyreResult<()> { | ||||
|         // | ||||
|     } | ||||
|  | ||||
|     #[instrument(level = "trace", skip(self), err)] | ||||
|     pub async fn do_public_dial_info_check( | ||||
|         &self, | ||||
| @@ -745,12 +118,12 @@ impl Network { | ||||
|         }; | ||||
|  | ||||
|         // Process all protocol and address combinations | ||||
|         let mut futures = FuturesUnordered::new(); | ||||
|         let mut unord = FuturesUnordered::new(); | ||||
|         // Do UDPv4+v6 at the same time as everything else | ||||
|         if protocol_config.inbound.contains(ProtocolType::UDP) { | ||||
|             // UDPv4 | ||||
|             if protocol_config.family_global.contains(AddressType::IPV4) { | ||||
|                 futures.push( | ||||
|                 unord.push( | ||||
|                     async { | ||||
|                         let udpv4_context = | ||||
|                             DiscoveryContext::new(self.routing_table(), self.clone()); | ||||
| @@ -765,7 +138,7 @@ impl Network { | ||||
|                             log_net!(debug "Failed UDPv4 dialinfo discovery: {}", e); | ||||
|                             return None; | ||||
|                         } | ||||
|                         Some(vec![udpv4_context]) | ||||
|                         Some(udpv4_context) | ||||
|                     } | ||||
|                     .instrument(trace_span!("do_public_dial_info_check UDPv4")) | ||||
|                     .boxed(), | ||||
| @@ -774,7 +147,7 @@ impl Network { | ||||
|  | ||||
|             // UDPv6 | ||||
|             if protocol_config.family_global.contains(AddressType::IPV6) { | ||||
|                 futures.push( | ||||
|                 unord.push( | ||||
|                     async { | ||||
|                         let udpv6_context = | ||||
|                             DiscoveryContext::new(self.routing_table(), self.clone()); | ||||
| @@ -789,7 +162,7 @@ impl Network { | ||||
|                             log_net!(debug "Failed UDPv6 dialinfo discovery: {}", e); | ||||
|                             return None; | ||||
|                         } | ||||
|                         Some(vec![udpv6_context]) | ||||
|                         Some(udpv6_context) | ||||
|                     } | ||||
|                     .instrument(trace_span!("do_public_dial_info_check UDPv6")) | ||||
|                     .boxed(), | ||||
| @@ -800,7 +173,7 @@ impl Network { | ||||
|         // Do TCPv4. Possibly do WSv4 if it is on a different port | ||||
|         if protocol_config.family_global.contains(AddressType::IPV4) { | ||||
|             if protocol_config.inbound.contains(ProtocolType::TCP) { | ||||
|                 futures.push( | ||||
|                 unord.push( | ||||
|                     async { | ||||
|                         // TCPv4 | ||||
|                         let tcpv4_context = | ||||
| @@ -816,7 +189,7 @@ impl Network { | ||||
|                             log_net!(debug "Failed TCPv4 dialinfo discovery: {}", e); | ||||
|                             return None; | ||||
|                         } | ||||
|                         Some(vec![tcpv4_context]) | ||||
|                         Some(tcpv4_context) | ||||
|                     } | ||||
|                     .instrument(trace_span!("do_public_dial_info_check TCPv4")) | ||||
|                     .boxed(), | ||||
| @@ -824,7 +197,7 @@ impl Network { | ||||
|             } | ||||
|  | ||||
|             if protocol_config.inbound.contains(ProtocolType::WS) && !tcp_same_port { | ||||
|                 futures.push( | ||||
|                 unord.push( | ||||
|                     async { | ||||
|                         // WSv4 | ||||
|                         let wsv4_context = | ||||
| @@ -840,7 +213,7 @@ impl Network { | ||||
|                             log_net!(debug "Failed WSv4 dialinfo discovery: {}", e); | ||||
|                             return None; | ||||
|                         } | ||||
|                         Some(vec![wsv4_context]) | ||||
|                         Some(wsv4_context) | ||||
|                     } | ||||
|                     .instrument(trace_span!("do_public_dial_info_check WSv4")) | ||||
|                     .boxed(), | ||||
| @@ -851,7 +224,7 @@ impl Network { | ||||
|         // Do TCPv6. Possibly do WSv6 if it is on a different port | ||||
|         if protocol_config.family_global.contains(AddressType::IPV6) { | ||||
|             if protocol_config.inbound.contains(ProtocolType::TCP) { | ||||
|                 futures.push( | ||||
|                 unord.push( | ||||
|                     async { | ||||
|                         // TCPv6 | ||||
|                         let tcpv6_context = | ||||
| @@ -867,7 +240,7 @@ impl Network { | ||||
|                             log_net!(debug "Failed TCPv6 dialinfo discovery: {}", e); | ||||
|                             return None; | ||||
|                         } | ||||
|                         Some(vec![tcpv6_context]) | ||||
|                         Some(tcpv6_context) | ||||
|                     } | ||||
|                     .instrument(trace_span!("do_public_dial_info_check TCPv6")) | ||||
|                     .boxed(), | ||||
| @@ -876,7 +249,7 @@ impl Network { | ||||
|  | ||||
|             // WSv6 | ||||
|             if protocol_config.inbound.contains(ProtocolType::WS) && !tcp_same_port { | ||||
|                 futures.push( | ||||
|                 unord.push( | ||||
|                     async { | ||||
|                         let wsv6_context = | ||||
|                             DiscoveryContext::new(self.routing_table(), self.clone()); | ||||
| @@ -891,7 +264,7 @@ impl Network { | ||||
|                             log_net!(debug "Failed WSv6 dialinfo discovery: {}", e); | ||||
|                             return None; | ||||
|                         } | ||||
|                         Some(vec![wsv6_context]) | ||||
|                         Some(wsv6_context) | ||||
|                     } | ||||
|                     .instrument(trace_span!("do_public_dial_info_check WSv6")) | ||||
|                     .boxed(), | ||||
| @@ -902,23 +275,22 @@ impl Network { | ||||
|         // Wait for all discovery futures to complete and collect contexts | ||||
|         let mut contexts = Vec::<DiscoveryContext>::new(); | ||||
|         let mut new_network_class = Option::<NetworkClass>::None; | ||||
|  | ||||
|         loop { | ||||
|             match futures.next().timeout_at(stop_token.clone()).await { | ||||
|                 Ok(Some(ctxvec)) => { | ||||
|                     if let Some(ctxvec) = ctxvec { | ||||
|                         for ctx in ctxvec { | ||||
|                             if let Some(nc) = ctx.inner.lock().detected_network_class { | ||||
|                                 if let Some(last_nc) = new_network_class { | ||||
|                                     if nc < last_nc { | ||||
|                                         new_network_class = Some(nc); | ||||
|                                     } | ||||
|                                 } else { | ||||
|             match unord.next().timeout_at(stop_token.clone()).await { | ||||
|                 Ok(Some(ctx)) => { | ||||
|                     if let Some(ctx) = ctx { | ||||
|                         if let Some(nc) = ctx.inner.lock().detected_network_class { | ||||
|                             if let Some(last_nc) = new_network_class { | ||||
|                                 if nc < last_nc { | ||||
|                                     new_network_class = Some(nc); | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 new_network_class = Some(nc); | ||||
|                             } | ||||
|  | ||||
|                             contexts.push(ctx); | ||||
|                         } | ||||
|  | ||||
|                         contexts.push(ctx); | ||||
|                     } | ||||
|                 } | ||||
|                 Ok(None) => { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user