From 855b7eaf7da14c6950ebedd63a27338336859e2e Mon Sep 17 00:00:00 2001 From: John Smith Date: Sun, 15 Oct 2023 11:27:31 -0400 Subject: [PATCH] ipv6 detection --- veilid-core/src/network_manager/native/mod.rs | 4 +- .../network_manager/native/start_protocols.rs | 112 ++++++++++-------- veilid-core/src/network_manager/wasm/mod.rs | 11 +- veilid-tools/src/tools.rs | 36 +++++- veilid-tools/src/wasm.rs | 24 ++++ 5 files changed, 126 insertions(+), 61 deletions(-) diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index 3eab81f8..a50a06b8 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -104,7 +104,7 @@ struct NetworkInner { public_dial_info_check_punishment: Option>, /// udp socket record for bound-first sockets, which are used to guarantee a port is available before /// creating a 'reuseport' socket there. we don't want to pick ports that other programs are using - bound_first_udp: BTreeMap>, + bound_first_udp: BTreeMap, Option)>, /// mapping of protocol handlers to accept messages from a set of bound socket addresses inbound_udp_protocol_handlers: BTreeMap, /// outbound udp protocol handler for udpv4 @@ -113,7 +113,7 @@ struct NetworkInner { outbound_udpv6_protocol_handler: Option, /// tcp socket record for bound-first sockets, which are used to guarantee a port is available before /// creating a 'reuseport' socket there. we don't want to pick ports that other programs are using - bound_first_tcp: BTreeMap>, + bound_first_tcp: BTreeMap, Option)>, /// TLS handling socket controller tls_acceptor: Option, /// Multiplexer record for protocols on low level TCP sockets diff --git a/veilid-core/src/network_manager/native/start_protocols.rs b/veilid-core/src/network_manager/native/start_protocols.rs index 85575fd5..4844da54 100644 --- a/veilid-core/src/network_manager/native/start_protocols.rs +++ b/veilid-core/src/network_manager/native/start_protocols.rs @@ -85,37 +85,44 @@ impl Network { if inner.bound_first_udp.contains_key(&udp_port) { return true; } + + // Check for ipv6 + let has_v6 = is_ipv6_supported(); + // If the address is specified, only use the specified port and fail otherwise let mut bound_first_socket_v4 = None; let mut bound_first_socket_v6 = None; if let Ok(bfs4) = new_bound_first_udp_socket(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), udp_port)) { - if let Ok(bfs6) = new_bound_first_udp_socket(SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - udp_port, - )) { - bound_first_socket_v4 = Some(bfs4); - bound_first_socket_v6 = Some(bfs6); - } - } - if let (Some(bfs4), Some(bfs6)) = (bound_first_socket_v4, bound_first_socket_v6) { - cfg_if! { - if #[cfg(windows)] { - // On windows, drop the socket. This is a race condition, but there's - // no way around it. This isn't for security anyway, it's to prevent multiple copies of the - // app from binding on the same port. - drop(bfs4); - drop(bfs6); - inner.bound_first_udp.insert(udp_port, None); - } else { - inner.bound_first_udp.insert(udp_port, Some((bfs4, bfs6))); + if has_v6 { + if let Ok(bfs6) = new_bound_first_udp_socket(SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + udp_port, + )) { + bound_first_socket_v4 = Some(bfs4); + bound_first_socket_v6 = Some(bfs6); } + } else { + bound_first_socket_v4 = Some(bfs4); } - true - } else { - false } + + if bound_first_socket_v4.is_none() && (has_v6 && bound_first_socket_v6.is_none()) { + return false; + } + + cfg_if! { + if #[cfg(windows)] { + // On windows, drop the socket. This is a race condition, but there's + // no way around it. This isn't for security anyway, it's to prevent multiple copies of the + // app from binding on the same port. + inner.bound_first_udp.insert(udp_port, (None, None)); + } else { + inner.bound_first_udp.insert(udp_port, (bound_first_socket_v4, bound_first_socket_v6)); + } + } + true } fn bind_first_tcp_port(&self, tcp_port: u16) -> bool { @@ -123,37 +130,44 @@ impl Network { if inner.bound_first_tcp.contains_key(&tcp_port) { return true; } + + // Check for ipv6 + let has_v6 = is_ipv6_supported(); + // If the address is specified, only use the specified port and fail otherwise let mut bound_first_socket_v4 = None; let mut bound_first_socket_v6 = None; if let Ok(bfs4) = new_bound_first_tcp_socket(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), tcp_port)) { - if let Ok(bfs6) = new_bound_first_tcp_socket(SocketAddr::new( - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - tcp_port, - )) { - bound_first_socket_v4 = Some(bfs4); - bound_first_socket_v6 = Some(bfs6); - } - } - if let (Some(bfs4), Some(bfs6)) = (bound_first_socket_v4, bound_first_socket_v6) { - cfg_if! { - if #[cfg(windows)] { - // On windows, drop the socket. This is a race condition, but there's - // no way around it. This isn't for security anyway, it's to prevent multiple copies of the - // app from binding on the same port. - drop(bfs4); - drop(bfs6); - inner.bound_first_tcp.insert(tcp_port, None); - } else { - inner.bound_first_tcp.insert(tcp_port, Some((bfs4, bfs6))); + if has_v6 { + if let Ok(bfs6) = new_bound_first_tcp_socket(SocketAddr::new( + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + tcp_port, + )) { + bound_first_socket_v4 = Some(bfs4); + bound_first_socket_v6 = Some(bfs6); } + } else { + bound_first_socket_v4 = Some(bfs4); } - true - } else { - false } + + if bound_first_socket_v4.is_none() && (has_v6 && bound_first_socket_v6.is_none()) { + return false; + } + + cfg_if! { + if #[cfg(windows)] { + // On windows, drop the socket. This is a race condition, but there's + // no way around it. This isn't for security anyway, it's to prevent multiple copies of the + // app from binding on the same port. + inner.bound_first_tcp.insert(tcp_port, (None, None)); + } else { + inner.bound_first_tcp.insert(tcp_port, (bound_first_socket_v4, bound_first_socket_v6)); + } + } + true } pub(super) fn free_bound_first_ports(&self) { @@ -204,10 +218,7 @@ impl Network { if listen_address.is_empty() { // If listen address is empty, find us a port iteratively let port = self.find_available_udp_port(5150)?; - let ip_addrs = vec![ - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - ]; + let ip_addrs = available_unspecified_addresses(); Ok((port, ip_addrs)) } else { // If no address is specified, but the port is, use ipv4 and ipv6 unspecified @@ -227,10 +238,7 @@ impl Network { if listen_address.is_empty() { // If listen address is empty, find us a port iteratively let port = self.find_available_tcp_port(5150)?; - let ip_addrs = vec![ - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - ]; + let ip_addrs = available_unspecified_addresses(); Ok((port, ip_addrs)) } else { // If no address is specified, but the port is, use ipv4 and ipv6 unspecified diff --git a/veilid-core/src/network_manager/wasm/mod.rs b/veilid-core/src/network_manager/wasm/mod.rs index 78439cb2..565356e1 100644 --- a/veilid-core/src/network_manager/wasm/mod.rs +++ b/veilid-core/src/network_manager/wasm/mod.rs @@ -345,9 +345,14 @@ impl Network { outbound.insert(ProtocolType::WSS); } - // XXX: See issue #92 - let family_global = AddressTypeSet::from(AddressType::IPV4); - let family_local = AddressTypeSet::from(AddressType::IPV4); + let supported_address_types: AddressTypeSet = if is_ipv6_supported() { + AddressType::IPV4 | AddressType::IPV6 + } else { + AddressType::IPV4.into() + }; + + let family_global = supported_address_types; + let family_local = supported_address_types; let public_internet_capabilities = { PUBLIC_INTERNET_CAPABILITIES diff --git a/veilid-tools/src/tools.rs b/veilid-tools/src/tools.rs index 9543f9aa..2ff88f67 100644 --- a/veilid-tools/src/tools.rs +++ b/veilid-tools/src/tools.rs @@ -239,13 +239,41 @@ pub fn compatible_unspecified_socket_addr(socket_addr: &SocketAddr) -> SocketAdd } } +cfg_if! { + if #[cfg(not(target_arch = "wasm32"))] { + use std::net::UdpSocket; + + static IPV6_IS_SUPPORTED: Mutex> = Mutex::new(None); + + pub fn is_ipv6_supported() -> bool { + let mut opt_supp = IPV6_IS_SUPPORTED.lock(); + if let Some(supp) = *opt_supp { + return supp; + } + // Not exhaustive but for our use case it should be sufficient. If no local ports are available for binding, Veilid isn't going to work anyway :P + let supp = UdpSocket::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0)).is_ok(); + *opt_supp = Some(supp); + supp + } + } +} + +pub fn available_unspecified_addresses() -> Vec { + if is_ipv6_supported() { + vec![ + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + IpAddr::V6(Ipv6Addr::UNSPECIFIED), + ] + } else { + vec![IpAddr::V4(Ipv4Addr::UNSPECIFIED)] + } +} + pub fn listen_address_to_socket_addrs(listen_address: &str) -> Result, String> { // If no address is specified, but the port is, use ipv4 and ipv6 unspecified // If the address is specified, only use the specified port and fail otherwise - let ip_addrs = [ - IpAddr::V4(Ipv4Addr::UNSPECIFIED), - IpAddr::V6(Ipv6Addr::UNSPECIFIED), - ]; + + let ip_addrs = available_unspecified_addresses(); Ok(if let Some(portstr) = listen_address.strip_prefix(':') { let port = portstr diff --git a/veilid-tools/src/wasm.rs b/veilid-tools/src/wasm.rs index 187112ca..cfdcdd50 100644 --- a/veilid-tools/src/wasm.rs +++ b/veilid-tools/src/wasm.rs @@ -1,3 +1,5 @@ +#![cfg(target_arch = "wasm32")] + use super::*; use core::sync::atomic::{AtomicI8, Ordering}; use js_sys::{global, Reflect}; @@ -58,3 +60,25 @@ pub struct JsValueError(String); pub fn map_jsvalue_error(x: JsValue) -> JsValueError { JsValueError(x.as_string().unwrap_or_default()) } + +static IPV6_IS_SUPPORTED: Mutex> = Mutex::new(None); + +pub fn is_ipv6_supported() -> bool { + let mut opt_supp = IPV6_IS_SUPPORTED.lock(); + if let Some(supp) = *opt_supp { + return supp; + } + // let supp = match UdpSocket::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0)) { + // Ok(_) => true, + // Err(e) => !matches!( + // e.kind(), + // std::io::ErrorKind::AddrNotAvailable | std::io::ErrorKind::Unsupported + // ), + // }; + + // XXX: See issue #92 + let supp = false; + + *opt_supp = Some(supp); + supp +}