Skip to main content

microsandbox_network/engine/tcp/
connection.rs

1//! Connection tracker: manages smoltcp TCP sockets for the poll loop.
2//!
3//! Creates sockets on SYN detection, tracks connection lifecycle, relays data
4//! between smoltcp sockets and proxy task channels, and cleans up closed
5//! connections.
6
7use std::collections::{HashMap, HashSet};
8use std::net::SocketAddr;
9use std::num::NonZeroUsize;
10use std::sync::Arc;
11use std::sync::atomic::{AtomicU8, Ordering};
12
13use bytes::Bytes;
14use smoltcp::iface::{SocketHandle, SocketSet};
15use smoltcp::socket::tcp;
16use smoltcp::wire::IpListenEndpoint;
17use tokio::net::TcpStream;
18use tokio::sync::mpsc;
19
20use crate::tcp::deferred_close::DeferredClose;
21use crate::tcp::pending::PendingConnect;
22
23//--------------------------------------------------------------------------------------------------
24// Constants
25//--------------------------------------------------------------------------------------------------
26
27/// Log target for opt-in profiling events.
28const PROFILING_TARGET: &str = "microsandbox::profiling";
29
30/// TCP socket receive buffer size (64 KiB).
31const TCP_RX_BUF_SIZE: usize = 65536;
32
33/// TCP socket transmit buffer size (64 KiB).
34const TCP_TX_BUF_SIZE: usize = 65536;
35
36/// Capacity of the mpsc channels between the poll loop and proxy tasks.
37const CHANNEL_CAPACITY: usize = 32;
38
39/// Buffer size for reading from smoltcp sockets.
40const RELAY_BUF_SIZE: usize = 16384;
41
42//--------------------------------------------------------------------------------------------------
43// Types
44//--------------------------------------------------------------------------------------------------
45
46/// Terminal connection status reported by an outbound proxy task.
47#[repr(u8)]
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub enum ProxyConnectStatus {
50    /// No final proxy connection status has been reported yet.
51    Pending = 0,
52    /// The proxy connected to the upstream.
53    Connected = 1,
54    /// The proxy denied the connection before dialing upstream.
55    PolicyDenied = 2,
56    /// The proxy attempted to dial upstream and the connect failed.
57    UpstreamConnectFailed = 3,
58}
59
60/// Shared status for an outbound proxy task.
61///
62/// The smoltcp poll loop reads this when the proxy task exits to decide
63/// whether the guest should see a clean close or a TCP reset.
64pub struct ProxyConnectState {
65    status: AtomicU8,
66}
67
68/// Tracks TCP connections between guest and proxy tasks.
69///
70/// Each guest TCP connection maps to a smoltcp socket and a pair of channels
71/// connecting it to a tokio proxy task. The tracker handles:
72///
73/// - **Socket creation** — on SYN detection, before smoltcp processes the frame.
74/// - **Data relay** — shuttles bytes between smoltcp sockets and channels.
75/// - **Lifecycle detection** — identifies newly-established connections for
76///   proxy spawning.
77/// - **Cleanup** — removes closed sockets from the socket set.
78pub struct TcpConnectionTracker {
79    /// Active connections keyed by smoltcp socket handle.
80    connections: HashMap<SocketHandle, Connection>,
81    /// Secondary index for O(1) duplicate-SYN detection by (src, dst) 4-tuple.
82    connection_keys: HashSet<(SocketAddr, SocketAddr)>,
83    /// Max concurrent connections (from NetworkConfig).
84    max_tcp_connections: Option<NonZeroUsize>,
85    rejected_connections: u64,
86}
87
88/// Deprecated name for [`TcpConnectionTracker`].
89#[deprecated(note = "use TcpConnectionTracker instead")]
90pub type ConnectionTracker = TcpConnectionTracker;
91
92/// How to handle a guest TCP handshake after evaluating policy.
93pub enum TcpAcceptMode {
94    /// Complete the handshake only after the host connection succeeds.
95    WaitForUpstream,
96    /// Accept for hostname inspection or local DNS handling.
97    AcceptImmediately,
98    /// Accept only to send an HTTP denial response.
99    RespondWithDenial,
100    /// Leave the destination without a socket so smoltcp sends a reset.
101    Reject,
102}
103
104/// Ownership of an optional connect-before-accept host socket.
105enum UpstreamConnection {
106    /// The handshake does not require a host dial here; the proxy dials if needed.
107    Deferred,
108    /// The dial failed or its stream was handed off; no host socket is owned here.
109    Finished,
110    /// Wait for smoltcp to validate a guest SYN before starting the host dial.
111    Waiting,
112    /// Host dial is in progress while the guest's SYN-ACK remains paused.
113    Connecting(PendingConnect),
114    /// Host connection succeeded; resume the guest handshake, then pass this
115    /// stream to the proxy for reuse.
116    Connected(TcpStream),
117}
118
119/// Internal state for a single tracked TCP connection.
120struct Connection {
121    /// Guest source address (from the guest's SYN).
122    src: SocketAddr,
123    /// Original destination (from the guest's SYN).
124    dst: SocketAddr,
125    /// Sends data from smoltcp socket to proxy task (guest → server).
126    ///
127    /// Set to `None` once the guest half-closes (FIN) and all its data has
128    /// been relayed: dropping the sender makes the proxy task's
129    /// `from_smoltcp.recv()` return `None`, propagating the half-close
130    /// upstream while the server → guest direction stays open.
131    to_proxy: Option<mpsc::Sender<Bytes>>,
132    /// Receives data from proxy task to write to smoltcp socket (server → guest).
133    from_proxy: mpsc::Receiver<Bytes>,
134    /// Proxy-side channel ends, held until the connection is ESTABLISHED.
135    /// Taken by [`TcpConnectionTracker::take_new_connections()`].
136    proxy_channels: Option<ProxyChannels>,
137    /// Whether a proxy task has been spawned for this connection.
138    proxy_spawned: bool,
139    /// Host dial and socket held until the guest handshake completes.
140    upstream: UpstreamConnection,
141    /// Status reported by the proxy task before it exits.
142    proxy_connect: Arc<ProxyConnectState>,
143    /// Partial data from proxy that couldn't be fully written to smoltcp socket.
144    write_buf: Option<(Bytes, usize)>,
145    /// Data read from smoltcp socket that couldn't be sent to proxy (channel full).
146    /// Must be sent before reading more from the socket to preserve stream order.
147    read_buf: Option<Bytes>,
148    /// Progress deadline while draining after the host task exits.
149    deferred_close: DeferredClose,
150    /// Egress policy already denied this flow at SYN time; the connection
151    /// was accepted only so an HTTP/HTTPS client can be answered with 403.
152    policy_denied: bool,
153}
154
155/// Proxy-side channel ends, created at socket creation time and taken when
156/// the connection becomes ESTABLISHED.
157struct ProxyChannels {
158    /// Receive data from smoltcp socket (guest → proxy task).
159    from_smoltcp: mpsc::Receiver<Bytes>,
160    /// Send data to smoltcp socket (proxy task → guest).
161    to_smoltcp: mpsc::Sender<Bytes>,
162}
163
164/// Information for spawning a proxy task for a newly established connection.
165///
166/// Returned by [`TcpConnectionTracker::take_new_connections()`]. The poll loop
167/// passes this to the proxy task spawner.
168pub struct NewConnection {
169    /// Host socket opened before completing the guest handshake, if authorized.
170    /// `None` for deferred hostname checks, local DNS handling, or denial responses.
171    pub upstream: Option<TcpStream>,
172    /// Original destination the guest was connecting to.
173    pub dst: SocketAddr,
174    /// Receive data from smoltcp socket (guest → proxy task).
175    pub from_smoltcp: mpsc::Receiver<Bytes>,
176    /// Send data to smoltcp socket (proxy task → guest).
177    pub to_smoltcp: mpsc::Sender<Bytes>,
178    /// Status the proxy task updates before it exits.
179    pub proxy_connect: Arc<ProxyConnectState>,
180    /// Egress policy already denied this flow at SYN time. The dispatcher
181    /// must answer it (HTTP 403) and never dial upstream.
182    pub policy_denied: bool,
183}
184
185//--------------------------------------------------------------------------------------------------
186// Methods
187//--------------------------------------------------------------------------------------------------
188
189impl ProxyConnectStatus {
190    fn as_u8(self) -> u8 {
191        self as u8
192    }
193
194    fn from_u8(value: u8) -> Self {
195        match value {
196            value if value == Self::Connected as u8 => Self::Connected,
197            value if value == Self::PolicyDenied as u8 => Self::PolicyDenied,
198            value if value == Self::UpstreamConnectFailed as u8 => Self::UpstreamConnectFailed,
199            _ => Self::Pending,
200        }
201    }
202}
203
204impl ProxyConnectState {
205    /// Create a new pending proxy connection status.
206    pub fn new() -> Self {
207        Self {
208            status: AtomicU8::new(ProxyConnectStatus::Pending.as_u8()),
209        }
210    }
211
212    /// Mark the proxy as successfully connected to upstream.
213    pub fn mark_connected(&self) {
214        self.store(ProxyConnectStatus::Connected);
215    }
216
217    /// Mark the proxy as denied by egress policy before dialing upstream.
218    pub fn mark_policy_denied(&self) {
219        self.store(ProxyConnectStatus::PolicyDenied);
220    }
221
222    /// Mark the proxy as failed while dialing upstream.
223    pub fn mark_upstream_connect_failed(&self) {
224        self.store(ProxyConnectStatus::UpstreamConnectFailed);
225    }
226
227    /// Load the latest proxy connection status.
228    pub fn status(&self) -> ProxyConnectStatus {
229        ProxyConnectStatus::from_u8(self.status.load(Ordering::Acquire))
230    }
231
232    fn store(&self, status: ProxyConnectStatus) {
233        self.status.store(status.as_u8(), Ordering::Release);
234    }
235}
236
237impl Default for ProxyConnectState {
238    fn default() -> Self {
239        Self::new()
240    }
241}
242
243impl TcpConnectionTracker {
244    /// Create a new tracker with the given connection limit.
245    pub fn new(max_tcp_connections: Option<NonZeroUsize>) -> Self {
246        Self {
247            connections: HashMap::new(),
248            connection_keys: HashSet::new(),
249            max_tcp_connections,
250            rejected_connections: 0,
251        }
252    }
253
254    /// Returns `true` if a tracked socket already exists for this exact
255    /// connection (same source AND destination). O(1) via HashSet lookup.
256    pub fn has_socket_for(&self, src: &SocketAddr, dst: &SocketAddr) -> bool {
257        self.connection_keys.contains(&(*src, *dst))
258    }
259
260    /// Attach a host dial to the socket `handle` returned by [`Self::poll_connects`].
261    /// Removing that connection also cancels its pending dial.
262    pub(crate) fn track_connect(&mut self, handle: SocketHandle, pending: PendingConnect) {
263        if let Some(conn) = self.connections.get_mut(&handle) {
264            conn.upstream = UpstreamConnection::Connecting(pending);
265        }
266    }
267
268    /// Finish host dials and collect sockets whose validated SYN needs a dial.
269    /// Returns `(socket handle, guest destination)` pairs to dial and track.
270    pub(crate) fn poll_connects(
271        &mut self,
272        sockets: &mut SocketSet<'_>,
273    ) -> Vec<(SocketHandle, SocketAddr)> {
274        // Drop sockets returned to Listen by a guest reset, including any
275        // pending dial or connected host stream they still own.
276        self.cleanup_closed(sockets);
277        let mut requests = Vec::new();
278        for (&handle, conn) in &mut self.connections {
279            let socket = sockets.get_mut::<tcp::Socket>(handle);
280            match &mut conn.upstream {
281                UpstreamConnection::Waiting => {
282                    if socket.state() == tcp::State::SynReceived {
283                        requests.push((handle, conn.dst));
284                    }
285                }
286                UpstreamConnection::Connecting(pending) => {
287                    if socket.state() != tcp::State::SynReceived {
288                        // The guest is no longer waiting for this dial.
289                        conn.upstream = UpstreamConnection::Finished;
290                        socket.abort();
291                        continue;
292                    }
293                    let Some(result) = pending.take_result() else {
294                        continue;
295                    };
296
297                    match result {
298                        Ok(stream) => {
299                            conn.upstream = UpstreamConnection::Connected(stream);
300                            conn.proxy_connect.mark_connected();
301                            socket.pause_synack(false);
302                        }
303                        Err(error) => {
304                            tracing::debug!(dst = %conn.dst, %error, "upstream TCP connect failed before guest handshake");
305                            conn.upstream = UpstreamConnection::Finished;
306                            socket.abort();
307                        }
308                    }
309                }
310                UpstreamConnection::Deferred
311                | UpstreamConnection::Finished
312                | UpstreamConnection::Connected(_) => {}
313            }
314        }
315        requests
316    }
317
318    /// Create a destination-bound socket using the policy's handshake mode.
319    /// `src` and `dst` are guest-visible addresses; `mode` is the policy decision.
320    /// Returns `false` when the mode rejects the connection or the budget is full.
321    pub fn create_tcp_socket(
322        &mut self,
323        src: SocketAddr,
324        dst: SocketAddr,
325        sockets: &mut SocketSet<'_>,
326        mode: TcpAcceptMode,
327    ) -> bool {
328        if matches!(mode, TcpAcceptMode::Reject) {
329            return false;
330        }
331
332        let pause_synack = matches!(mode, TcpAcceptMode::WaitForUpstream);
333        let policy_denied = matches!(mode, TcpAcceptMode::RespondWithDenial);
334        if self
335            .max_tcp_connections
336            .is_some_and(|max| self.connections.len() >= max.get())
337        {
338            self.cleanup_closed(sockets);
339            if self
340                .max_tcp_connections
341                .is_some_and(|max| self.connections.len() >= max.get())
342            {
343                self.rejected_connections = self.rejected_connections.saturating_add(1);
344                return false;
345            }
346        }
347
348        // Create smoltcp TCP socket with buffers.
349        let rx_buf = tcp::SocketBuffer::new(vec![0u8; TCP_RX_BUF_SIZE]);
350        let tx_buf = tcp::SocketBuffer::new(vec![0u8; TCP_TX_BUF_SIZE]);
351        let mut socket = tcp::Socket::new(rx_buf, tx_buf);
352        if pause_synack {
353            socket.pause_synack(true);
354        }
355
356        // Listen on the specific destination IP + port. With any_ip mode,
357        // binding to the IP ensures the correct socket accepts each SYN
358        // when multiple connections target the same port on different IPs.
359        let listen_endpoint = IpListenEndpoint {
360            addr: Some(dst.ip().into()),
361            port: dst.port(),
362        };
363        if socket.listen(listen_endpoint).is_err() {
364            return false;
365        }
366
367        let handle = sockets.add(socket);
368
369        // Create channel pairs for proxy task communication.
370        //
371        // smoltcp → proxy (guest sends data, proxy relays to server):
372        let (to_proxy_tx, to_proxy_rx) = mpsc::channel(CHANNEL_CAPACITY);
373        // proxy → smoltcp (server sends data, proxy relays to guest):
374        let (from_proxy_tx, from_proxy_rx) = mpsc::channel(CHANNEL_CAPACITY);
375        let proxy_connect = Arc::new(ProxyConnectState::new());
376
377        self.connection_keys.insert((src, dst));
378        self.connections.insert(
379            handle,
380            Connection {
381                src,
382                dst,
383                to_proxy: Some(to_proxy_tx),
384                from_proxy: from_proxy_rx,
385                proxy_channels: Some(ProxyChannels {
386                    from_smoltcp: to_proxy_rx,
387                    to_smoltcp: from_proxy_tx,
388                }),
389                proxy_spawned: false,
390                upstream: if pause_synack {
391                    UpstreamConnection::Waiting
392                } else {
393                    UpstreamConnection::Deferred
394                },
395                proxy_connect,
396                write_buf: None,
397                read_buf: None,
398                deferred_close: DeferredClose::default(),
399                policy_denied,
400            },
401        );
402
403        true
404    }
405
406    /// Earliest pending drain deadline for the network poll loop.
407    pub(crate) fn deferred_close_delay(&self) -> Option<std::time::Duration> {
408        self.connections
409            .values()
410            .filter_map(|conn| conn.deferred_close.poll_delay())
411            .min()
412    }
413
414    /// Relay data between smoltcp sockets and proxy task channels.
415    ///
416    /// For each connection with a spawned proxy:
417    /// - Reads data from the smoltcp socket and sends it to the proxy channel.
418    /// - Receives data from the proxy channel and writes it to the smoltcp socket.
419    pub fn relay_data(&mut self, sockets: &mut SocketSet<'_>) {
420        let mut relay_buf = [0u8; RELAY_BUF_SIZE];
421
422        for (&handle, conn) in &mut self.connections {
423            if !conn.proxy_spawned {
424                continue;
425            }
426
427            let socket = sockets.get_mut::<tcp::Socket>(handle);
428
429            // Already torn down (e.g. abort fired on a previous pass).
430            // Leave it for `cleanup_closed` to evict.
431            if matches!(socket.state(), tcp::State::Closed) {
432                conn.deferred_close = DeferredClose::default();
433                continue;
434            }
435
436            // Detect proxy task exit: when the proxy drops its channel
437            // ends, close the smoltcp socket so the guest gets a FIN.
438            //
439            // If the proxy attempted and failed to reach upstream,
440            // an RST via `abort()` reports the upstream error. For flows
441            // that required a hostname before dialing, the guest handshake
442            // already succeeded, so this cannot guarantee address fallback.
443            let proxy_exited = match &conn.to_proxy {
444                Some(to_proxy) => to_proxy.is_closed(),
445                // The guest already half-closed (sender dropped below), so
446                // proxy exit is detected on the other channel instead: the
447                // proxy drops its `to_smoltcp` sender when it returns.
448                None => conn.from_proxy.is_closed(),
449            };
450            if proxy_exited {
451                if matches!(
452                    conn.proxy_connect.status(),
453                    ProxyConnectStatus::UpstreamConnectFailed
454                ) {
455                    tracing::debug!(
456                        src = %conn.src,
457                        dst = %conn.dst,
458                        "upstream connect failed; aborting smoltcp socket (RST to guest)"
459                    );
460                    socket.abort();
461                    continue;
462                }
463                let queued_before = socket.send_queue();
464                write_proxy_data(socket, conn);
465                let written = socket.send_queue() - queued_before;
466                conn.deferred_close
467                    .finish(socket, conn.write_buf.is_some(), written);
468                continue;
469            }
470
471            // smoltcp → proxy: flush read_buf first, then read from socket.
472            if let Some(to_proxy) = &conn.to_proxy {
473                if let Some(pending) = conn.read_buf.take()
474                    && let Err(e) = to_proxy.try_send(pending)
475                {
476                    conn.read_buf = Some(e.into_inner());
477                }
478
479                if conn.read_buf.is_none() {
480                    while socket.can_recv() {
481                        match socket.recv_slice(&mut relay_buf) {
482                            Ok(n) if n > 0 => {
483                                let data = Bytes::copy_from_slice(&relay_buf[..n]);
484                                if let Err(e) = to_proxy.try_send(data) {
485                                    conn.read_buf = Some(e.into_inner());
486                                    break;
487                                }
488                            }
489                            _ => break,
490                        }
491                    }
492                }
493
494                // Guest half-close: the guest sent a FIN (CLOSE_WAIT) and
495                // everything it sent has been relayed. Drop the sender so
496                // the proxy task sees EOF and can shut down the guest →
497                // server direction upstream. The server → guest direction
498                // stays open; the socket is closed once the proxy task
499                // exits (see `proxy_exited` above).
500                if matches!(socket.state(), tcp::State::CloseWait)
501                    && conn.read_buf.is_none()
502                    && !socket.can_recv()
503                {
504                    conn.to_proxy = None;
505                }
506            }
507
508            // proxy → smoltcp: write pending data, then drain channel.
509            write_proxy_data(socket, conn);
510        }
511    }
512
513    /// Collect newly-established connections that need proxy tasks.
514    ///
515    /// Returns a list of [`NewConnection`] structs containing the channel ends
516    /// for the proxy task. The poll loop is responsible for spawning the task.
517    pub fn take_new_connections(&mut self, sockets: &mut SocketSet<'_>) -> Vec<NewConnection> {
518        let mut new = Vec::new();
519
520        for (&handle, conn) in &mut self.connections {
521            if conn.proxy_spawned {
522                continue;
523            }
524
525            let socket = sockets.get_mut::<tcp::Socket>(handle);
526            if matches!(
527                socket.state(),
528                tcp::State::Established | tcp::State::CloseWait
529            ) {
530                conn.proxy_spawned = true;
531
532                if let Some(channels) = conn.proxy_channels.take() {
533                    let upstream =
534                        match std::mem::replace(&mut conn.upstream, UpstreamConnection::Finished) {
535                            UpstreamConnection::Connected(stream) => Some(stream),
536                            UpstreamConnection::Deferred
537                            | UpstreamConnection::Finished
538                            | UpstreamConnection::Waiting
539                            | UpstreamConnection::Connecting(_) => None,
540                        };
541
542                    new.push(NewConnection {
543                        upstream,
544                        dst: conn.dst,
545                        from_smoltcp: channels.from_smoltcp,
546                        to_smoltcp: channels.to_smoltcp,
547                        proxy_connect: conn.proxy_connect.clone(),
548                        policy_denied: conn.policy_denied,
549                    });
550                }
551            }
552        }
553
554        new
555    }
556
557    /// Record bounded-cardinality diagnostics once per maintenance interval.
558    pub fn trace_stats(&self, sockets: &SocketSet<'_>) {
559        if !tracing::enabled!(target: PROFILING_TARGET, tracing::Level::TRACE) {
560            return;
561        }
562        let closing = self
563            .connections
564            .keys()
565            .filter(|&&handle| {
566                matches!(
567                    sockets.get::<tcp::Socket>(handle).state(),
568                    tcp::State::CloseWait
569                        | tcp::State::FinWait1
570                        | tcp::State::FinWait2
571                        | tcp::State::Closing
572                        | tcp::State::LastAck
573                        | tcp::State::TimeWait
574                )
575            })
576            .count();
577        tracing::trace!(
578            target: PROFILING_TARGET,
579            limit = ?self.max_tcp_connections,
580            tracked = self.connections.len(),
581            closing,
582            rejected_total = self.rejected_connections,
583            socket_buffer_bytes = self.connections.len() * (TCP_RX_BUF_SIZE + TCP_TX_BUF_SIZE),
584            "TCP connection budget"
585        );
586    }
587
588    /// Remove closed connections and their sockets.
589    ///
590    /// Idle listeners represent failed/reset SYNs: this tracker never owns
591    /// persistent listening sockets. Closed sockets with a remote endpoint
592    /// still owe the guest an RST and must survive until smoltcp emits it.
593    /// TIME_WAIT remains intact to reject delayed duplicate segments.
594    pub fn cleanup_closed(&mut self, sockets: &mut SocketSet<'_>) {
595        let keys = &mut self.connection_keys;
596        self.connections.retain(|&handle, conn| {
597            let socket = sockets.get::<tcp::Socket>(handle);
598            if matches!(socket.state(), tcp::State::Closed | tcp::State::Listen)
599                && socket.remote_endpoint().is_none()
600            {
601                keys.remove(&(conn.src, conn.dst));
602                sockets.remove(handle);
603                false
604            } else {
605                true
606            }
607        });
608    }
609}
610
611//--------------------------------------------------------------------------------------------------
612// Functions
613//--------------------------------------------------------------------------------------------------
614
615/// Try to write proxy data to the smoltcp socket.
616fn write_proxy_data(socket: &mut tcp::Socket<'_>, conn: &mut Connection) {
617    // First, try to finish writing any pending partial data.
618    if let Some((data, offset)) = &mut conn.write_buf {
619        if socket.can_send() {
620            match socket.send_slice(&data[*offset..]) {
621                Ok(written) => {
622                    *offset += written;
623                    if *offset >= data.len() {
624                        conn.write_buf = None;
625                    }
626                }
627                Err(_) => return,
628            }
629        } else {
630            return;
631        }
632    }
633
634    // Then drain the channel.
635    while conn.write_buf.is_none() {
636        match conn.from_proxy.try_recv() {
637            Ok(data) => {
638                if socket.can_send() {
639                    match socket.send_slice(&data) {
640                        Ok(written) if written < data.len() => {
641                            conn.write_buf = Some((data, written));
642                        }
643                        Err(_) => {
644                            conn.write_buf = Some((data, 0));
645                        }
646                        _ => {}
647                    }
648                } else {
649                    conn.write_buf = Some((data, 0));
650                }
651            }
652            Err(_) => break,
653        }
654    }
655}
656
657//--------------------------------------------------------------------------------------------------
658// Tests
659//--------------------------------------------------------------------------------------------------
660
661#[cfg(test)]
662mod tests {
663    use super::*;
664
665    #[tokio::test(start_paused = true)]
666    async fn exited_proxy_drains_slow_guest_and_times_out_only_when_stalled() {
667        use crate::tcp::test_support::TestNetwork;
668
669        for stalled in [false, true] {
670            let mut network = TestNetwork::new(true);
671            let mut tracker = TcpConnectionTracker::new(None);
672            assert!(tracker.create_tcp_socket(
673                "10.0.0.1:12345".parse().unwrap(),
674                "10.0.0.2:8099".parse().unwrap(),
675                &mut network.sockets,
676                TcpAcceptMode::AcceptImmediately,
677            ));
678            for _ in 0..16 {
679                network.poll();
680            }
681            let mut connections = tracker.take_new_connections(&mut network.sockets);
682            assert_eq!(connections.len(), 1);
683            let conn = connections.remove(0);
684            conn.proxy_connect.mark_connected();
685            let payload: Vec<u8> = (0..262144).map(|i| (i % 251) as u8).collect();
686            for chunk in payload.chunks(16384) {
687                conn.to_smoltcp
688                    .try_send(Bytes::copy_from_slice(chunk))
689                    .unwrap();
690            }
691            drop(conn);
692
693            network
694                .check_drain(|sockets| tracker.relay_data(sockets), &payload, stalled)
695                .await;
696        }
697    }
698
699    #[test]
700    fn omitted_limit_tracks_more_than_the_previous_default() {
701        let mut tracker = TcpConnectionTracker::new(None);
702        let mut sockets = SocketSet::new(Vec::new());
703        let dst = "198.51.100.1:443".parse().unwrap();
704        for port in 10000..10300 {
705            let src = SocketAddr::from(([192, 0, 2, 1], port));
706            assert!(tracker.create_tcp_socket(
707                src,
708                dst,
709                &mut sockets,
710                TcpAcceptMode::AcceptImmediately
711            ));
712        }
713        assert_eq!(tracker.connections.len(), 300);
714    }
715}