#![allow(unsafe_code)]
use std::io;
#[cfg(unix)]
#[inline]
pub fn enable_tcp_nodelay<S: std::os::unix::io::AsRawFd>(stream: &S) -> io::Result<()> {
use std::os::unix::io::FromRawFd;
let fd = stream.as_raw_fd();
let sock = unsafe { socket2::Socket::from_raw_fd(fd) };
sock.set_nodelay(true)?;
std::mem::forget(sock); Ok(())
}
#[cfg(windows)]
#[inline]
pub fn enable_tcp_nodelay<S: std::os::windows::io::AsRawSocket>(stream: &S) -> io::Result<()> {
use std::os::windows::io::FromRawSocket;
let raw = stream.as_raw_socket();
let sock = unsafe { socket2::Socket::from_raw_socket(raw) };
sock.set_nodelay(true)?;
std::mem::forget(sock); Ok(())
}
#[cfg(not(any(unix, windows)))]
#[inline]
pub fn enable_tcp_nodelay<S>(_stream: &S) -> io::Result<()> {
Ok(())
}
#[cfg(unix)]
pub fn reuseport_listener(addr: std::net::SocketAddr) -> io::Result<std::net::TcpListener> {
use socket2::{Domain, Protocol, Socket, Type};
let domain = if addr.is_ipv4() {
Domain::IPV4
} else {
Domain::IPV6
};
let sock = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
sock.set_reuse_address(true)?;
sock.set_reuse_port(true)?;
sock.bind(&addr.into())?;
sock.listen(1024)?;
Ok(sock.into())
}
#[cfg(not(unix))]
pub fn reuseport_listener(_addr: std::net::SocketAddr) -> io::Result<std::net::TcpListener> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"SO_REUSEPORT is only supported on Unix",
))
}
#[cfg(unix)]
pub fn configure_socket_buffers<S: std::os::unix::io::AsRawFd>(
stream: &S,
sndbuf: i32,
rcvbuf: i32,
) -> io::Result<()> {
use std::os::unix::io::FromRawFd;
if sndbuf <= 0 && rcvbuf <= 0 {
return Ok(());
}
let fd = stream.as_raw_fd();
let sock = unsafe { socket2::Socket::from_raw_fd(fd) };
let result = (|| {
if sndbuf > 0 {
sock.set_send_buffer_size(sndbuf as usize)?;
}
if rcvbuf > 0 {
sock.set_recv_buffer_size(rcvbuf as usize)?;
}
Ok(())
})();
std::mem::forget(sock); result
}
#[cfg(windows)]
pub fn configure_socket_buffers<S: std::os::windows::io::AsRawSocket>(
stream: &S,
sndbuf: i32,
rcvbuf: i32,
) -> io::Result<()> {
use std::os::windows::io::FromRawSocket;
if sndbuf <= 0 && rcvbuf <= 0 {
return Ok(());
}
let raw = stream.as_raw_socket();
let sock = unsafe { socket2::Socket::from_raw_socket(raw) };
let result = (|| {
if sndbuf > 0 {
sock.set_send_buffer_size(sndbuf as usize)?;
}
if rcvbuf > 0 {
sock.set_recv_buffer_size(rcvbuf as usize)?;
}
Ok(())
})();
std::mem::forget(sock); result
}
#[cfg(not(any(unix, windows)))]
pub fn configure_socket_buffers<S>(_stream: &S, sndbuf: i32, rcvbuf: i32) -> io::Result<()> {
let _ = (sndbuf, rcvbuf);
Ok(())
}
#[cfg(unix)]
pub fn configure_tcp_keepalive<S: std::os::unix::io::AsRawFd>(
stream: &S,
keepalive: i32,
keepalive_cnt: i32,
keepalive_idle: i32,
keepalive_intvl: i32,
) -> io::Result<()> {
{
use std::os::unix::io::FromRawFd;
use std::time::Duration;
let fd = stream.as_raw_fd();
let sock = unsafe { socket2::Socket::from_raw_fd(fd) };
if keepalive >= 0 {
sock.set_keepalive(keepalive != 0)?;
}
if keepalive == 1 {
let mut params = socket2::TcpKeepalive::new();
if keepalive_idle > 0 {
params = params.with_time(Duration::from_secs(keepalive_idle as u64));
}
#[cfg(not(target_os = "openbsd"))]
if keepalive_intvl > 0 {
params = params.with_interval(Duration::from_secs(keepalive_intvl as u64));
}
#[cfg(any(target_os = "linux", target_os = "android"))]
if keepalive_cnt > 0 {
params = params.with_retries(keepalive_cnt as u32);
}
sock.set_tcp_keepalive(¶ms)?;
}
std::mem::forget(sock); Ok(())
}
}
#[cfg(windows)]
pub fn configure_tcp_keepalive<S: std::os::windows::io::AsRawSocket>(
stream: &S,
keepalive: i32,
keepalive_cnt: i32,
keepalive_idle: i32,
keepalive_intvl: i32,
) -> io::Result<()> {
let _ = keepalive_cnt;
use std::os::windows::io::FromRawSocket;
use std::time::Duration;
let raw = stream.as_raw_socket();
let sock = unsafe { socket2::Socket::from_raw_socket(raw) };
if keepalive >= 0 {
sock.set_keepalive(keepalive != 0)?;
}
if keepalive == 1 {
let mut params = socket2::TcpKeepalive::new();
if keepalive_idle > 0 {
params = params.with_time(Duration::from_secs(keepalive_idle as u64));
}
if keepalive_intvl > 0 {
params = params.with_interval(Duration::from_secs(keepalive_intvl as u64));
}
sock.set_tcp_keepalive(¶ms)?;
}
std::mem::forget(sock); Ok(())
}
#[cfg(not(any(unix, windows)))]
pub fn configure_tcp_keepalive<S>(
_stream: &S,
keepalive: i32,
keepalive_cnt: i32,
keepalive_idle: i32,
keepalive_intvl: i32,
) -> io::Result<()> {
let _ = (keepalive, keepalive_cnt, keepalive_idle, keepalive_intvl);
Ok(())
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
use std::net::{TcpListener, TcpStream};
#[test]
fn configure_socket_buffers_applies_requested_sizes() {
use socket2::Socket;
use std::os::unix::io::{AsRawFd, FromRawFd};
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let client = TcpStream::connect(addr).unwrap();
let requested = 256 * 1024;
configure_socket_buffers(&client, requested, requested).unwrap();
let fd = client.as_raw_fd();
let sock = unsafe { Socket::from_raw_fd(fd) };
let snd = sock.send_buffer_size().unwrap();
let rcv = sock.recv_buffer_size().unwrap();
std::mem::forget(sock);
assert!(
snd >= requested as usize,
"SO_SNDBUF not applied: got {snd}, requested {requested}"
);
assert!(
rcv >= requested as usize,
"SO_RCVBUF not applied: got {rcv}, requested {requested}"
);
}
#[test]
fn reuseport_allows_two_listeners_on_same_port() {
let l1 = reuseport_listener("127.0.0.1:0".parse().unwrap()).unwrap();
let addr = l1.local_addr().unwrap();
let l2 = reuseport_listener(addr).expect("second REUSEPORT bind on same port");
assert_eq!(
l1.local_addr().unwrap().port(),
l2.local_addr().unwrap().port()
);
}
#[test]
fn configure_socket_buffers_zero_is_noop() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let client = TcpStream::connect(addr).unwrap();
configure_socket_buffers(&client, 0, 0).unwrap();
configure_socket_buffers(&client, -1, -1).unwrap();
}
}