public address detection refactoring
This commit is contained in:
parent
8d694f20cd
commit
1b5934dad4
@ -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) => {
|
||||
|
Loading…
Reference in New Issue
Block a user