use std::io;
use std::net::SocketAddr;
use std::time::Duration;
use tokio::net::{TcpSocket, TcpStream};
pub(crate) async fn connect(addr: SocketAddr, timeout_ms: u64) -> io::Result<TcpStream> {
let socket = if addr.is_ipv4() {
TcpSocket::new_v4()?
} else {
TcpSocket::new_v6()?
};
#[cfg(windows)]
windows::final_rst(&socket, timeout_ms);
match tokio::time::timeout(Duration::from_millis(timeout_ms), socket.connect(addr)).await {
Ok(result) => result,
Err(_) => Err(io::Error::new(io::ErrorKind::TimedOut, "connect timed out")),
}
}
#[cfg(windows)]
mod windows {
use std::os::windows::io::AsRawSocket;
use tokio::net::TcpSocket;
use windows_sys::Win32::Networking::WinSock::{
WSAIoctl, SIO_TCP_INITIAL_RTO, SOCKET, TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS,
TCP_INITIAL_RTO_PARAMETERS,
};
pub(super) fn final_rst(socket: &TcpSocket, timeout_ms: u64) {
let params = TCP_INITIAL_RTO_PARAMETERS {
Rtt: rtt(timeout_ms),
MaxSynRetransmissions: TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS as u8,
};
let mut returned = 0u32;
unsafe {
WSAIoctl(
socket.as_raw_socket() as SOCKET,
SIO_TCP_INITIAL_RTO,
¶ms as *const _ as *const core::ffi::c_void,
std::mem::size_of::<TCP_INITIAL_RTO_PARAMETERS>() as u32,
std::ptr::null_mut(),
0,
&mut returned,
std::ptr::null_mut(),
None,
);
}
}
pub(super) fn rtt(timeout_ms: u64) -> u16 {
timeout_ms.clamp(1, 65_534) as u16
}
#[cfg(test)]
mod tests {
use super::rtt;
#[test]
fn rtt_stays_inside_the_values_windows_accepts() {
assert_eq!(rtt(0), 1);
assert_eq!(rtt(500), 500);
assert_eq!(rtt(65_535), 65_534);
assert_eq!(rtt(u64::MAX), 65_534);
}
}
}