use std::time::Duration;
use tokio::net::TcpStream;
#[derive(Default, Debug, Clone)]
pub struct TcpInfo {
pub rtt: Option<Duration>,
pub rttvar: Option<Duration>,
pub retransmits: Option<u32>,
pub cwnd: Option<u32>,
pub snd_mss: Option<u32>,
}
#[cfg(unix)]
pub struct TcpInfoProbe(std::os::unix::io::OwnedFd);
#[cfg(not(unix))]
pub struct TcpInfoProbe;
impl TcpInfoProbe {
pub fn capture(stream: &TcpStream) -> (Option<TcpInfo>, Option<Self>) {
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let raw = stream.as_raw_fd();
let now = sample_fd(raw);
let dup_fd = unsafe { libc::dup(raw) };
let probe = if dup_fd >= 0 {
use std::os::unix::io::FromRawFd;
Some(TcpInfoProbe(unsafe {
std::os::unix::io::OwnedFd::from_raw_fd(dup_fd)
}))
} else {
None
};
(now, probe)
}
#[cfg(not(unix))]
{
let _ = stream;
(None, None)
}
}
#[allow(clippy::unused_self)]
pub fn sample(&self) -> Option<TcpInfo> {
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
sample_fd(self.0.as_raw_fd())
}
#[cfg(not(unix))]
{
None
}
}
}
#[cfg(target_os = "linux")]
fn sample_fd(fd: std::os::unix::io::RawFd) -> Option<TcpInfo> {
unsafe {
let mut info: libc::tcp_info = std::mem::zeroed();
let mut len = std::mem::size_of::<libc::tcp_info>() as libc::socklen_t;
let r = libc::getsockopt(
fd,
libc::IPPROTO_TCP,
libc::TCP_INFO,
&mut info as *mut _ as *mut libc::c_void,
&mut len,
);
if r != 0 {
return None;
}
Some(TcpInfo {
rtt: Some(Duration::from_micros(info.tcpi_rtt as u64)),
rttvar: Some(Duration::from_micros(info.tcpi_rttvar as u64)),
retransmits: Some(info.tcpi_total_retrans),
cwnd: Some(info.tcpi_snd_cwnd),
snd_mss: Some(info.tcpi_snd_mss),
})
}
}
#[cfg(target_os = "macos")]
fn sample_fd(fd: std::os::unix::io::RawFd) -> Option<TcpInfo> {
unsafe {
let mut info: libc::tcp_connection_info = std::mem::zeroed();
let mut len = std::mem::size_of::<libc::tcp_connection_info>() as libc::socklen_t;
let r = libc::getsockopt(
fd,
libc::IPPROTO_TCP,
libc::TCP_CONNECTION_INFO,
&mut info as *mut _ as *mut libc::c_void,
&mut len,
);
if r != 0 {
return None;
}
Some(TcpInfo {
rtt: Some(Duration::from_millis(info.tcpi_srtt as u64)),
rttvar: Some(Duration::from_millis(info.tcpi_rttvar as u64)),
retransmits: Some(info.tcpi_rxretransmitpackets as u32),
cwnd: Some(info.tcpi_snd_cwnd),
snd_mss: Some(info.tcpi_maxseg),
})
}
}
#[cfg(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
fn sample_fd(_fd: std::os::unix::io::RawFd) -> Option<TcpInfo> {
None
}
#[derive(Default, Debug, Clone)]
pub struct TcpInfoDelta {
pub retransmits_during: Option<u32>,
pub rtt_final: Option<Duration>,
pub cwnd_final: Option<u32>,
}
impl TcpInfoDelta {
pub fn compute(post_connect: Option<&TcpInfo>, final_: Option<&TcpInfo>) -> Option<Self> {
let f = final_?;
let retransmits_during = match (post_connect.and_then(|p| p.retransmits), f.retransmits) {
(Some(start), Some(end)) => Some(end.saturating_sub(start)),
_ => None,
};
Some(Self {
retransmits_during,
rtt_final: f.rtt,
cwnd_final: f.cwnd,
})
}
}