From 1156159748493514f8a5622581c001ed96810f18 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 23 Apr 2022 22:08:02 -0400 Subject: [PATCH] dialinfoclass refactor, cleaning up network class detection --- veilid-core/proto/veilid.capnp | 30 +-- .../native/network/network_class_discovery.rs | 153 ++++++++++++--- veilid-core/src/routing_table/find_nodes.rs | 10 +- veilid-core/src/routing_table/mod.rs | 70 ++++--- veilid-core/src/routing_table/node_ref.rs | 87 ++++++--- .../rpc_processor/coders/dial_info_class.rs | 23 +++ .../rpc_processor/coders/dial_info_detail.rs | 33 ++++ veilid-core/src/rpc_processor/coders/mod.rs | 4 + .../src/rpc_processor/coders/node_info.rs | 28 +-- veilid-core/src/rpc_processor/mod.rs | 14 +- veilid-core/src/veilid_api/mod.rs | 176 ++++++++++-------- veilid-core/src/xx/tools.rs | 17 ++ 12 files changed, 435 insertions(+), 210 deletions(-) create mode 100644 veilid-core/src/rpc_processor/coders/dial_info_class.rs create mode 100644 veilid-core/src/rpc_processor/coders/dial_info_detail.rs diff --git a/veilid-core/proto/veilid.capnp b/veilid-core/proto/veilid.capnp index d4309791..3d92e99b 100644 --- a/veilid-core/proto/veilid.capnp +++ b/veilid-core/proto/veilid.capnp @@ -179,16 +179,24 @@ struct OperationInfoQ { nodeStatus @0 :NodeStatus; # node status update about the infoq sender } - enum NetworkClass { - server @0; # S = Device with public IP and no UDP firewall - mapped @1; # M = Device with portmap behind any NAT - fullConeNAT @2; # F = Device without portmap behind full-cone NAT - addressRestrictedNAT @3; # A = Device without portmap behind address-only restricted NAT - portRestrictedNAT @4; # P = Device without portmap behind address-and-port restricted NAT - outboundOnly @5; # O = Outbound only - webApp @6; # W = PWA - invalid @7; # I = Invalid + inboundCapable @0; # I = Inbound capable without relay, may require signal + outboundOnly @1; # O = Outbound only, inbound relay required except with reverse connect signal + webApp @2; # W = PWA, outbound relay is required in most cases +} + +enum DialInfoClass { + direct @0; # D = Directly reachable with public IP and no firewall, with statically configured port + mapped @1; # M = Directly reachable with via portmap behind any NAT or firewalled with dynamically negotiated port + fullConeNAT @2; # F = Directly reachable device without portmap behind full-cone NAT + blocked @3; # B = Inbound blocked at firewall but may hole punch with public address + addressRestrictedNAT @4; # A = Device without portmap behind address-only restricted NAT + portRestrictedNAT @5; # P = Device without portmap behind address-and-port restricted NAT +} + +struct DialInfoDetail { + dialInfo @0; :DialInfo; + class @1; :DialInfoClass; } struct NodeStatus { @@ -208,8 +216,8 @@ struct ProtocolSet { struct NodeInfo { networkClass @0 :NetworkClass; # network class of this node - outboundProtocols @1 :ProtocolSet; # protocols that can go outbound - dialInfoList @2 :List(DialInfo); # inbound dial info for this node + outboundProtocols @1 :ProtocolSet; # protocols that can go outbound + dialInfoDetailList @2 :List(DialInfoDetail); # inbound dial info details for this node relayPeerInfo @3 :PeerInfo; # (optional) relay peer info for this node } 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 b10d27e9..f36a9841 100644 --- a/veilid-core/src/intf/native/network/network_class_discovery.rs +++ b/veilid-core/src/intf/native/network/network_class_discovery.rs @@ -4,6 +4,34 @@ use crate::intf::*; use crate::routing_table::*; use crate::*; +#[derive(Debug)] +struct DiscoveryContext { + routing_table: RoutingTable, + external_ipv4: Option, + external_ipv6: Option, + network_class: Option, +} + +impl DiscoveryContext { + pub fn new(routing_table: RoutingTable) -> Self { + Self { + routing_table, + external_ipv4: None, + external_ipv6: None, + network_class: None, + } + } + pub fn upgrade_network_class(&mut self, network_class: NetworkClass) { + if let Some(old_nc) = self.network_class { + if network_class < old_nc { + self.network_class = Some(network_class); + } + } else { + self.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 { @@ -100,7 +128,12 @@ impl Network { None } - pub async fn update_udpv4_dialinfo(&self) -> Result<(), String> { + xxx split this routine up into helper routines that can be used by different protocols too. + + pub async fn update_udpv4_dialinfo( + &self, + context: &mut DiscoveryContext, + ) -> Result<(), String> { log_net!("looking for udpv4 public dial info"); let routing_table = self.routing_table(); @@ -130,24 +163,41 @@ impl Network { // If our local interface list contains external1 then there is no NAT in place if intf_addrs.contains(&external1) { // No NAT - // Do a validate_dial_info on the external address from a routed node + // 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 Server network class - routing_table.register_public_dial_info( + // Add public dial info with Direct dialinfo class + routing_table.register_dial_info( + RoutingDomain::PublicInternet, external1_dial_info, - DialInfoOrigin::Discovered, - Some(NetworkClass::Server), + DialInfoClass::Direct, ); - - // No more retries - break; - } else { - // UDP firewall? - log_net!("UDP static public dial info not reachable. UDP firewall may be blocking inbound to {:?} for {:?}",external1_dial_info, node_b); } + // 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); + // No more retries + break; } else { // There is -some NAT- // Attempt a UDP port mapping via all available and enabled mechanisms @@ -157,11 +207,12 @@ impl Network { { // Got a port mapping, let's use it let external_mapped_dial_info = DialInfo::udp(external_mapped); - routing_table.register_public_dial_info( + routing_table.register_dial_info( + RoutingDomain::PublicInternet, external_mapped_dial_info, - DialInfoOrigin::Mapped, - Some(NetworkClass::Mapped), + DialInfoClass::Mapped, ); + context.upgrade_network_class(NetworkClass::InboundCapable); // No more retries break; @@ -180,11 +231,12 @@ impl Network { { // 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_public_dial_info( + routing_table.register_dial_info( + RoutingDomain::PublicInternet, external1_dial_info, - DialInfoOrigin::Discovered, - Some(NetworkClass::FullConeNAT), + DialInfoClass::FullConeNAT, ); + context.upgrade_network_class(NetworkClass::InboundCapable); // No more retries break; @@ -209,7 +261,7 @@ impl Network { // 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.inner.lock().network_class = Some(NetworkClass::OutboundOnly); + context.upgrade_network_class(NetworkClass::OutboundOnly); // No more retries break; @@ -230,19 +282,20 @@ impl Network { .await { // Got a reply from a non-default port, which means we're only address restricted - routing_table.register_public_dial_info( + routing_table.register_dial_info( + RoutingDomain::PublicInternet, external1_dial_info, - DialInfoOrigin::Discovered, - Some(NetworkClass::AddressRestrictedNAT), + DialInfoClass::AddressRestrictedNAT, ); } else { // Didn't get a reply from a non-default port, which means we are also port restricted - routing_table.register_public_dial_info( + routing_table.register_dial_info( + RoutingDomain::PublicInternet, external1_dial_info, - DialInfoOrigin::Discovered, - Some(NetworkClass::PortRestrictedNAT), + DialInfoClass::PortRestrictedNAT, ); } + context.upgrade_network_class(NetworkClass::InboundCapable); } } } @@ -255,18 +308,49 @@ impl Network { } } + // 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(&self) -> Result<(), String> { + pub async fn update_tcpv4_dialinfo( + &self, + context: &mut DiscoveryContext, + ) -> Result<(), String> { log_net!("looking for tcpv4 public dial info"); + + 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(()) } - pub async fn update_wsv4_dialinfo(&self) -> Result<(), String> { - log_net!("looking for wsv4 public dial info"); + 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(()) + } + + 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(()) @@ -282,18 +366,25 @@ impl Network { .clone() .unwrap_or_default(); + let mut context = DiscoveryContext::default(); + if protocol_config.inbound.contains(ProtocolType::UDP) { - self.update_udpv4_dialinfo().await?; + self.update_udpv4_dialinfo(&mut context).await?; + self.update_udpv6_dialinfo(&mut context).await?; } if protocol_config.inbound.contains(ProtocolType::TCP) { - self.update_tcpv4_dialinfo().await?; + self.update_tcpv4_dialinfo(&mut context).await?; + self.update_tcpv6_dialinfo(&mut context).await?; } if protocol_config.inbound.contains(ProtocolType::WS) { - self.update_wsv4_dialinfo().await?; + self.update_wsv4_dialinfo(&mut context).await?; + self.update_wsv6_dialinfo(&mut context).await?; } + self.inner.lock().network_class = context.network_class; + Ok(()) } } diff --git a/veilid-core/src/routing_table/find_nodes.rs b/veilid-core/src/routing_table/find_nodes.rs index f0ed3308..9395c74f 100644 --- a/veilid-core/src/routing_table/find_nodes.rs +++ b/veilid-core/src/routing_table/find_nodes.rs @@ -29,7 +29,9 @@ impl RoutingTable { // does it have matching public dial info? entry .node_info() - .first_filtered_dial_info(|di| di.matches_filter(&dial_info_filter1)) + .first_filtered_dial_info_detail(|did| { + did.matches_filter(&dial_info_filter1) + }) .is_some() }, )), @@ -55,11 +57,7 @@ impl RoutingTable { node_info: NodeInfo { network_class: netman.get_network_class().unwrap_or(NetworkClass::Invalid), outbound_protocols: netman.get_protocol_config().unwrap_or_default().outbound, - dial_info_list: self - .dial_info_details(RoutingDomain::PublicInternet) - .iter() - .map(|did| did.dial_info.clone()) - .collect(), + dial_info_detail_list: self.dial_info_details(RoutingDomain::PublicInternet), relay_peer_info: relay_node.map(|rn| Box::new(rn.peer_info())), }, } diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 0b74aa80..991b0ecf 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -22,13 +22,6 @@ pub use stats_accounting::*; ////////////////////////////////////////////////////////////////////////// -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)] -pub enum DialInfoOrigin { - Static, - Discovered, - Mapped, -} - #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)] pub enum RoutingDomain { PublicInternet, @@ -40,19 +33,6 @@ pub struct RoutingDomainDetail { dial_info_details: Vec, } -#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] -pub struct DialInfoDetail { - pub dial_info: DialInfo, - pub origin: DialInfoOrigin, - pub timestamp: u64, -} - -impl MatchesDialInfoFilter for DialInfoDetail { - fn matches_filter(&self, filter: &DialInfoFilter) -> bool { - self.dial_info.matches_filter(filter) - } -} - struct RoutingTableInner { network_manager: NetworkManager, node_id: DHTKey, @@ -223,28 +203,40 @@ impl RoutingTable { pub fn all_filtered_dial_info_details( &self, - domain: RoutingDomain, + domain: Option, filter: &DialInfoFilter, ) -> Vec { let inner = self.inner.lock(); - Self::with_routing_domain(&*inner, domain, |rd| { - let mut ret = Vec::new(); - for did in rd.dial_info_details { - if did.matches_filter(filter) { - ret.push(did.clone()); + let mut ret = Vec::new(); + + if domain == None || domain == Some(RoutingDomain::Local) { + Self::with_routing_domain(&*inner, RoutingDomain::Local, |rd| { + for did in rd.dial_info_details { + if did.matches_filter(filter) { + ret.push(did.clone()); + } } - } - ret - }) + }); + } + if domain == None || domain == Some(RoutingDomain::PublicInternet) { + Self::with_routing_domain(&*inner, RoutingDomain::PublicInternet, |rd| { + for did in rd.dial_info_details { + if did.matches_filter(filter) { + ret.push(did.clone()); + } + } + }); + } + ret.remove_duplicates(); + ret } pub fn register_dial_info( &self, domain: RoutingDomain, dial_info: DialInfo, - origin: DialInfoOrigin, + class: DialInfoClass, ) { - let timestamp = get_timestamp(); let enable_local_peer_scope = { let config = self.network_manager().config(); let c = config.get(); @@ -267,8 +259,7 @@ impl RoutingTable { Self::with_routing_domain_mut(&mut *inner, domain, |rd| { rd.dial_info_details.push(DialInfoDetail { dial_info: dial_info.clone(), - origin, - timestamp, + class, }); }); @@ -285,7 +276,7 @@ impl RoutingTable { } .to_string(), ); - debug!(" Origin: {:?}", origin); + debug!(" Class: {:?}", class); } pub fn clear_dial_info_details(&self, domain: RoutingDomain) { @@ -611,7 +602,7 @@ impl RoutingTable { log_rtab!("--- bootstrap_task"); // Map all bootstrap entries to a single key with multiple dialinfo - let mut bsmap: BTreeMap> = BTreeMap::new(); + let mut bsmap: BTreeMap> = BTreeMap::new(); for b in bootstrap { let ndis = NodeDialInfo::from_str(b.as_str()) .map_err(map_to_string) @@ -620,7 +611,10 @@ impl RoutingTable { bsmap .entry(node_id) .or_insert_with(Vec::new) - .push(ndis.dial_info); + .push(DialInfoDetail { + dial_info: ndis.dial_info, + class: DialInfoClass::Direct, + }); } log_rtab!(" bootstrap list: {:?}", bsmap); @@ -634,8 +628,8 @@ impl RoutingTable { NodeInfo { network_class: NetworkClass::Server, // Bootstraps are always full servers outbound_protocols: ProtocolSet::empty(), // Bootstraps do not participate in relaying and will not make outbound requests - dial_info_list: v, // Dial info is as specified in the bootstrap list - relay_peer_info: None, // Bootstraps never require a relay themselves + dial_info_detail_list: v, // Dial info is as specified in the bootstrap list + relay_peer_info: None, // Bootstraps never require a relay themselves }, ) .map_err(logthru_rtab!("Couldn't add bootstrap node: {}", k))?; diff --git a/veilid-core/src/routing_table/node_ref.rs b/veilid-core/src/routing_table/node_ref.rs index ecf8040f..e5bb9c99 100644 --- a/veilid-core/src/routing_table/node_ref.rs +++ b/veilid-core/src/routing_table/node_ref.rs @@ -90,30 +90,42 @@ impl NodeRef { nr }) } - pub fn first_filtered_dial_info(&self) -> Option { + pub fn first_filtered_dial_info_detail( + &self, + routing_domain: Option, + ) -> Option { self.operate(|e| { - if matches!( - self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), - PeerScope::All | PeerScope::Local - ) { - e.local_node_info().first_filtered_dial_info(|di| { - if let Some(filter) = self.filter { - di.matches_filter(&filter) - } else { - true - } - }) + if (routing_domain == None || routing_domain == Some(RoutingDomain::LocalNetwork)) + && matches!( + self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), + PeerScope::All | PeerScope::Local + ) + { + e.local_node_info() + .first_filtered_dial_info(|di| { + if let Some(filter) = self.filter { + di.matches_filter(&filter) + } else { + true + } + }) + .map(|di| DialInfoDetail { + class: DialInfoClass::Direct, + dial_info: di, + }) } else { None } .or_else(|| { - if matches!( - self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), - PeerScope::All | PeerScope::Global - ) { - e.node_info().first_filtered_dial_info(|di| { + if (routing_domain == None || routing_domain == Some(RoutingDomain::PublicInternet)) + && matches!( + self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), + PeerScope::All | PeerScope::Global + ) + { + e.node_info().first_filtered_dial_info_detail(|did| { if let Some(filter) = self.filter { - di.matches_filter(&filter) + did.matches_filter(&filter) } else { true } @@ -125,34 +137,47 @@ impl NodeRef { }) } - pub fn all_filtered_dial_info(&self) -> Vec { + pub fn all_filtered_dial_info_details( + &self, + routing_domain: Option, + ) -> Vec { let mut out = Vec::new(); self.operate(|e| { - if matches!( - self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), - PeerScope::All | PeerScope::Global - ) { - out.append(&mut e.node_info().all_filtered_dial_info(|di| { + if (routing_domain == None || routing_domain == Some(RoutingDomain::LocalNetwork)) + && matches!( + self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), + PeerScope::All | PeerScope::Local + ) + { + for di in e.local_node_info().all_filtered_dial_info(|di| { if let Some(filter) = self.filter { di.matches_filter(&filter) } else { true } - })) + }) { + out.push(DialInfoDetail { + class: DialInfoClass::Direct, + dial_info: di, + }); + } } - if matches!( - self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), - PeerScope::All | PeerScope::Local - ) { - out.append(&mut e.local_node_info().all_filtered_dial_info(|di| { + if (routing_domain == None || routing_domain == Some(RoutingDomain::PublicInternet)) + && matches!( + self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), + PeerScope::All | PeerScope::Global + ) + { + out.append(&mut e.node_info().all_filtered_dial_info_details(|did| { if let Some(filter) = self.filter { - di.matches_filter(&filter) + did.matches_filter(&filter) } else { true } })) } }); + out.remove_duplicates(); out } diff --git a/veilid-core/src/rpc_processor/coders/dial_info_class.rs b/veilid-core/src/rpc_processor/coders/dial_info_class.rs new file mode 100644 index 00000000..71197171 --- /dev/null +++ b/veilid-core/src/rpc_processor/coders/dial_info_class.rs @@ -0,0 +1,23 @@ +use crate::*; + +pub fn encode_dial_info_class(dial_info_class: DialInfoClass) -> veilid_capnp::DialInfoClass { + match dial_info_class { + DialInfoClass::Direct => veilid_capnp::DialInfoClass::Direct, + DialInfoClass::Mapped => veilid_capnp::DialInfoClass::Mapped, + DialInfoClass::FullConeNAT => veilid_capnp::DialInfoClass::FullConeNAT, + DialInfoClass::Blocked => veilid_capnp::DialInfoClass::Blocked, + DialInfoClass::AddressRestrictedNAT => veilid_capnp::DialInfoClass::AddressRestrictedNAT, + DialInfoClass::PortRestrictedNAT => veilid_capnp::DialInfoClass::PortRestrictedNAT, + } +} + +pub fn decode_dial_info_class(dial_info_class: veilid_capnp::DialInfoClass) -> DialInfoClass { + match dial_info_class { + veilid_capnp::DialInfoClass::Direct => DialInfoClass::Direct, + veilid_capnp::DialInfoClass::Mapped => DialInfoClass::Mapped, + veilid_capnp::DialInfoClass::FullConeNAT => DialInfoClass::FullConeNAT, + veilid_capnp::DialInfoClass::Blocked => DialInfoClass::Blocked, + veilid_capnp::DialInfoClass::AddressRestrictedNAT => DialInfoClass::AddressRestrictedNAT, + veilid_capnp::DialInfoClass::PortRestrictedNAT => DialInfoClass::PortRestrictedNAT, + } +} diff --git a/veilid-core/src/rpc_processor/coders/dial_info_detail.rs b/veilid-core/src/rpc_processor/coders/dial_info_detail.rs new file mode 100644 index 00000000..4fd85bb1 --- /dev/null +++ b/veilid-core/src/rpc_processor/coders/dial_info_detail.rs @@ -0,0 +1,33 @@ +use crate::*; +use rpc_processor::*; + +pub fn encode_dial_info_detail( + dial_info_detail: &DialInfoDetail, + 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)?; + + builder.set_class(encode_dial_info_class(dial_info_detail.class)); + Ok(()) +} + +pub fn decode_dial_info_detail( + reader: &veilid_capnp::dial_info_detail::Reader, +) -> Result { + let dial_info = decode_dial_info( + &reader + .reborrow() + .get_dial_info() + .map_err(map_error_capnp_error!())?, + )?; + + let dial_info_class = decode_dial_info_class( + reader + .reborrow() + .get_class() + .map_err(map_error_capnp_notinschema!())?, + ); + + Ok(DialInfoDetail { dial_info, class }) +} diff --git a/veilid-core/src/rpc_processor/coders/mod.rs b/veilid-core/src/rpc_processor/coders/mod.rs index fc4968a6..86fdb14a 100644 --- a/veilid-core/src/rpc_processor/coders/mod.rs +++ b/veilid-core/src/rpc_processor/coders/mod.rs @@ -1,5 +1,7 @@ mod address; mod dial_info; +mod dial_info_class; +mod dial_info_detail; mod network_class; mod node_dial_info; mod node_info; @@ -15,6 +17,8 @@ mod socket_address; pub use address::*; pub use dial_info::*; +pub use dial_info_class::*; +pub use dial_info_detail::*; pub use network_class::*; pub use node_dial_info::*; pub use node_info::*; diff --git a/veilid-core/src/rpc_processor/coders/node_info.rs b/veilid-core/src/rpc_processor/coders/node_info.rs index eae6e3c0..6d9aa8bc 100644 --- a/veilid-core/src/rpc_processor/coders/node_info.rs +++ b/veilid-core/src/rpc_processor/coders/node_info.rs @@ -10,17 +10,19 @@ pub fn encode_node_info( let mut ps_builder = builder.reborrow().init_outbound_protocols(); encode_protocol_set(&node_info.outbound_protocols, &mut ps_builder)?; - let mut dil_builder = builder.reborrow().init_dial_info_list( + let mut didl_builder = builder.reborrow().init_dial_info_detail_list( node_info - .dial_info_list + .dial_info_detail_list .len() .try_into() - .map_err(map_error_protocol!("too many dial infos in node info"))?, + .map_err(map_error_protocol!( + "too many dial info details in node info" + ))?, ); - for idx in 0..node_info.dial_info_list.len() { - let mut di_builder = dil_builder.reborrow().get(idx as u32); - encode_dial_info(&node_info.dial_info_list[idx], &mut di_builder)?; + for idx in 0..node_info.dial_info_detail_list.len() { + let mut did_builder = didl_builder.reborrow().get(idx as u32); + encode_dial_info_detail(&node_info.dial_info_detail_list[idx], &mut did_builder)?; } if let Some(rpi) = &node_info.relay_peer_info { @@ -49,18 +51,18 @@ pub fn decode_node_info( .map_err(map_error_capnp_error!())?, )?; - let dil_reader = reader + let didl_reader = reader .reborrow() - .get_dial_info_list() + .get_dial_info_detail_list() .map_err(map_error_capnp_error!())?; - let mut dial_info_list = Vec::::with_capacity( - dil_reader + let mut dial_info_detail_list = Vec::::with_capacity( + didl_reader .len() .try_into() - .map_err(map_error_protocol!("too many dial infos"))?, + .map_err(map_error_protocol!("too many dial info details"))?, ); for di in dil_reader.iter() { - dial_info_list.push(decode_dial_info(&di)?) + dial_info_detail_list.push(decode_dial_info_detail(&di)?) } let relay_peer_info = if allow_relay_peer_info { @@ -82,7 +84,7 @@ pub fn decode_node_info( Ok(NodeInfo { network_class, outbound_protocols, - dial_info_list, + dial_info_detail_list, relay_peer_info, }) } diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index c613c477..f682a99c 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -1349,12 +1349,16 @@ impl RPCProcessor { } // Gets a 'RespondTo::Sender' that contains either our dial info, - // or None if the peer has seen our dial info before - pub fn get_respond_to_sender(&self, peer: NodeRef) -> RespondTo { - if peer.has_seen_our_node_info() { + // or None if the peer has seen our dial info before or our node info is not yet valid + // because of an unknown network class + pub fn make_respond_to_sender(&self, peer: NodeRef) -> RespondTo { + let our_node_info = self.routing_table().get_own_peer_info().node_info; + if peer.has_seen_our_node_info() + || matches!(our_node_info.network_class, NetworkClass::Invalid) + { RespondTo::Sender(None) } else { - RespondTo::Sender(Some(self.routing_table().get_own_peer_info().node_info)) + RespondTo::Sender(Some(our_node_info)) } } @@ -1366,7 +1370,7 @@ impl RPCProcessor { let mut question = info_q_msg.init_root::(); question.set_op_id(self.get_next_op_id()); let mut respond_to = question.reborrow().init_respond_to(); - self.get_respond_to_sender(peer.clone()) + self.make_respond_to_sender(peer.clone()) .encode(&mut respond_to)?; let detail = question.reborrow().init_detail(); let mut iqb = detail.init_info_q(); diff --git a/veilid-core/src/veilid_api/mod.rs b/veilid-core/src/veilid_api/mod.rs index cec18efc..80f150dd 100644 --- a/veilid-core/src/veilid_api/mod.rs +++ b/veilid-core/src/veilid_api/mod.rs @@ -236,78 +236,56 @@ pub struct SenderInfo { } #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] -pub enum NetworkClass { - Server = 0, // S = Device with public IP and no UDP firewall - Mapped = 1, // M = Device with portmap behind any NAT - FullConeNAT = 2, // F = Device without portmap behind full-cone NAT - AddressRestrictedNAT = 3, // A = Device without portmap behind address-only restricted NAT - PortRestrictedNAT = 4, // P = Device without portmap behind address-and-port restricted NAT - OutboundOnly = 5, // O = Outbound only - WebApp = 6, // W = PWA - Invalid = 7, // I = Invalid network class, unreachable or can not send packets +pub enum DialInfoClass { + Direct = 0, // D = Directly reachable with public IP and no firewall, with statically configured port + Mapped = 1, // M = Directly reachable with via portmap behind any NAT or firewalled with dynamically negotiated port + FullConeNAT = 2, // F = Directly reachable device without portmap behind full-cone NAT + Blocked = 3, // B = Inbound blocked at firewall but may hole punch with public address + AddressRestrictedNAT = 4, // A = Device without portmap behind address-only restricted NAT + PortRestrictedNAT = 5, // P = Device without portmap behind address-and-port restricted NAT } -impl NetworkClass { - // Can the node receive inbound requests without a relay? - pub fn inbound_capable(&self) -> bool { - matches!( - self, - Self::Server - | Self::Mapped - | Self::FullConeNAT - | Self::AddressRestrictedNAT - | Self::PortRestrictedNAT - ) - } - - // Should an outbound relay be kept available? - pub fn outbound_wants_relay(&self) -> bool { - matches!(self, Self::WebApp) - } - +impl DialInfoClass { // Is a signal required to do an inbound hole-punch? - pub fn inbound_requires_signal(&self) -> bool { - matches!(self, Self::AddressRestrictedNAT | Self::PortRestrictedNAT) - } - - // Is some relay required either for signal or inbound relay or outbound relay? - pub fn needs_relay(&self) -> bool { + pub fn requires_signal(&self) -> bool { matches!( self, - Self::AddressRestrictedNAT - | Self::PortRestrictedNAT - | Self::OutboundOnly - | Self::WebApp + Self::Blocked | Self::AddressRestrictedNAT | Self::PortRestrictedNAT ) } - // Must keepalive be used to preserve the public dialinfo in use? - // Keepalive can be to either a - pub fn dialinfo_requires_keepalive(&self) -> bool { + // Does a relay node need to be allocated for this dial info? + // For full cone NAT, the relay itself may not be used but the keepalive sent to it + // is required to keep the NAT mapping valid in the router state table + pub fn requires_relay(&self) -> bool { matches!( self, Self::FullConeNAT + | Self::Blocked | Self::AddressRestrictedNAT | Self::PortRestrictedNAT - | Self::OutboundOnly - | Self::WebApp ) } +} - // Can this node assist with signalling? Yes but only if it doesn't require signalling, itself. - pub fn can_signal(&self) -> bool { - self.inbound_capable() && !self.inbound_requires_signal() - } +#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)] +pub struct DialInfoDetail { + pub dial_info: DialInfo, + pub class: DialInfoClass, +} - // Can this node relay be an inbound relay? - pub fn can_inbound_relay(&self) -> bool { - matches!(self, Self::Server | Self::Mapped | Self::FullConeNAT) +impl MatchesDialInfoFilter for DialInfoDetail { + fn matches_filter(&self, filter: &DialInfoFilter) -> bool { + self.dial_info.matches_filter(filter) } +} - // Is this node capable of validating dial info - pub fn can_validate_dial_info(&self) -> bool { - matches!(self, Self::Server | Self::Mapped | Self::FullConeNAT) - } +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] +pub enum NetworkClass { + InboundCapable = 0, // I = Inbound capable without relay, may require signal + OutboundOnly = 1, // O = Outbound only, inbound relay required except with reverse connect signal + WebApp = 2, // W = PWA, outbound relay is required in most cases + Invalid = 3, // X = Invalid network class, we don't know how to reach this node } impl Default for NetworkClass { @@ -316,6 +294,13 @@ impl Default for NetworkClass { } } +impl NetworkClass { + // Should an outbound relay be kept available? + pub fn outbound_wants_relay(&self) -> bool { + matches!(self, Self::WebApp) + } +} + #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct NodeStatus { pub will_route: bool, @@ -329,39 +314,39 @@ pub struct NodeStatus { pub struct NodeInfo { pub network_class: NetworkClass, pub outbound_protocols: ProtocolSet, - pub dial_info_list: Vec, + pub dial_info_detail_list: Vec, pub relay_peer_info: Option>, } impl NodeInfo { - pub fn first_filtered_dial_info(&self, filter: F) -> Option + pub fn first_filtered_dial_info_detail(&self, filter: F) -> Option where - F: Fn(&DialInfo) -> bool, + F: Fn(&DialInfoDetail) -> bool, { - for di in &self.dial_info_list { - if filter(di) { - return Some(di.clone()); + for did in &self.dial_info_detail_list { + if filter(&did) { + return Some(did.clone()); } } None } - pub fn all_filtered_dial_info(&self, filter: F) -> Vec + pub fn all_filtered_dial_info_details(&self, filter: F) -> Vec where - F: Fn(&DialInfo) -> bool, + F: Fn(&DialInfoDetail) -> bool, { - let mut dial_info_list = Vec::new(); + let mut dial_info_detail_list = Vec::new(); - for di in &self.dial_info_list { - if filter(di) { - dial_info_list.push(di.clone()); + for did in &self.dial_info_detail_list { + if filter(&did) { + dial_info_detail_list.push(did.clone()); } } - dial_info_list + dial_info_detail_list } pub fn has_any_dial_info(&self) -> bool { - !self.dial_info_list.is_empty() + !self.dial_info_detail_list.is_empty() || !self .relay_peer_info .as_ref() @@ -370,7 +355,55 @@ impl NodeInfo { } pub fn has_direct_dial_info(&self) -> bool { - !self.dial_info_list.is_empty() + !self.dial_info_detail_list.is_empty() + } + + // Is some relay required either for signal or inbound relay or outbound relay? + pub fn requires_relay(&self) -> bool { + match self.network_class { + NetworkClass::InboundCapable => { + for did in &self.dial_info_detail_list { + if did.class.requires_relay() { + return true; + } + } + } + NetworkClass::OutboundOnly => { + return true; + } + NetworkClass::WebApp => { + return true; + } + NetworkClass::Invalid => {} + } + false + } + + // Can this node assist with signalling? Yes but only if it doesn't require signalling, itself. + pub fn can_signal(&self) -> bool { + // Must be inbound capable + if !matches!(self.network_class, NetworkClass::InboundCapable) { + return false; + } + // Do any of our dial info require signalling? if so, we can't offer signalling + for did in &self.dial_info_detail_list { + if did.class.requires_signal() { + return false; + } + } + true + } + + // Can this node relay be an inbound relay? + pub fn can_inbound_relay(&self) -> bool { + // For now this is the same + self.can_signal() + } + + // Is this node capable of validating dial info + pub fn can_validate_dial_info(&self) -> bool { + // For now this is the same + self.can_signal() } } @@ -674,13 +707,6 @@ pub struct DialInfoWSS { pub request: String, } -#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)] -#[serde(tag = "kind")] -pub enum DialInfoClass { - Direct, - Relay, -} - #[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 diff --git a/veilid-core/src/xx/tools.rs b/veilid-core/src/xx/tools.rs index 14165531..d9e696d8 100644 --- a/veilid-core/src/xx/tools.rs +++ b/veilid-core/src/xx/tools.rs @@ -168,3 +168,20 @@ pub fn listen_address_to_socket_addrs(listen_address: &str) -> Result { + fn remove_duplicates(&mut self); +} + +impl Dedup for Vec { + fn remove_duplicates(&mut self) { + let mut already_seen = Vec::new(); + self.retain(|item| match already_seen.contains(item) { + true => false, + _ => { + already_seen.push(item.clone()); + true + } + }) + } +}