mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-10 06:23:44 +02:00
Fix binding and port over BSD/macOS version of check for IPv6 temporary addresses.
This commit is contained in:
parent
fd00642ec1
commit
51817ed557
6 changed files with 122 additions and 47 deletions
|
@ -56,7 +56,7 @@ impl DataDir {
|
|||
|
||||
/// Load identity.secret from data directory.
|
||||
pub async fn load_identity(&self) -> std::io::Result<Identity> {
|
||||
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()));
|
||||
}
|
||||
|
|
70
zerotier-system-service/src/ipv6.rs
Normal file
70
zerotier-system-service/src/ipv6.rs
Normal file
|
@ -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<i32> = 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::<libc::sockaddr_in6>());
|
||||
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;
|
|
@ -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;
|
||||
|
|
|
@ -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<HashMap<u16, BoundUdpPort>>,
|
||||
pub udp_sockets: parking_lot::RwLock<HashMap<u16, BoundUdpPort>>,
|
||||
pub num_listeners_per_socket: usize,
|
||||
_core: Option<NetworkHypervisor<Self>>,
|
||||
}
|
||||
|
||||
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<Self>, port: u16, interface_prefix_blacklist: &Vec<String>, cidr_blacklist: &Vec<InetAddress>) -> Option<Vec<(LocalInterface, InetAddress, std::io::Error)>> {
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
@ -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<tokio::net::UdpSocket>,
|
||||
|
||||
/// 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<Vec<tokio::task::JoinHandle<()>>>,
|
||||
|
||||
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::<c_int>().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::<libc::c_int>().as_()) };
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos"))]
|
||||
|
@ -187,28 +178,29 @@ impl BoundUdpPort {
|
|||
pub fn update_bindings(&mut self, interface_prefix_blacklist: &Vec<String>, cidr_blacklist: &Vec<InetAddress>) -> (Vec<(LocalInterface, InetAddress, std::io::Error)>, Vec<Arc<BoundUdpSocket>>) {
|
||||
let mut existing_bindings: HashMap<LocalInterface, HashMap<InetAddress, Arc<BoundUdpSocket>>> = 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::<c_int>().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::<c_int>().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::<libc::c_int>().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::<libc::c_int>().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::<c_int>().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::<libc::c_int>().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::<c_int>().as_());
|
||||
libc::setsockopt(s, libc::IPPROTO_IP.as_(), libc::IP_DONTFRAG.as_(), (&mut fl as *mut libc::c_int).cast(), std::mem::size_of::<libc::c_int>().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::<c_int>().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::<libc::c_int>().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::<c_int>().as_());
|
||||
libc::setsockopt(s, libc::IPPROTO_IPV6.as_(), libc::IPV6_DONTFRAG.as_(), (&mut fl as *mut libc::c_int).cast(), std::mem::size_of::<libc::c_int>().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::<c_int>().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::<libc::c_int>().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::<c_int>().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::<libc::c_int>().as_()) == 0 {
|
||||
break;
|
||||
}
|
||||
fl -= 65536;
|
||||
|
|
|
@ -183,6 +183,11 @@ pub async fn parse_cli_identity(input: &str, validate: bool) -> Result<Identity,
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn c_strerror() -> 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;
|
||||
|
|
Loading…
Add table
Reference in a new issue