Skip to main content

netstack3_tcp/state/
info.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::fmt::Debug;
6use core::time::Duration;
7
8use derivative::Derivative;
9use netstack3_base::{Inspectable, Inspector, Instant};
10
11use crate::internal::buffer::{ReceiveBuffer, SendBuffer};
12use crate::internal::congestion::LossRecoveryMode;
13use crate::internal::counters::TcpCountersWithSocketInner;
14use crate::internal::state::{
15    CloseWait, Closed, Closing, Established, FinWait1, FinWait2, LastAck, Listen, Recv, RecvParams,
16    Send, State, SynRcvd, SynSent, TimeWait,
17};
18
19/// Information about a TCP socket.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct TcpSocketInfo<I> {
22    /// The current state of the TCP state machine.
23    pub state: netstack3_base::TcpSocketState,
24    /// The congestion control state.
25    pub ca_state: CongestionControlState,
26    /// The current RTO.
27    pub rto: Option<Duration>,
28    /// The estimated RTT.
29    pub rtt: Option<Duration>,
30    /// The RTT variance.
31    pub rtt_var: Option<Duration>,
32    /// The slow start threshold.
33    pub snd_ssthresh: u32,
34    /// The congestion window.
35    pub snd_cwnd: u32,
36    /// The number of retransmissions.
37    pub retransmits: u64,
38    /// Timestamp of the last ACK received.
39    pub last_ack_recv: Option<I>,
40    /// Segments sent.
41    pub segs_out: u64,
42    /// Segments received.
43    pub segs_in: u64,
44    /// The sender maximum segment size.
45    pub snd_mss: Option<u32>,
46    /// The receiver maximum segment size.
47    pub rcv_mss: Option<u32>,
48    /// Timestamp of the last data sent.
49    pub last_data_sent: Option<I>,
50}
51
52impl<I: Instant> Inspectable for TcpSocketInfo<I> {
53    fn record<II: Inspector>(&self, inspector: &mut II) {
54        let Self {
55            ca_state,
56            rto,
57            rtt,
58            rtt_var,
59            snd_ssthresh,
60            snd_cwnd,
61            last_ack_recv,
62            last_data_sent,
63            snd_mss,
64            rcv_mss,
65
66            // Already recorded by the caller.
67            state: _,
68            // These metrics are exported under the`Counters` inspect node.
69            retransmits: _,
70            segs_out: _,
71            segs_in: _,
72        } = self;
73
74        inspector.record_debug("CongestionControlState", ca_state);
75        if let Some(rto) = rto {
76            inspector.record_uint("RtoMs", u64::try_from(rto.as_millis()).unwrap_or(u64::MAX));
77        }
78        if let Some(rtt) = rtt {
79            inspector.record_uint("RttMs", u64::try_from(rtt.as_millis()).unwrap_or(u64::MAX));
80        }
81        if let Some(rtt_var) = rtt_var {
82            inspector
83                .record_uint("RttVarMs", u64::try_from(rtt_var.as_millis()).unwrap_or(u64::MAX));
84        }
85        inspector.record_uint("SndSsthresh", *snd_ssthresh);
86        inspector.record_uint("SndCwnd", *snd_cwnd);
87        if let Some(last_ack_recv) = last_ack_recv {
88            inspector.record_inspectable_value("LastAckRecv", last_ack_recv);
89        }
90        if let Some(last_data_sent) = last_data_sent {
91            inspector.record_inspectable_value("LastDataSent", last_data_sent);
92        }
93        if let Some(snd_mss) = snd_mss {
94            inspector.record_uint("SndMss", *snd_mss);
95        }
96        if let Some(rcv_mss) = rcv_mss {
97            inspector.record_uint("RcvMss", *rcv_mss);
98        }
99    }
100}
101
102/// The state of congestion control.
103#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
104pub enum CongestionControlState {
105    /// Normal state, no congestion detected.
106    #[default]
107    Open,
108    /// Disorder detected (e.g. DUP-ACKs).
109    Disorder,
110    /// We received an Explicit Congestion Notification.
111    CongestionWindowReduced,
112    /// In recovery (Fast Recovery).
113    Recovery,
114    /// Loss detected (RTO).
115    Loss,
116}
117
118/// Helper struct to hold parameters extracted from [`Send`] that will end
119/// up in [`TcpSocketInfo`].
120#[derive(Debug, Clone, Derivative)]
121#[derivative(Default(bound = ""))]
122#[cfg_attr(test, derive(PartialEq, Eq))]
123pub(crate) struct SendInfo<I> {
124    rto: Option<Duration>,
125    rtt: Option<Duration>,
126    rtt_var: Option<Duration>,
127    snd_ssthresh: u32,
128    snd_cwnd: u32,
129    snd_mss: Option<u32>,
130    last_data_sent: Option<I>,
131    ca_state: CongestionControlState,
132}
133
134impl<I: Instant> SendInfo<I> {
135    pub(super) fn from_send<S: SendBuffer, const FIN: bool>(snd: &Send<I, S, FIN>) -> Self {
136        let cc = &snd.congestion_control;
137        let est = &snd.rtt_estimator;
138
139        Self {
140            snd_cwnd: cc.inspect_cwnd().cwnd(),
141            snd_ssthresh: cc.slow_start_threshold(),
142            ca_state: match cc.inspect_loss_recovery_mode() {
143                Some(LossRecoveryMode::FastRecovery) => CongestionControlState::Recovery,
144                Some(LossRecoveryMode::SackRecovery) => CongestionControlState::Recovery,
145                None => CongestionControlState::Open,
146            },
147            rtt: est.srtt(),
148            rtt_var: est.rtt_var(),
149            rto: Some(est.rto().into()),
150            snd_mss: Some(u32::from(cc.mss())),
151            last_data_sent: snd.last_data_sent,
152        }
153    }
154}
155
156/// Helper struct to hold parameters extracted from [`Recv`] that will end
157/// up in [`TcpSocketInfo`].
158#[derive(Debug, Clone, Derivative)]
159#[derivative(Default(bound = ""))]
160#[cfg_attr(test, derive(PartialEq, Eq))]
161struct RecvInfo<I> {
162    last_ack_recv: Option<I>,
163    rcv_mss: Option<u32>,
164}
165
166impl<I: Instant> RecvInfo<I> {
167    fn from_recv<R: ReceiveBuffer>(rcv: &Recv<I, R>) -> Self {
168        Self { last_ack_recv: rcv.last_segment_at, rcv_mss: Some(u32::from(rcv.mss)) }
169    }
170
171    fn from_recv_params(rcv: &RecvParams<I>) -> Self {
172        let RecvParams { last_ack_recv, ack: _, wnd_scale: _, wnd: _, ts_opt: _ } = rcv;
173        Self { last_ack_recv: *last_ack_recv, rcv_mss: None }
174    }
175}
176
177/// Helper struct to hold parameters extracted from socket counters that
178/// will end up in [`TcpSocketInfo`].
179struct CounterParams {
180    retransmits: u64,
181    segs_out: u64,
182    segs_in: u64,
183}
184
185impl CounterParams {
186    fn from_counters(counters: &TcpCountersWithSocketInner) -> Self {
187        Self {
188            retransmits: counters.retransmits.get(),
189            segs_out: counters.segments_sent.get(),
190            segs_in: counters.received_segments_dispatched.get(),
191        }
192    }
193}
194
195impl<I: Instant> TcpSocketInfo<I> {
196    /// Constructs a [`TcpSocketInfo`] from just the state machine state and
197    /// counters. This is useful when the full state machine doesn't exist.
198    pub(crate) fn from_partial_state(
199        state: netstack3_base::TcpSocketState,
200        counters: &TcpCountersWithSocketInner,
201    ) -> Self {
202        let SendInfo {
203            rto,
204            rtt,
205            rtt_var,
206            snd_ssthresh,
207            snd_cwnd,
208            last_data_sent,
209            ca_state,
210            snd_mss: _,
211        } = SendInfo::default();
212        let RecvInfo { last_ack_recv, rcv_mss: _ } = RecvInfo::default();
213        let CounterParams { retransmits, segs_out, segs_in } =
214            CounterParams::from_counters(counters);
215
216        Self {
217            state,
218            retransmits,
219            segs_out,
220            ca_state,
221            rto,
222            rtt,
223            rtt_var,
224            snd_ssthresh,
225            snd_cwnd,
226            last_ack_recv,
227            segs_in,
228            snd_mss: None,
229            rcv_mss: None,
230            last_data_sent,
231        }
232    }
233
234    /// Constructs a [`TcpSocketInfo`] from the full state machine state
235    /// and counters.
236    pub(crate) fn from_full_state<R, S, ActiveOpen>(
237        state: &State<I, R, S, ActiveOpen>,
238        counters: &TcpCountersWithSocketInner,
239    ) -> Self
240    where
241        R: ReceiveBuffer,
242        S: SendBuffer,
243    {
244        let (state, send_params, recv_params) = match state {
245            State::Closed(Closed { reason: _ }) => {
246                (netstack3_base::TcpSocketState::Close, SendInfo::default(), RecvInfo::default())
247            }
248            State::Listen(Listen {
249                iss: _,
250                timestamp_offset: _,
251                buffer_sizes: _,
252                device_mss: _,
253                default_mss: _,
254                user_timeout: _,
255            }) => {
256                (netstack3_base::TcpSocketState::Listen, SendInfo::default(), RecvInfo::default())
257            }
258            State::SynSent(SynSent {
259                iss: _,
260                timestamp: _,
261                retrans_timer: _,
262                active_open: _,
263                buffer_sizes: _,
264                device_mss: _,
265                default_mss: _,
266                rcv_wnd_scale: _,
267                ts_opt: _,
268            }) => {
269                (netstack3_base::TcpSocketState::SynSent, SendInfo::default(), RecvInfo::default())
270            }
271            State::SynRcvd(SynRcvd {
272                iss: _,
273                irs: _,
274                timestamp: _,
275                retrans_timer: _,
276                simultaneous_open: _,
277                buffer_sizes: _,
278                smss: _,
279                rcv_wnd_scale: _,
280                snd_wnd_scale: _,
281                sack_permitted: _,
282                // NB: This is of type RecvParams (unrelated), not Recv.
283                rcv: _,
284            }) => {
285                (netstack3_base::TcpSocketState::SynRecv, SendInfo::default(), RecvInfo::default())
286            }
287            State::Established(Established { snd, rcv }) => (
288                netstack3_base::TcpSocketState::Established,
289                SendInfo::from_send(snd.get()),
290                RecvInfo::from_recv(rcv.get()),
291            ),
292            State::FinWait1(FinWait1 { snd, rcv }) => (
293                netstack3_base::TcpSocketState::FinWait1,
294                SendInfo::from_send(snd.get()),
295                RecvInfo::from_recv(rcv.get()),
296            ),
297            State::FinWait2(FinWait2 { last_seq: _, rcv, timeout_at: _, snd_info }) => (
298                netstack3_base::TcpSocketState::FinWait2,
299                snd_info.clone(),
300                RecvInfo::from_recv(rcv),
301            ),
302            State::CloseWait(CloseWait { snd, closed_rcv }) => (
303                netstack3_base::TcpSocketState::CloseWait,
304                SendInfo::from_send(snd.get()),
305                RecvInfo::from_recv_params(closed_rcv),
306            ),
307            State::Closing(Closing { snd, closed_rcv }) => (
308                netstack3_base::TcpSocketState::Closing,
309                SendInfo::from_send(snd),
310                RecvInfo::from_recv_params(closed_rcv),
311            ),
312            State::LastAck(LastAck { snd, closed_rcv }) => (
313                netstack3_base::TcpSocketState::LastAck,
314                SendInfo::from_send(snd),
315                RecvInfo::from_recv_params(closed_rcv),
316            ),
317            State::TimeWait(TimeWait { last_seq: _, expiry: _, closed_rcv, snd_info }) => (
318                netstack3_base::TcpSocketState::TimeWait,
319                snd_info.clone(),
320                RecvInfo::from_recv_params(closed_rcv),
321            ),
322        };
323
324        let SendInfo {
325            snd_cwnd,
326            snd_ssthresh,
327            ca_state,
328            rtt,
329            rtt_var,
330            rto,
331            snd_mss,
332            last_data_sent,
333        } = send_params;
334        let RecvInfo { last_ack_recv, rcv_mss } = recv_params;
335        let CounterParams { retransmits, segs_out, segs_in } =
336            CounterParams::from_counters(counters);
337
338        Self {
339            state,
340            ca_state,
341            rto,
342            rtt,
343            rtt_var,
344            snd_ssthresh,
345            snd_cwnd,
346            retransmits,
347            last_ack_recv,
348            segs_out,
349            segs_in,
350            snd_mss,
351            rcv_mss,
352            last_data_sent,
353        }
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360
361    use core::time::Duration;
362
363    use netstack3_base::testutil::FakeInstant;
364    use netstack3_base::{
365        EffectiveMss, Mss, MssSizeLimiters, SeqNum, TcpSocketState, Timestamp, WindowScale,
366        WindowSize,
367    };
368
369    use crate::internal::buffer::Assembler;
370    use crate::internal::congestion::CongestionControl;
371    use crate::internal::rtt::{Estimator, RttSampler};
372    use crate::internal::state::{Established, Listen, Recv, RecvBufferState, Send, State};
373    use crate::internal::timestamp::TimestampOptionState;
374    use crate::testutil::RingBuffer;
375
376    impl TcpCountersWithSocketInner {
377        fn new_for_test(retransmits: u64, segs_out: u64, segs_in: u64) -> Self {
378            let counters = Self::default();
379            counters.retransmits.add(retransmits);
380            counters.segments_sent.add(segs_out);
381            counters.received_segments_dispatched.add(segs_in);
382            counters
383        }
384    }
385
386    #[test]
387    fn test_from_partial_state() {
388        let state = TcpSocketState::Listen;
389        let counters = TcpCountersWithSocketInner::new_for_test(5, 10, 20);
390
391        let info = TcpSocketInfo::<FakeInstant>::from_partial_state(state, &counters);
392
393        assert_eq!(
394            info,
395            TcpSocketInfo {
396                state: TcpSocketState::Listen,
397                retransmits: 5,
398                segs_out: 10,
399                segs_in: 20,
400
401                // These are just the defaults.
402                ca_state: CongestionControlState::Open,
403                rto: None,
404                rtt: None,
405                rtt_var: None,
406                snd_ssthresh: 0,
407                snd_cwnd: 0,
408                last_ack_recv: None,
409                snd_mss: None,
410                rcv_mss: None,
411                last_data_sent: None,
412            }
413        );
414    }
415
416    #[test]
417    fn test_from_full_state_listen() {
418        let state = State::<FakeInstant, RingBuffer, RingBuffer, ()>::Listen(Listen {
419            iss: SeqNum::new(100),
420            timestamp_offset: Timestamp::new(0),
421            buffer_sizes: Default::default(),
422            device_mss: Mss::new(1460).unwrap(),
423            default_mss: Mss::new(536).unwrap(),
424            user_timeout: None,
425        });
426
427        let counters = TcpCountersWithSocketInner::new_for_test(5, 10, 20);
428
429        let info = TcpSocketInfo::from_full_state(&state, &counters);
430
431        assert_eq!(
432            info,
433            TcpSocketInfo {
434                state: TcpSocketState::Listen,
435                retransmits: 5,
436                segs_out: 10,
437                segs_in: 20,
438
439                // These are all defaults.
440                ca_state: CongestionControlState::Open,
441                rto: None,
442                rtt: None,
443                rtt_var: None,
444                snd_ssthresh: 0,
445                snd_cwnd: 0,
446                last_ack_recv: None,
447                snd_mss: None,
448                rcv_mss: None,
449                last_data_sent: None,
450            }
451        );
452    }
453
454    #[test]
455    fn test_from_full_state_established() {
456        let now = FakeInstant::from(Duration::from_secs(10));
457        let mss = Mss::new(1460).unwrap();
458        let effective_mss =
459            EffectiveMss::from_mss(mss, MssSizeLimiters { timestamp_enabled: false });
460
461        let mut rtt_estimator = Estimator::default();
462        let rtt = Duration::from_millis(50);
463        rtt_estimator.sample(rtt);
464
465        let mut congestion_control = CongestionControl::cubic_with_mss(effective_mss);
466        congestion_control.inflate_cwnd(u32::from(mss));
467
468        let send = Send {
469            nxt: SeqNum::new(200),
470            max: SeqNum::new(200),
471            una: SeqNum::new(100),
472            wnd: WindowSize::new(1000).unwrap(),
473            wnd_scale: WindowScale::default(),
474            wnd_max: WindowSize::new(1000).unwrap(),
475            wl1: SeqNum::new(100),
476            wl2: SeqNum::new(100),
477            last_push: SeqNum::new(200),
478            rtt_sampler: RttSampler::default(),
479            rtt_estimator,
480            timer: None,
481            congestion_control,
482            last_data_sent: Some(now - Duration::from_secs(1)),
483            buffer: RingBuffer::new(1000),
484        };
485
486        let recv = Recv {
487            buffer: RecvBufferState::Open {
488                buffer: RingBuffer::new(1000),
489                assembler: Assembler::new(SeqNum::new(50)),
490            },
491            remaining_quickacks: Default::default(),
492            last_segment_at: Some(now - Duration::from_secs(2)),
493            timer: None,
494            mss: effective_mss,
495            wnd_scale: WindowScale::default(),
496            last_window_update: (SeqNum::new(50), WindowSize::new(1000).unwrap()),
497            sack_permitted: false,
498            ts_opt: TimestampOptionState::Disabled,
499        };
500
501        let state = State::<FakeInstant, RingBuffer, RingBuffer, ()>::Established(Established {
502            snd: send.into(),
503            rcv: recv.into(),
504        });
505
506        let counters = TcpCountersWithSocketInner::new_for_test(5, 10, 20);
507        let info = TcpSocketInfo::from_full_state(&state, &counters);
508
509        assert_eq!(
510            info,
511            TcpSocketInfo {
512                state: TcpSocketState::Established,
513                ca_state: CongestionControlState::Open,
514                rto: Some(Duration::from_millis(200)),
515                rtt: Some(rtt),
516                rtt_var: Some(rtt / 2),
517                snd_ssthresh: u32::MAX,
518                snd_cwnd: 4380 + 1460,
519                retransmits: 5,
520                last_ack_recv: Some(now - Duration::from_secs(2)),
521                segs_out: 10,
522                segs_in: 20,
523                snd_mss: Some(1460),
524                rcv_mss: Some(1460),
525                last_data_sent: Some(now - Duration::from_secs(1))
526            }
527        );
528    }
529
530    #[test]
531    fn test_from_full_state_recovery() {
532        let mss = Mss::new(1460).unwrap();
533        let effective_mss =
534            EffectiveMss::from_mss(mss, MssSizeLimiters { timestamp_enabled: false });
535
536        let mut congestion_control = CongestionControl::cubic_with_mss(effective_mss);
537        // Trigger recovery by receiving 3 duplicate ACKs
538        let ack = SeqNum::new(100);
539        let nxt = SeqNum::new(200);
540        let _ = congestion_control.on_dup_ack(ack, nxt);
541        let _ = congestion_control.on_dup_ack(ack, nxt);
542        let _ = congestion_control.on_dup_ack(ack, nxt);
543
544        let send = Send {
545            nxt,
546            max: nxt,
547            una: ack,
548            wnd: WindowSize::new(1000).unwrap(),
549            wnd_scale: WindowScale::default(),
550            wnd_max: WindowSize::new(1000).unwrap(),
551            wl1: ack,
552            wl2: ack,
553            last_push: nxt,
554            rtt_sampler: RttSampler::default(),
555            rtt_estimator: Estimator::default(),
556            timer: None,
557            congestion_control,
558            last_data_sent: None,
559            buffer: RingBuffer::new(1000),
560        };
561
562        let recv = Recv {
563            buffer: RecvBufferState::Open {
564                buffer: RingBuffer::new(1000),
565                assembler: Assembler::new(SeqNum::new(50)),
566            },
567            remaining_quickacks: Default::default(),
568            last_segment_at: None,
569            timer: None,
570            mss: effective_mss,
571            wnd_scale: WindowScale::default(),
572            last_window_update: (SeqNum::new(50), WindowSize::new(1000).unwrap()),
573            sack_permitted: false,
574            ts_opt: TimestampOptionState::Disabled,
575        };
576
577        let state = State::<FakeInstant, RingBuffer, RingBuffer, ()>::Established(Established {
578            snd: send.into(),
579            rcv: recv.into(),
580        });
581
582        let counters = TcpCountersWithSocketInner::new_for_test(5, 10, 20);
583        let info = TcpSocketInfo::from_full_state(&state, &counters);
584
585        assert_eq!(
586            info,
587            TcpSocketInfo {
588                state: TcpSocketState::Established,
589                retransmits: 5,
590                segs_out: 10,
591                segs_in: 20,
592                ca_state: CongestionControlState::Recovery,
593                rto: Some(Duration::from_secs(1)),
594                rtt: None,
595                rtt_var: None,
596                snd_ssthresh: 3066,
597                snd_cwnd: 7300,
598                last_ack_recv: None,
599                snd_mss: Some(1460),
600                rcv_mss: Some(1460),
601                last_data_sent: None,
602            }
603        );
604    }
605
606    #[cfg(target_os = "fuchsia")]
607    #[test]
608    fn test_inspect_tcp_socket_info() {
609        use diagnostics_assertions::assert_data_tree;
610        use diagnostics_traits::FuchsiaInspector;
611        use fuchsia_inspect::Inspector;
612
613        let info = TcpSocketInfo::<FakeInstant> {
614            state: TcpSocketState::Established,
615            ca_state: CongestionControlState::Open,
616            rto: Some(Duration::from_millis(200)),
617            rtt: Some(Duration::from_millis(50)),
618            rtt_var: Some(Duration::from_millis(25)),
619            snd_ssthresh: 1000,
620            snd_cwnd: 2000,
621            retransmits: 5,
622            last_ack_recv: None,
623            segs_out: 10,
624            segs_in: 20,
625            snd_mss: Some(1460),
626            rcv_mss: Some(1460),
627            last_data_sent: None,
628        };
629
630        let inspector = Inspector::new(Default::default());
631        let mut bindings_inspector = FuchsiaInspector::<()>::new(inspector.root());
632        info.record(&mut bindings_inspector);
633
634        let mut exec = fuchsia_async::TestExecutor::new();
635
636        assert_data_tree!(@executor exec, inspector, "root": {
637            "CongestionControlState": "Open",
638            "RtoMs": 200u64,
639            "RttMs": 50u64,
640            "RttVarMs": 25u64,
641            "SndSsthresh": 1000u64,
642            "SndCwnd": 2000u64,
643            "SndMss": 1460u64,
644            "RcvMss": 1460u64,
645        });
646    }
647}