diff --git a/zerotier-system-service/src/datadir.rs b/zerotier-system-service/src/datadir.rs index 3b522fa20..1edd131cf 100644 --- a/zerotier-system-service/src/datadir.rs +++ b/zerotier-system-service/src/datadir.rs @@ -56,7 +56,7 @@ impl DataDir { /// Load identity.secret from data directory. pub async fn load_identity(&self) -> std::io::Result { - let id_data = Identity::from_str(String::from_utf8_lossy(read_limit(self.base_path.join(IDENTITY_PUBLIC_FILENAME), 4096).await?.as_slice()).as_ref()); + let id_data = Identity::from_str(String::from_utf8_lossy(read_limit(self.base_path.join(IDENTITY_SECRET_FILENAME), 4096).await?.as_slice()).as_ref()); if id_data.is_err() { return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, id_data.err().unwrap())); } diff --git a/zerotier-system-service/src/ipv6.rs b/zerotier-system-service/src/ipv6.rs new file mode 100644 index 000000000..ef0a1c808 --- /dev/null +++ b/zerotier-system-service/src/ipv6.rs @@ -0,0 +1,70 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. + +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "darwin"))] +mod freebsd_like { + use lazy_static::lazy_static; + use num_traits::AsPrimitive; + use parking_lot::Mutex; + use std::mem::size_of; + use zerotier_network_hypervisor::vl1::InetAddress; + + lazy_static! { + static ref INFO_SOCKET: Mutex = Mutex::new(-1); + } + + #[allow(unused)] + const SIZE_OF_IN6_IFREQ: usize = 288; + + #[allow(unused)] + const SIOCGIFAFLAG_IN6: libc::c_ulong = 3240126793; + + #[allow(unused)] + const IN6_IFF_TEMPORARY: libc::c_int = 128; + + #[allow(unused)] + #[repr(C)] + union in6_ifreq_inner { + ifru_addr: libc::sockaddr_in6, + ifru_flags: libc::c_short, + ifru_flags6: libc::c_int, + _padding: [u8; SIZE_OF_IN6_IFREQ - 16], + } + + #[allow(non_camel_case_types)] + #[allow(unused)] + #[repr(C)] + struct in6_ifreq { + ifr_name: [u8; 16], + ifr_ifru: in6_ifreq_inner, + } + + pub fn is_ipv6_temporary(device_name: &str, address: &InetAddress) -> bool { + if address.is_ipv6() { + unsafe { + let mut info_socket = INFO_SOCKET.lock(); + if *info_socket < 0 { + *info_socket = libc::socket(libc::AF_INET6.as_(), libc::SOCK_DGRAM.as_(), 0) as i32; + if *info_socket < 0 { + return true; // probably means IPv6 is not enabled! + } + } + + let mut ifr6: in6_ifreq = std::mem::zeroed(); + let device_name_bytes = device_name.as_bytes(); + assert!(device_name_bytes.len() <= 15); + ifr6.ifr_name[..device_name_bytes.len()].copy_from_slice(device_name_bytes); + std::ptr::copy_nonoverlapping((address as *const InetAddress).cast(), &mut ifr6.ifr_ifru.ifru_addr, size_of::()); + if libc::ioctl((*info_socket).as_(), SIOCGIFAFLAG_IN6, &mut ifr6 as *mut in6_ifreq) != -1 { + if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TEMPORARY) != 0 { + return true; + } + } + } + } + + return false; + } +} + +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "darwin"))] +pub use freebsd_like::is_ipv6_temporary; diff --git a/zerotier-system-service/src/main.rs b/zerotier-system-service/src/main.rs index 09196c0f1..d5f03717d 100644 --- a/zerotier-system-service/src/main.rs +++ b/zerotier-system-service/src/main.rs @@ -4,6 +4,7 @@ pub mod cli; pub mod datadir; pub mod exitcode; pub mod getifaddrs; +pub mod ipv6; pub mod jsonformatter; pub mod localconfig; pub mod localinterface; diff --git a/zerotier-system-service/src/service.rs b/zerotier-system-service/src/service.rs index 0a3f05983..2612bb836 100644 --- a/zerotier-system-service/src/service.rs +++ b/zerotier-system-service/src/service.rs @@ -35,21 +35,19 @@ struct ServiceImpl { pub rt: tokio::runtime::Handle, pub data: DataDir, pub local_socket_unique_id_counter: AtomicUsize, - pub udp_sockets: tokio::sync::RwLock>, + pub udp_sockets: parking_lot::RwLock>, pub num_listeners_per_socket: usize, _core: Option>, } impl Drop for Service { fn drop(&mut self) { - self.internal.rt.block_on(async { - // Kill all background tasks associated with this service. - self.udp_binding_task.abort(); - self.core_background_service_task.abort(); + // Kill all background tasks associated with this service. + self.udp_binding_task.abort(); + self.core_background_service_task.abort(); - // Drop all bound sockets since these can hold circular Arc<> references to 'internal'. - self.internal.udp_sockets.write().await.clear(); - }); + // Drop all bound sockets since these can hold circular Arc<> references to 'internal'. + self.internal.udp_sockets.write().clear(); } } @@ -63,7 +61,7 @@ impl Service { rt, data: DataDir::open(base_path).await.map_err(|e| Box::new(e))?, local_socket_unique_id_counter: AtomicUsize::new(1), - udp_sockets: tokio::sync::RwLock::new(HashMap::with_capacity(4)), + udp_sockets: parking_lot::RwLock::new(HashMap::with_capacity(4)), num_listeners_per_socket: std::thread::available_parallelism().unwrap().get(), _core: None, }; @@ -88,13 +86,15 @@ impl ServiceImpl { /// Called in udp_binding_task_main() to service a particular UDP port. async fn update_udp_bindings_for_port(self: &Arc, port: u16, interface_prefix_blacklist: &Vec, cidr_blacklist: &Vec) -> Option> { - let mut udp_sockets = self.udp_sockets.write().await; - let bp = udp_sockets.entry(port).or_insert_with(|| BoundUdpPort::new(port)); - let (errors, new_sockets) = bp.update_bindings(interface_prefix_blacklist, cidr_blacklist); - if bp.sockets.is_empty() { - return Some(errors); - } - drop(udp_sockets); + let new_sockets = { + let mut udp_sockets = self.udp_sockets.write(); + let bp = udp_sockets.entry(port).or_insert_with(|| BoundUdpPort::new(port)); + let (errors, new_sockets) = bp.update_bindings(interface_prefix_blacklist, cidr_blacklist); + if bp.sockets.is_empty() { + return Some(errors); + } + new_sockets + }; for ns in new_sockets.iter() { /* @@ -193,13 +193,13 @@ impl SystemInterface for ServiceImpl { // Otherwise we try to send from one socket on every interface or from the specified interface. // This path only happens when the core is trying new endpoints. The fast path is for most packets. - let sockets = self.udp_sockets.read().await; + let sockets = self.udp_sockets.read(); if !sockets.is_empty() { if let Some(specific_interface) = local_interface { for (_, p) in sockets.iter() { for s in p.sockets.iter() { if s.interface.eq(specific_interface) { - if s.send_async(&self.rt, address, data, packet_ttl).await { + if s.send_sync_nonblock(&self.rt, address, data, packet_ttl) { return true; } } @@ -213,8 +213,9 @@ impl SystemInterface for ServiceImpl { let p = sockets.get(*bound_ports.get(rn.wrapping_add(i) % bound_ports.len()).unwrap()).unwrap(); for s in p.sockets.iter() { if !sent_on_interfaces.contains(&s.interface) { - if s.send_async(&self.rt, address, data, packet_ttl).await { + if s.send_sync_nonblock(&self.rt, address, data, packet_ttl) { sent_on_interfaces.insert(s.interface.clone()); + break; } } } @@ -222,6 +223,7 @@ impl SystemInterface for ServiceImpl { return !sent_on_interfaces.is_empty(); } } + return false; } _ => {} diff --git a/zerotier-system-service/src/udp.rs b/zerotier-system-service/src/udp.rs index 587eb99cc..973135eeb 100644 --- a/zerotier-system-service/src/udp.rs +++ b/zerotier-system-service/src/udp.rs @@ -6,8 +6,6 @@ use std::mem::{size_of, transmute, MaybeUninit}; #[allow(unused_imports)] use std::net::SocketAddr; #[allow(unused_imports)] -use std::os::raw::*; -#[allow(unused_imports)] use std::ptr::{null, null_mut}; use std::sync::Arc; @@ -15,6 +13,7 @@ use std::sync::Arc; use std::os::unix::io::{FromRawFd, RawFd}; use crate::getifaddrs; +use crate::ipv6; use crate::localinterface::LocalInterface; #[allow(unused_imports)] @@ -33,18 +32,10 @@ pub struct BoundUdpPort { /// A socket bound to a specific interface and IP. pub struct BoundUdpSocket { - /// Local IP address to which this socket is bound. pub address: InetAddress, - - /// High-level async socket, but UDP also supports non-blocking sync send. pub socket: Arc, - - /// Local interface on which socket appears. pub interface: LocalInterface, - - /// Add tasks here that should be aborted when this socket is closed. pub socket_associated_tasks: parking_lot::Mutex>>, - fd: RawFd, } @@ -60,8 +51,8 @@ impl BoundUdpSocket { #[cfg(unix)] #[inline(always)] fn set_ttl(&self, packet_ttl: u8) { - let ttl = packet_ttl as c_int; - unsafe { libc::setsockopt(self.fd.as_(), libc::IPPROTO_IP.as_(), libc::IP_TOS.as_(), (&ttl as *const c_int).cast(), std::mem::size_of::().as_()) }; + let ttl = packet_ttl as libc::c_int; + unsafe { libc::setsockopt(self.fd.as_(), libc::IPPROTO_IP.as_(), libc::IP_TOS.as_(), (&ttl as *const libc::c_int).cast(), std::mem::size_of::().as_()) }; } #[cfg(any(target_os = "macos"))] @@ -187,28 +178,29 @@ impl BoundUdpPort { pub fn update_bindings(&mut self, interface_prefix_blacklist: &Vec, cidr_blacklist: &Vec) -> (Vec<(LocalInterface, InetAddress, std::io::Error)>, Vec>) { let mut existing_bindings: HashMap>> = HashMap::with_capacity(4); for s in self.sockets.drain(..) { - existing_bindings.entry(s.interface.clone()).or_insert_with(|| HashMap::with_capacity(4)).insert(s.address.clone(), s); + existing_bindings.entry(s.interface).or_insert_with(|| HashMap::with_capacity(4)).insert(s.address.clone(), s); } let mut errors = Vec::new(); let mut new_sockets = Vec::new(); getifaddrs::for_each_address(|address, interface| { let interface_str = interface.to_string(); + let mut addr_with_port = address.clone(); + addr_with_port.set_port(self.port); if address.is_ip() && matches!(address.scope(), IpScope::Global | IpScope::PseudoPrivate | IpScope::Private | IpScope::Shared) && !interface_prefix_blacklist.iter().any(|pfx| interface_str.starts_with(pfx.as_str())) && !cidr_blacklist.iter().any(|r| address.is_within(r)) + && !ipv6::is_ipv6_temporary(interface_str.as_str(), address) { let mut found = false; if let Some(byaddr) = existing_bindings.get(interface) { - if let Some(socket) = byaddr.get(address) { + if let Some(socket) = byaddr.get(&addr_with_port) { found = true; self.sockets.push(socket.clone()); } } if !found { - let mut addr_with_port = address.clone(); - addr_with_port.set_port(self.port); let s = unsafe { bind_udp_to_device(interface_str.as_str(), &addr_with_port) }; if s.is_ok() { let fd = s.unwrap(); @@ -253,16 +245,21 @@ unsafe fn bind_udp_to_device(device_name: &str, address: &InetAddress) -> Result return Err("unable to create socket"); } - let mut setsockopt_results: c_int = 0; - - let mut fl: c_int = 0; - setsockopt_results |= libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_LINGER.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + #[allow(unused_variables)] + let mut setsockopt_results: libc::c_int = 0; + let mut fl: libc::c_int; fl = 1; - setsockopt_results |= libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_BROADCAST.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + setsockopt_results |= libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_REUSEPORT.as_(), (&mut fl as *mut libc::c_int).cast(), std::mem::size_of::().as_()); + debug_assert!(setsockopt_results == 0); + + fl = 1; + setsockopt_results |= libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_BROADCAST.as_(), (&mut fl as *mut libc::c_int).cast(), std::mem::size_of::().as_()); + debug_assert!(setsockopt_results == 0); if af == libc::AF_INET6 { fl = 1; - setsockopt_results |= libc::setsockopt(s, libc::IPPROTO_IPV6.as_(), libc::IPV6_V6ONLY.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + setsockopt_results |= libc::setsockopt(s, libc::IPPROTO_IPV6.as_(), libc::IPV6_V6ONLY.as_(), (&mut fl as *mut libc::c_int).cast(), std::mem::size_of::().as_()); + debug_assert!(setsockopt_results == 0); } #[cfg(target_os = "linux")] @@ -284,30 +281,30 @@ unsafe fn bind_udp_to_device(device_name: &str, address: &InetAddress) -> Result #[cfg(not(target_os = "linux"))] { fl = 0; - libc::setsockopt(s, libc::IPPROTO_IP.as_(), libc::IP_DONTFRAG.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + libc::setsockopt(s, libc::IPPROTO_IP.as_(), libc::IP_DONTFRAG.as_(), (&mut fl as *mut libc::c_int).cast(), std::mem::size_of::().as_()); } #[cfg(target_os = "linux")] { fl = libc::IP_PMTUDISC_DONT as c_int; - libc::setsockopt(s, libc::IPPROTO_IP.as_(), libc::IP_MTU_DISCOVER.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + libc::setsockopt(s, libc::IPPROTO_IP.as_(), libc::IP_MTU_DISCOVER.as_(), (&mut fl as *mut libc::c_int).cast(), std::mem::size_of::().as_()); } } if af == libc::AF_INET6 { fl = 0; - libc::setsockopt(s, libc::IPPROTO_IPV6.as_(), libc::IPV6_DONTFRAG.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()); + libc::setsockopt(s, libc::IPPROTO_IPV6.as_(), libc::IPV6_DONTFRAG.as_(), (&mut fl as *mut libc::c_int).cast(), std::mem::size_of::().as_()); } fl = 1048576; while fl >= 65536 { - if libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_RCVBUF.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()) == 0 { + if libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_RCVBUF.as_(), (&mut fl as *mut libc::c_int).cast(), std::mem::size_of::().as_()) == 0 { break; } fl -= 65536; } fl = 1048576; while fl >= 65536 { - if libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_SNDBUF.as_(), (&mut fl as *mut c_int).cast(), std::mem::size_of::().as_()) == 0 { + if libc::setsockopt(s, libc::SOL_SOCKET.as_(), libc::SO_SNDBUF.as_(), (&mut fl as *mut libc::c_int).cast(), std::mem::size_of::().as_()) == 0 { break; } fl -= 65536; diff --git a/zerotier-system-service/src/utils.rs b/zerotier-system-service/src/utils.rs index b63794f04..63c0feb8e 100644 --- a/zerotier-system-service/src/utils.rs +++ b/zerotier-system-service/src/utils.rs @@ -183,6 +183,11 @@ pub async fn parse_cli_identity(input: &str, validate: bool) -> Result String { + unsafe { std::ffi::CStr::from_ptr(libc::strerror(*libc::__error()).cast()).to_string_lossy().to_string() } +} + #[cfg(test)] mod tests { use crate::utils::ms_monotonic;