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