Skip to main content

netstack3_tcp/socket/
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::num::NonZeroU16;
6
7use net_types::Witness as _;
8use net_types::ip::{Ip, IpAddress as _};
9use netstack3_base::{
10    InstantBindingsTypes, IpSocketProperties, Marks, Matcher, SocketDiagnosticsSeed,
11    WeakDeviceIdentifier,
12};
13
14use crate::TcpCountersWithSocket;
15use crate::internal::socket::{
16    BoundState, DualStackIpExt, Listener, SocketCookie, TcpBindingsTypes, TcpSocketId,
17    TcpSocketState, TcpSocketStateInner, Unbound,
18};
19use crate::internal::state::State;
20use crate::internal::state::info::TcpSocketInfo;
21
22/// Required state gathered into one struct for matching a socket, so it's
23/// possible to implement traits against the collection.
24pub(crate) struct TcpSocketStateForMatching<
25    'a,
26    I: DualStackIpExt,
27    D: netstack3_base::WeakDeviceIdentifier,
28    BT: TcpBindingsTypes,
29> {
30    pub(crate) state: &'a TcpSocketState<I, D, BT>,
31    pub(crate) id: &'a TcpSocketId<I, D, BT>,
32}
33
34impl<'a, I: DualStackIpExt, D: netstack3_base::WeakDeviceIdentifier, BT: TcpBindingsTypes>
35    netstack3_base::MaybeSocketTransportProperties for TcpSocketStateForMatching<'a, I, D, BT>
36{
37    type TcpProps<'b>
38        = Self
39    where
40        Self: 'b;
41
42    type UdpProps<'b>
43        = !
44    where
45        Self: 'b;
46
47    fn tcp_socket_properties(&self) -> Option<&Self::TcpProps<'_>> {
48        Some(self)
49    }
50
51    fn udp_socket_properties(&self) -> Option<&Self::UdpProps<'_>> {
52        None
53    }
54}
55
56impl<'a, I: DualStackIpExt, D: netstack3_base::WeakDeviceIdentifier, BT: TcpBindingsTypes>
57    netstack3_base::TcpSocketProperties for TcpSocketStateForMatching<'a, I, D, BT>
58{
59    fn src_port_matches(&self, matcher: &netstack3_base::BoundPortMatcher) -> bool {
60        let src_port = match &self.state.socket_state {
61            TcpSocketStateInner::Unbound(_) => None,
62            TcpSocketStateInner::Bound(BoundState { addr, .. })
63            | TcpSocketStateInner::Listener(Listener { addr, .. }) => {
64                Some(I::get_bound_info(addr).port.get())
65            }
66            TcpSocketStateInner::Connected { conn, .. } => {
67                Some(I::get_conn_info(conn).local_addr.port.get())
68            }
69        };
70
71        matcher.matches(&src_port)
72    }
73
74    fn dst_port_matches(&self, matcher: &netstack3_base::BoundPortMatcher) -> bool {
75        let dst_port = match &self.state.socket_state {
76            TcpSocketStateInner::Unbound(_)
77            | TcpSocketStateInner::Bound(_)
78            | TcpSocketStateInner::Listener(_) => None,
79            TcpSocketStateInner::Connected { conn, .. } => {
80                Some(I::get_conn_info(conn).remote_addr.port.get())
81            }
82        };
83
84        matcher.matches(&dst_port)
85    }
86
87    fn state_matches(&self, matcher: &netstack3_base::TcpStateMatcher) -> bool {
88        matcher.matches(&self.state.base_state())
89    }
90}
91
92impl<'a, I: DualStackIpExt, D: netstack3_base::WeakDeviceIdentifier, BT: TcpBindingsTypes>
93    IpSocketProperties<BT::DeviceClass> for TcpSocketStateForMatching<'a, I, D, BT>
94where
95    D::Strong: netstack3_base::InterfaceProperties<BT::DeviceClass>,
96{
97    fn family_matches(&self, family: &net_types::ip::IpVersion) -> bool {
98        I::VERSION == *family
99    }
100
101    fn src_addr_matches(&self, addr: &netstack3_base::BoundAddressMatcherEither) -> bool {
102        let src_addr = match &self.state.socket_state {
103            TcpSocketStateInner::Unbound(_) => None,
104            TcpSocketStateInner::Bound(BoundState { addr, .. })
105            | TcpSocketStateInner::Listener(Listener { addr, .. }) => {
106                I::get_bound_info(addr).addr.map(|a| a.addr().get())
107            }
108            TcpSocketStateInner::Connected { conn, .. } => {
109                Some(I::get_conn_info(conn).local_addr.ip.addr().get())
110            }
111        };
112
113        addr.matches(&src_addr.map(|a| a.to_ip_addr()))
114    }
115
116    fn dst_addr_matches(&self, addr: &netstack3_base::BoundAddressMatcherEither) -> bool {
117        let dst_addr = match &self.state.socket_state {
118            TcpSocketStateInner::Unbound(_)
119            | TcpSocketStateInner::Bound(_)
120            | TcpSocketStateInner::Listener(_) => None,
121            TcpSocketStateInner::Connected { conn, .. } => {
122                Some(I::get_conn_info(conn).remote_addr.ip.addr().get())
123            }
124        };
125
126        addr.matches(&dst_addr.map(|a| a.to_ip_addr()))
127    }
128
129    fn transport_protocol_matches(
130        &self,
131        proto: &netstack3_base::SocketTransportProtocolMatcher,
132    ) -> bool {
133        proto.matches(self)
134    }
135
136    fn bound_interface_matches(
137        &self,
138        iface: &netstack3_base::BoundInterfaceMatcher<BT::DeviceClass>,
139    ) -> bool {
140        let device = match &self.state.socket_state {
141            TcpSocketStateInner::Unbound(_) => None,
142            TcpSocketStateInner::Bound(BoundState { addr, .. })
143            | TcpSocketStateInner::Listener(Listener { addr, .. }) => {
144                I::get_bound_info(addr).device
145            }
146            TcpSocketStateInner::Connected { conn, .. } => I::get_conn_info(conn).device,
147        };
148        iface.matches(&device.and_then(|d| d.upgrade()).as_ref())
149    }
150
151    fn cookie_matches(&self, cookie: &netstack3_base::SocketCookieMatcher) -> bool {
152        cookie.matches(&self.id.socket_cookie().export_value())
153    }
154
155    fn mark_matches(&self, matcher: &netstack3_base::MarkInDomainMatcher) -> bool {
156        matcher.matcher.matches(self.state.socket_options.ip_options.marks.get(matcher.domain))
157    }
158}
159
160/// Publicly-accessible diagnostic information about TCP sockets.
161#[cfg_attr(any(test, feature = "testutils"), derive(Debug, PartialEq, Eq))]
162pub struct TcpSocketDiagnostics<I: Ip, Instant> {
163    /// The socket's TCP state machine.
164    pub state_machine: netstack3_base::TcpSocketState,
165    /// The socket's tuple.
166    pub tuple: TcpSocketDiagnosticTuple<I>,
167    /// The socket's cookie.
168    pub cookie: SocketCookie,
169    /// The socket's marks.
170    pub marks: Marks,
171    /// The socket's extended info.
172    pub tcp_info: Option<TcpSocketInfo<Instant>>,
173}
174
175/// All of the information required to compute [`TcpSocketDiagnostics`]. Gives
176/// the owner control over when (or if) the transformation happens.
177pub struct TcpSocketDiagnosticsSeed<I, D, BT>
178where
179    I: DualStackIpExt,
180    D: WeakDeviceIdentifier,
181    BT: TcpBindingsTypes,
182{
183    pub(crate) state: TcpSocketState<I, D, BT>,
184    pub(crate) counters: TcpCountersWithSocket<I>,
185    pub(crate) cookie: SocketCookie,
186}
187
188impl<I, D, BT> SocketDiagnosticsSeed for TcpSocketDiagnosticsSeed<I, D, BT>
189where
190    I: DualStackIpExt,
191    D: WeakDeviceIdentifier,
192    BT: TcpBindingsTypes,
193{
194    type Output = TcpSocketDiagnostics<I, <BT as InstantBindingsTypes>::Instant>;
195
196    fn resolve(self) -> Option<Self::Output> {
197        let Self { state, counters, cookie } = self;
198
199        state.get_diagnostics(&counters, true).map(|(tuple, state_machine, marks, tcp_info)| {
200            TcpSocketDiagnostics { tuple, state_machine, cookie, marks, tcp_info }
201        })
202    }
203}
204
205/// The tuple of a TCP socket.
206///
207/// This is separate from the state machine state because it's possible for
208/// some states to be entered while having just the 2-tuple or the full
209/// 4-tuple (CLOSED and LISTENING).
210#[derive(Debug, PartialEq, Eq)]
211#[allow(missing_docs)]
212pub enum TcpSocketDiagnosticTuple<I: Ip> {
213    /// The socket is bound, but not connected. Only the 2-tuple is available,
214    /// although the source address might be None if the socket is bound to the
215    /// catch-all address.
216    Bound { src_addr: Option<I::Addr>, src_port: NonZeroU16 },
217    /// The socket is connected, so the full 4-tuple is available.
218    Connected { src_addr: I::Addr, src_port: NonZeroU16, dst_addr: I::Addr, dst_port: NonZeroU16 },
219}
220
221impl<I: Ip> TcpSocketDiagnosticTuple<I> {
222    /// Returns the socket's source address.
223    pub fn src_addr(&self) -> Option<I::Addr> {
224        match self {
225            Self::Bound { src_addr, src_port: _ } => *src_addr,
226            Self::Connected { src_addr, src_port: _, dst_addr: _, dst_port: _ } => Some(*src_addr),
227        }
228    }
229
230    /// Returns the socket's source port.
231    pub fn src_port(&self) -> Option<NonZeroU16> {
232        match self {
233            Self::Bound { src_addr: _, src_port }
234            | Self::Connected { src_addr: _, src_port, dst_addr: _, dst_port: _ } => {
235                Some(*src_port)
236            }
237        }
238    }
239
240    /// Returns the socket's destination address.
241    pub fn dst_addr(&self) -> Option<I::Addr> {
242        match self {
243            Self::Bound { src_addr: _, src_port: _ } => None,
244            Self::Connected { src_addr: _, src_port: _, dst_addr, dst_port: _ } => Some(*dst_addr),
245        }
246    }
247
248    /// Returns the socket's destination port.
249    pub fn dst_port(&self) -> Option<NonZeroU16> {
250        match self {
251            Self::Bound { src_addr: _, src_port: _ } => None,
252            Self::Connected { src_addr: _, src_port: _, dst_addr: _, dst_port } => Some(*dst_port),
253        }
254    }
255}
256
257impl<I, D, BT> TcpSocketState<I, D, BT>
258where
259    I: DualStackIpExt,
260    D: WeakDeviceIdentifier,
261    BT: TcpBindingsTypes,
262{
263    pub(crate) fn tcp_info(
264        &self,
265        counters: &crate::internal::counters::TcpCountersWithSocket<I>,
266    ) -> TcpSocketInfo<BT::Instant> {
267        match &self.socket_state {
268            TcpSocketStateInner::Unbound(unbound) => {
269                TcpSocketInfo::from_partial_state(unbound.base_state(), counters.as_ref())
270            }
271            TcpSocketStateInner::Bound(bound) => {
272                TcpSocketInfo::from_partial_state(bound.base_state(), counters.as_ref())
273            }
274            TcpSocketStateInner::Listener(listener) => {
275                TcpSocketInfo::from_partial_state(listener.base_state(), counters.as_ref())
276            }
277            TcpSocketStateInner::Connected { conn, timer: _ } => {
278                TcpSocketInfo::from_full_state(I::get_state(conn), counters.as_ref())
279            }
280        }
281    }
282
283    pub(crate) fn get_diagnostics(
284        &self,
285        counters: &crate::internal::counters::TcpCountersWithSocket<I>,
286        extended_info: bool,
287    ) -> Option<(
288        TcpSocketDiagnosticTuple<I>,
289        netstack3_base::TcpSocketState,
290        Marks,
291        Option<TcpSocketInfo<BT::Instant>>,
292    )> {
293        let tuple = match &self.socket_state {
294            TcpSocketStateInner::Unbound(_) => None,
295            TcpSocketStateInner::Bound(BoundState { addr, .. })
296            | TcpSocketStateInner::Listener(Listener { addr, .. }) => {
297                let addr_info = I::get_bound_info(addr);
298                Some(TcpSocketDiagnosticTuple::Bound {
299                    src_addr: addr_info.addr.map(|ip| ip.addr().get()),
300                    src_port: addr_info.port,
301                })
302            }
303            TcpSocketStateInner::Connected { conn, .. } => {
304                let info = I::get_conn_info(conn);
305                Some(TcpSocketDiagnosticTuple::Connected {
306                    src_addr: info.local_addr.ip.addr().get(),
307                    dst_addr: info.remote_addr.ip.addr().get(),
308                    src_port: info.local_addr.port,
309                    dst_port: info.remote_addr.port,
310                })
311            }
312        }?;
313
314        Some((
315            tuple,
316            self.base_state(),
317            self.socket_options.ip_options.marks,
318            extended_info.then(|| self.tcp_info(counters)),
319        ))
320    }
321
322    pub(crate) fn base_state(&self) -> netstack3_base::TcpSocketState {
323        match &self.socket_state {
324            TcpSocketStateInner::Unbound(unbound) => unbound.base_state(),
325            TcpSocketStateInner::Bound(bound) => bound.base_state(),
326            TcpSocketStateInner::Listener(listener) => listener.base_state(),
327            TcpSocketStateInner::Connected { conn, .. } => match I::get_state(conn) {
328                State::Closed(_) => netstack3_base::TcpSocketState::Close,
329                State::Listen(_) => netstack3_base::TcpSocketState::Listen,
330                State::SynRcvd(_) => netstack3_base::TcpSocketState::SynRecv,
331                State::SynSent(_) => netstack3_base::TcpSocketState::SynSent,
332                State::Established(_) => netstack3_base::TcpSocketState::Established,
333                State::CloseWait(_) => netstack3_base::TcpSocketState::CloseWait,
334                State::LastAck(_) => netstack3_base::TcpSocketState::LastAck,
335                State::FinWait1(_) => netstack3_base::TcpSocketState::FinWait1,
336                State::FinWait2(_) => netstack3_base::TcpSocketState::FinWait2,
337                State::Closing(_) => netstack3_base::TcpSocketState::Closing,
338                State::TimeWait(_) => netstack3_base::TcpSocketState::TimeWait,
339            },
340        }
341    }
342}
343
344impl<D, Extra> Unbound<D, Extra> {
345    fn base_state(&self) -> netstack3_base::TcpSocketState {
346        netstack3_base::TcpSocketState::Close
347    }
348}
349
350impl<I, D, BT> BoundState<I, D, BT>
351where
352    I: DualStackIpExt,
353    D: WeakDeviceIdentifier,
354    BT: TcpBindingsTypes,
355{
356    fn base_state(&self) -> netstack3_base::TcpSocketState {
357        netstack3_base::TcpSocketState::Close
358    }
359}
360
361impl<I, D, BT> Listener<I, D, BT>
362where
363    I: DualStackIpExt,
364    D: WeakDeviceIdentifier,
365    BT: TcpBindingsTypes,
366{
367    fn base_state(&self) -> netstack3_base::TcpSocketState {
368        netstack3_base::TcpSocketState::Listen
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use alloc::string::ToString;
375    use alloc::vec;
376    use alloc::vec::Vec;
377    use assert_matches::assert_matches;
378    use core::num::NonZeroUsize;
379    use core::time::Duration;
380
381    use ip_test_macro::ip_test;
382    use net_types::ZonedAddr;
383    use net_types::ip::{Ip, Subnet};
384    use netstack3_base::testutil::{
385        FakeDeviceId, FakeInstant, FakeNetworkSpec as _, set_logger_for_test,
386    };
387    use netstack3_base::{
388        AddressMatcher, AddressMatcherEither, AddressMatcherType, BoundAddressMatcherEither,
389        BoundInterfaceMatcher, BoundPortMatcher, InterfaceMatcher, IpSocketMatcher, Mark,
390        MarkDomain, MarkMatcher, PortMatcher, SocketCookieMatcher, SocketTransportProtocolMatcher,
391        StrongDeviceIdentifier, SubnetMatcher, TcpSocketMatcher, TcpStateMatcher, UdpSocketMatcher,
392    };
393    use test_case::test_case;
394
395    use super::*;
396    use crate::AcceptError;
397    use crate::internal::base::ConnectionError;
398    use crate::internal::socket::TcpContext;
399    use crate::internal::socket::tests::{
400        FakeTcpNetworkSpec, TcpApiExt, TcpBindingsCtx, TcpCoreCtx, TcpCtx, TcpTestIpExt,
401    };
402    use crate::internal::state::info::CongestionControlState;
403
404    const LOCAL_PORT_1: NonZeroU16 = NonZeroU16::new(1234).unwrap();
405    const LOCAL_PORT_2: NonZeroU16 = NonZeroU16::new(5678).unwrap();
406    const LOCAL_PORT_3: NonZeroU16 = NonZeroU16::new(4321).unwrap();
407
408    const REMOTE_PORT_1: NonZeroU16 = NonZeroU16::new(100).unwrap();
409    const REMOTE_PORT_2: NonZeroU16 = NonZeroU16::new(200).unwrap();
410
411    const MARK: u32 = 0x10;
412    const MARK_MASK: u32 = !0;
413
414    #[ip_test(I)]
415    fn diagnostics_match_ip_version<I: TcpTestIpExt>()
416    where
417        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
418            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
419    {
420        set_logger_for_test();
421
422        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
423            I::TEST_ADDRS.local_ip,
424            I::TEST_ADDRS.remote_ip,
425        ));
426        let mut api = ctx.tcp_api::<I>();
427        let s = api.create(Default::default());
428        api.bind(&s, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
429        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
430
431        let mut results = Vec::new();
432        api.bound_sockets_diagnostics(&IpSocketMatcher::Family(I::VERSION), &mut results, false);
433        assert_eq!(
434            results,
435            vec![TcpSocketDiagnostics {
436                state_machine: netstack3_base::TcpSocketState::Listen,
437                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
438                cookie: s.socket_cookie(),
439                marks: Marks::default(),
440                tcp_info: None,
441            }]
442        );
443
444        results.clear();
445        api.bound_sockets_diagnostics(
446            &IpSocketMatcher::Family(
447                <<I as netstack3_base::socket::DualStackIpExt>::OtherVersion as Ip>::VERSION,
448            ),
449            &mut results,
450            false,
451        );
452        assert_eq!(results, Vec::new());
453    }
454
455    #[ip_test(I)]
456    fn diagnostics_match_src_addr<I: TcpTestIpExt>()
457    where
458        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
459            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
460    {
461        set_logger_for_test();
462
463        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
464            I::TEST_ADDRS.local_ip,
465            I::TEST_ADDRS.remote_ip,
466        ));
467        let mut api = ctx.tcp_api::<I>();
468        let s = api.create(Default::default());
469        api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
470            .expect("bind should succeed");
471        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
472
473        let mut results = Vec::new();
474        let matcher = I::map_ip_in(
475            I::TEST_ADDRS.local_ip.get(),
476            |addr| {
477                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
478                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
479                        Subnet::new(addr, 32).unwrap(),
480                    )),
481                    invert: false,
482                }))
483            },
484            |addr| {
485                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
486                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
487                        Subnet::new(addr, 128).unwrap(),
488                    )),
489                    invert: false,
490                }))
491            },
492        );
493        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results, false);
494        assert_eq!(
495            results,
496            vec![TcpSocketDiagnostics {
497                state_machine: netstack3_base::TcpSocketState::Listen,
498                tuple: TcpSocketDiagnosticTuple::Bound {
499                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
500                    src_port: LOCAL_PORT_1,
501                },
502                cookie: s.socket_cookie(),
503                marks: Marks::default(),
504                tcp_info: None,
505            }]
506        );
507
508        results.clear();
509        let matcher = I::map_ip_in(
510            I::TEST_ADDRS.remote_ip.get(),
511            |addr| {
512                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
513                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
514                        Subnet::new(addr, 32).unwrap(),
515                    )),
516                    invert: false,
517                }))
518            },
519            |addr| {
520                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
521                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
522                        Subnet::new(addr, 128).unwrap(),
523                    )),
524                    invert: false,
525                }))
526            },
527        );
528        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results, false);
529        assert_eq!(results, Vec::new());
530    }
531
532    #[ip_test(I)]
533    fn diagnostics_match_dst_addr<I: TcpTestIpExt>()
534    where
535        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
536            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
537    {
538        set_logger_for_test();
539
540        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
541            I::TEST_ADDRS.local_ip,
542            I::TEST_ADDRS.remote_ip,
543        ));
544        let mut api = ctx.tcp_api::<I>();
545        let s = api.create(Default::default());
546        api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
547            .expect("bind should succeed");
548        api.connect(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), LOCAL_PORT_2)
549            .expect("connect should succeed");
550
551        let mut results = Vec::new();
552        let matcher = I::map_ip_in(
553            I::TEST_ADDRS.remote_ip.get(),
554            |addr| {
555                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
556                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
557                        Subnet::new(addr, 32).unwrap(),
558                    )),
559                    invert: false,
560                }))
561            },
562            |addr| {
563                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
564                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
565                        Subnet::new(addr, 128).unwrap(),
566                    )),
567                    invert: false,
568                }))
569            },
570        );
571        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results, false);
572        assert_eq!(
573            results,
574            vec![TcpSocketDiagnostics {
575                state_machine: netstack3_base::TcpSocketState::SynSent,
576                tuple: TcpSocketDiagnosticTuple::Connected {
577                    src_addr: I::TEST_ADDRS.local_ip.get(),
578                    src_port: LOCAL_PORT_1,
579                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
580                    dst_port: LOCAL_PORT_2,
581                },
582                cookie: s.socket_cookie(),
583                marks: Marks::default(),
584                tcp_info: None,
585            }]
586        );
587
588        results.clear();
589        let matcher = I::map_ip_in(
590            I::TEST_ADDRS.local_ip.get(),
591            |addr| {
592                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
593                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
594                        Subnet::new(addr, 32).unwrap(),
595                    )),
596                    invert: false,
597                }))
598            },
599            |addr| {
600                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
601                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
602                        Subnet::new(addr, 128).unwrap(),
603                    )),
604                    invert: false,
605                }))
606            },
607        );
608        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results, false);
609        assert_eq!(results, Vec::new());
610    }
611
612    #[ip_test(I)]
613    fn diagnostics_match_proto<I: TcpTestIpExt>()
614    where
615        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
616            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
617    {
618        set_logger_for_test();
619
620        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
621            I::TEST_ADDRS.local_ip,
622            I::TEST_ADDRS.remote_ip,
623        ));
624        let mut api = ctx.tcp_api::<I>();
625        let s = api.create(Default::default());
626        api.bind(&s, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
627        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
628
629        let mut results = Vec::new();
630        api.bound_sockets_diagnostics(
631            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(TcpSocketMatcher::Empty)),
632            &mut results,
633            false,
634        );
635        assert_eq!(
636            results,
637            vec![TcpSocketDiagnostics {
638                state_machine: netstack3_base::TcpSocketState::Listen,
639                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
640                cookie: s.socket_cookie(),
641                marks: Marks::default(),
642                tcp_info: None,
643            }]
644        );
645
646        results.clear();
647        api.bound_sockets_diagnostics(
648            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(UdpSocketMatcher::Empty)),
649            &mut results,
650            false,
651        );
652        assert_eq!(results, Vec::new());
653    }
654
655    #[ip_test(I)]
656    fn diagnostics_match_device<I: TcpTestIpExt>()
657    where
658        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
659            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
660    {
661        set_logger_for_test();
662
663        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
664            I::TEST_ADDRS.local_ip,
665            I::TEST_ADDRS.remote_ip,
666        ));
667        let mut api = ctx.tcp_api::<I>();
668        let s = api.create(Default::default());
669        api.set_device(&s, Some(FakeDeviceId)).expect("set device should succeed");
670        api.bind(&s, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
671        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
672
673        let mut results = Vec::new();
674        api.bound_sockets_diagnostics(
675            &IpSocketMatcher::BoundInterface(BoundInterfaceMatcher::Bound(InterfaceMatcher::Name(
676                FakeDeviceId::FAKE_NAME.to_string(),
677            ))),
678            &mut results,
679            false,
680        );
681        assert_eq!(
682            results,
683            vec![TcpSocketDiagnostics {
684                state_machine: netstack3_base::TcpSocketState::Listen,
685                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
686                cookie: s.socket_cookie(),
687                marks: Marks::default(),
688                tcp_info: None,
689            }]
690        );
691
692        results.clear();
693        api.bound_sockets_diagnostics(
694            &IpSocketMatcher::BoundInterface(BoundInterfaceMatcher::Unbound),
695            &mut results,
696            false,
697        );
698        assert_eq!(results, Vec::new());
699    }
700
701    #[ip_test(I)]
702    fn diagnostics_match_cookie<I: TcpTestIpExt>()
703    where
704        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
705            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
706    {
707        set_logger_for_test();
708
709        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
710            I::TEST_ADDRS.local_ip,
711            I::TEST_ADDRS.remote_ip,
712        ));
713        let mut api = ctx.tcp_api::<I>();
714
715        let socket_1 = api.create(Default::default());
716        api.bind(&socket_1, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
717        api.listen(&socket_1, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
718        let socket_2 = api.create(Default::default());
719        api.bind(&socket_2, None, Some(LOCAL_PORT_2)).expect("bind should succeed");
720        api.listen(&socket_2, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
721
722        let mut results = Vec::new();
723        api.bound_sockets_diagnostics(
724            &IpSocketMatcher::Cookie(SocketCookieMatcher {
725                cookie: socket_1.socket_cookie().export_value(),
726                invert: false,
727            }),
728            &mut results,
729            false,
730        );
731        assert_eq!(
732            results,
733            vec![TcpSocketDiagnostics {
734                state_machine: netstack3_base::TcpSocketState::Listen,
735                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
736                cookie: socket_1.socket_cookie(),
737                marks: Marks::default(),
738                tcp_info: None,
739            }]
740        );
741
742        results.clear();
743        api.bound_sockets_diagnostics(
744            &IpSocketMatcher::Cookie(SocketCookieMatcher {
745                cookie: socket_2.socket_cookie().export_value(),
746                invert: false,
747            }),
748            &mut results,
749            false,
750        );
751        assert_eq!(
752            results,
753            vec![TcpSocketDiagnostics {
754                state_machine: netstack3_base::TcpSocketState::Listen,
755                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_2 },
756                cookie: socket_2.socket_cookie(),
757                marks: Marks::default(),
758                tcp_info: None,
759            }]
760        );
761    }
762
763    #[ip_test(I, test = false)]
764    #[test_case::test_case(MarkDomain::Mark1; "mark_1")]
765    #[test_case::test_case(MarkDomain::Mark2; "mark_2")]
766    fn diagnostics_match_mark<I: TcpTestIpExt>(domain: MarkDomain)
767    where
768        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
769            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
770    {
771        set_logger_for_test();
772
773        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
774            I::TEST_ADDRS.local_ip,
775            I::TEST_ADDRS.remote_ip,
776        ));
777        let mut api = ctx.tcp_api::<I>();
778
779        let s = api.create(Default::default());
780        api.bind(&s, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
781        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
782        api.set_mark(&s, domain, Mark(Some(MARK)));
783
784        let mut results = Vec::new();
785        let matcher = |query_mark| {
786            IpSocketMatcher::Mark(netstack3_base::MarkInDomainMatcher {
787                domain,
788                matcher: MarkMatcher::Marked {
789                    mask: MARK_MASK,
790                    start: query_mark,
791                    end: query_mark,
792                    invert: false,
793                },
794            })
795        };
796        api.bound_sockets_diagnostics(&matcher(MARK), &mut results, false);
797        assert_eq!(
798            results,
799            vec![TcpSocketDiagnostics {
800                state_machine: netstack3_base::TcpSocketState::Listen,
801                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
802                cookie: s.socket_cookie(),
803                marks: netstack3_base::MarkStorage::new([(domain, MARK)]),
804                tcp_info: None,
805            }]
806        );
807
808        results.clear();
809        api.bound_sockets_diagnostics(&matcher(MARK + 1), &mut results, false);
810        assert_eq!(results, Vec::new());
811    }
812
813    #[ip_test(I)]
814    fn diagnostics_match_multiple<I: TcpTestIpExt>()
815    where
816        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
817            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
818    {
819        set_logger_for_test();
820
821        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
822            I::TEST_ADDRS.local_ip,
823            I::TEST_ADDRS.remote_ip,
824        ));
825        let mut api = ctx.tcp_api::<I>();
826
827        let socket_1 = api.create(Default::default());
828        api.bind(&socket_1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
829            .expect("bind should succeed");
830        api.connect(&socket_1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_1)
831            .expect("connect socket 1");
832        let socket_2 = api.create(Default::default());
833        api.bind(&socket_2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
834            .expect("bind should succeed");
835        api.connect(&socket_2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_1)
836            .expect("connect socket 2");
837        let socket_3 = api.create(Default::default());
838        api.bind(&socket_3, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_3))
839            .expect("bind should succeed");
840        api.connect(&socket_3, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_2)
841            .expect("connect socket 3");
842
843        let mut results = Vec::new();
844
845        api.bound_sockets_diagnostics(
846            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
847                TcpSocketMatcher::DstPort(BoundPortMatcher::Bound(PortMatcher {
848                    range: REMOTE_PORT_1.get()..=REMOTE_PORT_1.get(),
849                    invert: false,
850                })),
851            )),
852            &mut results,
853            false,
854        );
855
856        results.sort_by(|a, b| a.cookie.cmp(&b.cookie));
857        let mut expected = vec![
858            TcpSocketDiagnostics {
859                state_machine: netstack3_base::TcpSocketState::SynSent,
860                tuple: TcpSocketDiagnosticTuple::Connected {
861                    src_addr: I::TEST_ADDRS.local_ip.get(),
862                    src_port: LOCAL_PORT_1,
863                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
864                    dst_port: REMOTE_PORT_1,
865                },
866                cookie: socket_1.socket_cookie(),
867                marks: Marks::default(),
868                tcp_info: None,
869            },
870            TcpSocketDiagnostics {
871                state_machine: netstack3_base::TcpSocketState::SynSent,
872                tuple: TcpSocketDiagnosticTuple::Connected {
873                    src_addr: I::TEST_ADDRS.local_ip.get(),
874                    src_port: LOCAL_PORT_2,
875                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
876                    dst_port: REMOTE_PORT_1,
877                },
878                cookie: socket_2.socket_cookie(),
879                marks: Marks::default(),
880                tcp_info: None,
881            },
882        ];
883        expected.sort_by(|a, b| a.cookie.cmp(&b.cookie));
884        assert_eq!(results, expected);
885
886        results.clear();
887        api.bound_sockets_diagnostics(
888            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
889                TcpSocketMatcher::DstPort(BoundPortMatcher::Bound(PortMatcher {
890                    range: REMOTE_PORT_2.get()..=REMOTE_PORT_2.get(),
891                    invert: false,
892                })),
893            )),
894            &mut results,
895            false,
896        );
897        assert_eq!(
898            results,
899            vec![TcpSocketDiagnostics {
900                state_machine: netstack3_base::TcpSocketState::SynSent,
901                tuple: TcpSocketDiagnosticTuple::Connected {
902                    src_addr: I::TEST_ADDRS.local_ip.get(),
903                    src_port: LOCAL_PORT_3,
904                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
905                    dst_port: REMOTE_PORT_2,
906                },
907                cookie: socket_3.socket_cookie(),
908                marks: Marks::default(),
909                tcp_info: None,
910            }]
911        );
912    }
913
914    #[ip_test(I)]
915    fn diagnostics_match_src_port<I: TcpTestIpExt>()
916    where
917        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
918                I,
919                TcpBindingsCtx<FakeDeviceId>,
920                SingleStackConverter = I::SingleStackConverter,
921                DualStackConverter = I::DualStackConverter,
922            >,
923    {
924        set_logger_for_test();
925
926        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
927            I::TEST_ADDRS.local_ip,
928            I::TEST_ADDRS.remote_ip,
929        ));
930        let mut api = ctx.tcp_api::<I>();
931        let s = api.create(Default::default());
932        api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
933            .expect("bind should succeed");
934        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
935
936        let mut results = Vec::new();
937        api.bound_sockets_diagnostics(
938            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
939                TcpSocketMatcher::SrcPort(BoundPortMatcher::Bound(PortMatcher {
940                    range: LOCAL_PORT_1.get()..=LOCAL_PORT_1.get(),
941                    invert: false,
942                })),
943            )),
944            &mut results,
945            false,
946        );
947        assert_eq!(
948            results,
949            vec![TcpSocketDiagnostics {
950                state_machine: netstack3_base::TcpSocketState::Listen,
951                tuple: TcpSocketDiagnosticTuple::Bound {
952                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
953                    src_port: LOCAL_PORT_1,
954                },
955                cookie: s.socket_cookie(),
956                marks: Marks::default(),
957                tcp_info: None,
958            }]
959        );
960
961        results.clear();
962        api.bound_sockets_diagnostics(
963            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
964                TcpSocketMatcher::SrcPort(BoundPortMatcher::Bound(PortMatcher {
965                    range: (LOCAL_PORT_1.get() + 1)..=(LOCAL_PORT_1.get() + 1),
966                    invert: false,
967                })),
968            )),
969            &mut results,
970            false,
971        );
972        assert_eq!(results, Vec::new());
973    }
974
975    #[ip_test(I)]
976    fn diagnostics_match_dst_port<I: TcpTestIpExt>()
977    where
978        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
979                I,
980                TcpBindingsCtx<FakeDeviceId>,
981                SingleStackConverter = I::SingleStackConverter,
982                DualStackConverter = I::DualStackConverter,
983            >,
984    {
985        set_logger_for_test();
986
987        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
988            I::TEST_ADDRS.local_ip,
989            I::TEST_ADDRS.remote_ip,
990        ));
991        let mut api = ctx.tcp_api::<I>();
992        let s = api.create(Default::default());
993        api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
994            .expect("bind should succeed");
995        api.connect(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), LOCAL_PORT_2)
996            .expect("connect should succeed");
997
998        let mut results = Vec::new();
999        api.bound_sockets_diagnostics(
1000            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
1001                TcpSocketMatcher::DstPort(BoundPortMatcher::Bound(PortMatcher {
1002                    range: LOCAL_PORT_2.get()..=LOCAL_PORT_2.get(),
1003                    invert: false,
1004                })),
1005            )),
1006            &mut results,
1007            false,
1008        );
1009        assert_eq!(
1010            results,
1011            vec![TcpSocketDiagnostics {
1012                state_machine: netstack3_base::TcpSocketState::SynSent,
1013                tuple: TcpSocketDiagnosticTuple::Connected {
1014                    src_addr: I::TEST_ADDRS.local_ip.get(),
1015                    src_port: LOCAL_PORT_1,
1016                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
1017                    dst_port: LOCAL_PORT_2,
1018                },
1019                cookie: s.socket_cookie(),
1020                marks: Marks::default(),
1021                tcp_info: None,
1022            }]
1023        );
1024
1025        results.clear();
1026        api.bound_sockets_diagnostics(
1027            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
1028                TcpSocketMatcher::DstPort(BoundPortMatcher::Bound(PortMatcher {
1029                    range: (LOCAL_PORT_2.get() + 1)..=(LOCAL_PORT_2.get() + 1),
1030                    invert: false,
1031                })),
1032            )),
1033            &mut results,
1034            false,
1035        );
1036        assert_eq!(results, Vec::new());
1037    }
1038
1039    #[ip_test(I)]
1040    fn diagnostics_match_state<I: TcpTestIpExt>()
1041    where
1042        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
1043                I,
1044                TcpBindingsCtx<FakeDeviceId>,
1045                SingleStackConverter = I::SingleStackConverter,
1046                DualStackConverter = I::DualStackConverter,
1047            >,
1048    {
1049        set_logger_for_test();
1050
1051        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1052            I::TEST_ADDRS.local_ip,
1053            I::TEST_ADDRS.remote_ip,
1054        ));
1055        let mut api = ctx.tcp_api::<I>();
1056
1057        // Socket 1: LISTEN
1058        let listen_socket = api.create(Default::default());
1059        api.bind(
1060            &listen_socket,
1061            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)),
1062            Some(LOCAL_PORT_1),
1063        )
1064        .expect("bind");
1065        api.listen(&listen_socket, NonZeroUsize::new(1).unwrap()).expect("listen");
1066
1067        // Socket 2: SYN_SENT
1068        let syn_sent_socket = api.create(Default::default());
1069        api.bind(
1070            &syn_sent_socket,
1071            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)),
1072            Some(LOCAL_PORT_2),
1073        )
1074        .expect("bind");
1075        // Connect to a remote address that won't respond immediately (since we don't step the
1076        // network).
1077        api.connect(
1078            &syn_sent_socket,
1079            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
1080            LOCAL_PORT_3,
1081        )
1082        .expect("connect");
1083
1084        let mut results = Vec::new();
1085
1086        api.bound_sockets_diagnostics(
1087            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(TcpSocketMatcher::State(
1088                TcpStateMatcher::LISTEN,
1089            ))),
1090            &mut results,
1091            false,
1092        );
1093        assert_eq!(
1094            results,
1095            vec![TcpSocketDiagnostics {
1096                state_machine: netstack3_base::TcpSocketState::Listen,
1097                tuple: TcpSocketDiagnosticTuple::Bound {
1098                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
1099                    src_port: LOCAL_PORT_1,
1100                },
1101                cookie: listen_socket.socket_cookie(),
1102                marks: Marks::default(),
1103                tcp_info: None,
1104            }]
1105        );
1106
1107        results.clear();
1108        api.bound_sockets_diagnostics(
1109            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(TcpSocketMatcher::State(
1110                TcpStateMatcher::SYN_SENT,
1111            ))),
1112            &mut results,
1113            false,
1114        );
1115        assert_eq!(
1116            results,
1117            vec![TcpSocketDiagnostics {
1118                state_machine: netstack3_base::TcpSocketState::SynSent,
1119                tuple: TcpSocketDiagnosticTuple::Connected {
1120                    src_addr: I::TEST_ADDRS.local_ip.get(),
1121                    src_port: LOCAL_PORT_2,
1122                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
1123                    dst_port: LOCAL_PORT_3,
1124                },
1125                cookie: syn_sent_socket.socket_cookie(),
1126                marks: Marks::default(),
1127                tcp_info: None,
1128            }]
1129        );
1130
1131        results.clear();
1132        api.bound_sockets_diagnostics(
1133            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(TcpSocketMatcher::State(
1134                TcpStateMatcher::ESTABLISHED,
1135            ))),
1136            &mut results,
1137            false,
1138        );
1139        assert_eq!(results, Vec::new());
1140    }
1141
1142    #[ip_test(I)]
1143    fn diagnostics_match_src_addr_unbound<I: TcpTestIpExt>()
1144    where
1145        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
1146            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
1147    {
1148        set_logger_for_test();
1149
1150        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1151            I::TEST_ADDRS.local_ip,
1152            I::TEST_ADDRS.remote_ip,
1153        ));
1154        let mut api = ctx.tcp_api::<I>();
1155
1156        // Bound to wildcard address.
1157        let s1 = api.create(Default::default());
1158        api.bind(&s1, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
1159        api.listen(&s1, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
1160
1161        // Bound to specific address.
1162        let s2 = api.create(Default::default());
1163        api.bind(&s2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
1164            .expect("bind should succeed");
1165        api.listen(&s2, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
1166
1167        let mut results = Vec::new();
1168        let matcher = match I::VERSION {
1169            net_types::ip::IpVersion::V4 => BoundAddressMatcherEither::Unbound,
1170            net_types::ip::IpVersion::V6 => BoundAddressMatcherEither::Unbound,
1171        };
1172        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results, false);
1173        assert_eq!(
1174            results,
1175            vec![TcpSocketDiagnostics {
1176                state_machine: netstack3_base::TcpSocketState::Listen,
1177                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
1178                cookie: s1.socket_cookie(),
1179                marks: Marks::default(),
1180                tcp_info: None,
1181            }]
1182        );
1183
1184        results.clear();
1185        let matcher = I::map_ip_in(
1186            I::TEST_ADDRS.local_ip.get(),
1187            |addr| {
1188                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
1189                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
1190                        Subnet::new(addr, 32).unwrap(),
1191                    )),
1192                    invert: false,
1193                }))
1194            },
1195            |addr| {
1196                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
1197                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
1198                        Subnet::new(addr, 128).unwrap(),
1199                    )),
1200                    invert: false,
1201                }))
1202            },
1203        );
1204        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results, false);
1205        assert_eq!(
1206            results,
1207            vec![TcpSocketDiagnostics {
1208                state_machine: netstack3_base::TcpSocketState::Listen,
1209                tuple: TcpSocketDiagnosticTuple::Bound {
1210                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
1211                    src_port: LOCAL_PORT_2,
1212                },
1213                cookie: s2.socket_cookie(),
1214                marks: Marks::default(),
1215                tcp_info: None,
1216            }]
1217        );
1218    }
1219
1220    #[ip_test(I)]
1221    fn diagnostics_match_dst_addr_unbound<I: TcpTestIpExt>()
1222    where
1223        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
1224            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
1225    {
1226        set_logger_for_test();
1227
1228        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1229            I::TEST_ADDRS.local_ip,
1230            I::TEST_ADDRS.remote_ip,
1231        ));
1232        let mut api = ctx.tcp_api::<I>();
1233
1234        // Not connected, so no destination address.
1235        let s1 = api.create(Default::default());
1236        api.bind(&s1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
1237            .expect("bind should succeed");
1238        api.listen(&s1, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
1239
1240        // Connected, so has a destination address.
1241        let s2 = api.create(Default::default());
1242        api.bind(&s2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
1243            .expect("bind should succeed");
1244        api.connect(&s2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_1)
1245            .expect("connect should succeed");
1246
1247        let mut results = Vec::new();
1248        let matcher = match I::VERSION {
1249            net_types::ip::IpVersion::V4 => BoundAddressMatcherEither::Unbound,
1250            net_types::ip::IpVersion::V6 => BoundAddressMatcherEither::Unbound,
1251        };
1252        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results, false);
1253        assert_eq!(
1254            results,
1255            vec![TcpSocketDiagnostics {
1256                state_machine: netstack3_base::TcpSocketState::Listen,
1257                tuple: TcpSocketDiagnosticTuple::Bound {
1258                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
1259                    src_port: LOCAL_PORT_1
1260                },
1261                cookie: s1.socket_cookie(),
1262                marks: Marks::default(),
1263                tcp_info: None,
1264            }]
1265        );
1266
1267        results.clear();
1268        let matcher = I::map_ip_in(
1269            I::TEST_ADDRS.remote_ip.get(),
1270            |addr| {
1271                BoundAddressMatcherEither::Bound(AddressMatcherEither::V4(AddressMatcher {
1272                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
1273                        Subnet::new(addr, 32).unwrap(),
1274                    )),
1275                    invert: false,
1276                }))
1277            },
1278            |addr| {
1279                BoundAddressMatcherEither::Bound(AddressMatcherEither::V6(AddressMatcher {
1280                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
1281                        Subnet::new(addr, 128).unwrap(),
1282                    )),
1283                    invert: false,
1284                }))
1285            },
1286        );
1287        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results, false);
1288        assert_eq!(
1289            results,
1290            vec![TcpSocketDiagnostics {
1291                state_machine: netstack3_base::TcpSocketState::SynSent,
1292                tuple: TcpSocketDiagnosticTuple::Connected {
1293                    src_addr: I::TEST_ADDRS.local_ip.get(),
1294                    src_port: LOCAL_PORT_2,
1295                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
1296                    dst_port: REMOTE_PORT_1,
1297                },
1298                cookie: s2.socket_cookie(),
1299                marks: Marks::default(),
1300                tcp_info: None,
1301            }]
1302        );
1303    }
1304
1305    #[ip_test(I)]
1306    fn diagnostics_match_src_port_unbound<I: TcpTestIpExt>()
1307    where
1308        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
1309            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
1310    {
1311        set_logger_for_test();
1312
1313        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1314            I::TEST_ADDRS.local_ip,
1315            I::TEST_ADDRS.remote_ip,
1316        ));
1317        let mut api = ctx.tcp_api::<I>();
1318
1319        let s1 = api.create(Default::default());
1320        api.bind(&s1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
1321            .expect("bind should succeed");
1322        api.listen(&s1, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
1323
1324        let mut results = Vec::new();
1325        api.bound_sockets_diagnostics(
1326            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
1327                TcpSocketMatcher::SrcPort(BoundPortMatcher::Unbound),
1328            )),
1329            &mut results,
1330            false,
1331        );
1332        // All visible TCP sockets have a source port.
1333        assert_eq!(results, Vec::new());
1334    }
1335
1336    #[ip_test(I)]
1337    fn diagnostics_match_dst_port_unbound<I: TcpTestIpExt>()
1338    where
1339        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
1340            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
1341    {
1342        set_logger_for_test();
1343
1344        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1345            I::TEST_ADDRS.local_ip,
1346            I::TEST_ADDRS.remote_ip,
1347        ));
1348        let mut api = ctx.tcp_api::<I>();
1349
1350        // Not connected, so no destination port.
1351        let s1 = api.create(Default::default());
1352        api.bind(&s1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
1353            .expect("bind should succeed");
1354        api.listen(&s1, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
1355
1356        // Connected, so has a destination port.
1357        let s2 = api.create(Default::default());
1358        api.bind(&s2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
1359            .expect("bind should succeed");
1360        api.connect(&s2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_1)
1361            .expect("connect should succeed");
1362
1363        let mut results = Vec::new();
1364        api.bound_sockets_diagnostics(
1365            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
1366                TcpSocketMatcher::DstPort(BoundPortMatcher::Unbound),
1367            )),
1368            &mut results,
1369            false,
1370        );
1371        assert_eq!(
1372            results,
1373            vec![TcpSocketDiagnostics {
1374                state_machine: netstack3_base::TcpSocketState::Listen,
1375                tuple: TcpSocketDiagnosticTuple::Bound {
1376                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
1377                    src_port: LOCAL_PORT_1
1378                },
1379                cookie: s1.socket_cookie(),
1380                marks: Marks::default(),
1381                tcp_info: None,
1382            }]
1383        );
1384
1385        results.clear();
1386        api.bound_sockets_diagnostics(
1387            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
1388                TcpSocketMatcher::DstPort(BoundPortMatcher::Bound(PortMatcher {
1389                    range: REMOTE_PORT_1.get()..=REMOTE_PORT_1.get(),
1390                    invert: false,
1391                })),
1392            )),
1393            &mut results,
1394            false,
1395        );
1396        assert_eq!(
1397            results,
1398            vec![TcpSocketDiagnostics {
1399                state_machine: netstack3_base::TcpSocketState::SynSent,
1400                tuple: TcpSocketDiagnosticTuple::Connected {
1401                    src_addr: I::TEST_ADDRS.local_ip.get(),
1402                    src_port: LOCAL_PORT_2,
1403                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
1404                    dst_port: REMOTE_PORT_1,
1405                },
1406                cookie: s2.socket_cookie(),
1407                marks: Marks::default(),
1408                tcp_info: None,
1409            }]
1410        );
1411    }
1412
1413    const LOCAL: &'static str = "local";
1414    const REMOTE: &'static str = "remote";
1415
1416    #[ip_test(I)]
1417    fn disconnect_connected<I: TcpTestIpExt>()
1418    where
1419        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
1420                I,
1421                TcpBindingsCtx<FakeDeviceId>,
1422                SingleStackConverter = I::SingleStackConverter,
1423                DualStackConverter = I::DualStackConverter,
1424            >,
1425    {
1426        set_logger_for_test();
1427
1428        let mut net = FakeTcpNetworkSpec::new_network(
1429            [
1430                (
1431                    LOCAL,
1432                    TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1433                        I::TEST_ADDRS.local_ip,
1434                        I::TEST_ADDRS.remote_ip,
1435                    )),
1436                ),
1437                (
1438                    REMOTE,
1439                    TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1440                        I::TEST_ADDRS.remote_ip,
1441                        I::TEST_ADDRS.local_ip,
1442                    )),
1443                ),
1444            ],
1445            |net, meta| {
1446                if net == LOCAL {
1447                    alloc::vec![(REMOTE, meta, None)]
1448                } else {
1449                    alloc::vec![(LOCAL, meta, None)]
1450                }
1451            },
1452        );
1453
1454        let client_socket = net.with_context(LOCAL, |ctx| {
1455            let mut api = ctx.tcp_api();
1456            let s: TcpSocketId<I, _, _> = api.create(Default::default());
1457            // Set device so we can check that it's not cleared.
1458            api.set_device(&s, Some(FakeDeviceId)).expect("set device should succeed");
1459            api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
1460                .expect("bind should succeed");
1461            api.connect(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_1)
1462                .expect("can connect");
1463            s
1464        });
1465        let server_listener = net.with_context(REMOTE, |ctx| {
1466            let mut api = ctx.tcp_api::<I>();
1467            let s = api.create(Default::default());
1468            api.bind(&s, None, Some(REMOTE_PORT_1)).expect("failed to bind the server socket");
1469            api.listen(&s, NonZeroUsize::MIN).expect("can listen");
1470            s
1471        });
1472
1473        net.run_until_idle();
1474
1475        let server_socket = net.with_context(REMOTE, |ctx| {
1476            let mut api = ctx.tcp_api();
1477            let (server_connection, _addr, _buffers) =
1478                api.accept(&server_listener).expect("connection is waiting");
1479            server_connection
1480        });
1481
1482        net.with_context(LOCAL, |ctx| {
1483            let mut api = ctx.tcp_api();
1484            let count = api.disconnect_bound(&IpSocketMatcher::Cookie(SocketCookieMatcher {
1485                cookie: client_socket.socket_cookie().export_value(),
1486                invert: false,
1487            }));
1488            assert_eq!(count, 1);
1489
1490            ctx.core_ctx.with_socket(&client_socket, |s| {
1491                let conn = assert_matches!(
1492                    &s.socket_state,
1493                    TcpSocketStateInner::Connected {conn, ..} => conn
1494                );
1495
1496                let info = I::get_conn_info(conn);
1497                assert_eq!(info.local_addr.ip.addr().get(), I::TEST_ADDRS.local_ip.get());
1498                assert_eq!(info.remote_addr.ip.addr().get(), I::TEST_ADDRS.remote_ip.get());
1499                assert_eq!(info.local_addr.port, LOCAL_PORT_1);
1500                assert_eq!(info.remote_addr.port, REMOTE_PORT_1);
1501                assert_eq!(info.device, Some(FakeDeviceId.downgrade()));
1502            });
1503        });
1504
1505        // Deliver the RST from `client_socket` to `server_socket`.
1506        net.run_until_idle();
1507
1508        net.with_context(REMOTE, |ctx| {
1509            let mut api = ctx.tcp_api();
1510            assert_matches!(
1511                api.get_socket_error(&server_socket),
1512                Some(ConnectionError::ConnectionReset)
1513            );
1514        });
1515
1516        // Trying to connect to the same remote will "succeed" because the
1517        // connect call is idempotent. However, the socket thinks it's already
1518        // connected so no SYN will be sent. This is done to align with the
1519        // implementation of TcpApi::shutdown.
1520
1521        net.with_context(LOCAL, |ctx| {
1522            let mut api = ctx.tcp_api::<I>();
1523            assert_matches!(
1524                api.connect(
1525                    &client_socket,
1526                    Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
1527                    REMOTE_PORT_1
1528                ),
1529                Ok(())
1530            );
1531        });
1532        net.run_until_idle();
1533        net.with_context(REMOTE, |ctx| {
1534            let mut api = ctx.tcp_api();
1535            assert_matches!(api.accept(&server_listener), Err(AcceptError::WouldBlock));
1536        });
1537
1538        // Another socket can be created with the exact same tuple as the
1539        // disconnected socket. This matches Linux behavior.
1540        net.with_context(LOCAL, |ctx| {
1541            let mut api = ctx.tcp_api();
1542            let s: TcpSocketId<I, _, _> = api.create(Default::default());
1543            api.set_device(&s, Some(FakeDeviceId)).expect("set device should succeed");
1544            api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
1545                .expect("bind should succeed");
1546            api.connect(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_1)
1547                .expect("can connect");
1548
1549            // Close both sockets to ensure nothing bad happens from them
1550            // having the same tuple.
1551            api.close(s);
1552            api.close(client_socket);
1553        });
1554    }
1555
1556    #[ip_test(I)]
1557    fn disconnect_listener<I: TcpTestIpExt>()
1558    where
1559        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
1560                I,
1561                TcpBindingsCtx<FakeDeviceId>,
1562                SingleStackConverter = I::SingleStackConverter,
1563                DualStackConverter = I::DualStackConverter,
1564            >,
1565    {
1566        set_logger_for_test();
1567
1568        let mut net = FakeTcpNetworkSpec::new_network(
1569            [
1570                (
1571                    LOCAL,
1572                    TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1573                        I::TEST_ADDRS.local_ip,
1574                        I::TEST_ADDRS.remote_ip,
1575                    )),
1576                ),
1577                (
1578                    REMOTE,
1579                    TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1580                        I::TEST_ADDRS.remote_ip,
1581                        I::TEST_ADDRS.local_ip,
1582                    )),
1583                ),
1584            ],
1585            |net, meta| {
1586                if net == LOCAL {
1587                    alloc::vec![(REMOTE, meta, None)]
1588                } else {
1589                    alloc::vec![(LOCAL, meta, None)]
1590                }
1591            },
1592        );
1593
1594        let client_accepted_socket = net.with_context(LOCAL, |ctx| {
1595            let mut api = ctx.tcp_api();
1596            let s: TcpSocketId<I, _, _> = api.create(Default::default());
1597            api.connect(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_1)
1598                .expect("can connect");
1599            s
1600        });
1601        let client_unaccepted_socket = net.with_context(LOCAL, |ctx| {
1602            let mut api = ctx.tcp_api();
1603            let s: TcpSocketId<I, _, _> = api.create(Default::default());
1604            api.connect(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_1)
1605                .expect("can connect");
1606            s
1607        });
1608        let server_listener = net.with_context(REMOTE, |ctx| {
1609            let mut api = ctx.tcp_api::<I>();
1610            let s = api.create(Default::default());
1611            // Set device so we can check that it's not cleared.
1612            api.set_device(&s, Some(FakeDeviceId)).expect("set device should succeed");
1613            api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), Some(REMOTE_PORT_1))
1614                .expect("bind should succeed");
1615            api.listen(&s, NonZeroUsize::new(2).unwrap()).expect("listen should succeed");
1616            s
1617        });
1618
1619        // Establish both connections.
1620        net.run_until_idle();
1621
1622        net.with_context(REMOTE, |ctx| {
1623            let mut api = ctx.tcp_api();
1624            let _: (TcpSocketId<_, _, _>, _, _) =
1625                api.accept(&server_listener).expect("connection is waiting");
1626
1627            let count = api.disconnect_bound(&IpSocketMatcher::Cookie(SocketCookieMatcher {
1628                cookie: server_listener.socket_cookie().export_value(),
1629                invert: false,
1630            }));
1631            assert_eq!(count, 1);
1632
1633            ctx.core_ctx.with_socket(&server_listener, |s| {
1634                let addr = assert_matches!(
1635                    &s.socket_state,
1636                    TcpSocketStateInner::Bound(BoundState { addr, .. }) => addr
1637                );
1638                let info = I::get_bound_info(addr);
1639                assert_eq!(
1640                    info.addr.expect("address is set").addr().get(),
1641                    I::TEST_ADDRS.remote_ip.get(),
1642                );
1643                assert_eq!(info.port, REMOTE_PORT_1);
1644                assert_eq!(info.device, Some(FakeDeviceId.downgrade()));
1645            });
1646
1647            let mut api = ctx.tcp_api::<I>();
1648            api.listen(&server_listener, NonZeroUsize::new(1).unwrap())
1649                .expect("listen should succeed");
1650        });
1651
1652        // Deliver the RSTs.
1653        net.run_until_idle();
1654
1655        // Since the first socket was already accepted, it shouldn't have been
1656        // affected by the disconnection of the listener. However, the (pending)
1657        // second socket should have received an RST.
1658        net.with_context(LOCAL, |ctx| {
1659            let mut api = ctx.tcp_api();
1660            assert_matches!(api.get_socket_error(&client_accepted_socket), None);
1661            assert_matches!(
1662                api.get_socket_error(&client_unaccepted_socket),
1663                Some(ConnectionError::ConnectionReset)
1664            );
1665        })
1666    }
1667
1668    #[ip_test(I)]
1669    fn disconnect_bound<I: TcpTestIpExt>()
1670    where
1671        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
1672                I,
1673                TcpBindingsCtx<FakeDeviceId>,
1674                SingleStackConverter = I::SingleStackConverter,
1675                DualStackConverter = I::DualStackConverter,
1676            >,
1677    {
1678        set_logger_for_test();
1679
1680        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1681            I::TEST_ADDRS.local_ip,
1682            I::TEST_ADDRS.remote_ip,
1683        ));
1684        let mut api = ctx.tcp_api::<I>();
1685        let s = api.create(Default::default());
1686        // Set device so we can check that it's not cleared.
1687        api.set_device(&s, Some(FakeDeviceId)).expect("set device should succeed");
1688        api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
1689            .expect("bind should succeed");
1690
1691        let mut api = ctx.tcp_api::<I>();
1692        let count = api.disconnect_bound(&IpSocketMatcher::Cookie(SocketCookieMatcher {
1693            cookie: s.socket_cookie().export_value(),
1694            invert: false,
1695        }));
1696        assert_eq!(count, 1);
1697
1698        ctx.core_ctx.with_socket(&s, |s| {
1699            let addr = assert_matches!(
1700                &s.socket_state,
1701                TcpSocketStateInner::Bound(BoundState { addr, .. }) => addr
1702            );
1703            let info = I::get_bound_info(addr);
1704            assert_eq!(
1705                info.addr.expect("address is set").addr().get(),
1706                I::TEST_ADDRS.local_ip.get(),
1707            );
1708            assert_eq!(info.port, LOCAL_PORT_1);
1709            assert_eq!(info.device, Some(FakeDeviceId.downgrade()));
1710        });
1711    }
1712
1713    #[ip_test(I)]
1714    fn tcp_info_unbound<I: TcpTestIpExt>()
1715    where
1716        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
1717                I,
1718                TcpBindingsCtx<FakeDeviceId>,
1719                SingleStackConverter = I::SingleStackConverter,
1720                DualStackConverter = I::DualStackConverter,
1721            >,
1722    {
1723        set_logger_for_test();
1724        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1725            I::TEST_ADDRS.local_ip,
1726            I::TEST_ADDRS.remote_ip,
1727        ));
1728        let mut api = ctx.tcp_api::<I>();
1729        let s = api.create(Default::default());
1730        let info = api.get_tcp_info(&s);
1731        assert_eq!(
1732            info,
1733            TcpSocketInfo {
1734                state: netstack3_base::TcpSocketState::Close,
1735                ca_state: CongestionControlState::Open,
1736                rto: None,
1737                rtt: None,
1738                rtt_var: None,
1739                snd_ssthresh: 0,
1740                snd_cwnd: 0,
1741                retransmits: 0,
1742                last_ack_recv: None,
1743                segs_out: 0,
1744                segs_in: 0,
1745                last_data_sent: None,
1746                snd_mss: None,
1747                rcv_mss: None,
1748            }
1749        );
1750    }
1751
1752    #[ip_test(I)]
1753    #[test_case(true, netstack3_base::TcpSocketState::Listen; "listen")]
1754    #[test_case(false, netstack3_base::TcpSocketState::Close; "bound")]
1755    fn tcp_info_bound<I: TcpTestIpExt>(
1756        should_listen: bool,
1757        expected_state: netstack3_base::TcpSocketState,
1758    ) where
1759        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
1760                I,
1761                TcpBindingsCtx<FakeDeviceId>,
1762                SingleStackConverter = I::SingleStackConverter,
1763                DualStackConverter = I::DualStackConverter,
1764            >,
1765    {
1766        set_logger_for_test();
1767        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1768            I::TEST_ADDRS.local_ip,
1769            I::TEST_ADDRS.remote_ip,
1770        ));
1771        let mut api = ctx.tcp_api::<I>();
1772        let s = api.create(Default::default());
1773        api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
1774            .expect("bind should succeed");
1775        if should_listen {
1776            api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
1777        }
1778
1779        let info = api.get_tcp_info(&s);
1780        assert_eq!(
1781            info,
1782            TcpSocketInfo {
1783                state: expected_state,
1784                ca_state: CongestionControlState::Open,
1785                rto: None,
1786                rtt: None,
1787                rtt_var: None,
1788                snd_ssthresh: 0,
1789                snd_cwnd: 0,
1790                retransmits: 0,
1791                last_ack_recv: None,
1792                segs_out: 0,
1793                segs_in: 0,
1794                last_data_sent: None,
1795                snd_mss: None,
1796                rcv_mss: None,
1797            }
1798        );
1799    }
1800
1801    #[ip_test(I)]
1802    fn tcp_info_connected<I: TcpTestIpExt>()
1803    where
1804        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
1805                I,
1806                TcpBindingsCtx<FakeDeviceId>,
1807                SingleStackConverter = I::SingleStackConverter,
1808                DualStackConverter = I::DualStackConverter,
1809            >,
1810    {
1811        set_logger_for_test();
1812
1813        let mut net = FakeTcpNetworkSpec::new_network(
1814            [
1815                (
1816                    LOCAL,
1817                    TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1818                        I::TEST_ADDRS.local_ip,
1819                        I::TEST_ADDRS.remote_ip,
1820                    )),
1821                ),
1822                (
1823                    REMOTE,
1824                    TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
1825                        I::TEST_ADDRS.remote_ip,
1826                        I::TEST_ADDRS.local_ip,
1827                    )),
1828                ),
1829            ],
1830            |net, meta| {
1831                if net == LOCAL {
1832                    vec![(REMOTE, meta, Some(Duration::from_millis(100)))]
1833                } else {
1834                    vec![(LOCAL, meta, Some(Duration::from_millis(100)))]
1835                }
1836            },
1837        );
1838
1839        let client_socket = net.with_context(LOCAL, |ctx| {
1840            let mut api = ctx.tcp_api();
1841            let s: TcpSocketId<I, _, _> = api.create(Default::default());
1842            s
1843        });
1844
1845        let server_socket = net.with_context(REMOTE, |ctx| {
1846            let mut api = ctx.tcp_api::<I>();
1847            let s = api.create(Default::default());
1848            api.set_device(&s, Some(FakeDeviceId)).expect("set device should succeed");
1849            api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), Some(REMOTE_PORT_1))
1850                .expect("bind should succeed");
1851            api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
1852            s
1853        });
1854
1855        net.with_context(LOCAL, |ctx| {
1856            let mut api = ctx.tcp_api();
1857            api.connect(
1858                &client_socket,
1859                Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
1860                REMOTE_PORT_1,
1861            )
1862            .expect("should connect");
1863        });
1864
1865        net.run_until_idle();
1866
1867        net.with_context(LOCAL, |ctx| {
1868            let mut api = ctx.tcp_api();
1869            let data = [1u8; 10];
1870            api.with_send_buffer(&client_socket, |buf| {
1871                buf.enqueue_data(&data[..]);
1872            })
1873            .expect("send buffer should be available");
1874            api.do_send(&client_socket);
1875        });
1876
1877        net.run_until_idle();
1878
1879        let accepted_socket = net.with_context(REMOTE, |ctx| {
1880            let mut api = ctx.tcp_api();
1881            let (accepted, _, _) = api.accept(&server_socket).expect("accept should succeed");
1882
1883            // Send data back to client to ensure client receives a data segment
1884            // (populating last_segment_at/last_ack_recv).
1885            let data = [2u8; 10];
1886            api.with_send_buffer(&accepted, |buf| {
1887                buf.enqueue_data(&data[..]);
1888            })
1889            .expect("send buffer should be available");
1890            api.do_send(&accepted);
1891
1892            accepted
1893        });
1894
1895        net.run_until_idle();
1896
1897        net.with_context(LOCAL, |ctx| {
1898            let mut api = ctx.tcp_api();
1899            let info = api.get_tcp_info(&client_socket);
1900
1901            assert_matches!(
1902                info,
1903                TcpSocketInfo {
1904                    state: netstack3_base::TcpSocketState::Established,
1905                    ca_state: CongestionControlState::Open,
1906                    retransmits: 0,
1907                    snd_ssthresh: u32::MAX,
1908                    segs_out,
1909                    segs_in,
1910                    snd_cwnd,
1911                    rto: Some(rto),
1912                    rtt: Some(rtt),
1913                    rtt_var: Some(rtt_var),
1914                    last_ack_recv: Some(last_ack_recv),
1915                    last_data_sent: Some(last_data_sent),
1916                    snd_mss: _,
1917                    rcv_mss: _,
1918                } => {
1919                    assert_eq!(segs_out, 4);
1920                    assert_eq!(segs_in, 3);
1921                    assert!(snd_cwnd > 0, "{snd_cwnd} > 0");
1922                    assert_eq!(rto, Duration::from_millis(500));
1923                    assert_eq!(rtt, Duration::from_millis(200));
1924                    assert_eq!(rtt_var, Duration::from_millis(75));
1925                    assert_eq!(last_ack_recv, FakeInstant::from(Duration::from_millis(1800)));
1926                    assert_eq!(last_data_sent, FakeInstant::from(Duration::from_millis(1100)));
1927                }
1928            );
1929        });
1930
1931        net.with_context(REMOTE, |ctx| {
1932            let mut api = ctx.tcp_api();
1933            let info = api.get_tcp_info(&accepted_socket);
1934
1935            assert_matches!(
1936                info,
1937                TcpSocketInfo {
1938                    state: netstack3_base::TcpSocketState::Established,
1939                    ca_state: CongestionControlState::Open,
1940                    retransmits: 0,
1941                    snd_ssthresh: u32::MAX,
1942                    segs_out,
1943                    segs_in,
1944                    snd_cwnd,
1945                    rto: Some(rto),
1946                    rtt: Some(rtt),
1947                    rtt_var: Some(rtt_var),
1948                    last_ack_recv: Some(last_ack_recv),
1949                    last_data_sent: Some(last_data_sent),
1950                    snd_mss: _,
1951                    rcv_mss: _,
1952                } => {
1953                    assert_eq!(segs_out, 2);
1954                    assert_eq!(segs_in, 3);
1955                    assert!(snd_cwnd > 0, "{snd_cwnd} > 0");
1956                    assert_eq!(rto, Duration::from_millis(500));
1957                    assert_eq!(rtt, Duration::from_millis(200));
1958                    assert_eq!(rtt_var, Duration::from_millis(75));
1959                    assert_eq!(last_ack_recv, FakeInstant::from(Duration::from_millis(1200)));
1960                    assert_eq!(last_data_sent, FakeInstant::from(Duration::from_millis(1700)));
1961                }
1962            );
1963        });
1964    }
1965}