diff --git a/veilid-core/proto/veilid.capnp b/veilid-core/proto/veilid.capnp index 3d92e99b..11d5f2e6 100644 --- a/veilid-core/proto/veilid.capnp +++ b/veilid-core/proto/veilid.capnp @@ -195,8 +195,8 @@ enum DialInfoClass { } struct DialInfoDetail { - dialInfo @0; :DialInfo; - class @1; :DialInfoClass; + dialInfo @0 :DialInfo; + class @1 :DialInfoClass; } struct NodeStatus { @@ -243,7 +243,6 @@ struct OperationReturnReceipt { struct OperationFindNodeQ { nodeId @0 :NodeID; # node id to locate - senderNodeInfo @1 :NodeInfo; # dial info for the node asking the question } struct PeerInfo { diff --git a/veilid-core/src/intf/native/network/network_class_discovery.rs b/veilid-core/src/intf/native/network/network_class_discovery.rs index f36a9841..2389f178 100644 --- a/veilid-core/src/intf/native/network/network_class_discovery.rs +++ b/veilid-core/src/intf/native/network/network_class_discovery.rs @@ -4,39 +4,62 @@ use crate::intf::*; use crate::routing_table::*; use crate::*; -#[derive(Debug)] +struct DiscoveryContextInner { + network_class: Option, + // per-protocol + intf_addrs: Option>, + protocol_type: Option, + address_type: Option, + low_level_protocol_type: Option, + external1_dial_info: Option, + external1: Option, + node_b: Option, +} + struct DiscoveryContext { routing_table: RoutingTable, - external_ipv4: Option, - external_ipv6: Option, - network_class: Option, + net: Network, + inner: Arc>, } impl DiscoveryContext { - pub fn new(routing_table: RoutingTable) -> Self { + pub fn new(routing_table: RoutingTable, net: Network) -> Self { Self { routing_table, - external_ipv4: None, - external_ipv6: None, - network_class: None, + net, + inner: Arc::new(Mutex::new(DiscoveryContextInner { + network_class: None, + // per-protocol + intf_addrs: None, + protocol_type: None, + address_type: None, + low_level_protocol_type: None, + external1_dial_info: None, + external1: None, + node_b: None, + })), } } - pub fn upgrade_network_class(&mut self, network_class: NetworkClass) { - if let Some(old_nc) = self.network_class { + + /////// + // Utilities +xxxx continue converting to async safe inner + // Pick the best network class we have seen so far + pub fn upgrade_network_class(&self, network_class: NetworkClass) { + let inner = self.inner.lock(); + + if let Some(old_nc) = inner.network_class { if network_class < old_nc { - self.network_class = Some(network_class); + inner.network_class = Some(network_class); } } else { - self.network_class = Some(network_class); + inner.network_class = Some(network_class); } } -} -impl Network { // Ask for a public address check from a particular noderef async fn request_public_address(&self, node_ref: NodeRef) -> Option { - let routing_table = self.routing_table(); - let rpc = routing_table.rpc_processor(); + let rpc = self.routing_table.rpc_processor(); rpc.rpc_call_info(node_ref.clone()) .await .map_err(logthru_net!( @@ -54,11 +77,10 @@ impl Network { address_type: AddressType, ignore_node: Option, ) -> Option<(SocketAddress, NodeRef)> { - let routing_table = self.routing_table(); let filter = DialInfoFilter::global() .with_protocol_type(protocol_type) .with_address_type(address_type); - let peers = routing_table.find_fast_public_nodes_filtered(&filter); + let peers = self.routing_table.find_fast_public_nodes_filtered(&filter); if peers.is_empty() { log_net!("no peers of type '{:?}'", filter); return None; @@ -82,12 +104,10 @@ impl Network { protocol_type: ProtocolType, address_type: AddressType, ) -> Vec { - let routing_table = self.routing_table(); - let filter = DialInfoFilter::local() .with_protocol_type(protocol_type) .with_address_type(address_type); - routing_table + self.routing_table .dial_info_details(RoutingDomain::LocalNetwork) .iter() .filter_map(|did| { @@ -107,8 +127,7 @@ impl Network { redirect: bool, alternate_port: bool, ) -> bool { - let routing_table = self.routing_table(); - let rpc = routing_table.rpc_processor(); + let rpc = self.routing_table.rpc_processor(); rpc.rpc_call_validate_dial_info(node_ref.clone(), dial_info, redirect, alternate_port) .await .map_err(logthru_net!( @@ -118,241 +137,287 @@ impl Network { .unwrap_or(false) } - async fn try_port_mapping>( - &self, - _intf_addrs: I, - _protocol_type: ProtocolType, - _address_type: AddressType, - ) -> Option { + async fn try_port_mapping(&mut self) -> Option { //xxx None } - xxx split this routine up into helper routines that can be used by different protocols too. + 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"), + } + } - pub async fn update_udpv4_dialinfo( + /////// + // Per-protocol discovery routines + + pub fn protocol_begin(&mut self, protocol_type: ProtocolType, address_type: AddressType) { + // Get our interface addresses + self.intf_addrs = Some(self.get_local_addresses(protocol_type, address_type)); + self.protocol_type = Some(protocol_type); + self.address_type = Some(address_type); + self.low_level_protocol_type = Some(match protocol_type { + ProtocolType::UDP => ProtocolType::UDP, + ProtocolType::TCP => ProtocolType::TCP, + ProtocolType::WS => ProtocolType::TCP, + ProtocolType::WSS => ProtocolType::TCP, + }); + self.external1_dial_info = None; + self.external1 = None; + self.node_b = None; + } + + pub async fn protocol_get_external_address_1(&mut self) -> bool { + let protocol_type = self.protocol_type.unwrap(); + let address_type = self.address_type.unwrap(); + + // Get our external address from some fast node, call it node B + let (external1, node_b) = 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 + return false; + } + Some(v) => v, + }; + let external1_dial_info = self.make_dial_info(external1, protocol_type); + + self.external1_dial_info = Some(external1_dial_info); + self.external1 = Some(external1); + self.node_b = Some(node_b); + + true + } + + pub async fn protocol_process_no_nat(&mut self) { + let node_b = self.node_b.as_ref().unwrap().clone(); + let external1_dial_info = self.external1_dial_info.as_ref().unwrap().clone(); + let external1 = self.external1.unwrap(); + let protocol_type = self.protocol_type.unwrap(); + let address_type = self.address_type.unwrap(); + let intf_addrs = self.intf_addrs.as_ref().unwrap(); + + // Do a validate_dial_info on the external address from a redirected node + if self + .validate_dial_info(node_b.clone(), external1_dial_info.clone(), true, false) + .await + { + // Add public dial info with Direct dialinfo class + self.routing_table.register_dial_info( + RoutingDomain::PublicInternet, + external1_dial_info, + DialInfoClass::Direct, + ); + } + // Attempt a UDP port mapping via all available and enabled mechanisms + else if let Some(external_mapped_dial_info) = self.try_port_mapping().await { + // Got a port mapping, let's use it + self.routing_table.register_dial_info( + RoutingDomain::PublicInternet, + external_mapped_dial_info, + DialInfoClass::Mapped, + ); + } else { + // Add public dial info with Blocked dialinfo class + self.routing_table.register_dial_info( + RoutingDomain::PublicInternet, + external1_dial_info, + DialInfoClass::Blocked, + ); + } + self.upgrade_network_class(NetworkClass::InboundCapable); + } + + pub async fn protocol_process_nat(&mut self) -> bool { + let node_b = self.node_b.as_ref().unwrap().clone(); + let external1_dial_info = self.external1_dial_info.as_ref().unwrap().clone(); + let external1 = self.external1.unwrap(); + let protocol_type = self.protocol_type.unwrap(); + let address_type = self.address_type.unwrap(); + let intf_addrs = self.intf_addrs.as_ref().unwrap(); + + // Attempt a UDP port mapping via all available and enabled mechanisms + if let Some(external_mapped_dial_info) = self.try_port_mapping().await { + // Got a port mapping, let's use it + self.routing_table.register_dial_info( + RoutingDomain::PublicInternet, + external_mapped_dial_info, + DialInfoClass::Mapped, + ); + self.upgrade_network_class(NetworkClass::InboundCapable); + + // No more retries + return true; + } + + // Port mapping was not possible, let's see what kind of NAT we have + + // Does a redirected dial info validation find us? + if self + .validate_dial_info(node_b.clone(), external1_dial_info.clone(), true, false) + .await + { + // Yes, another machine can use the dial info directly, so Full Cone + // Add public dial info with full cone NAT network class + self.routing_table.register_dial_info( + RoutingDomain::PublicInternet, + external1_dial_info, + DialInfoClass::FullConeNAT, + ); + self.upgrade_network_class(NetworkClass::InboundCapable); + + return true; + } + + // No, we are restricted, determine what kind of restriction + + // Get our external address from some fast node, that is not node B, call it node D + let (external2, node_d) = match self + .discover_external_address(protocol_type, address_type, Some(node_b.node_id())) + .await + { + None => { + // If we can't get an external address, allow retry + return false; + } + Some(v) => v, + }; + + // If we have two different external addresses, then this is a symmetric NAT + if external2 != external1 { + // Symmetric NAT is outbound only, no public dial info will work + self.upgrade_network_class(NetworkClass::OutboundOnly); + + // No more retries + return 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 + let external2_dial_info = DialInfo::udp(external2); + // Do a validate_dial_info on the external address from a routed node + if self + .validate_dial_info(node_d.clone(), external2_dial_info.clone(), false, true) + .await + { + // Got a reply from a non-default port, which means we're only address restricted + self.routing_table.register_dial_info( + RoutingDomain::PublicInternet, + external1_dial_info, + DialInfoClass::AddressRestrictedNAT, + ); + } else { + // Didn't get a reply from a non-default port, which means we are also port restricted + self.routing_table.register_dial_info( + RoutingDomain::PublicInternet, + external1_dial_info, + DialInfoClass::PortRestrictedNAT, + ); + } + self.upgrade_network_class(NetworkClass::InboundCapable); + + // Allow another retry because sometimes trying again will get us Full Cone NAT instead + false + } +} + +impl Network { + pub async fn update_ipv4_protocol_dialinfo( &self, context: &mut DiscoveryContext, + protocol_type: ProtocolType, ) -> Result<(), String> { - log_net!("looking for udpv4 public dial info"); - let routing_table = self.routing_table(); - let mut retry_count = { let c = self.config.get(); c.network.restricted_nat_retries }; - // Get our interface addresses - let intf_addrs = self.get_local_addresses(ProtocolType::UDP, AddressType::IPV4); + // Start doing ipv4 protocol + context.protocol_begin(protocol_type, AddressType::IPV4); // Loop for restricted NAT retries loop { // Get our external address from some fast node, call it node B - let (external1, node_b) = match self - .discover_external_address(ProtocolType::UDP, AddressType::IPV4, None) - .await - { - None => { - // If we can't get an external address, exit but don't throw an error so we can try again later - return Ok(()); - } - Some(v) => v, - }; - let external1_dial_info = DialInfo::udp(external1); + 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 external1 then there is no NAT in place - if intf_addrs.contains(&external1) { + if context + .intf_addrs + .as_ref() + .unwrap() + .contains(&context.external1.as_ref().unwrap()) + { // No NAT - // Do a validate_dial_info on the external address from a redirected node - if self - .validate_dial_info(node_b.clone(), external1_dial_info.clone(), true, false) - .await - { - // Add public dial info with Direct dialinfo class - routing_table.register_dial_info( - RoutingDomain::PublicInternet, - external1_dial_info, - DialInfoClass::Direct, - ); - } - // Attempt a UDP port mapping via all available and enabled mechanisms - else if let Some(external_mapped) = self - .try_port_mapping(&intf_addrs, ProtocolType::UDP, AddressType::IPV4) - .await - { - // Got a port mapping, let's use it - let external_mapped_dial_info = DialInfo::udp(external_mapped); - routing_table.register_dial_info( - RoutingDomain::PublicInternet, - external_mapped_dial_info, - DialInfoClass::Mapped, - ); - } else { - // Add public dial info with Blocked dialinfo class - routing_table.register_dial_info( - RoutingDomain::PublicInternet, - external1_dial_info, - DialInfoClass::Blocked, - ); - } - context.upgrade_network_class(NetworkClass::InboundCapable); + context.protocol_process_no_nat().await; + // No more retries break; - } else { - // There is -some NAT- - // Attempt a UDP port mapping via all available and enabled mechanisms - if let Some(external_mapped) = self - .try_port_mapping(&intf_addrs, ProtocolType::UDP, AddressType::IPV4) - .await - { - // Got a port mapping, let's use it - let external_mapped_dial_info = DialInfo::udp(external_mapped); - routing_table.register_dial_info( - RoutingDomain::PublicInternet, - external_mapped_dial_info, - DialInfoClass::Mapped, - ); - context.upgrade_network_class(NetworkClass::InboundCapable); - - // No more retries - break; - } else { - // Port mapping was not possible, let's see what kind of NAT we have - - // Does a redirected dial info validation find us? - if self - .validate_dial_info( - node_b.clone(), - external1_dial_info.clone(), - true, - false, - ) - .await - { - // Yes, another machine can use the dial info directly, so Full Cone - // Add public dial info with full cone NAT network class - routing_table.register_dial_info( - RoutingDomain::PublicInternet, - external1_dial_info, - DialInfoClass::FullConeNAT, - ); - context.upgrade_network_class(NetworkClass::InboundCapable); - - // No more retries - break; - } else { - // No, we are restricted, determine what kind of restriction - - // Get our external address from some fast node, that is not node B, call it node D - let (external2, node_d) = match self - .discover_external_address( - ProtocolType::UDP, - AddressType::IPV4, - Some(node_b.node_id()), - ) - .await - { - None => { - // If we can't get an external address, exit but don't throw an error so we can try again later - return Ok(()); - } - Some(v) => v, - }; - // If we have two different external addresses, then this is a symmetric NAT - if external2 != external1 { - // Symmetric NAT is outbound only, no public dial info will work - context.upgrade_network_class(NetworkClass::OutboundOnly); - - // No more retries - break; - } else { - // If we're going to end up as a restricted NAT of some sort - // we should go through our retries before we assign a dial info - if retry_count == 0 { - // Address is the same, so it's address or port restricted - let external2_dial_info = DialInfo::udp(external2); - // Do a validate_dial_info on the external address from a routed node - if self - .validate_dial_info( - node_d.clone(), - external2_dial_info.clone(), - false, - true, - ) - .await - { - // Got a reply from a non-default port, which means we're only address restricted - routing_table.register_dial_info( - RoutingDomain::PublicInternet, - external1_dial_info, - DialInfoClass::AddressRestrictedNAT, - ); - } else { - // Didn't get a reply from a non-default port, which means we are also port restricted - routing_table.register_dial_info( - RoutingDomain::PublicInternet, - external1_dial_info, - DialInfoClass::PortRestrictedNAT, - ); - } - context.upgrade_network_class(NetworkClass::InboundCapable); - } - } - } - } - - if retry_count == 0 { - break; - } - retry_count -= 1; } + + // 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; } - // xxx should verify hole punch capable somehow and switch to outbound-only if hole punch can't work - Ok(()) } - pub async fn update_tcpv4_dialinfo( + pub async fn update_ipv6_protocol_dialinfo( &self, context: &mut DiscoveryContext, + protocol_type: ProtocolType, ) -> Result<(), String> { - log_net!("looking for tcpv4 public dial info"); + // Start doing ipv6 protocol + context.protocol_begin(protocol_type, AddressType::IPV6); - Ok(()) - } + // Get our external address from some fast node, call it node B + 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(()); + } - pub async fn update_wsv4_dialinfo(&self, context: &mut DiscoveryContext) -> Result<(), String> { - log_net!("looking for wsv4 public dial info"); - // xxx - //Err("unimplemented".to_owned()) - Ok(()) - } + // If our local interface list doesn't contain external1 then there is an Ipv6 NAT in place + if !context + .intf_addrs + .as_ref() + .unwrap() + .contains(&context.external1.as_ref().unwrap()) + { + // IPv6 NAT is not supported today + log_net!(warn + "IPv6 NAT is not supported for external address: {}", + context.external1.unwrap() + ); + return Ok(()); + } - pub async fn update_udpv6_dialinfo( - &self, - context: &mut DiscoveryContext, - ) -> Result<(), String> { - log_net!("looking for udpv6 public dial info"); - // xxx - //Err("unimplemented".to_owned()) - Ok(()) - } + // No NAT + context.protocol_process_no_nat().await; - pub async fn update_tcpv6_dialinfo( - &self, - context: &mut DiscoveryContext, - ) -> Result<(), String> { - log_net!("looking for tcpv6 public dial info"); - // xxx - //Err("unimplemented".to_owned()) - Ok(()) - } - - pub async fn update_wsv6_dialinfo(&self, context: &mut DiscoveryContext) -> Result<(), String> { - log_net!("looking for wsv6 public dial info"); - // xxx - //Err("unimplemented".to_owned()) Ok(()) } @@ -366,21 +431,27 @@ impl Network { .clone() .unwrap_or_default(); - let mut context = DiscoveryContext::default(); + let context = DiscoveryContext::new(self.routing_table(), self.clone()); if protocol_config.inbound.contains(ProtocolType::UDP) { - self.update_udpv4_dialinfo(&mut context).await?; - self.update_udpv6_dialinfo(&mut context).await?; + self.update_ipv4_protocol_dialinfo(&mut context, ProtocolType::UDP) + .await?; + self.update_ipv6_protocol_dialinfo(&mut context, ProtocolType::UDP) + .await?; } if protocol_config.inbound.contains(ProtocolType::TCP) { - self.update_tcpv4_dialinfo(&mut context).await?; - self.update_tcpv6_dialinfo(&mut context).await?; + self.update_ipv4_protocol_dialinfo(&mut context, ProtocolType::TCP) + .await?; + self.update_ipv6_protocol_dialinfo(&mut context, ProtocolType::TCP) + .await?; } if protocol_config.inbound.contains(ProtocolType::WS) { - self.update_wsv4_dialinfo(&mut context).await?; - self.update_wsv6_dialinfo(&mut context).await?; + self.update_ipv4_protocol_dialinfo(&mut context, ProtocolType::WS) + .await?; + self.update_ipv6_protocol_dialinfo(&mut context, ProtocolType::WS) + .await?; } self.inner.lock().network_class = context.network_class; diff --git a/veilid-core/src/intf/native/network/start_protocols.rs b/veilid-core/src/intf/native/network/start_protocols.rs index e1e48dd6..bc31464b 100644 --- a/veilid-core/src/intf/native/network/start_protocols.rs +++ b/veilid-core/src/intf/native/network/start_protocols.rs @@ -299,7 +299,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::PublicInternet, di.clone(), - DialInfoOrigin::Static, + DialInfoClass::Direct, ); static_public = true; } @@ -308,7 +308,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::LocalNetwork, di.clone(), - DialInfoOrigin::Static, + DialInfoClass::Direct, ); } @@ -328,7 +328,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::PublicInternet, pdi.clone(), - DialInfoOrigin::Static, + DialInfoClass::Direct, ); // See if this public address is also a local interface address @@ -345,7 +345,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::LocalNetwork, DialInfo::udp_from_socketaddr(pdi_addr), - DialInfoOrigin::Static, + DialInfoClass::Direct, ); } @@ -427,7 +427,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::PublicInternet, pdi.clone(), - DialInfoOrigin::Static, + DialInfoClass::Direct, ); static_public = true; @@ -445,7 +445,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::LocalNetwork, pdi, - DialInfoOrigin::Static, + DialInfoClass::Direct, ); } @@ -469,7 +469,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::PublicInternet, local_di.clone(), - DialInfoOrigin::Static, + DialInfoClass::Direct, ); static_public = true; } @@ -478,7 +478,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::LocalNetwork, local_di, - DialInfoOrigin::Static, + DialInfoClass::Direct, ); } @@ -561,7 +561,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::PublicInternet, pdi.clone(), - DialInfoOrigin::Static, + DialInfoClass::Direct, ); static_public = true; @@ -579,7 +579,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::LocalNetwork, pdi, - DialInfoOrigin::Static, + DialInfoClass::Direct, ); } @@ -646,7 +646,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::PublicInternet, di.clone(), - DialInfoOrigin::Static, + DialInfoClass::Direct, ); static_public = true; } @@ -654,7 +654,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::LocalNetwork, di.clone(), - DialInfoOrigin::Static, + DialInfoClass::Direct, ); registered_addresses.insert(socket_address.to_ip_addr()); } @@ -678,7 +678,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::PublicInternet, pdi.clone(), - DialInfoOrigin::Static, + DialInfoClass::Direct, ); static_public = true; @@ -694,7 +694,7 @@ impl Network { routing_table.register_dial_info( RoutingDomain::LocalNetwork, pdi, - DialInfoOrigin::Static, + DialInfoClass::Direct, ); } diff --git a/veilid-core/src/network_manager.rs b/veilid-core/src/network_manager.rs index 25541026..dadf1692 100644 --- a/veilid-core/src/network_manager.rs +++ b/veilid-core/src/network_manager.rs @@ -363,13 +363,13 @@ impl NetworkManager { // Get our node's capabilities pub fn generate_node_status(&self) -> NodeStatus { - let network_class = self.get_network_class().unwrap_or(NetworkClass::Invalid); + let peer_info = self.routing_table().get_own_peer_info(); - let will_route = network_class.can_inbound_relay(); // xxx: eventually this may have more criteria added - let will_tunnel = network_class.can_inbound_relay(); // xxx: we may want to restrict by battery life and network bandwidth at some point - let will_signal = network_class.can_signal(); - let will_relay = network_class.can_inbound_relay(); - let will_validate_dial_info = network_class.can_validate_dial_info(); + let will_route = peer_info.node_info.can_inbound_relay(); // xxx: eventually this may have more criteria added + let will_tunnel = peer_info.node_info.can_inbound_relay(); // xxx: we may want to restrict by battery life and network bandwidth at some point + let will_signal = peer_info.node_info.can_signal(); + let will_relay = peer_info.node_info.can_inbound_relay(); + let will_validate_dial_info = peer_info.node_info.can_validate_dial_info(); NodeStatus { will_route, @@ -500,20 +500,23 @@ impl NetworkManager { // Get the udp direct dialinfo for the hole punch peer_nr.filter_protocols(ProtocolSet::only(ProtocolType::UDP)); - let hole_punch_dial_info = peer_nr - .first_filtered_dial_info() + let hole_punch_dial_info_detail = peer_nr + .first_filtered_dial_info_detail(Some(RoutingDomain::PublicInternet)) .ok_or_else(|| "No hole punch capable dialinfo found for node".to_owned())?; // Do our half of the hole punch by sending an empty packet // Both sides will do this and then the receipt will get sent over the punched hole self.net() - .send_data_to_dial_info(hole_punch_dial_info.clone(), Vec::new()) + .send_data_to_dial_info( + hole_punch_dial_info_detail.dial_info.clone(), + Vec::new(), + ) .await?; // XXX: do we need a delay here? or another hole punch packet? // Return the receipt over the direct channel since we want to use exactly the same dial info - self.send_direct_receipt(hole_punch_dial_info, receipt, false) + self.send_direct_receipt(hole_punch_dial_info_detail.dial_info, receipt, false) .await .map_err(map_to_string)?; } @@ -623,57 +626,88 @@ impl NetworkManager { } // Figure out how to reach a node - fn get_contact_method(&self, node_ref: NodeRef) -> Result { + fn get_contact_method(&self, target_node_ref: NodeRef) -> Result { + let routing_table = self.routing_table(); + // Get our network class and protocol config let our_network_class = self.get_network_class().unwrap_or(NetworkClass::Invalid); let our_protocol_config = self.get_protocol_config().unwrap(); // Scope noderef down to protocols we can do outbound - if !node_ref.filter_protocols(our_protocol_config.outbound) { + if !target_node_ref.filter_protocols(our_protocol_config.outbound) { return Ok(ContactMethod::Unreachable); } - // Get the best matching direct dial info if we have it - let opt_direct_dial_info = node_ref.first_filtered_dial_info(); - - // See if this is a local node reachable directly - if let Some(direct_dial_info) = opt_direct_dial_info { - if direct_dial_info.is_local() { - return Ok(ContactMethod::Direct(direct_dial_info)); - } + // Get the best matching local direct dial info if we have it + let opt_local_did = + target_node_ref.first_filtered_dial_info_detail(Some(RoutingDomain::LocalNetwork)); + if let Some(local_did) = opt_local_did { + return Ok(ContactMethod::Direct(local_did.dial_info)); } + // Get the best match internet dial info if we have it + let opt_public_did = + target_node_ref.first_filtered_dial_info_detail(Some(RoutingDomain::PublicInternet)); + // Can the target node do inbound? - let target_network_class = node_ref.network_class(); - if target_network_class.inbound_capable() { + let target_network_class = target_node_ref.network_class(); + //if matches!(target_network_class, NetworkClass::InboundCapable) { + if let Some(public_did) = opt_public_did { // Do we need to signal before going inbound? - if target_network_class.inbound_requires_signal() { + if public_did.class.requires_signal() { // Get the target's inbound relay, it must have one or it is not reachable - if let Some(inbound_relay_nr) = node_ref.relay() { + if let Some(inbound_relay_nr) = target_node_ref.relay() { // Can we reach the inbound relay? - if inbound_relay_nr.first_filtered_dial_info().is_some() { + if inbound_relay_nr + .first_filtered_dial_info_detail(Some(RoutingDomain::PublicInternet)) + .is_some() + { // Can we receive anything inbound ever? - if our_network_class.inbound_capable() { - // Can we receive a direct reverse connection? - if !our_network_class.inbound_requires_signal() { - return Ok(ContactMethod::SignalReverse( - inbound_relay_nr, - node_ref, - )); - } - // Can we hole-punch? - else if our_protocol_config.inbound.contains(ProtocolType::UDP) - && node_ref.outbound_protocols().contains(ProtocolType::UDP) + if matches!(our_network_class, NetworkClass::InboundCapable) { + // Get the best match dial info for an reverse inbound connection + let reverse_dif = DialInfoFilter::global() + .with_protocol_set(target_node_ref.outbound_protocols()); + if let Some(reverse_did) = routing_table + .first_filtered_dial_info_detail( + RoutingDomain::PublicInternet, + &reverse_dif, + ) { - let udp_inbound_relay_nr = inbound_relay_nr.clone(); - let udp_target_nr = node_ref.clone(); - let can_reach_inbound_relay = udp_inbound_relay_nr + // Can we receive a direct reverse connection? + if !reverse_did.class.requires_signal() { + return Ok(ContactMethod::SignalReverse( + inbound_relay_nr, + target_node_ref, + )); + } + } + + // Does we and the target have outbound protocols to hole-punch? + if our_protocol_config.outbound.contains(ProtocolType::UDP) + && target_node_ref + .outbound_protocols() + .contains(ProtocolType::UDP) + { + // Do the target and self nodes have a direct udp dialinfo + let udp_dif = + DialInfoFilter::global().with_protocol_type(ProtocolType::UDP); + let udp_target_nr = target_node_ref.clone(); + udp_target_nr .filter_protocols(ProtocolSet::only(ProtocolType::UDP)); - let can_reach_target = udp_target_nr - .filter_protocols(ProtocolSet::only(ProtocolType::UDP)); - if can_reach_inbound_relay && can_reach_target { + let target_has_udp_dialinfo = target_node_ref + .first_filtered_dial_info_detail(Some( + RoutingDomain::PublicInternet, + )) + .is_some(); + let self_has_udp_dialinfo = routing_table + .first_filtered_dial_info_detail( + RoutingDomain::PublicInternet, + &udp_dif, + ) + .is_some(); + if target_has_udp_dialinfo && self_has_udp_dialinfo { return Ok(ContactMethod::SignalHolePunch( - udp_inbound_relay_nr, + inbound_relay_nr, udp_target_nr, )); } @@ -688,15 +722,18 @@ impl NetworkManager { // Go direct without signaling else { // If we have direct dial info we can use, do it - if let Some(ddi) = opt_direct_dial_info { - return Ok(ContactMethod::Direct(ddi)); + if let Some(did) = opt_public_did { + return Ok(ContactMethod::Direct(did.dial_info)); } } } else { // If the other node is not inbound capable at all, it is using a full relay - if let Some(target_inbound_relay_nr) = node_ref.relay() { + if let Some(target_inbound_relay_nr) = target_node_ref.relay() { // Can we reach the full relay? - if target_inbound_relay_nr.first_filtered_dial_info().is_some() { + if target_inbound_relay_nr + .first_filtered_dial_info_detail(Some(RoutingDomain::PublicInternet)) + .is_some() + { return Ok(ContactMethod::InboundRelay(target_inbound_relay_nr)); } } @@ -806,14 +843,14 @@ impl NetworkManager { let peer_info = self.routing_table().get_own_peer_info(); // Get the udp direct dialinfo for the hole punch - let hole_punch_dial_info = target_nr - .first_filtered_dial_info() + let hole_punch_did = target_nr + .first_filtered_dial_info_detail(Some(RoutingDomain::PublicInternet)) .ok_or_else(|| "No hole punch capable dialinfo found for node".to_owned())?; // Do our half of the hole punch by sending an empty packet // Both sides will do this and then the receipt will get sent over the punched hole self.net() - .send_data_to_dial_info(hole_punch_dial_info, Vec::new()) + .send_data_to_dial_info(hole_punch_did.dial_info, Vec::new()) .await?; // Issue the signal @@ -1070,47 +1107,49 @@ impl NetworkManager { async fn relay_management_task_routine(self, _last_ts: u64, cur_ts: u64) -> Result<(), String> { log_net!("--- network manager relay_management task"); - // Get our node's current network class and do the right thing + // Get our node's current node info and network class and do the right thing + let routing_table = self.routing_table(); + let node_info = routing_table.get_own_peer_info().node_info; let network_class = self.get_network_class(); // Do we know our network class yet? if let Some(network_class) = network_class { - let routing_table = self.routing_table(); - // If we already have a relay, see if it is dead, or if we don't need it any more { let mut inner = self.inner.lock(); if let Some(relay_node) = inner.relay_node.clone() { let state = relay_node.operate(|e| e.state(cur_ts)); - if matches!(state, BucketEntryState::Dead) || !network_class.needs_relay() { + if matches!(state, BucketEntryState::Dead) || !node_info.requires_relay() { // Relay node is dead or no longer needed inner.relay_node = None; } } } - // Do we need an outbound relay? - if network_class.outbound_wants_relay() { - // The outbound relay is the host of the PWA - if let Some(outbound_relay_peerinfo) = intf::get_outbound_relay_peer().await { - let mut inner = self.inner.lock(); + // Do we need a relay? + if node_info.requires_relay() { + // Do we need an outbound relay? + if network_class.outbound_wants_relay() { + // The outbound relay is the host of the PWA + if let Some(outbound_relay_peerinfo) = intf::get_outbound_relay_peer().await { + let mut inner = self.inner.lock(); - // Register new outbound relay - let nr = routing_table.register_node_with_node_info( - outbound_relay_peerinfo.node_id.key, - outbound_relay_peerinfo.node_info, - )?; - inner.relay_node = Some(nr); - } - } else if network_class.needs_relay() { - // Find a node in our routing table that is an acceptable inbound relay - if let Some(nr) = routing_table.find_inbound_relay(cur_ts) { - let mut inner = self.inner.lock(); - inner.relay_node = Some(nr); + // Register new outbound relay + let nr = routing_table.register_node_with_node_info( + outbound_relay_peerinfo.node_id.key, + outbound_relay_peerinfo.node_info, + )?; + inner.relay_node = Some(nr); + } + // Otherwise we must need an inbound relay + } else { + // Find a node in our routing table that is an acceptable inbound relay + if let Some(nr) = routing_table.find_inbound_relay(cur_ts) { + let mut inner = self.inner.lock(); + inner.relay_node = Some(nr); + } } } - } else { - // If we don't know our network class, we do nothing here and wait until we do } Ok(()) @@ -1208,14 +1247,11 @@ impl NetworkManager { .global_address_check_cache .insert(reporting_peer.node_id(), socket_address); - let network_class = inner - .components - .unwrap() - .net - .get_network_class() - .unwrap_or(NetworkClass::Invalid); + let net = inner.components.as_ref().unwrap().net.clone(); - if network_class.inbound_capable() { + let network_class = net.get_network_class().unwrap_or(NetworkClass::Invalid); + + if matches!(network_class, NetworkClass::InboundCapable) { // If we are inbound capable, but start to see inconsistent socket addresses from multiple reporting peers // then we zap the network class and re-detect it @@ -1225,7 +1261,7 @@ impl NetworkManager { // If we are currently outbound only, we don't have any public dial info // but if we are starting to see consistent socket address from multiple reporting peers // then we may be become inbound capable, so zap the network class so we can re-detect it and any public dial info - inner.components.unwrap().net.reset_network_class(); + net.reset_network_class(); } } } diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 991b0ecf..b98a298b 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -209,8 +209,8 @@ impl RoutingTable { let inner = self.inner.lock(); let mut ret = Vec::new(); - if domain == None || domain == Some(RoutingDomain::Local) { - Self::with_routing_domain(&*inner, RoutingDomain::Local, |rd| { + if domain == None || domain == Some(RoutingDomain::LocalNetwork) { + Self::with_routing_domain(&*inner, RoutingDomain::LocalNetwork, |rd| { for did in rd.dial_info_details { if did.matches_filter(filter) { ret.push(did.clone()); @@ -261,6 +261,7 @@ impl RoutingTable { dial_info: dial_info.clone(), class, }); + rd.dial_info_details.sort(); }); let domain_str = match domain { @@ -520,7 +521,7 @@ impl RoutingTable { Destination::Direct(node_ref.clone()), node_id, None, - rpc_processor.get_respond_to_sender(node_ref.clone()), + rpc_processor.make_respond_to_sender(node_ref.clone()), ) .await .map_err(map_to_string) @@ -613,7 +614,7 @@ impl RoutingTable { .or_insert_with(Vec::new) .push(DialInfoDetail { dial_info: ndis.dial_info, - class: DialInfoClass::Direct, + class: DialInfoClass::Direct, // Bootstraps are always directly reachable }); } log_rtab!(" bootstrap list: {:?}", bsmap); @@ -626,7 +627,7 @@ impl RoutingTable { .register_node_with_node_info( k, NodeInfo { - network_class: NetworkClass::Server, // Bootstraps are always full servers + network_class: NetworkClass::InboundCapable, // Bootstraps are always inbound capable outbound_protocols: ProtocolSet::empty(), // Bootstraps do not participate in relaying and will not make outbound requests dial_info_detail_list: v, // Dial info is as specified in the bootstrap list relay_peer_info: None, // Bootstraps never require a relay themselves diff --git a/veilid-core/src/rpc_processor/coders/dial_info_detail.rs b/veilid-core/src/rpc_processor/coders/dial_info_detail.rs index 4fd85bb1..3a237c18 100644 --- a/veilid-core/src/rpc_processor/coders/dial_info_detail.rs +++ b/veilid-core/src/rpc_processor/coders/dial_info_detail.rs @@ -6,7 +6,7 @@ pub fn encode_dial_info_detail( builder: &mut veilid_capnp::dial_info_detail::Builder, ) -> Result<(), RPCError> { let mut di_builder = builder.reborrow().init_dial_info(); - encode_dial_info(&node_info.dial_info, &mut di_builder)?; + encode_dial_info(&dial_info_detail.dial_info, &mut di_builder)?; builder.set_class(encode_dial_info_class(dial_info_detail.class)); Ok(()) @@ -22,7 +22,7 @@ pub fn decode_dial_info_detail( .map_err(map_error_capnp_error!())?, )?; - let dial_info_class = decode_dial_info_class( + let class = decode_dial_info_class( reader .reborrow() .get_class() diff --git a/veilid-core/src/rpc_processor/coders/network_class.rs b/veilid-core/src/rpc_processor/coders/network_class.rs index 1a272414..eaea3d78 100644 --- a/veilid-core/src/rpc_processor/coders/network_class.rs +++ b/veilid-core/src/rpc_processor/coders/network_class.rs @@ -2,26 +2,17 @@ use crate::*; pub fn encode_network_class(network_class: NetworkClass) -> veilid_capnp::NetworkClass { match network_class { - NetworkClass::Server => veilid_capnp::NetworkClass::Server, - NetworkClass::Mapped => veilid_capnp::NetworkClass::Mapped, - NetworkClass::FullConeNAT => veilid_capnp::NetworkClass::FullConeNAT, - NetworkClass::AddressRestrictedNAT => veilid_capnp::NetworkClass::AddressRestrictedNAT, - NetworkClass::PortRestrictedNAT => veilid_capnp::NetworkClass::PortRestrictedNAT, + NetworkClass::InboundCapable => veilid_capnp::NetworkClass::InboundCapable, NetworkClass::OutboundOnly => veilid_capnp::NetworkClass::OutboundOnly, NetworkClass::WebApp => veilid_capnp::NetworkClass::WebApp, - NetworkClass::Invalid => veilid_capnp::NetworkClass::Invalid, + NetworkClass::Invalid => panic!("invalid network class should not be encoded"), } } pub fn decode_network_class(network_class: veilid_capnp::NetworkClass) -> NetworkClass { match network_class { - veilid_capnp::NetworkClass::Server => NetworkClass::Server, - veilid_capnp::NetworkClass::Mapped => NetworkClass::Mapped, - veilid_capnp::NetworkClass::FullConeNAT => NetworkClass::FullConeNAT, - veilid_capnp::NetworkClass::AddressRestrictedNAT => NetworkClass::AddressRestrictedNAT, - veilid_capnp::NetworkClass::PortRestrictedNAT => NetworkClass::PortRestrictedNAT, + veilid_capnp::NetworkClass::InboundCapable => NetworkClass::InboundCapable, veilid_capnp::NetworkClass::OutboundOnly => NetworkClass::OutboundOnly, veilid_capnp::NetworkClass::WebApp => NetworkClass::WebApp, - veilid_capnp::NetworkClass::Invalid => NetworkClass::Invalid, } } diff --git a/veilid-core/src/rpc_processor/coders/node_info.rs b/veilid-core/src/rpc_processor/coders/node_info.rs index 6d9aa8bc..46dc3097 100644 --- a/veilid-core/src/rpc_processor/coders/node_info.rs +++ b/veilid-core/src/rpc_processor/coders/node_info.rs @@ -55,14 +55,14 @@ pub fn decode_node_info( .reborrow() .get_dial_info_detail_list() .map_err(map_error_capnp_error!())?; - let mut dial_info_detail_list = Vec::::with_capacity( + let mut dial_info_detail_list = Vec::::with_capacity( didl_reader .len() .try_into() .map_err(map_error_protocol!("too many dial info details"))?, ); - for di in dil_reader.iter() { - dial_info_detail_list.push(decode_dial_info_detail(&di)?) + for did in didl_reader.iter() { + dial_info_detail_list.push(decode_dial_info_detail(&did)?) } let relay_peer_info = if allow_relay_peer_info { diff --git a/veilid-core/src/rpc_processor/debug.rs b/veilid-core/src/rpc_processor/debug.rs index dac121fb..c9470c4f 100644 --- a/veilid-core/src/rpc_processor/debug.rs +++ b/veilid-core/src/rpc_processor/debug.rs @@ -204,26 +204,8 @@ impl RPCProcessor { return format!("(invalid node id: {})", e); } }; - - let sni_reader = match fnqr.reborrow().get_sender_node_info() { - Ok(snir) => snir, - Err(e) => { - return format!("(invalid sender node info: {})", e); - } - }; - let sender_node_info = match decode_node_info(&sni_reader, true) { - Ok(v) => v, - Err(e) => { - return format!("(unable to decode node info: {})", e); - } - }; - let node_id = decode_public_key(&nidr); - format!( - "FindNodeQ: node_id={} sender_node_info={:#?}", - node_id.encode(), - sender_node_info - ) + format!("FindNodeQ: node_id={}", node_id.encode(),) } veilid_capnp::operation::detail::FindNodeA(d) => { let fnar = match d { @@ -236,7 +218,7 @@ impl RPCProcessor { let p_reader = match fnar.reborrow().get_peers() { Ok(pr) => pr, Err(e) => { - return format!("(invalid sender node info: {})", e); + return format!("(invalid peers: {})", e); } }; let mut peers = Vec::::with_capacity(match p_reader.len().try_into() { diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index f682a99c..2a50ff78 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -213,22 +213,22 @@ impl RPCProcessor { get_random_u64() } - fn filter_peer_scope(&self, peer_info: &PeerInfo) -> bool { + fn filter_peer_scope(&self, node_info: &NodeInfo) -> bool { // if local peer scope is enabled, then don't reject any peer info if self.enable_local_peer_scope { return true; } // reject attempts to include non-public addresses in results - for di in &peer_info.node_info.dial_info_list { - if !di.is_global() { + for did in &node_info.dial_info_detail_list { + if !did.dial_info.is_global() { // non-public address causes rejection return false; } } - if let Some(rpi) = &peer_info.node_info.relay_peer_info { - for di in &rpi.node_info.dial_info_list { - if !di.is_global() { + if let Some(rpi) = &node_info.relay_peer_info { + for did in &rpi.node_info.dial_info_detail_list { + if !did.dial_info.is_global() { // non-public address causes rejection return false; } @@ -964,26 +964,8 @@ impl RPCProcessor { .map_err(logthru_rpc!())?, ); - // get the sender NodeInfo of the requesting node - let sni_reader = fnq_reader - .reborrow() - .get_sender_node_info() - .map_err(map_error_capnp_error!())?; - let peer_info = PeerInfo { - node_id: NodeId::new(rpcreader.header.envelope.get_sender_id()), - node_info: decode_node_info(&sni_reader, true)?, - }; - - // filter out attempts to pass non-public addresses in for peers - if !self.filter_peer_scope(&peer_info) { - return Err(RPCError::InvalidFormat); - } - // add node information for the requesting node to our routing table let routing_table = self.routing_table(); - let _requesting_node_ref = routing_table - .register_node_with_node_info(peer_info.node_id.key, peer_info.node_info) - .map_err(map_error_string!())?; // find N nodes closest to the target node in our routing table let own_peer_info = routing_table.get_own_peer_info(); @@ -1192,6 +1174,9 @@ impl RPCProcessor { opt_sender_nr = if let Some(sender_ni) = self.get_respond_to_sender_node_info(&operation)? { // Sender NodeInfo was specified, update our routing table with it + if !self.filter_peer_scope(&sender_ni) { + return Err(RPCError::InvalidFormat); + } let nr = self .routing_table() .register_node_with_node_info( @@ -1544,11 +1529,6 @@ impl RPCProcessor { let mut node_id_builder = fnq.reborrow().init_node_id(); encode_public_key(&key, &mut node_id_builder)?; - let own_peer_info = self.routing_table().get_own_peer_info(); - - let mut ni_builder = fnq.reborrow().init_sender_node_info(); - encode_node_info(&own_peer_info.node_info, &mut ni_builder)?; - find_node_q_msg.into_reader() }; @@ -1590,7 +1570,7 @@ impl RPCProcessor { for p in peers_reader.iter() { let peer_info = decode_peer_info(&p, true)?; - if !self.filter_peer_scope(&peer_info) { + if !self.filter_peer_scope(&peer_info.node_info) { return Err(RPCError::InvalidFormat); } diff --git a/veilid-core/src/veilid_api/mod.rs b/veilid-core/src/veilid_api/mod.rs index 80f150dd..7d67f858 100644 --- a/veilid-core/src/veilid_api/mod.rs +++ b/veilid-core/src/veilid_api/mod.rs @@ -235,6 +235,7 @@ pub struct SenderInfo { pub socket_address: Option, } +// Keep member order appropriate for sorting < preference #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] pub enum DialInfoClass { Direct = 0, // D = Directly reachable with public IP and no firewall, with statically configured port @@ -268,10 +269,11 @@ impl DialInfoClass { } } +// Keep member order appropriate for sorting < preference #[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)] pub struct DialInfoDetail { - pub dial_info: DialInfo, pub class: DialInfoClass, + pub dial_info: DialInfo, } impl MatchesDialInfoFilter for DialInfoDetail { @@ -445,7 +447,7 @@ impl LocalNodeInfo { } #[derive(Debug, PartialOrd, Ord, Hash, Serialize, Deserialize, EnumSetType)] -// The derived ordering here is the order of preference, lower is preferred for connections +// Keep member order appropriate for sorting < preference // Must match DialInfo order pub enum ProtocolType { UDP, @@ -661,6 +663,10 @@ impl DialInfoFilter { self.protocol_set = ProtocolSet::only(protocol_type); self } + pub fn with_protocol_set(mut self, protocol_set: ProtocolSet) -> Self { + self.protocol_set = protocol_set; + self + } pub fn with_address_type(mut self, address_type: AddressType) -> Self { self.address_type = Some(address_type); self @@ -709,7 +715,7 @@ pub struct DialInfoWSS { #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)] #[serde(tag = "kind")] -// The derived ordering here is the order of preference, lower is preferred for connections +// Keep member order appropriate for sorting < preference // Must match ProtocolType order pub enum DialInfo { UDP(DialInfoUDP),