netstack3_tcp/
counters.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
5//! Facilities for tracking the counts of various TCP events.
6
7use net_types::ip::{Ip, IpMarked};
8use netstack3_base::socket::EitherStack;
9use netstack3_base::{
10    Counter, CounterContext, Inspectable, Inspector, InspectorExt as _, ResourceCounterContext,
11    WeakDeviceIdentifier,
12};
13
14use crate::internal::socket::{DualStackIpExt, TcpBindingsTypes, TcpSocketId};
15
16/// A marker trait to simplify bounds for TCP counters.
17pub trait TcpCounterContext<I: DualStackIpExt, D: WeakDeviceIdentifier, BT: TcpBindingsTypes>:
18    ResourceCounterContext<TcpSocketId<I, D, BT>, TcpCountersWithSocket<I>>
19    + CounterContext<TcpCountersWithoutSocket<I>>
20{
21}
22
23impl<I, D, BT, CC> TcpCounterContext<I, D, BT> for CC
24where
25    I: DualStackIpExt,
26    D: WeakDeviceIdentifier,
27    BT: TcpBindingsTypes,
28    CC: ResourceCounterContext<TcpSocketId<I, D, BT>, TcpCountersWithSocket<I>>
29        + CounterContext<TcpCountersWithoutSocket<I>>,
30{
31}
32
33/// Counters for TCP events that cannot be attributed to an individual socket.
34///
35/// These counters are tracked stack wide.
36///
37/// Note on dual stack sockets: These counters are tracked for `WireI`.
38pub type TcpCountersWithoutSocket<I> = IpMarked<I, TcpCountersWithoutSocketInner>;
39
40/// The IP agnostic version of [`TcpCountersWithoutSocket`].
41///
42/// The counter type `C` is generic to facilitate testing.
43#[derive(Default, Debug)]
44#[cfg_attr(
45    any(test, feature = "testutils"),
46    derive(PartialEq, netstack3_macros::CounterCollection)
47)]
48pub struct TcpCountersWithoutSocketInner<C = Counter> {
49    /// Count of received IP packets that were dropped because they had
50    /// unexpected IP addresses (either src or dst).
51    pub invalid_ip_addrs_received: C,
52    /// Count of received TCP segments that were dropped because they could not
53    /// be parsed.
54    pub invalid_segments_received: C,
55    /// Count of received TCP segments that were valid.
56    pub valid_segments_received: C,
57    /// Count of received TCP segments that were not associated with any
58    /// existing sockets.
59    pub received_segments_no_dispatch: C,
60    /// Count of received segments whose checksums were invalid.
61    pub checksum_errors: C,
62}
63
64/// Counters for TCP events that can be attributed to an individual socket.
65///
66/// These counters are tracked stack wide and per socket.
67///
68/// Note on dual stack sockets: These counters are tracked for `SockI`.
69// TODO(https://fxbug.dev/396127493): For some of these events, it would be
70// better to track them for `WireI` (e.g. `received_segments_dispatched`,
71// `segments_sent`, etc.). Doing so may require splitting up the struct and/or
72// reworking the `ResourceCounterContext` trait.
73pub type TcpCountersWithSocket<I> = IpMarked<I, TcpCountersWithSocketInner<Counter>>;
74
75/// The IP agnostic version of [`TcpCountersWithSocket`].
76///
77/// The counter type `C` is generic to facilitate testing.
78// TODO(https://fxbug.dev/42052878): Add counters for SYN cookies.
79#[derive(Default, Debug)]
80#[cfg_attr(
81    any(test, feature = "testutils"),
82    derive(PartialEq, netstack3_macros::CounterCollection)
83)]
84pub struct TcpCountersWithSocketInner<C = Counter> {
85    /// Count of received TCP segments that were successfully dispatched to a
86    /// socket.
87    pub received_segments_dispatched: C,
88    /// Count of received TCP segments that were dropped because the listener
89    /// queue was full.
90    pub listener_queue_overflow: C,
91    /// Count of TCP segments that failed to send.
92    pub segment_send_errors: C,
93    /// Count of TCP segments that were sent.
94    pub segments_sent: C,
95    /// Count of passive open attempts that failed because the stack doesn't
96    /// have route to the peer.
97    pub passive_open_no_route_errors: C,
98    /// Count of passive connections that have been opened.
99    pub passive_connection_openings: C,
100    /// Count of active open attempts that have failed because the stack doesn't
101    /// have a route to the peer.
102    pub active_open_no_route_errors: C,
103    /// Count of active connections that have been opened.
104    pub active_connection_openings: C,
105    /// Count of all failed connection attempts, including both passive and
106    /// active opens.
107    pub failed_connection_attempts: C,
108    /// Count of port reservation attempts that failed.
109    pub failed_port_reservations: C,
110    /// Count of received segments with the RST flag set.
111    pub resets_received: C,
112    /// Count of sent segments with the RST flag set.
113    pub resets_sent: C,
114    /// Count of received segments with the SYN flag set.
115    pub syns_received: C,
116    /// Count of sent segments with the SYN flag set.
117    pub syns_sent: C,
118    /// Count of received segments with the FIN flag set.
119    pub fins_received: C,
120    /// Count of sent segments with the FIN flag set.
121    pub fins_sent: C,
122    /// Count of retransmission timeouts.
123    pub timeouts: C,
124    /// Count of retransmissions of segments.
125    pub retransmits: C,
126    /// Count of retransmissions of segments while in slow start.
127    pub slow_start_retransmits: C,
128    /// Count of retransmissions of segments while in fast recovery.
129    pub fast_retransmits: C,
130    /// Count of retransmissions of segments while in SACK recovery.
131    pub sack_retransmits: C,
132    /// Count of times fast recovery was initiated to recover from packet loss.
133    pub fast_recovery: C,
134    /// Count of times SACK recovery was initiated to recover from packet loss.
135    pub sack_recovery: C,
136    /// Count of times an established TCP connection transitioned to CLOSED.
137    pub established_closed: C,
138    /// Count of times an established TCP connection transitioned to CLOSED due
139    /// to a RST segment.
140    pub established_resets: C,
141    /// Count of times an established TCP connection transitioned to CLOSED due
142    /// to a timeout (e.g. a keep-alive or retransmit timeout).
143    pub established_timedout: C,
144    /// Count of times loss recovery completes successfully.
145    pub loss_recovered: C,
146    /// Count of times duplicate ACKs were received.
147    pub dup_acks: C,
148}
149
150/// A composition of the TCP Counters with and without a socket.
151pub struct CombinedTcpCounters<'a, I: Ip> {
152    /// The TCP Counters that can be associated with a socket.
153    pub with_socket: &'a TcpCountersWithSocket<I>,
154    /// The TCP Counters that cannot be associated with a socket.
155    ///
156    /// This field is optional so that the same [`Inspectable`] implementation
157    /// can be used for both the stack-wide counters, and the per-socket
158    /// counters.
159    pub without_socket: Option<&'a TcpCountersWithoutSocket<I>>,
160}
161
162impl<I: Ip> Inspectable for CombinedTcpCounters<'_, I> {
163    fn record<II: Inspector>(&self, inspector: &mut II) {
164        let CombinedTcpCounters { with_socket, without_socket } = self;
165        let TcpCountersWithSocketInner {
166            received_segments_dispatched,
167            listener_queue_overflow,
168            segment_send_errors,
169            segments_sent,
170            passive_open_no_route_errors,
171            passive_connection_openings,
172            active_open_no_route_errors,
173            active_connection_openings,
174            failed_connection_attempts,
175            failed_port_reservations,
176            resets_received,
177            resets_sent,
178            syns_received,
179            syns_sent,
180            fins_received,
181            fins_sent,
182            timeouts,
183            retransmits,
184            slow_start_retransmits,
185            fast_retransmits,
186            sack_retransmits,
187            fast_recovery,
188            sack_recovery,
189            established_closed,
190            established_resets,
191            established_timedout,
192            loss_recovered,
193            dup_acks,
194        } = with_socket.as_ref();
195
196        // Note: Organize the "without socket" counters into helper structs to
197        // make the optionality more ergonomic to handle.
198        struct WithoutSocketRx<'a> {
199            valid_segments_received: &'a Counter,
200        }
201        struct WithoutSocketRxError<'a> {
202            invalid_ip_addrs_received: &'a Counter,
203            invalid_segments_received: &'a Counter,
204            received_segments_no_dispatch: &'a Counter,
205            checksum_errors: &'a Counter,
206        }
207        let (without_socket_rx, without_socket_rx_error) = match without_socket.map(AsRef::as_ref) {
208            None => (None, None),
209            Some(TcpCountersWithoutSocketInner {
210                invalid_ip_addrs_received,
211                invalid_segments_received,
212                valid_segments_received,
213                received_segments_no_dispatch,
214                checksum_errors,
215            }) => (
216                Some(WithoutSocketRx { valid_segments_received }),
217                Some(WithoutSocketRxError {
218                    invalid_ip_addrs_received,
219                    invalid_segments_received,
220                    received_segments_no_dispatch,
221                    checksum_errors,
222                }),
223            ),
224        };
225
226        inspector.record_child("Rx", |inspector| {
227            if let Some(WithoutSocketRx { valid_segments_received }) = without_socket_rx {
228                inspector.record_counter("ValidSegmentsReceived", valid_segments_received);
229            }
230            inspector.record_counter("ReceivedSegmentsDispatched", received_segments_dispatched);
231            inspector.record_counter("ResetsReceived", resets_received);
232            inspector.record_counter("SynsReceived", syns_received);
233            inspector.record_counter("FinsReceived", fins_received);
234            inspector.record_counter("DupAcks", dup_acks);
235            inspector.record_child("Errors", |inspector| {
236                inspector.record_counter("ListenerQueueOverflow", listener_queue_overflow);
237                inspector.record_counter("PassiveOpenNoRouteErrors", passive_open_no_route_errors);
238                if let Some(WithoutSocketRxError {
239                    invalid_ip_addrs_received,
240                    invalid_segments_received,
241                    received_segments_no_dispatch,
242                    checksum_errors,
243                }) = without_socket_rx_error
244                {
245                    inspector.record_counter("InvalidIpAddrsReceived", invalid_ip_addrs_received);
246                    inspector.record_counter("InvalidSegmentsReceived", invalid_segments_received);
247                    inspector.record_counter(
248                        "ReceivedSegmentsNoDispatch",
249                        received_segments_no_dispatch,
250                    );
251                    inspector.record_counter("ChecksumErrors", checksum_errors);
252                }
253            })
254        });
255        inspector.record_child("Tx", |inspector| {
256            inspector.record_counter("SegmentsSent", segments_sent);
257            inspector.record_counter("ResetsSent", resets_sent);
258            inspector.record_counter("SynsSent", syns_sent);
259            inspector.record_counter("FinsSent", fins_sent);
260            inspector.record_counter("Timeouts", timeouts);
261            inspector.record_counter("Retransmits", retransmits);
262            inspector.record_counter("SlowStartRetransmits", slow_start_retransmits);
263            inspector.record_counter("FastRetransmits", fast_retransmits);
264            inspector.record_counter("SackRetransmits", sack_retransmits);
265            inspector.record_child("Errors", |inspector| {
266                inspector.record_counter("SegmentSendErrors", segment_send_errors);
267                inspector.record_counter("ActiveOpenNoRouteErrors", active_open_no_route_errors);
268            });
269        });
270        inspector.record_counter("PassiveConnectionOpenings", passive_connection_openings);
271        inspector.record_counter("ActiveConnectionOpenings", active_connection_openings);
272        inspector.record_counter("FastRecovery", fast_recovery);
273        inspector.record_counter("SackRecovery", sack_recovery);
274        inspector.record_counter("LossRecovered", loss_recovered);
275        inspector.record_counter("EstablishedClosed", established_closed);
276        inspector.record_counter("EstablishedResets", established_resets);
277        inspector.record_counter("EstablishedTimedout", established_timedout);
278        inspector.record_child("Errors", |inspector| {
279            inspector.record_counter("FailedConnectionOpenings", failed_connection_attempts);
280            inspector.record_counter("FailedPortReservations", failed_port_reservations);
281        })
282    }
283}
284
285/// Holds references to the stack-wide and per-socket counters.
286///
287/// This is used to easily increment both counters in contexts that don't have
288/// access to `ResourceCounterContext`. This is currently used by the TCP state
289/// machine, which does not operate on a core context so that it can remain
290/// IP agnostic (and thus avoid duplicate code generation).
291pub(crate) struct TcpCountersRefs<'a> {
292    pub(crate) stack_wide: &'a TcpCountersWithSocketInner,
293    pub(crate) per_socket: &'a TcpCountersWithSocketInner,
294}
295
296impl<'a> TcpCountersRefs<'a> {
297    pub(crate) fn from_ctx<I: Ip, R, CC: ResourceCounterContext<R, TcpCountersWithSocket<I>>>(
298        ctx: &'a CC,
299        resource: &'a R,
300    ) -> Self {
301        TcpCountersRefs {
302            stack_wide: ctx.counters(),
303            per_socket: ctx.per_resource_counters(resource),
304        }
305    }
306
307    pub(crate) fn increment<F: Fn(&TcpCountersWithSocketInner<Counter>) -> &Counter>(&self, cb: F) {
308        let Self { stack_wide, per_socket } = self;
309        cb(stack_wide).increment();
310        cb(per_socket).increment();
311    }
312}
313
314/// Increments the stack-wide counters, and optionally the per-resouce counters.
315///
316/// Used to increment counters that can, but are not required to, have an
317/// associated socket. For example, sent segment counts.
318pub(crate) fn increment_counter_with_optional_socket_id<I, CC, BT, D, F>(
319    core_ctx: &CC,
320    socket_id: Option<&TcpSocketId<I, D, BT>>,
321    cb: F,
322) where
323    I: DualStackIpExt,
324    CC: TcpCounterContext<I, D, BT>,
325    D: WeakDeviceIdentifier,
326    BT: TcpBindingsTypes,
327    F: Fn(&TcpCountersWithSocket<I>) -> &Counter,
328{
329    match socket_id {
330        Some(id) => core_ctx.increment_both(id, cb),
331        None => cb(core_ctx.counters()).increment(),
332    }
333}
334
335/// Increments the stack-wide counters, and optionally the per-resouce counters.
336///
337/// Used to increment counters that can, but are not required to, have an
338/// associated socket. For example, received segment counts.
339pub(crate) fn increment_counter_with_optional_demux_id<I, CC, BT, D, F>(
340    core_ctx: &CC,
341    demux_id: Option<&I::DemuxSocketId<D, BT>>,
342    cb: F,
343) where
344    I: DualStackIpExt,
345    CC: TcpCounterContext<I, D, BT> + TcpCounterContext<I::OtherVersion, D, BT>,
346    D: WeakDeviceIdentifier,
347    BT: TcpBindingsTypes,
348    F: Fn(&TcpCountersWithSocketInner) -> &Counter,
349{
350    match demux_id {
351        Some(id) => increment_counter_for_demux_id::<I, _, _, _, _>(core_ctx, &id, cb),
352        None => {
353            cb(CounterContext::<TcpCountersWithSocket<I>>::counters(core_ctx).as_ref()).increment()
354        }
355    }
356}
357
358/// Increment a counter for TCP demux_ids, which may exist in either stack.
359pub(crate) fn increment_counter_for_demux_id<I, D, BT, CC, F>(
360    core_ctx: &CC,
361    demux_id: &I::DemuxSocketId<D, BT>,
362    cb: F,
363) where
364    I: DualStackIpExt,
365    D: WeakDeviceIdentifier,
366    BT: TcpBindingsTypes,
367    CC: TcpCounterContext<I, D, BT> + TcpCounterContext<I::OtherVersion, D, BT>,
368    F: Fn(&TcpCountersWithSocketInner<Counter>) -> &Counter,
369{
370    match I::as_dual_stack_ip_socket(demux_id) {
371        EitherStack::ThisStack(socket_id) => core_ctx
372            .increment_both(socket_id, |counters: &TcpCountersWithSocket<I>| cb(counters.as_ref())),
373        EitherStack::OtherStack(socket_id) => core_ctx
374            .increment_both(socket_id, |counters: &TcpCountersWithSocket<I::OtherVersion>| {
375                cb(counters.as_ref())
376            }),
377    }
378}
379
380#[cfg(test)]
381pub(crate) mod testutil {
382    use super::*;
383
384    pub(crate) type CounterExpectations = TcpCountersWithSocketInner<u64>;
385
386    pub(crate) type CounterExpectationsWithoutSocket = TcpCountersWithoutSocketInner<u64>;
387}