Fix binding and port over BSD/macOS version of check for IPv6 temporary addresses.

This commit is contained in:
Adam Ierymenko 2022-06-17 15:07:41 -04:00
parent fd00642ec1
commit 51817ed557
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
6 changed files with 122 additions and 47 deletions

View file

@ -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()));
}

View 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;

View file

@ -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;

View file

@ -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;
}
_ => {}

View file

@ -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;

View file

@ -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;