use std::{
net::{Ipv4Addr, SocketAddrV4, UdpSocket},
path::PathBuf,
sync::Once,
};
use crate::util::socket::UdpBufferSizeData;
use anyhow::Result;
use cfg_if::cfg_if;
use rustix::net::sockopt as RustixSO;
pub mod osx;
#[cfg(unix)]
pub mod unix; pub mod windows;
static_assertions::assert_cfg!(any(unix, windows), "This OS is not currently supported");
const TESTING_BUFFERS_MESSAGE: &str = r"ℹ️ For best performance, it is necessary to set the kernel UDP buffer size limits.
This program attempts to automatically set buffer sizes for itself,
but this usually requires the kernel limits to be configured appropriately.
Testing this system...";
cfg_if! {
if #[cfg(unix)] {
use rustix::fd::AsFd as SocketType;
pub use unix::UnixPlatform;
pub use UnixPlatform as Platform;
}
}
cfg_if! {
if #[cfg(windows)] {
use rustix::fd::AsSocket as SocketType;
pub use WindowsPlatform as Platform;
}
}
#[cfg(windows_or_dev)]
pub use windows::WindowsPlatform;
const fn buffer_size_fix(s: usize) -> usize {
if cfg!(linux) { s / 2 } else { s }
}
pub fn initialise_platform() -> Result<()> {
static ONCE: Once = Once::new();
ONCE.call_once(|| {
cfg_if! {
if #[cfg(windows)] {
let _ = rustix::net::wsa_startup().unwrap();
}
}
});
Ok(())
}
mod private {
pub trait SealedSocket: super::SocketType {}
impl SealedSocket for super::UdpSocket {}
}
pub trait SocketOptions: private::SealedSocket
where
Self: Sized,
{
fn get_sendbuf(&self) -> Result<usize> {
initialise_platform()?;
Ok(buffer_size_fix(RustixSO::socket_send_buffer_size(self)?))
}
fn set_sendbuf(&mut self, size: usize) -> Result<()> {
initialise_platform()?;
RustixSO::set_socket_send_buffer_size(self, size)?;
Ok(())
}
#[allow(clippy::used_underscore_binding)]
fn force_sendbuf(&mut self, _size: usize) -> Result<()> {
initialise_platform()?;
cfg_if! {
if #[cfg(linux)] {
RustixSO::set_socket_send_buffer_size_force(self, _size)?;
}
}
Ok(())
}
fn get_recvbuf(&self) -> Result<usize> {
initialise_platform()?;
Ok(buffer_size_fix(RustixSO::socket_recv_buffer_size(self)?))
}
fn set_recvbuf(&mut self, size: usize) -> Result<()> {
initialise_platform()?;
RustixSO::set_socket_recv_buffer_size(self, size)?;
Ok(())
}
#[allow(clippy::used_underscore_binding)]
fn force_recvbuf(&mut self, _size: usize) -> Result<()> {
initialise_platform()?;
cfg_if! {
if #[cfg(linux)] {
RustixSO::set_socket_recv_buffer_size_force(self, _size)?;
}
}
Ok(())
}
fn has_force_sendrecvbuf(&self) -> bool {
cfg!(linux)
}
}
impl SocketOptions for UdpSocket {}
fn test_udp_buffers(wanted_recv: u64, wanted_send: u64) -> anyhow::Result<UdpBufferSizeData> {
let mut socket = UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))?;
crate::util::socket::set_udp_buffer_sizes(
&mut socket,
Some(wanted_send.try_into()?),
Some(wanted_recv.try_into()?),
)
}
pub trait AbstractPlatform {
fn system_ssh_config() -> Option<PathBuf>;
fn user_ssh_config() -> Option<PathBuf>;
fn user_config_path_extra() -> Option<PathBuf>;
#[must_use]
fn user_config_paths() -> Vec<PathBuf> {
let mut paths = Vec::new();
if let Some(mut pb) = dirs::config_dir() {
pb.push("qcp");
pb.push(crate::config::BASE_CONFIG_FILENAME);
paths.push(pb);
}
if let Some(path) = Self::user_config_path_extra() {
paths.push(path);
}
paths
}
fn system_config_path() -> Option<PathBuf>;
fn system_ssh_dir_path() -> Option<PathBuf>;
fn help_buffers_mode(udp: u64) -> String;
#[must_use]
fn override_path_is_local(_path: &str) -> bool {
false
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod test {
use super::test_udp_buffers;
#[test]
fn test_buffers_small_ok() {
assert!(
test_udp_buffers(131_072, 131_072)
.unwrap()
.warning
.is_none()
);
}
#[test]
fn test_buffers_gigantic_err() {
let big = 2u64.pow(60);
let result = test_udp_buffers(big, big).unwrap();
eprintln!("{result:?}");
assert!(result.warning.is_some());
}
}