Skip to main content

netstack3_ip/
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 IP events.
6
7use core::fmt::Debug;
8use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6};
9use netstack3_base::{
10    Counter, CounterCollectionSpec, CounterRepr, Inspectable, Inspector, InspectorExt as _,
11    TestOnlyPartialEq,
12};
13
14use crate::internal::fragmentation::FragmentationCounters;
15use crate::internal::gso::{GsoCounters, GsoSourceCounters};
16
17/// An IP extension trait supporting counters at the IP layer.
18pub trait IpCountersIpExt: Ip {
19    /// Receive counters.
20    type RxCounters: CounterCollectionSpec<CounterCollection<Counter>: Inspectable>
21        + Default
22        + Debug
23        + TestOnlyPartialEq;
24}
25
26impl IpCountersIpExt for Ipv4 {
27    type RxCounters = Ipv4RxCounters;
28}
29
30impl IpCountersIpExt for Ipv6 {
31    type RxCounters = Ipv6RxCounters;
32}
33
34/// Ip layer counters.
35#[derive(Default, Debug, GenericOverIp)]
36#[generic_over_ip(I, Ip)]
37#[cfg_attr(
38    any(test, feature = "testutils"),
39    derive(PartialEq, netstack3_macros::CounterCollection)
40)]
41pub struct IpCounters<I: IpCountersIpExt, C: CounterRepr = Counter> {
42    /// Count of incoming IP unicast packets delivered.
43    pub deliver_unicast: C,
44    /// Count of incoming IP multicast packets delivered.
45    pub deliver_multicast: C,
46    /// Count of incoming IP packets that are dispatched to the appropriate protocol.
47    pub dispatch_receive_ip_packet: C,
48    /// Count of incoming IP packets destined to another host.
49    pub drop_ip_packet_other_host: C,
50    /// Count of incoming IP packets received by the stack.
51    pub receive_ip_packet: C,
52    /// Count of sent outgoing IP packets.
53    pub send_ip_packet: C,
54    /// Count of packets to be forwarded which are instead dropped because
55    /// forwarding is disabled.
56    pub forwarding_disabled: C,
57    /// Count of incoming packets forwarded to another host.
58    pub forward: C,
59    /// Count of incoming packets which cannot be forwarded because there is no
60    /// route to the destination host.
61    pub no_route_to_host: C,
62    /// Count of incoming packets which cannot be forwarded because the MTU has
63    /// been exceeded.
64    pub mtu_exceeded: C,
65    /// Count of incoming packets which cannot be forwarded because the TTL has
66    /// expired.
67    pub ttl_expired: C,
68    /// Count of ICMP error messages received.
69    pub receive_icmp_error: C,
70    /// Count of IP fragment reassembly errors.
71    pub fragment_reassembly_error: C,
72    /// Count of IP fragments that could not be reassembled because more
73    /// fragments were needed.
74    pub need_more_fragments: C,
75    /// Count of IP fragments that could not be reassembled because the fragment
76    /// was invalid.
77    pub invalid_fragment: C,
78    /// Count of IP fragments that could not be reassembled because the stack's
79    /// per-IP-protocol fragment cache was full.
80    pub fragment_cache_full: C,
81    /// Count of incoming IP packets not delivered because of a parameter problem.
82    pub parameter_problem: C,
83    /// Count of incoming IP packets with an unspecified destination address.
84    pub unspecified_destination: C,
85    /// Count of incoming IP packets with an unspecified source address.
86    pub unspecified_source: C,
87    /// Count of incoming IP packets with an invalid source address.
88    /// See the definitions of [`net_types::ip::Ipv4SourceAddr`] and
89    /// [`net_types::ip::Ipv6SourceAddr`] for the exact requirements.
90    pub invalid_source: C,
91    /// Count of incoming IP packets dropped.
92    pub dropped: C,
93    /// Count of incoming packets dropped because the destination address
94    /// is only tentatively assigned to the device.
95    pub drop_for_tentative: C,
96    /// Number of frames rejected because they'd cause illegal loopback
97    /// addresses on the wire.
98    pub tx_illegal_loopback_address: C,
99    /// Version specific rx counters.
100    pub version_rx: <I::RxCounters as CounterCollectionSpec>::CounterCollection<C>,
101    /// Count of incoming IP multicast packets that were dropped because
102    /// The stack doesn't have any sockets that belong to the multicast group,
103    /// and the stack isn't configured to forward the multicast packet.
104    pub multicast_no_interest: C,
105    /// Count of looped-back packets that held a cached conntrack entry that could
106    /// not be downcasted to the expected type. This would happen if, for example, a
107    /// packet was modified to a different IP version between EGRESS and INGRESS.
108    pub invalid_cached_conntrack_entry: C,
109    /// IP fragmentation counters.
110    pub fragmentation: FragmentationCounters<C>,
111    /// Software segmentation counters.
112    pub gso: GsoCounters<C>,
113    /// Number of packets filtered out by the socket egress filter.
114    pub socket_egress_filter_dropped: C,
115    /// Number of packets that could not be parsed and had to be dropped
116    /// without sending an ICMP response.
117    pub unparsable_packet: C,
118}
119
120impl<I: IpCountersIpExt> Inspectable for IpCounters<I> {
121    fn record<II: Inspector>(&self, inspector: &mut II) {
122        let IpCounters {
123            deliver_unicast,
124            deliver_multicast,
125            dispatch_receive_ip_packet,
126            drop_ip_packet_other_host,
127            receive_ip_packet,
128            send_ip_packet,
129            forwarding_disabled,
130            forward,
131            no_route_to_host,
132            mtu_exceeded,
133            ttl_expired,
134            receive_icmp_error,
135            fragment_reassembly_error,
136            need_more_fragments,
137            invalid_fragment,
138            fragment_cache_full,
139            parameter_problem,
140            unspecified_destination,
141            unspecified_source,
142            invalid_source,
143            dropped,
144            drop_for_tentative,
145            tx_illegal_loopback_address,
146            version_rx,
147            multicast_no_interest,
148            invalid_cached_conntrack_entry,
149            fragmentation,
150            gso,
151            socket_egress_filter_dropped,
152            unparsable_packet,
153        } = self;
154        inspector.record_child("PacketTx", |inspector| {
155            inspector.record_counter("Sent", send_ip_packet);
156            inspector.record_counter("IllegalLoopbackAddress", tx_illegal_loopback_address);
157            inspector.record_counter("SocketEgressFilterDropped", socket_egress_filter_dropped);
158        });
159        inspector.record_child("PacketRx", |inspector| {
160            inspector.record_counter("Received", receive_ip_packet);
161            inspector.record_counter("Dispatched", dispatch_receive_ip_packet);
162            inspector.record_counter("OtherHost", drop_ip_packet_other_host);
163            inspector.record_counter("ParameterProblem", parameter_problem);
164            inspector.record_counter("UnspecifiedDst", unspecified_destination);
165            inspector.record_counter("UnspecifiedSrc", unspecified_source);
166            inspector.record_counter("InvalidSrc", invalid_source);
167            inspector.record_counter("Dropped", dropped);
168            inspector.record_counter("DroppedTentativeDst", drop_for_tentative);
169            inspector.record_counter("MulticastNoInterest", multicast_no_interest);
170            inspector.record_counter("DeliveredUnicast", deliver_unicast);
171            inspector.record_counter("DeliveredMulticast", deliver_multicast);
172            inspector.record_counter("InvalidCachedConntrackEntry", invalid_cached_conntrack_entry);
173            inspector.record_counter("UnparsablePacket", unparsable_packet);
174            inspector.delegate_inspectable(version_rx);
175        });
176        inspector.record_child("Forwarding", |inspector| {
177            inspector.record_counter("Forwarded", forward);
178            inspector.record_counter("ForwardingDisabled", forwarding_disabled);
179            inspector.record_counter("NoRouteToHost", no_route_to_host);
180            inspector.record_counter("MtuExceeded", mtu_exceeded);
181            inspector.record_counter("TtlExpired", ttl_expired);
182        });
183        inspector.record_counter("RxIcmpError", receive_icmp_error);
184        inspector.record_child("FragmentsRx", |inspector| {
185            inspector.record_counter("ReassemblyError", fragment_reassembly_error);
186            inspector.record_counter("NeedMoreFragments", need_more_fragments);
187            inspector.record_counter("InvalidFragment", invalid_fragment);
188            inspector.record_counter("CacheFull", fragment_cache_full);
189        });
190        inspector.record_child("FragmentsTx", |inspector| {
191            let FragmentationCounters {
192                fragmentation_required,
193                fragments,
194                error_not_allowed,
195                error_mtu_too_small,
196                error_body_too_long,
197                error_inner_size_limit_exceeded,
198                error_fragmented_serializer,
199            } = fragmentation;
200            inspector.record_counter("FragmentationRequired", fragmentation_required);
201            inspector.record_counter("Fragments", fragments);
202            inspector.record_counter("ErrorNotAllowed", error_not_allowed);
203            inspector.record_counter("ErrorMtuTooSmall", error_mtu_too_small);
204            inspector.record_counter("ErrorBodyTooLong", error_body_too_long);
205            inspector
206                .record_counter("ErrorInnerSizeLimitExceeded", error_inner_size_limit_exceeded);
207            inspector.record_counter("ErrorFragmentedSerializer", error_fragmented_serializer);
208        });
209        inspector.record_child("SegmentsTx", |inspector| {
210            let GsoCounters { forwarded, local } = gso;
211            inspector.record_child("Forwarded", |inspector| {
212                inspector.delegate_inspectable(forwarded);
213            });
214            inspector.record_child("Local", |inspector| {
215                inspector.delegate_inspectable(local);
216            });
217        });
218    }
219}
220
221impl<C: CounterRepr> Inspectable for GsoSourceCounters<C> {
222    fn record<I: Inspector>(&self, inspector: &mut I) {
223        let Self {
224            segmentation_required,
225            segments,
226            error_parse,
227            error_not_segmentable,
228            error_unsupported_headers,
229            error_payload_too_long,
230        } = self;
231        inspector.record_counter("SegmentationRequired", segmentation_required);
232        inspector.record_counter("Segments", segments);
233        inspector.record_counter("ErrorParse", error_parse);
234        inspector.record_counter("ErrorNotSegmentable", error_not_segmentable);
235        inspector.record_counter("ErrorUnsupportedHeaders", error_unsupported_headers);
236        inspector.record_counter("ErrorPayloadTooLong", error_payload_too_long);
237    }
238}
239
240/// IPv4-specific Rx counters.
241#[derive(Default, Debug, netstack3_macros::CounterCollection)]
242#[cfg_attr(any(test, feature = "testutils"), derive(PartialEq,))]
243pub struct Ipv4RxCounters<C = Counter> {
244    /// Count of incoming broadcast IPv4 packets delivered.
245    pub deliver_broadcast: C,
246}
247
248impl<C: CounterRepr> Inspectable for Ipv4RxCounters<C> {
249    fn record<I: Inspector>(&self, inspector: &mut I) {
250        let Self { deliver_broadcast } = self;
251        inspector.record_counter("DeliveredBroadcast", deliver_broadcast);
252    }
253}
254
255/// IPv6-specific Rx counters.
256#[derive(Default, Debug, netstack3_macros::CounterCollection)]
257#[cfg_attr(any(test, feature = "testutils"), derive(PartialEq,))]
258pub struct Ipv6RxCounters<C = Counter> {
259    /// Count of incoming IPv6 packets discarded while processing extension
260    /// headers.
261    pub extension_header_discard: C,
262    /// Count of incoming neighbor solicitations discarded as looped-back
263    /// DAD probes.
264    pub drop_looped_back_dad_probe: C,
265}
266
267impl<C: CounterRepr> Inspectable for Ipv6RxCounters<C> {
268    fn record<I: Inspector>(&self, inspector: &mut I) {
269        let Self { extension_header_discard, drop_looped_back_dad_probe } = self;
270        inspector.record_counter("DroppedExtensionHeader", extension_header_discard);
271        inspector.record_counter("DroppedLoopedBackDadProbe", drop_looped_back_dad_probe);
272    }
273}
274
275#[cfg(any(test, feature = "testutils"))]
276pub mod testutil {
277    use super::*;
278
279    use netstack3_base::{CounterCollection, ResourceCounterContext};
280
281    /// Expected values of [`IpCounters<I>`].
282    pub type IpCounterExpectations<I> = IpCounters<I, u64>;
283
284    impl<I: IpCountersIpExt> IpCounterExpectations<I> {
285        /// Constructs the expected counter state when the given count of IP
286        /// packets have been received & dispatched.
287        pub fn expect_dispatched(count: u64) -> Self {
288            IpCounterExpectations {
289                receive_ip_packet: count,
290                dispatch_receive_ip_packet: count,
291                deliver_unicast: count,
292                ..Default::default()
293            }
294        }
295
296        /// Assert that the counters tracked by `core_ctx` match expectations.
297        #[track_caller]
298        pub fn assert_counters<D, CC: ResourceCounterContext<D, IpCounters<I>>>(
299            self,
300            core_ctx: &CC,
301            device: &D,
302        ) {
303            assert_eq!(core_ctx.counters().cast::<u64>(), self, "stack-wide counters");
304            assert_eq!(
305                core_ctx.per_resource_counters(device).cast::<u64>(),
306                self,
307                "per-device counters"
308            );
309        }
310    }
311}