From 1b5934dad48f4ea946f4466bd79555314dd84236 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Sun, 3 Sep 2023 13:07:36 -0400 Subject: [PATCH 01/10] public address detection refactoring --- veilid-core/src/network_manager/native/mod.rs | 2 + .../native/network_class_discovery.rs | 754 ++---------------- 2 files changed, 65 insertions(+), 691 deletions(-) diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index 1cc1e7b1..8dc42508 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -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; 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 6432e058..85fd20c5 100644 --- a/veilid-core/src/network_manager/native/network_class_discovery.rs +++ b/veilid-core/src/network_manager/native/network_class_discovery.rs @@ -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>, - protocol_type: Option, - address_type: Option, - // first node contacted - external_1_dial_info: Option, - external_1_address: Option, - node_1: Option, - // detected public dialinfo - detected_network_class: Option, - 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 { - 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 { - 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, - ) -> 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>| { - 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>| { - 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>| { - 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 { - 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 { - 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 { - 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> = 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 { - // 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> = 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); - } - } - - // 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 - - 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> = 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::::new(); let mut new_network_class = Option::::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) => { From 80b2e7b9da78ba7b072038401da5f1d671f33764 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 4 Sep 2023 13:33:27 -0400 Subject: [PATCH 02/10] pad work --- veilid-core/src/network_manager/native/mod.rs | 4 +- .../native/network_class_discovery.rs | 479 +++++++----------- .../routing_table/routing_domain_editor.rs | 31 +- .../src/routing_table/routing_domains.rs | 17 +- 4 files changed, 223 insertions(+), 308 deletions(-) diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index 8dc42508..b3bbe8d8 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -929,14 +929,14 @@ impl Network { routing_table .edit_routing_domain(RoutingDomain::PublicInternet) - .clear_dial_info_details() + .clear_dial_info_details(None, None) .set_network_class(None) .clear_relay_node() .commit(); routing_table .edit_routing_domain(RoutingDomain::LocalNetwork) - .clear_dial_info_details() + .clear_dial_info_details(None, None) .set_network_class(None) .clear_relay_node() .commit(); 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 85fd20c5..da87b05d 100644 --- a/veilid-core/src/network_manager/native/network_class_discovery.rs +++ b/veilid-core/src/network_manager/native/network_class_discovery.rs @@ -1,95 +1,96 @@ /// 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; impl Network { - #[instrument(level = "trace", skip(self, context), err)] - pub async fn update_protocol_dialinfo( - &self, - context: &DiscoveryContext, - protocol_type: ProtocolType, - address_type: AddressType, - ) -> EyreResult<()> { - let mut retry_count = { - let c = self.config.get(); - c.network.restricted_nat_retries - }; + #[instrument(level = "trace", skip(self), err)] + pub async fn update_with_detected_dial_info(&self, ddi: DetectedDialInfo) -> EyreResult<()> { + let existing_network_class = self + .routing_table() + .get_network_class(RoutingDomain::PublicInternet) + .unwrap_or_default(); - // Start doing protocol - context.protocol_begin(protocol_type, address_type); - - // 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); - - // Loop for restricted NAT retries - loop { - log_net!(debug - "=== update_protocol_dialinfo {:?} {:?} tries_left={} ===", - address_type, - protocol_type, - retry_count + // get existing dial info into table by protocol/address type + let mut existing_dial_info = BTreeMap::<(ProtocolType, AddressType), DialInfoDetail>::new(); + for did in self.routing_table().all_filtered_dial_info_details( + RoutingDomain::PublicInternet.into(), + &DialInfoFilter::all(), + ) { + // Only need to keep one per pt/at pair, since they will all have the same dialinfoclass + existing_dial_info.insert( + (did.dial_info.protocol_type(), did.dial_info.address_type()), + did, ); - // Get our external address from some fast node, call it node 1 - if !context.protocol_get_external_address_1().await { - // If we couldn't get an external address, then we should just try the whole network class detection again later - return Ok(()); - } - - // 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; - } - - // There is -some NAT- - if context.protocol_process_nat().await? { - // We either got dial info or a network class without one - break; - } - - // If we tried everything, break anyway after N attempts - if retry_count == 0 { - break; - } - retry_count -= 1; } - Ok(()) - } + match ddi { + DetectedDialInfo::SymmetricNAT => { + // If we get any symmetric nat dialinfo, this whole network class is outbound only, + // and all dial info should be treated as invalid + if !matches!(existing_network_class, NetworkClass::OutboundOnly) { + let mut editor = self + .routing_table() + .edit_routing_domain(RoutingDomain::PublicInternet); - #[instrument(level = "trace", skip(self), err)] - pub async fn update_with_discovery_context(&self, ctx: DiscoveryContext) -> EyreResult<()> { - // + editor.clear_dial_info_details(None, None); + editor.set_network_class(Some(NetworkClass::OutboundOnly)); + editor.commit(); + } + } + DetectedDialInfo::Detected(did) => { + // We got a dial info, upgrade everything unless we are fixed to outbound only due to a symmetric nat + if !matches!(existing_network_class, NetworkClass::OutboundOnly) { + // Get existing dial info for protocol/address type combination + let pt = did.dial_info.protocol_type(); + let at = did.dial_info.address_type(); + + // See what operations to perform with this dialinfo + let mut clear = false; + let mut add = false; + + if let Some(edi) = existing_dial_info.get(&(pt, at)) { + if did.class < edi.class { + // Better dial info class was found, clear existing dialinfo for this pt/at pair + clear = true; + add = true; + } else if did.class == edi.class { + // Same dial info class, just add dial info + add = true; + } + // Otherwise, don't upgrade, don't add, this is worse than what we have already + } else { + // No existing dial info of this type accept it, no need to upgrade, but add it + add = true; + } + + if clear || add { + let mut editor = self + .routing_table() + .edit_routing_domain(RoutingDomain::PublicInternet); + + if clear { + editor.clear_dial_info_details( + Some(did.dial_info.address_type()), + Some(did.dial_info.protocol_type()), + ); + } + + if add { + if let Err(e) = + editor.register_dial_info(did.dial_info.clone(), did.class) + { + log_net!(debug "Failed to register detected dialinfo {:?}: {}", did, e); + } + } + + editor.set_network_class(Some(NetworkClass::InboundCapable)); + editor.commit(); + } + } + } + } + Ok(()) } #[instrument(level = "trace", skip(self), err)] @@ -99,14 +100,10 @@ impl Network { _l: u64, _t: u64, ) -> EyreResult<()> { - let routing_table = self.routing_table(); - // Figure out if we can optimize TCP/WS checking since they are often on the same port - let (protocol_config, existing_network_class, tcp_same_port) = { + let (protocol_config, tcp_same_port) = { let inner = self.inner.lock(); let protocol_config = inner.protocol_config; - let existing_network_class = - routing_table.get_network_class(RoutingDomain::PublicInternet); let tcp_same_port = if protocol_config.inbound.contains(ProtocolType::TCP) && protocol_config.inbound.contains(ProtocolType::WS) { @@ -114,187 +111,133 @@ impl Network { } else { false }; - (protocol_config, existing_network_class, tcp_same_port) + (protocol_config, tcp_same_port) }; + // Save off existing public dial info for change detection later + let existing_public_dial_info: HashSet = self + .routing_table() + .all_filtered_dial_info_details( + RoutingDomain::PublicInternet.into(), + &DialInfoFilter::all(), + ) + .into_iter() + .collect(); + + // Clear public dialinfo and network class in prep for discovery + let mut editor = self + .routing_table() + .edit_routing_domain(RoutingDomain::PublicInternet); + editor.clear_dial_info_details(None, None); + editor.set_network_class(None); + editor.clear_relay_node(); + editor.commit(); + log_net!(debug "PublicInternet network class cleared"); + // Process all protocol and address combinations 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) { - unord.push( - async { - let udpv4_context = - DiscoveryContext::new(self.routing_table(), self.clone()); - if let Err(e) = self - .update_protocol_dialinfo( - &udpv4_context, - ProtocolType::UDP, - AddressType::IPV4, - ) - .await - { - log_net!(debug "Failed UDPv4 dialinfo discovery: {}", e); - return None; - } - Some(udpv4_context) - } - .instrument(trace_span!("do_public_dial_info_check UDPv4")) - .boxed(), + let udpv4_context = DiscoveryContext::new( + self.routing_table(), + self.clone(), + ProtocolType::UDP, + AddressType::IPV4, ); + udpv4_context + .discover(&mut unord) + .instrument(trace_span!("udpv4_context.discover")) + .await; } // UDPv6 if protocol_config.family_global.contains(AddressType::IPV6) { - unord.push( - async { - let udpv6_context = - DiscoveryContext::new(self.routing_table(), self.clone()); - if let Err(e) = self - .update_protocol_dialinfo( - &udpv6_context, - ProtocolType::UDP, - AddressType::IPV6, - ) - .await - { - log_net!(debug "Failed UDPv6 dialinfo discovery: {}", e); - return None; - } - Some(udpv6_context) - } - .instrument(trace_span!("do_public_dial_info_check UDPv6")) - .boxed(), + let udpv6_context = DiscoveryContext::new( + self.routing_table(), + self.clone(), + ProtocolType::UDP, + AddressType::IPV6, ); + udpv6_context + .discover(&mut unord) + .instrument(trace_span!("udpv6_context.discover")) + .await; } } // 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) { - unord.push( - async { - // TCPv4 - let tcpv4_context = - DiscoveryContext::new(self.routing_table(), self.clone()); - if let Err(e) = self - .update_protocol_dialinfo( - &tcpv4_context, - ProtocolType::TCP, - AddressType::IPV4, - ) - .await - { - log_net!(debug "Failed TCPv4 dialinfo discovery: {}", e); - return None; - } - Some(tcpv4_context) - } - .instrument(trace_span!("do_public_dial_info_check TCPv4")) - .boxed(), + let tcpv4_context = DiscoveryContext::new( + self.routing_table(), + self.clone(), + ProtocolType::TCP, + AddressType::IPV4, ); + tcpv4_context + .discover(&mut unord) + .instrument(trace_span!("tcpv4_context.discover")) + .await; } if protocol_config.inbound.contains(ProtocolType::WS) && !tcp_same_port { - unord.push( - async { - // WSv4 - let wsv4_context = - DiscoveryContext::new(self.routing_table(), self.clone()); - if let Err(e) = self - .update_protocol_dialinfo( - &wsv4_context, - ProtocolType::WS, - AddressType::IPV4, - ) - .await - { - log_net!(debug "Failed WSv4 dialinfo discovery: {}", e); - return None; - } - Some(wsv4_context) - } - .instrument(trace_span!("do_public_dial_info_check WSv4")) - .boxed(), + let wsv4_context = DiscoveryContext::new( + self.routing_table(), + self.clone(), + ProtocolType::WS, + AddressType::IPV4, ); + wsv4_context + .discover(&mut unord) + .instrument(trace_span!("wsv4_context.discover")) + .await; } } // 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) { - unord.push( - async { - // TCPv6 - let tcpv6_context = - DiscoveryContext::new(self.routing_table(), self.clone()); - if let Err(e) = self - .update_protocol_dialinfo( - &tcpv6_context, - ProtocolType::TCP, - AddressType::IPV6, - ) - .await - { - log_net!(debug "Failed TCPv6 dialinfo discovery: {}", e); - return None; - } - Some(tcpv6_context) - } - .instrument(trace_span!("do_public_dial_info_check TCPv6")) - .boxed(), + let tcpv6_context = DiscoveryContext::new( + self.routing_table(), + self.clone(), + ProtocolType::TCP, + AddressType::IPV6, ); + tcpv6_context + .discover(&mut unord) + .instrument(trace_span!("tcpv6_context.discover")) + .await; } // WSv6 if protocol_config.inbound.contains(ProtocolType::WS) && !tcp_same_port { - unord.push( - async { - let wsv6_context = - DiscoveryContext::new(self.routing_table(), self.clone()); - if let Err(e) = self - .update_protocol_dialinfo( - &wsv6_context, - ProtocolType::WS, - AddressType::IPV6, - ) - .await - { - log_net!(debug "Failed WSv6 dialinfo discovery: {}", e); - return None; - } - Some(wsv6_context) - } - .instrument(trace_span!("do_public_dial_info_check WSv6")) - .boxed(), + let wsv6_context = DiscoveryContext::new( + self.routing_table(), + self.clone(), + ProtocolType::WS, + AddressType::IPV6, ); + wsv6_context + .discover(&mut unord) + .instrument(trace_span!("wsv6_context.discover")) + .await; } } - // Wait for all discovery futures to complete and collect contexts - let mut contexts = Vec::::new(); - let mut new_network_class = Option::::None; - + // Wait for all discovery futures to complete and apply discoverycontexts loop { 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); - } + Ok(Some(Some(ddi))) => { + // Found some new dial info for this protocol/address combination + self.update_with_detected_dial_info(ddi).await? + } + Ok(Some(None)) => { + // Found no new dial info for this protocol/address combination } Ok(None) => { - // Normal completion + // All done, normally break; } Err(_) => { @@ -304,85 +247,21 @@ impl Network { } } - // If a network class could be determined - // see about updating our public dial info - let mut changed = false; - let mut editor = routing_table.edit_routing_domain(RoutingDomain::PublicInternet); - if new_network_class.is_some() { - // Get existing public dial info - let existing_public_dial_info: HashSet = routing_table - .all_filtered_dial_info_details( - RoutingDomain::PublicInternet.into(), - &DialInfoFilter::all(), - ) - .into_iter() - .collect(); - - // Get new public dial info and ensure it is valid - let mut new_public_dial_info: HashSet = HashSet::new(); - for ctx in contexts { - let inner = ctx.inner.lock(); - if let Some(pdi) = &inner.detected_public_dial_info { - if routing_table - .ensure_dial_info_is_valid(RoutingDomain::PublicInternet, &pdi.dial_info) - { - new_public_dial_info.insert(DialInfoDetail { - class: pdi.class, - dial_info: pdi.dial_info.clone(), - }); - } - - // duplicate for same port - if tcp_same_port && pdi.dial_info.protocol_type() == ProtocolType::TCP { - let ws_dial_info = - ctx.make_dial_info(pdi.dial_info.socket_address(), ProtocolType::WS); - if routing_table - .ensure_dial_info_is_valid(RoutingDomain::PublicInternet, &ws_dial_info) - { - new_public_dial_info.insert(DialInfoDetail { - class: pdi.class, - dial_info: ws_dial_info, - }); - } - } - } - } - - // Is the public dial info different? - if existing_public_dial_info != new_public_dial_info { - // If so, clear existing public dial info and re-register the new public dial info - editor.clear_dial_info_details(); - for did in new_public_dial_info { - if let Err(e) = editor.register_dial_info(did.dial_info, did.class) { - log_net!(error "Failed to register detected public dial info: {}", e); - } - } - changed = true; - } - - // Is the network class different? - if existing_network_class != new_network_class { - editor.set_network_class(new_network_class); - changed = true; - log_net!(debug "PublicInternet network class changed to {:?}", new_network_class); - } - } else if existing_network_class.is_some() { - // Network class could not be determined - editor.clear_dial_info_details(); - editor.set_network_class(None); - editor.clear_relay_node(); - changed = true; - log_net!(debug "PublicInternet network class cleared"); - } + // All done, see if things changed + let new_public_dial_info: HashSet = self + .routing_table() + .all_filtered_dial_info_details( + RoutingDomain::PublicInternet.into(), + &DialInfoFilter::all(), + ) + .into_iter() + .collect(); // Punish nodes that told us our public address had changed when it didn't - if !changed { + if new_public_dial_info == existing_public_dial_info { if let Some(punish) = self.inner.lock().public_dial_info_check_punishment.take() { punish(); } - } else { - // Commit updates - editor.commit(); } Ok(()) @@ -398,7 +277,11 @@ impl Network { let out = self.do_public_dial_info_check(stop_token, l, t).await; // Done with public dial info check - self.inner.lock().needs_public_dial_info_check = false; + { + let mut inner = self.inner.lock(); + inner.needs_public_dial_info_check = false; + inner.public_dial_info_check_punishment = None; + } out } diff --git a/veilid-core/src/routing_table/routing_domain_editor.rs b/veilid-core/src/routing_table/routing_domain_editor.rs index 898e1b82..4f9c6ac7 100644 --- a/veilid-core/src/routing_table/routing_domain_editor.rs +++ b/veilid-core/src/routing_table/routing_domain_editor.rs @@ -1,7 +1,10 @@ use super::*; enum RoutingDomainChange { - ClearDialInfoDetails, + ClearDialInfoDetails { + address_type: Option, + protocol_type: Option, + }, ClearRelayNode, SetRelayNode { relay_node: NodeRef, @@ -39,8 +42,16 @@ impl RoutingDomainEditor { } #[instrument(level = "debug", skip(self))] - pub fn clear_dial_info_details(&mut self) -> &mut Self { - self.changes.push(RoutingDomainChange::ClearDialInfoDetails); + pub fn clear_dial_info_details( + &mut self, + address_type: Option, + protocol_type: Option, + ) -> &mut Self { + self.changes + .push(RoutingDomainChange::ClearDialInfoDetails { + address_type, + protocol_type, + }); self } #[instrument(level = "debug", skip(self))] @@ -125,9 +136,17 @@ impl RoutingDomainEditor { inner.with_routing_domain_mut(self.routing_domain, |detail| { for change in self.changes.drain(..) { match change { - RoutingDomainChange::ClearDialInfoDetails => { - debug!("[{:?}] cleared dial info details", self.routing_domain); - detail.common_mut().clear_dial_info_details(); + RoutingDomainChange::ClearDialInfoDetails { + address_type, + protocol_type, + } => { + debug!( + "[{:?}] cleared dial info details: at={:?} pt={:?}", + self.routing_domain, address_type, protocol_type + ); + detail + .common_mut() + .clear_dial_info_details(address_type, protocol_type); changed = true; } RoutingDomainChange::ClearRelayNode => { diff --git a/veilid-core/src/routing_table/routing_domains.rs b/veilid-core/src/routing_table/routing_domains.rs index 6e4bec50..55bcef67 100644 --- a/veilid-core/src/routing_table/routing_domains.rs +++ b/veilid-core/src/routing_table/routing_domains.rs @@ -103,8 +103,21 @@ impl RoutingDomainDetailCommon { pub fn dial_info_details(&self) -> &Vec { &self.dial_info_details } - pub(super) fn clear_dial_info_details(&mut self) { - self.dial_info_details.clear(); + pub(super) fn clear_dial_info_details(&mut self, address_type: Option, protocol_type: Option) { + self.dial_info_details.retain_mut(|e| { + let mut remove = true; + if let Some(pt) = protocol_type { + if pt != e.dial_info.protocol_type() { + remove = false; + } + } + if let Some(at) = address_type { + if at != e.dial_info.address_type() { + remove = false; + } + } + !remove + }); self.clear_cache(); } pub(super) fn add_dial_info_detail(&mut self, did: DialInfoDetail) { From 3dfb612e6f600030514a600a552d64309cf0bd2e Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 4 Sep 2023 13:52:43 -0400 Subject: [PATCH 03/10] fix ws --- .../native/network_class_discovery.rs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) 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 da87b05d..60036fd8 100644 --- a/veilid-core/src/network_manager/native/network_class_discovery.rs +++ b/veilid-core/src/network_manager/native/network_class_discovery.rs @@ -231,7 +231,23 @@ impl Network { match unord.next().timeout_at(stop_token.clone()).await { Ok(Some(Some(ddi))) => { // Found some new dial info for this protocol/address combination - self.update_with_detected_dial_info(ddi).await? + self.update_with_detected_dial_info(ddi.clone()).await?; + + // Add WS dialinfo as well if it is on the same port as TCP + if let DetectedDialInfo::Detected(did) = &ddi { + if did.dial_info.protocol_type() == ProtocolType::TCP && tcp_same_port { + // Make WS dialinfo as well with same socket address as TCP + let ws_ddi = DetectedDialInfo::Detected(DialInfoDetail { + dial_info: self.make_dial_info( + did.dial_info.socket_address(), + ProtocolType::WS, + ), + class: did.class, + }); + // Add additional WS dialinfo + self.update_with_detected_dial_info(ws_ddi).await?; + } + } } Ok(Some(None)) => { // Found no new dial info for this protocol/address combination @@ -285,4 +301,21 @@ impl Network { out } + + /// Make a dialinfo from an address and protocol type + pub 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.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"), + } + } } From dac8e75229ff69b7cdd426b7771147b2f871f8f5 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 4 Sep 2023 20:33:43 -0400 Subject: [PATCH 04/10] xfer --- .../network_manager/tasks/public_address_check.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/veilid-core/src/network_manager/tasks/public_address_check.rs b/veilid-core/src/network_manager/tasks/public_address_check.rs index ec084c99..e106af9f 100644 --- a/veilid-core/src/network_manager/tasks/public_address_check.rs +++ b/veilid-core/src/network_manager/tasks/public_address_check.rs @@ -77,7 +77,7 @@ impl NetworkManager { }); // Get the ip(block) this report is coming from - let ipblock = ip_to_ipblock( + let reporting_ipblock = ip_to_ipblock( ip6_prefix_size, connection_descriptor.remote_address().to_ip_addr(), ); @@ -91,6 +91,13 @@ impl NetworkManager { return; } + // If the socket address reported is the same as the reporter, then this is coming through a relay + // or it should be ignored due to local proximity (nodes on the same network block should not be trusted as + // public ip address reporters, only disinterested parties) + if reporting_ipblock == ip_to_ipblock(ip6_prefix_size, socket_address.to_ip_addr()) { + return; + } + // Check if the public address report is coming from a node/block that gives an 'inconsistent' location // meaning that the node may be not useful for public address detection // This is done on a per address/protocol basis @@ -105,7 +112,7 @@ impl NetworkManager { if inner .public_address_inconsistencies_table .get(&addr_proto_type_key) - .map(|pait| pait.contains_key(&ipblock)) + .map(|pait| pait.contains_key(&reporting_ipblock)) .unwrap_or(false) { return; @@ -117,7 +124,7 @@ impl NetworkManager { .public_address_check_cache .entry(addr_proto_type_key) .or_insert_with(|| LruCache::new(PUBLIC_ADDRESS_CHECK_CACHE_SIZE)); - pacc.insert(ipblock, socket_address); + pacc.insert(reporting_ipblock, socket_address); // Determine if our external address has likely changed let mut bad_public_address_detection_punishment: Option< From d5a4be8a36910b147397d605a8bfd8cc21c0f178 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Mon, 4 Sep 2023 20:47:07 -0400 Subject: [PATCH 05/10] missing file --- .../native/discovery_context.rs | 582 ++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 veilid-core/src/network_manager/native/discovery_context.rs diff --git a/veilid-core/src/network_manager/native/discovery_context.rs b/veilid-core/src/network_manager/native/discovery_context.rs new file mode 100644 index 00000000..2f3344b2 --- /dev/null +++ b/veilid-core/src/network_manager/native/discovery_context.rs @@ -0,0 +1,582 @@ +/// Context detection of public dial info for a single protocol and address type +/// Also performs UPNP/IGD mapping if enabled and possible +use super::*; +use futures_util::stream::FuturesUnordered; + +const PORT_MAP_VALIDATE_TRY_COUNT: usize = 3; +const PORT_MAP_VALIDATE_DELAY_MS: u32 = 500; +const PORT_MAP_TRY_COUNT: usize = 3; + +// Detection result of dial info detection futures +#[derive(Clone, Debug)] +pub enum DetectedDialInfo { + SymmetricNAT, + Detected(DialInfoDetail), +} + +// Result of checking external address +#[derive(Clone, Debug)] +struct ExternalInfo { + dial_info: DialInfo, + address: SocketAddress, + node: NodeRef, +} + +struct DiscoveryContextInner { + // first node contacted + external_1: Option, + // second node contacted + external_2: Option, +} + +struct DiscoveryContextUnlockedInner { + routing_table: RoutingTable, + net: Network, + // per-protocol + intf_addrs: Vec, + protocol_type: ProtocolType, + address_type: AddressType, +} + +#[derive(Clone)] +pub struct DiscoveryContext { + unlocked_inner: Arc, + inner: Arc>, +} + +impl DiscoveryContext { + pub fn new( + routing_table: RoutingTable, + net: Network, + protocol_type: ProtocolType, + address_type: AddressType, + ) -> Self { + let intf_addrs = + Self::get_local_addresses(routing_table.clone(), protocol_type, address_type); + + Self { + unlocked_inner: Arc::new(DiscoveryContextUnlockedInner { + routing_table, + net, + intf_addrs, + protocol_type, + address_type, + }), + inner: Arc::new(Mutex::new(DiscoveryContextInner { + external_1: None, + external_2: None, + })), + } + } + + /////// + // Utilities + + // This pulls the already-detected local interface dial info from the routing table + #[instrument(level = "trace", skip(routing_table), ret)] + fn get_local_addresses( + routing_table: RoutingTable, + protocol_type: ProtocolType, + address_type: AddressType, + ) -> Vec { + let filter = DialInfoFilter::all() + .with_protocol_type(protocol_type) + .with_address_type(address_type); + 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() + } + + // 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 { + let rpc = self.unlocked_inner.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_addresses(&self) -> bool { + let node_count = { + let config = self.unlocked_inner.routing_table.network_manager().config(); + let c = config.get(); + c.network.dht.max_find_node_count as usize + }; + let routing_domain = RoutingDomain::PublicInternet; + let protocol_type = self.unlocked_inner.protocol_type; + let address_type = self.unlocked_inner.address_type; + + // 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>| { + 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>| { + 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 filters = VecDeque::from([ + inbound_dial_info_entry_filter, + disallow_relays_filter, + will_validate_dial_info_filter, + ]); + + // Find public nodes matching this filter + let nodes = self + .unlocked_inner + .routing_table + .find_fast_public_nodes_filtered(node_count, filters); + if nodes.is_empty() { + log_net!(debug + "no external address detection peers of type {:?}:{:?}", + protocol_type, + address_type + ); + return false; + } + + // For each peer, ask them for our public address, filtering on desired dial info + let mut unord = FuturesUnordered::new(); + + let get_public_address_func = |node: NodeRef| { + let this = self.clone(); + let node = node.filtered_clone( + NodeRefFilter::new() + .with_routing_domain(routing_domain) + .with_dial_info_filter(dial_info_filter.clone()), + ); + async move { + if let Some(address) = this.request_public_address(node.clone()).await { + let dial_info = this + .unlocked_inner + .net + .make_dial_info(address, this.unlocked_inner.protocol_type); + return Some(ExternalInfo { + dial_info, + address, + node, + }); + } + None + } + }; + + let mut external_address_infos = Vec::new(); + + for ni in 0..nodes.len() - 1 { + let node = nodes[ni].clone(); + + let gpa_future = get_public_address_func(node); + unord.push(gpa_future); + + // Always process two at a time so we get both addresses in parallel if possible + if unord.len() == 2 { + // Process one + if let Some(Some(ei)) = unord.next().await { + external_address_infos.push(ei); + if external_address_infos.len() == 2 { + break; + } + } + } + } + // Finish whatever is left if we need to + if external_address_infos.len() < 2 { + while let Some(res) = unord.next().await { + if let Some(ei) = res { + external_address_infos.push(ei); + if external_address_infos.len() == 2 { + break; + } + } + } + } + if external_address_infos.len() < 2 { + log_net!(debug "not enough peers responded with an external address"); + return false; + } + + { + let mut inner = self.inner.lock(); + inner.external_1 = Some(external_address_infos[0].clone()); + log_net!(debug "external_1: {:?}", inner.external_1); + inner.external_2 = Some(external_address_infos[1].clone()); + log_net!(debug "external_2: {:?}", inner.external_2); + } + + true + } + + #[instrument(level = "trace", skip(self), ret)] + async fn validate_dial_info( + &self, + node_ref: NodeRef, + dial_info: DialInfo, + redirect: bool, + ) -> bool { + let rpc_processor = self.unlocked_inner.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_processor + .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 { + let protocol_type = self.unlocked_inner.protocol_type; + let low_level_protocol_type = protocol_type.low_level_protocol_type(); + let address_type = self.unlocked_inner.address_type; + let local_port = self + .unlocked_inner + .net + .get_local_port(protocol_type) + .unwrap(); + let external_1 = self.inner.lock().external_1.as_ref().unwrap().clone(); + + let igd_manager = self.unlocked_inner.net.unlocked_inner.igd_manager.clone(); + 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) = igd_manager + .map_any_port(low_level_protocol_type, address_type, local_port, Some(external_1.address.to_ip_addr())) + .await else + { + return None; + }; + + // Make dial info from the port mapping + let external_mapped_dial_info = self.unlocked_inner.net.make_dial_info( + SocketAddress::from_socket_addr(mapped_external_address), + protocol_type, + ); + + // 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( + external_1.node.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 low_level_protocol_type { + LowLevelProtocolType::UDP => "udp", + LowLevelProtocolType::TCP => "tcp", + }); + sleep(PORT_MAP_VALIDATE_DELAY_MS).await + } else { + break; + } + } + + // Release the mapping if we're still unreachable + let _ = igd_manager + .unmap_port( + low_level_protocol_type, + address_type, + external_1.address.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 low_level_protocol_type { + LowLevelProtocolType::UDP => "udp", + LowLevelProtocolType::TCP => "tcp", + } + ); + break; + } + } + None + } + + /////// + // Per-protocol discovery routines + + // If we know we are not behind NAT, check our firewall status + #[instrument(level = "trace", skip(self), ret)] + async fn protocol_process_no_nat(&self) -> DetectedDialInfo { + let external_1 = self.inner.lock().external_1.as_ref().unwrap().clone(); + + // Do a validate_dial_info on the external address from a redirected node + if self + .validate_dial_info(external_1.node.clone(), external_1.dial_info.clone(), true) + .await + { + // Add public dial info with Direct dialinfo class + DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_1.dial_info.clone(), + class: DialInfoClass::Direct, + }) + } else { + // Add public dial info with Blocked dialinfo class + DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_1.dial_info.clone(), + class: DialInfoClass::Blocked, + }) + } + } + + // If we know we are behind NAT check what kind + #[instrument(level = "trace", skip(self), ret)] + async fn protocol_process_nat(&self) -> Option { + // Get the external dial info for our use here + let external_1 = self.inner.lock().external_1.as_ref().unwrap().clone(); + let external_2 = self.inner.lock().external_2.as_ref().unwrap().clone(); + + // If we have two different external addresses, then this is a symmetric NAT + if external_2.address != external_1.address { + // No more retries + return Some(DetectedDialInfo::SymmetricNAT); + } + + // Do these detections in parallel, but with ordering preference + let mut ord = FuturesOrdered::new(); + + // Manual Mapping Detection + /////////// + let this = self.clone(); + if let Some(local_port) = self + .unlocked_inner + .net + .get_local_port(self.unlocked_inner.protocol_type) + { + if external_1.dial_info.port() != local_port { + let c_external_1 = external_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_external_1.node.clone(), + external_1_dial_info_with_local_port.clone(), + true, + ) + .await + { + // Add public dial info with Direct dialinfo class + return Some(DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_1_dial_info_with_local_port, + class: DialInfoClass::Direct, + })); + } + + None + }); + ord.push_back(do_manual_map_fut); + } + } + + // Full Cone NAT Detection + /////////// + let this = self.clone(); + let c_external_1 = external_1.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_external_1.node.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::Detected(DialInfoDetail { + dial_info: c_external_1.dial_info, + class: DialInfoClass::FullConeNAT, + })); + } + 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 { + return Some(ddi); + } + } + + // We are restricted, determine what kind of restriction + + // 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(external_2.node.clone(), external_1.dial_info.clone(), false) + .await + { + // Got a reply from a non-default port, which means we're only address restricted + return Some(DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_1.dial_info.clone(), + class: DialInfoClass::AddressRestrictedNAT, + })); + } + // Didn't get a reply from a non-default port, which means we are also port restricted + Some(DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_1.dial_info.clone(), + class: DialInfoClass::PortRestrictedNAT, + })) + } + + /// Add discovery futures to an unordered set that may detect dialinfo when they complete + pub async fn discover( + &self, + unord: &mut FuturesUnordered>>, + ) { + let (mut retry_count, enable_upnp) = { + let c = self.unlocked_inner.net.config.get(); + (c.network.restricted_nat_retries, c.network.upnp) + }; + + // Do this right away because it's fast and every detection is going to need it + // Get our external addresses from two fast nodes + if !self.discover_external_addresses().await { + // If we couldn't get an external address, then we should just try the whole network class detection again later + return; + } + + // UPNP Automatic Mapping + /////////// + if enable_upnp { + 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_upnp_port_mapping().await { + // Got a port mapping, let's use it + return Some(DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_mapped_dial_info.clone(), + class: DialInfoClass::Mapped, + })); + } + None + }); + unord.push(do_mapped_fut); + } + + // NAT Detection + /////////// + + // If our local interface list contains external_1 then there is no NAT in place + let external_1 = self.inner.lock().external_1.as_ref().unwrap().clone(); + + if self.unlocked_inner.intf_addrs.contains(&external_1.address) { + this.protocol_process_no_nat(unord).await + xxx continue here + } else { + // Loop for restricted NAT retries + let this = self.clone(); + let do_nat_fut: SendPinBoxFuture> = Box::pin(async move { + loop { + // There is -some NAT- + if let Some(ddi) = this.protocol_process_nat().await { + if let DetectedDialInfo::Detected(did) = &ddi { + // If we got something better than restricted NAT or we're done retrying + if did.class < DialInfoClass::AddressRestrictedNAT || retry_count == 0 { + return Some(ddi); + } + } + } + if retry_count == 0 { + break; + } + retry_count -= 1; + } + None + }); + unord.push(do_nat_fut); + } + } +} From dfcdcf2364d9d2635dcc6d1ebc86fac9eca17e4b Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 5 Sep 2023 09:44:49 -0400 Subject: [PATCH 06/10] xfer --- .../native/discovery_context.rs | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/veilid-core/src/network_manager/native/discovery_context.rs b/veilid-core/src/network_manager/native/discovery_context.rs index 2f3344b2..7a4b1a01 100644 --- a/veilid-core/src/network_manager/native/discovery_context.rs +++ b/veilid-core/src/network_manager/native/discovery_context.rs @@ -375,7 +375,10 @@ impl DiscoveryContext { // If we know we are not behind NAT, check our firewall status #[instrument(level = "trace", skip(self), ret)] - async fn protocol_process_no_nat(&self) -> DetectedDialInfo { + async fn protocol_process_no_nat( + &self, + unord: &mut FuturesUnordered>>, + ) -> DetectedDialInfo { let external_1 = self.inner.lock().external_1.as_ref().unwrap().clone(); // Do a validate_dial_info on the external address from a redirected node @@ -399,7 +402,10 @@ impl DiscoveryContext { // If we know we are behind NAT check what kind #[instrument(level = "trace", skip(self), ret)] - async fn protocol_process_nat(&self) -> Option { + async fn protocol_process_nat( + &self, + unord: &mut FuturesUnordered>>, + ) -> Option { // Get the external dial info for our use here let external_1 = self.inner.lock().external_1.as_ref().unwrap().clone(); let external_2 = self.inner.lock().external_2.as_ref().unwrap().clone(); @@ -553,30 +559,31 @@ impl DiscoveryContext { let external_1 = self.inner.lock().external_1.as_ref().unwrap().clone(); if self.unlocked_inner.intf_addrs.contains(&external_1.address) { - this.protocol_process_no_nat(unord).await - xxx continue here + self.protocol_process_no_nat(unord).await; } else { - // Loop for restricted NAT retries - let this = self.clone(); - let do_nat_fut: SendPinBoxFuture> = Box::pin(async move { - loop { - // There is -some NAT- - if let Some(ddi) = this.protocol_process_nat().await { - if let DetectedDialInfo::Detected(did) = &ddi { - // If we got something better than restricted NAT or we're done retrying - if did.class < DialInfoClass::AddressRestrictedNAT || retry_count == 0 { - return Some(ddi); - } - } - } - if retry_count == 0 { - break; - } - retry_count -= 1; - } - None - }); - unord.push(do_nat_fut); + self.protocol_process_nat(unord).await; + + // // Loop for restricted NAT retries + // let this = self.clone(); + // let do_nat_fut: SendPinBoxFuture> = Box::pin(async move { + // loop { + // // There is -some NAT- + // if let Some(ddi) = this.protocol_process_nat().await { + // if let DetectedDialInfo::Detected(did) = &ddi { + // // If we got something better than restricted NAT or we're done retrying + // if did.class < DialInfoClass::AddressRestrictedNAT || retry_count == 0 { + // return Some(ddi); + // } + // } + // } + // if retry_count == 0 { + // break; + // } + // retry_count -= 1; + // } + // None + // }); + // unord.push(do_nat_fut); } } } From b325c82b9a2a2b4fb495362ac4055d0173e96feb Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Tue, 5 Sep 2023 19:27:16 -0400 Subject: [PATCH 07/10] more parallel --- .../native/discovery_context.rs | 230 ++++++++++-------- 1 file changed, 131 insertions(+), 99 deletions(-) diff --git a/veilid-core/src/network_manager/native/discovery_context.rs b/veilid-core/src/network_manager/native/discovery_context.rs index 7a4b1a01..b4175e28 100644 --- a/veilid-core/src/network_manager/native/discovery_context.rs +++ b/veilid-core/src/network_manager/native/discovery_context.rs @@ -378,26 +378,30 @@ impl DiscoveryContext { async fn protocol_process_no_nat( &self, unord: &mut FuturesUnordered>>, - ) -> DetectedDialInfo { + ) { let external_1 = self.inner.lock().external_1.as_ref().unwrap().clone(); - // Do a validate_dial_info on the external address from a redirected node - if self - .validate_dial_info(external_1.node.clone(), external_1.dial_info.clone(), true) - .await - { - // Add public dial info with Direct dialinfo class - DetectedDialInfo::Detected(DialInfoDetail { - dial_info: external_1.dial_info.clone(), - class: DialInfoClass::Direct, - }) - } else { - // Add public dial info with Blocked dialinfo class - DetectedDialInfo::Detected(DialInfoDetail { - dial_info: external_1.dial_info.clone(), - class: DialInfoClass::Blocked, - }) - } + let this = self.clone(); + let do_no_nat_fut: SendPinBoxFuture> = Box::pin(async move { + // Do a validate_dial_info on the external address from a redirected node + if this + .validate_dial_info(external_1.node.clone(), external_1.dial_info.clone(), true) + .await + { + // Add public dial info with Direct dialinfo class + Some(DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_1.dial_info.clone(), + class: DialInfoClass::Direct, + })) + } else { + // Add public dial info with Blocked dialinfo class + Some(DetectedDialInfo::Detected(DialInfoDetail { + dial_info: external_1.dial_info.clone(), + class: DialInfoClass::Blocked, + })) + } + }); + unord.push(do_no_nat_fut); } // If we know we are behind NAT check what kind @@ -405,20 +409,24 @@ impl DiscoveryContext { async fn protocol_process_nat( &self, unord: &mut FuturesUnordered>>, - ) -> Option { + ) { // Get the external dial info for our use here - let external_1 = self.inner.lock().external_1.as_ref().unwrap().clone(); - let external_2 = self.inner.lock().external_2.as_ref().unwrap().clone(); + let (external_1, external_2) = { + let inner = self.inner.lock(); + ( + inner.external_1.as_ref().unwrap().clone(), + inner.external_2.as_ref().unwrap().clone(), + ) + }; // If we have two different external addresses, then this is a symmetric NAT if external_2.address != external_1.address { - // No more retries - return Some(DetectedDialInfo::SymmetricNAT); + let do_symmetric_nat_fut: SendPinBoxFuture> = + Box::pin(async move { Some(DetectedDialInfo::SymmetricNAT) }); + unord.push(do_symmetric_nat_fut); + return; } - // Do these detections in parallel, but with ordering preference - let mut ord = FuturesOrdered::new(); - // Manual Mapping Detection /////////// let this = self.clone(); @@ -454,65 +462,111 @@ impl DiscoveryContext { None }); - ord.push_back(do_manual_map_fut); + unord.push(do_manual_map_fut); } } + // NAT Detection + /////////// + // Full Cone NAT Detection /////////// let this = self.clone(); - let c_external_1 = external_1.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_external_1.node.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 + let do_nat_detect_fut: SendPinBoxFuture> = Box::pin(async move { + let mut retry_count = { + let c = this.unlocked_inner.net.config.get(); + c.network.restricted_nat_retries + }; - return Some(DetectedDialInfo::Detected(DialInfoDetail { - dial_info: c_external_1.dial_info, - class: DialInfoClass::FullConeNAT, - })); + // Loop for restricted NAT retries + loop { + let mut ord = FuturesOrdered::new(); + + let c_this = this.clone(); + let c_external_1 = external_1.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 c_this + .validate_dial_info( + c_external_1.node.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::Detected(DialInfoDetail { + dial_info: c_external_1.dial_info, + class: DialInfoClass::FullConeNAT, + })); + } + None + }); + ord.push_back(do_full_cone_fut); + + let c_this = this.clone(); + let c_external_1 = external_1.clone(); + let c_external_2 = external_2.clone(); + let do_restricted_cone_fut: SendPinBoxFuture> = + Box::pin(async move { + // We are restricted, determine what kind of restriction + + // 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 c_this + .validate_dial_info( + c_external_2.node.clone(), + c_external_1.dial_info.clone(), + false, + ) + .await + { + // Got a reply from a non-default port, which means we're only address restricted + return Some(DetectedDialInfo::Detected(DialInfoDetail { + dial_info: c_external_1.dial_info.clone(), + class: DialInfoClass::AddressRestrictedNAT, + })); + } + // Didn't get a reply from a non-default port, which means we are also port restricted + Some(DetectedDialInfo::Detected(DialInfoDetail { + dial_info: c_external_1.dial_info.clone(), + class: DialInfoClass::PortRestrictedNAT, + })) + }); + ord.push_back(do_restricted_cone_fut); + + // Return the first result we get + let mut some_ddi = None; + while let Some(res) = ord.next().await { + if let Some(ddi) = res { + some_ddi = Some(ddi); + break; + } + } + + if let Some(ddi) = some_ddi { + if let DetectedDialInfo::Detected(did) = &ddi { + // If we got something better than restricted NAT or we're done retrying + if did.class < DialInfoClass::AddressRestrictedNAT || retry_count == 0 { + return Some(ddi); + } + } + } + if retry_count == 0 { + break; + } + retry_count -= 1; } + 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 { - return Some(ddi); - } - } - - // We are restricted, determine what kind of restriction - - // 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(external_2.node.clone(), external_1.dial_info.clone(), false) - .await - { - // Got a reply from a non-default port, which means we're only address restricted - return Some(DetectedDialInfo::Detected(DialInfoDetail { - dial_info: external_1.dial_info.clone(), - class: DialInfoClass::AddressRestrictedNAT, - })); - } - // Didn't get a reply from a non-default port, which means we are also port restricted - Some(DetectedDialInfo::Detected(DialInfoDetail { - dial_info: external_1.dial_info.clone(), - class: DialInfoClass::PortRestrictedNAT, - })) + unord.push(do_nat_detect_fut); } /// Add discovery futures to an unordered set that may detect dialinfo when they complete @@ -520,9 +574,9 @@ impl DiscoveryContext { &self, unord: &mut FuturesUnordered>>, ) { - let (mut retry_count, enable_upnp) = { + let enable_upnp = { let c = self.unlocked_inner.net.config.get(); - (c.network.restricted_nat_retries, c.network.upnp) + c.network.upnp }; // Do this right away because it's fast and every detection is going to need it @@ -562,28 +616,6 @@ impl DiscoveryContext { self.protocol_process_no_nat(unord).await; } else { self.protocol_process_nat(unord).await; - - // // Loop for restricted NAT retries - // let this = self.clone(); - // let do_nat_fut: SendPinBoxFuture> = Box::pin(async move { - // loop { - // // There is -some NAT- - // if let Some(ddi) = this.protocol_process_nat().await { - // if let DetectedDialInfo::Detected(did) = &ddi { - // // If we got something better than restricted NAT or we're done retrying - // if did.class < DialInfoClass::AddressRestrictedNAT || retry_count == 0 { - // return Some(ddi); - // } - // } - // } - // if retry_count == 0 { - // break; - // } - // retry_count -= 1; - // } - // None - // }); - // unord.push(do_nat_fut); } } } From 28ed99d2af855487c94d2d5bfb432847a459545e Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 6 Sep 2023 10:02:47 -0400 Subject: [PATCH 08/10] deps --- veilid-flutter/example/pubspec.lock | 40 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/veilid-flutter/example/pubspec.lock b/veilid-flutter/example/pubspec.lock index 667b0da5..25f2d414 100644 --- a/veilid-flutter/example/pubspec.lock +++ b/veilid-flutter/example/pubspec.lock @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -168,14 +168,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" json_annotation: dependency: transitive description: @@ -212,18 +204,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -329,10 +321,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -385,10 +377,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" typed_data: dependency: transitive description: @@ -411,7 +403,15 @@ packages: path: ".." relative: true source: path - version: "0.2.0" + version: "0.2.1" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" win32: dependency: transitive description: @@ -437,5 +437,5 @@ packages: source: hosted version: "3.5.0" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.6" From bfc42cdd8e79302b8ca38df95249aff58e7dba7c Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 6 Sep 2023 13:20:36 -0400 Subject: [PATCH 09/10] clean up ui pause routing table tasks when making routing domain changes --- veilid-core/src/network_manager/native/mod.rs | 16 +++- .../native/network_class_discovery.rs | 7 +- .../routing_table/routing_domain_editor.rs | 93 +++++++++++-------- .../src/routing_table/routing_table_inner.rs | 3 + veilid-core/src/routing_table/tasks/mod.rs | 33 ++++++- .../src/routing_table/tasks/ping_validator.rs | 18 ++-- .../tasks/private_route_management.rs | 5 - .../routing_table/tasks/relay_management.rs | 16 ++-- veilid-core/src/veilid_api/debug.rs | 3 +- 9 files changed, 122 insertions(+), 72 deletions(-) diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index b3bbe8d8..02335a6a 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -647,7 +647,11 @@ impl Network { let peer_socket_addr = dial_info.to_socket_addr(); let ph = match self.find_best_udp_protocol_handler(&peer_socket_addr, &None) { Some(ph) => ph, - None => bail!("no appropriate UDP protocol handler for dial_info"), + None => { + return Ok(NetworkResult::no_connection_other( + "no appropriate UDP protocol handler for dial_info", + )); + } }; connection_descriptor = network_result_try!(ph .send_message(data, peer_socket_addr) @@ -876,8 +880,8 @@ impl Network { } // commit routing table edits - editor_public_internet.commit(); - editor_local_network.commit(); + editor_public_internet.commit(true).await; + editor_local_network.commit(true).await; info!("network started"); self.inner.lock().network_started = true; @@ -932,14 +936,16 @@ impl Network { .clear_dial_info_details(None, None) .set_network_class(None) .clear_relay_node() - .commit(); + .commit(true) + .await; routing_table .edit_routing_domain(RoutingDomain::LocalNetwork) .clear_dial_info_details(None, None) .set_network_class(None) .clear_relay_node() - .commit(); + .commit(true) + .await; // Reset state including network class *self.inner.lock() = Self::new_inner(); 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 60036fd8..62d0ba95 100644 --- a/veilid-core/src/network_manager/native/network_class_discovery.rs +++ b/veilid-core/src/network_manager/native/network_class_discovery.rs @@ -35,7 +35,7 @@ impl Network { editor.clear_dial_info_details(None, None); editor.set_network_class(Some(NetworkClass::OutboundOnly)); - editor.commit(); + editor.commit(true).await; } } DetectedDialInfo::Detected(did) => { @@ -85,7 +85,7 @@ impl Network { } editor.set_network_class(Some(NetworkClass::InboundCapable)); - editor.commit(); + editor.commit(true).await; } } } @@ -131,8 +131,7 @@ impl Network { editor.clear_dial_info_details(None, None); editor.set_network_class(None); editor.clear_relay_node(); - editor.commit(); - log_net!(debug "PublicInternet network class cleared"); + editor.commit(true).await; // Process all protocol and address combinations let mut unord = FuturesUnordered::new(); diff --git a/veilid-core/src/routing_table/routing_domain_editor.rs b/veilid-core/src/routing_table/routing_domain_editor.rs index 4f9c6ac7..1b22657e 100644 --- a/veilid-core/src/routing_table/routing_domain_editor.rs +++ b/veilid-core/src/routing_table/routing_domain_editor.rs @@ -122,16 +122,20 @@ impl RoutingDomainEditor { } #[instrument(level = "debug", skip(self))] - pub fn commit(&mut self) { + pub async fn commit(&mut self, pause_tasks: bool) { // No locking if we have nothing to do if self.changes.is_empty() { return; } + // Briefly pause routing table ticker while changes are made + if pause_tasks { + self.routing_table.pause_tasks(true).await; + } + + // Apply changes let mut changed = false; { - let node_ids = self.routing_table.node_ids(); - let mut inner = self.routing_table.inner.write(); inner.with_routing_domain_mut(self.routing_domain, |detail| { for change in self.changes.drain(..) { @@ -140,22 +144,32 @@ impl RoutingDomainEditor { address_type, protocol_type, } => { - debug!( - "[{:?}] cleared dial info details: at={:?} pt={:?}", - self.routing_domain, address_type, protocol_type - ); + if address_type.is_some() || protocol_type.is_some() { + info!( + "[{:?}] cleared dial info: {}:{}", + self.routing_domain, + address_type + .map(|at| format!("{:?}", at)) + .unwrap_or("---".to_string()), + protocol_type + .map(|at| format!("{:?}", at)) + .unwrap_or("---".to_string()), + ); + } else { + info!("[{:?}] cleared all dial info", self.routing_domain); + } detail .common_mut() .clear_dial_info_details(address_type, protocol_type); changed = true; } RoutingDomainChange::ClearRelayNode => { - debug!("[{:?}] cleared relay node", self.routing_domain); + info!("[{:?}] cleared relay node", self.routing_domain); detail.common_mut().set_relay_node(None); changed = true; } RoutingDomainChange::SetRelayNode { relay_node } => { - debug!("[{:?}] set relay node: {}", self.routing_domain, relay_node); + info!("[{:?}] set relay node: {}", self.routing_domain, relay_node); detail.common_mut().set_relay_node(Some(relay_node.clone())); changed = true; } @@ -165,18 +179,16 @@ impl RoutingDomainEditor { changed = true; } RoutingDomainChange::AddDialInfoDetail { dial_info_detail } => { - debug!( - "[{:?}] add dial info detail: {:?}", - self.routing_domain, dial_info_detail + info!( + "[{:?}] dial info: {:?}:{}", + self.routing_domain, + dial_info_detail.class, + dial_info_detail.dial_info ); detail .common_mut() .add_dial_info_detail(dial_info_detail.clone()); - info!( - "{:?} Dial Info: {}@{}", - self.routing_domain, node_ids, dial_info_detail.dial_info - ); changed = true; } RoutingDomainChange::SetupNetwork { @@ -195,22 +207,22 @@ impl RoutingDomainEditor { || old_address_types != address_types || old_capabilities != *capabilities; - debug!( - "[{:?}] setup network: {:?} {:?} {:?} {:?}", - self.routing_domain, - outbound_protocols, - inbound_protocols, - address_types, - capabilities - ); - - detail.common_mut().setup_network( - outbound_protocols, - inbound_protocols, - address_types, - capabilities.clone(), - ); if this_changed { + info!( + "[{:?}] setup network: {:?} {:?} {:?} {:?}", + self.routing_domain, + outbound_protocols, + inbound_protocols, + address_types, + capabilities + ); + + detail.common_mut().setup_network( + outbound_protocols, + inbound_protocols, + address_types, + capabilities.clone(), + ); changed = true; } } @@ -218,14 +230,16 @@ impl RoutingDomainEditor { let old_network_class = detail.common().network_class(); let this_changed = old_network_class != network_class; - - debug!( - "[{:?}] set network class: {:?}", - self.routing_domain, network_class, - ); - - detail.common_mut().set_network_class(network_class); if this_changed { + if let Some(network_class) = network_class { + info!( + "[{:?}] set network class: {:?}", + self.routing_domain, network_class, + ); + } else { + info!("[{:?}] cleared network class", self.routing_domain,); + } + detail.common_mut().set_network_class(network_class); changed = true; } } @@ -248,5 +262,8 @@ impl RoutingDomainEditor { rss.reset(); } } + + // Unpause routing table ticker + self.routing_table.pause_tasks(false).await; } } diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index d71e934a..8ba6cf88 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -34,6 +34,8 @@ pub struct RoutingTableInner { pub(super) recent_peers: LruCache, /// Storage for private/safety RouteSpecs pub(super) route_spec_store: Option, + /// Tick paused or not + pub(super) tick_paused: bool, } impl RoutingTableInner { @@ -50,6 +52,7 @@ impl RoutingTableInner { self_transfer_stats: TransferStatsDownUp::default(), recent_peers: LruCache::new(RECENT_PEERS_TABLE_SIZE), route_spec_store: None, + tick_paused: false, } } diff --git a/veilid-core/src/routing_table/tasks/mod.rs b/veilid-core/src/routing_table/tasks/mod.rs index cf7f7cdc..25a67cd0 100644 --- a/veilid-core/src/routing_table/tasks/mod.rs +++ b/veilid-core/src/routing_table/tasks/mod.rs @@ -125,6 +125,11 @@ impl RoutingTable { /// Ticks about once per second /// to run tick tasks which may run at slower tick rates as configured pub async fn tick(&self) -> EyreResult<()> { + // Don't tick if paused + if self.inner.read().tick_paused { + return Ok(()); + } + // Do rolling transfers every ROLLING_TRANSFERS_INTERVAL_SECS secs self.unlocked_inner.rolling_transfers_task.tick().await?; @@ -168,13 +173,33 @@ impl RoutingTable { self.unlocked_inner.relay_management_task.tick().await?; // Run the private route management task - self.unlocked_inner - .private_route_management_task - .tick() - .await?; + // If we don't know our network class then don't do this yet + if self.has_valid_network_class(RoutingDomain::PublicInternet) { + self.unlocked_inner + .private_route_management_task + .tick() + .await?; + } Ok(()) } + pub(crate) async fn pause_tasks(&self, paused: bool) { + let cancel = { + let mut inner = self.inner.write(); + if !inner.tick_paused && paused { + inner.tick_paused = true; + true + } else if inner.tick_paused && !paused { + inner.tick_paused = false; + false + } else { + false + } + }; + if cancel { + self.cancel_tasks().await; + } + } pub(crate) async fn cancel_tasks(&self) { // Cancel all tasks being ticked diff --git a/veilid-core/src/routing_table/tasks/ping_validator.rs b/veilid-core/src/routing_table/tasks/ping_validator.rs index 6f70b86b..bfc2fb24 100644 --- a/veilid-core/src/routing_table/tasks/ping_validator.rs +++ b/veilid-core/src/routing_table/tasks/ping_validator.rs @@ -12,7 +12,7 @@ impl RoutingTable { // Ping each node in the routing table if they need to be pinged // to determine their reliability #[instrument(level = "trace", skip(self), err)] - fn relay_keepalive_public_internet( + async fn relay_keepalive_public_internet( &self, cur_ts: Timestamp, relay_nr: NodeRef, @@ -41,7 +41,8 @@ impl RoutingTable { // Say we're doing this keepalive now self.edit_routing_domain(RoutingDomain::PublicInternet) .set_relay_node_keepalive(Some(cur_ts)) - .commit(); + .commit(false) + .await; // We need to keep-alive at one connection per ordering for relays // but also one per NAT mapping that we need to keep open for our inbound dial info @@ -119,7 +120,7 @@ impl RoutingTable { // Ping each node in the routing table if they need to be pinged // to determine their reliability #[instrument(level = "trace", skip(self), err)] - fn ping_validator_public_internet( + async fn ping_validator_public_internet( &self, cur_ts: Timestamp, unord: &mut FuturesUnordered< @@ -136,7 +137,8 @@ impl RoutingTable { // If this is our relay, let's check for NAT keepalives if let Some(relay_nr) = opt_relay_nr { - self.relay_keepalive_public_internet(cur_ts, relay_nr, unord)?; + self.relay_keepalive_public_internet(cur_ts, relay_nr, unord) + .await?; } // Just do a single ping with the best protocol for all the other nodes to check for liveness @@ -156,7 +158,7 @@ impl RoutingTable { // Ping each node in the LocalNetwork routing domain if they // need to be pinged to determine their reliability #[instrument(level = "trace", skip(self), err)] - fn ping_validator_local_network( + async fn ping_validator_local_network( &self, cur_ts: Timestamp, unord: &mut FuturesUnordered< @@ -195,10 +197,12 @@ impl RoutingTable { let mut unord = FuturesUnordered::new(); // PublicInternet - self.ping_validator_public_internet(cur_ts, &mut unord)?; + self.ping_validator_public_internet(cur_ts, &mut unord) + .await?; // LocalNetwork - self.ping_validator_local_network(cur_ts, &mut unord)?; + self.ping_validator_local_network(cur_ts, &mut unord) + .await?; // Wait for ping futures to complete in parallel while let Ok(Some(_)) = unord.next().timeout_at(stop_token.clone()).await {} diff --git a/veilid-core/src/routing_table/tasks/private_route_management.rs b/veilid-core/src/routing_table/tasks/private_route_management.rs index b8ddba51..dcf9fee1 100644 --- a/veilid-core/src/routing_table/tasks/private_route_management.rs +++ b/veilid-core/src/routing_table/tasks/private_route_management.rs @@ -169,11 +169,6 @@ impl RoutingTable { _last_ts: Timestamp, cur_ts: Timestamp, ) -> EyreResult<()> { - // If we don't know our network class then don't do this yet - if !self.has_valid_network_class(RoutingDomain::PublicInternet) { - return Ok(()); - } - // Test locally allocated routes first // This may remove dead routes let routes_needing_testing = self.get_allocated_routes_to_test(cur_ts); diff --git a/veilid-core/src/routing_table/tasks/relay_management.rs b/veilid-core/src/routing_table/tasks/relay_management.rs index 874200d6..b5b0bb02 100644 --- a/veilid-core/src/routing_table/tasks/relay_management.rs +++ b/veilid-core/src/routing_table/tasks/relay_management.rs @@ -23,13 +23,13 @@ impl RoutingTable { let state = relay_node.state(cur_ts); // Relay node is dead or no longer needed if matches!(state, BucketEntryState::Dead) { - info!("Relay node died, dropping relay {}", relay_node); + debug!("Relay node died, dropping relay {}", relay_node); editor.clear_relay_node(); false } // Relay node no longer can relay else if relay_node.operate(|_rti, e| !relay_node_filter(e)) { - info!( + debug!( "Relay node can no longer relay, dropping relay {}", relay_node ); @@ -38,7 +38,7 @@ impl RoutingTable { } // Relay node is no longer required else if !own_node_info.requires_relay() { - info!( + debug!( "Relay node no longer required, dropping relay {}", relay_node ); @@ -47,7 +47,7 @@ impl RoutingTable { } // Should not have relay for invalid network class else if !self.has_valid_network_class(RoutingDomain::PublicInternet) { - info!( + debug!( "Invalid network class does not get a relay, dropping relay {}", relay_node ); @@ -75,7 +75,7 @@ impl RoutingTable { false, ) { Ok(nr) => { - info!("Outbound relay node selected: {}", nr); + debug!("Outbound relay node selected: {}", nr); editor.set_relay_node(nr); got_outbound_relay = true; } @@ -84,20 +84,20 @@ impl RoutingTable { } } } else { - info!("Outbound relay desired but not available"); + debug!("Outbound relay desired but not available"); } } if !got_outbound_relay { // Find a node in our routing table that is an acceptable inbound relay if let Some(nr) = self.find_inbound_relay(RoutingDomain::PublicInternet, cur_ts) { - info!("Inbound relay node selected: {}", nr); + debug!("Inbound relay node selected: {}", nr); editor.set_relay_node(nr); } } } // Commit the changes - editor.commit(); + editor.commit(false).await; Ok(()) } diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 9c169c5b..3e70cf04 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -570,7 +570,8 @@ impl VeilidAPI { routing_table .edit_routing_domain(routing_domain) .set_relay_node(relay_node) - .commit(); + .commit(true) + .await; Ok("Relay changed".to_owned()) } From dc17e25bac46a46b4d6d08d4cfa35f4317573462 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Wed, 6 Sep 2023 17:06:33 -0400 Subject: [PATCH 10/10] Fixes #297 WASM build for new public address detection code --- veilid-core/src/network_manager/wasm/mod.rs | 7 +++-- veilid-core/src/veilid_api/debug.rs | 15 +++++++--- veilid-core/src/veilid_config.rs | 32 +++++++++++++++++---- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/veilid-core/src/network_manager/wasm/mod.rs b/veilid-core/src/network_manager/wasm/mod.rs index c9a0c445..b9e66e8c 100644 --- a/veilid-core/src/network_manager/wasm/mod.rs +++ b/veilid-core/src/network_manager/wasm/mod.rs @@ -387,7 +387,7 @@ impl Network { editor_public_internet.set_network_class(Some(NetworkClass::WebApp)); // commit routing table edits - editor_public_internet.commit(); + editor_public_internet.commit(true).await; self.inner.lock().network_started = true; Ok(()) @@ -414,10 +414,11 @@ impl Network { // Drop all dial info routing_table .edit_routing_domain(RoutingDomain::PublicInternet) - .clear_dial_info_details() + .clear_dial_info_details(None, None) .set_network_class(None) .clear_relay_node() - .commit(); + .commit(true) + .await; // Cancels all async background tasks by dropping join handles *self.inner.lock() = Self::new_inner(); diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index 3e70cf04..17050629 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -582,17 +582,24 @@ impl VeilidAPI { } async fn debug_config(&self, args: String) -> VeilidAPIResult { - let config = self.config()?; + let mut args = args.as_str(); + let mut config = self.config()?; + if !args.starts_with("insecure") { + config = config.safe_config(); + } else { + args = &args[8..]; + } let args = args.trim_start(); + if args.is_empty() { - return config.get_key_json(""); + return config.get_key_json("", true); } let (arg, rest) = args.split_once(' ').unwrap_or((args, "")); let rest = rest.trim_start().to_owned(); // One argument is 'config get' if rest.is_empty() { - return config.get_key_json(arg); + return config.get_key_json(arg, true); } // More than one argument is 'config set' @@ -1372,7 +1379,7 @@ peerinfo [routingdomain] entries [dead|reliable] entry nodeinfo -config [configkey [new value]] +config [insecure] [configkey [new value]] txtrecord keypair purge diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index d8c6d67e..f8a5cf88 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -576,7 +576,7 @@ impl VeilidConfig { self.inner.read() } - pub fn safe_config(&self) -> VeilidConfigInner { + fn safe_config_inner(&self) -> VeilidConfigInner { let mut safe_cfg = self.inner.read().clone(); // Remove secrets @@ -587,6 +587,20 @@ impl VeilidConfig { safe_cfg } + pub fn safe_config(&self) -> VeilidConfig { + let mut safe_cfg = self.inner.read().clone(); + + // Remove secrets + safe_cfg.network.routing_table.node_id_secret = TypedSecretGroup::new(); + safe_cfg.protected_store.device_encryption_key_password = "".to_owned(); + safe_cfg.protected_store.new_device_encryption_key_password = None; + + VeilidConfig { + update_cb: self.update_cb.clone(), + inner: Arc::new(RwLock::new(safe_cfg)), + } + } + pub fn with_mut(&self, f: F) -> VeilidAPIResult where F: FnOnce(&mut VeilidConfigInner) -> VeilidAPIResult, @@ -611,14 +625,14 @@ impl VeilidConfig { // Send configuration update to clients if let Some(update_cb) = &self.update_cb { - let safe_cfg = self.safe_config(); + let safe_cfg = self.safe_config_inner(); update_cb(VeilidUpdate::Config(VeilidStateConfig { config: safe_cfg })); } Ok(out) } - pub fn get_key_json(&self, key: &str) -> VeilidAPIResult { + pub fn get_key_json(&self, key: &str, pretty: bool) -> VeilidAPIResult { let c = self.get(); // Generate json from whole config @@ -627,7 +641,11 @@ impl VeilidConfig { // Find requested subkey if key.is_empty() { - Ok(jvc.to_string()) + Ok(if pretty { + jvc.pretty(2) + } else { + jvc.to_string() + }) } else { // Split key into path parts let keypath: Vec<&str> = key.split('.').collect(); @@ -638,7 +656,11 @@ impl VeilidConfig { } out = &out[k]; } - Ok(out.to_string()) + Ok(if pretty { + out.pretty(2) + } else { + out.to_string() + }) } } pub fn set_key_json(&self, key: &str, value: &str) -> VeilidAPIResult<()> {