Skip to main content

netstack3_filter/
conntrack.rs

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