use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum AddressFamily {
Unix = libc::AF_UNIX as u8,
Inet = libc::AF_INET as u8,
Inet6 = libc::AF_INET6 as u8,
Netlink = libc::AF_NETLINK as u8,
Packet = libc::AF_PACKET as u8,
Vsock = 40, Tipc = 30, Xdp = 44, }
impl AddressFamily {
pub fn from_u8(value: u8) -> Option<Self> {
match value as i32 {
libc::AF_UNIX => Some(Self::Unix),
libc::AF_INET => Some(Self::Inet),
libc::AF_INET6 => Some(Self::Inet6),
libc::AF_NETLINK => Some(Self::Netlink),
libc::AF_PACKET => Some(Self::Packet),
40 => Some(Self::Vsock),
30 => Some(Self::Tipc),
44 => Some(Self::Xdp),
_ => None,
}
}
pub fn netid(&self) -> &'static str {
match self {
Self::Unix => "u_str",
Self::Inet => "tcp",
Self::Inet6 => "tcp6",
Self::Netlink => "nl",
Self::Packet => "p_raw",
Self::Vsock => "v_str",
Self::Tipc => "tipc",
Self::Xdp => "xdp",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Protocol {
Tcp,
Udp,
Sctp,
Dccp,
Mptcp,
Raw,
}
impl Protocol {
pub fn number(&self) -> u8 {
match self {
Self::Tcp => libc::IPPROTO_TCP as u8,
Self::Udp => libc::IPPROTO_UDP as u8,
Self::Sctp => 132,
Self::Dccp => 33,
Self::Mptcp => 6, Self::Raw => libc::IPPROTO_RAW as u8,
}
}
pub fn from_u8(value: u8) -> Option<Self> {
match value as i32 {
libc::IPPROTO_TCP => Some(Self::Tcp),
libc::IPPROTO_UDP => Some(Self::Udp),
132 => Some(Self::Sctp),
33 => Some(Self::Dccp),
libc::IPPROTO_RAW => Some(Self::Raw),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Tcp => "tcp",
Self::Udp => "udp",
Self::Sctp => "sctp",
Self::Dccp => "dccp",
Self::Mptcp => "mptcp",
Self::Raw => "raw",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum TcpState {
Unknown = 0,
Established = 1,
SynSent = 2,
SynRecv = 3,
FinWait1 = 4,
FinWait2 = 5,
TimeWait = 6,
Close = 7,
CloseWait = 8,
LastAck = 9,
Listen = 10,
Closing = 11,
NewSynRecv = 12,
BoundInactive = 13,
}
impl TcpState {
pub fn from_u8(value: u8) -> Self {
match value {
1 => Self::Established,
2 => Self::SynSent,
3 => Self::SynRecv,
4 => Self::FinWait1,
5 => Self::FinWait2,
6 => Self::TimeWait,
7 => Self::Close,
8 => Self::CloseWait,
9 => Self::LastAck,
10 => Self::Listen,
11 => Self::Closing,
12 => Self::NewSynRecv,
13 => Self::BoundInactive,
_ => Self::Unknown,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Unknown => "UNKNOWN",
Self::Established => "ESTAB",
Self::SynSent => "SYN-SENT",
Self::SynRecv => "SYN-RECV",
Self::FinWait1 => "FIN-WAIT-1",
Self::FinWait2 => "FIN-WAIT-2",
Self::TimeWait => "TIME-WAIT",
Self::Close => "UNCONN",
Self::CloseWait => "CLOSE-WAIT",
Self::LastAck => "LAST-ACK",
Self::Listen => "LISTEN",
Self::Closing => "CLOSING",
Self::NewSynRecv => "NEW-SYN-RECV",
Self::BoundInactive => "BOUND-INACTIVE",
}
}
pub fn mask(&self) -> u32 {
1 << (*self as u32)
}
pub fn connected_mask() -> u32 {
Self::all_mask()
& !(Self::Listen.mask()
| Self::Close.mask()
| Self::TimeWait.mask()
| Self::SynRecv.mask())
}
pub fn all_mask() -> u32 {
(1 << 14) - 1
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SocketState {
Tcp(TcpState),
Close,
Established,
Listen,
}
impl SocketState {
pub fn name(&self) -> &'static str {
match self {
Self::Tcp(state) => state.name(),
Self::Close => "UNCONN",
Self::Established => "ESTAB",
Self::Listen => "LISTEN",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum InetExtension {
MemInfo = 1,
Info = 2,
VegasInfo = 3,
Cong = 4,
Tos = 5,
TClass = 6,
SkMemInfo = 7,
Shutdown = 8,
}
impl InetExtension {
pub fn mask(&self) -> u8 {
1 << (*self as u8)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum UnixShow {
Name = 0x00000001,
Vfs = 0x00000002,
Peer = 0x00000004,
Icons = 0x00000008,
RqLen = 0x00000010,
MemInfo = 0x00000020,
Uid = 0x00000040,
}
impl UnixShow {
pub fn mask(&self) -> u32 {
*self as u32
}
pub fn combine(options: &[UnixShow]) -> u32 {
options.iter().fold(0, |acc, opt| acc | opt.mask())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TcpInfo {
pub state: u8,
pub ca_state: u8,
pub retransmits: u8,
pub probes: u8,
pub backoff: u8,
pub options: u8,
pub wscale: u8,
pub delivery_rate_app_limited: bool,
pub rto: u32,
pub rtt: u32,
pub rttvar: u32,
pub snd_mss: u32,
pub rcv_mss: u32,
pub unacked: u32,
pub sacked: u32,
pub lost: u32,
pub retrans: u32,
pub fackets: u32,
pub last_data_sent: u32,
pub last_ack_sent: u32,
pub last_data_recv: u32,
pub last_ack_recv: u32,
pub pmtu: u32,
pub rcv_ssthresh: u32,
pub snd_ssthresh: u32,
pub snd_cwnd: u32,
pub advmss: u32,
pub reordering: u32,
pub rcv_rtt: u32,
pub rcv_space: u32,
pub total_retrans: u32,
pub pacing_rate: u64,
pub max_pacing_rate: u64,
pub bytes_acked: u64,
pub bytes_received: u64,
pub segs_out: u32,
pub segs_in: u32,
pub notsent_bytes: u32,
pub min_rtt: u32,
pub data_segs_in: u32,
pub data_segs_out: u32,
pub delivery_rate: u64,
pub busy_time: u64,
pub rwnd_limited: u64,
pub sndbuf_limited: u64,
pub delivered: u32,
pub delivered_ce: u32,
pub bytes_sent: u64,
pub bytes_retrans: u64,
pub dsack_dups: u32,
pub reord_seen: u32,
pub rcv_ooopack: u32,
pub snd_wnd: u32,
}
impl TcpInfo {
pub fn format_ss(&self) -> String {
let mut parts = Vec::new();
if self.rtt > 0 {
let rtt_ms = self.rtt as f64 / 1000.0;
let rttvar_ms = self.rttvar as f64 / 1000.0;
parts.push(format!("rtt:{:.3}/{:.3}", rtt_ms, rttvar_ms));
}
if self.rto > 0 && self.rto != 200_000 {
parts.push(format!("rto:{}", self.rto / 1000));
}
if self.snd_mss > 0 {
parts.push(format!("mss:{}", self.snd_mss));
}
if self.snd_cwnd > 0 {
parts.push(format!("cwnd:{}", self.snd_cwnd));
}
if self.snd_ssthresh > 0 && self.snd_ssthresh < u32::MAX {
parts.push(format!("ssthresh:{}", self.snd_ssthresh));
}
if self.bytes_acked > 0 {
parts.push(format!("bytes_acked:{}", self.bytes_acked));
}
if self.bytes_received > 0 {
parts.push(format!("bytes_received:{}", self.bytes_received));
}
if self.segs_out > 0 {
parts.push(format!("segs_out:{}", self.segs_out));
}
if self.segs_in > 0 {
parts.push(format!("segs_in:{}", self.segs_in));
}
if self.retrans > 0 {
parts.push(format!("retrans:{}/{}", self.retrans, self.total_retrans));
} else if self.total_retrans > 0 {
parts.push(format!("retrans:0/{}", self.total_retrans));
}
if self.delivery_rate > 0 {
parts.push(format!(
"delivery_rate:{}{}",
format_rate_bps(self.delivery_rate * 8),
if self.delivery_rate_app_limited {
"app_limited"
} else {
""
}
));
}
if self.pacing_rate > 0 && self.pacing_rate < u64::MAX {
parts.push(format!(
"pacing_rate:{}",
format_rate_bps(self.pacing_rate * 8)
));
}
if self.min_rtt > 0 {
parts.push(format!("minrtt:{:.3}", self.min_rtt as f64 / 1000.0));
}
if self.rcv_space > 0 {
parts.push(format!("rcv_space:{}", self.rcv_space));
}
parts.join(" ")
}
pub fn rtt_str(&self) -> String {
if self.rtt > 0 {
format!(
"{:.3}/{:.3}",
self.rtt as f64 / 1000.0,
self.rttvar as f64 / 1000.0
)
} else {
String::new()
}
}
pub fn cwnd_str(&self) -> String {
if self.snd_cwnd > 0 {
format!("{}", self.snd_cwnd)
} else {
String::new()
}
}
pub fn delivery_rate_str(&self) -> String {
if self.delivery_rate > 0 {
format_rate_bps(self.delivery_rate * 8)
} else {
String::new()
}
}
pub fn pacing_rate_str(&self) -> String {
if self.pacing_rate > 0 && self.pacing_rate < u64::MAX {
format_rate_bps(self.pacing_rate * 8)
} else {
String::new()
}
}
pub fn snd_wscale(&self) -> u8 {
self.wscale >> 4
}
pub fn rcv_wscale(&self) -> u8 {
self.wscale & 0x0f
}
}
fn format_rate_bps(bps: u64) -> String {
if bps >= 1_000_000_000 {
format!("{:.1}Gbps", bps as f64 / 1_000_000_000.0)
} else if bps >= 1_000_000 {
format!("{:.1}Mbps", bps as f64 / 1_000_000.0)
} else if bps >= 1_000 {
format!("{:.1}Kbps", bps as f64 / 1_000.0)
} else {
format!("{}bps", bps)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MemInfo {
pub rmem_alloc: u32,
pub rcvbuf: u32,
pub wmem_alloc: u32,
pub sndbuf: u32,
pub fwd_alloc: u32,
pub wmem_queued: u32,
pub optmem: u32,
pub backlog: u32,
pub drops: u32,
}
impl MemInfo {
pub fn format_skmem(&self) -> String {
format!(
"skmem:(r{},rb{},t{},tb{},f{},w{},o{},bl{},d{})",
self.rmem_alloc,
self.rcvbuf,
self.wmem_alloc,
self.sndbuf,
self.fwd_alloc,
self.wmem_queued,
self.optmem,
self.backlog,
self.drops
)
}
pub fn format_skmem_compact(&self) -> String {
let mut parts = Vec::new();
if self.rmem_alloc > 0 {
parts.push(format!("r{}", self.rmem_alloc));
}
if self.rcvbuf > 0 {
parts.push(format!("rb{}", self.rcvbuf));
}
if self.wmem_alloc > 0 {
parts.push(format!("t{}", self.wmem_alloc));
}
if self.sndbuf > 0 {
parts.push(format!("tb{}", self.sndbuf));
}
if self.fwd_alloc > 0 {
parts.push(format!("f{}", self.fwd_alloc));
}
if self.wmem_queued > 0 {
parts.push(format!("w{}", self.wmem_queued));
}
if self.optmem > 0 {
parts.push(format!("o{}", self.optmem));
}
if self.backlog > 0 {
parts.push(format!("bl{}", self.backlog));
}
if self.drops > 0 {
parts.push(format!("d{}", self.drops));
}
if parts.is_empty() {
"skmem:()".to_string()
} else {
format!("skmem:({})", parts.join(","))
}
}
pub fn has_drops(&self) -> bool {
self.drops > 0
}
pub fn total_mem(&self) -> u64 {
self.rmem_alloc as u64 + self.wmem_alloc as u64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Timer {
Off,
On { expires_ms: u32, retrans: u8 },
Keepalive { expires_ms: u32, probes: u8 },
TimeWait { expires_ms: u32 },
Probe { expires_ms: u32, retrans: u8 },
}
impl Timer {
pub fn from_raw(timer: u8, expires: u32, retrans: u8) -> Self {
match timer {
0 => Self::Off,
1 => Self::On {
expires_ms: expires,
retrans,
},
2 => Self::Keepalive {
expires_ms: expires,
probes: retrans,
},
3 => Self::TimeWait {
expires_ms: expires,
},
4 => Self::Probe {
expires_ms: expires,
retrans,
},
_ => Self::Off,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SocketSummary {
pub tcp: TcpSummary,
pub udp: u32,
pub raw: u32,
pub unix: u32,
}
impl fmt::Display for SocketSummary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let total = self.tcp.total + self.udp + self.raw + self.unix;
writeln!(f, "Total: {total}")?;
writeln!(
f,
"TCP: {} (estab {}, closed {}, orphaned 0, timewait {})",
self.tcp.total, self.tcp.established, self.tcp.close, self.tcp.time_wait
)?;
writeln!(f, "UDP: {}", self.udp)?;
writeln!(f, "RAW: {}", self.raw)?;
write!(f, "UNIX: {}", self.unix)
}
}
#[derive(Debug)]
pub struct DestroyResult {
pub destroyed: u32,
pub errors: Vec<DestroyError>,
}
impl DestroyResult {
pub fn all_ok(&self) -> bool {
self.errors.is_empty()
}
}
#[derive(Debug)]
pub struct DestroyError {
pub socket: std::net::SocketAddr,
pub error: crate::netlink::Error,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TcpSummary {
pub total: u32,
pub established: u32,
pub syn_sent: u32,
pub syn_recv: u32,
pub fin_wait1: u32,
pub fin_wait2: u32,
pub time_wait: u32,
pub close: u32,
pub close_wait: u32,
pub last_ack: u32,
pub listen: u32,
pub closing: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_socket_summary_is_all_zeros() {
let summary = SocketSummary::default();
assert_eq!(summary.tcp.total, 0);
assert_eq!(summary.tcp.established, 0);
assert_eq!(summary.tcp.syn_sent, 0);
assert_eq!(summary.tcp.syn_recv, 0);
assert_eq!(summary.tcp.fin_wait1, 0);
assert_eq!(summary.tcp.fin_wait2, 0);
assert_eq!(summary.tcp.time_wait, 0);
assert_eq!(summary.tcp.close, 0);
assert_eq!(summary.tcp.close_wait, 0);
assert_eq!(summary.tcp.last_ack, 0);
assert_eq!(summary.tcp.listen, 0);
assert_eq!(summary.tcp.closing, 0);
assert_eq!(summary.udp, 0);
assert_eq!(summary.raw, 0);
assert_eq!(summary.unix, 0);
}
#[test]
fn display_format_matches_expected_output() {
let summary = SocketSummary {
tcp: TcpSummary {
total: 15,
established: 8,
syn_sent: 1,
syn_recv: 0,
fin_wait1: 0,
fin_wait2: 1,
time_wait: 3,
close: 2,
close_wait: 0,
last_ack: 0,
listen: 0,
closing: 0,
},
udp: 5,
raw: 1,
unix: 10,
};
let output = format!("{}", summary);
let lines: Vec<&str> = output.lines().collect();
assert_eq!(lines.len(), 5);
assert_eq!(lines[0], "Total: 31");
assert_eq!(
lines[1],
"TCP: 15 (estab 8, closed 2, orphaned 0, timewait 3)"
);
assert_eq!(lines[2], "UDP: 5");
assert_eq!(lines[3], "RAW: 1");
assert_eq!(lines[4], "UNIX: 10");
}
#[test]
fn display_total_is_sum_of_all_socket_types() {
let summary = SocketSummary {
tcp: TcpSummary {
total: 10,
..Default::default()
},
udp: 20,
raw: 3,
unix: 7,
};
let output = format!("{}", summary);
let first_line = output.lines().next().unwrap();
assert_eq!(first_line, "Total: 40");
}
}