netstack3_udp/
diagnostics.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
5use core::convert::Infallible as Never;
6use core::num::NonZeroU16;
7
8use net_types::ip::Ip;
9use netstack3_base::socket::SocketCookie;
10use netstack3_base::{
11    Marks, Matcher, MaybeSocketTransportProperties, SocketTransportProtocolMatcher,
12    UdpSocketProperties, UdpSocketState as UdpSocketMatcherState, WeakDeviceIdentifier,
13};
14use netstack3_datagram::{
15    DatagramSocketDiagnosticsSpec, IpExt, SocketInfo, SocketState as DatagramSocketState,
16};
17
18use crate::internal::base::{Udp, UdpBindingsTypes, UdpSocketId, UdpSocketState};
19
20/// Publicly-accessible diagnostic information about UDP sockets.
21//
22// The reason this isn't on the datagram API is that we don't have plans to
23// support other datagram socket types at this time.
24#[derive(Debug)]
25#[cfg_attr(any(test, feature = "testutils"), derive(PartialEq, Eq))]
26#[allow(missing_docs)]
27pub struct UdpSocketDiagnostics<I: Ip> {
28    pub state: UdpSocketDiagnosticTuple<I>,
29    pub cookie: SocketCookie,
30    pub marks: Marks,
31}
32
33/// UDP socket tuple information for diagnostics.
34#[derive(Debug)]
35#[cfg_attr(any(test, feature = "testutils"), derive(PartialEq, Eq))]
36#[allow(missing_docs)]
37pub enum UdpSocketDiagnosticTuple<I: Ip> {
38    Bound { src_addr: Option<I::Addr>, src_port: NonZeroU16 },
39    Connected { src_addr: I::Addr, src_port: NonZeroU16, dst_addr: I::Addr, dst_port: u16 },
40}
41
42impl<I: Ip> UdpSocketDiagnosticTuple<I> {
43    /// Returns the source address of the socket.
44    pub fn src_addr(&self) -> Option<I::Addr> {
45        match self {
46            Self::Bound { src_addr, src_port: _ } => *src_addr,
47            Self::Connected { src_addr, src_port: _, dst_addr: _, dst_port: _ } => Some(*src_addr),
48        }
49    }
50
51    /// Returns the source port of the socket.
52    pub fn src_port(&self) -> Option<NonZeroU16> {
53        match self {
54            Self::Bound { src_addr: _, src_port }
55            | Self::Connected { src_addr: _, src_port, dst_addr: _, dst_port: _ } => {
56                Some(*src_port)
57            }
58        }
59    }
60
61    /// Returns the destination address of the socket.
62    pub fn dst_addr(&self) -> Option<I::Addr> {
63        match self {
64            Self::Bound { src_addr: _, src_port: _ } => None,
65            Self::Connected { src_addr: _, src_port: _, dst_addr, dst_port: _ } => Some(*dst_addr),
66        }
67    }
68
69    /// Returns the destination port of the socket.
70    pub fn dst_port(&self) -> Option<u16> {
71        match self {
72            Self::Bound { src_addr: _, src_port: _ } => None,
73            Self::Connected { src_addr: _, src_port: _, dst_addr: _, dst_port } => Some(*dst_port),
74        }
75    }
76}
77
78/// A wrapper around [`UdpSocketState`], which is defined in
79/// `netstack3_datagram`, to allow implementing traits on it.
80pub struct UdpTransportProtocolDiagnosticsProperties<'a, I, D, BT>(&'a UdpSocketState<I, D, BT>)
81where
82    I: IpExt,
83    D: WeakDeviceIdentifier,
84    BT: UdpBindingsTypes;
85
86impl<I, D, BT> MaybeSocketTransportProperties
87    for UdpTransportProtocolDiagnosticsProperties<'_, I, D, BT>
88where
89    I: IpExt,
90    D: WeakDeviceIdentifier,
91    BT: UdpBindingsTypes,
92{
93    type TcpProps<'a>
94        = Never
95    where
96        Self: 'a;
97
98    type UdpProps<'a>
99        = Self
100    where
101        Self: 'a;
102
103    fn tcp_socket_properties(&self) -> Option<&Self::TcpProps<'_>> {
104        None
105    }
106
107    fn udp_socket_properties(&self) -> Option<&Self::UdpProps<'_>> {
108        Some(self)
109    }
110}
111
112impl<I, D, BT> UdpSocketProperties for UdpTransportProtocolDiagnosticsProperties<'_, I, D, BT>
113where
114    I: IpExt,
115    D: WeakDeviceIdentifier,
116    BT: UdpBindingsTypes,
117{
118    fn src_port_matches(&self, matcher: &netstack3_base::PortMatcher) -> bool {
119        let Self(udp_state) = self;
120        matcher.required_matches(udp_state.local_identifier().map(|p| p.get()).as_ref())
121    }
122
123    fn dst_port_matches(&self, matcher: &netstack3_base::PortMatcher) -> bool {
124        let Self(udp_state) = self;
125        matcher.required_matches(udp_state.remote_identifier().as_ref())
126    }
127
128    fn state_matches(&self, matcher: &netstack3_base::UdpStateMatcher) -> bool {
129        let Self(udp_state) = self;
130        matcher.matches(match udp_state.to_socket_info() {
131            SocketInfo::Unbound => return false,
132            SocketInfo::Listener(_) => &UdpSocketMatcherState::Bound,
133            SocketInfo::Connected(_) => &UdpSocketMatcherState::Connected,
134        })
135    }
136}
137
138impl<BT: UdpBindingsTypes> DatagramSocketDiagnosticsSpec for Udp<BT> {
139    type DeviceClass = BT::DeviceClass;
140
141    fn transport_protocol_matches<I: IpExt, D: WeakDeviceIdentifier>(
142        state: &DatagramSocketState<I, D, Udp<BT>>,
143        matcher: &SocketTransportProtocolMatcher,
144    ) -> bool {
145        matcher.matches(&UdpTransportProtocolDiagnosticsProperties(state))
146    }
147
148    fn cookie_matches<I: IpExt, D: WeakDeviceIdentifier>(
149        id: &UdpSocketId<I, D, BT>,
150        matcher: &netstack3_base::SocketCookieMatcher,
151    ) -> bool {
152        matcher.matches(&id.socket_cookie().export_value())
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use alloc::string::ToString;
159    use alloc::vec;
160    use alloc::vec::Vec;
161    use core::num::NonZeroU16;
162
163    use ip_test_macro::ip_test;
164    use net_types::ip::Subnet;
165    use net_types::{Witness as _, ZonedAddr};
166    use netstack3_base::testutil::FakeDeviceId;
167    use netstack3_base::{
168        AddressMatcher, AddressMatcherEither, AddressMatcherType, BoundInterfaceMatcher,
169        InterfaceMatcher, IpSocketMatcher, Mark, MarkDomain, MarkMatcher, PortMatcher,
170        SocketCookieMatcher, SocketTransportProtocolMatcher, SubnetMatcher, TcpSocketMatcher,
171        UdpSocketMatcher, UdpStateMatcher,
172    };
173
174    use crate::internal::base::testutils::{FakeUdpCoreCtx, TestIpExt, UdpFakeDeviceCtx};
175    use crate::internal::base::{UdpApi, UdpRemotePort};
176
177    use super::*;
178
179    const LOCAL_PORT_1: NonZeroU16 = NonZeroU16::new(1234).unwrap();
180    const LOCAL_PORT_2: NonZeroU16 = NonZeroU16::new(5678).unwrap();
181    const LOCAL_PORT_3: NonZeroU16 = NonZeroU16::new(4321).unwrap();
182
183    const REMOTE_PORT_1: NonZeroU16 = NonZeroU16::new(100).unwrap();
184    const REMOTE_PORT_2: NonZeroU16 = NonZeroU16::new(200).unwrap();
185
186    const MARK: u32 = 0x10;
187    const MARK_MASK: u32 = !0;
188
189    #[ip_test(I)]
190    fn diagnostics_match_ip_version<I: TestIpExt>() {
191        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
192        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
193
194        let socket = api.create();
195        api.listen(&socket, None, Some(LOCAL_PORT_1)).expect("listen should succeed");
196
197        let mut results = Vec::new();
198        api.bound_sockets_diagnostics(&IpSocketMatcher::Family(I::VERSION), &mut results);
199        assert_eq!(
200            results,
201            vec![UdpSocketDiagnostics {
202                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
203                cookie: socket.socket_cookie(),
204                marks: Marks::default(),
205            }]
206        );
207
208        results.clear();
209        api.bound_sockets_diagnostics(
210            &IpSocketMatcher::Family(
211                <<I as netstack3_base::socket::DualStackIpExt>::OtherVersion as Ip>::VERSION,
212            ),
213            &mut results,
214        );
215        assert_eq!(results, Vec::new());
216    }
217
218    #[ip_test(I)]
219    fn diagnostics_match_src_addr<I: TestIpExt>() {
220        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
221        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
222
223        let socket = api.create();
224        api.listen(&socket, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
225            .expect("listen should succeed");
226
227        let mut results = Vec::new();
228        let matcher = I::map_ip_in(
229            I::TEST_ADDRS.subnet,
230            |subnet| {
231                AddressMatcherEither::V4(AddressMatcher {
232                    matcher: AddressMatcherType::Subnet(SubnetMatcher(subnet)),
233                    invert: false,
234                })
235            },
236            |subnet| {
237                AddressMatcherEither::V6(AddressMatcher {
238                    matcher: AddressMatcherType::Subnet(SubnetMatcher(subnet)),
239                    invert: false,
240                })
241            },
242        );
243        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results);
244        assert_eq!(
245            results,
246            vec![UdpSocketDiagnostics {
247                state: UdpSocketDiagnosticTuple::Bound {
248                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
249                    src_port: LOCAL_PORT_1
250                },
251                cookie: socket.socket_cookie(),
252                marks: Marks::default(),
253            }]
254        );
255
256        results.clear();
257        let matcher = I::map_ip_in(
258            I::TEST_ADDRS.remote_ip.get(),
259            |addr| {
260                AddressMatcherEither::V4(AddressMatcher {
261                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
262                        Subnet::new(addr, 32).unwrap(),
263                    )),
264                    invert: false,
265                })
266            },
267            |addr| {
268                AddressMatcherEither::V6(AddressMatcher {
269                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
270                        Subnet::new(addr, 128).unwrap(),
271                    )),
272                    invert: false,
273                })
274            },
275        );
276        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results);
277        assert_eq!(results, Vec::new());
278    }
279
280    #[ip_test(I)]
281    fn diagnostics_match_dst_addr<I: TestIpExt>() {
282        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
283        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
284
285        let socket = api.create();
286        api.listen(&socket, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
287            .expect("listen should succeed");
288        api.connect(
289            &socket,
290            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
291            UdpRemotePort::Set(LOCAL_PORT_1),
292        )
293        .expect("connect should succeed");
294
295        let mut results = Vec::new();
296        let matcher = I::map_ip_in(
297            I::TEST_ADDRS.subnet,
298            |subnet| {
299                AddressMatcherEither::V4(AddressMatcher {
300                    matcher: AddressMatcherType::Subnet(SubnetMatcher(subnet)),
301                    invert: false,
302                })
303            },
304            |subnet| {
305                AddressMatcherEither::V6(AddressMatcher {
306                    matcher: AddressMatcherType::Subnet(SubnetMatcher(subnet)),
307                    invert: false,
308                })
309            },
310        );
311        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results);
312        assert_eq!(
313            results,
314            vec![UdpSocketDiagnostics {
315                state: UdpSocketDiagnosticTuple::Connected {
316                    src_addr: I::TEST_ADDRS.local_ip.get(),
317                    src_port: LOCAL_PORT_2,
318                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
319                    dst_port: LOCAL_PORT_1.get(),
320                },
321                cookie: socket.socket_cookie(),
322                marks: Marks::default(),
323            }]
324        );
325
326        results.clear();
327        let matcher = I::map_ip_in(
328            I::TEST_ADDRS.local_ip.get(),
329            |addr| {
330                AddressMatcherEither::V4(AddressMatcher {
331                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
332                        Subnet::new(addr, 32).unwrap(),
333                    )),
334                    invert: false,
335                })
336            },
337            |addr| {
338                AddressMatcherEither::V6(AddressMatcher {
339                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
340                        Subnet::new(addr, 128).unwrap(),
341                    )),
342                    invert: false,
343                })
344            },
345        );
346        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results);
347        assert_eq!(results, Vec::new());
348    }
349
350    #[ip_test(I)]
351    fn diagnostics_match_proto<I: TestIpExt>() {
352        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
353        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
354
355        let socket = api.create();
356        api.listen(&socket, None, Some(LOCAL_PORT_1)).expect("listen should succeed");
357
358        let mut results = Vec::new();
359        api.bound_sockets_diagnostics(
360            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(UdpSocketMatcher::Empty)),
361            &mut results,
362        );
363        assert_eq!(
364            results,
365            vec![UdpSocketDiagnostics {
366                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
367                cookie: socket.socket_cookie(),
368                marks: Marks::default(),
369            }]
370        );
371
372        results.clear();
373        api.bound_sockets_diagnostics(
374            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(TcpSocketMatcher::Empty)),
375            &mut results,
376        );
377        assert_eq!(results, Vec::new());
378    }
379
380    #[ip_test(I)]
381    fn diagnostics_match_src_port<I: TestIpExt>() {
382        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
383        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
384
385        let socket = api.create();
386        api.listen(&socket, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
387            .expect("listen should succeed");
388
389        let mut results = Vec::new();
390        api.bound_sockets_diagnostics(
391            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
392                UdpSocketMatcher::SrcPort(PortMatcher {
393                    range: LOCAL_PORT_1.get()..=LOCAL_PORT_1.get(),
394                    invert: false,
395                }),
396            )),
397            &mut results,
398        );
399        assert_eq!(
400            results,
401            vec![UdpSocketDiagnostics {
402                state: UdpSocketDiagnosticTuple::Bound {
403                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
404                    src_port: LOCAL_PORT_1
405                },
406                cookie: socket.socket_cookie(),
407                marks: Marks::default(),
408            }]
409        );
410
411        results.clear();
412        api.bound_sockets_diagnostics(
413            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
414                UdpSocketMatcher::SrcPort(PortMatcher {
415                    range: (LOCAL_PORT_1.get() + 1)..=(LOCAL_PORT_1.get() + 1),
416                    invert: false,
417                }),
418            )),
419            &mut results,
420        );
421        assert_eq!(results, Vec::new());
422    }
423
424    #[ip_test(I)]
425    fn diagnostics_match_dst_port<I: TestIpExt>() {
426        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
427        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
428
429        let socket = api.create();
430        api.listen(&socket, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
431            .expect("listen should succeed");
432        api.connect(
433            &socket,
434            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
435            UdpRemotePort::Set(LOCAL_PORT_1),
436        )
437        .expect("connect should succeed");
438
439        let mut results = Vec::new();
440        api.bound_sockets_diagnostics(
441            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
442                UdpSocketMatcher::DstPort(PortMatcher {
443                    range: LOCAL_PORT_1.get()..=LOCAL_PORT_1.get(),
444                    invert: false,
445                }),
446            )),
447            &mut results,
448        );
449        assert_eq!(
450            results,
451            vec![UdpSocketDiagnostics {
452                state: UdpSocketDiagnosticTuple::Connected {
453                    src_addr: I::TEST_ADDRS.local_ip.get(),
454                    src_port: LOCAL_PORT_2,
455                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
456                    dst_port: LOCAL_PORT_1.get(),
457                },
458                cookie: socket.socket_cookie(),
459                marks: Marks::default(),
460            }]
461        );
462
463        results.clear();
464        api.bound_sockets_diagnostics(
465            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
466                UdpSocketMatcher::DstPort(PortMatcher {
467                    range: (LOCAL_PORT_1.get() + 1)..=(LOCAL_PORT_1.get() + 1),
468                    invert: false,
469                }),
470            )),
471            &mut results,
472        );
473        assert_eq!(results, Vec::new());
474    }
475
476    #[ip_test(I)]
477    fn diagnostics_match_state<I: TestIpExt>() {
478        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
479        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
480
481        let socket_1 = api.create();
482        api.listen(&socket_1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
483            .expect("listen should succeed");
484
485        let socket_2 = api.create();
486        api.listen(&socket_2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
487            .expect("listen should succeed");
488        api.connect(
489            &socket_2,
490            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
491            UdpRemotePort::Set(LOCAL_PORT_3),
492        )
493        .expect("connect should succeed");
494
495        let mut results = Vec::new();
496        api.bound_sockets_diagnostics(
497            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(UdpSocketMatcher::State(
498                UdpStateMatcher::BOUND,
499            ))),
500            &mut results,
501        );
502        assert_eq!(
503            results,
504            vec![UdpSocketDiagnostics {
505                state: UdpSocketDiagnosticTuple::Bound {
506                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
507                    src_port: LOCAL_PORT_1
508                },
509                cookie: socket_1.socket_cookie(),
510                marks: Marks::default(),
511            }]
512        );
513
514        results.clear();
515        api.bound_sockets_diagnostics(
516            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(UdpSocketMatcher::State(
517                UdpStateMatcher::CONNECTED,
518            ))),
519            &mut results,
520        );
521        assert_eq!(
522            results,
523            vec![UdpSocketDiagnostics {
524                state: UdpSocketDiagnosticTuple::Connected {
525                    src_addr: I::TEST_ADDRS.local_ip.get(),
526                    src_port: LOCAL_PORT_2,
527                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
528                    dst_port: LOCAL_PORT_3.get(),
529                },
530                cookie: socket_2.socket_cookie(),
531                marks: Marks::default(),
532            }]
533        );
534    }
535
536    #[ip_test(I)]
537    fn diagnostics_match_device<I: TestIpExt>() {
538        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
539        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
540
541        let socket = api.create();
542        api.set_device(&socket, Some(&FakeDeviceId)).expect("set device should succeed");
543        api.listen(&socket, None, Some(LOCAL_PORT_1)).expect("listen should succeed");
544
545        let mut results = Vec::new();
546        api.bound_sockets_diagnostics(
547            &IpSocketMatcher::BoundInterface(BoundInterfaceMatcher::Bound(InterfaceMatcher::Name(
548                FakeDeviceId::FAKE_NAME.to_string(),
549            ))),
550            &mut results,
551        );
552        assert_eq!(
553            results,
554            vec![UdpSocketDiagnostics {
555                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
556                cookie: socket.socket_cookie(),
557                marks: Marks::default(),
558            }]
559        );
560
561        results.clear();
562        api.bound_sockets_diagnostics(
563            &IpSocketMatcher::BoundInterface(BoundInterfaceMatcher::Unbound),
564            &mut results,
565        );
566        assert_eq!(results, Vec::new());
567    }
568
569    #[ip_test(I)]
570    fn diagnostics_match_cookie<I: TestIpExt>() {
571        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
572        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
573
574        let socket = api.create();
575        api.listen(&socket, None, Some(LOCAL_PORT_1)).expect("listen should succeed");
576
577        let mut results = Vec::new();
578        api.bound_sockets_diagnostics(
579            &IpSocketMatcher::Cookie(SocketCookieMatcher {
580                cookie: socket.socket_cookie().export_value(),
581                invert: false,
582            }),
583            &mut results,
584        );
585        assert_eq!(
586            results,
587            vec![UdpSocketDiagnostics {
588                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
589                cookie: socket.socket_cookie(),
590                marks: Marks::default(),
591            }]
592        );
593
594        results.clear();
595        api.bound_sockets_diagnostics(
596            &IpSocketMatcher::Cookie(SocketCookieMatcher {
597                cookie: socket.socket_cookie().export_value() + 1,
598                invert: false,
599            }),
600            &mut results,
601        );
602        assert_eq!(results, Vec::new());
603    }
604
605    #[ip_test(I)]
606    #[test_case::test_case(MarkDomain::Mark1; "mark_1")]
607    #[test_case::test_case(MarkDomain::Mark2; "mark_2")]
608    fn diagnostics_match_mark<I: TestIpExt>(domain: MarkDomain) {
609        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
610        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
611
612        let socket = api.create();
613        api.listen(&socket, None, Some(LOCAL_PORT_1)).expect("listen should succeed");
614
615        api.set_mark(&socket, domain, Mark(Some(MARK)));
616
617        let mut results = Vec::new();
618        let matcher = |query_mark| {
619            IpSocketMatcher::Mark(netstack3_base::MarkInDomainMatcher {
620                domain,
621                matcher: MarkMatcher::Marked {
622                    mask: MARK_MASK,
623                    start: query_mark,
624                    end: query_mark,
625                    invert: false,
626                },
627            })
628        };
629        api.bound_sockets_diagnostics(&matcher(MARK), &mut results);
630        assert_eq!(
631            results,
632            vec![UdpSocketDiagnostics {
633                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
634                cookie: socket.socket_cookie(),
635                marks: netstack3_base::MarkStorage::new([(domain, MARK)]),
636            }]
637        );
638
639        results.clear();
640        api.bound_sockets_diagnostics(&matcher(MARK + 1), &mut results);
641        assert_eq!(results, Vec::new());
642    }
643
644    /// Create three sockets, two of which target the same remote port, and make
645    /// sure that multiple matching sockets are returned.
646    #[ip_test(I)]
647    fn diagnostics_match_multiple<I: TestIpExt>() {
648        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
649        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
650
651        let socket_1 = api.create();
652        api.listen(&socket_1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
653            .expect("listen should succeed");
654        api.connect(
655            &socket_1,
656            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
657            UdpRemotePort::Set(REMOTE_PORT_1),
658        )
659        .expect("connect should succeed");
660
661        let socket_2 = api.create();
662        api.listen(&socket_2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
663            .expect("listen should succeed");
664        api.connect(
665            &socket_2,
666            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
667            UdpRemotePort::Set(REMOTE_PORT_1),
668        )
669        .expect("connect should succeed");
670
671        let socket_3 = api.create();
672        api.listen(&socket_3, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_3))
673            .expect("listen should succeed");
674        api.connect(
675            &socket_3,
676            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
677            UdpRemotePort::Set(REMOTE_PORT_2),
678        )
679        .expect("connect should succeed");
680
681        let mut results = Vec::new();
682        api.bound_sockets_diagnostics(
683            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
684                UdpSocketMatcher::DstPort(PortMatcher {
685                    range: REMOTE_PORT_1.get()..=REMOTE_PORT_1.get(),
686                    invert: false,
687                }),
688            )),
689            &mut results,
690        );
691
692        results.sort_by(|a, b| a.cookie.cmp(&b.cookie));
693        let mut expected = vec![
694            UdpSocketDiagnostics {
695                state: UdpSocketDiagnosticTuple::Connected {
696                    src_addr: I::TEST_ADDRS.local_ip.get(),
697                    src_port: LOCAL_PORT_1,
698                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
699                    dst_port: REMOTE_PORT_1.get(),
700                },
701                cookie: socket_1.socket_cookie(),
702                marks: Marks::default(),
703            },
704            UdpSocketDiagnostics {
705                state: UdpSocketDiagnosticTuple::Connected {
706                    src_addr: I::TEST_ADDRS.local_ip.get(),
707                    src_port: LOCAL_PORT_2,
708                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
709                    dst_port: REMOTE_PORT_1.get(),
710                },
711                cookie: socket_2.socket_cookie(),
712                marks: Marks::default(),
713            },
714        ];
715        expected.sort_by(|a, b| a.cookie.cmp(&b.cookie));
716        assert_eq!(results, expected);
717
718        results.clear();
719        api.bound_sockets_diagnostics(
720            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
721                UdpSocketMatcher::DstPort(PortMatcher {
722                    range: REMOTE_PORT_2.get()..=REMOTE_PORT_2.get(),
723                    invert: false,
724                }),
725            )),
726            &mut results,
727        );
728        assert_eq!(
729            results,
730            vec![UdpSocketDiagnostics {
731                state: UdpSocketDiagnosticTuple::Connected {
732                    src_addr: I::TEST_ADDRS.local_ip.get(),
733                    src_port: LOCAL_PORT_3,
734                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
735                    dst_port: REMOTE_PORT_2.get(),
736                },
737                cookie: socket_3.socket_cookie(),
738                marks: Marks::default(),
739            }]
740        );
741    }
742}