dialinfoclass refactor, cleaning up network class detection

This commit is contained in:
John Smith 2022-04-23 22:08:02 -04:00
parent 99dc4e16f9
commit 1156159748
12 changed files with 435 additions and 210 deletions

View File

@ -179,16 +179,24 @@ struct OperationInfoQ {
nodeStatus @0 :NodeStatus; # node status update about the infoq sender nodeStatus @0 :NodeStatus; # node status update about the infoq sender
} }
enum NetworkClass { enum NetworkClass {
server @0; # S = Device with public IP and no UDP firewall inboundCapable @0; # I = Inbound capable without relay, may require signal
mapped @1; # M = Device with portmap behind any NAT outboundOnly @1; # O = Outbound only, inbound relay required except with reverse connect signal
fullConeNAT @2; # F = Device without portmap behind full-cone NAT webApp @2; # W = PWA, outbound relay is required in most cases
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 enum DialInfoClass {
webApp @6; # W = PWA direct @0; # D = Directly reachable with public IP and no firewall, with statically configured port
invalid @7; # I = Invalid 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 { struct NodeStatus {
@ -209,7 +217,7 @@ struct ProtocolSet {
struct NodeInfo { struct NodeInfo {
networkClass @0 :NetworkClass; # network class of this node networkClass @0 :NetworkClass; # network class of this node
outboundProtocols @1 :ProtocolSet; # protocols that can go outbound outboundProtocols @1 :ProtocolSet; # protocols that can go outbound
dialInfoList @2 :List(DialInfo); # inbound dial info for this node dialInfoDetailList @2 :List(DialInfoDetail); # inbound dial info details for this node
relayPeerInfo @3 :PeerInfo; # (optional) relay peer info for this node relayPeerInfo @3 :PeerInfo; # (optional) relay peer info for this node
} }

View File

@ -4,6 +4,34 @@ use crate::intf::*;
use crate::routing_table::*; use crate::routing_table::*;
use crate::*; use crate::*;
#[derive(Debug)]
struct DiscoveryContext {
routing_table: RoutingTable,
external_ipv4: Option<Ipv4Addr>,
external_ipv6: Option<Ipv6Addr>,
network_class: Option<NetworkClass>,
}
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 { impl Network {
// Ask for a public address check from a particular noderef // Ask for a public address check from a particular noderef
async fn request_public_address(&self, node_ref: NodeRef) -> Option<SocketAddress> { async fn request_public_address(&self, node_ref: NodeRef) -> Option<SocketAddress> {
@ -100,7 +128,12 @@ impl Network {
None 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"); log_net!("looking for udpv4 public dial info");
let routing_table = self.routing_table(); 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 our local interface list contains external1 then there is no NAT in place
if intf_addrs.contains(&external1) { if intf_addrs.contains(&external1) {
// No NAT // 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 if self
.validate_dial_info(node_b.clone(), external1_dial_info.clone(), true, false) .validate_dial_info(node_b.clone(), external1_dial_info.clone(), true, false)
.await .await
{ {
// Add public dial info with Server network class // Add public dial info with Direct dialinfo class
routing_table.register_public_dial_info( routing_table.register_dial_info(
RoutingDomain::PublicInternet,
external1_dial_info, external1_dial_info,
DialInfoOrigin::Discovered, DialInfoClass::Direct,
Some(NetworkClass::Server),
); );
}
// 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 // No more retries
break; 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);
}
} else { } else {
// There is -some NAT- // There is -some NAT-
// Attempt a UDP port mapping via all available and enabled mechanisms // 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 // Got a port mapping, let's use it
let external_mapped_dial_info = DialInfo::udp(external_mapped); 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, external_mapped_dial_info,
DialInfoOrigin::Mapped, DialInfoClass::Mapped,
Some(NetworkClass::Mapped),
); );
context.upgrade_network_class(NetworkClass::InboundCapable);
// No more retries // No more retries
break; break;
@ -180,11 +231,12 @@ impl Network {
{ {
// Yes, another machine can use the dial info directly, so Full Cone // Yes, another machine can use the dial info directly, so Full Cone
// Add public dial info with full cone NAT network class // 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, external1_dial_info,
DialInfoOrigin::Discovered, DialInfoClass::FullConeNAT,
Some(NetworkClass::FullConeNAT),
); );
context.upgrade_network_class(NetworkClass::InboundCapable);
// No more retries // No more retries
break; break;
@ -209,7 +261,7 @@ impl Network {
// If we have two different external addresses, then this is a symmetric NAT // If we have two different external addresses, then this is a symmetric NAT
if external2 != external1 { if external2 != external1 {
// Symmetric NAT is outbound only, no public dial info will work // 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 // No more retries
break; break;
@ -230,19 +282,20 @@ impl Network {
.await .await
{ {
// Got a reply from a non-default port, which means we're only address restricted // 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, external1_dial_info,
DialInfoOrigin::Discovered, DialInfoClass::AddressRestrictedNAT,
Some(NetworkClass::AddressRestrictedNAT),
); );
} else { } else {
// Didn't get a reply from a non-default port, which means we are also port restricted // 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, external1_dial_info,
DialInfoOrigin::Discovered, DialInfoClass::PortRestrictedNAT,
Some(NetworkClass::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(()) 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"); 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 // xxx
//Err("unimplemented".to_owned()) //Err("unimplemented".to_owned())
Ok(()) Ok(())
} }
pub async fn update_wsv4_dialinfo(&self) -> Result<(), String> { pub async fn update_udpv6_dialinfo(
log_net!("looking for wsv4 public dial info"); &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 // xxx
//Err("unimplemented".to_owned()) //Err("unimplemented".to_owned())
Ok(()) Ok(())
@ -282,18 +366,25 @@ impl Network {
.clone() .clone()
.unwrap_or_default(); .unwrap_or_default();
let mut context = DiscoveryContext::default();
if protocol_config.inbound.contains(ProtocolType::UDP) { 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) { 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) { 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(()) Ok(())
} }
} }

View File

@ -29,7 +29,9 @@ impl RoutingTable {
// does it have matching public dial info? // does it have matching public dial info?
entry entry
.node_info() .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() .is_some()
}, },
)), )),
@ -55,11 +57,7 @@ impl RoutingTable {
node_info: NodeInfo { node_info: NodeInfo {
network_class: netman.get_network_class().unwrap_or(NetworkClass::Invalid), network_class: netman.get_network_class().unwrap_or(NetworkClass::Invalid),
outbound_protocols: netman.get_protocol_config().unwrap_or_default().outbound, outbound_protocols: netman.get_protocol_config().unwrap_or_default().outbound,
dial_info_list: self dial_info_detail_list: self.dial_info_details(RoutingDomain::PublicInternet),
.dial_info_details(RoutingDomain::PublicInternet)
.iter()
.map(|did| did.dial_info.clone())
.collect(),
relay_peer_info: relay_node.map(|rn| Box::new(rn.peer_info())), relay_peer_info: relay_node.map(|rn| Box::new(rn.peer_info())),
}, },
} }

View File

@ -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)] #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
pub enum RoutingDomain { pub enum RoutingDomain {
PublicInternet, PublicInternet,
@ -40,19 +33,6 @@ pub struct RoutingDomainDetail {
dial_info_details: Vec<DialInfoDetail>, dial_info_details: Vec<DialInfoDetail>,
} }
#[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 { struct RoutingTableInner {
network_manager: NetworkManager, network_manager: NetworkManager,
node_id: DHTKey, node_id: DHTKey,
@ -223,28 +203,40 @@ impl RoutingTable {
pub fn all_filtered_dial_info_details( pub fn all_filtered_dial_info_details(
&self, &self,
domain: RoutingDomain, domain: Option<RoutingDomain>,
filter: &DialInfoFilter, filter: &DialInfoFilter,
) -> Vec<DialInfoDetail> { ) -> Vec<DialInfoDetail> {
let inner = self.inner.lock(); let inner = self.inner.lock();
Self::with_routing_domain(&*inner, domain, |rd| {
let mut ret = Vec::new(); 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 { for did in rd.dial_info_details {
if did.matches_filter(filter) { if did.matches_filter(filter) {
ret.push(did.clone()); ret.push(did.clone());
} }
} }
});
}
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 ret
})
} }
pub fn register_dial_info( pub fn register_dial_info(
&self, &self,
domain: RoutingDomain, domain: RoutingDomain,
dial_info: DialInfo, dial_info: DialInfo,
origin: DialInfoOrigin, class: DialInfoClass,
) { ) {
let timestamp = get_timestamp();
let enable_local_peer_scope = { let enable_local_peer_scope = {
let config = self.network_manager().config(); let config = self.network_manager().config();
let c = config.get(); let c = config.get();
@ -267,8 +259,7 @@ impl RoutingTable {
Self::with_routing_domain_mut(&mut *inner, domain, |rd| { Self::with_routing_domain_mut(&mut *inner, domain, |rd| {
rd.dial_info_details.push(DialInfoDetail { rd.dial_info_details.push(DialInfoDetail {
dial_info: dial_info.clone(), dial_info: dial_info.clone(),
origin, class,
timestamp,
}); });
}); });
@ -285,7 +276,7 @@ impl RoutingTable {
} }
.to_string(), .to_string(),
); );
debug!(" Origin: {:?}", origin); debug!(" Class: {:?}", class);
} }
pub fn clear_dial_info_details(&self, domain: RoutingDomain) { pub fn clear_dial_info_details(&self, domain: RoutingDomain) {
@ -611,7 +602,7 @@ impl RoutingTable {
log_rtab!("--- bootstrap_task"); log_rtab!("--- bootstrap_task");
// Map all bootstrap entries to a single key with multiple dialinfo // Map all bootstrap entries to a single key with multiple dialinfo
let mut bsmap: BTreeMap<DHTKey, Vec<DialInfo>> = BTreeMap::new(); let mut bsmap: BTreeMap<DHTKey, Vec<DialInfoDetail>> = BTreeMap::new();
for b in bootstrap { for b in bootstrap {
let ndis = NodeDialInfo::from_str(b.as_str()) let ndis = NodeDialInfo::from_str(b.as_str())
.map_err(map_to_string) .map_err(map_to_string)
@ -620,7 +611,10 @@ impl RoutingTable {
bsmap bsmap
.entry(node_id) .entry(node_id)
.or_insert_with(Vec::new) .or_insert_with(Vec::new)
.push(ndis.dial_info); .push(DialInfoDetail {
dial_info: ndis.dial_info,
class: DialInfoClass::Direct,
});
} }
log_rtab!(" bootstrap list: {:?}", bsmap); log_rtab!(" bootstrap list: {:?}", bsmap);
@ -634,7 +628,7 @@ impl RoutingTable {
NodeInfo { NodeInfo {
network_class: NetworkClass::Server, // Bootstraps are always full servers 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 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 dial_info_detail_list: v, // Dial info is as specified in the bootstrap list
relay_peer_info: None, // Bootstraps never require a relay themselves relay_peer_info: None, // Bootstraps never require a relay themselves
}, },
) )

View File

@ -90,30 +90,42 @@ impl NodeRef {
nr nr
}) })
} }
pub fn first_filtered_dial_info(&self) -> Option<DialInfo> { pub fn first_filtered_dial_info_detail(
&self,
routing_domain: Option<RoutingDomain>,
) -> Option<DialInfoDetail> {
self.operate(|e| { self.operate(|e| {
if matches!( if (routing_domain == None || routing_domain == Some(RoutingDomain::LocalNetwork))
&& matches!(
self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All),
PeerScope::All | PeerScope::Local PeerScope::All | PeerScope::Local
) { )
e.local_node_info().first_filtered_dial_info(|di| { {
e.local_node_info()
.first_filtered_dial_info(|di| {
if let Some(filter) = self.filter { if let Some(filter) = self.filter {
di.matches_filter(&filter) di.matches_filter(&filter)
} else { } else {
true true
} }
}) })
.map(|di| DialInfoDetail {
class: DialInfoClass::Direct,
dial_info: di,
})
} else { } else {
None None
} }
.or_else(|| { .or_else(|| {
if matches!( if (routing_domain == None || routing_domain == Some(RoutingDomain::PublicInternet))
&& matches!(
self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All),
PeerScope::All | PeerScope::Global PeerScope::All | PeerScope::Global
) { )
e.node_info().first_filtered_dial_info(|di| { {
e.node_info().first_filtered_dial_info_detail(|did| {
if let Some(filter) = self.filter { if let Some(filter) = self.filter {
di.matches_filter(&filter) did.matches_filter(&filter)
} else { } else {
true true
} }
@ -125,34 +137,47 @@ impl NodeRef {
}) })
} }
pub fn all_filtered_dial_info<F>(&self) -> Vec<DialInfo> { pub fn all_filtered_dial_info_details<F>(
&self,
routing_domain: Option<RoutingDomain>,
) -> Vec<DialInfoDetail> {
let mut out = Vec::new(); let mut out = Vec::new();
self.operate(|e| { self.operate(|e| {
if matches!( if (routing_domain == None || routing_domain == Some(RoutingDomain::LocalNetwork))
&& matches!(
self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All),
PeerScope::All | PeerScope::Global PeerScope::All | PeerScope::Local
) { )
out.append(&mut e.node_info().all_filtered_dial_info(|di| { {
for di in e.local_node_info().all_filtered_dial_info(|di| {
if let Some(filter) = self.filter { if let Some(filter) = self.filter {
di.matches_filter(&filter) di.matches_filter(&filter)
} else { } else {
true true
} }
})) }) {
out.push(DialInfoDetail {
class: DialInfoClass::Direct,
dial_info: di,
});
} }
if matches!( }
if (routing_domain == None || routing_domain == Some(RoutingDomain::PublicInternet))
&& matches!(
self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All), self.filter.map(|f| f.peer_scope).unwrap_or(PeerScope::All),
PeerScope::All | PeerScope::Local PeerScope::All | PeerScope::Global
) { )
out.append(&mut e.local_node_info().all_filtered_dial_info(|di| { {
out.append(&mut e.node_info().all_filtered_dial_info_details(|did| {
if let Some(filter) = self.filter { if let Some(filter) = self.filter {
di.matches_filter(&filter) did.matches_filter(&filter)
} else { } else {
true true
} }
})) }))
} }
}); });
out.remove_duplicates();
out out
} }

View File

@ -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,
}
}

View File

@ -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<DialInfoDetail, RPCError> {
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 })
}

View File

@ -1,5 +1,7 @@
mod address; mod address;
mod dial_info; mod dial_info;
mod dial_info_class;
mod dial_info_detail;
mod network_class; mod network_class;
mod node_dial_info; mod node_dial_info;
mod node_info; mod node_info;
@ -15,6 +17,8 @@ mod socket_address;
pub use address::*; pub use address::*;
pub use dial_info::*; pub use dial_info::*;
pub use dial_info_class::*;
pub use dial_info_detail::*;
pub use network_class::*; pub use network_class::*;
pub use node_dial_info::*; pub use node_dial_info::*;
pub use node_info::*; pub use node_info::*;

View File

@ -10,17 +10,19 @@ pub fn encode_node_info(
let mut ps_builder = builder.reborrow().init_outbound_protocols(); let mut ps_builder = builder.reborrow().init_outbound_protocols();
encode_protocol_set(&node_info.outbound_protocols, &mut ps_builder)?; 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 node_info
.dial_info_list .dial_info_detail_list
.len() .len()
.try_into() .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() { for idx in 0..node_info.dial_info_detail_list.len() {
let mut di_builder = dil_builder.reborrow().get(idx as u32); let mut did_builder = didl_builder.reborrow().get(idx as u32);
encode_dial_info(&node_info.dial_info_list[idx], &mut di_builder)?; encode_dial_info_detail(&node_info.dial_info_detail_list[idx], &mut did_builder)?;
} }
if let Some(rpi) = &node_info.relay_peer_info { if let Some(rpi) = &node_info.relay_peer_info {
@ -49,18 +51,18 @@ pub fn decode_node_info(
.map_err(map_error_capnp_error!())?, .map_err(map_error_capnp_error!())?,
)?; )?;
let dil_reader = reader let didl_reader = reader
.reborrow() .reborrow()
.get_dial_info_list() .get_dial_info_detail_list()
.map_err(map_error_capnp_error!())?; .map_err(map_error_capnp_error!())?;
let mut dial_info_list = Vec::<DialInfo>::with_capacity( let mut dial_info_detail_list = Vec::<DialInfo>::with_capacity(
dil_reader didl_reader
.len() .len()
.try_into() .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() { 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 { let relay_peer_info = if allow_relay_peer_info {
@ -82,7 +84,7 @@ pub fn decode_node_info(
Ok(NodeInfo { Ok(NodeInfo {
network_class, network_class,
outbound_protocols, outbound_protocols,
dial_info_list, dial_info_detail_list,
relay_peer_info, relay_peer_info,
}) })
} }

View File

@ -1349,12 +1349,16 @@ impl RPCProcessor {
} }
// Gets a 'RespondTo::Sender' that contains either our dial info, // Gets a 'RespondTo::Sender' that contains either our dial info,
// or None if the peer has seen our dial info before // or None if the peer has seen our dial info before or our node info is not yet valid
pub fn get_respond_to_sender(&self, peer: NodeRef) -> RespondTo { // because of an unknown network class
if peer.has_seen_our_node_info() { 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) RespondTo::Sender(None)
} else { } 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::<veilid_capnp::operation::Builder>(); let mut question = info_q_msg.init_root::<veilid_capnp::operation::Builder>();
question.set_op_id(self.get_next_op_id()); question.set_op_id(self.get_next_op_id());
let mut respond_to = question.reborrow().init_respond_to(); 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)?; .encode(&mut respond_to)?;
let detail = question.reborrow().init_detail(); let detail = question.reborrow().init_detail();
let mut iqb = detail.init_info_q(); let mut iqb = detail.init_info_q();

View File

@ -236,78 +236,56 @@ pub struct SenderInfo {
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub enum NetworkClass { pub enum DialInfoClass {
Server = 0, // S = Device with public IP and no UDP firewall Direct = 0, // D = Directly reachable with public IP and no firewall, with statically configured port
Mapped = 1, // M = Device with portmap behind any NAT Mapped = 1, // M = Directly reachable with via portmap behind any NAT or firewalled with dynamically negotiated port
FullConeNAT = 2, // F = Device without portmap behind full-cone NAT FullConeNAT = 2, // F = Directly reachable device without portmap behind full-cone NAT
AddressRestrictedNAT = 3, // A = Device without portmap behind address-only restricted NAT Blocked = 3, // B = Inbound blocked at firewall but may hole punch with public address
PortRestrictedNAT = 4, // P = Device without portmap behind address-and-port restricted NAT AddressRestrictedNAT = 4, // A = Device without portmap behind address-only restricted NAT
OutboundOnly = 5, // O = Outbound only PortRestrictedNAT = 5, // P = Device without portmap behind address-and-port restricted NAT
WebApp = 6, // W = PWA
Invalid = 7, // I = Invalid network class, unreachable or can not send packets
} }
impl NetworkClass { impl DialInfoClass {
// 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)
}
// Is a signal required to do an inbound hole-punch? // Is a signal required to do an inbound hole-punch?
pub fn inbound_requires_signal(&self) -> bool { pub fn 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 {
matches!( matches!(
self, self,
Self::AddressRestrictedNAT Self::Blocked | Self::AddressRestrictedNAT | Self::PortRestrictedNAT
| Self::PortRestrictedNAT
| Self::OutboundOnly
| Self::WebApp
) )
} }
// Must keepalive be used to preserve the public dialinfo in use? // Does a relay node need to be allocated for this dial info?
// Keepalive can be to either a // For full cone NAT, the relay itself may not be used but the keepalive sent to it
pub fn dialinfo_requires_keepalive(&self) -> bool { // is required to keep the NAT mapping valid in the router state table
pub fn requires_relay(&self) -> bool {
matches!( matches!(
self, self,
Self::FullConeNAT Self::FullConeNAT
| Self::Blocked
| Self::AddressRestrictedNAT | Self::AddressRestrictedNAT
| Self::PortRestrictedNAT | Self::PortRestrictedNAT
| Self::OutboundOnly
| Self::WebApp
) )
} }
}
// Can this node assist with signalling? Yes but only if it doesn't require signalling, itself. #[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)]
pub fn can_signal(&self) -> bool { pub struct DialInfoDetail {
self.inbound_capable() && !self.inbound_requires_signal() pub dial_info: DialInfo,
} pub class: DialInfoClass,
}
// Can this node relay be an inbound relay? impl MatchesDialInfoFilter for DialInfoDetail {
pub fn can_inbound_relay(&self) -> bool { fn matches_filter(&self, filter: &DialInfoFilter) -> bool {
matches!(self, Self::Server | Self::Mapped | Self::FullConeNAT) self.dial_info.matches_filter(filter)
} }
}
// Is this node capable of validating dial info #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub fn can_validate_dial_info(&self) -> bool { pub enum NetworkClass {
matches!(self, Self::Server | Self::Mapped | Self::FullConeNAT) 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 { 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)] #[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct NodeStatus { pub struct NodeStatus {
pub will_route: bool, pub will_route: bool,
@ -329,39 +314,39 @@ pub struct NodeStatus {
pub struct NodeInfo { pub struct NodeInfo {
pub network_class: NetworkClass, pub network_class: NetworkClass,
pub outbound_protocols: ProtocolSet, pub outbound_protocols: ProtocolSet,
pub dial_info_list: Vec<DialInfo>, pub dial_info_detail_list: Vec<DialInfoDetail>,
pub relay_peer_info: Option<Box<PeerInfo>>, pub relay_peer_info: Option<Box<PeerInfo>>,
} }
impl NodeInfo { impl NodeInfo {
pub fn first_filtered_dial_info<F>(&self, filter: F) -> Option<DialInfo> pub fn first_filtered_dial_info_detail<F>(&self, filter: F) -> Option<DialInfoDetail>
where where
F: Fn(&DialInfo) -> bool, F: Fn(&DialInfoDetail) -> bool,
{ {
for di in &self.dial_info_list { for did in &self.dial_info_detail_list {
if filter(di) { if filter(&did) {
return Some(di.clone()); return Some(did.clone());
} }
} }
None None
} }
pub fn all_filtered_dial_info<F>(&self, filter: F) -> Vec<DialInfo> pub fn all_filtered_dial_info_details<F>(&self, filter: F) -> Vec<DialInfoDetail>
where 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 { for did in &self.dial_info_detail_list {
if filter(di) { if filter(&did) {
dial_info_list.push(di.clone()); dial_info_detail_list.push(did.clone());
} }
} }
dial_info_list dial_info_detail_list
} }
pub fn has_any_dial_info(&self) -> bool { pub fn has_any_dial_info(&self) -> bool {
!self.dial_info_list.is_empty() !self.dial_info_detail_list.is_empty()
|| !self || !self
.relay_peer_info .relay_peer_info
.as_ref() .as_ref()
@ -370,7 +355,55 @@ impl NodeInfo {
} }
pub fn has_direct_dial_info(&self) -> bool { 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, 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)] #[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize)]
#[serde(tag = "kind")] #[serde(tag = "kind")]
// The derived ordering here is the order of preference, lower is preferred for connections // The derived ordering here is the order of preference, lower is preferred for connections

View File

@ -168,3 +168,20 @@ pub fn listen_address_to_socket_addrs(listen_address: &str) -> Result<Vec<Socket
} }
}) })
} }
pub trait Dedup<T: PartialEq + Clone> {
fn remove_duplicates(&mut self);
}
impl<T: PartialEq + Clone> Dedup<T> for Vec<T> {
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
}
})
}
}