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