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