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    BoundSocketState, DualStackIpExt, SocketCookie, TcpBindingsTypes, TcpSocketId, TcpSocketState,
14    TcpSocketStateInner,
15};
16use crate::internal::state::State;
17
18/// Required state gathered into one struct for matching a socket, so it's
19/// possible to implement traits against the collection.
20pub(crate) struct TcpSocketStateForMatching<
21    'a,
22    I: DualStackIpExt,
23    D: netstack3_base::WeakDeviceIdentifier,
24    BT: TcpBindingsTypes,
25> {
26    pub(crate) state: &'a TcpSocketState<I, D, BT>,
27    pub(crate) id: &'a TcpSocketId<I, D, BT>,
28}
29
30impl<'a, I: DualStackIpExt, D: netstack3_base::WeakDeviceIdentifier, BT: TcpBindingsTypes>
31    netstack3_base::MaybeSocketTransportProperties for TcpSocketStateForMatching<'a, I, D, BT>
32{
33    type TcpProps<'b>
34        = Self
35    where
36        Self: 'b;
37
38    type UdpProps<'b>
39        = Never
40    where
41        Self: 'b;
42
43    fn tcp_socket_properties(&self) -> Option<&Self::TcpProps<'_>> {
44        Some(self)
45    }
46
47    fn udp_socket_properties(&self) -> Option<&Self::UdpProps<'_>> {
48        None
49    }
50}
51
52impl<'a, I: DualStackIpExt, D: netstack3_base::WeakDeviceIdentifier, BT: TcpBindingsTypes>
53    netstack3_base::TcpSocketProperties for TcpSocketStateForMatching<'a, I, D, BT>
54{
55    fn src_port_matches(&self, matcher: &netstack3_base::PortMatcher) -> bool {
56        let src_port = match &self.state.socket_state {
57            TcpSocketStateInner::Unbound(_) => return false,
58            TcpSocketStateInner::Bound(BoundSocketState::Listener((_, _, addr))) => {
59                I::get_bound_info(addr).port.get()
60            }
61            TcpSocketStateInner::Bound(BoundSocketState::Connected { conn, .. }) => {
62                I::get_conn_info(conn).local_addr.port.get()
63            }
64        };
65
66        matcher.matches(&src_port)
67    }
68
69    fn dst_port_matches(&self, matcher: &netstack3_base::PortMatcher) -> bool {
70        let dst_port = match &self.state.socket_state {
71            TcpSocketStateInner::Unbound(_)
72            | TcpSocketStateInner::Bound(BoundSocketState::Listener(_)) => return false,
73            TcpSocketStateInner::Bound(BoundSocketState::Connected { conn, .. }) => {
74                I::get_conn_info(conn).remote_addr.port.get()
75            }
76        };
77
78        matcher.matches(&dst_port)
79    }
80
81    fn state_matches(&self, matcher: &netstack3_base::TcpStateMatcher) -> bool {
82        matcher.matches(&self.state.base_state())
83    }
84}
85
86impl<'a, I: DualStackIpExt, D: netstack3_base::WeakDeviceIdentifier, BT: TcpBindingsTypes>
87    IpSocketProperties<BT::DeviceClass> for TcpSocketStateForMatching<'a, I, D, BT>
88where
89    D::Strong: netstack3_base::InterfaceProperties<BT::DeviceClass>,
90{
91    fn family_matches(&self, family: &net_types::ip::IpVersion) -> bool {
92        I::VERSION == *family
93    }
94
95    fn src_addr_matches(&self, addr: &netstack3_base::AddressMatcherEither) -> bool {
96        let src_addr = match &self.state.socket_state {
97            TcpSocketStateInner::Unbound(_) => None,
98            TcpSocketStateInner::Bound(BoundSocketState::Listener((_, _, addr))) => {
99                I::get_bound_info(addr).addr.map(|a| a.addr().get())
100            }
101            TcpSocketStateInner::Bound(BoundSocketState::Connected { conn, .. }) => {
102                Some(I::get_conn_info(conn).local_addr.ip.addr().get())
103            }
104        };
105
106        let Some(src_addr) = src_addr else { return false };
107        addr.matches(&src_addr.to_ip_addr())
108    }
109
110    fn dst_addr_matches(&self, addr: &netstack3_base::AddressMatcherEither) -> bool {
111        let dst_addr = match &self.state.socket_state {
112            TcpSocketStateInner::Unbound(_)
113            | TcpSocketStateInner::Bound(BoundSocketState::Listener(_)) => return false,
114            TcpSocketStateInner::Bound(BoundSocketState::Connected { conn, .. }) => {
115                I::get_conn_info(conn).remote_addr.ip.addr().get()
116            }
117        };
118
119        addr.matches(&dst_addr.to_ip_addr())
120    }
121
122    fn transport_protocol_matches(
123        &self,
124        proto: &netstack3_base::SocketTransportProtocolMatcher,
125    ) -> bool {
126        proto.matches(self)
127    }
128
129    fn bound_interface_matches(
130        &self,
131        iface: &netstack3_base::BoundInterfaceMatcher<BT::DeviceClass>,
132    ) -> bool {
133        let device = match &self.state.socket_state {
134            TcpSocketStateInner::Unbound(_) => None,
135            TcpSocketStateInner::Bound(BoundSocketState::Listener((_, _, addr))) => {
136                I::get_bound_info(addr).device
137            }
138            TcpSocketStateInner::Bound(BoundSocketState::Connected { conn, .. }) => {
139                I::get_conn_info(conn).device
140            }
141        };
142        iface.matches(&device.and_then(|d| d.upgrade()).as_ref())
143    }
144
145    fn cookie_matches(&self, cookie: &netstack3_base::SocketCookieMatcher) -> bool {
146        cookie.matches(&self.id.socket_cookie().export_value())
147    }
148
149    fn mark_matches(&self, matcher: &netstack3_base::MarkInDomainMatcher) -> bool {
150        matcher.matcher.matches(self.state.socket_options.ip_options.marks.get(matcher.domain))
151    }
152}
153
154/// Publicly-accessible diagnostic information about TCP sockets.
155#[cfg_attr(any(test, feature = "testutils"), derive(Debug, PartialEq, Eq))]
156pub struct TcpSocketDiagnostics<I: Ip> {
157    /// The socket's TCP state machine.
158    pub state_machine: netstack3_base::TcpSocketState,
159    /// The socket's tuple.
160    pub tuple: TcpSocketDiagnosticTuple<I>,
161    /// The socket's cookie.
162    pub cookie: SocketCookie,
163    /// The socket's marks.
164    pub marks: Marks,
165}
166
167/// The tuple of a TCP socket.
168///
169/// This is separate from the state machine state because it's possible for
170/// some states to be entered while having just the 2-tuple or the full
171/// 4-tuple (CLOSED and LISTENING).
172#[derive(Debug, PartialEq, Eq)]
173#[allow(missing_docs)]
174pub enum TcpSocketDiagnosticTuple<I: Ip> {
175    /// The socket is bound, but not connected. Only the 2-tuple is available,
176    /// although the source address might be None if the socket is bound to the
177    /// catch-all address.
178    Bound { src_addr: Option<I::Addr>, src_port: NonZeroU16 },
179    /// The socket is connected, so the full 4-tuple is available.
180    Connected { src_addr: I::Addr, src_port: NonZeroU16, dst_addr: I::Addr, dst_port: NonZeroU16 },
181}
182
183impl<I: Ip> TcpSocketDiagnosticTuple<I> {
184    /// Returns the socket's source address.
185    pub fn src_addr(&self) -> Option<I::Addr> {
186        match self {
187            Self::Bound { src_addr, src_port: _ } => *src_addr,
188            Self::Connected { src_addr, src_port: _, dst_addr: _, dst_port: _ } => Some(*src_addr),
189        }
190    }
191
192    /// Returns the socket's source port.
193    pub fn src_port(&self) -> Option<NonZeroU16> {
194        match self {
195            Self::Bound { src_addr: _, src_port }
196            | Self::Connected { src_addr: _, src_port, dst_addr: _, dst_port: _ } => {
197                Some(*src_port)
198            }
199        }
200    }
201
202    /// Returns the socket's destination address.
203    pub fn dst_addr(&self) -> Option<I::Addr> {
204        match self {
205            Self::Bound { src_addr: _, src_port: _ } => None,
206            Self::Connected { src_addr: _, src_port: _, dst_addr, dst_port: _ } => Some(*dst_addr),
207        }
208    }
209
210    /// Returns the socket's destination port.
211    pub fn dst_port(&self) -> Option<NonZeroU16> {
212        match self {
213            Self::Bound { src_addr: _, src_port: _ } => None,
214            Self::Connected { src_addr: _, src_port: _, dst_addr: _, dst_port } => Some(*dst_port),
215        }
216    }
217}
218
219impl<I, D, BT> TcpSocketState<I, D, BT>
220where
221    I: DualStackIpExt,
222    D: WeakDeviceIdentifier,
223    BT: TcpBindingsTypes,
224{
225    pub(crate) fn get_diagnostics(
226        &self,
227    ) -> Option<(TcpSocketDiagnosticTuple<I>, netstack3_base::TcpSocketState, Marks)> {
228        let tuple = match &self.socket_state {
229            TcpSocketStateInner::Unbound(_) => None,
230            TcpSocketStateInner::Bound(BoundSocketState::Listener((_, _, addr))) => {
231                let addr_info = I::get_bound_info(addr);
232                Some(TcpSocketDiagnosticTuple::Bound {
233                    src_addr: addr_info.addr.map(|ip| ip.addr().get()),
234                    src_port: addr_info.port,
235                })
236            }
237            TcpSocketStateInner::Bound(BoundSocketState::Connected { conn, .. }) => {
238                let info = I::get_conn_info(conn);
239                Some(TcpSocketDiagnosticTuple::Connected {
240                    src_addr: info.local_addr.ip.addr().get(),
241                    dst_addr: info.remote_addr.ip.addr().get(),
242                    src_port: info.local_addr.port,
243                    dst_port: info.remote_addr.port,
244                })
245            }
246        }?;
247
248        Some((tuple, self.base_state(), self.socket_options.ip_options.marks))
249    }
250
251    pub(crate) fn base_state(&self) -> netstack3_base::TcpSocketState {
252        match &self.socket_state {
253            TcpSocketStateInner::Unbound(_) => netstack3_base::TcpSocketState::Close,
254            TcpSocketStateInner::Bound(BoundSocketState::Listener(_)) => {
255                netstack3_base::TcpSocketState::Listen
256            }
257            TcpSocketStateInner::Bound(BoundSocketState::Connected { conn, .. }) => {
258                match I::get_state(conn) {
259                    State::Closed(_) => netstack3_base::TcpSocketState::Close,
260                    State::Listen(_) => netstack3_base::TcpSocketState::Listen,
261                    State::SynRcvd(_) => netstack3_base::TcpSocketState::SynRecv,
262                    State::SynSent(_) => netstack3_base::TcpSocketState::SynSent,
263                    State::Established(_) => netstack3_base::TcpSocketState::Established,
264                    State::CloseWait(_) => netstack3_base::TcpSocketState::CloseWait,
265                    State::LastAck(_) => netstack3_base::TcpSocketState::LastAck,
266                    State::FinWait1(_) => netstack3_base::TcpSocketState::FinWait1,
267                    State::FinWait2(_) => netstack3_base::TcpSocketState::FinWait2,
268                    State::Closing(_) => netstack3_base::TcpSocketState::Closing,
269                    State::TimeWait(_) => netstack3_base::TcpSocketState::TimeWait,
270                }
271            }
272        }
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use alloc::string::ToString;
279    use alloc::vec;
280    use alloc::vec::Vec;
281    use core::num::NonZeroUsize;
282
283    use ip_test_macro::ip_test;
284    use net_types::ZonedAddr;
285    use net_types::ip::{Ip, Subnet};
286    use netstack3_base::testutil::FakeDeviceId;
287    use netstack3_base::{
288        AddressMatcher, AddressMatcherEither, AddressMatcherType, BoundInterfaceMatcher,
289        InterfaceMatcher, IpSocketMatcher, Mark, MarkDomain, MarkMatcher, PortMatcher,
290        SocketCookieMatcher, SocketTransportProtocolMatcher, SubnetMatcher, TcpSocketMatcher,
291        TcpStateMatcher, UdpSocketMatcher,
292    };
293
294    use super::*;
295    use crate::TcpContext;
296    use crate::internal::socket::tests::{
297        TcpApiExt, TcpBindingsCtx, TcpCoreCtx, TcpCtx, TcpTestIpExt,
298    };
299
300    const LOCAL_PORT_1: NonZeroU16 = NonZeroU16::new(1234).unwrap();
301    const LOCAL_PORT_2: NonZeroU16 = NonZeroU16::new(5678).unwrap();
302    const LOCAL_PORT_3: NonZeroU16 = NonZeroU16::new(4321).unwrap();
303
304    const REMOTE_PORT_1: NonZeroU16 = NonZeroU16::new(100).unwrap();
305    const REMOTE_PORT_2: NonZeroU16 = NonZeroU16::new(200).unwrap();
306
307    const MARK: u32 = 0x10;
308    const MARK_MASK: u32 = !0;
309
310    #[ip_test(I)]
311    fn diagnostics_match_ip_version<I: TcpTestIpExt>()
312    where
313        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
314            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
315    {
316        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
317            I::TEST_ADDRS.local_ip,
318            I::TEST_ADDRS.remote_ip,
319        ));
320        let mut api = ctx.tcp_api::<I>();
321        let s = api.create(Default::default());
322        api.bind(&s, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
323        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
324
325        let mut results = Vec::new();
326        api.bound_sockets_diagnostics(&IpSocketMatcher::Family(I::VERSION), &mut results);
327        assert_eq!(
328            results,
329            vec![TcpSocketDiagnostics {
330                state_machine: netstack3_base::TcpSocketState::Listen,
331                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
332                cookie: s.socket_cookie(),
333                marks: Marks::default(),
334            }]
335        );
336
337        results.clear();
338        api.bound_sockets_diagnostics(
339            &IpSocketMatcher::Family(
340                <<I as netstack3_base::socket::DualStackIpExt>::OtherVersion as Ip>::VERSION,
341            ),
342            &mut results,
343        );
344        assert_eq!(results, Vec::new());
345    }
346
347    #[ip_test(I)]
348    fn diagnostics_match_src_addr<I: TcpTestIpExt>()
349    where
350        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
351            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
352    {
353        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
354            I::TEST_ADDRS.local_ip,
355            I::TEST_ADDRS.remote_ip,
356        ));
357        let mut api = ctx.tcp_api::<I>();
358        let s = api.create(Default::default());
359        api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
360            .expect("bind should succeed");
361        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
362
363        let mut results = Vec::new();
364        let matcher = I::map_ip_in(
365            I::TEST_ADDRS.local_ip.get(),
366            |addr| {
367                AddressMatcherEither::V4(AddressMatcher {
368                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
369                        Subnet::new(addr, 32).unwrap(),
370                    )),
371                    invert: false,
372                })
373            },
374            |addr| {
375                AddressMatcherEither::V6(AddressMatcher {
376                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
377                        Subnet::new(addr, 128).unwrap(),
378                    )),
379                    invert: false,
380                })
381            },
382        );
383        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results);
384        assert_eq!(
385            results,
386            vec![TcpSocketDiagnostics {
387                state_machine: netstack3_base::TcpSocketState::Listen,
388                tuple: TcpSocketDiagnosticTuple::Bound {
389                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
390                    src_port: LOCAL_PORT_1,
391                },
392                cookie: s.socket_cookie(),
393                marks: Marks::default(),
394            }]
395        );
396
397        results.clear();
398        let matcher = I::map_ip_in(
399            I::TEST_ADDRS.remote_ip.get(),
400            |addr| {
401                AddressMatcherEither::V4(AddressMatcher {
402                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
403                        Subnet::new(addr, 32).unwrap(),
404                    )),
405                    invert: false,
406                })
407            },
408            |addr| {
409                AddressMatcherEither::V6(AddressMatcher {
410                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
411                        Subnet::new(addr, 128).unwrap(),
412                    )),
413                    invert: false,
414                })
415            },
416        );
417        api.bound_sockets_diagnostics(&IpSocketMatcher::SrcAddr(matcher), &mut results);
418        assert_eq!(results, Vec::new());
419    }
420
421    #[ip_test(I)]
422    fn diagnostics_match_dst_addr<I: TcpTestIpExt>()
423    where
424        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
425            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
426    {
427        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
428            I::TEST_ADDRS.local_ip,
429            I::TEST_ADDRS.remote_ip,
430        ));
431        let mut api = ctx.tcp_api::<I>();
432        let s = api.create(Default::default());
433        api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
434            .expect("bind should succeed");
435        api.connect(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), LOCAL_PORT_2)
436            .expect("connect should succeed");
437
438        let mut results = Vec::new();
439        let matcher = I::map_ip_in(
440            I::TEST_ADDRS.remote_ip.get(),
441            |addr| {
442                AddressMatcherEither::V4(AddressMatcher {
443                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
444                        Subnet::new(addr, 32).unwrap(),
445                    )),
446                    invert: false,
447                })
448            },
449            |addr| {
450                AddressMatcherEither::V6(AddressMatcher {
451                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
452                        Subnet::new(addr, 128).unwrap(),
453                    )),
454                    invert: false,
455                })
456            },
457        );
458        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results);
459        assert_eq!(
460            results,
461            vec![TcpSocketDiagnostics {
462                state_machine: netstack3_base::TcpSocketState::SynSent,
463                tuple: TcpSocketDiagnosticTuple::Connected {
464                    src_addr: I::TEST_ADDRS.local_ip.get(),
465                    src_port: LOCAL_PORT_1,
466                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
467                    dst_port: LOCAL_PORT_2,
468                },
469                cookie: s.socket_cookie(),
470                marks: Marks::default(),
471            }]
472        );
473
474        results.clear();
475        let matcher = I::map_ip_in(
476            I::TEST_ADDRS.local_ip.get(),
477            |addr| {
478                AddressMatcherEither::V4(AddressMatcher {
479                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
480                        Subnet::new(addr, 32).unwrap(),
481                    )),
482                    invert: false,
483                })
484            },
485            |addr| {
486                AddressMatcherEither::V6(AddressMatcher {
487                    matcher: AddressMatcherType::Subnet(SubnetMatcher(
488                        Subnet::new(addr, 128).unwrap(),
489                    )),
490                    invert: false,
491                })
492            },
493        );
494        api.bound_sockets_diagnostics(&IpSocketMatcher::DstAddr(matcher), &mut results);
495        assert_eq!(results, Vec::new());
496    }
497
498    #[ip_test(I)]
499    fn diagnostics_match_proto<I: TcpTestIpExt>()
500    where
501        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
502            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
503    {
504        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
505            I::TEST_ADDRS.local_ip,
506            I::TEST_ADDRS.remote_ip,
507        ));
508        let mut api = ctx.tcp_api::<I>();
509        let s = api.create(Default::default());
510        api.bind(&s, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
511        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
512
513        let mut results = Vec::new();
514        api.bound_sockets_diagnostics(
515            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(TcpSocketMatcher::Empty)),
516            &mut results,
517        );
518        assert_eq!(
519            results,
520            vec![TcpSocketDiagnostics {
521                state_machine: netstack3_base::TcpSocketState::Listen,
522                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
523                cookie: s.socket_cookie(),
524                marks: Marks::default(),
525            }]
526        );
527
528        results.clear();
529        api.bound_sockets_diagnostics(
530            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Udp(UdpSocketMatcher::Empty)),
531            &mut results,
532        );
533        assert_eq!(results, Vec::new());
534    }
535
536    #[ip_test(I)]
537    fn diagnostics_match_device<I: TcpTestIpExt>()
538    where
539        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
540            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
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.set_device(&s, Some(FakeDeviceId)).expect("set device should succeed");
549        api.bind(&s, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
550        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
551
552        let mut results = Vec::new();
553        api.bound_sockets_diagnostics(
554            &IpSocketMatcher::BoundInterface(BoundInterfaceMatcher::Bound(InterfaceMatcher::Name(
555                FakeDeviceId::FAKE_NAME.to_string(),
556            ))),
557            &mut results,
558        );
559        assert_eq!(
560            results,
561            vec![TcpSocketDiagnostics {
562                state_machine: netstack3_base::TcpSocketState::Listen,
563                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
564                cookie: s.socket_cookie(),
565                marks: Marks::default(),
566            }]
567        );
568
569        results.clear();
570        api.bound_sockets_diagnostics(
571            &IpSocketMatcher::BoundInterface(BoundInterfaceMatcher::Unbound),
572            &mut results,
573        );
574        assert_eq!(results, Vec::new());
575    }
576
577    #[ip_test(I)]
578    fn diagnostics_match_cookie<I: TcpTestIpExt>()
579    where
580        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
581            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
582    {
583        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
584            I::TEST_ADDRS.local_ip,
585            I::TEST_ADDRS.remote_ip,
586        ));
587        let mut api = ctx.tcp_api::<I>();
588
589        let socket_1 = api.create(Default::default());
590        api.bind(&socket_1, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
591        api.listen(&socket_1, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
592        let socket_2 = api.create(Default::default());
593        api.bind(&socket_2, None, Some(LOCAL_PORT_2)).expect("bind should succeed");
594        api.listen(&socket_2, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
595
596        let mut results = Vec::new();
597        api.bound_sockets_diagnostics(
598            &IpSocketMatcher::Cookie(SocketCookieMatcher {
599                cookie: socket_1.socket_cookie().export_value(),
600                invert: false,
601            }),
602            &mut results,
603        );
604        assert_eq!(
605            results,
606            vec![TcpSocketDiagnostics {
607                state_machine: netstack3_base::TcpSocketState::Listen,
608                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
609                cookie: socket_1.socket_cookie(),
610                marks: Marks::default(),
611            }]
612        );
613
614        results.clear();
615        api.bound_sockets_diagnostics(
616            &IpSocketMatcher::Cookie(SocketCookieMatcher {
617                cookie: socket_2.socket_cookie().export_value(),
618                invert: false,
619            }),
620            &mut results,
621        );
622        assert_eq!(
623            results,
624            vec![TcpSocketDiagnostics {
625                state_machine: netstack3_base::TcpSocketState::Listen,
626                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_2 },
627                cookie: socket_2.socket_cookie(),
628                marks: Marks::default(),
629            }]
630        );
631    }
632
633    #[ip_test(I)]
634    #[test_case::test_case(MarkDomain::Mark1; "mark_1")]
635    #[test_case::test_case(MarkDomain::Mark2; "mark_2")]
636    fn diagnostics_match_mark<I: TcpTestIpExt>(domain: MarkDomain)
637    where
638        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
639            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
640    {
641        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
642            I::TEST_ADDRS.local_ip,
643            I::TEST_ADDRS.remote_ip,
644        ));
645        let mut api = ctx.tcp_api::<I>();
646
647        let s = api.create(Default::default());
648        api.bind(&s, None, Some(LOCAL_PORT_1)).expect("bind should succeed");
649        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
650        api.set_mark(&s, domain, Mark(Some(MARK)));
651
652        let mut results = Vec::new();
653        let matcher = |query_mark| {
654            IpSocketMatcher::Mark(netstack3_base::MarkInDomainMatcher {
655                domain,
656                matcher: MarkMatcher::Marked {
657                    mask: MARK_MASK,
658                    start: query_mark,
659                    end: query_mark,
660                    invert: false,
661                },
662            })
663        };
664        api.bound_sockets_diagnostics(&matcher(MARK), &mut results);
665        assert_eq!(
666            results,
667            vec![TcpSocketDiagnostics {
668                state_machine: netstack3_base::TcpSocketState::Listen,
669                tuple: TcpSocketDiagnosticTuple::Bound { src_addr: None, src_port: LOCAL_PORT_1 },
670                cookie: s.socket_cookie(),
671                marks: netstack3_base::MarkStorage::new([(domain, MARK)]),
672            }]
673        );
674
675        results.clear();
676        api.bound_sockets_diagnostics(&matcher(MARK + 1), &mut results);
677        assert_eq!(results, Vec::new());
678    }
679
680    #[ip_test(I)]
681    fn diagnostics_match_multiple<I: TcpTestIpExt>()
682    where
683        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>:
684            TcpContext<I, TcpBindingsCtx<FakeDeviceId>>,
685    {
686        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
687            I::TEST_ADDRS.local_ip,
688            I::TEST_ADDRS.remote_ip,
689        ));
690        let mut api = ctx.tcp_api::<I>();
691
692        let socket_1 = api.create(Default::default());
693        api.bind(&socket_1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
694            .expect("bind should succeed");
695        api.connect(&socket_1, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_1)
696            .expect("connect socket 1");
697        let socket_2 = api.create(Default::default());
698        api.bind(&socket_2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_2))
699            .expect("bind should succeed");
700        api.connect(&socket_2, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_1)
701            .expect("connect socket 2");
702        let socket_3 = api.create(Default::default());
703        api.bind(&socket_3, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_3))
704            .expect("bind should succeed");
705        api.connect(&socket_3, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), REMOTE_PORT_2)
706            .expect("connect socket 3");
707
708        let mut results = Vec::new();
709
710        api.bound_sockets_diagnostics(
711            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
712                TcpSocketMatcher::DstPort(PortMatcher {
713                    range: REMOTE_PORT_1.get()..=REMOTE_PORT_1.get(),
714                    invert: false,
715                }),
716            )),
717            &mut results,
718        );
719
720        results.sort_by(|a, b| a.cookie.cmp(&b.cookie));
721        let mut expected = vec![
722            TcpSocketDiagnostics {
723                state_machine: netstack3_base::TcpSocketState::SynSent,
724                tuple: TcpSocketDiagnosticTuple::Connected {
725                    src_addr: I::TEST_ADDRS.local_ip.get(),
726                    src_port: LOCAL_PORT_1,
727                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
728                    dst_port: REMOTE_PORT_1,
729                },
730                cookie: socket_1.socket_cookie(),
731                marks: Marks::default(),
732            },
733            TcpSocketDiagnostics {
734                state_machine: netstack3_base::TcpSocketState::SynSent,
735                tuple: TcpSocketDiagnosticTuple::Connected {
736                    src_addr: I::TEST_ADDRS.local_ip.get(),
737                    src_port: LOCAL_PORT_2,
738                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
739                    dst_port: REMOTE_PORT_1,
740                },
741                cookie: socket_2.socket_cookie(),
742                marks: Marks::default(),
743            },
744        ];
745        expected.sort_by(|a, b| a.cookie.cmp(&b.cookie));
746        assert_eq!(results, expected);
747
748        results.clear();
749        api.bound_sockets_diagnostics(
750            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
751                TcpSocketMatcher::DstPort(PortMatcher {
752                    range: REMOTE_PORT_2.get()..=REMOTE_PORT_2.get(),
753                    invert: false,
754                }),
755            )),
756            &mut results,
757        );
758        assert_eq!(
759            results,
760            vec![TcpSocketDiagnostics {
761                state_machine: netstack3_base::TcpSocketState::SynSent,
762                tuple: TcpSocketDiagnosticTuple::Connected {
763                    src_addr: I::TEST_ADDRS.local_ip.get(),
764                    src_port: LOCAL_PORT_3,
765                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
766                    dst_port: REMOTE_PORT_2,
767                },
768                cookie: socket_3.socket_cookie(),
769                marks: Marks::default(),
770            }]
771        );
772    }
773
774    #[ip_test(I)]
775    fn diagnostics_match_src_port<I: TcpTestIpExt>()
776    where
777        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
778                I,
779                TcpBindingsCtx<FakeDeviceId>,
780                SingleStackConverter = I::SingleStackConverter,
781                DualStackConverter = I::DualStackConverter,
782            >,
783    {
784        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
785            I::TEST_ADDRS.local_ip,
786            I::TEST_ADDRS.remote_ip,
787        ));
788        let mut api = ctx.tcp_api::<I>();
789        let s = api.create(Default::default());
790        api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
791            .expect("bind should succeed");
792        api.listen(&s, NonZeroUsize::new(1).unwrap()).expect("listen should succeed");
793
794        let mut results = Vec::new();
795        api.bound_sockets_diagnostics(
796            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
797                TcpSocketMatcher::SrcPort(PortMatcher {
798                    range: LOCAL_PORT_1.get()..=LOCAL_PORT_1.get(),
799                    invert: false,
800                }),
801            )),
802            &mut results,
803        );
804        assert_eq!(
805            results,
806            vec![TcpSocketDiagnostics {
807                state_machine: netstack3_base::TcpSocketState::Listen,
808                tuple: TcpSocketDiagnosticTuple::Bound {
809                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
810                    src_port: LOCAL_PORT_1,
811                },
812                cookie: s.socket_cookie(),
813                marks: Marks::default(),
814            }]
815        );
816
817        results.clear();
818        api.bound_sockets_diagnostics(
819            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
820                TcpSocketMatcher::SrcPort(PortMatcher {
821                    range: (LOCAL_PORT_1.get() + 1)..=(LOCAL_PORT_1.get() + 1),
822                    invert: false,
823                }),
824            )),
825            &mut results,
826        );
827        assert_eq!(results, Vec::new());
828    }
829
830    #[ip_test(I)]
831    fn diagnostics_match_dst_port<I: TcpTestIpExt>()
832    where
833        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
834                I,
835                TcpBindingsCtx<FakeDeviceId>,
836                SingleStackConverter = I::SingleStackConverter,
837                DualStackConverter = I::DualStackConverter,
838            >,
839    {
840        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
841            I::TEST_ADDRS.local_ip,
842            I::TEST_ADDRS.remote_ip,
843        ));
844        let mut api = ctx.tcp_api::<I>();
845        let s = api.create(Default::default());
846        api.bind(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)), Some(LOCAL_PORT_1))
847            .expect("bind should succeed");
848        api.connect(&s, Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)), LOCAL_PORT_2)
849            .expect("connect should succeed");
850
851        let mut results = Vec::new();
852        api.bound_sockets_diagnostics(
853            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
854                TcpSocketMatcher::DstPort(PortMatcher {
855                    range: LOCAL_PORT_2.get()..=LOCAL_PORT_2.get(),
856                    invert: false,
857                }),
858            )),
859            &mut results,
860        );
861        assert_eq!(
862            results,
863            vec![TcpSocketDiagnostics {
864                state_machine: netstack3_base::TcpSocketState::SynSent,
865                tuple: TcpSocketDiagnosticTuple::Connected {
866                    src_addr: I::TEST_ADDRS.local_ip.get(),
867                    src_port: LOCAL_PORT_1,
868                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
869                    dst_port: LOCAL_PORT_2,
870                },
871                cookie: s.socket_cookie(),
872                marks: Marks::default(),
873            }]
874        );
875
876        results.clear();
877        api.bound_sockets_diagnostics(
878            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(
879                TcpSocketMatcher::DstPort(PortMatcher {
880                    range: (LOCAL_PORT_2.get() + 1)..=(LOCAL_PORT_2.get() + 1),
881                    invert: false,
882                }),
883            )),
884            &mut results,
885        );
886        assert_eq!(results, Vec::new());
887    }
888
889    #[ip_test(I)]
890    fn diagnostics_match_state<I: TcpTestIpExt>()
891    where
892        TcpCoreCtx<FakeDeviceId, TcpBindingsCtx<FakeDeviceId>>: TcpContext<
893                I,
894                TcpBindingsCtx<FakeDeviceId>,
895                SingleStackConverter = I::SingleStackConverter,
896                DualStackConverter = I::DualStackConverter,
897            >,
898    {
899        let mut ctx = TcpCtx::with_core_ctx(TcpCoreCtx::new::<I>(
900            I::TEST_ADDRS.local_ip,
901            I::TEST_ADDRS.remote_ip,
902        ));
903        let mut api = ctx.tcp_api::<I>();
904
905        // Socket 1: LISTEN
906        let listen_socket = api.create(Default::default());
907        api.bind(
908            &listen_socket,
909            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)),
910            Some(LOCAL_PORT_1),
911        )
912        .expect("bind");
913        api.listen(&listen_socket, NonZeroUsize::new(1).unwrap()).expect("listen");
914
915        // Socket 2: SYN_SENT
916        let syn_sent_socket = api.create(Default::default());
917        api.bind(
918            &syn_sent_socket,
919            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.local_ip)),
920            Some(LOCAL_PORT_2),
921        )
922        .expect("bind");
923        // Connect to a remote address that won't respond immediately (since we don't step the
924        // network).
925        api.connect(
926            &syn_sent_socket,
927            Some(ZonedAddr::Unzoned(I::TEST_ADDRS.remote_ip)),
928            LOCAL_PORT_3,
929        )
930        .expect("connect");
931
932        let mut results = Vec::new();
933
934        api.bound_sockets_diagnostics(
935            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(TcpSocketMatcher::State(
936                TcpStateMatcher::LISTEN,
937            ))),
938            &mut results,
939        );
940        assert_eq!(
941            results,
942            vec![TcpSocketDiagnostics {
943                state_machine: netstack3_base::TcpSocketState::Listen,
944                tuple: TcpSocketDiagnosticTuple::Bound {
945                    src_addr: Some(I::TEST_ADDRS.local_ip.get()),
946                    src_port: LOCAL_PORT_1,
947                },
948                cookie: listen_socket.socket_cookie(),
949                marks: Marks::default(),
950            }]
951        );
952
953        results.clear();
954        api.bound_sockets_diagnostics(
955            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(TcpSocketMatcher::State(
956                TcpStateMatcher::SYN_SENT,
957            ))),
958            &mut results,
959        );
960        assert_eq!(
961            results,
962            vec![TcpSocketDiagnostics {
963                state_machine: netstack3_base::TcpSocketState::SynSent,
964                tuple: TcpSocketDiagnosticTuple::Connected {
965                    src_addr: I::TEST_ADDRS.local_ip.get(),
966                    src_port: LOCAL_PORT_2,
967                    dst_addr: I::TEST_ADDRS.remote_ip.get(),
968                    dst_port: LOCAL_PORT_3,
969                },
970                cookie: syn_sent_socket.socket_cookie(),
971                marks: Marks::default(),
972            }]
973        );
974
975        results.clear();
976        api.bound_sockets_diagnostics(
977            &IpSocketMatcher::Proto(SocketTransportProtocolMatcher::Tcp(TcpSocketMatcher::State(
978                TcpStateMatcher::ESTABLISHED,
979            ))),
980            &mut results,
981        );
982        assert_eq!(results, Vec::new());
983    }
984}