Skip to main content

netstack3_filter/
conntrack.rs

1// Copyright 2024 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
5mod tcp;
6
7use alloc::fmt::Debug;
8use alloc::sync::{Arc, Weak};
9use alloc::vec::Vec;
10use assert_matches::assert_matches;
11use core::any::Any;
12use core::fmt::Display;
13use core::hash::Hash;
14use core::time::Duration;
15
16use derivative::Derivative;
17use net_types::ip::{GenericOverIp, Ip, IpVersionMarker};
18use netstack3_hashmap::HashMap;
19use netstack3_hashmap::hash_map::Entry;
20use packet_formats::ip::{IpExt, IpProto, Ipv4Proto, Ipv6Proto};
21
22use crate::context::FilterBindingsTypes;
23use crate::logic::FilterTimerId;
24use crate::packets::TransportPacketData;
25use netstack3_base::sync::Mutex;
26use netstack3_base::{CoreTimerContext, Inspectable, Inspector, Instant, TimerContext};
27
28/// The time from the end of one GC cycle to the beginning of the next.
29const GC_INTERVAL: Duration = Duration::from_secs(10);
30
31/// The time since the last seen packet after which an established UDP
32/// connection will be considered expired and is eligible for garbage
33/// collection.
34///
35/// This was taken from RFC 4787 REQ-5.
36const CONNECTION_EXPIRY_TIME_UDP: Duration = Duration::from_secs(120);
37
38/// The time since the last seen packet after which a generic connection will be
39/// considered expired and is eligible for garbage collection.
40const CONNECTION_EXPIRY_OTHER: Duration = Duration::from_secs(30);
41
42/// The maximum number of entries in the conntrack table.
43///
44/// NOTE: This is subtly different from the number of connections in the table
45/// because self-connected sockets only have a single entry instead of the
46/// normal two.
47pub(crate) const MAXIMUM_ENTRIES: usize = 100_000;
48
49/// Implements a connection tracking subsystem.
50///
51/// The `E` parameter is for external data that is stored in the [`Connection`]
52/// struct and can be extracted with the [`Connection::external_data()`]
53/// function.
54pub struct Table<I: IpExt, E, BT: FilterBindingsTypes> {
55    inner: Mutex<TableInner<I, E, BT>>,
56}
57
58struct TableInner<I: IpExt, E, BT: FilterBindingsTypes> {
59    /// A connection is inserted into the map twice: once for the original
60    /// tuple, and once for the reply tuple.
61    table: HashMap<Tuple<I>, Arc<ConnectionShared<I, E, BT>>>,
62    /// A timer for triggering garbage collection events.
63    gc_timer: BT::Timer,
64    /// The number of times the table size limit was hit.
65    table_limit_hits: u32,
66    /// Of the times the table limit was hit, the number of times we had to drop
67    /// a packet because we couldn't make space in the table.
68    table_limit_drops: u32,
69}
70
71impl<I: IpExt, E, BT: FilterBindingsTypes> Table<I, E, BT> {
72    /// Returns whether the table contains a connection for the specified tuple.
73    ///
74    /// This is for NAT to determine whether a generated tuple will clash with
75    /// one already in the map. While it might seem inefficient, to require
76    /// locking in a loop, taking an uncontested lock is going to be
77    /// significantly faster than the RNG used to allocate NAT parameters.
78    pub fn contains_tuple(&self, tuple: &Tuple<I>) -> bool {
79        self.inner.lock().table.contains_key(tuple)
80    }
81
82    /// Returns a [`Connection`] for the flow indexed by `tuple`, if one exists.
83    pub(crate) fn get_shared_connection(
84        &self,
85        tuple: &Tuple<I>,
86    ) -> Option<Arc<ConnectionShared<I, E, BT>>> {
87        let guard = self.inner.lock();
88        let conn = guard.table.get(tuple)?;
89        Some(conn.clone())
90    }
91
92    /// Returns a [`Connection`] for the flow indexed by `tuple`, if one exists.
93    pub fn get_connection(&self, tuple: &Tuple<I>) -> Option<Connection<I, E, BT>> {
94        let guard = self.inner.lock();
95        let conn = guard.table.get(tuple)?;
96        Some(Connection::Shared(conn.clone()))
97    }
98
99    /// Returns the number of entries in the table.
100    ///
101    /// NOTE: This is usually twice the number of connections, but self-connected sockets will only
102    /// have a single entry.
103    #[cfg(feature = "testutils")]
104    pub fn num_entries(&self) -> usize {
105        self.inner.lock().table.len()
106    }
107
108    /// Removes the [`Connection`] for the flow indexed by `tuple`, if one exists,
109    /// and returns it to the caller.
110    #[cfg(feature = "testutils")]
111    pub fn remove_connection(&mut self, tuple: &Tuple<I>) -> Option<Connection<I, E, BT>> {
112        let mut guard = self.inner.lock();
113
114        // Remove the entry indexed by the tuple.
115        let conn = guard.table.remove(tuple)?;
116        let (original, reply) = (&conn.inner.original_tuple, &conn.inner.reply_tuple);
117
118        // If this is not a self-connected flow, we need to remove the other tuple on
119        // which the connection is indexed.
120        if original != reply {
121            if tuple == original {
122                assert!(guard.table.remove(reply).is_some());
123            } else {
124                assert!(guard.table.remove(original).is_some());
125            }
126        }
127
128        Some(Connection::Shared(conn))
129    }
130}
131
132fn schedule_gc<BC>(bindings_ctx: &mut BC, timer: &mut BC::Timer)
133where
134    BC: TimerContext,
135{
136    let _ = bindings_ctx.schedule_timer(GC_INTERVAL, timer);
137}
138
139impl<I, E, BC> Table<I, E, BC>
140where
141    I: IpExt,
142    BC: FilterBindingsTypes + TimerContext,
143{
144    pub(crate) fn new<CC>(bindings_ctx: &mut BC) -> Self
145    where
146        CC: CoreTimerContext<FilterTimerId<I>, BC>,
147    {
148        Self {
149            inner: Mutex::new(TableInner {
150                table: HashMap::new(),
151                gc_timer: CC::new_timer(
152                    bindings_ctx,
153                    FilterTimerId::ConntrackGc(IpVersionMarker::<I>::new()),
154                ),
155                table_limit_hits: 0,
156                table_limit_drops: 0,
157            }),
158        }
159    }
160}
161
162impl<I, E, BC> Table<I, E, BC>
163where
164    I: IpExt,
165    E: Debug + Default + Send + Sync + PartialEq + CompatibleWith + 'static,
166    BC: FilterBindingsTypes + TimerContext,
167{
168    /// Attempts to insert the `Connection` into the table.
169    ///
170    /// To be called once a packet for the connection has passed all filtering.
171    /// The boolean return value represents whether the connection was newly
172    /// added to the connection tracking state.
173    ///
174    /// This is on [`Table`] instead of [`Connection`] because conntrack needs
175    /// to be able to manipulate its internal map.
176    pub(crate) fn finalize_connection(
177        &self,
178        bindings_ctx: &mut BC,
179        connection: Connection<I, E, BC>,
180    ) -> Result<(bool, Option<Arc<ConnectionShared<I, E, BC>>>), FinalizeConnectionError> {
181        let exclusive = match connection {
182            Connection::Exclusive(c) => c,
183            // Given that make_shared is private, the only way for us to receive
184            // a shared connection is if it was already present in the map. This
185            // is far and away the most common case under normal operation.
186            Connection::Shared(inner) => return Ok((false, Some(inner))),
187        };
188
189        if exclusive.do_not_insert {
190            return Ok((false, None));
191        }
192
193        let mut guard = self.inner.lock();
194
195        if guard.table.len() >= MAXIMUM_ENTRIES {
196            guard.table_limit_hits = guard.table_limit_hits.saturating_add(1);
197
198            struct Info<'a, I: IpExt, BT: FilterBindingsTypes> {
199                original_tuple: &'a Tuple<I>,
200                reply_tuple: &'a Tuple<I>,
201                lifecycle: EstablishmentLifecycle,
202                last_seen: BT::Instant,
203            }
204
205            let mut info: Option<Info<'_, I, BC>> = None;
206
207            let now = bindings_ctx.now();
208            // Find a non-established connection to evict.
209            //
210            // 1. If a connection is expired, immediately choose it.
211            // 2. Otherwise, pick the connection that is "least established".
212            //    - SeenOriginal is less established than SeenReply
213            //    - A connection is less established than another with the same
214            //      establishment lifecycle if it saw a packet less recently.
215            //
216            // If all connections are established, then we can't free any space
217            // and report the error to the caller.
218            for (_, conn) in &guard.table {
219                let state = conn.state.lock();
220                if state.is_expired(now) {
221                    info = Some(Info {
222                        original_tuple: &conn.inner.original_tuple,
223                        reply_tuple: &conn.inner.reply_tuple,
224                        lifecycle: state.establishment_lifecycle,
225                        last_seen: state.last_packet_time,
226                    });
227                    break;
228                }
229
230                match state.establishment_lifecycle {
231                    EstablishmentLifecycle::SeenOriginal | EstablishmentLifecycle::SeenReply => {
232                        match &info {
233                            None => {
234                                info = Some(Info {
235                                    original_tuple: &conn.inner.original_tuple,
236                                    reply_tuple: &conn.inner.reply_tuple,
237                                    lifecycle: state.establishment_lifecycle,
238                                    last_seen: state.last_packet_time,
239                                })
240                            }
241                            Some(existing) => {
242                                if state.establishment_lifecycle < existing.lifecycle
243                                    || (state.establishment_lifecycle == existing.lifecycle
244                                        && state.last_packet_time < existing.last_seen)
245                                {
246                                    info = Some(Info {
247                                        original_tuple: &conn.inner.original_tuple,
248                                        reply_tuple: &conn.inner.reply_tuple,
249                                        lifecycle: state.establishment_lifecycle,
250                                        last_seen: state.last_packet_time,
251                                    })
252                                }
253                            }
254                        }
255                    }
256                    EstablishmentLifecycle::Established => {}
257                }
258            }
259
260            if let Some(Info { original_tuple, reply_tuple, .. }) = info {
261                let original_tuple = original_tuple.clone();
262                let reply_tuple = reply_tuple.clone();
263
264                assert!(guard.table.remove(&original_tuple).is_some());
265                if original_tuple != reply_tuple {
266                    assert!(guard.table.remove(&reply_tuple).is_some());
267                }
268            } else {
269                guard.table_limit_drops = guard.table_limit_drops.saturating_add(1);
270                return Err(FinalizeConnectionError::TableFull);
271            }
272        }
273
274        // Ensure there aren't conflicts before inserting to avoid avoid heap
275        // allocation if there's a conflict in the reply tuple[1]. Normally,
276        // you'd want to use the entry API for this to avoid the redundant
277        // lookups, but here that would require holding two mutable borrows of
278        // the table. This is a little wasteful in the expected case where there
279        // aren't conflicts, but it minimizes the worst case, which was deemed
280        // to be the more important factor.
281        //
282        // [1]: For context, the ConnectionExclusive associated with a tentative
283        // connection is stored on the stack throughout packet processing so
284        // there are no conntrack-related heap allocations if the packet is
285        // dropped.
286        if guard.table.contains_key(&exclusive.inner.original_tuple)
287            || guard.table.contains_key(&exclusive.inner.reply_tuple)
288        {
289            // NOTE: It's possible for the first two packets (or more) in the
290            // same flow to create ExclusiveConnections. Typically packets for
291            // the same flow are handled sequentically, so each subsequent
292            // packet should see the connection created by the first one.
293            // However, it is possible (e.g. if these two packets arrive on
294            // different interfaces) for them to race.
295            //
296            // In this case, subsequent packets would be reported as conflicts.
297            // To avoid this race condition, we check whether the conflicting
298            // connection in the table is actually the same as the connection
299            // that we are attempting to finalize; if so, we can simply adopt
300            // the already-finalized connection.
301            let conn = if let Some(conn) = guard.table.get(&exclusive.inner.original_tuple) {
302                conn
303            } else {
304                guard
305                    .table
306                    .get(&exclusive.inner.reply_tuple)
307                    .expect("checked that tuple is in table and table is locked")
308            };
309            if conn.compatible_with(&exclusive) {
310                return Ok((false, Some(conn.clone())));
311            }
312
313            // TODO(https://fxbug.dev/372549231): add a counter for this error.
314            Err(FinalizeConnectionError::Conflict)
315        } else {
316            let shared = exclusive.make_shared();
317            let clone = Arc::clone(&shared);
318
319            assert_matches!(
320                guard.table.insert(shared.inner.original_tuple.clone(), shared.clone()),
321                None
322            );
323
324            if shared.inner.reply_tuple != shared.inner.original_tuple {
325                assert_matches!(guard.table.insert(shared.inner.reply_tuple.clone(), shared), None);
326            }
327
328            // For the most part, this will only schedule the timer once, when
329            // the first packet hits the netstack. However, since the GC timer
330            // is only rescheduled during GC when the table has entries, it's
331            // possible that this will be called again if the table ever becomes
332            // empty.
333            if bindings_ctx.scheduled_instant(&mut guard.gc_timer).is_none() {
334                schedule_gc(bindings_ctx, &mut guard.gc_timer);
335            }
336
337            Ok((true, Some(clone)))
338        }
339    }
340}
341
342impl<I, E, BC> Table<I, E, BC>
343where
344    I: IpExt,
345    E: Debug + Default,
346    BC: FilterBindingsTypes + TimerContext,
347{
348    /// Returns a [`Connection`] for the packet's flow. If a connection does not
349    /// currently exist, a new one is created.
350    ///
351    /// At the same time, process the packet for the connection, updating
352    /// internal connection state.
353    ///
354    /// After processing is complete, you must call
355    /// [`finalize_connection`](Table::finalize_connection) with this
356    /// connection.
357    pub(crate) fn get_connection_for_packet_and_update(
358        &self,
359        bindings_ctx: &BC,
360        packet: PacketMetadata<I>,
361    ) -> Result<Option<(Connection<I, E, BC>, ConnectionDirection)>, GetConnectionError<I, E, BC>>
362    {
363        let tuple = packet.tuple();
364        let mut connection = match self.inner.lock().table.get(&tuple) {
365            Some(connection) => Connection::Shared(connection.clone()),
366            None => match ConnectionExclusive::from_deconstructed_packet(bindings_ctx, &packet) {
367                None => return Ok(None),
368                Some(c) => Connection::Exclusive(c),
369            },
370        };
371
372        let direction = match connection.direction(&tuple) {
373            Some(direction) => direction,
374            None => unreachable!(
375                "tuple didn't match connection after looking up in map: {tuple:?} {connection:?}"
376            ),
377        };
378
379        match connection.update(bindings_ctx, &packet, direction) {
380            Ok(ConnectionUpdateAction::NoAction) => Ok(Some((connection, direction))),
381            Ok(ConnectionUpdateAction::RemoveEntry) => match connection {
382                Connection::Exclusive(mut conn) => {
383                    conn.do_not_insert = true;
384                    Ok(Some((Connection::Exclusive(conn), direction)))
385                }
386                Connection::Shared(conn) => {
387                    // RACE #1: It's possible that GC already removed the
388                    // connection from the table, since we released the table
389                    // lock while updating the connection.
390                    //
391                    // RACE #2: It's possible that another connection with the
392                    // same tuple as either the original or reply has been
393                    // inserted. We use Arc::ptr_eq to ensure that the entry
394                    // we're removing is actually ours.
395                    let mut guard = self.inner.lock();
396
397                    let (original_tuple, reply_tuple) =
398                        (&conn.inner.original_tuple, &conn.inner.reply_tuple);
399
400                    // Tries to remove a tuple from the map and returns whether
401                    // that happened.
402                    let mut remove_tuple = |tuple| {
403                        match guard.table.entry(tuple) {
404                            Entry::Occupied(occupied) => {
405                                // See "RACE #2" above.
406                                if Arc::ptr_eq(occupied.get(), &conn) {
407                                    let _ = occupied.remove();
408                                    true
409                                } else {
410                                    false
411                                }
412                            }
413                            Entry::Vacant(_) => false,
414                        }
415                    };
416
417                    let original_removed = remove_tuple(original_tuple.clone());
418                    if reply_tuple != original_tuple {
419                        let reply_removed = remove_tuple(reply_tuple.clone());
420
421                        // For non-self-connected sockets (tuples aren't equal
422                        // we maintain the invariant that either both tuples are
423                        // present or neither is. There should be no case where
424                        // we remove a single half of the connection.
425                        assert_eq!(
426                            original_removed, reply_removed,
427                            "Only one tuple removed: {:?}={} {:?}={}",
428                            original_tuple, original_removed, reply_tuple, reply_removed,
429                        );
430                    }
431
432                    Ok(Some((Connection::Shared(conn), direction)))
433                }
434            },
435            Err(ConnectionUpdateError::InvalidPacket) => {
436                Err(GetConnectionError::InvalidPacket(connection, direction))
437            }
438        }
439    }
440
441    pub(crate) fn perform_gc(&self, bindings_ctx: &mut BC) {
442        let now = bindings_ctx.now();
443        let mut guard = self.inner.lock();
444
445        // Sadly, we can't easily remove entries from the map in-place for two
446        // reasons:
447        // - HashMap::retain() will look at each connection twice, since it will
448        // be inserted under both tuples. If a packet updates last_packet_time
449        // between these two checks, we might remove one tuple of the connection
450        // but not the other, leaving a single tuple in the table, which breaks
451        // a core invariant.
452        // - You can't modify a std::HashMap while iterating over it.
453        let to_remove: Vec<_> = guard
454            .table
455            .iter()
456            .filter_map(|(tuple, conn)| {
457                if *tuple == conn.inner.original_tuple && conn.is_expired(now) {
458                    Some((conn.inner.original_tuple.clone(), conn.inner.reply_tuple.clone()))
459                } else {
460                    None
461                }
462            })
463            .collect();
464
465        for (original_tuple, reply_tuple) in to_remove {
466            assert!(guard.table.remove(&original_tuple).is_some());
467            if reply_tuple != original_tuple {
468                assert!(guard.table.remove(&reply_tuple).is_some());
469            }
470        }
471
472        // The table is only expected to be empty in exceptional cases, or
473        // during tests. The test case especially important, because some tests
474        // will wait for core to quiesce by waiting for timers to stop firing.
475        // By only rescheduling when there are still entries in the table, we
476        // ensure that we won't enter an infinite timer firing/scheduling loop.
477        if !guard.table.is_empty() {
478            schedule_gc(bindings_ctx, &mut guard.gc_timer);
479        }
480    }
481}
482
483impl<I, E, BT> Inspectable for Table<I, E, BT>
484where
485    I: IpExt,
486    E: Inspectable,
487    BT: FilterBindingsTypes,
488{
489    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
490        let guard = self.inner.lock();
491
492        inspector.record_usize("num_entries", guard.table.len());
493        inspector.record_uint("table_limit_hits", guard.table_limit_hits);
494        inspector.record_uint("table_limit_drops", guard.table_limit_drops);
495
496        inspector.record_child("connections", |inspector| {
497            guard
498                .table
499                .iter()
500                .filter_map(|(tuple, connection)| {
501                    if *tuple == connection.inner.original_tuple { Some(connection) } else { None }
502                })
503                .for_each(|connection| {
504                    inspector.record_unnamed_child(|inspector| {
505                        inspector.delegate_inspectable(connection.as_ref())
506                    });
507                });
508        });
509    }
510}
511
512/// A tuple for a flow in a single direction.
513#[derive(Debug, Clone, PartialEq, Eq, Hash, GenericOverIp)]
514#[generic_over_ip(I, Ip)]
515pub struct Tuple<I: IpExt> {
516    /// The IP protocol number of the flow.
517    pub protocol: TransportProtocol,
518    /// The source IP address of the flow.
519    pub src_addr: I::Addr,
520    /// The destination IP address of the flow.
521    pub dst_addr: I::Addr,
522    /// The transport-layer source port or ID of the flow.
523    pub src_port_or_id: u16,
524    /// The transport-layer destination port or ID of the flow.
525    pub dst_port_or_id: u16,
526}
527
528impl<I: IpExt> Tuple<I> {
529    fn new(
530        src_addr: I::Addr,
531        dst_addr: I::Addr,
532        src_port_or_id: u16,
533        dst_port_or_id: u16,
534        protocol: TransportProtocol,
535    ) -> Self {
536        Self { protocol, src_addr, dst_addr, src_port_or_id, dst_port_or_id }
537    }
538
539    /// Returns the inverted version of the tuple.
540    ///
541    /// This means the src and dst addresses are swapped. For TCP and UDP, the
542    /// ports are reversed, but for ICMP, where the ports stand in for other
543    /// information, things are more complicated.
544    pub(crate) fn invert(self) -> Tuple<I> {
545        // TODO(https://fxbug.dev/328064082): Support tracking different ICMP
546        // request/response types.
547        Self {
548            protocol: self.protocol,
549            src_addr: self.dst_addr,
550            dst_addr: self.src_addr,
551            src_port_or_id: self.dst_port_or_id,
552            dst_port_or_id: self.src_port_or_id,
553        }
554    }
555}
556
557impl<I: IpExt> Inspectable for Tuple<I> {
558    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
559        inspector.record_debug("protocol", self.protocol);
560        inspector.record_ip_addr("src_addr", self.src_addr);
561        inspector.record_ip_addr("dst_addr", self.dst_addr);
562        inspector.record_usize("src_port_or_id", self.src_port_or_id);
563        inspector.record_usize("dst_port_or_id", self.dst_port_or_id);
564    }
565}
566
567/// The direction of a packet when compared to a given connection.
568#[derive(Debug, Copy, Clone, PartialEq, Eq)]
569pub enum ConnectionDirection {
570    /// The packet is traveling in the same direction as the first packet seen
571    /// for the [`Connection`].
572    Original,
573
574    /// The packet is traveling in the opposite direction from the first packet
575    /// seen for the [`Connection`].
576    Reply,
577}
578
579/// An error returned from [`Table::finalize_connection`].
580#[derive(Debug)]
581pub(crate) enum FinalizeConnectionError {
582    /// There is a conflicting connection already tracked by conntrack. The
583    /// to-be-finalized connection was not inserted into the table.
584    Conflict,
585
586    /// The table has reached the hard size cap and no room could be made.
587    TableFull,
588}
589
590/// Type to track additional processing required after updating a connection.
591#[derive(Debug, PartialEq, Eq)]
592enum ConnectionUpdateAction {
593    /// Processing completed and no further action necessary.
594    NoAction,
595
596    /// The entry for this connection should be removed from the conntrack table.
597    RemoveEntry,
598}
599
600/// An error returned from [`Connection::update`].
601#[derive(Debug, PartialEq, Eq)]
602enum ConnectionUpdateError {
603    /// The packet was invalid. The caller may decide whether to drop this
604    /// packet or not.
605    InvalidPacket,
606}
607
608/// An error returned from [`Table::get_connection_for_packet_and_update`].
609#[derive(Derivative)]
610#[derivative(Debug(bound = "E: Debug"))]
611pub(crate) enum GetConnectionError<I: IpExt, E, BT: FilterBindingsTypes> {
612    /// The packet was invalid. The caller may decide whether to drop it or not.
613    InvalidPacket(Connection<I, E, BT>, ConnectionDirection),
614}
615
616/// A `Connection` contains all of the information about a single connection
617/// tracked by conntrack.
618#[derive(Derivative)]
619#[derivative(Debug(bound = "E: Debug"))]
620pub enum Connection<I: IpExt, E, BT: FilterBindingsTypes> {
621    /// A connection that is directly owned by the packet that originated the
622    /// connection and no others. All fields are modifiable.
623    Exclusive(ConnectionExclusive<I, E, BT>),
624
625    /// This is an existing connection, and there are possibly many other
626    /// packets that are concurrently modifying it.
627    Shared(Arc<ConnectionShared<I, E, BT>>),
628}
629
630/// An error when attempting to retrieve the underlying conntrack entry from a
631/// weak handle to it.
632#[derive(Debug)]
633pub enum WeakConnectionError {
634    /// The entry was removed from the table after the weak handle was created.
635    EntryRemoved,
636    /// The entry does not match the type that is expected (due to an IP version
637    /// mismatch, for example).
638    InvalidEntry,
639}
640
641/// A type-erased weak handle to a connection tracking entry.
642///
643/// We use type erasure here to get rid of the parameterization on IP version;
644/// this handle is meant to be able to transit the device layer and at that
645/// point things are not parameterized on IP version (IPv4 and IPv6 packets both
646/// end up in the same device queue, for example).
647///
648/// When this is received for incoming packets, [`WeakConnection::into_inner`]
649/// can be used to downcast to the expected concrete [`Connection`] type.
650#[derive(Debug, Clone)]
651pub struct WeakConnection(pub(crate) Weak<dyn Any + Send + Sync>);
652
653impl WeakConnection {
654    /// Creates a new type-erased weak handle to the provided conntrack entry,
655    /// provided it is a shared entry.
656    pub fn new<I: IpExt, BT: FilterBindingsTypes + 'static, E: Send + Sync + 'static>(
657        conn: &Connection<I, E, BT>,
658    ) -> Option<Self> {
659        let shared = match conn {
660            Connection::Exclusive(_) => return None,
661            Connection::Shared(shared) => shared,
662        };
663        let weak = Arc::downgrade(shared);
664        Some(Self(weak))
665    }
666
667    /// Attempts to upgrade the provided weak handle to the conntrack entry and
668    /// downcast it to the specified concrete [`Connection`] type.
669    ///
670    /// Fails if either the weak handle cannot be upgraded (because the conntrack
671    /// entry has since been removed), or the type-erased handle cannot be downcast
672    /// to the concrete type (because the packet was modified after the creation of
673    /// this handle such that it no longer matches, e.g. the IP version of the
674    /// connection).
675    pub fn into_inner<I: IpExt, BT: FilterBindingsTypes + 'static, E: Send + Sync + 'static>(
676        self,
677    ) -> Result<Connection<I, E, BT>, WeakConnectionError> {
678        let Self(inner) = self;
679        let shared = inner
680            .upgrade()
681            .ok_or(WeakConnectionError::EntryRemoved)?
682            .downcast()
683            .map_err(|_err: Arc<_>| WeakConnectionError::InvalidEntry)?;
684        Ok(Connection::Shared(shared))
685    }
686}
687
688impl<I: IpExt, E, BT: FilterBindingsTypes> Connection<I, E, BT> {
689    /// Returns the tuple of the original direction of this connection.
690    pub fn original_tuple(&self) -> &Tuple<I> {
691        match self {
692            Connection::Exclusive(c) => &c.inner.original_tuple,
693            Connection::Shared(c) => &c.inner.original_tuple,
694        }
695    }
696
697    /// Returns the tuple of the reply direction of this connection.
698    pub(crate) fn reply_tuple(&self) -> &Tuple<I> {
699        match self {
700            Connection::Exclusive(c) => &c.inner.reply_tuple,
701            Connection::Shared(c) => &c.inner.reply_tuple,
702        }
703    }
704
705    /// Returns a reference to the [`Connection::external_data`] field.
706    pub fn external_data(&self) -> &E {
707        match self {
708            Connection::Exclusive(c) => &c.inner.external_data,
709            Connection::Shared(c) => &c.inner.external_data,
710        }
711    }
712
713    /// Returns the direction the tuple represents with respect to the
714    /// connection.
715    pub(crate) fn direction(&self, tuple: &Tuple<I>) -> Option<ConnectionDirection> {
716        let (original, reply) = match self {
717            Connection::Exclusive(c) => (&c.inner.original_tuple, &c.inner.reply_tuple),
718            Connection::Shared(c) => (&c.inner.original_tuple, &c.inner.reply_tuple),
719        };
720
721        // The ordering here is sadly mildly load-bearing. For self-connected
722        // sockets, the first comparison will be true, so having the original
723        // tuple first would mean that the connection is never marked
724        // established.
725        //
726        // This ordering means that all self-connected connections will be
727        // marked as established immediately upon receiving the first packet.
728        if tuple == reply {
729            Some(ConnectionDirection::Reply)
730        } else if tuple == original {
731            Some(ConnectionDirection::Original)
732        } else {
733            None
734        }
735    }
736
737    /// Returns a copy of the internal connection state
738    #[cfg(test)]
739    pub(crate) fn state(&self) -> ConnectionState<BT> {
740        match self {
741            Connection::Exclusive(c) => c.state.clone(),
742            Connection::Shared(c) => c.state.lock().clone(),
743        }
744    }
745}
746
747impl<I, E, BC> Connection<I, E, BC>
748where
749    I: IpExt,
750    BC: FilterBindingsTypes + TimerContext,
751{
752    fn update(
753        &mut self,
754        bindings_ctx: &BC,
755        packet: &PacketMetadata<I>,
756        direction: ConnectionDirection,
757    ) -> Result<ConnectionUpdateAction, ConnectionUpdateError> {
758        match packet {
759            PacketMetadata::Full { transport_data, .. } => {
760                let now = bindings_ctx.now();
761                match self {
762                    Connection::Exclusive(c) => c.state.update(direction, &transport_data, now),
763                    Connection::Shared(c) => c.state.lock().update(direction, &transport_data, now),
764                }
765            }
766            PacketMetadata::IcmpError(_) => Ok(ConnectionUpdateAction::NoAction),
767        }
768    }
769}
770
771/// Fields common to both [`ConnectionExclusive`] and [`ConnectionShared`].
772#[derive(Derivative)]
773#[derivative(Debug(bound = "E: Debug"), PartialEq(bound = "E: PartialEq"))]
774pub struct ConnectionCommon<I: IpExt, E> {
775    /// The 5-tuple for the connection in the original direction. This is
776    /// arbitrary, and is just the direction where a packet was first seen.
777    pub(crate) original_tuple: Tuple<I>,
778
779    /// The 5-tuple for the connection in the reply direction. This is what's
780    /// used for packet rewriting for NAT.
781    pub(crate) reply_tuple: Tuple<I>,
782
783    /// Extra information that is not needed by the conntrack module itself. In
784    /// the case of NAT, we expect this to contain things such as the kind of
785    /// rewriting that will occur (e.g. SNAT vs DNAT).
786    pub(crate) external_data: E,
787}
788
789impl<I: IpExt, E: Inspectable> Inspectable for ConnectionCommon<I, E> {
790    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
791        inspector.record_child("original_tuple", |inspector| {
792            inspector.delegate_inspectable(&self.original_tuple);
793        });
794
795        inspector.record_child("reply_tuple", |inspector| {
796            inspector.delegate_inspectable(&self.reply_tuple);
797        });
798
799        // We record external_data as an inspectable because that allows us to
800        // prevent accidentally leaking data, which could happen if we just used
801        // the Debug impl.
802        inspector.record_child("external_data", |inspector| {
803            inspector.delegate_inspectable(&self.external_data);
804        });
805    }
806}
807
808#[derive(Debug, Clone)]
809enum ProtocolState {
810    Tcp(tcp::Connection),
811    Udp,
812    Other,
813}
814
815impl ProtocolState {
816    fn update(
817        &mut self,
818        dir: ConnectionDirection,
819        transport_data: &TransportPacketData,
820    ) -> Result<ConnectionUpdateAction, ConnectionUpdateError> {
821        match self {
822            ProtocolState::Tcp(tcp_conn) => {
823                let (segment, payload_len) = assert_matches!(
824                    transport_data,
825                    TransportPacketData::Tcp { segment, payload_len, .. } => (segment, payload_len)
826                );
827                tcp_conn.update(&segment, *payload_len, dir)
828            }
829            ProtocolState::Udp | ProtocolState::Other => Ok(ConnectionUpdateAction::NoAction),
830        }
831    }
832}
833
834/// The lifecycle of the connection getting to being established.
835///
836/// To mimic Linux behavior, we require seeing three packets in order to mark a
837/// connection established.
838/// 1. Original
839/// 2. Reply
840/// 3. Original
841///
842/// The first packet is implicit in the creation of the connection when the
843/// first packet is seen.
844#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
845enum EstablishmentLifecycle {
846    SeenOriginal,
847    SeenReply,
848    Established,
849}
850
851impl EstablishmentLifecycle {
852    fn update(self, dir: ConnectionDirection) -> Self {
853        match self {
854            EstablishmentLifecycle::SeenOriginal => match dir {
855                ConnectionDirection::Original => self,
856                ConnectionDirection::Reply => EstablishmentLifecycle::SeenReply,
857            },
858            EstablishmentLifecycle::SeenReply => match dir {
859                ConnectionDirection::Original => EstablishmentLifecycle::Established,
860                ConnectionDirection::Reply => self,
861            },
862            EstablishmentLifecycle::Established => self,
863        }
864    }
865}
866
867/// Dynamic per-connection state.
868#[derive(Derivative)]
869#[derivative(Clone(bound = ""), Debug(bound = ""))]
870pub(crate) struct ConnectionState<BT: FilterBindingsTypes> {
871    /// The time the last packet was seen for this connection (in either of the
872    /// original or reply directions).
873    last_packet_time: BT::Instant,
874
875    /// Where in the generic establishment lifecycle the current connection is.
876    establishment_lifecycle: EstablishmentLifecycle,
877
878    /// State that is specific to a given protocol (e.g. TCP or UDP).
879    protocol_state: ProtocolState,
880}
881
882impl<BT: FilterBindingsTypes> ConnectionState<BT> {
883    fn update(
884        &mut self,
885        dir: ConnectionDirection,
886        transport_data: &TransportPacketData,
887        now: BT::Instant,
888    ) -> Result<ConnectionUpdateAction, ConnectionUpdateError> {
889        let action = self.protocol_state.update(dir, transport_data)?;
890
891        if self.last_packet_time < now {
892            self.last_packet_time = now;
893        }
894
895        self.establishment_lifecycle = self.establishment_lifecycle.update(dir);
896
897        Ok(action)
898    }
899
900    fn is_expired(&self, now: BT::Instant) -> bool {
901        let duration = now.saturating_duration_since(self.last_packet_time);
902
903        let expiry_duration = match &self.protocol_state {
904            ProtocolState::Tcp(tcp_conn) => tcp_conn.expiry_duration(self.establishment_lifecycle),
905            ProtocolState::Udp => CONNECTION_EXPIRY_TIME_UDP,
906            // ICMP ends up here. The ICMP messages we track are simple
907            // request/response protocols, so we always expect to get a response
908            // quickly (within 2 RTT). Any followup messages (e.g. if making
909            // periodic ECHO requests) should reuse this existing connection.
910            ProtocolState::Other => CONNECTION_EXPIRY_OTHER,
911        };
912
913        duration >= expiry_duration
914    }
915}
916
917impl<BT: FilterBindingsTypes> Inspectable for ConnectionState<BT> {
918    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
919        inspector.record_bool(
920            "established",
921            match self.establishment_lifecycle {
922                EstablishmentLifecycle::SeenOriginal | EstablishmentLifecycle::SeenReply => false,
923                EstablishmentLifecycle::Established => true,
924            },
925        );
926        inspector.record_inspectable_value("last_packet_time", &self.last_packet_time);
927    }
928}
929
930/// A conntrack connection with single ownership.
931///
932/// Because of this, many fields may be updated without synchronization. There
933/// is no chance of messing with other packets for this connection or ending up
934/// out-of-sync with the table (e.g. by changing the tuples once the connection
935/// has been inserted).
936#[derive(Derivative)]
937#[derivative(Debug(bound = "E: Debug"))]
938pub struct ConnectionExclusive<I: IpExt, E, BT: FilterBindingsTypes> {
939    pub(crate) inner: ConnectionCommon<I, E>,
940    pub(crate) state: ConnectionState<BT>,
941
942    /// When true, do not insert the connection into the conntrack table.
943    ///
944    /// This allows the stack to still operate against the connection (e.g. for
945    /// NAT), while guaranteeing that it won't make it into the table.
946    do_not_insert: bool,
947}
948
949impl<I: IpExt, E, BT: FilterBindingsTypes> ConnectionExclusive<I, E, BT> {
950    /// Turn this exclusive connection into a shared one. This is required in
951    /// order to insert into the [`Table`] table.
952    fn make_shared(self) -> Arc<ConnectionShared<I, E, BT>> {
953        Arc::new(ConnectionShared { inner: self.inner, state: Mutex::new(self.state) })
954    }
955
956    pub(crate) fn reply_tuple(&self) -> &Tuple<I> {
957        &self.inner.reply_tuple
958    }
959
960    pub(crate) fn rewrite_reply_dst_addr(&mut self, addr: I::Addr) {
961        self.inner.reply_tuple.dst_addr = addr;
962    }
963
964    pub(crate) fn rewrite_reply_src_addr(&mut self, addr: I::Addr) {
965        self.inner.reply_tuple.src_addr = addr;
966    }
967
968    pub(crate) fn rewrite_reply_src_port_or_id(&mut self, port_or_id: u16) {
969        self.inner.reply_tuple.src_port_or_id = port_or_id;
970        match self.inner.reply_tuple.protocol {
971            TransportProtocol::Icmp => {
972                // ICMP uses a single ID and conntrack keeps track of it in both
973                // ID fields. This makes it easier to keep a single logic to
974                // flip the direction. Hence we need to update the rest of the
975                // tuple.
976                //
977                // TODO(https://fxbug.dev/328064082): Probably needs revisiting
978                // as part of better support for ICMP request/response.
979                self.inner.reply_tuple.dst_port_or_id = port_or_id;
980            }
981            TransportProtocol::Tcp | TransportProtocol::Udp | TransportProtocol::Other(_) => {}
982        }
983    }
984
985    pub(crate) fn rewrite_reply_dst_port_or_id(&mut self, port_or_id: u16) {
986        self.inner.reply_tuple.dst_port_or_id = port_or_id;
987        match self.inner.reply_tuple.protocol {
988            TransportProtocol::Icmp => {
989                // ICMP uses a single ID and conntrack keeps track of it in both
990                // ID fields. This makes it easier to keep a single logic to
991                // flip the direction. Hence we need to update the rest of the
992                // tuple.
993                //
994                // TODO(https://fxbug.dev/328064082): Probably needs revisiting
995                // as part of better support for ICMP request/response.
996                self.inner.reply_tuple.src_port_or_id = port_or_id;
997            }
998            TransportProtocol::Tcp | TransportProtocol::Udp | TransportProtocol::Other(_) => {}
999        }
1000    }
1001}
1002
1003impl<I, E, BC> ConnectionExclusive<I, E, BC>
1004where
1005    I: IpExt,
1006    E: Default,
1007    BC: FilterBindingsTypes + TimerContext,
1008{
1009    pub(crate) fn from_deconstructed_packet(
1010        bindings_ctx: &BC,
1011        packet_metadata: &PacketMetadata<I>,
1012    ) -> Option<Self> {
1013        let (tuple, transport_data) = match packet_metadata {
1014            PacketMetadata::Full { tuple, transport_data } => (tuple, transport_data),
1015            PacketMetadata::IcmpError(_) => return None,
1016        };
1017
1018        let reply_tuple = tuple.clone().invert();
1019        let self_connected = reply_tuple == *tuple;
1020
1021        Some(Self {
1022            inner: ConnectionCommon {
1023                original_tuple: tuple.clone(),
1024                reply_tuple,
1025                external_data: E::default(),
1026            },
1027            state: ConnectionState {
1028                last_packet_time: bindings_ctx.now(),
1029                establishment_lifecycle: EstablishmentLifecycle::SeenOriginal,
1030                protocol_state: match tuple.protocol {
1031                    TransportProtocol::Tcp => {
1032                        let (segment, payload_len) = match transport_data.tcp_segment_and_len() {
1033                            Some(v) => v,
1034                            // This should be impossible as PacketMetadata
1035                            // ensures the Tuple and transport information
1036                            // are the same protocol.
1037                            None => unreachable!(
1038                                "protocol was TCP, but didn't have TCP info: {transport_data:?}"
1039                            ),
1040                        };
1041
1042                        ProtocolState::Tcp(tcp::Connection::new(
1043                            segment,
1044                            payload_len,
1045                            self_connected,
1046                        )?)
1047                    }
1048                    TransportProtocol::Udp => ProtocolState::Udp,
1049                    TransportProtocol::Icmp | TransportProtocol::Other(_) => ProtocolState::Other,
1050                },
1051            },
1052            do_not_insert: false,
1053        })
1054    }
1055}
1056
1057/// A conntrack connection with shared ownership.
1058///
1059/// All fields are private, because other packets, and the conntrack table
1060/// itself, will be depending on them not to change. Fields must be accessed
1061/// through the associated methods.
1062#[derive(Derivative)]
1063#[derivative(Debug(bound = "E: Debug"))]
1064pub struct ConnectionShared<I: IpExt, E, BT: FilterBindingsTypes> {
1065    inner: ConnectionCommon<I, E>,
1066    state: Mutex<ConnectionState<BT>>,
1067}
1068
1069/// The IP-agnostic transport protocol of a packet.
1070#[allow(missing_docs)]
1071#[derive(Copy, Clone, PartialEq, Eq, Hash, GenericOverIp)]
1072#[generic_over_ip()]
1073pub enum TransportProtocol {
1074    Tcp,
1075    Udp,
1076    Icmp,
1077    Other(u8),
1078}
1079
1080impl From<Ipv4Proto> for TransportProtocol {
1081    fn from(value: Ipv4Proto) -> Self {
1082        match value {
1083            Ipv4Proto::Proto(IpProto::Tcp) => TransportProtocol::Tcp,
1084            Ipv4Proto::Proto(IpProto::Udp) => TransportProtocol::Udp,
1085            Ipv4Proto::Icmp => TransportProtocol::Icmp,
1086            v => TransportProtocol::Other(v.into()),
1087        }
1088    }
1089}
1090
1091impl From<Ipv6Proto> for TransportProtocol {
1092    fn from(value: Ipv6Proto) -> Self {
1093        match value {
1094            Ipv6Proto::Proto(IpProto::Tcp) => TransportProtocol::Tcp,
1095            Ipv6Proto::Proto(IpProto::Udp) => TransportProtocol::Udp,
1096            Ipv6Proto::Icmpv6 => TransportProtocol::Icmp,
1097            v => TransportProtocol::Other(v.into()),
1098        }
1099    }
1100}
1101
1102impl From<IpProto> for TransportProtocol {
1103    fn from(value: IpProto) -> Self {
1104        match value {
1105            IpProto::Tcp => TransportProtocol::Tcp,
1106            IpProto::Udp => TransportProtocol::Udp,
1107            v @ IpProto::Reserved => TransportProtocol::Other(v.into()),
1108        }
1109    }
1110}
1111
1112impl Display for TransportProtocol {
1113    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1114        match self {
1115            TransportProtocol::Tcp => write!(f, "TCP"),
1116            TransportProtocol::Udp => write!(f, "UDP"),
1117            TransportProtocol::Icmp => write!(f, "ICMP"),
1118            TransportProtocol::Other(n) => write!(f, "Other({n})"),
1119        }
1120    }
1121}
1122
1123impl Debug for TransportProtocol {
1124    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1125        Display::fmt(&self, f)
1126    }
1127}
1128
1129impl<I: IpExt, E, BT: FilterBindingsTypes> ConnectionShared<I, E, BT> {
1130    fn is_expired(&self, now: BT::Instant) -> bool {
1131        self.state.lock().is_expired(now)
1132    }
1133}
1134
1135impl<I: IpExt, E: CompatibleWith, BT: FilterBindingsTypes> ConnectionShared<I, E, BT> {
1136    /// Returns whether the provided exclusive connection is compatible with this
1137    /// one, to the extent that a shared reference to this tracked connection could
1138    /// be adopted in place of the exclusive connection.
1139    pub(crate) fn compatible_with(&self, conn: &ConnectionExclusive<I, E, BT>) -> bool {
1140        self.inner.original_tuple == conn.inner.original_tuple
1141            && self.inner.reply_tuple == conn.inner.reply_tuple
1142            && self.inner.external_data.compatible_with(&conn.inner.external_data)
1143    }
1144}
1145
1146impl<I: IpExt, E: Inspectable, BT: FilterBindingsTypes> Inspectable for ConnectionShared<I, E, BT> {
1147    fn record<Inspector: netstack3_base::Inspector>(&self, inspector: &mut Inspector) {
1148        inspector.delegate_inspectable(&self.inner);
1149        inspector.delegate_inspectable(&*self.state.lock());
1150    }
1151}
1152
1153/// Allows a caller to check whether a given connection tracking entry (or some
1154/// configuration owned by that entry) is compatible with another.
1155pub trait CompatibleWith {
1156    /// Returns whether the provided entity is compatible with this entity in the
1157    /// context of connection tracking.
1158    fn compatible_with(&self, other: &Self) -> bool;
1159}
1160
1161/// A struct containing relevant fields extracted from the IP and transport
1162/// headers that means we only have to touch the incoming packet once. Also acts
1163/// as a witness type that the tuple and transport data have the same transport
1164/// protocol.
1165#[derive(Debug, Clone, PartialEq, Eq)]
1166pub enum PacketMetadata<I: IpExt> {
1167    Full { tuple: Tuple<I>, transport_data: TransportPacketData },
1168    IcmpError(Tuple<I>),
1169}
1170
1171impl<I: IpExt> PacketMetadata<I> {
1172    pub(crate) fn new(
1173        src_addr: I::Addr,
1174        dst_addr: I::Addr,
1175        protocol: TransportProtocol,
1176        transport_data: TransportPacketData,
1177    ) -> Self {
1178        match protocol {
1179            TransportProtocol::Tcp => {
1180                assert_matches!(transport_data, TransportPacketData::Tcp { .. })
1181            }
1182            TransportProtocol::Udp | TransportProtocol::Icmp | TransportProtocol::Other(_) => {
1183                assert_matches!(transport_data, TransportPacketData::Generic { .. })
1184            }
1185        }
1186
1187        Self::Full {
1188            tuple: Tuple::new(
1189                src_addr,
1190                dst_addr,
1191                transport_data.src_port(),
1192                transport_data.dst_port(),
1193                protocol,
1194            ),
1195            transport_data,
1196        }
1197    }
1198
1199    pub(crate) fn new_from_icmp_error(
1200        src_addr: I::Addr,
1201        dst_addr: I::Addr,
1202        src_port: u16,
1203        dst_port: u16,
1204        protocol: TransportProtocol,
1205    ) -> Self {
1206        Self::IcmpError(Tuple::new(src_addr, dst_addr, src_port, dst_port, protocol))
1207    }
1208
1209    pub(crate) fn tuple(&self) -> Tuple<I> {
1210        match self {
1211            PacketMetadata::Full { tuple, .. } => tuple.clone(),
1212            // We need to invert the tuple for ICMP errors because they aren't
1213            // necessarily ones that exist in the map due to being post-NAT.
1214            //
1215            // For example, if we have a connection A -> R -> B with Masquerade
1216            // NAT on R rewriting packets from A, we'll end up with the
1217            // following tuples for a connection originating at A:
1218            //
1219            // Original {
1220            //   src_ip: A
1221            //   dst_ip: B
1222            // }
1223            //
1224            // Reply {
1225            //   src_ip: B
1226            //   dst_ip: R
1227            // }
1228            //
1229            // However, if there's an ICMP error packet from B in response to A,
1230            // the tuple of the original packet in the ICMP error's payload will
1231            // look like R -> B. In the opposite direction, the ICMP error from
1232            // A in response to B will have an inner payload with a tuple that
1233            // looks like B -> A.
1234            //
1235            // If we invert the error tuple, then we get the correct connection
1236            // from the map, and the direction also corresponds to the direction
1237            // of the outer packet, which is what we need for NAT to work
1238            // correctly.
1239            PacketMetadata::IcmpError(tuple) => tuple.clone().invert(),
1240        }
1241    }
1242}
1243
1244#[cfg(test)]
1245pub(crate) mod testutils {
1246    use crate::packets::testutil::internal::{FakeIpPacket, FakeUdpPacket, TestIpExt};
1247
1248    /// Create a pair of UDP packets that are inverses of one another. Uses `index` to create
1249    /// packets that are unique.
1250    pub(crate) fn make_test_udp_packets<I: TestIpExt>(
1251        index: u32,
1252    ) -> (FakeIpPacket<I, FakeUdpPacket>, FakeIpPacket<I, FakeUdpPacket>) {
1253        // This ensures that, no matter how big index is, we'll always have
1254        // unique src and dst ports, and thus unique connections.
1255        let src_port = (index % (u16::MAX as u32)) as u16;
1256        let dst_port = (index / (u16::MAX as u32)) as u16;
1257
1258        let packet = FakeIpPacket::<I, _> {
1259            src_ip: I::SRC_IP,
1260            dst_ip: I::DST_IP,
1261            body: FakeUdpPacket { src_port, dst_port },
1262        };
1263        let reply_packet = FakeIpPacket::<I, _> {
1264            src_ip: I::DST_IP,
1265            dst_ip: I::SRC_IP,
1266            body: FakeUdpPacket { src_port: dst_port, dst_port: src_port },
1267        };
1268
1269        (packet, reply_packet)
1270    }
1271}
1272
1273#[cfg(test)]
1274mod tests {
1275    use assert_matches::assert_matches;
1276    use ip_test_macro::ip_test;
1277    use netstack3_base::testutil::FakeTimerCtxExt;
1278    use netstack3_base::{Control, IntoCoreTimerCtx, SegmentHeader, SeqNum, UnscaledWindowSize};
1279    use test_case::test_case;
1280
1281    use super::testutils::make_test_udp_packets;
1282    use super::*;
1283    use crate::context::testutil::{FakeBindingsCtx, FakeCtx};
1284    use crate::packets::IpPacket;
1285    use crate::packets::testutil::internal::ArbitraryValue;
1286    use crate::state::IpRoutines;
1287    use crate::testutil::TestIpExt;
1288
1289    impl CompatibleWith for () {
1290        fn compatible_with(&self, (): &()) -> bool {
1291            true
1292        }
1293    }
1294
1295    #[test_case(
1296        EstablishmentLifecycle::SeenOriginal,
1297        ConnectionDirection::Original
1298          => EstablishmentLifecycle::SeenOriginal
1299    )]
1300    #[test_case(
1301        EstablishmentLifecycle::SeenOriginal,
1302        ConnectionDirection::Reply
1303          => EstablishmentLifecycle::SeenReply
1304    )]
1305    #[test_case(
1306        EstablishmentLifecycle::SeenReply,
1307        ConnectionDirection::Original
1308          => EstablishmentLifecycle::Established
1309    )]
1310    #[test_case(
1311        EstablishmentLifecycle::SeenReply,
1312        ConnectionDirection::Reply
1313          => EstablishmentLifecycle::SeenReply
1314    )]
1315    #[test_case(
1316        EstablishmentLifecycle::Established,
1317        ConnectionDirection::Original
1318          => EstablishmentLifecycle::Established
1319    )]
1320    #[test_case(
1321        EstablishmentLifecycle::Established,
1322        ConnectionDirection::Reply
1323          => EstablishmentLifecycle::Established
1324    )]
1325    fn establishment_lifecycle_test(
1326        lifecycle: EstablishmentLifecycle,
1327        dir: ConnectionDirection,
1328    ) -> EstablishmentLifecycle {
1329        lifecycle.update(dir)
1330    }
1331
1332    #[ip_test(I)]
1333    #[test_case(TransportProtocol::Udp)]
1334    #[test_case(TransportProtocol::Tcp)]
1335    fn tuple_invert_udp_tcp<I: IpExt + TestIpExt>(protocol: TransportProtocol) {
1336        let orig_tuple = Tuple::<I> {
1337            protocol: protocol,
1338            src_addr: I::SRC_IP,
1339            dst_addr: I::DST_IP,
1340            src_port_or_id: I::SRC_PORT,
1341            dst_port_or_id: I::DST_PORT,
1342        };
1343
1344        let expected = Tuple::<I> {
1345            protocol: protocol,
1346            src_addr: I::DST_IP,
1347            dst_addr: I::SRC_IP,
1348            src_port_or_id: I::DST_PORT,
1349            dst_port_or_id: I::SRC_PORT,
1350        };
1351
1352        let inverted = orig_tuple.invert();
1353
1354        assert_eq!(inverted, expected);
1355    }
1356
1357    #[ip_test(I)]
1358    fn tuple_from_tcp_packet<I: IpExt + TestIpExt>() {
1359        let expected = Tuple::<I> {
1360            protocol: TransportProtocol::Tcp,
1361            src_addr: I::SRC_IP,
1362            dst_addr: I::DST_IP,
1363            src_port_or_id: I::SRC_PORT,
1364            dst_port_or_id: I::DST_PORT,
1365        };
1366
1367        let packet = PacketMetadata::<I>::new(
1368            I::SRC_IP,
1369            I::DST_IP,
1370            TransportProtocol::Tcp,
1371            TransportPacketData::Tcp {
1372                src_port: I::SRC_PORT,
1373                dst_port: I::DST_PORT,
1374                segment: SegmentHeader::arbitrary_value(),
1375                payload_len: 4,
1376            },
1377        );
1378
1379        assert_eq!(packet.tuple(), expected);
1380    }
1381
1382    #[ip_test(I)]
1383    fn connection_from_tuple<I: IpExt + TestIpExt>() {
1384        let bindings_ctx = FakeBindingsCtx::<I>::new();
1385
1386        let packet = PacketMetadata::<I>::new(
1387            I::SRC_IP,
1388            I::DST_IP,
1389            TransportProtocol::Udp,
1390            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
1391        );
1392        let original_tuple = packet.tuple();
1393        let reply_tuple = packet.tuple().invert();
1394
1395        let connection =
1396            ConnectionExclusive::<_, (), _>::from_deconstructed_packet(&bindings_ctx, &packet)
1397                .unwrap();
1398
1399        assert_eq!(&connection.inner.original_tuple, &original_tuple);
1400        assert_eq!(&connection.inner.reply_tuple, &reply_tuple);
1401    }
1402
1403    #[ip_test(I)]
1404    fn connection_make_shared_has_same_underlying_info<I: IpExt + TestIpExt>() {
1405        let bindings_ctx = FakeBindingsCtx::<I>::new();
1406
1407        let packet = PacketMetadata::<I>::new(
1408            I::SRC_IP,
1409            I::DST_IP,
1410            TransportProtocol::Udp,
1411            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
1412        );
1413        let original_tuple = packet.tuple();
1414        let reply_tuple = original_tuple.clone().invert();
1415
1416        let mut connection =
1417            ConnectionExclusive::from_deconstructed_packet(&bindings_ctx, &packet).unwrap();
1418        connection.inner.external_data = 1234;
1419        let shared = connection.make_shared();
1420
1421        assert_eq!(shared.inner.original_tuple, original_tuple);
1422        assert_eq!(shared.inner.reply_tuple, reply_tuple);
1423        assert_eq!(shared.inner.external_data, 1234);
1424    }
1425
1426    enum ConnectionKind {
1427        Exclusive,
1428        Shared,
1429    }
1430
1431    #[ip_test(I)]
1432    #[test_case(ConnectionKind::Exclusive)]
1433    #[test_case(ConnectionKind::Shared)]
1434    fn connection_getters<I: IpExt + TestIpExt>(connection_kind: ConnectionKind) {
1435        let bindings_ctx = FakeBindingsCtx::<I>::new();
1436
1437        let packet = PacketMetadata::<I>::new(
1438            I::SRC_IP,
1439            I::DST_IP,
1440            TransportProtocol::Udp,
1441            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
1442        );
1443        let original_tuple = packet.tuple();
1444        let reply_tuple = original_tuple.clone().invert();
1445
1446        let mut connection =
1447            ConnectionExclusive::from_deconstructed_packet(&bindings_ctx, &packet).unwrap();
1448        connection.inner.external_data = 1234;
1449
1450        let connection = match connection_kind {
1451            ConnectionKind::Exclusive => Connection::Exclusive(connection),
1452            ConnectionKind::Shared => Connection::Shared(connection.make_shared()),
1453        };
1454
1455        assert_eq!(connection.original_tuple(), &original_tuple);
1456        assert_eq!(connection.reply_tuple(), &reply_tuple);
1457        assert_eq!(connection.external_data(), &1234);
1458    }
1459
1460    #[ip_test(I)]
1461    #[test_case(ConnectionKind::Exclusive)]
1462    #[test_case(ConnectionKind::Shared)]
1463    fn connection_direction<I: IpExt + TestIpExt>(connection_kind: ConnectionKind) {
1464        let bindings_ctx = FakeBindingsCtx::<I>::new();
1465
1466        let packet = PacketMetadata::<I>::new(
1467            I::SRC_IP,
1468            I::DST_IP,
1469            TransportProtocol::Udp,
1470            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
1471        );
1472        let original_tuple = packet.tuple();
1473        let reply_tuple = original_tuple.clone().invert();
1474
1475        let mut other_tuple = original_tuple.clone();
1476        other_tuple.src_port_or_id += 1;
1477
1478        let connection: ConnectionExclusive<_, (), _> =
1479            ConnectionExclusive::from_deconstructed_packet(&bindings_ctx, &packet).unwrap();
1480        let connection = match connection_kind {
1481            ConnectionKind::Exclusive => Connection::Exclusive(connection),
1482            ConnectionKind::Shared => Connection::Shared(connection.make_shared()),
1483        };
1484
1485        assert_matches!(connection.direction(&original_tuple), Some(ConnectionDirection::Original));
1486        assert_matches!(connection.direction(&reply_tuple), Some(ConnectionDirection::Reply));
1487        assert_matches!(connection.direction(&other_tuple), None);
1488    }
1489
1490    #[ip_test(I)]
1491    #[test_case(ConnectionKind::Exclusive)]
1492    #[test_case(ConnectionKind::Shared)]
1493    fn connection_update<I: IpExt + TestIpExt>(connection_kind: ConnectionKind) {
1494        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1495        bindings_ctx.sleep(Duration::from_secs(1));
1496
1497        let packet = PacketMetadata::<I>::new(
1498            I::SRC_IP,
1499            I::DST_IP,
1500            TransportProtocol::Udp,
1501            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
1502        );
1503
1504        let reply_packet = PacketMetadata::<I>::new(
1505            I::DST_IP,
1506            I::SRC_IP,
1507            TransportProtocol::Udp,
1508            TransportPacketData::Generic { src_port: I::DST_PORT, dst_port: I::SRC_PORT },
1509        );
1510
1511        let connection =
1512            ConnectionExclusive::<_, (), _>::from_deconstructed_packet(&bindings_ctx, &packet)
1513                .unwrap();
1514        let mut connection = match connection_kind {
1515            ConnectionKind::Exclusive => Connection::Exclusive(connection),
1516            ConnectionKind::Shared => Connection::Shared(connection.make_shared()),
1517        };
1518
1519        assert_matches!(
1520            connection.update(&bindings_ctx, &packet, ConnectionDirection::Original),
1521            Ok(ConnectionUpdateAction::NoAction)
1522        );
1523        let state = connection.state();
1524        assert_matches!(state.establishment_lifecycle, EstablishmentLifecycle::SeenOriginal);
1525        assert_eq!(state.last_packet_time.offset, Duration::from_secs(1));
1526
1527        // Tuple in reply direction should set established to true and obviously
1528        // update last packet time.
1529        bindings_ctx.sleep(Duration::from_secs(1));
1530        assert_matches!(
1531            connection.update(&bindings_ctx, &reply_packet, ConnectionDirection::Reply),
1532            Ok(ConnectionUpdateAction::NoAction)
1533        );
1534        let state = connection.state();
1535        assert_matches!(state.establishment_lifecycle, EstablishmentLifecycle::SeenReply);
1536        assert_eq!(state.last_packet_time.offset, Duration::from_secs(2));
1537    }
1538
1539    #[ip_test(I)]
1540    #[test_case(ConnectionKind::Exclusive)]
1541    #[test_case(ConnectionKind::Shared)]
1542    fn skip_connection_update_for_icmp_error<I: IpExt + TestIpExt>(
1543        connection_kind: ConnectionKind,
1544    ) {
1545        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
1546        bindings_ctx.sleep(Duration::from_secs(1));
1547
1548        let packet = PacketMetadata::<I>::new(
1549            I::SRC_IP,
1550            I::DST_IP,
1551            TransportProtocol::Udp,
1552            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
1553        );
1554
1555        let reply_packet = PacketMetadata::<I>::new_from_icmp_error(
1556            I::DST_IP,
1557            I::SRC_IP,
1558            I::DST_PORT,
1559            I::SRC_PORT,
1560            TransportProtocol::Udp,
1561        );
1562
1563        let connection =
1564            ConnectionExclusive::<_, (), _>::from_deconstructed_packet(&bindings_ctx, &packet)
1565                .unwrap();
1566        let mut connection = match connection_kind {
1567            ConnectionKind::Exclusive => Connection::Exclusive(connection),
1568            ConnectionKind::Shared => Connection::Shared(connection.make_shared()),
1569        };
1570
1571        assert_matches!(
1572            connection.update(&bindings_ctx, &packet, ConnectionDirection::Original),
1573            Ok(ConnectionUpdateAction::NoAction)
1574        );
1575        let state = connection.state();
1576        assert_matches!(state.establishment_lifecycle, EstablishmentLifecycle::SeenOriginal);
1577        assert_eq!(state.last_packet_time.offset, Duration::from_secs(1));
1578
1579        // The tuple in the reply direction was actually inside an ICMP error,
1580        // so it shouldn't update anything.
1581        bindings_ctx.sleep(Duration::from_secs(1));
1582        assert_matches!(
1583            connection.update(&bindings_ctx, &reply_packet, ConnectionDirection::Reply),
1584            Ok(ConnectionUpdateAction::NoAction)
1585        );
1586        let state = connection.state();
1587        assert_matches!(state.establishment_lifecycle, EstablishmentLifecycle::SeenOriginal);
1588        assert_eq!(state.last_packet_time.offset, Duration::from_secs(1));
1589    }
1590
1591    #[ip_test(I)]
1592    fn skip_connection_creation_for_icmp_error<I: IpExt + TestIpExt>() {
1593        let mut bindings_ctx = FakeBindingsCtx::new();
1594        bindings_ctx.sleep(Duration::from_secs(1));
1595        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1596
1597        let packet = PacketMetadata::<I>::new_from_icmp_error(
1598            I::DST_IP,
1599            I::SRC_IP,
1600            I::DST_PORT,
1601            I::SRC_PORT,
1602            TransportProtocol::Udp,
1603        );
1604
1605        // Because `packet` is an ICMP error, we shouldn't create and return a
1606        // connection for it.
1607        assert!(
1608            table
1609                .get_connection_for_packet_and_update(&bindings_ctx, packet.clone())
1610                .expect("packet should be valid")
1611                .is_none()
1612        );
1613        assert!(!table.contains_tuple(&packet.tuple()));
1614    }
1615
1616    #[ip_test(I)]
1617    fn table_get_exclusive_connection_and_finalize_shared<I: IpExt + TestIpExt>() {
1618        let mut bindings_ctx = FakeBindingsCtx::new();
1619        bindings_ctx.sleep(Duration::from_secs(1));
1620        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1621
1622        let packet = PacketMetadata::<I>::new(
1623            I::SRC_IP,
1624            I::DST_IP,
1625            TransportProtocol::Udp,
1626            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
1627        );
1628
1629        let reply_packet = PacketMetadata::<I>::new(
1630            I::DST_IP,
1631            I::SRC_IP,
1632            TransportProtocol::Udp,
1633            TransportPacketData::Generic { src_port: I::DST_PORT, dst_port: I::SRC_PORT },
1634        );
1635
1636        let original_tuple = packet.tuple();
1637        let reply_tuple = reply_packet.tuple();
1638
1639        let (conn, dir) = table
1640            .get_connection_for_packet_and_update(&bindings_ctx, packet.clone())
1641            .expect("packet should be valid")
1642            .expect("connection should be present");
1643        let state = conn.state();
1644        assert_matches!(state.establishment_lifecycle, EstablishmentLifecycle::SeenOriginal);
1645        assert_eq!(state.last_packet_time.offset, Duration::from_secs(1));
1646
1647        // Since the connection isn't present in the map, we should get a
1648        // freshly-allocated exclusive connection and the map should not have
1649        // been touched.
1650        assert_matches!(conn, Connection::Exclusive(_));
1651        assert_eq!(dir, ConnectionDirection::Original);
1652        assert!(!table.contains_tuple(&original_tuple));
1653        assert!(!table.contains_tuple(&reply_tuple));
1654
1655        // Once we finalize the connection, it should be present in the map.
1656        assert_matches!(table.finalize_connection(&mut bindings_ctx, conn), Ok((true, Some(_))));
1657        assert!(table.contains_tuple(&original_tuple));
1658        assert!(table.contains_tuple(&reply_tuple));
1659
1660        // We should now get a shared connection back for packets in either
1661        // direction now that the connection is present in the table.
1662        bindings_ctx.sleep(Duration::from_secs(1));
1663        let (conn, dir) = table
1664            .get_connection_for_packet_and_update(&bindings_ctx, packet.clone())
1665            .expect("packet should be valid")
1666            .expect("connection should be present");
1667        assert_eq!(dir, ConnectionDirection::Original);
1668        let state = conn.state();
1669        assert_matches!(state.establishment_lifecycle, EstablishmentLifecycle::SeenOriginal);
1670        assert_eq!(state.last_packet_time.offset, Duration::from_secs(2));
1671        let conn = assert_matches!(conn, Connection::Shared(conn) => conn);
1672
1673        bindings_ctx.sleep(Duration::from_secs(1));
1674        let (reply_conn, dir) = table
1675            .get_connection_for_packet_and_update(&bindings_ctx, reply_packet)
1676            .expect("packet should be valid")
1677            .expect("connection should be present");
1678        assert_eq!(dir, ConnectionDirection::Reply);
1679        let state = reply_conn.state();
1680        assert_matches!(state.establishment_lifecycle, EstablishmentLifecycle::SeenReply);
1681        assert_eq!(state.last_packet_time.offset, Duration::from_secs(3));
1682        let reply_conn = assert_matches!(reply_conn, Connection::Shared(conn) => conn);
1683
1684        // We should be getting the same connection in both directions.
1685        assert!(Arc::ptr_eq(&conn, &reply_conn));
1686
1687        // Inserting the connection a second time shouldn't change the map.
1688        let (conn, _dir) = table
1689            .get_connection_for_packet_and_update(&bindings_ctx, packet)
1690            .expect("packet should be valid")
1691            .unwrap();
1692        assert_matches!(table.finalize_connection(&mut bindings_ctx, conn), Ok((false, Some(_))));
1693        assert!(table.contains_tuple(&original_tuple));
1694        assert!(table.contains_tuple(&reply_tuple));
1695    }
1696
1697    #[ip_test(I)]
1698    fn table_conflict<I: IpExt + TestIpExt>() {
1699        let mut bindings_ctx = FakeBindingsCtx::new();
1700        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1701
1702        let original_packet = PacketMetadata::<I>::new(
1703            I::SRC_IP,
1704            I::DST_IP,
1705            TransportProtocol::Udp,
1706            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
1707        );
1708
1709        let nated_original_packet = PacketMetadata::<I>::new(
1710            I::SRC_IP,
1711            I::DST_IP,
1712            TransportProtocol::Udp,
1713            TransportPacketData::Generic { src_port: I::SRC_PORT + 1, dst_port: I::DST_PORT + 1 },
1714        );
1715
1716        let conn1 = Connection::Exclusive(
1717            ConnectionExclusive::<_, (), _>::from_deconstructed_packet(
1718                &bindings_ctx,
1719                &original_packet,
1720            )
1721            .unwrap(),
1722        );
1723
1724        // Fake NAT that ends up allocating the same reply tuple as an existing
1725        // connection.
1726        let mut conn2 = ConnectionExclusive::<_, (), _>::from_deconstructed_packet(
1727            &bindings_ctx,
1728            &original_packet,
1729        )
1730        .unwrap();
1731        conn2.inner.original_tuple = nated_original_packet.tuple();
1732        let conn2 = Connection::Exclusive(conn2);
1733
1734        // Fake NAT that ends up allocating the same original tuple as an
1735        // existing connection.
1736        let mut conn3 = ConnectionExclusive::<_, (), _>::from_deconstructed_packet(
1737            &bindings_ctx,
1738            &original_packet,
1739        )
1740        .unwrap();
1741        conn3.inner.reply_tuple = nated_original_packet.tuple().invert();
1742        let conn3 = Connection::Exclusive(conn3);
1743
1744        assert_matches!(table.finalize_connection(&mut bindings_ctx, conn1), Ok((true, Some(_))));
1745        assert_matches!(
1746            table.finalize_connection(&mut bindings_ctx, conn2),
1747            Err(FinalizeConnectionError::Conflict)
1748        );
1749        assert_matches!(
1750            table.finalize_connection(&mut bindings_ctx, conn3),
1751            Err(FinalizeConnectionError::Conflict)
1752        );
1753    }
1754
1755    #[ip_test(I)]
1756    fn table_conflict_identical_connection<
1757        I: IpExt + crate::packets::testutil::internal::TestIpExt,
1758    >() {
1759        let mut bindings_ctx = FakeBindingsCtx::new();
1760        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
1761
1762        let original_packet = PacketMetadata::<I>::new(
1763            I::SRC_IP,
1764            I::DST_IP,
1765            TransportProtocol::Udp,
1766            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
1767        );
1768
1769        // Simulate a race where two packets in the same flow both end up
1770        // creating identical exclusive connections.
1771
1772        let conn = Connection::Exclusive(
1773            ConnectionExclusive::<_, (), _>::from_deconstructed_packet(
1774                &bindings_ctx,
1775                &original_packet,
1776            )
1777            .unwrap(),
1778        );
1779        let finalized = assert_matches!(
1780            table.finalize_connection(&mut bindings_ctx, conn),
1781            Ok((true, Some(conn))) => conn
1782        );
1783
1784        let conn = Connection::Exclusive(
1785            ConnectionExclusive::<_, (), _>::from_deconstructed_packet(
1786                &bindings_ctx,
1787                &original_packet,
1788            )
1789            .unwrap(),
1790        );
1791        let conn = assert_matches!(
1792            table.finalize_connection(&mut bindings_ctx, conn),
1793            Ok((false, Some(conn))) => conn
1794        );
1795        assert!(Arc::ptr_eq(&finalized, &conn));
1796    }
1797
1798    #[derive(Copy, Clone)]
1799    enum GcTrigger {
1800        /// Call [`perform_gc`] function directly, avoiding any timer logic.
1801        Direct,
1802        /// Trigger a timer expiry, which indirectly calls into [`perform_gc`].
1803        Timer,
1804    }
1805
1806    #[ip_test(I)]
1807    #[test_case(GcTrigger::Direct)]
1808    #[test_case(GcTrigger::Timer)]
1809    fn garbage_collection<I: TestIpExt>(gc_trigger: GcTrigger) {
1810        fn perform_gc<I: TestIpExt>(
1811            core_ctx: &mut FakeCtx<I>,
1812            bindings_ctx: &mut FakeBindingsCtx<I>,
1813            gc_trigger: GcTrigger,
1814        ) {
1815            match gc_trigger {
1816                GcTrigger::Direct => core_ctx.conntrack().perform_gc(bindings_ctx),
1817                GcTrigger::Timer => {
1818                    for timer in bindings_ctx
1819                        .trigger_timers_until_instant(bindings_ctx.timer_ctx.instant.time, core_ctx)
1820                    {
1821                        assert_matches!(timer, FilterTimerId::ConntrackGc(_));
1822                    }
1823                }
1824            }
1825        }
1826
1827        let mut bindings_ctx = FakeBindingsCtx::new();
1828        let mut core_ctx = FakeCtx::with_ip_routines(&mut bindings_ctx, IpRoutines::default());
1829
1830        let first_packet = PacketMetadata::<I>::new(
1831            I::SRC_IP,
1832            I::DST_IP,
1833            TransportProtocol::Udp,
1834            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
1835        );
1836
1837        let second_packet = PacketMetadata::<I>::new(
1838            I::SRC_IP,
1839            I::DST_IP,
1840            TransportProtocol::Udp,
1841            TransportPacketData::Generic { src_port: I::SRC_PORT + 1, dst_port: I::DST_PORT },
1842        );
1843        let second_packet_reply = PacketMetadata::<I>::new(
1844            I::DST_IP,
1845            I::SRC_IP,
1846            TransportProtocol::Udp,
1847            TransportPacketData::Generic { src_port: I::DST_PORT, dst_port: I::SRC_PORT + 1 },
1848        );
1849
1850        let first_tuple = first_packet.tuple();
1851        let first_tuple_reply = first_tuple.clone().invert();
1852        let second_tuple = second_packet.tuple();
1853        let second_tuple_reply = second_packet_reply.tuple();
1854
1855        // T=0: Packets for two connections come in.
1856        let (conn, _dir) = core_ctx
1857            .conntrack()
1858            .get_connection_for_packet_and_update(&bindings_ctx, first_packet)
1859            .expect("packet should be valid")
1860            .expect("packet should be trackable");
1861        assert_matches!(
1862            core_ctx
1863                .conntrack()
1864                .finalize_connection(&mut bindings_ctx, conn)
1865                .expect("connection finalize should succeed"),
1866            (true, Some(_))
1867        );
1868        let (conn, _dir) = core_ctx
1869            .conntrack()
1870            .get_connection_for_packet_and_update(&bindings_ctx, second_packet)
1871            .expect("packet should be valid")
1872            .expect("packet should be trackable");
1873        assert_matches!(
1874            core_ctx
1875                .conntrack()
1876                .finalize_connection(&mut bindings_ctx, conn)
1877                .expect("connection finalize should succeed"),
1878            (true, Some(_))
1879        );
1880        assert!(core_ctx.conntrack().contains_tuple(&first_tuple));
1881        assert!(core_ctx.conntrack().contains_tuple(&second_tuple));
1882        assert_eq!(core_ctx.conntrack().inner.lock().table.len(), 4);
1883
1884        // T=GC_INTERVAL: Triggering a GC does not clean up any connections,
1885        // because no connections are stale yet.
1886        bindings_ctx.sleep(GC_INTERVAL);
1887        perform_gc(&mut core_ctx, &mut bindings_ctx, gc_trigger);
1888        assert_eq!(core_ctx.conntrack().contains_tuple(&first_tuple), true);
1889        assert_eq!(core_ctx.conntrack().contains_tuple(&first_tuple_reply), true);
1890        assert_eq!(core_ctx.conntrack().contains_tuple(&second_tuple), true);
1891        assert_eq!(core_ctx.conntrack().contains_tuple(&second_tuple_reply), true);
1892        assert_eq!(core_ctx.conntrack().inner.lock().table.len(), 4);
1893
1894        // T=GC_INTERVAL a packet for just the second connection comes in.
1895        let (conn, _dir) = core_ctx
1896            .conntrack()
1897            .get_connection_for_packet_and_update(&bindings_ctx, second_packet_reply)
1898            .expect("packet should be valid")
1899            .expect("packet should be trackable");
1900        assert_matches!(conn.state().establishment_lifecycle, EstablishmentLifecycle::SeenReply);
1901        assert_matches!(
1902            core_ctx
1903                .conntrack()
1904                .finalize_connection(&mut bindings_ctx, conn)
1905                .expect("connection finalize should succeed"),
1906            (false, Some(_))
1907        );
1908        assert_eq!(core_ctx.conntrack().contains_tuple(&first_tuple), true);
1909        assert_eq!(core_ctx.conntrack().contains_tuple(&first_tuple_reply), true);
1910        assert_eq!(core_ctx.conntrack().contains_tuple(&second_tuple), true);
1911        assert_eq!(core_ctx.conntrack().contains_tuple(&second_tuple_reply), true);
1912        assert_eq!(core_ctx.conntrack().inner.lock().table.len(), 4);
1913
1914        // The state in the table at this point is:
1915        // Connection 1:
1916        //   - Last packet seen at T=0
1917        //   - Expires after T=CONNECTION_EXPIRY_TIME_UDP
1918        // Connection 2:
1919        //   - Last packet seen at T=GC_INTERVAL
1920        //   - Expires after CONNECTION_EXPIRY_TIME_UDP + GC_INTERVAL
1921
1922        // T=2*GC_INTERVAL: Triggering a GC does not clean up any connections.
1923        bindings_ctx.sleep(GC_INTERVAL);
1924        perform_gc(&mut core_ctx, &mut bindings_ctx, gc_trigger);
1925        assert_eq!(core_ctx.conntrack().contains_tuple(&first_tuple), true);
1926        assert_eq!(core_ctx.conntrack().contains_tuple(&first_tuple_reply), true);
1927        assert_eq!(core_ctx.conntrack().contains_tuple(&second_tuple), true);
1928        assert_eq!(core_ctx.conntrack().contains_tuple(&second_tuple_reply), true);
1929        assert_eq!(core_ctx.conntrack().inner.lock().table.len(), 4);
1930
1931        // Time advances to expiry for the first packet
1932        // (T=CONNECTION_EXPIRY_TIME_UDP) trigger gc and note that the first
1933        // connection was cleaned up
1934        bindings_ctx.sleep(CONNECTION_EXPIRY_TIME_UDP - 2 * GC_INTERVAL);
1935        perform_gc(&mut core_ctx, &mut bindings_ctx, gc_trigger);
1936        assert_eq!(core_ctx.conntrack().contains_tuple(&first_tuple), false);
1937        assert_eq!(core_ctx.conntrack().contains_tuple(&first_tuple_reply), false);
1938        assert_eq!(core_ctx.conntrack().contains_tuple(&second_tuple), true);
1939        assert_eq!(core_ctx.conntrack().contains_tuple(&second_tuple_reply), true);
1940        assert_eq!(core_ctx.conntrack().inner.lock().table.len(), 2);
1941
1942        // Advance time past the expiry time for the second connection
1943        // (T=CONNECTION_EXPIRY_TIME_UDP + GC_INTERVAL) and see that it is
1944        // cleaned up.
1945        bindings_ctx.sleep(GC_INTERVAL);
1946        perform_gc(&mut core_ctx, &mut bindings_ctx, gc_trigger);
1947        assert_eq!(core_ctx.conntrack().contains_tuple(&first_tuple), false);
1948        assert_eq!(core_ctx.conntrack().contains_tuple(&first_tuple_reply), false);
1949        assert_eq!(core_ctx.conntrack().contains_tuple(&second_tuple), false);
1950        assert_eq!(core_ctx.conntrack().contains_tuple(&second_tuple_reply), false);
1951        assert!(core_ctx.conntrack().inner.lock().table.is_empty());
1952    }
1953
1954    fn fill_table<I, E, BC>(
1955        bindings_ctx: &mut BC,
1956        table: &Table<I, E, BC>,
1957        entries: impl Iterator<Item = u32>,
1958        establishment_lifecycle: EstablishmentLifecycle,
1959    ) where
1960        I: IpExt + TestIpExt,
1961        E: Debug + Default + Send + Sync + PartialEq + CompatibleWith + 'static,
1962        BC: FilterBindingsTypes + TimerContext,
1963    {
1964        for i in entries {
1965            let (packet, reply_packet) = make_test_udp_packets(i);
1966            let packet = packet.conntrack_packet().unwrap();
1967            let reply_packet = reply_packet.conntrack_packet().unwrap();
1968
1969            let (conn, _dir) = table
1970                .get_connection_for_packet_and_update(&bindings_ctx, packet.clone())
1971                .expect("packet should be valid")
1972                .expect("packet should be trackable");
1973            assert_matches!(
1974                table
1975                    .finalize_connection(bindings_ctx, conn)
1976                    .expect("connection finalize should succeed"),
1977                (true, Some(_))
1978            );
1979
1980            if establishment_lifecycle >= EstablishmentLifecycle::SeenReply {
1981                let (conn, _dir) = table
1982                    .get_connection_for_packet_and_update(&bindings_ctx, reply_packet.clone())
1983                    .expect("packet should be valid")
1984                    .expect("packet should be trackable");
1985                assert_matches!(
1986                    table
1987                        .finalize_connection(bindings_ctx, conn)
1988                        .expect("connection finalize should succeed"),
1989                    (false, Some(_))
1990                );
1991
1992                if establishment_lifecycle >= EstablishmentLifecycle::Established {
1993                    let (conn, _dir) = table
1994                        .get_connection_for_packet_and_update(&bindings_ctx, packet)
1995                        .expect("packet should be valid")
1996                        .expect("packet should be trackable");
1997                    assert_matches!(
1998                        table
1999                            .finalize_connection(bindings_ctx, conn)
2000                            .expect("connection finalize should succeed"),
2001                        (false, Some(_))
2002                    );
2003                }
2004            }
2005        }
2006    }
2007
2008    #[ip_test(I)]
2009    #[test_case(EstablishmentLifecycle::SeenOriginal; "existing connections unestablished")]
2010    #[test_case(EstablishmentLifecycle::SeenReply; "existing connections partially established")]
2011    #[test_case(EstablishmentLifecycle::Established; "existing connections established")]
2012    fn table_size_limit_evict_less_established<I: IpExt + TestIpExt>(
2013        existing_lifecycle: EstablishmentLifecycle,
2014    ) {
2015        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2016        bindings_ctx.sleep(Duration::from_secs(1));
2017        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2018
2019        fill_table(
2020            &mut bindings_ctx,
2021            &table,
2022            0..(MAXIMUM_ENTRIES / 2).try_into().unwrap(),
2023            existing_lifecycle,
2024        );
2025
2026        // The table should be full whether or not the connections are
2027        // established since finalize_connection always inserts the connection
2028        // under the original and reply tuples.
2029        assert_eq!(table.inner.lock().table.len(), MAXIMUM_ENTRIES);
2030
2031        let (packet, _) = make_test_udp_packets((MAXIMUM_ENTRIES / 2).try_into().unwrap());
2032        let packet = packet.conntrack_packet().unwrap();
2033        let (conn, _dir) = table
2034            .get_connection_for_packet_and_update(&bindings_ctx, packet)
2035            .expect("packet should be valid")
2036            .expect("packet should be trackable");
2037        if existing_lifecycle == EstablishmentLifecycle::Established {
2038            // Inserting a new connection should fail because it would grow the
2039            // table.
2040            assert_matches!(
2041                table.finalize_connection(&mut bindings_ctx, conn),
2042                Err(FinalizeConnectionError::TableFull)
2043            );
2044
2045            // Inserting an existing connection again should succeed because
2046            // it's not growing the table.
2047            let (packet, _) = make_test_udp_packets((MAXIMUM_ENTRIES / 2 - 1).try_into().unwrap());
2048            let packet = packet.conntrack_packet().unwrap();
2049            let (conn, _dir) = table
2050                .get_connection_for_packet_and_update(&bindings_ctx, packet)
2051                .expect("packet should be valid")
2052                .expect("packet should be trackable");
2053            assert_matches!(
2054                table
2055                    .finalize_connection(&mut bindings_ctx, conn)
2056                    .expect("connection finalize should succeed"),
2057                (false, Some(_))
2058            );
2059        } else {
2060            assert_matches!(
2061                table
2062                    .finalize_connection(&mut bindings_ctx, conn)
2063                    .expect("connection finalize should succeed"),
2064                (true, Some(_))
2065            );
2066        }
2067    }
2068
2069    #[ip_test(I)]
2070    fn table_size_limit_evict_expired<I: IpExt + TestIpExt>() {
2071        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2072        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2073
2074        // Add one connection that expires a second sooner than the others.
2075        let evicted_tuple = {
2076            let (packet, _) = make_test_udp_packets(0);
2077            let packet = packet.conntrack_packet().unwrap();
2078            packet.tuple()
2079        };
2080        fill_table(&mut bindings_ctx, &table, 0..=0, EstablishmentLifecycle::Established);
2081        bindings_ctx.sleep(Duration::from_secs(1));
2082        fill_table(
2083            &mut bindings_ctx,
2084            &table,
2085            1..(MAXIMUM_ENTRIES / 2).try_into().unwrap(),
2086            EstablishmentLifecycle::Established,
2087        );
2088
2089        assert_eq!(table.inner.lock().table.len(), MAXIMUM_ENTRIES);
2090        assert!(table.contains_tuple(&evicted_tuple));
2091
2092        let (packet, _) = make_test_udp_packets((MAXIMUM_ENTRIES / 2).try_into().unwrap());
2093        let packet = packet.conntrack_packet().unwrap();
2094        // The table is full, and no connections can be evicted (they're all
2095        // established and unexpired), so we can't insert a new connection.
2096        let (conn, _dir) = table
2097            .get_connection_for_packet_and_update(&bindings_ctx, packet.clone())
2098            .expect("packet should be valid")
2099            .expect("packet should be trackable");
2100        assert_matches!(
2101            table.finalize_connection(&mut bindings_ctx, conn),
2102            Err(FinalizeConnectionError::TableFull)
2103        );
2104
2105        // Now the first connection can be evicted because it's expired, and we
2106        // see that we're able to insert a new connection.
2107        bindings_ctx.sleep(CONNECTION_EXPIRY_TIME_UDP - Duration::from_secs(1));
2108        let (conn, _dir) = table
2109            .get_connection_for_packet_and_update(&bindings_ctx, packet)
2110            .expect("packet should be valid")
2111            .expect("packet should be trackable");
2112        assert_matches!(table.finalize_connection(&mut bindings_ctx, conn), Ok(_));
2113        assert!(!table.contains_tuple(&evicted_tuple));
2114    }
2115
2116    #[ip_test(I)]
2117    fn table_size_limit_less_established<I: IpExt + TestIpExt>() {
2118        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2119        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2120
2121        let evicted_tuple = {
2122            let (packet, _) = make_test_udp_packets(0);
2123            let packet = packet.conntrack_packet().unwrap();
2124            packet.tuple()
2125        };
2126        // Add one connection that expires a second sooner than the others.
2127        fill_table(&mut bindings_ctx, &table, 0..=0, EstablishmentLifecycle::SeenOriginal);
2128        bindings_ctx.sleep(Duration::from_secs(1));
2129        fill_table(&mut bindings_ctx, &table, 1..=1, EstablishmentLifecycle::SeenOriginal);
2130        fill_table(
2131            &mut bindings_ctx,
2132            &table,
2133            2..(MAXIMUM_ENTRIES / 2).try_into().unwrap(),
2134            EstablishmentLifecycle::SeenReply,
2135        );
2136
2137        assert_eq!(table.inner.lock().table.len(), MAXIMUM_ENTRIES);
2138        assert!(table.contains_tuple(&evicted_tuple));
2139
2140        // We can insert since all connections in the table are eligible for
2141        // eviction, but we want to be sure that the least established
2142        // connection was the one that's actually evicted.
2143        let (packet, _) = make_test_udp_packets((MAXIMUM_ENTRIES / 2).try_into().unwrap());
2144        let packet = packet.conntrack_packet().unwrap();
2145        let (conn, _dir) = table
2146            .get_connection_for_packet_and_update(&bindings_ctx, packet)
2147            .expect("packet should be valid")
2148            .expect("packet should be trackable");
2149        assert_matches!(table.finalize_connection(&mut bindings_ctx, conn), Ok(_));
2150        assert!(!table.contains_tuple(&evicted_tuple));
2151    }
2152
2153    #[cfg(target_os = "fuchsia")]
2154    #[ip_test(I)]
2155    fn inspect<I: IpExt + TestIpExt>() {
2156        use alloc::string::ToString;
2157        use diagnostics_assertions::assert_data_tree;
2158        use diagnostics_traits::FuchsiaInspector;
2159        use fuchsia_inspect::Inspector;
2160
2161        let mut bindings_ctx = FakeBindingsCtx::<I>::new();
2162        bindings_ctx.sleep(Duration::from_secs(1));
2163        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2164
2165        {
2166            let inspector = Inspector::new(Default::default());
2167            let mut bindings_inspector = FuchsiaInspector::<()>::new(inspector.root());
2168            bindings_inspector.delegate_inspectable(&table);
2169
2170            let mut exec = fuchsia_async::TestExecutor::new();
2171
2172            assert_data_tree!(@executor exec, inspector, "root": {
2173                "table_limit_drops": 0u64,
2174                "table_limit_hits": 0u64,
2175                "num_entries": 0u64,
2176                "connections": {},
2177            });
2178        }
2179
2180        // Insert the first connection into the table in an unestablished state.
2181        // This will later be evicted when the table fills up.
2182        let (packet, _) = make_test_udp_packets::<I>(0);
2183        let packet = packet.conntrack_packet().unwrap();
2184        let (conn, _dir) = table
2185            .get_connection_for_packet_and_update(&bindings_ctx, packet)
2186            .expect("packet should be valid")
2187            .expect("packet should be trackable");
2188        assert_matches!(conn.state().establishment_lifecycle, EstablishmentLifecycle::SeenOriginal);
2189        assert_matches!(
2190            table
2191                .finalize_connection(&mut bindings_ctx, conn)
2192                .expect("connection finalize should succeed"),
2193            (true, Some(_))
2194        );
2195
2196        {
2197            let inspector = Inspector::new(Default::default());
2198            let mut bindings_inspector = FuchsiaInspector::<()>::new(inspector.root());
2199            bindings_inspector.delegate_inspectable(&table);
2200
2201            let mut exec = fuchsia_async::TestExecutor::new();
2202            assert_data_tree!(@executor exec, inspector, "root": {
2203                "table_limit_drops": 0u64,
2204                "table_limit_hits": 0u64,
2205                "num_entries": 2u64,
2206                "connections": {
2207                    "0": {
2208                        "original_tuple": {
2209                            "protocol": "UDP",
2210                            "src_addr": I::SRC_IP.to_string(),
2211                            "dst_addr": I::DST_IP.to_string(),
2212                            "src_port_or_id": 0u64,
2213                            "dst_port_or_id": 0u64,
2214                        },
2215                        "reply_tuple": {
2216                            "protocol": "UDP",
2217                            "src_addr": I::DST_IP.to_string(),
2218                            "dst_addr": I::SRC_IP.to_string(),
2219                            "src_port_or_id": 0u64,
2220                            "dst_port_or_id": 0u64,
2221                        },
2222                        "external_data": {},
2223                        "established": false,
2224                        "last_packet_time": 1_000_000_000u64,
2225                    }
2226                },
2227            });
2228        }
2229
2230        // Fill the table up the rest of the way.
2231        fill_table(
2232            &mut bindings_ctx,
2233            &table,
2234            1..(MAXIMUM_ENTRIES / 2).try_into().unwrap(),
2235            EstablishmentLifecycle::Established,
2236        );
2237
2238        assert_eq!(table.inner.lock().table.len(), MAXIMUM_ENTRIES);
2239
2240        // This first one should succeed because it can evict the
2241        // non-established connection.
2242        let (packet, reply_packet) =
2243            make_test_udp_packets((MAXIMUM_ENTRIES / 2).try_into().unwrap());
2244        let packet = packet.conntrack_packet().unwrap();
2245        let reply_packet = reply_packet.conntrack_packet().unwrap();
2246        let (conn, _dir) = table
2247            .get_connection_for_packet_and_update(&bindings_ctx, packet.clone())
2248            .expect("packet should be valid")
2249            .expect("packet should be trackable");
2250        assert_matches!(
2251            table
2252                .finalize_connection(&mut bindings_ctx, conn)
2253                .expect("connection finalize should succeed"),
2254            (true, Some(_))
2255        );
2256        let (conn, _dir) = table
2257            .get_connection_for_packet_and_update(&bindings_ctx, reply_packet)
2258            .expect("packet should be valid")
2259            .expect("packet should be trackable");
2260        assert_matches!(
2261            table
2262                .finalize_connection(&mut bindings_ctx, conn)
2263                .expect("connection finalize should succeed"),
2264            (false, Some(_))
2265        );
2266        let (conn, _dir) = table
2267            .get_connection_for_packet_and_update(&bindings_ctx, packet)
2268            .expect("packet should be valid")
2269            .expect("packet should be trackable");
2270        assert_matches!(
2271            table
2272                .finalize_connection(&mut bindings_ctx, conn)
2273                .expect("connection finalize should succeed"),
2274            (false, Some(_))
2275        );
2276
2277        // This next one should fail because there are no connections left to
2278        // evict.
2279        let (packet, _) = make_test_udp_packets((MAXIMUM_ENTRIES / 2 + 1).try_into().unwrap());
2280        let packet = packet.conntrack_packet().unwrap();
2281        let (conn, _dir) = table
2282            .get_connection_for_packet_and_update(&bindings_ctx, packet)
2283            .expect("packet should be valid")
2284            .expect("packet should be trackable");
2285        assert_matches!(
2286            table.finalize_connection(&mut bindings_ctx, conn),
2287            Err(FinalizeConnectionError::TableFull)
2288        );
2289
2290        {
2291            let inspector = Inspector::new(Default::default());
2292            let mut bindings_inspector = FuchsiaInspector::<()>::new(inspector.root());
2293            bindings_inspector.delegate_inspectable(&table);
2294
2295            let mut exec = fuchsia_async::TestExecutor::new();
2296            assert_data_tree!(@executor exec, inspector, "root": contains {
2297                "table_limit_drops": 1u64,
2298                "table_limit_hits": 2u64,
2299                "num_entries": MAXIMUM_ENTRIES as u64,
2300            });
2301        }
2302    }
2303
2304    #[ip_test(I)]
2305    fn self_connected_socket<I: IpExt + TestIpExt>() {
2306        let mut bindings_ctx = FakeBindingsCtx::new();
2307        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2308
2309        let packet = PacketMetadata::<I>::new(
2310            I::SRC_IP,
2311            I::SRC_IP,
2312            TransportProtocol::Udp,
2313            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::SRC_PORT },
2314        );
2315
2316        let tuple = packet.tuple();
2317        let reply_tuple = tuple.clone().invert();
2318
2319        assert_eq!(tuple, reply_tuple);
2320
2321        let (conn, _dir) = table
2322            .get_connection_for_packet_and_update(&bindings_ctx, packet)
2323            .expect("packet should be valid")
2324            .expect("packet should be trackable");
2325        let state = conn.state();
2326        // Since we can't differentiate between the original and reply tuple,
2327        // the connection ends up being marked established immediately.
2328        assert_matches!(state.establishment_lifecycle, EstablishmentLifecycle::SeenReply);
2329
2330        assert_matches!(conn, Connection::Exclusive(_));
2331        assert!(!table.contains_tuple(&tuple));
2332
2333        // Once we finalize the connection, it should be present in the map.
2334        assert_matches!(table.finalize_connection(&mut bindings_ctx, conn), Ok((true, Some(_))));
2335        assert!(table.contains_tuple(&tuple));
2336
2337        // There should be a single connection in the table, despite there only
2338        // being a single tuple.
2339        assert_eq!(table.inner.lock().table.len(), 1);
2340
2341        bindings_ctx.sleep(CONNECTION_EXPIRY_TIME_UDP);
2342        table.perform_gc(&mut bindings_ctx);
2343
2344        assert!(table.inner.lock().table.is_empty());
2345    }
2346
2347    #[ip_test(I)]
2348    fn remove_entry_on_update<I: IpExt + TestIpExt>() {
2349        let mut bindings_ctx = FakeBindingsCtx::new();
2350        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2351
2352        let original_packet = PacketMetadata::<I>::new(
2353            I::SRC_IP,
2354            I::DST_IP,
2355            TransportProtocol::Tcp,
2356            TransportPacketData::Tcp {
2357                src_port: I::SRC_PORT,
2358                dst_port: I::DST_PORT,
2359                segment: SegmentHeader {
2360                    seq: SeqNum::new(1024),
2361                    wnd: UnscaledWindowSize::from(16u16),
2362                    control: Some(Control::SYN),
2363                    ..Default::default()
2364                },
2365                payload_len: 0,
2366            },
2367        );
2368
2369        let reply_packet = PacketMetadata::<I>::new(
2370            I::DST_IP,
2371            I::SRC_IP,
2372            TransportProtocol::Tcp,
2373            TransportPacketData::Tcp {
2374                src_port: I::DST_PORT,
2375                dst_port: I::SRC_PORT,
2376                segment: SegmentHeader {
2377                    seq: SeqNum::new(0),
2378                    ack: Some(SeqNum::new(1025)),
2379                    wnd: UnscaledWindowSize::from(16u16),
2380                    control: Some(Control::RST),
2381                    ..Default::default()
2382                },
2383                payload_len: 0,
2384            },
2385        );
2386
2387        let tuple = original_packet.tuple();
2388        let reply_tuple = tuple.clone().invert();
2389
2390        let (conn, _dir) = table
2391            .get_connection_for_packet_and_update(&bindings_ctx, original_packet)
2392            .expect("packet should be valid")
2393            .expect("packet should be trackable");
2394        assert_matches!(table.finalize_connection(&mut bindings_ctx, conn), Ok((true, Some(_))));
2395
2396        assert!(table.contains_tuple(&tuple));
2397        assert!(table.contains_tuple(&reply_tuple));
2398
2399        // Sending the reply RST through should result in the connection being
2400        // removed from the table.
2401        let (conn, _dir) = table
2402            .get_connection_for_packet_and_update(&bindings_ctx, reply_packet)
2403            .expect("packet should be valid")
2404            .expect("packet should be trackable");
2405
2406        assert!(!table.contains_tuple(&tuple));
2407        assert!(!table.contains_tuple(&reply_tuple));
2408        assert!(table.inner.lock().table.is_empty());
2409
2410        // The connection should not added back on finalization.
2411        assert_matches!(table.finalize_connection(&mut bindings_ctx, conn), Ok((false, Some(_))));
2412
2413        assert!(!table.contains_tuple(&tuple));
2414        assert!(!table.contains_tuple(&reply_tuple));
2415        assert!(table.inner.lock().table.is_empty());
2416
2417        // GC should complete successfully.
2418        bindings_ctx.sleep(Duration::from_secs(60 * 60 * 24 * 6));
2419        table.perform_gc(&mut bindings_ctx);
2420    }
2421
2422    #[ip_test(I)]
2423    fn do_not_insert<I: IpExt + TestIpExt>() {
2424        let mut bindings_ctx = FakeBindingsCtx::new();
2425        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2426
2427        let packet = PacketMetadata::<I>::new(
2428            I::SRC_IP,
2429            I::DST_IP,
2430            TransportProtocol::Udp,
2431            TransportPacketData::Generic { src_port: I::SRC_PORT, dst_port: I::DST_PORT },
2432        );
2433
2434        let tuple = packet.tuple();
2435        let reply_tuple = tuple.clone().invert();
2436
2437        let (conn, _dir) = table
2438            .get_connection_for_packet_and_update(&bindings_ctx, packet)
2439            .expect("packet should be valid")
2440            .expect("packet should be trackable");
2441        let mut conn = assert_matches!(conn, Connection::Exclusive(conn) => conn);
2442        conn.do_not_insert = true;
2443        assert_matches!(
2444            table.finalize_connection(&mut bindings_ctx, Connection::Exclusive(conn)),
2445            Ok((false, None))
2446        );
2447
2448        assert!(!table.contains_tuple(&tuple));
2449        assert!(!table.contains_tuple(&reply_tuple));
2450    }
2451
2452    // Regression test for https://fxbug.dev/518686402
2453    #[ip_test(I)]
2454    fn tcp_invalid_packet_does_not_advance_lifecycle<I: IpExt + TestIpExt>() {
2455        let mut bindings_ctx = FakeBindingsCtx::new();
2456        let table = Table::<_, (), _>::new::<IntoCoreTimerCtx>(&mut bindings_ctx);
2457
2458        // Begin connection setup.
2459
2460        let syn_packet = PacketMetadata::<I>::new(
2461            I::SRC_IP,
2462            I::DST_IP,
2463            TransportProtocol::Tcp,
2464            TransportPacketData::Tcp {
2465                src_port: I::SRC_PORT,
2466                dst_port: I::DST_PORT,
2467                segment: SegmentHeader {
2468                    seq: SeqNum::new(1000),
2469                    wnd: UnscaledWindowSize::from(1000u16),
2470                    control: Some(Control::SYN),
2471                    ..Default::default()
2472                },
2473                payload_len: 0,
2474            },
2475        );
2476
2477        let (conn, dir) = table
2478            .get_connection_for_packet_and_update(&bindings_ctx, syn_packet.clone())
2479            .expect("packet should be valid")
2480            .expect("packet should be trackable");
2481
2482        assert_eq!(dir, ConnectionDirection::Original);
2483        assert_matches!(conn.state().establishment_lifecycle, EstablishmentLifecycle::SeenOriginal);
2484
2485        let (inserted, finalized_conn) =
2486            table.finalize_connection(&mut bindings_ctx, conn).expect("finalize should succeed");
2487        assert!(inserted);
2488        let _finalized_conn = finalized_conn.expect("should have connection");
2489
2490        let syn_ack_packet = PacketMetadata::<I>::new(
2491            I::DST_IP,
2492            I::SRC_IP,
2493            TransportProtocol::Tcp,
2494            TransportPacketData::Tcp {
2495                src_port: I::DST_PORT,
2496                dst_port: I::SRC_PORT,
2497                segment: SegmentHeader {
2498                    seq: SeqNum::new(2000),
2499                    ack: Some(SeqNum::new(1001)),
2500                    wnd: UnscaledWindowSize::from(1000u16),
2501                    control: Some(Control::SYN),
2502                    ..Default::default()
2503                },
2504                payload_len: 0,
2505            },
2506        );
2507
2508        let (conn, dir) = table
2509            .get_connection_for_packet_and_update(&bindings_ctx, syn_ack_packet)
2510            .expect("packet should be valid")
2511            .expect("packet should be trackable");
2512
2513        assert_eq!(dir, ConnectionDirection::Reply);
2514        assert_matches!(conn.state().establishment_lifecycle, EstablishmentLifecycle::SeenReply);
2515
2516        let (inserted, _) =
2517            table.finalize_connection(&mut bindings_ctx, conn).expect("finalize should succeed");
2518        assert!(!inserted);
2519
2520        // Verify connection in table is indeed SeenReply
2521        assert_matches!(
2522            table
2523                .get_connection(&syn_packet.tuple())
2524                .expect("should exist")
2525                .state()
2526                .establishment_lifecycle,
2527            EstablishmentLifecycle::SeenReply
2528        );
2529
2530        // End connection setup.
2531
2532        // Send invalid ACK in the original direction (ACK of 2002 > max_next_seq 2001)
2533        let invalid_ack_packet = PacketMetadata::<I>::new(
2534            I::SRC_IP,
2535            I::DST_IP,
2536            TransportProtocol::Tcp,
2537            TransportPacketData::Tcp {
2538                src_port: I::SRC_PORT,
2539                dst_port: I::DST_PORT,
2540                segment: SegmentHeader {
2541                    seq: SeqNum::new(1001),
2542                    ack: Some(SeqNum::new(2002)),
2543                    wnd: UnscaledWindowSize::from(1000u16),
2544                    ..Default::default()
2545                },
2546                payload_len: 0,
2547            },
2548        );
2549
2550        let update_result =
2551            table.get_connection_for_packet_and_update(&bindings_ctx, invalid_ack_packet);
2552        assert_matches!(update_result, Err(GetConnectionError::InvalidPacket(_, _)));
2553
2554        // The connection lifecycle didn't change.
2555        assert_matches!(
2556            table
2557                .get_connection(&syn_packet.tuple())
2558                .expect("should exist")
2559                .state()
2560                .establishment_lifecycle,
2561            EstablishmentLifecycle::SeenReply
2562        );
2563    }
2564}