public address detection refactoring
This commit is contained in:
		@@ -1,3 +1,4 @@
 | 
			
		||||
mod discovery_context;
 | 
			
		||||
mod igd_manager;
 | 
			
		||||
mod network_class_discovery;
 | 
			
		||||
mod network_tcp;
 | 
			
		||||
@@ -8,6 +9,7 @@ mod start_protocols;
 | 
			
		||||
use super::*;
 | 
			
		||||
use crate::routing_table::*;
 | 
			
		||||
use connection_manager::*;
 | 
			
		||||
use discovery_context::*;
 | 
			
		||||
use network_interfaces::*;
 | 
			
		||||
use network_tcp::*;
 | 
			
		||||
use protocol::tcp::RawTcpProtocolHandler;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,659 +1,9 @@
 | 
			
		||||
/// Detect NetworkClass and DialInfo for the PublicInternet RoutingDomain
 | 
			
		||||
/// Also performs UPNP/IGD mapping if enabled and possible
 | 
			
		||||
/// Detect NetworkClass and DialInfo for the DialInfo for the PublicInternet RoutingDomain
 | 
			
		||||
use super::*;
 | 
			
		||||
use futures_util::stream::FuturesUnordered;
 | 
			
		||||
use futures_util::FutureExt;
 | 
			
		||||
use stop_token::future::FutureExt as StopTokenFutureExt;
 | 
			
		||||
 | 
			
		||||
const PORT_MAP_VALIDATE_TRY_COUNT: usize = 3;
 | 
			
		||||
const PORT_MAP_VALIDATE_DELAY_MS: u32 = 500;
 | 
			
		||||
const PORT_MAP_TRY_COUNT: usize = 3;
 | 
			
		||||
 | 
			
		||||
struct DetectedPublicDialInfo {
 | 
			
		||||
    dial_info: DialInfo,
 | 
			
		||||
    class: DialInfoClass,
 | 
			
		||||
}
 | 
			
		||||
struct DiscoveryContextInner {
 | 
			
		||||
    // per-protocol
 | 
			
		||||
    intf_addrs: Option<Vec<SocketAddress>>,
 | 
			
		||||
    protocol_type: Option<ProtocolType>,
 | 
			
		||||
    address_type: Option<AddressType>,
 | 
			
		||||
    // first node contacted
 | 
			
		||||
    external_1_dial_info: Option<DialInfo>,
 | 
			
		||||
    external_1_address: Option<SocketAddress>,
 | 
			
		||||
    node_1: Option<NodeRef>,
 | 
			
		||||
    // detected public dialinfo
 | 
			
		||||
    detected_network_class: Option<NetworkClass>,
 | 
			
