diff --git a/veilid-core/src/intf/native/network/public_dialinfo_discovery.rs b/veilid-core/src/intf/native/network/public_dialinfo_discovery.rs index abf978b9..a55474f9 100644 --- a/veilid-core/src/intf/native/network/public_dialinfo_discovery.rs +++ b/veilid-core/src/intf/native/network/public_dialinfo_discovery.rs @@ -105,108 +105,143 @@ impl Network { None } - pub async fn update_udpv4_dialinfo_task_routine(self, l: u64, t: u64) -> Result<(), String> { + pub async fn update_udpv4_dialinfo_task_routine(self, _l: u64, _t: u64) -> Result<(), String> { trace!("looking for udpv4 public dial info"); let routing_table = self.routing_table(); + let mut retry_count = { + let c = self.config.get(); + c.network.restricted_nat_retries + }; + // Get our local address let local1 = self.discover_local_address(ProtocolAddressType::UDPv4)?; - // Get our external address from some fast node, call it node B - let (external1, node_b) = self - .discover_external_address(ProtocolAddressType::UDPv4, None) - .await?; - let external1_dial_info = DialInfo::udp_from_socketaddr(external1); - // If local1 == external1 then there is no NAT in place - if local1 == external1 { - // No NAT - // Do a validate_dial_info on the external address from a routed 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( - external1_dial_info, - Some(NetworkClass::Server), - DialInfoOrigin::Discovered, - ); - } else { - // UDP firewall? - warn!("UDP static public dial info not reachable. UDP firewall may be blocking inbound to {:?} for {:?}",external1_dial_info, node_b); - } - } else { - // There is -some NAT- - // Attempt a UDP port mapping via all available and enabled mechanisms - if let Some(external_mapped) = self - .try_port_mapping(local1.clone(), ProtocolAddressType::UDPv4) - .await - { - // Got a port mapping, let's use it - let external_mapped_dial_info = DialInfo::udp_from_socketaddr(external_mapped); - routing_table.register_public_dial_info( - external_mapped_dial_info, - Some(NetworkClass::Mapped), - DialInfoOrigin::Mapped, - ); - } else { - // Port mapping was not possible, let's see what kind of NAT we have + // Loop for restricted NAT retries + loop { + // Get our external address from some fast node, call it node B + let (external1, node_b) = self + .discover_external_address(ProtocolAddressType::UDPv4, None) + .await?; + let external1_dial_info = DialInfo::udp_from_socketaddr(external1); - // Does a redirected dial info validation find us? + // If local1 == external1 then there is no NAT in place + if local1 == external1 { + // No NAT + // Do a validate_dial_info on the external address from a routed node if self .validate_dial_info(node_b.clone(), external1_dial_info.clone(), true, false) .await { - // Yes, another machine can use the dial info directly, so Full Cone - // Add public dial info with full cone NAT network class + // Add public dial info with Server network class routing_table.register_public_dial_info( external1_dial_info, - Some(NetworkClass::FullNAT), + Some(NetworkClass::Server), DialInfoOrigin::Discovered, ); - } else { - // No, we are restricted, determine what kind of restriction - // Get our external address from some fast node, that is not node B, call it node D - let (external2, node_d) = self - .discover_external_address( - ProtocolAddressType::UDPv4, - Some(node_b.node_id()), + // No more retries + break; + } else { + // UDP firewall? + warn!("UDP static public dial info not reachable. UDP firewall may be blocking inbound to {:?} for {:?}",external1_dial_info, node_b); + } + } else { + // There is -some NAT- + // Attempt a UDP port mapping via all available and enabled mechanisms + if let Some(external_mapped) = self + .try_port_mapping(local1.clone(), ProtocolAddressType::UDPv4) + .await + { + // Got a port mapping, let's use it + let external_mapped_dial_info = DialInfo::udp_from_socketaddr(external_mapped); + routing_table.register_public_dial_info( + external_mapped_dial_info, + Some(NetworkClass::Mapped), + DialInfoOrigin::Mapped, + ); + + // No more retries + break; + } else { + // Port mapping was not possible, let's see what kind of NAT we have + + // Does a redirected dial info validation find us? + if self + .validate_dial_info( + node_b.clone(), + external1_dial_info.clone(), + true, + false, ) - .await?; - // 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); + .await + { + // 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( + external1_dial_info, + Some(NetworkClass::FullNAT), + DialInfoOrigin::Discovered, + ); + + // No more retries + break; } else { - // Address is the same, so it's address or port restricted - let external2_dial_info = DialInfo::udp_from_socketaddr(external2); - // Do a validate_dial_info on the external address from a routed node - if self - .validate_dial_info( - node_d.clone(), - external2_dial_info.clone(), - false, - true, + // No, we are restricted, determine what kind of restriction + + // Get our external address from some fast node, that is not node B, call it node D + let (external2, node_d) = self + .discover_external_address( + ProtocolAddressType::UDPv4, + Some(node_b.node_id()), ) - .await - { - // Got a reply from a non-default port, which means we're only address restricted - routing_table.register_public_dial_info( - external1_dial_info, - Some(NetworkClass::AddressRestrictedNAT), - DialInfoOrigin::Discovered, - ); + .await?; + // 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); + + // No more retries + break; } else { - // Didn't get a reply from a non-default port, which means we are also port restricted - routing_table.register_public_dial_info( - external1_dial_info, - Some(NetworkClass::PortRestrictedNAT), - DialInfoOrigin::Discovered, - ); + // If we're going to end up as a restricted NAT of some sort + // we should go through our retries before we assign a dial info + if retry_count == 0 { + // Address is the same, so it's address or port restricted + let external2_dial_info = DialInfo::udp_from_socketaddr(external2); + // Do a validate_dial_info on the external address from a routed node + if self + .validate_dial_info( + node_d.clone(), + external2_dial_info.clone(), + false, + true, + ) + .await + { + // Got a reply from a non-default port, which means we're only address restricted + routing_table.register_public_dial_info( + external1_dial_info, + Some(NetworkClass::AddressRestrictedNAT), + DialInfoOrigin::Discovered, + ); + } else { + // Didn't get a reply from a non-default port, which means we are also port restricted + routing_table.register_public_dial_info( + external1_dial_info, + Some(NetworkClass::PortRestrictedNAT), + DialInfoOrigin::Discovered, + ); + } + } } } } + + if retry_count == 0 { + break; + } + retry_count -= 1; } } diff --git a/veilid-core/src/tests/common/test_veilid_config.rs b/veilid-core/src/tests/common/test_veilid_config.rs index a9b2831b..abba4303 100644 --- a/veilid-core/src/tests/common/test_veilid_config.rs +++ b/veilid-core/src/tests/common/test_veilid_config.rs @@ -183,6 +183,7 @@ pub fn config_callback(key: String) -> Result, String> { "network.upnp" => Ok(Box::new(false)), "network.natpmp" => Ok(Box::new(false)), "network.address_filter" => Ok(Box::new(true)), + "network.restricted_nat_retries" => Ok(Box::new(3u32)), "network.tls.certificate_path" => Ok(Box::new(get_certfile_path())), "network.tls.private_key_path" => Ok(Box::new(get_keyfile_path())), "network.tls.connection_initial_timeout" => Ok(Box::new(2_000_000u64)), @@ -270,6 +271,7 @@ pub async fn test_config() { assert_eq!(inner.network.upnp, false); assert_eq!(inner.network.natpmp, false); assert_eq!(inner.network.address_filter, true); + assert_eq!(inner.network.restricted_nat_retries, 3u32); assert_eq!(inner.network.tls.certificate_path, get_certfile_path()); assert_eq!(inner.network.tls.private_key_path, get_keyfile_path()); assert_eq!(inner.network.tls.connection_initial_timeout, 2_000_000u64); diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 2b69a06f..c8d7a7d4 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -128,6 +128,7 @@ pub struct VeilidConfigNetwork { pub upnp: bool, pub natpmp: bool, pub address_filter: bool, + pub restricted_nat_retries: u32, pub tls: VeilidConfigTLS, pub application: VeilidConfigApplication, pub protocol: VeilidConfigProtocol, @@ -222,6 +223,7 @@ impl VeilidConfig { get_config!(inner.network.upnp); get_config!(inner.network.natpmp); get_config!(inner.network.address_filter); + get_config!(inner.network.restricted_nat_retries); get_config!(inner.network.tls.certificate_path); get_config!(inner.network.tls.private_key_path); get_config!(inner.network.tls.connection_initial_timeout); diff --git a/veilid-server/src/settings.rs b/veilid-server/src/settings.rs index 02a9d0a8..4185eed2 100644 --- a/veilid-server/src/settings.rs +++ b/veilid-server/src/settings.rs @@ -63,6 +63,7 @@ core: upnp: false natpmp: false address_filter: true + restricted_nat_retries: 3 tls: certificate_path: "/etc/veilid/server.crt" private_key_path: "/etc/veilid/private/server.key" @@ -391,6 +392,7 @@ pub struct Network { pub upnp: bool, pub natpmp: bool, pub address_filter: bool, + pub restricted_nat_retries: u32, pub tls: TLS, pub application: Application, pub protocol: Protocol, @@ -638,6 +640,9 @@ impl Settings { "network.upnp" => Ok(Box::new(inner.core.network.upnp)), "network.natpmp" => Ok(Box::new(inner.core.network.natpmp)), "network.address_filter" => Ok(Box::new(inner.core.network.address_filter)), + "network.restricted_nat_retries" => { + Ok(Box::new(inner.core.network.restricted_nat_retries)) + } "network.tls.certificate_path" => Ok(Box::new( inner .core @@ -869,6 +874,7 @@ mod tests { assert_eq!(s.core.network.upnp, false); assert_eq!(s.core.network.natpmp, false); assert_eq!(s.core.network.address_filter, true); + assert_eq!(s.core.network.restricted_nat_retries, 3u32); // assert_eq!( s.core.network.tls.certificate_path, diff --git a/veilid-wasm/src/js_veilid_core.rs b/veilid-wasm/src/js_veilid_core.rs index a41b1d5f..53475710 100644 --- a/veilid-wasm/src/js_veilid_core.rs +++ b/veilid-wasm/src/js_veilid_core.rs @@ -120,6 +120,7 @@ impl JsVeilidCore { "network.upnp" => Self::value_to_bool(val), "network.natpmp" => Self::value_to_bool(val), "network.address_filter" => Self::value_to_bool(val), + "network.restricted_nat_retries" => Self::value_to_u32(val), "network.tls.certificate_path" => Self::value_to_string(val), "network.tls.private_key_path" => Self::value_to_string(val), "network.application.path" => Self::value_to_string(val), diff --git a/veilid-wasm/tests/web.rs b/veilid-wasm/tests/web.rs index 28481690..1afc7455 100644 --- a/veilid-wasm/tests/web.rs +++ b/veilid-wasm/tests/web.rs @@ -63,6 +63,7 @@ fn init_callbacks() { case "network.upnp": return false; case "network.natpmp": return false; case "network.address_filter": return true; + case "network.restricted_nat_retries": return 3; case "network.tls.certificate_path": return ""; case "network.tls.private_key_path": return ""; case "network.application.path": return "/app";