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