		||||
    detected_public_dial_info: Option<DetectedPublicDialInfo>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub struct DiscoveryContext {
 | 
			
		||||
    routing_table: RoutingTable,
 | 
			
		||||
    net: Network,
 | 
			
		||||
    inner: Arc<Mutex<DiscoveryContextInner>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
struct DetectedDialInfo {
 | 
			
		||||
    dial_info: DialInfo,
 | 
			
		||||
    dial_info_class: DialInfoClass,
 | 
			
		||||
    network_class: NetworkClass,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DiscoveryContext {
 | 
			
		||||
    pub fn new(routing_table: RoutingTable, net: Network) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            routing_table,
 | 
			
		||||
            net,
 | 
			
		||||
            inner: Arc::new(Mutex::new(DiscoveryContextInner {
 | 
			
		||||
                // per-protocol
 | 
			
		||||
                intf_addrs: None,
 | 
			
		||||
                protocol_type: None,
 | 
			
		||||
                address_type: None,
 | 
			
		||||
                external_1_dial_info: None,
 | 
			
		||||
                external_1_address: None,
 | 
			
		||||
                node_1: None,
 | 
			
		||||
                detected_network_class: None,
 | 
			
		||||
                detected_public_dial_info: None,
 | 
			
		||||
            })),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///////
 | 
			
		||||
    // Utilities
 | 
			
		||||
 | 
			
		||||
    // Pick the best network class we have seen so far
 | 
			
		||||
    pub fn set_detected_network_class(&self, network_class: NetworkClass) {
 | 
			
		||||
        let mut inner = self.inner.lock();
 | 
			
		||||
        debug!(target: "net",
 | 
			
		||||
            protocol_type=?inner.protocol_type,
 | 
			
		||||
            address_type=?inner.address_type,
 | 
			
		||||
            ?network_class,
 | 
			
		||||
            "set_detected_network_class"
 | 
			
		||||
        );
 | 
			
		||||
        inner.detected_network_class = Some(network_class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn set_detected_public_dial_info(&self, dial_info: DialInfo, class: DialInfoClass) {
 | 
			
		||||
        let mut inner = self.inner.lock();
 | 
			
		||||
        debug!(target: "net",
 | 
			
		||||
            protocol_type=?inner.protocol_type,
 | 
			
		||||
            address_type=?inner.address_type,
 | 
			
		||||
            ?dial_info,
 | 
			
		||||
            ?class,
 | 
			
		||||
            "set_detected_public_dial_info"
 | 
			
		||||
        );
 | 
			
		||||
        inner.detected_public_dial_info = Some(DetectedPublicDialInfo { dial_info, class });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Ask for a public address check from a particular noderef
 | 
			
		||||
    // This is done over the normal port using RPC
 | 
			
		||||
    #[instrument(level = "trace", skip(self), ret)]
 | 
			
		||||
    async fn request_public_address(&self, node_ref: NodeRef) -> Option<SocketAddress> {
 | 
			
		||||
        let rpc = self.routing_table.rpc_processor();
 | 
			
		||||
 | 
			
		||||
        let res = network_result_value_or_log!(match rpc.rpc_call_status(Destination::direct(node_ref.clone())).await {
 | 
			
		||||
                Ok(v) => v,
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    log_net!(error
 | 
			
		||||
                        "failed to get status answer from {:?}: {}",
 | 
			
		||||
                        node_ref, e
 | 
			
		||||
                    );
 | 
			
		||||
                    return None;
 | 
			
		||||
                }
 | 
			
		||||
            } => [ format!(": node_ref={}", node_ref) ] {
 | 
			
		||||
                return None;
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        log_net!(
 | 
			
		||||
            "request_public_address {:?}: Value({:?})",
 | 
			
		||||
            node_ref,
 | 
			
		||||
            res.answer
 | 
			
		||||
        );
 | 
			
		||||
        res.answer.map(|si| si.socket_address)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // find fast peers with a particular address type, and ask them to tell us what our external address is
 | 
			
		||||
    // This is done over the normal port using RPC
 | 
			
		||||
    #[instrument(level = "trace", skip(self), ret)]
 | 
			
		||||
    async fn discover_external_address(
 | 
			
		||||
        &self,
 | 
			
		||||
        protocol_type: ProtocolType,
 | 
			
		||||
        address_type: AddressType,
 | 
			
		||||
        ignore_node_ids: Option<TypedKeyGroup>,
 | 
			
		||||
    ) -> Option<(SocketAddress, NodeRef)> {
 | 
			
		||||
        let node_count = {
 | 
			
		||||
            let config = self.routing_table.network_manager().config();
 | 
			
		||||
            let c = config.get();
 | 
			
		||||
            c.network.dht.max_find_node_count as usize
 | 
			
		||||
        };
 | 
			
		||||
        let routing_domain = RoutingDomain::PublicInternet;
 | 
			
		||||
 | 
			
		||||
        // Build an filter that matches our protocol and address type
 | 
			
		||||
        // and excludes relayed nodes so we can get an accurate external address
 | 
			
		||||
        let dial_info_filter = DialInfoFilter::all()
 | 
			
		||||
            .with_protocol_type(protocol_type)
 | 
			
		||||
            .with_address_type(address_type);
 | 
			
		||||
        let inbound_dial_info_entry_filter = RoutingTable::make_inbound_dial_info_entry_filter(
 | 
			
		||||
            routing_domain,
 | 
			
		||||
            dial_info_filter.clone(),
 | 
			
		||||
        );
 | 
			
		||||
        let disallow_relays_filter = Box::new(
 | 
			
		||||
            move |rti: &RoutingTableInner, v: Option<Arc<BucketEntry>>| {
 | 
			
		||||
                let v = v.unwrap();
 | 
			
		||||
                v.with(rti, |_rti, e| {
 | 
			
		||||
                    if let Some(n) = e.signed_node_info(routing_domain) {
 | 
			
		||||
                        n.relay_ids().is_empty()
 | 
			
		||||
                    } else {
 | 
			
		||||
                        false
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            },
 | 
			
		||||
        ) as RoutingTableEntryFilter;
 | 
			
		||||
        let will_validate_dial_info_filter = Box::new(
 | 
			
		||||
            move |rti: &RoutingTableInner, v: Option<Arc<BucketEntry>>| {
 | 
			
		||||
                let entry = v.unwrap();
 | 
			
		||||
                entry.with(rti, move |_rti, e| {
 | 
			
		||||
                    e.node_info(routing_domain)
 | 
			
		||||
                        .map(|ni| {
 | 
			
		||||
                            ni.has_capability(CAP_VALIDATE_DIAL_INFO)
 | 
			
		||||
                                && ni.is_fully_direct_inbound()
 | 
			
		||||
                        })
 | 
			
		||||
                        .unwrap_or(false)
 | 
			
		||||
                })
 | 
			
		||||
            },
 | 
			
		||||
        ) as RoutingTableEntryFilter;
 | 
			
		||||
 | 
			
		||||
        let mut filters = VecDeque::from([
 | 
			
		||||
            inbound_dial_info_entry_filter,
 | 
			
		||||
            disallow_relays_filter,
 | 
			
		||||
            will_validate_dial_info_filter,
 | 
			
		||||
        ]);
 | 
			
		||||
        if let Some(ignore_node_ids) = ignore_node_ids {
 | 
			
		||||
            let ignore_nodes_filter = Box::new(
 | 
			
		||||
                move |rti: &RoutingTableInner, v: Option<Arc<BucketEntry>>| {
 | 
			
		||||
                    let v = v.unwrap();
 | 
			
		||||
                    v.with(rti, |_rti, e| !e.node_ids().contains_any(&ignore_node_ids))
 | 
			
		||||
                },
 | 
			
		||||
            ) as RoutingTableEntryFilter;
 | 
			
		||||
            filters.push_back(ignore_nodes_filter);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Find public nodes matching this filter
 | 
			
		||||
        let peers = self
 | 
			
		||||
            .routing_table
 | 
			
		||||
            .find_fast_public_nodes_filtered(node_count, filters);
 | 
			
		||||
        if peers.is_empty() {
 | 
			
		||||
            log_net!(debug
 | 
			
		||||
                "no external address detection peers of type {:?}:{:?}",
 | 
			
		||||
                protocol_type,
 | 
			
		||||
                address_type
 | 
			
		||||
            );
 | 
			
		||||
            return None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // For each peer, ask them for our public address, filtering on desired dial info
 | 
			
		||||
        for mut peer in peers {
 | 
			
		||||
            peer.set_filter(Some(
 | 
			
		||||
                NodeRefFilter::new()
 | 
			
		||||
                    .with_routing_domain(routing_domain)
 | 
			
		||||
                    .with_dial_info_filter(dial_info_filter.clone()),
 | 
			
		||||
            ));
 | 
			
		||||
            if let Some(sa) = self.request_public_address(peer.clone()).await {
 | 
			
		||||
                return Some((sa, peer));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        log_net!(debug "no peers responded with an external address");
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // This pulls the already-detected local interface dial info from the routing table
 | 
			
		||||
    #[instrument(level = "trace", skip(self), ret)]
 | 
			
		||||
    fn get_local_addresses(
 | 
			
		||||
        &self,
 | 
			
		||||
        protocol_type: ProtocolType,
 | 
			
		||||
        address_type: AddressType,
 | 
			
		||||
    ) -> Vec<SocketAddress> {
 | 
			
		||||
        let filter = DialInfoFilter::all()
 | 
			
		||||
            .with_protocol_type(protocol_type)
 | 
			
		||||
            .with_address_type(address_type);
 | 
			
		||||
        self.routing_table
 | 
			
		||||
            .dial_info_details(RoutingDomain::LocalNetwork)
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter_map(|did| {
 | 
			
		||||
                if did.dial_info.matches_filter(&filter) {
 | 
			
		||||
                    Some(did.dial_info.socket_address())
 | 
			
		||||
                } else {
 | 
			
		||||
                    None
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[instrument(level = "trace", skip(self), ret)]
 | 
			
		||||
    async fn validate_dial_info(
 | 
			
		||||
        &self,
 | 
			
		||||
        node_ref: NodeRef,
 | 
			
		||||
        dial_info: DialInfo,
 | 
			
		||||
        redirect: bool,
 | 
			
		||||
    ) -> bool {
 | 
			
		||||
        let rpc = self.routing_table.rpc_processor();
 | 
			
		||||
 | 
			
		||||
        // asking for node validation doesn't have to use the dial info filter of the dial info we are validating
 | 
			
		||||
        let mut node_ref = node_ref.clone();
 | 
			
		||||
        node_ref.set_filter(None);
 | 
			
		||||
 | 
			
		||||
        // ask the node to send us a dial info validation receipt
 | 
			
		||||
        let out = rpc
 | 
			
		||||
            .rpc_call_validate_dial_info(node_ref.clone(), dial_info, redirect)
 | 
			
		||||
            .await
 | 
			
		||||
            .map_err(logthru_net!(
 | 
			
		||||
                "failed to send validate_dial_info to {:?}",
 | 
			
		||||
                node_ref
 | 
			
		||||
            ))
 | 
			
		||||
            .unwrap_or(false);
 | 
			
		||||
        out
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[instrument(level = "trace", skip(self), ret)]
 | 
			
		||||
    async fn try_upnp_port_mapping(&self) -> Option<DialInfo> {
 | 
			
		||||
        let (pt, llpt, at, external_address_1, node_1, local_port) = {
 | 
			
		||||
            let inner = self.inner.lock();
 | 
			
		||||
            let pt = inner.protocol_type.unwrap();
 | 
			
		||||
            let llpt = pt.low_level_protocol_type();
 | 
			
		||||
            let at = inner.address_type.unwrap();
 | 
			
		||||
            let external_address_1 = inner.external_1_address.unwrap();
 | 
			
		||||
            let node_1 = inner.node_1.as_ref().unwrap().clone();
 | 
			
		||||
            let local_port = self.net.get_local_port(pt).unwrap();
 | 
			
		||||
            (pt, llpt, at, external_address_1, node_1, local_port)
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let mut tries = 0;
 | 
			
		||||
        loop {
 | 
			
		||||
            tries += 1;
 | 
			
		||||
 | 
			
		||||
            // Attempt a port mapping. If this doesn't succeed, it's not going to
 | 
			
		||||
            let Some(mapped_external_address) = self
 | 
			
		||||
                .net
 | 
			
		||||
                .unlocked_inner
 | 
			
		||||
                .igd_manager
 | 
			
		||||
                .map_any_port(llpt, at, local_port, Some(external_address_1.to_ip_addr()))
 | 
			
		||||
                .await else
 | 
			
		||||
            {
 | 
			
		||||
                return None;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // Make dial info from the port mapping
 | 
			
		||||
            let external_mapped_dial_info =
 | 
			
		||||
                self.make_dial_info(SocketAddress::from_socket_addr(mapped_external_address), pt);
 | 
			
		||||
 | 
			
		||||
            // Attempt to validate the port mapping
 | 
			
		||||
            let mut validate_tries = 0;
 | 
			
		||||
            loop {
 | 
			
		||||
                validate_tries += 1;
 | 
			
		||||
 | 
			
		||||
                // Ensure people can reach us. If we're firewalled off, this is useless
 | 
			
		||||
                if self
 | 
			
		||||
                    .validate_dial_info(node_1.clone(), external_mapped_dial_info.clone(), false)
 | 
			
		||||
                    .await
 | 
			
		||||
                {
 | 
			
		||||
                    return Some(external_mapped_dial_info);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if validate_tries == PORT_MAP_VALIDATE_TRY_COUNT {
 | 
			
		||||
                    log_net!(debug "UPNP port mapping succeeded but port {}/{} is still unreachable.\nretrying\n",
 | 
			
		||||
                    local_port, match llpt {
 | 
			
		||||
                        LowLevelProtocolType::UDP => "udp",
 | 
			
		||||
                        LowLevelProtocolType::TCP => "tcp",
 | 
			
		||||
                    });
 | 
			
		||||
                    sleep(PORT_MAP_VALIDATE_DELAY_MS).await
 | 
			
		||||
                } else {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Release the mapping if we're still unreachable
 | 
			
		||||
            let _ = self
 | 
			
		||||
                .net
 | 
			
		||||
                .unlocked_inner
 | 
			
		||||
                .igd_manager
 | 
			
		||||
                .unmap_port(llpt, at, external_address_1.port())
 | 
			
		||||
                .await;
 | 
			
		||||
 | 
			
		||||
            if tries == PORT_MAP_TRY_COUNT {
 | 
			
		||||
                warn!("UPNP port mapping succeeded but port {}/{} is still unreachable.\nYou may need to add a local firewall allowed port on this machine.\n",
 | 
			
		||||
                    local_port, match llpt {
 | 
			
		||||
                        LowLevelProtocolType::UDP => "udp",
 | 
			
		||||
                        LowLevelProtocolType::TCP => "tcp",
 | 
			
		||||
                    }
 | 
			
		||||
                );
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[instrument(level = "trace", skip(self), ret)]
 | 
			
		||||
    async fn try_port_mapping(&self) -> Option<DialInfo> {
 | 
			
		||||
        let enable_upnp = {
 | 
			
		||||
            let c = self.net.config.get();
 | 
			
		||||
            c.network.upnp
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if enable_upnp {
 | 
			
		||||
            return self.try_upnp_port_mapping().await;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn make_dial_info(&self, addr: SocketAddress, protocol_type: ProtocolType) -> DialInfo {
 | 
			
		||||
        match protocol_type {
 | 
			
		||||
            ProtocolType::UDP => DialInfo::udp(addr),
 | 
			
		||||
            ProtocolType::TCP => DialInfo::tcp(addr),
 | 
			
		||||
            ProtocolType::WS => {
 | 
			
		||||
                let c = self.net.config.get();
 | 
			
		||||
                DialInfo::try_ws(
 | 
			
		||||
                    addr,
 | 
			
		||||
                    format!("ws://{}/{}", addr, c.network.protocol.ws.path),
 | 
			
		||||
                )
 | 
			
		||||
                .unwrap()
 | 
			
		||||
            }
 | 
			
		||||
            ProtocolType::WSS => panic!("none of the discovery functions are used for wss"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ///////
 | 
			
		||||
    // Per-protocol discovery routines
 | 
			
		||||
 | 
			
		||||
    #[instrument(level = "trace", skip(self))]
 | 
			
		||||
    pub fn protocol_begin(&self, protocol_type: ProtocolType, address_type: AddressType) {
 | 
			
		||||
        // Get our interface addresses
 | 
			
		||||
        let intf_addrs = self.get_local_addresses(protocol_type, address_type);
 | 
			
		||||
 | 
			
		||||
        let mut inner = self.inner.lock();
 | 
			
		||||
        inner.intf_addrs = Some(intf_addrs);
 | 
			
		||||
        inner.protocol_type = Some(protocol_type);
 | 
			
		||||
        inner.address_type = Some(address_type);
 | 
			
		||||
        inner.external_1_dial_info = None;
 | 
			
		||||
        inner.external_1_address = None;
 | 
			
		||||
        inner.node_1 = None;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get our first node's view of our external IP address via normal RPC
 | 
			
		||||
    #[instrument(level = "trace", skip(self), ret)]
 | 
			
		||||
    pub async fn protocol_get_external_address_1(&self) -> bool {
 | 
			
		||||
        let (protocol_type, address_type) = {
 | 
			
		||||
            let inner = self.inner.lock();
 | 
			
		||||
            (inner.protocol_type.unwrap(), inner.address_type.unwrap())
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Get our external address from some fast node, call it node 1
 | 
			
		||||
        let (external_1, node_1) = match self
 | 
			
		||||
            .discover_external_address(protocol_type, address_type, None)
 | 
			
		||||
            .await
 | 
			
		||||
        {
 | 
			
		||||
            None => {
 | 
			
		||||
                // If we can't get an external address, exit but don't throw an error so we can try again later
 | 
			
		||||
                log_net!(debug "couldn't get external address 1 for {:?} {:?}", protocol_type, address_type);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            Some(v) => v,
 | 
			
		||||
        };
 | 
			
		||||
        let external_1_dial_info = self.make_dial_info(external_1, protocol_type);
 | 
			
		||||
 | 
			
		||||
        let mut inner = self.inner.lock();
 | 
			
		||||
        inner.external_1_dial_info = Some(external_1_dial_info);
 | 
			
		||||
        inner.external_1_address = Some(external_1);
 | 
			
		||||
        inner.node_1 = Some(node_1);
 | 
			
		||||
 | 
			
		||||
        log_net!(debug
 | 
			
		||||
            "external_1_dial_info: {:?}\nexternal_1_address: {:?}\nnode_1: {:?}",
 | 
			
		||||
            inner.external_1_dial_info, inner.external_1_address, inner.node_1
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If we know we are not behind NAT, check our firewall status
 | 
			
		||||
    #[instrument(level = "trace", skip(self), err)]
 | 
			
		||||
    pub async fn protocol_process_no_nat(&self) -> EyreResult<()> {
 | 
			
		||||
        // Do these detections in parallel, but with ordering preference
 | 
			
		||||
        let mut ord = FuturesOrdered::new();
 | 
			
		||||
 | 
			
		||||
        // UPNP Automatic Mapping
 | 
			
		||||
        ///////////
 | 
			
		||||
        let this = self.clone();
 | 
			
		||||
        let do_mapped_fut: SendPinBoxFuture<Option<DetectedDialInfo>> = Box::pin(async move {
 | 
			
		||||
            // Attempt a port mapping via all available and enabled mechanisms
 | 
			
		||||
            // Try this before the direct mapping in the event that we are restarting
 | 
			
		||||
            // and may not have recorded a mapping created the last time
 | 
			
		||||
            if let Some(external_mapped_dial_info) = this.try_port_mapping().await {
 | 
			
		||||
                // Got a port mapping, let's use it
 | 
			
		||||
                return Some(DetectedDialInfo {
 | 
			
		||||
                    dial_info: external_mapped_dial_info.clone(),
 | 
			
		||||
                    dial_info_class: DialInfoClass::Mapped,
 | 
			
		||||
                    network_class: NetworkClass::InboundCapable,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            None
 | 
			
		||||
        });
 | 
			
		||||
        ord.push_back(do_mapped_fut);
 | 
			
		||||
 | 
			
		||||
        let this = self.clone();
 | 
			
		||||
        let do_direct_fut: SendPinBoxFuture<Option<DetectedDialInfo>> = Box::pin(async move {
 | 
			
		||||
            let (node_1, external_1_dial_info) = {
 | 
			
		||||
                let inner = this.inner.lock();
 | 
			
		||||
                (
 | 
			
		||||
                    inner.node_1.as_ref().unwrap().clone(),
 | 
			
		||||
                    inner.external_1_dial_info.as_ref().unwrap().clone(),
 | 
			
		||||
                )
 | 
			
		||||
            };
 | 
			
		||||
            // Do a validate_dial_info on the external address from a redirected node
 | 
			
		||||
            if this
 | 
			
		||||
                .validate_dial_info(node_1.clone(), external_1_dial_info.clone(), true)
 | 
			
		||||
                .await
 | 
			
		||||
            {
 | 
			
		||||
                // Add public dial info with Direct dialinfo class
 | 
			
		||||
                Some(DetectedDialInfo {
 | 
			
		||||
                    dial_info: external_1_dial_info.clone(),
 | 
			
		||||
                    dial_info_class: DialInfoClass::Direct,
 | 
			
		||||
                    network_class: NetworkClass::InboundCapable,
 | 
			
		||||
                })
 | 
			
		||||
            } else {
 | 
			
		||||
                // Add public dial info with Blocked dialinfo class
 | 
			
		||||
                Some(DetectedDialInfo {
 | 
			
		||||
                    dial_info: external_1_dial_info.clone(),
 | 
			
		||||
                    dial_info_class: DialInfoClass::Blocked,
 | 
			
		||||
                    network_class: NetworkClass::InboundCapable,
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        ord.push_back(do_direct_fut);
 | 
			
		||||
 | 
			
		||||
        while let Some(res) = ord.next().await {
 | 
			
		||||
            if let Some(ddi) = res {
 | 
			
		||||
                self.set_detected_public_dial_info(ddi.dial_info, ddi.dial_info_class);
 | 
			
		||||
                self.set_detected_network_class(ddi.network_class);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If we know we are behind NAT check what kind
 | 
			
		||||
    #[instrument(level = "trace", skip(self), ret, err)]
 | 
			
		||||
    pub async fn protocol_process_nat(&self) -> EyreResult<bool> {
 | 
			
		||||
        // Get the external dial info for our use here
 | 
			
		||||
        let (node_1, external_1_dial_info, protocol_type) = {
 | 
			
		||||
            let inner = self.inner.lock();
 | 
			
		||||
            (
 | 
			
		||||
                inner.node_1.as_ref().unwrap().clone(),
 | 
			
		||||
                inner.external_1_dial_info.as_ref().unwrap().clone(),
 | 
			
		||||
                inner.protocol_type.unwrap(),
 | 
			
		||||
            )
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Do these detections in parallel, but with ordering preference
 | 
			
		||||
        let mut ord = FuturesOrdered::new();
 | 
			
		||||
 | 
			
		||||
        // UPNP Automatic Mapping
 | 
			
		||||
        ///////////
 | 
			
		||||
        let this = self.clone();
 | 
			
		||||
        let do_mapped_fut: SendPinBoxFuture<Option<DetectedDialInfo>> = Box::pin(async move {
 | 
			
		||||
            // Attempt a port mapping via all available and enabled mechanisms
 | 
			
		||||
            // Try this before the direct mapping in the event that we are restarting
 | 
			
		||||
            // and may not have recorded a mapping created the last time
 | 
			
		||||
            if let Some(external_mapped_dial_info) = this.try_port_mapping().await {
 | 
			
		||||
                // Got a port mapping, let's use it
 | 
			
		||||
                return Some(DetectedDialInfo {
 | 
			
		||||
                    dial_info: external_mapped_dial_info.clone(),
 | 
			
		||||
                    dial_info_class: DialInfoClass::Mapped,
 | 
			
		||||
                    network_class: NetworkClass::InboundCapable,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            None
 | 
			
		||||
        });
 | 
			
		||||
        ord.push_back(do_mapped_fut);
 | 
			
		||||
 | 
			
		||||
        // Manual Mapping Detection
 | 
			
		||||
        ///////////
 | 
			
		||||
        let this = self.clone();
 | 
			
		||||
        if let Some(local_port) = this.net.get_local_port(protocol_type) {
 | 
			
		||||
            if external_1_dial_info.port() != local_port {
 | 
			
		||||
                let c_external_1_dial_info = external_1_dial_info.clone();
 | 
			
		||||
                let c_node_1 = node_1.clone();
 | 
			
		||||
                let do_manual_map_fut: SendPinBoxFuture<Option<DetectedDialInfo>> =
 | 
			
		||||
                    Box::pin(async move {
 | 
			
		||||
                        // Do a validate_dial_info on the external address, but with the same port as the local port of local interface, from a redirected node
 | 
			
		||||
                        // This test is to see if a node had manual port forwarding done with the same port number as the local listener
 | 
			
		||||
                        let mut external_1_dial_info_with_local_port =
 | 
			
		||||
                            c_external_1_dial_info.clone();
 | 
			
		||||
                        external_1_dial_info_with_local_port.set_port(local_port);
 | 
			
		||||
 | 
			
		||||
                        if this
 | 
			
		||||
                            .validate_dial_info(
 | 
			
		||||
                                c_node_1.clone(),
 | 
			
		||||
                                external_1_dial_info_with_local_port.clone(),
 | 
			
		||||
                                true,
 | 
			
		||||
                            )
 | 
			
		||||
                            .await
 | 
			
		||||
                        {
 | 
			
		||||
                            // Add public dial info with Direct dialinfo class
 | 
			
		||||
                            return Some(DetectedDialInfo {
 | 
			
		||||
                                dial_info: external_1_dial_info_with_local_port,
 | 
			
		||||
                                dial_info_class: DialInfoClass::Direct,
 | 
			
		||||
                                network_class: NetworkClass::InboundCapable,
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        None
 | 
			
		||||
                    });
 | 
			
		||||
                ord.push_back(do_manual_map_fut);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Full Cone NAT Detection
 | 
			
		||||
        ///////////
 | 
			
		||||
        let this = self.clone();
 | 
			
		||||
        let c_node_1 = node_1.clone();
 | 
			
		||||
        let c_external_1_dial_info = external_1_dial_info.clone();
 | 
			
		||||
        let do_full_cone_fut: SendPinBoxFuture<Option<DetectedDialInfo>> = Box::pin(async move {
 | 
			
		||||
            // Let's see what kind of NAT we have
 | 
			
		||||
            // Does a redirected dial info validation from a different address and a random port find us?
 | 
			
		||||
            if this
 | 
			
		||||
                .validate_dial_info(c_node_1.clone(), c_external_1_dial_info.clone(), true)
 | 
			
		||||
                .await
 | 
			
		||||
            {
 | 
			
		||||
                // Yes, another machine can use the dial info directly, so Full Cone
 | 
			
		||||
                // Add public dial info with full cone NAT network class
 | 
			
		||||
 | 
			
		||||
                return Some(DetectedDialInfo {
 | 
			
		||||
                    dial_info: c_external_1_dial_info,
 | 
			
		||||
                    dial_info_class: DialInfoClass::FullConeNAT,
 | 
			
		||||
                    network_class: NetworkClass::InboundCapable,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            None
 | 
			
		||||
        });
 | 
			
		||||
        ord.push_back(do_full_cone_fut);
 | 
			
		||||
 | 
			
		||||
        // Run detections in parallel and take the first one, ordered by preference, that returns a result
 | 
			
		||||
        while let Some(res) = ord.next().await {
 | 
			
		||||
            if let Some(ddi) = res {
 | 
			
		||||
                self.set_detected_public_dial_info(ddi.dial_info, ddi.dial_info_class);
 | 
			
		||||
                self.set_detected_network_class(ddi.network_class);
 | 
			
		||||
                return Ok(true);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // We are restricted, determine what kind of restriction
 | 
			
		||||
        // Get the external dial info for our use here
 | 
			
		||||
        let (external_1_address, address_type) = {
 | 
			
		||||
            let inner = self.inner.lock();
 | 
			
		||||
            (
 | 
			
		||||
                inner.external_1_address.unwrap(),
 | 
			
		||||
                inner.address_type.unwrap(),
 | 
			
		||||
            )
 | 
			
		||||
        };
 | 
			
		||||
        // Get our external address from some fast node, that is not node 1, call it node 2
 | 
			
		||||
        let (external_2_address, node_2) = match self
 | 
			
		||||
            .discover_external_address(protocol_type, address_type, Some(node_1.node_ids()))
 | 
			
		||||
            .await
 | 
			
		||||
        {
 | 
			
		||||
            None => {
 | 
			
		||||
                // If we can't get an external address, allow retry
 | 
			
		||||
                log_net!(debug "failed to discover external address 2 for {:?}:{:?}, skipping node {:?}", protocol_type, address_type, node_1);
 | 
			
		||||
                return Ok(false);
 | 
			
		||||
            }
 | 
			
		||||
            Some(v) => v,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        log_net!(debug
 | 
			
		||||
            "external_2_address: {:?}\nnode_2: {:?}",
 | 
			
		||||
            external_2_address, node_2
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // If we have two different external addresses, then this is a symmetric NAT
 | 
			
		||||
        if external_2_address != external_1_address {
 | 
			
		||||
            // Symmetric NAT is outbound only, no public dial info will work
 | 
			
		||||
            self.set_detected_network_class(NetworkClass::OutboundOnly);
 | 
			
		||||
 | 
			
		||||
            // No more retries
 | 
			
		||||
            return Ok(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If we're going to end up as a restricted NAT of some sort
 | 
			
		||||
        // Address is the same, so it's address or port restricted
 | 
			
		||||
 | 
			
		||||
        // Do a validate_dial_info on the external address from a random port
 | 
			
		||||
        if self
 | 
			
		||||
            .validate_dial_info(node_2.clone(), external_1_dial_info.clone(), false)
 | 
			
		||||
            .await
 | 
			
		||||
        {
 | 
			
		||||
            // Got a reply from a non-default port, which means we're only address restricted
 | 
			
		||||
            self.set_detected_public_dial_info(
 | 
			
		||||
                external_1_dial_info,
 | 
			
		||||
                DialInfoClass::AddressRestrictedNAT,
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            // Didn't get a reply from a non-default port, which means we are also port restricted
 | 
			
		||||
            self.set_detected_public_dial_info(
 | 
			
		||||
                external_1_dial_info,
 | 
			
		||||
                DialInfoClass::PortRestrictedNAT,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        self.set_detected_network_class(NetworkClass::InboundCapable);
 | 
			
		||||
 | 
			
		||||
        // Allow another retry because sometimes trying again will get us Full Cone NAT instead
 | 
			
		||||
        Ok(false)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Network {
 | 
			
		||||
    #[instrument(level = "trace", skip(self, context), err)]
 | 
			
		||||
    pub async fn update_protocol_dialinfo(
 | 
			
		||||
@@ -670,6 +20,25 @@ impl Network {
 | 
			
		||||
        // Start doing protocol
 | 
			
		||||
        context.protocol_begin(protocol_type, address_type);
 | 
			
		||||
 | 
			
		||||
        // UPNP Automatic Mapping
 | 
			
		||||
        ///////////
 | 
			
		||||
        let this = self.clone();
 | 
			
		||||
        let do_mapped_fut: SendPinBoxFuture<Option<DetectedDialInfo>> = Box::pin(async move {
 | 
			
		||||
            // Attempt a port mapping via all available and enabled mechanisms
 | 
			
		||||
            // Try this before the direct mapping in the event that we are restarting
 | 
			
		||||
            // and may not have recorded a mapping created the last time
 | 
			
		||||
            if let Some(external_mapped_dial_info) = this.try_port_mapping().await {
 | 
			
		||||
                // Got a port mapping, let's use it
 | 
			
		||||
                return Some(DetectedDialInfo {
 | 
			
		||||
                    dial_info: external_mapped_dial_info.clone(),
 | 
			
		||||
                    dial_info_class: DialInfoClass::Mapped,
 | 
			
		||||
                    network_class: NetworkClass::InboundCapable,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            None
 | 
			
		||||
        });
 | 
			
		||||
        ord.push_back(do_mapped_fut);
 | 
			
		||||
 | 
			
		||||
        // Loop for restricted NAT retries
 | 
			
		||||
        loop {
 | 
			
		||||
            log_net!(debug
 | 
			
		||||
@@ -685,22 +54,21 @@ impl Network {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // If our local interface list contains external_1 then there is no NAT in place
 | 
			
		||||
            {
 | 
			
		||||
                let res = {
 | 
			
		||||
                    let inner = context.inner.lock();
 | 
			
		||||
                    inner
 | 
			
		||||
                        .intf_addrs
 | 
			
		||||
                        .as_ref()
 | 
			
		||||
                        .unwrap()
 | 
			
		||||
                        .contains(inner.external_1_address.as_ref().unwrap())
 | 
			
		||||
                };
 | 
			
		||||
                if res {
 | 
			
		||||
                    // No NAT
 | 
			
		||||
                    context.protocol_process_no_nat().await?;
 | 
			
		||||
 | 
			
		||||
                    // No more retries
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            let res = {
 | 
			
		||||
                let inner = context.inner.lock();
 | 
			
		||||
                inner
 | 
			
		||||
                    .intf_addrs
 | 
			
		||||
                    .as_ref()
 | 
			
		||||
                    .unwrap()
 | 
			
		||||
                    .contains(inner.external_1_address.as_ref().unwrap())
 | 
			
		||||
            };
 | 
			
		||||
            if res {
 | 
			
		||||
                // No NAT
 | 
			
		||||
                context.protocol_process_no_nat().await?;
 | 
			
		||||
 | 
			
		||||
                // No more retries
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // There is -some NAT-
 | 
			
		||||
@@ -719,6 +87,11 @@ impl Network {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[instrument(level = "trace", skip(self), err)]
 | 
			
		||||
    pub async fn update_with_discovery_context(&self, ctx: DiscoveryContext) -> EyreResult<()> {
 | 
			
		||||
        //
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[instrument(level = "trace", skip(self), err)]
 | 
			
		||||
    pub async fn do_public_dial_info_check(
 | 
			
		||||
        &self,
 | 
			
		||||
@@ -745,12 +118,12 @@ impl Network {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Process all protocol and address combinations
 | 
			
		||||
        let mut futures = FuturesUnordered::new();
 | 
			
		||||
        let mut unord = FuturesUnordered::new();
 | 
			
		||||
        // Do UDPv4+v6 at the same time as everything else
 | 
			
		||||
        if protocol_config.inbound.contains(ProtocolType::UDP) {
 | 
			
		||||
            // UDPv4
 | 
			
		||||
            if protocol_config.family_global.contains(AddressType::IPV4) {
 | 
			
		||||
                futures.push(
 | 
			
		||||
                unord.push(
 | 
			
		||||
                    async {
 | 
			
		||||
                        let udpv4_context =
 | 
			
		||||
                            DiscoveryContext::new(self.routing_table(), self.clone());
 | 
			
		||||
@@ -765,7 +138,7 @@ impl Network {
 | 
			
		||||
                            log_net!(debug "Failed UDPv4 dialinfo discovery: {}", e);
 | 
			
		||||
                            return None;
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(vec![udpv4_context])
 | 
			
		||||
                        Some(udpv4_context)
 | 
			
		||||
                    }
 | 
			
		||||
                    .instrument(trace_span!("do_public_dial_info_check UDPv4"))
 | 
			
		||||
                    .boxed(),
 | 
			
		||||
@@ -774,7 +147,7 @@ impl Network {
 | 
			
		||||
 | 
			
		||||
            // UDPv6
 | 
			
		||||
            if protocol_config.family_global.contains(AddressType::IPV6) {
 | 
			
		||||
                futures.push(
 | 
			
		||||
                unord.push(
 | 
			
		||||
                    async {
 | 
			
		||||
                        let udpv6_context =
 | 
			
		||||
                            DiscoveryContext::new(self.routing_table(), self.clone());
 | 
			
		||||
@@ -789,7 +162,7 @@ impl Network {
 | 
			
		||||
                            log_net!(debug "Failed UDPv6 dialinfo discovery: {}", e);
 | 
			
		||||
                            return None;
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(vec![udpv6_context])
 | 
			
		||||
                        Some(udpv6_context)
 | 
			
		||||
                    }
 | 
			
		||||
                    .instrument(trace_span!("do_public_dial_info_check UDPv6"))
 | 
			
		||||
                    .boxed(),
 | 
			
		||||
@@ -800,7 +173,7 @@ impl Network {
 | 
			
		||||
        // Do TCPv4. Possibly do WSv4 if it is on a different port
 | 
			
		||||
        if protocol_config.family_global.contains(AddressType::IPV4) {
 | 
			
		||||
            if protocol_config.inbound.contains(ProtocolType::TCP) {
 | 
			
		||||
                futures.push(
 | 
			
		||||
                unord.push(
 | 
			
		||||
                    async {
 | 
			
		||||
                        // TCPv4
 | 
			
		||||
                        let tcpv4_context =
 | 
			
		||||
@@ -816,7 +189,7 @@ impl Network {
 | 
			
		||||
                            log_net!(debug "Failed TCPv4 dialinfo discovery: {}", e);
 | 
			
		||||
                            return None;
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(vec![tcpv4_context])
 | 
			
		||||
                        Some(tcpv4_context)
 | 
			
		||||
                    }
 | 
			
		||||
                    .instrument(trace_span!("do_public_dial_info_check TCPv4"))
 | 
			
		||||
                    .boxed(),
 | 
			
		||||
@@ -824,7 +197,7 @@ impl Network {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if protocol_config.inbound.contains(ProtocolType::WS) && !tcp_same_port {
 | 
			
		||||
                futures.push(
 | 
			
		||||
                unord.push(
 | 
			
		||||
                    async {
 | 
			
		||||
                        // WSv4
 | 
			
		||||
                        let wsv4_context =
 | 
			
		||||
@@ -840,7 +213,7 @@ impl Network {
 | 
			
		||||
                            log_net!(debug "Failed WSv4 dialinfo discovery: {}", e);
 | 
			
		||||
                            return None;
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(vec![wsv4_context])
 | 
			
		||||
                        Some(wsv4_context)
 | 
			
		||||
                    }
 | 
			
		||||
                    .instrument(trace_span!("do_public_dial_info_check WSv4"))
 | 
			
		||||
                    .boxed(),
 | 
			
		||||
@@ -851,7 +224,7 @@ impl Network {
 | 
			
		||||
        // Do TCPv6. Possibly do WSv6 if it is on a different port
 | 
			
		||||
        if protocol_config.family_global.contains(AddressType::IPV6) {
 | 
			
		||||
            if protocol_config.inbound.contains(ProtocolType::TCP) {
 | 
			
		||||
                futures.push(
 | 
			
		||||
                unord.push(
 | 
			
		||||
                    async {
 | 
			
		||||
                        // TCPv6
 | 
			
		||||
                        let tcpv6_context =
 | 
			
		||||
@@ -867,7 +240,7 @@ impl Network {
 | 
			
		||||
                            log_net!(debug "Failed TCPv6 dialinfo discovery: {}", e);
 | 
			
		||||
                            return None;
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(vec![tcpv6_context])
 | 
			
		||||
                        Some(tcpv6_context)
 | 
			
		||||
                    }
 | 
			
		||||
                    .instrument(trace_span!("do_public_dial_info_check TCPv6"))
 | 
			
		||||
                    .boxed(),
 | 
			
		||||
@@ -876,7 +249,7 @@ impl Network {
 | 
			
		||||
 | 
			
		||||
            // WSv6
 | 
			
		||||
            if protocol_config.inbound.contains(ProtocolType::WS) && !tcp_same_port {
 | 
			
		||||
                futures.push(
 | 
			
		||||
                unord.push(
 | 
			
		||||
                    async {
 | 
			
		||||
                        let wsv6_context =
 | 
			
		||||
                            DiscoveryContext::new(self.routing_table(), self.clone());
 | 
			
		||||
@@ -891,7 +264,7 @@ impl Network {
 | 
			
		||||
                            log_net!(debug "Failed WSv6 dialinfo discovery: {}", e);
 | 
			
		||||
                            return None;
 | 
			
		||||
                        }
 | 
			
		||||
                        Some(vec![wsv6_context])
 | 
			
		||||
                        Some(wsv6_context)
 | 
			
		||||
                    }
 | 
			
		||||
                    .instrument(trace_span!("do_public_dial_info_check WSv6"))
 | 
			
		||||
                    .boxed(),
 | 
			
		||||
@@ -902,23 +275,22 @@ impl Network {
 | 
			
		||||
        // Wait for all discovery futures to complete and collect contexts
 | 
			
		||||
        let mut contexts = Vec::<DiscoveryContext>::new();
 | 
			
		||||
        let mut new_network_class = Option::<NetworkClass>::None;
 | 
			
		||||
 | 
			
		||||
        loop {
 | 
			
		||||
            match futures.next().timeout_at(stop_token.clone()).await {
 | 
			
		||||
                Ok(Some(ctxvec)) => {
 | 
			
		||||
                    if let Some(ctxvec) = ctxvec {
 | 
			
		||||
                        for ctx in ctxvec {
 | 
			
		||||
                            if let Some(nc) = ctx.inner.lock().detected_network_class {
 | 
			
		||||
                                if let Some(last_nc) = new_network_class {
 | 
			
		||||
                                    if nc < last_nc {
 | 
			
		||||
                                        new_network_class = Some(nc);
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
            match unord.next().timeout_at(stop_token.clone()).await {
 | 
			
		||||
                Ok(Some(ctx)) => {
 | 
			
		||||
                    if let Some(ctx) = ctx {
 | 
			
		||||
                        if let Some(nc) = ctx.inner.lock().detected_network_class {
 | 
			
		||||
                            if let Some(last_nc) = new_network_class {
 | 
			
		||||
                                if nc < last_nc {
 | 
			
		||||
                                    new_network_class = Some(nc);
 | 
			
		||||
                                }
 | 
			
		||||
                            } else {
 | 
			
		||||
                                new_network_class = Some(nc);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            contexts.push(ctx);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        contexts.push(ctx);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Ok(None) => {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user