use std::io;
use std::net::TcpStream;
use std::time::Duration;
use super::TcpSocket;
use super::opt::{self, IPPROTO_TCP, Int, SO_KEEPALIVE, SOL_SOCKET};
use crate::io::raw::Raw;
use crate::io::{Handle, IoObject};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TcpKeepalive {
pub time: Option<Duration>,
pub interval: Option<Duration>,
pub retries: Option<u32>,
}
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "netbsd"
))]
const KEEPALIVE: Option<[Int; 3]> =
Some([libc::TCP_KEEPIDLE, libc::TCP_KEEPINTVL, libc::TCP_KEEPCNT]);
#[cfg(target_vendor = "apple")]
const KEEPALIVE: Option<[Int; 3]> =
Some([libc::TCP_KEEPALIVE, libc::TCP_KEEPINTVL, libc::TCP_KEEPCNT]);
#[cfg(windows)]
const KEEPALIVE: Option<[Int; 3]> = {
use crate::syscore::windows as win;
Some([win::TCP_KEEPALIVE, win::TCP_KEEPINTVL, win::TCP_KEEPCNT])
};
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "netbsd",
target_vendor = "apple",
windows
)))]
const KEEPALIVE: Option<[Int; 3]> = None;
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(any(target_arch = "sparc", target_arch = "sparc64"))
))]
const SO_INCOMING_CPU: Int = 49;
#[cfg(all(
any(target_os = "linux", target_os = "android"),
any(target_arch = "sparc", target_arch = "sparc64")
))]
const SO_INCOMING_CPU: Int = 0x33;
const SECOND: Duration = Duration::from_secs(1);
fn secs(time: Duration, what: &str) -> io::Result<Int> {
opt::int(opt::whole(time, SECOND), what)
}
fn set_keepalive_timing(raw: Raw, keepalive: TcpKeepalive) -> io::Result<()> {
let [idle, interval, count] =
KEEPALIVE.ok_or_else(|| opt::unsupported("TCP keepalive timing"))?;
if let Some(time) = keepalive.time {
opt::set(raw, IPPROTO_TCP, idle, secs(time, "keepalive time")?)?;
}
if let Some(time) = keepalive.interval {
opt::set(
raw,
IPPROTO_TCP,
interval,
secs(time, "keepalive interval")?,
)?;
}
if let Some(retries) = keepalive.retries {
opt::set_count(raw, IPPROTO_TCP, count, retries, "keepalive retry count")?;
}
opt::set_flag(raw, SOL_SOCKET, SO_KEEPALIVE, true)
}
fn keepalive_timing(raw: Raw) -> io::Result<TcpKeepalive> {
let [idle, interval, count] =
KEEPALIVE.ok_or_else(|| opt::unsupported("TCP keepalive timing"))?;
let secs = |name| opt::count(raw, IPPROTO_TCP, name).map(|s| Duration::from_secs(s.into()));
Ok(TcpKeepalive {
time: Some(secs(idle)?),
interval: Some(secs(interval)?),
retries: Some(opt::count(raw, IPPROTO_TCP, count)?),
})
}
fn set_user_timeout(raw: Raw, timeout: Option<Duration>) -> io::Result<()> {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
let ms = timeout.map_or(0, |t| opt::whole(t, Duration::from_millis(1)));
opt::set(
raw,
IPPROTO_TCP,
libc::TCP_USER_TIMEOUT,
opt::int(ms, "user timeout")?,
)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
let _ = (raw, timeout);
Err(opt::unsupported("TCP_USER_TIMEOUT"))
}
}
fn user_timeout(raw: Raw) -> io::Result<Option<Duration>> {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
let ms = opt::count(raw, IPPROTO_TCP, libc::TCP_USER_TIMEOUT)?;
Ok((ms > 0).then(|| Duration::from_millis(ms.into())))
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
let _ = raw;
Err(opt::unsupported("TCP_USER_TIMEOUT"))
}
}
macro_rules! tcp_options {
($($ty:ty => $raw:ident),* $(,)?) => {$(
impl $ty {
pub fn set_keepalive_timing(&self, keepalive: TcpKeepalive) -> io::Result<()> {
set_keepalive_timing(self.$raw(), keepalive)
}
pub fn keepalive_timing(&self) -> io::Result<TcpKeepalive> {
keepalive_timing(self.$raw())
}
pub fn set_user_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
set_user_timeout(self.$raw(), timeout)
}
pub fn user_timeout(&self) -> io::Result<Option<Duration>> {
user_timeout(self.$raw())
}
}
)*};
}
tcp_options!(TcpSocket => raw, Handle<TcpStream> => raw_io);
impl TcpSocket {
pub fn set_incoming_cpu(&self, cpu: u32) -> io::Result<()> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return opt::set_count(self.raw(), SOL_SOCKET, SO_INCOMING_CPU, cpu, "CPU");
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
let _ = cpu;
Err(opt::unsupported("SO_INCOMING_CPU"))
}
}
pub fn incoming_cpu(&self) -> io::Result<Option<u32>> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return opt::get::<Int>(self.raw(), SOL_SOCKET, SO_INCOMING_CPU)
.map(|cpu| u32::try_from(cpu).ok());
#[cfg(not(any(target_os = "linux", target_os = "android")))]
Err(opt::unsupported("SO_INCOMING_CPU"))
}
pub fn set_fastopen(&self, queue: u32) -> io::Result<()> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return opt::set_count(
self.raw(),
IPPROTO_TCP,
libc::TCP_FASTOPEN,
queue,
"fast open queue",
);
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
return opt::set_flag(self.raw(), IPPROTO_TCP, libc::TCP_FASTOPEN, queue > 0);
#[cfg(windows)]
return opt::set_flag(
self.raw(),
IPPROTO_TCP,
crate::syscore::windows::TCP_FASTOPEN,
queue > 0,
);
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_vendor = "apple",
target_os = "freebsd",
windows
)))]
{
let _ = queue;
Err(opt::unsupported("TCP_FASTOPEN"))
}
}
pub fn fastopen(&self) -> io::Result<u32> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return opt::count(self.raw(), IPPROTO_TCP, libc::TCP_FASTOPEN);
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
return opt::flag(self.raw(), IPPROTO_TCP, libc::TCP_FASTOPEN).map(u32::from);
#[cfg(windows)]
return opt::flag(
self.raw(),
IPPROTO_TCP,
crate::syscore::windows::TCP_FASTOPEN,
)
.map(u32::from);
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_vendor = "apple",
target_os = "freebsd",
windows
)))]
Err(opt::unsupported("TCP_FASTOPEN"))
}
pub fn set_defer_accept(&self, wait: Option<Duration>) -> io::Result<()> {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
let secs = match wait {
Some(wait) => secs(wait, "deferred accept's wait")?,
None => 0,
};
opt::set(self.raw(), IPPROTO_TCP, libc::TCP_DEFER_ACCEPT, secs)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
let _ = wait;
Err(opt::unsupported("TCP_DEFER_ACCEPT"))
}
}
pub fn defer_accept(&self) -> io::Result<Option<Duration>> {
#[cfg(any(target_os = "linux", target_os = "android"))]
{
let secs = opt::count(self.raw(), IPPROTO_TCP, libc::TCP_DEFER_ACCEPT)?;
Ok((secs > 0).then(|| Duration::from_secs(secs.into())))
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
Err(opt::unsupported("TCP_DEFER_ACCEPT"))
}
}
impl Handle<TcpStream> {
pub fn set_quickack(&self, quickack: bool) -> io::Result<()> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return opt::set_flag(self.raw_io(), IPPROTO_TCP, libc::TCP_QUICKACK, quickack);
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
let _ = quickack;
Err(opt::unsupported("TCP_QUICKACK"))
}
}
pub fn quickack(&self) -> io::Result<bool> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return opt::flag(self.raw_io(), IPPROTO_TCP, libc::TCP_QUICKACK);
#[cfg(not(any(target_os = "linux", target_os = "android")))]
Err(opt::unsupported("TCP_QUICKACK"))
}
}