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