Skip to main content

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 soft ICMP error messages received.
52    pub rx_icmp_error_soft: C,
53    /// Count of hard ICMP error messages received.
54    pub rx_icmp_error_hard: C,
55    /// Count of hard ICMP error messages received that were malformed or missing info.
56    pub rx_icmp_error_hard_malformed: C,
57    /// Count of hard ICMP error messages received that could not be dispatched to a socket.
58    pub rx_icmp_error_hard_no_socket: C,
59    /// Count of UDP datagrams received from the IP layer, including error
60    /// cases.
61    pub rx: C,
62    /// Count of incoming UDP datagrams dropped because it contained a mapped IP
63    /// address in the header.
64    pub rx_mapped_addr: C,
65    /// Count of incoming UDP datagrams dropped because of an unknown
66    /// destination port.
67    pub rx_unknown_dest_port: C,
68    /// Count of incoming UDP datagrams dropped because their UDP header was in
69    /// a malformed state.
70    pub rx_malformed: C,
71}
72
73/// Counters for UDP events that can be attributed to an individual socket.
74///
75/// These counters are tracked stack wide and per socket.
76///
77/// Note on dual stack sockets: These counters are tracked for `SockI`.
78// TODO(https://fxbug.dev/396127493): For some of these events, it would be
79// better to track them for `WireI` (e.g. `received_segments_dispatched`,
80// `segments_sent`, etc.). Doing so may require splitting up the struct and/or
81// reworking the `ResourceCounterContext` trait.
82pub type UdpCountersWithSocket<I> = IpMarked<I, UdpCountersWithSocketInner>;
83
84/// The IP agnostic version of [`UdpCountersWithSocket`].
85///
86/// The counter type `C` is generic to facilitate testing.
87#[derive(Default, Debug)]
88#[cfg_attr(
89    any(test, feature = "testutils"),
90    derive(PartialEq, netstack3_macros::CounterCollection)
91)]
92pub struct UdpCountersWithSocketInner<C = Counter> {
93    /// Count of UDP datagrams that were delivered to a socket. Because some
94    /// datagrams may be delivered to multiple sockets (e.g. multicast traffic)
95    /// this counter may exceed the total number of individual UDP datagrams
96    /// received by the stack.
97    pub rx_delivered: C,
98    /// Count of UDP datagrams that could not be delivered to a socket because
99    /// its receive buffer was full.
100    pub rx_queue_full: C,
101    /// Count of outgoing UDP datagrams sent from the socket layer, including
102    /// error cases.
103    pub tx: C,
104    /// Count of outgoing UDP datagrams which failed to be sent out of the
105    /// transport layer.
106    pub tx_error: C,
107    /// Count of hard ICMP error messages successfully delivered to the socket.
108    pub rx_icmp_error_hard_delivered: C,
109}
110
111/// A composition of the UDP counters with and without a socket.
112pub struct CombinedUdpCounters<'a, I: Ip> {
113    /// The UDP counters that can be associated with a socket.
114    pub with_socket: &'a UdpCountersWithSocket<I>,
115    /// The UDP counters that cannot be associated with a socket.
116    ///
117    /// This field is optional so that the same [`Inspectable`] implementation
118    /// can be used for both the stack-wide counters and the per-socket
119    /// counters.
120    pub without_socket: Option<&'a UdpCountersWithoutSocket<I>>,
121}
122
123impl<I: Ip> Inspectable for CombinedUdpCounters<'_, I> {
124    fn record<II: Inspector>(&self, inspector: &mut II) {
125        let CombinedUdpCounters { with_socket, without_socket } = self;
126        let UdpCountersWithSocketInner {
127            rx_delivered,
128            rx_queue_full,
129            tx,
130            tx_error,
131            rx_icmp_error_hard_delivered,
132        } = with_socket.as_ref();
133
134        // Note: Organize the "without socket" counters into helper struct to
135        // make the optionality more ergonomic to handle.
136        struct WithoutSocketRx<'a> {
137            rx: &'a Counter,
138        }
139        struct WithoutSocketRxError<'a> {
140            rx_mapped_addr: &'a Counter,
141            rx_unknown_dest_port: &'a Counter,
142            rx_malformed: &'a Counter,
143        }
144        struct WithoutSocketError<'a> {
145            rx_icmp_error: &'a Counter,
146            rx_icmp_error_soft: &'a Counter,
147            rx_icmp_error_hard: &'a Counter,
148            rx_icmp_error_hard_malformed: &'a Counter,
149            rx_icmp_error_hard_no_socket: &'a Counter,
150        }
151        let (without_socket_rx, without_socket_rx_error, without_socket_error) =
152            match without_socket.map(AsRef::as_ref) {
153                None => (None, None, None),
154                Some(UdpCountersWithoutSocketInner {
155                    rx_icmp_error,
156                    rx_icmp_error_soft,
157                    rx_icmp_error_hard,
158                    rx_icmp_error_hard_malformed,
159                    rx_icmp_error_hard_no_socket,
160                    rx,
161                    rx_mapped_addr,
162                    rx_unknown_dest_port,
163                    rx_malformed,
164                }) => (
165                    Some(WithoutSocketRx { rx }),
166                    Some(WithoutSocketRxError {
167                        rx_mapped_addr,
168                        rx_unknown_dest_port,
169                        rx_malformed,
170                    }),
171                    Some(WithoutSocketError {
172                        rx_icmp_error,
173                        rx_icmp_error_soft,
174                        rx_icmp_error_hard,
175                        rx_icmp_error_hard_malformed,
176                        rx_icmp_error_hard_no_socket,
177                    }),
178                ),
179            };
180        inspector.record_child("Rx", |inspector| {
181            inspector.record_counter("Delivered", rx_delivered);
182            if let Some(WithoutSocketRx { rx }) = without_socket_rx {
183                inspector.record_counter("Received", rx);
184            }
185            inspector.record_child("Errors", |inspector| {
186                inspector.record_counter("DroppedQueueFull", rx_queue_full);
187                inspector.record_counter("HardIcmpErrors", rx_icmp_error_hard_delivered);
188                if let Some(WithoutSocketRxError {
189                    rx_mapped_addr,
190                    rx_unknown_dest_port,
191                    rx_malformed,
192                }) = without_socket_rx_error
193                {
194                    inspector.record_counter("MappedAddr", rx_mapped_addr);
195                    inspector.record_counter("UnknownDstPort", rx_unknown_dest_port);
196                    inspector.record_counter("Malformed", rx_malformed);
197                }
198            });
199        });
200        inspector.record_child("Tx", |inspector| {
201            inspector.record_counter("Sent", tx);
202            inspector.record_counter("Errors", tx_error);
203        });
204        if let Some(WithoutSocketError {
205            rx_icmp_error,
206            rx_icmp_error_soft,
207            rx_icmp_error_hard,
208            rx_icmp_error_hard_malformed,
209            rx_icmp_error_hard_no_socket,
210        }) = without_socket_error
211        {
212            inspector.record_child("IcmpErrors", |inspector| {
213                inspector.record_counter("Count", rx_icmp_error);
214                inspector.record_counter("Soft", rx_icmp_error_soft);
215                inspector.record_counter("Hard", rx_icmp_error_hard);
216                inspector.record_counter("Malformed", rx_icmp_error_hard_malformed);
217                inspector.record_counter("NoSocket", rx_icmp_error_hard_no_socket);
218            });
219        }
220    }
221}
222
223#[cfg(test)]
224pub(crate) mod testutil {
225    use super::*;
226
227    pub(crate) type CounterExpectationsWithSocket = UdpCountersWithSocketInner<u64>;
228
229    pub(crate) type CounterExpectationsWithoutSocket = UdpCountersWithoutSocketInner<u64>;
230}