Skip to main content

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::Witness as _;
9use net_types::ip::Ip;
10use netstack3_base::socket::SocketCookie;
11use netstack3_base::{
12    Marks, Matcher, MaybeSocketTransportProperties, SocketDiagnosticsSeed,
13    SocketTransportProtocolMatcher, UdpSocketProperties, UdpSocketState as UdpSocketMatcherState,
14    WeakDeviceIdentifier,
15};
16use netstack3_datagram::{
17    ConnInfo, DatagramSocketDiagnosticsSpec, IpExt, ListenerInfo, SocketInfo,
18    SocketState as DatagramSocketState,
19};
20
21use crate::internal::base::{Udp, UdpBindingsTypes, UdpSocketId, UdpSocketState};
22
23/// Publicly-accessible diagnostic information about UDP sockets.
24//
25// The reason this isn't on the datagram API is that we don't have plans to
26// support other datagram socket types at this time.
27#[derive(Debug)]
28#[cfg_attr(any(test, feature = "testutils"), derive(PartialEq, Eq))]
29#[allow(missing_docs)]
30pub struct UdpSocketDiagnostics<I: Ip> {
31    pub state: UdpSocketDiagnosticTuple<I>,
32    pub cookie: SocketCookie,
33    pub marks: Marks,
34}
35
36impl<I: IpExt> UdpSocketDiagnostics<I> {
37    pub(crate) fn from_parts<D: WeakDeviceIdentifier, BT: UdpBindingsTypes>(
38        socket_state: &UdpSocketState<I, D, BT>,
39        cookie: SocketCookie,
40    ) -> Option<Self> {
41        let state = UdpSocketDiagnosticTuple::from_socket_info(socket_state.to_socket_info())?;
42        let marks = *socket_state.options().marks();
43
44        Some(Self { state, cookie, marks })
45    }
46}
47
48/// All of the information required to compute [`UdpSocketDiagnostics`]. Gives
49/// the owner control over when (or if) the transformation happens.
50pub struct UdpSocketDiagnosticsSeed<I, D, BT>
51where
52    I: IpExt,
53    D: WeakDeviceIdentifier,
54    BT: UdpBindingsTypes,
55{
56    pub(crate) state: UdpSocketState<I, D, BT>,
57    pub(crate) cookie: SocketCookie,
58}
59
60impl<I, D, BT> SocketDiagnosticsSeed for UdpSocketDiagnosticsSeed<I, D, BT>
61where
62    I: IpExt,
63    D: WeakDeviceIdentifier,
64    BT: UdpBindingsTypes,
65{
66    type Output = UdpSocketDiagnostics<I>;
67
68    fn resolve(self) -> Option<UdpSocketDiagnostics<I>> {
69        let Self { state, cookie } = self;
70        UdpSocketDiagnostics::from_parts(&state, cookie)
71    }
72}
73
74/// UDP socket tuple information for diagnostics.
75#[derive(Debug)]
76#[cfg_attr(any(test, feature = "testutils"), derive(PartialEq, Eq))]
77#[allow(missing_docs)]
78pub enum UdpSocketDiagnosticTuple<I: Ip> {
79    Bound { src_addr: Option<I::Addr>, src_port: NonZeroU16 },
80    Connected { src_addr: I::Addr, src_port: NonZeroU16, dst_addr: I::Addr, dst_port: u16 },
81}
82
83impl<I: Ip> UdpSocketDiagnosticTuple<I> {
84    fn from_socket_info<D: WeakDeviceIdentifier>(info: SocketInfo<I::Addr, D>) -> Option<Self> {
85        match info {
86            // We don't return unbound sockets to match Linux's behavior (for
87            // now, at least).
88            SocketInfo::Unbound => None,
89            SocketInfo::Listener(ListenerInfo { local_ip, local_identifier }) => {
90                Some(Self::Bound {
91                    src_addr: local_ip.map(|ip| ip.into_inner().addr().get()),
92                    src_port: local_identifier,
93                })
94            }
95            SocketInfo::Connected(ConnInfo {
96                local_ip,
97                local_identifier,
98                remote_ip,
99                remote_identifier,
100            }) => Some(Self::Connected {
101                src_addr: local_ip.into_inner().addr().get(),
102                src_port: local_identifier,
103                dst_addr: remote_ip.into_inner().addr().get(),
104                dst_port: remote_identifier,
105            }),
106        }
107    }
108
109    /// Returns the source address of the socket.
110    pub fn src_addr(&self) -> Option<I::Addr> {
111        match self {
112            Self::Bound { src_addr, src_port: _ } => *src_addr,
113            Self::Connected { src_addr, src_port: _, dst_addr: _, dst_port: _ } => Some(*src_addr),
114        }
115    }
116
117    /// Returns the source port of the socket.
118    pub fn src_port(&self) -> Option<NonZeroU16> {
119        match self {
120            Self::Bound { src_addr: _, src_port }
121            | Self::Connected { src_addr: _, src_port, dst_addr: _, dst_port: _ } => {
122                Some(*src_port)
123            }
124        }
125    }
126
127    /// Returns the destination address of the socket.
128    pub fn dst_addr(&self) -> Option<I::Addr> {
129        match self {
130            Self::Bound { src_addr: _, src_port: _ } => None,
131            Self::Connected { src_addr: _, src_port: _, dst_addr, dst_port: _ } => Some(*dst_addr),
132        }
133    }
134
135    /// Returns the destination port of the socket.
136    pub fn dst_port(&self) -> Option<u16> {
137        match self {
138            Self::Bound { src_addr: _, src_port: _ } => None,
139            Self::Connected { src_addr: _, src_port: _, dst_addr: _, dst_port } => Some(*dst_port),
140        }
141    }
142}
143
144/// A wrapper around [`UdpSocketState`], which is defined in
145/// `netstack3_datagram`, to allow implementing traits on it.
146pub struct UdpTransportProtocolDiagnosticsProperties<'a, I, D, BT>(&'a UdpSocketState<I, D, BT>)
147where
148    I: IpExt,
149    D: WeakDeviceIdentifier,
150    BT: UdpBindingsTypes;
151
152impl<I, D, BT> MaybeSocketTransportProperties
153    for UdpTransportProtocolDiagnosticsProperties<'_, I, D, BT>
154where
155    I: IpExt,
156    D: WeakDeviceIdentifier,
157    BT: UdpBindingsTypes,
158{
159    type TcpProps<'a>
160        = Never
161    where
162        Self: 'a;
163
164    type UdpProps<'a>
165        = Self
166    where
167        Self: 'a;
168
169    fn tcp_socket_properties(&self) -> Option<&Self::TcpProps<'_>> {
170        None
171    }
172
173    fn udp_socket_properties(&self) -> Option<&Self::UdpProps<'_>> {
174        Some(self)
175    }
176}
177
178impl<I, D, BT> UdpSocketProperties for UdpTransportProtocolDiagnosticsProperties<'_, I, D, BT>
179where
180    I: IpExt,
181    D: WeakDeviceIdentifier,
182    BT: UdpBindingsTypes,
183{
184    fn src_port_matches(&self, matcher: &netstack3_base::BoundPortMatcher) -> bool {
185        let Self(udp_state) = self;
186        matcher.matches(&udp_state.local_identifier().map(|p| p.get()))
187    }
188
189    fn dst_port_matches(&self, matcher: &netstack3_base::BoundPortMatcher) -> bool {
190        let Self(udp_state) = self;
191        matcher.matches(&udp_state.remote_identifier())
192    }
193
194    fn state_matches(&self, matcher: &netstack3_base::UdpStateMatcher) -> bool {
195        let Self(udp_state) = self;
196        matcher.matches(match udp_state.to_socket_info() {
197            SocketInfo::Unbound => return false,
198            SocketInfo::Listener(_) => &UdpSocketMatcherState::Bound,
199            SocketInfo::Connected(_) => &UdpSocketMatcherState::Connected,
200        })
201    }
202}
203
204impl<BT: UdpBindingsTypes> DatagramSocketDiagnosticsSpec for Udp<BT> {
205    type DeviceClass = BT::DeviceClass;
206
207    fn transport_protocol_matches<I: IpExt, D: WeakDeviceIdentifier>(
208        state: &DatagramSocketState<I, D, Udp<BT>>,
209        matcher: &SocketTransportProtocolMatcher,
210    ) -> bool {
211        matcher.matches(&UdpTransportProtocolDiagnosticsProperties(state))
212    }
213
214    fn cookie_matches<I: IpExt, D: WeakDeviceIdentifier>(
215        id: &UdpSocketId<I, D, BT>,
216        matcher: &netstack3_base::SocketCookieMatcher,
217    ) -> bool {
218        matcher.matches(&id.socket_cookie().export_value())
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use alloc::string::ToString;
225    use alloc::vec;
226    use alloc::vec::Vec;
227    use assert_matches::assert_matches;
228    use core::num::NonZeroU16;
229
230    use ip_test_macro::ip_test;
231    use net_types::ip::Subnet;
232    use net_types::{ZonedAddr};
233    use netstack3_base::testutil::{FakeDeviceId, set_logger_for_test};
234    use netstack3_base::{
235        AddressMatcher, AddressMatcherEither, AddressMatcherType, BoundAddressMatcherEither,
236        BoundInterfaceMatcher, BoundPortMatcher, InterfaceMatcher, IpSocketMatcher, Mark,
237        MarkDomain, MarkMatcher, PortMatcher, SocketCookieMatcher, SocketTransportProtocolMatcher,
238        SubnetMatcher, TcpSocketMatcher, UdpSocketMatcher, UdpStateMatcher,
239    };
240
241    use crate::internal::base::testutils::{FakeUdpCoreCtx, TestIpExt, UdpFakeDeviceCtx};
242    use crate::internal::base::{UdpApi, UdpRemotePort};
243    use netstack3_datagram::{ConnInfo, ListenerInfo};
244
245    use super::*;
246
247    const LOCAL_PORT_1: NonZeroU16 = NonZeroU16::new(1234).unwrap();
248    const LOCAL_PORT_2: NonZeroU16 = NonZeroU16::new(5678).unwrap();
249    const LOCAL_PORT_3: NonZeroU16 = NonZeroU16::new(4321).unwrap();
250
251    const REMOTE_PORT_1: NonZeroU16 = NonZeroU16::new(100).unwrap();
252    const REMOTE_PORT_2: NonZeroU16 = NonZeroU16::new(200).unwrap();
253
254    const MARK: u32 = 0x10;
255    const MARK_MASK: u32 = !0;
256
257    #[ip_test(I)]
258    fn diagnostics_match_ip_version<I: TestIpExt>() {
259        set_logger_for_test();
260
261        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
262        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
263
264        let socket = api.create();
265        api.listen(&socket, None, Some(LOCAL_PORT_1)).expect("listen should succeed");
266
267        let mut results = Vec::new();
268        api.bound_sockets_diagnostics(&IpSocketMatcher::Family(I::VERSION), &mut results);
269        assert_eq!(
270            results,
271            vec![UdpSocketDiagnostics {
272                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
273                cookie: socket.socket_cookie(),
274                marks: Marks::default(),
275            }]
276        );
277
278        results.clear();
279        api.bound_sockets_diagnostics(
280            &IpSocketMatcher::Family(
281                <<I as netstack3_base::socket::DualStackIpExt>::OtherVersion as Ip>::VERSION,
282            ),
283            &mut results,
284        );
285        assert_eq!(results, Vec::new());
286    }
287
288    #[ip_test(I)]
289    fn diagnostics_match_src_addr<I: TestIpExt>() {
290        set_logger_for_test();
291
292        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
293        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
294
295        let socket = api.create();
296        api.listen(&socket, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
297            .expect("listen should succeed");
298
299        let mut results = Vec::new();
300        let matcher = I::map_ip_in(
301            I::TEST_ADDRS.subnet,
302            |subnet| {
303                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
304                    matcher: AddressMatcherType::Subnet(SubnetMatcher(subnet)),
305                    invert: false,
306                }))
307            },
308            |subnet| {
309                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
310                    matcher: AddressMatcherType::Subnet(SubnetMatcher(subnet)),
311                    invert: false,
312                }))
313            },
314        );
315        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results);
316        assert_eq!(
317            results,
318            vec![UdpSocketDiagnostics {
319                state: UdpSocketDiagnosticTuple::Bound {
320                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
321                    src_port: LOCAL_PORT_1
322                },
323                cookie: socket.socket_cookie(),
324                marks: Marks::default(),
325            }]
326        );
327
328        results.clear();
329        let matcher = I::map_ip_in(
330            I::TEST_ADDRS.remote_ip.get(),
331            |addr| {
332                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
333                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
334                        Subnet::new(addr, 32).unwrap(),
335                    )),
336                    invert: false,
337                }))
338            },
339            |addr| {
340                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
341                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
342                        Subnet::new(addr, 128).unwrap(),
343                    )),
344                    invert: false,
345                }))
346            },
347        );
348        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results);
349        assert_eq!(results, Vec::new());
350    }
351
352    #[ip_test(I)]
353    fn diagnostics_match_src_addr_unbound<I: TestIpExt>() {
354        set_logger_for_test();
355
356        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
357        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
358
359        let socket1 = api.create();
360        api.listen(&socket1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
361            .expect("listen should succeed");
362
363        let socket2 = api.create();
364        api.listen(&socket2, None, Some(LOCAL_PORT_2)).expect("listen should succeed");
365
366        let mut results = Vec::new();
367        let matcher = match I::VERSION {
368            net_types::ip::IpVersion::V4 => BoundAddressMatcherEither::Unbound,
369            net_types::ip::IpVersion::V6 => BoundAddressMatcherEither::Unbound,
370        };
371        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results);
372        assert_eq!(
373            results,
374            vec![UdpSocketDiagnostics {
375                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_2 },
376                cookie: socket2.socket_cookie(),
377                marks: Marks::default(),
378            }]
379        );
380
381        results.clear();
382        let matcher = I::map_ip_in(
383            I::TEST_ADDRS.subnet,
384            |subnet| {
385                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
386                    matcher: AddressMatcherType::Subnet(SubnetMatcher(subnet)),
387                    invert: false,
388                }))
389            },
390            |subnet| {
391                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
392                    matcher: AddressMatcherType::Subnet(SubnetMatcher(subnet)),
393                    invert: false,
394                }))
395            },
396        );
397        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results);
398        assert_eq!(
399            results,
400            vec![UdpSocketDiagnostics {
401                state: UdpSocketDiagnosticTuple::Bound {
402                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
403                    src_port: LOCAL_PORT_1
404                },
405                cookie: socket1.socket_cookie(),
406                marks: Marks::default(),
407            }]
408        );
409    }
410
411    #[ip_test(I)]
412    fn diagnostics_match_dst_addr<I: TestIpExt>() {
413        set_logger_for_test();
414
415        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
416        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
417
418        let socket = api.create();
419        api.listen(&socket, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
420            .expect("listen should succeed");
421        api.connect(
422            &socket,
423            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
424            UdpRemotePort::Set(LOCAL_PORT_1),
425        )
426        .expect("connect should succeed");
427
428        let mut results = Vec::new();
429        let matcher = I::map_ip_in(
430            I::TEST_ADDRS.subnet,
431            |subnet| {
432                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
433                    matcher: AddressMatcherType::Subnet(SubnetMatcher(subnet)),
434                    invert: false,
435                }))
436            },
437            |subnet| {
438                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
439                    matcher: AddressMatcherType::Subnet(SubnetMatcher(subnet)),
440                    invert: false,
441                }))
442            },
443        );
444        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results);
445        assert_eq!(
446            results,
447            vec![UdpSocketDiagnostics {
448                state: UdpSocketDiagnosticTuple::Connected {
449                    src_addr: I::TEST_ADDRS.local_ip.get(),
450                    src_port: LOCAL_PORT_2,
451                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
452                    dst_port: LOCAL_PORT_1.get(),
453                },
454                cookie: socket.socket_cookie(),
455                marks: Marks::default(),
456            }]
457        );
458
459        results.clear();
460        let matcher = I::map_ip_in(
461            I::TEST_ADDRS.local_ip.get(),
462            |addr| {
463                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
464                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
465                        Subnet::new(addr, 32).unwrap(),
466                    )),
467                    invert: false,
468                }))
469            },
470            |addr| {
471                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
472                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
473                        Subnet::new(addr, 128).unwrap(),
474                    )),
475                    invert: false,
476                }))
477            },
478        );
479        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results);
480        assert_eq!(results, Vec::new());
481    }
482
483    #[ip_test(I)]
484    fn diagnostics_match_dst_addr_unbound<I: TestIpExt>() {
485        set_logger_for_test();
486
487        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
488        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
489
490        let socket1 = api.create();
491        api.listen(&socket1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
492            .expect("listen should succeed");
493        api.connect(
494            &socket1,
495            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
496            UdpRemotePort::Set(REMOTE_PORT_1),
497        )
498        .expect("connect should succeed");
499
500        let socket2 = api.create();
501        api.listen(&socket2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
502            .expect("listen should succeed");
503
504        let mut results = Vec::new();
505        let matcher = match I::VERSION {
506            net_types::ip::IpVersion::V4 => BoundAddressMatcherEither::Unbound,
507            net_types::ip::IpVersion::V6 => BoundAddressMatcherEither::Unbound,
508        };
509        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results);
510        assert_eq!(
511            results,
512            vec![UdpSocketDiagnostics {
513                state: UdpSocketDiagnosticTuple::Bound {
514                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
515                    src_port: LOCAL_PORT_2
516                },
517                cookie: socket2.socket_cookie(),
518                marks: Marks::default(),
519            }]
520        );
521
522        results.clear();
523        let matcher = I::map_ip_in(
524            I::TEST_ADDRS.remote_ip.get(),
525            |addr| {
526                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
527                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
528                        Subnet::new(addr, 32).unwrap(),
529                    )),
530                    invert: false,
531                }))
532            },
533            |addr| {
534                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
535                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
536                        Subnet::new(addr, 128).unwrap(),
537                    )),
538                    invert: false,
539                }))
540            },
541        );
542        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results);
543        assert_eq!(
544            results,
545            vec![UdpSocketDiagnostics {
546                state: UdpSocketDiagnosticTuple::Connected {
547                    src_addr: I::TEST_ADDRS.local_ip.get(),
548                    src_port: LOCAL_PORT_1,
549                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
550                    dst_port: REMOTE_PORT_1.get(),
551                },
552                cookie: socket1.socket_cookie(),
553                marks: Marks::default(),
554            }]
555        );
556    }
557
558    #[ip_test(I)]
559    fn diagnostics_match_proto<I: TestIpExt>() {
560        set_logger_for_test();
561
562        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
563        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
564
565        let socket = api.create();
566        api.listen(&socket, None, Some(LOCAL_PORT_1)).expect("listen should succeed");
567
568        let mut results = Vec::new();
569        api.bound_sockets_diagnostics(
570            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(UdpSocketMatcher::Empty)),
571            &mut results,
572        );
573        assert_eq!(
574            results,
575            vec![UdpSocketDiagnostics {
576                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
577                cookie: socket.socket_cookie(),
578                marks: Marks::default(),
579            }]
580        );
581
582        results.clear();
583        api.bound_sockets_diagnostics(
584            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(TcpSocketMatcher::Empty)),
585            &mut results,
586        );
587        assert_eq!(results, Vec::new());
588    }
589
590    #[ip_test(I)]
591    fn diagnostics_match_src_port<I: TestIpExt>() {
592        set_logger_for_test();
593
594        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
595        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
596
597        let socket = api.create();
598        api.listen(&socket, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
599            .expect("listen should succeed");
600
601        let mut results = Vec::new();
602        api.bound_sockets_diagnostics(
603            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
604                UdpSocketMatcher::SrcPort(BoundPortMatcher::Bound(PortMatcher {
605                    range: LOCAL_PORT_1.get()..=LOCAL_PORT_1.get(),
606                    invert: false,
607                })),
608            )),
609            &mut results,
610        );
611        assert_eq!(
612            results,
613            vec![UdpSocketDiagnostics {
614                state: UdpSocketDiagnosticTuple::Bound {
615                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
616                    src_port: LOCAL_PORT_1
617                },
618                cookie: socket.socket_cookie(),
619                marks: Marks::default(),
620            }]
621        );
622
623        results.clear();
624        api.bound_sockets_diagnostics(
625            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
626                UdpSocketMatcher::SrcPort(BoundPortMatcher::Bound(PortMatcher {
627                    range: (LOCAL_PORT_1.get() + 1)..=(LOCAL_PORT_1.get() + 1),
628                    invert: false,
629                })),
630            )),
631            &mut results,
632        );
633        assert_eq!(results, Vec::new());
634    }
635
636    #[ip_test(I)]
637    fn diagnostics_match_dst_port<I: TestIpExt>() {
638        set_logger_for_test();
639
640        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
641        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
642
643        let socket = api.create();
644        api.listen(&socket, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
645            .expect("listen should succeed");
646        api.connect(
647            &socket,
648            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
649            UdpRemotePort::Set(LOCAL_PORT_1),
650        )
651        .expect("connect should succeed");
652
653        let mut results = Vec::new();
654        api.bound_sockets_diagnostics(
655            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
656                UdpSocketMatcher::DstPort(BoundPortMatcher::Bound(PortMatcher {
657                    range: LOCAL_PORT_1.get()..=LOCAL_PORT_1.get(),
658                    invert: false,
659                })),
660            )),
661            &mut results,
662        );
663        assert_eq!(
664            results,
665            vec![UdpSocketDiagnostics {
666                state: UdpSocketDiagnosticTuple::Connected {
667                    src_addr: I::TEST_ADDRS.local_ip.get(),
668                    src_port: LOCAL_PORT_2,
669                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
670                    dst_port: LOCAL_PORT_1.get(),
671                },
672                cookie: socket.socket_cookie(),
673                marks: Marks::default(),
674            }]
675        );
676
677        results.clear();
678        api.bound_sockets_diagnostics(
679            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
680                UdpSocketMatcher::DstPort(BoundPortMatcher::Bound(PortMatcher {
681                    range: (LOCAL_PORT_1.get() + 1)..=(LOCAL_PORT_1.get() + 1),
682                    invert: false,
683                })),
684            )),
685            &mut results,
686        );
687        assert_eq!(results, Vec::new());
688    }
689
690    #[ip_test(I)]
691    fn diagnostics_match_src_port_unbound<I: TestIpExt>() {
692        set_logger_for_test();
693
694        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
695        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
696
697        let socket = api.create();
698        api.listen(&socket, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
699            .expect("listen should succeed");
700
701        let mut results = Vec::new();
702        // Source port is always present (bound) for sockets visible in diagnostics.
703        api.bound_sockets_diagnostics(
704            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
705                UdpSocketMatcher::SrcPort(BoundPortMatcher::Unbound),
706            )),
707            &mut results,
708        );
709        assert_eq!(results, Vec::new());
710    }
711
712    #[ip_test(I)]
713    fn diagnostics_match_dst_port_unbound<I: TestIpExt>() {
714        set_logger_for_test();
715
716        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
717        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
718
719        // Unconnected socket (no destination port).
720        let socket1 = api.create();
721        api.listen(&socket1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
722            .expect("listen should succeed");
723
724        // Connected socket (has destination port).
725        let socket2 = api.create();
726        api.listen(&socket2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
727            .expect("listen should succeed");
728        api.connect(
729            &socket2,
730            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
731            UdpRemotePort::Set(REMOTE_PORT_2),
732        )
733        .expect("connect should succeed");
734
735        let mut results = Vec::new();
736        api.bound_sockets_diagnostics(
737            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
738                UdpSocketMatcher::DstPort(BoundPortMatcher::Unbound),
739            )),
740            &mut results,
741        );
742        assert_eq!(
743            results,
744            vec![UdpSocketDiagnostics {
745                state: UdpSocketDiagnosticTuple::Bound {
746                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
747                    src_port: LOCAL_PORT_1
748                },
749                cookie: socket1.socket_cookie(),
750                marks: Marks::default(),
751            }]
752        );
753
754        results.clear();
755        api.bound_sockets_diagnostics(
756            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
757                UdpSocketMatcher::DstPort(BoundPortMatcher::Unbound),
758            )),
759            &mut results,
760        );
761        assert_eq!(
762            results,
763            vec![UdpSocketDiagnostics {
764                state: UdpSocketDiagnosticTuple::Bound {
765                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
766                    src_port: LOCAL_PORT_1,
767                },
768                cookie: socket1.socket_cookie(),
769                marks: Marks::default(),
770            }]
771        );
772    }
773
774    #[ip_test(I)]
775    fn diagnostics_match_state<I: TestIpExt>() {
776        set_logger_for_test();
777
778        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
779        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
780
781        let socket_1 = api.create();
782        api.listen(&socket_1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
783            .expect("listen should succeed");
784
785        let socket_2 = api.create();
786        api.listen(&socket_2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
787            .expect("listen should succeed");
788        api.connect(
789            &socket_2,
790            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
791            UdpRemotePort::Set(LOCAL_PORT_3),
792        )
793        .expect("connect should succeed");
794
795        let mut results = Vec::new();
796        api.bound_sockets_diagnostics(
797            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(UdpSocketMatcher::State(
798                UdpStateMatcher::BOUND,
799            ))),
800            &mut results,
801        );
802        assert_eq!(
803            results,
804            vec![UdpSocketDiagnostics {
805                state: UdpSocketDiagnosticTuple::Bound {
806                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
807                    src_port: LOCAL_PORT_1
808                },
809                cookie: socket_1.socket_cookie(),
810                marks: Marks::default(),
811            }]
812        );
813
814        results.clear();
815        api.bound_sockets_diagnostics(
816            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(UdpSocketMatcher::State(
817                UdpStateMatcher::CONNECTED,
818            ))),
819            &mut results,
820        );
821        assert_eq!(
822            results,
823            vec![UdpSocketDiagnostics {
824                state: UdpSocketDiagnosticTuple::Connected {
825                    src_addr: I::TEST_ADDRS.local_ip.get(),
826                    src_port: LOCAL_PORT_2,
827                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
828                    dst_port: LOCAL_PORT_3.get(),
829                },
830                cookie: socket_2.socket_cookie(),
831                marks: Marks::default(),
832            }]
833        );
834    }
835
836    #[ip_test(I)]
837    fn diagnostics_match_device<I: TestIpExt>() {
838        set_logger_for_test();
839
840        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
841        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
842
843        let socket = api.create();
844        api.set_device(&socket, Some(&FakeDeviceId)).expect("set device should succeed");
845        api.listen(&socket, None, Some(LOCAL_PORT_1)).expect("listen should succeed");
846
847        let mut results = Vec::new();
848        api.bound_sockets_diagnostics(
849            &IpSocketMatcher::BoundInterface(BoundInterfaceMatcher::Bound(InterfaceMatcher::Name(
850                FakeDeviceId::FAKE_NAME.to_string(),
851            ))),
852            &mut results,
853        );
854        assert_eq!(
855            results,
856            vec![UdpSocketDiagnostics {
857                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
858                cookie: socket.socket_cookie(),
859                marks: Marks::default(),
860            }]
861        );
862
863        results.clear();
864        api.bound_sockets_diagnostics(
865            &IpSocketMatcher::BoundInterface(BoundInterfaceMatcher::Unbound),
866            &mut results,
867        );
868        assert_eq!(results, Vec::new());
869    }
870
871    #[ip_test(I)]
872    fn diagnostics_match_cookie<I: TestIpExt>() {
873        set_logger_for_test();
874
875        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
876        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
877
878        let socket = api.create();
879        api.listen(&socket, None, Some(LOCAL_PORT_1)).expect("listen should succeed");
880
881        let mut results = Vec::new();
882        api.bound_sockets_diagnostics(
883            &IpSocketMatcher::Cookie(SocketCookieMatcher {
884                cookie: socket.socket_cookie().export_value(),
885                invert: false,
886            }),
887            &mut results,
888        );
889        assert_eq!(
890            results,
891            vec![UdpSocketDiagnostics {
892                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
893                cookie: socket.socket_cookie(),
894                marks: Marks::default(),
895            }]
896        );
897
898        results.clear();
899        api.bound_sockets_diagnostics(
900            &IpSocketMatcher::Cookie(SocketCookieMatcher {
901                cookie: socket.socket_cookie().export_value() + 1,
902                invert: false,
903            }),
904            &mut results,
905        );
906        assert_eq!(results, Vec::new());
907    }
908
909    #[ip_test(I, test = false)]
910    #[test_case::test_case(MarkDomain::Mark1; "mark_1")]
911    #[test_case::test_case(MarkDomain::Mark2; "mark_2")]
912    fn diagnostics_match_mark<I: TestIpExt>(domain: MarkDomain) {
913        set_logger_for_test();
914
915        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
916        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
917
918        let socket = api.create();
919        api.listen(&socket, None, Some(LOCAL_PORT_1)).expect("listen should succeed");
920
921        api.set_mark(&socket, domain, Mark(Some(MARK)));
922
923        let mut results = Vec::new();
924        let matcher = |query_mark| {
925            IpSocketMatcher::Mark(netstack3_base::MarkInDomainMatcher {
926                domain,
927                matcher: MarkMatcher::Marked {
928                    mask: MARK_MASK,
929                    start: query_mark,
930                    end: query_mark,
931                    invert: false,
932                },
933            })
934        };
935        api.bound_sockets_diagnostics(&matcher(MARK), &mut results);
936        assert_eq!(
937            results,
938            vec![UdpSocketDiagnostics {
939                state: UdpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
940                cookie: socket.socket_cookie(),
941                marks: netstack3_base::MarkStorage::new([(domain, MARK)]),
942            }]
943        );
944
945        results.clear();
946        api.bound_sockets_diagnostics(&matcher(MARK + 1), &mut results);
947        assert_eq!(results, Vec::new());
948    }
949
950    /// Create three sockets, two of which target the same remote port, and make
951    /// sure that multiple matching sockets are returned.
952    #[ip_test(I)]
953    fn diagnostics_match_multiple<I: TestIpExt>() {
954        set_logger_for_test();
955
956        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
957        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
958
959        let socket_1 = api.create();
960        api.listen(&socket_1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
961            .expect("listen should succeed");
962        api.connect(
963            &socket_1,
964            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
965            UdpRemotePort::Set(REMOTE_PORT_1),
966        )
967        .expect("connect should succeed");
968
969        let socket_2 = api.create();
970        api.listen(&socket_2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
971            .expect("listen should succeed");
972        api.connect(
973            &socket_2,
974            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
975            UdpRemotePort::Set(REMOTE_PORT_1),
976        )
977        .expect("connect should succeed");
978
979        let socket_3 = api.create();
980        api.listen(&socket_3, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_3))
981            .expect("listen should succeed");
982        api.connect(
983            &socket_3,
984            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
985            UdpRemotePort::Set(REMOTE_PORT_2),
986        )
987        .expect("connect should succeed");
988
989        let mut results = Vec::new();
990        api.bound_sockets_diagnostics(
991            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
992                UdpSocketMatcher::DstPort(BoundPortMatcher::Bound(PortMatcher {
993                    range: REMOTE_PORT_1.get()..=REMOTE_PORT_1.get(),
994                    invert: false,
995                })),
996            )),
997            &mut results,
998        );
999
1000        results.sort_by(|a, b| a.cookie.cmp(&b.cookie));
1001        let mut expected = vec![
1002            UdpSocketDiagnostics {
1003                state: UdpSocketDiagnosticTuple::Connected {
1004                    src_addr: I::TEST_ADDRS.local_ip.get(),
1005                    src_port: LOCAL_PORT_1,
1006                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
1007                    dst_port: REMOTE_PORT_1.get(),
1008                },
1009                cookie: socket_1.socket_cookie(),
1010                marks: Marks::default(),
1011            },
1012            UdpSocketDiagnostics {
1013                state: UdpSocketDiagnosticTuple::Connected {
1014                    src_addr: I::TEST_ADDRS.local_ip.get(),
1015                    src_port: LOCAL_PORT_2,
1016                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
1017                    dst_port: REMOTE_PORT_1.get(),
1018                },
1019                cookie: socket_2.socket_cookie(),
1020                marks: Marks::default(),
1021            },
1022        ];
1023        expected.sort_by(|a, b| a.cookie.cmp(&b.cookie));
1024        assert_eq!(results, expected);
1025
1026        results.clear();
1027        api.bound_sockets_diagnostics(
1028            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(
1029                UdpSocketMatcher::DstPort(BoundPortMatcher::Bound(PortMatcher {
1030                    range: REMOTE_PORT_2.get()..=REMOTE_PORT_2.get(),
1031                    invert: false,
1032                })),
1033            )),
1034            &mut results,
1035        );
1036        assert_eq!(
1037            results,
1038            vec![UdpSocketDiagnostics {
1039                state: UdpSocketDiagnosticTuple::Connected {
1040                    src_addr: I::TEST_ADDRS.local_ip.get(),
1041                    src_port: LOCAL_PORT_3,
1042                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
1043                    dst_port: REMOTE_PORT_2.get(),
1044                },
1045                cookie: socket_3.socket_cookie(),
1046                marks: Marks::default(),
1047            }]
1048        );
1049    }
1050
1051    #[ip_test(I)]
1052    fn disconnect_listener<I: TestIpExt>() {
1053        set_logger_for_test();
1054
1055        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::new_fake_device::<I>());
1056        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
1057        let socket = api.create();
1058        api.listen(&socket, None, Some(LOCAL_PORT_1)).unwrap();
1059        assert_matches!(api.get_info(&socket), SocketInfo::Listener(ListenerInfo { .. }));
1060
1061        let count = api.disconnect_bound(&IpSocketMatcher::Cookie(SocketCookieMatcher {
1062            cookie: socket.socket_cookie().export_value(),
1063            invert: false,
1064        }));
1065        assert_eq!(count, 1);
1066        assert_matches!(api.get_info(&socket), SocketInfo::Unbound);
1067    }
1068
1069    #[ip_test(I)]
1070    fn disconnect_implicitly_bound<I: TestIpExt>() {
1071        set_logger_for_test();
1072
1073        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs(
1074            vec![I::TEST_ADDRS.local_ip],
1075            vec![I::TEST_ADDRS.remote_ip],
1076        ));
1077        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
1078        let socket = api.create();
1079
1080        api.send_to(
1081            &socket,
1082            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
1083            REMOTE_PORT_1.into(),
1084            packet::Buf::new(vec![], ..),
1085        )
1086        .expect("send failed");
1087        assert_matches!(api.get_info(&socket), SocketInfo::Listener(ListenerInfo { .. }));
1088
1089        let count = api.disconnect_bound(&IpSocketMatcher::Cookie(SocketCookieMatcher {
1090            cookie: socket.socket_cookie().export_value(),
1091            invert: false,
1092        }));
1093        assert_eq!(count, 1);
1094        assert_matches!(api.get_info(&socket), SocketInfo::Unbound);
1095    }
1096
1097    #[ip_test(I)]
1098    fn disconnect_connected_and_reuse<I: TestIpExt>() {
1099        set_logger_for_test();
1100
1101        let mut ctx = UdpFakeDeviceCtx::with_core_ctx(FakeUdpCoreCtx::with_local_remote_ip_addrs(
1102            vec![I::TEST_ADDRS.local_ip],
1103            vec![I::TEST_ADDRS.remote_ip],
1104        ));
1105        let mut api = UdpApi::<I, _>::new(ctx.as_mut());
1106        let socket = api.create();
1107
1108        api.listen(&socket, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
1109            .unwrap();
1110        api.connect(
1111            &socket,
1112            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
1113            REMOTE_PORT_1.into(),
1114        )
1115        .unwrap();
1116
1117        assert_matches!(api.get_info(&socket), SocketInfo::Connected(ConnInfo { .. }));
1118
1119        let count = api.disconnect_bound(&IpSocketMatcher::Cookie(SocketCookieMatcher {
1120            cookie: socket.socket_cookie().export_value(),
1121            invert: false,
1122        }));
1123        assert_eq!(count, 1);
1124        assert_matches!(api.get_info(&socket), SocketInfo::Unbound);
1125
1126        // Unlike TCP sockets, a UDP sockets that's been disconnected
1127        // can be reused (after putting it back in the right state).
1128        let new_remote_port = NonZeroU16::new(9999).unwrap();
1129        api.connect(
1130            &socket,
1131            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
1132            new_remote_port.into(),
1133        )
1134        .unwrap();
1135
1136        let info = api.get_info(&socket);
1137        assert_matches!(info, SocketInfo::Connected(ConnInfo { .. }));
1138        if let SocketInfo::Connected(conn_info) = info {
1139            // Local port was reallocated, so no assert for that.
1140            assert_eq!(conn_info.remote_identifier, new_remote_port.get());
1141        }
1142    }
1143}