netstack3_filter/
conntrack.rs

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