1use std::fmt::{self, Display};
15use std::net::SocketAddr;
16
17use crate::kernel::{Kernel, ListenState, Socket, Tcb, TcpState, Type};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Netstat {
21 pub entries: Vec<NetstatEntry>,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct NetstatEntry {
26 pub proto: Proto,
27 pub recv_q: usize,
28 pub send_q: usize,
29 pub local: SocketAddr,
30 pub peer: Option<SocketAddr>,
32 pub state: Option<NetstatState>,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum Proto {
38 Tcp,
39 Udp,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum NetstatState {
46 Listen,
47 SynSent,
48 SynReceived,
49 Established,
50 FinWait1,
51 FinWait2,
52 CloseWait,
53 LastAck,
54 Closing,
55 Closed,
60}
61
62impl From<TcpState> for NetstatState {
63 fn from(s: TcpState) -> Self {
64 match s {
65 TcpState::SynSent => NetstatState::SynSent,
66 TcpState::SynReceived => NetstatState::SynReceived,
67 TcpState::Established => NetstatState::Established,
68 TcpState::FinWait1 => NetstatState::FinWait1,
69 TcpState::FinWait2 => NetstatState::FinWait2,
70 TcpState::CloseWait => NetstatState::CloseWait,
71 TcpState::LastAck => NetstatState::LastAck,
72 TcpState::Closing => NetstatState::Closing,
73 TcpState::Closed => NetstatState::Closed,
74 }
75 }
76}
77
78pub fn snapshot(kernel: &Kernel) -> Netstat {
79 let mut entries = Vec::new();
80 for (_fd, sock) in kernel.sockets() {
81 if let Some(entry) = entry_for(sock) {
82 entries.push(entry);
83 }
84 }
85 Netstat { entries }
86}
87
88fn entry_for(sock: &Socket) -> Option<NetstatEntry> {
89 let bound = sock.bound.as_ref()?;
90 let local = SocketAddr::new(bound.local_addr, bound.local_port);
91
92 match sock.ty {
93 Type::Stream => tcp_entry(sock, local),
94 Type::Dgram => Some(udp_entry(sock, local)),
95 Type::SeqPacket => None,
96 }
97}
98
99fn tcp_entry(sock: &Socket, local: SocketAddr) -> Option<NetstatEntry> {
100 if let Some(tcb) = sock.tcb.as_ref() {
101 if tcb.state == TcpState::Closed {
107 return None;
108 }
109 return Some(tcb_entry(tcb, local));
110 }
111 if let Some(listen) = sock.listen.as_ref() {
112 return Some(listen_entry(listen, local));
113 }
114 None
115}
116
117fn tcb_entry(tcb: &Tcb, local: SocketAddr) -> NetstatEntry {
118 let send_q = tcb.send_buf.len();
121 let recv_q = tcb.recv_buf.len();
122 NetstatEntry {
123 proto: Proto::Tcp,
124 recv_q,
125 send_q,
126 local,
127 peer: Some(tcb.peer),
128 state: Some(NetstatState::from(tcb.state)),
129 }
130}
131
132fn listen_entry(listen: &ListenState, local: SocketAddr) -> NetstatEntry {
133 NetstatEntry {
138 proto: Proto::Tcp,
139 recv_q: listen.ready.len(),
140 send_q: listen.backlog,
141 local,
142 peer: None,
143 state: Some(NetstatState::Listen),
144 }
145}
146
147fn udp_entry(sock: &Socket, local: SocketAddr) -> NetstatEntry {
148 let recv_q: usize = sock.recv_queue.iter().map(|(_, b)| b.len()).sum();
149 NetstatEntry {
150 proto: Proto::Udp,
151 recv_q,
152 send_q: 0,
153 local,
154 peer: None,
155 state: None,
156 }
157}
158
159impl Display for Proto {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 f.write_str(match self {
162 Proto::Tcp => "tcp",
163 Proto::Udp => "udp",
164 })
165 }
166}
167
168impl Display for NetstatState {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 f.write_str(match self {
171 NetstatState::Listen => "LISTEN",
172 NetstatState::SynSent => "SYN_SENT",
173 NetstatState::SynReceived => "SYN_RCVD",
174 NetstatState::Established => "ESTABLISHED",
175 NetstatState::FinWait1 => "FIN_WAIT1",
176 NetstatState::FinWait2 => "FIN_WAIT2",
177 NetstatState::CloseWait => "CLOSE_WAIT",
178 NetstatState::LastAck => "LAST_ACK",
179 NetstatState::Closing => "CLOSING",
180 NetstatState::Closed => "CLOSED",
181 })
182 }
183}
184
185impl Display for Netstat {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 const HEADERS: [&str; 6] = [
190 "Proto",
191 "Recv-Q",
192 "Send-Q",
193 "Local Address",
194 "Foreign Address",
195 "State",
196 ];
197
198 let rows: Vec<[String; 6]> = self
199 .entries
200 .iter()
201 .map(|e| {
202 [
203 e.proto.to_string(),
204 e.recv_q.to_string(),
205 e.send_q.to_string(),
206 e.local.to_string(),
207 e.peer
208 .map(|p| p.to_string())
209 .unwrap_or_else(|| "*:*".into()),
210 e.state.map(|s| s.to_string()).unwrap_or_default(),
211 ]
212 })
213 .collect();
214
215 let mut widths = HEADERS.map(str::len);
216 for row in &rows {
217 for (i, cell) in row.iter().enumerate() {
218 widths[i] = widths[i].max(cell.len());
219 }
220 }
221
222 write_row(f, &HEADERS.map(String::from), &widths)?;
223 for row in &rows {
224 write_row(f, row, &widths)?;
225 }
226 Ok(())
227 }
228}
229
230fn write_row(f: &mut fmt::Formatter<'_>, row: &[String; 6], widths: &[usize; 6]) -> fmt::Result {
231 for (i, cell) in row.iter().enumerate() {
235 if i > 0 {
236 f.write_str(" ")?;
237 }
238 let w = widths[i];
239 let right_align = matches!(i, 1 | 2);
240 if i == row.len() - 1 {
241 f.write_str(cell)?;
242 } else if right_align {
243 write!(f, "{cell:>w$}")?;
244 } else {
245 write!(f, "{cell:<w$}")?;
246 }
247 }
248 writeln!(f)
249}