netstack3_udp/
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 UDP events.
6
7use net_types::ip::{Ip, IpMarked};
8use netstack3_base::{
9    Counter, CounterContext, Inspectable, Inspector, InspectorExt as _, ResourceCounterContext,
10    WeakDeviceIdentifier,
11};
12use netstack3_datagram::IpExt;
13
14use crate::internal::base::{UdpBindingsTypes, UdpSocketId};
15
16/// A marker trait to simplify bounds for UDP counters.
17pub trait UdpCounterContext<I: IpExt, D: WeakDeviceIdentifier, BT: UdpBindingsTypes>:
18    ResourceCounterContext<UdpSocketId<I, D, BT>, UdpCountersWithSocket<I>>
19    + CounterContext<UdpCountersWithoutSocket<I>>
20{
21}
22
23impl<I, D, BT, CC> UdpCounterContext<I, D, BT> for CC
24where
25    I: IpExt,
26    D: WeakDeviceIdentifier,
27    BT: UdpBindingsTypes,
28    CC: ResourceCounterContext<UdpSocketId<I, D, BT>, UdpCountersWithSocket<I>>
29        + CounterContext<UdpCountersWithoutSocket<I>>,
30{
31}
32
33/// Counters for UDP 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 UdpCountersWithoutSocket<I> = IpMarked<I, UdpCountersWithoutSocketInner>;
39
40/// The IP agnostic version of [`UdpCountersWithoutSocket`].
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 UdpCountersWithoutSocketInner<C = Counter> {
49    /// Count of ICMP error messages received.
50    pub rx_icmp_error: C,
51    /// Count of UDP datagrams received from the IP layer, including error
52    /// cases.
53    pub rx: C,
54    /// Count of incoming UDP datagrams dropped because it contained a mapped IP
55    /// address in the header.
56    pub rx_mapped_addr: C,
57    /// Count of incoming UDP datagrams dropped because of an unknown
58    /// destination port.
59    pub rx_unknown_dest_port: C,
60    /// Count of incoming UDP datagrams dropped because their UDP header was in
61    /// a malformed state.
62    pub rx_malformed: C,
63}
64
65/// Counters for UDP events that can be attributed to an individual socket.
66///
67/// These counters are tracked stack wide and per socket.
68///
69/// Note on dual stack sockets: These counters are tracked for `SockI`.
70// TODO(https://fxbug.dev/396127493): For some of these events, it would be
71// better to track them for `WireI` (e.g. `received_segments_dispatched`,
72// `segments_sent`, etc.). Doing so may require splitting up the struct and/or
73// reworking the `ResourceCounterContext` trait.
74pub type UdpCountersWithSocket<I> = IpMarked<I, UdpCountersWithSocketInner>;
75
76/// The IP agnostic version of [`UdpCountersWithSocket`].
77///
78/// The counter type `C` is generic to facilitate testing.
79#[derive(Default, Debug)]
80#[cfg_attr(
81    any(test, feature = "testutils"),
82    derive(PartialEq, netstack3_macros::CounterCollection)
83)]
84pub struct UdpCountersWithSocketInner<C = Counter> {
85    /// Count of UDP datagrams that were delivered to a socket. Because some
86    /// datagrams may be delivered to multiple sockets (e.g. multicast traffic)
87    /// this counter may exceed the total number of individual UDP datagrams
88    /// received by the stack.
89    pub rx_delivered: C,
90    /// Count of UDP datagrams that could not be delivered to a socket because
91    /// its receive buffer was full.
92    pub rx_queue_full: C,
93    /// Count of outgoing UDP datagrams sent from the socket layer, including
94    /// error cases.
95    pub tx: C,
96    /// Count of outgoing UDP datagrams which failed to be sent out of the
97    /// transport layer.
98    pub tx_error: C,
99}
100
101/// A composition of the UDP counters with and without a socket.
102pub struct CombinedUdpCounters<'a, I: Ip> {
103    /// The UDP counters that can be associated with a socket.
104    pub with_socket: &'a UdpCountersWithSocket<I>,
105    /// The UDP counters that cannot be associated with a socket.
106    ///
107    /// This field is optional so that the same [`Inspectable`] implementation
108    /// can be used for both the stack-wide counters and the per-socket
109    /// counters.
110    pub without_socket: Option<&'a UdpCountersWithoutSocket<I>>,
111}
112
113impl<I: Ip> Inspectable for CombinedUdpCounters<'_, I> {
114    fn record<II: Inspector>(&self, inspector: &mut II) {
115        let CombinedUdpCounters { with_socket, without_socket } = self;
116        let UdpCountersWithSocketInner { rx_delivered, rx_queue_full, tx, tx_error } =
117            with_socket.as_ref();
118
119        // Note: Organize the "without socket" counters into helper struct to
120        // make the optionality more ergonomic to handle.
121        struct WithoutSocketRx<'a> {
122            rx: &'a Counter,
123        }
124        struct WithoutSocketRxError<'a> {
125            rx_mapped_addr: &'a Counter,
126            rx_unknown_dest_port: &'a Counter,
127            rx_malformed: &'a Counter,
128        }
129        struct WithoutSocketError<'a> {
130            rx_icmp_error: &'a Counter,
131        }
132        let (without_socket_rx, without_socket_rx_error, without_socket_error) =
133            match without_socket.map(AsRef::as_ref) {
134                None => (None, None, None),
135                Some(UdpCountersWithoutSocketInner {
136                    rx_icmp_error,
137                    rx,
138                    rx_mapped_addr,
139                    rx_unknown_dest_port,
140                    rx_malformed,
141                }) => (
142                    Some(WithoutSocketRx { rx }),
143                    Some(WithoutSocketRxError {
144                        rx_mapped_addr,
145                        rx_unknown_dest_port,
146                        rx_malformed,
147                    }),
148                    Some(WithoutSocketError { rx_icmp_error }),
149                ),
150            };
151        inspector.record_child("Rx", |inspector| {
152            inspector.record_counter("Delivered", rx_delivered);
153            if let Some(WithoutSocketRx { rx }) = without_socket_rx {
154                inspector.record_counter("Received", rx);
155            }
156            inspector.record_child("Errors", |inspector| {
157                inspector.record_counter("DroppedQueueFull", rx_queue_full);
158                if let Some(WithoutSocketRxError {
159                    rx_mapped_addr,
160                    rx_unknown_dest_port,
161                    rx_malformed,
162                }) = without_socket_rx_error
163                {
164                    inspector.record_counter("MappedAddr", rx_mapped_addr);
165                    inspector.record_counter("UnknownDstPort", rx_unknown_dest_port);
166                    inspector.record_counter("Malformed", rx_malformed);
167                }
168            });
169        });
170        inspector.record_child("Tx", |inspector| {
171            inspector.record_counter("Sent", tx);
172            inspector.record_counter("Errors", tx_error);
173        });
174        if let Some(WithoutSocketError { rx_icmp_error }) = without_socket_error {
175            inspector.record_counter("IcmpErrors", rx_icmp_error);
176        }
177    }
178}
179
180#[cfg(test)]
181pub(crate) mod testutil {
182    use super::*;
183
184    pub(crate) type CounterExpectationsWithSocket = UdpCountersWithSocketInner<u64>;
185
186    pub(crate) type CounterExpectationsWithoutSocket = UdpCountersWithoutSocketInner<u64>;
187}