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}