dialinfoclass refactor, cleaning up network class detection
This commit is contained in:
		@@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,34 @@ use crate::intf::*;
 | 
			
		||||
use crate::routing_table::*;
 | 
			
		||||
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 {
 | 
			
		||||
    // Ask for a public address check from a particular noderef
 | 
			
		||||
    async fn request_public_address(&self, node_ref: NodeRef) -> Option<SocketAddress> {
 | 
			
		||||
@@ -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(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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())),
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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<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 {
 | 
			
		||||
    network_manager: NetworkManager,
 | 
			
		||||
    node_id: DHTKey,
 | 
			
		||||
@@ -223,28 +203,40 @@ impl RoutingTable {
 | 
			
		||||
 | 
			
		||||
    pub fn all_filtered_dial_info_details(
 | 
			
		||||
        &self,
 | 
			
		||||
        domain: RoutingDomain,
 | 
			
		||||
        domain: Option<RoutingDomain>,
 | 
			
		||||
        filter: &DialInfoFilter,
 | 
			
		||||
    ) -> Vec<DialInfoDetail> {
 | 
			
		||||
        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<DHTKey, Vec<DialInfo>> = BTreeMap::new();
 | 
			
		||||
        let mut bsmap: BTreeMap<DHTKey, Vec<DialInfoDetail>> = 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))?;
 | 
			
		||||
 
 | 
			
		||||
@@ -90,30 +90,42 @@ impl NodeRef {
 | 
			
		||||
                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| {
 | 
			
		||||
            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<F>(&self) -> Vec<DialInfo> {
 | 
			
		||||
    pub fn all_filtered_dial_info_details<F>(
 | 
			
		||||
        &self,
 | 
			
		||||
        routing_domain: Option<RoutingDomain>,
 | 
			
		||||
    ) -> Vec<DialInfoDetail> {
 | 
			
		||||
        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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								veilid-core/src/rpc_processor/coders/dial_info_class.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								veilid-core/src/rpc_processor/coders/dial_info_class.rs
									
									
									
									
									
										Normal 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,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								veilid-core/src/rpc_processor/coders/dial_info_detail.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								veilid-core/src/rpc_processor/coders/dial_info_detail.rs
									
									
									
									
									
										Normal 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 })
 | 
			
		||||
}
 | 
			
		||||
@@ -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::*;
 | 
			
		||||
 
 | 
			
		||||
@@ -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::<DialInfo>::with_capacity(
 | 
			
		||||
        dil_reader
 | 
			
		||||
    let mut dial_info_detail_list = Vec::<DialInfo>::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,
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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::<veilid_capnp::operation::Builder>();
 | 
			
		||||
            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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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<DialInfo>,
 | 
			
		||||
    pub dial_info_detail_list: Vec<DialInfoDetail>,
 | 
			
		||||
    pub relay_peer_info: Option<Box<PeerInfo>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
        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<F>(&self, filter: F) -> Vec<DialInfo>
 | 
			
		||||
    pub fn all_filtered_dial_info_details<F>(&self, filter: F) -> Vec<DialInfoDetail>
 | 
			
		||||
    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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user