1#![cfg(any(target_os = "macos", all(test, unix)))]
2
3mod device;
16mod manager;
17#[cfg(test)]
18mod tests;
19
20use std::collections::HashSet;
21use std::io::{self, Read, Write};
22use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpListener, TcpStream, UdpSocket};
23use std::os::unix::net::UnixDatagram;
24use std::path::PathBuf;
25use std::sync::{
26 atomic::{AtomicBool, AtomicUsize, Ordering},
27 mpsc::{self, Receiver, TryRecvError},
28 Arc,
29};
30use std::time::Duration;
31
32use smoltcp::iface::{Config, Interface, SocketSet};
33use smoltcp::socket::{tcp, udp};
34use smoltcp::time::Instant;
35use smoltcp::wire::{
36 EthernetFrame, EthernetProtocol, IpAddress, IpCidr, IpEndpoint, IpProtocol, Ipv4Address,
37 Ipv4Packet, TcpPacket,
38};
39
40use device::{BridgePort, NetStats, UnixgramDevice, GATEWAY_MAC};
41use manager::write_stats_file;
42
43pub use manager::{spawn_inherited_netproxy, InheritedNetProxyConfig, NetProxyManager};
44
45fn smoltcp_now() -> Instant {
49 use std::time::{SystemTime, UNIX_EPOCH};
50 let us = SystemTime::now()
51 .duration_since(UNIX_EPOCH)
52 .unwrap_or_default()
53 .as_micros() as i64;
54 Instant::from_micros(us)
55}
56
57fn to_smoltcp_ipv4(ip: Ipv4Addr) -> Ipv4Address {
59 Ipv4Address::from(ip)
60}
61
62const EPHEMERAL_BASE: u16 = 49152;
66const MAX_OUTBOUND_CONNECTIONS: usize = 256;
68const OUTBOUND_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
70const TCP_IDLE_TIMEOUT: smoltcp::time::Duration = smoltcp::time::Duration::from_secs(300);
72const STATS_WRITE_INTERVAL: Duration = Duration::from_secs(1);
74
75struct PortForward {
79 listener: TcpListener,
80 guest_ip: Ipv4Addr,
81 guest_port: u16,
82 pending: Vec<PendingGuestConnection>,
84 active: Vec<TcpProxyConnection>,
86}
87
88struct PendingGuestConnection {
89 handle: smoltcp::iface::SocketHandle,
90 host_stream: TcpStream,
91 started_at: std::time::Instant,
92}
93
94struct TcpProxyConnection {
95 handle: smoltcp::iface::SocketHandle,
96 host_stream: TcpStream,
97 host_read_closed: bool,
98 guest_read_closed: bool,
99 abort_pending: bool,
102}
103
104impl TcpProxyConnection {
105 fn new(handle: smoltcp::iface::SocketHandle, host_stream: TcpStream) -> Self {
106 Self {
107 handle,
108 host_stream,
109 host_read_closed: false,
110 guest_read_closed: false,
111 abort_pending: false,
112 }
113 }
114}
115
116#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
117struct OutboundFlow {
118 guest_ip: Ipv4Addr,
119 guest_port: u16,
120 remote_ip: Ipv4Addr,
121 remote_port: u16,
122}
123
124struct PendingOutboundConnection {
125 flow: OutboundFlow,
126 handle: smoltcp::iface::SocketHandle,
127 connect_result: Receiver<io::Result<TcpStream>>,
128 host_stream: Option<TcpStream>,
129 started_at: std::time::Instant,
130 failed: bool,
131}
132
133struct ActiveOutboundConnection {
134 flow: OutboundFlow,
135 proxy: TcpProxyConnection,
136}
137
138struct ProxyEngineConfig {
141 socket: UnixDatagram,
142 guest_ip: Ipv4Addr,
143 gateway_ip: Ipv4Addr,
144 prefix_len: u8,
145 dns_servers: Vec<Ipv4Addr>,
146 port_forwards: Vec<PortForward>,
147 shutdown: Arc<AtomicBool>,
148 stats: Arc<NetStats>,
149 stats_path: Option<PathBuf>,
150 bridge: Option<BridgePort>,
151}
152
153struct ProxyEngine {
154 device: UnixgramDevice,
155 iface: Interface,
156 sockets: SocketSet<'static>,
157 dns_sockets: Vec<(smoltcp::iface::SocketHandle, Ipv4Addr)>,
158 guest_ip: Ipv4Addr,
159 gateway_ip: Ipv4Addr,
160 port_forwards: Vec<PortForward>,
161 pending_outbound: Vec<PendingOutboundConnection>,
162 active_outbound: Vec<ActiveOutboundConnection>,
163 outbound_connectors: Arc<AtomicUsize>,
164 next_ephemeral: u16,
165 shutdown: Arc<AtomicBool>,
166 stats: Arc<NetStats>,
167 stats_path: Option<PathBuf>,
168 last_stats_write: std::time::Instant,
169}
170
171impl ProxyEngine {
172 fn new(config: ProxyEngineConfig) -> Self {
173 let ProxyEngineConfig {
174 socket,
175 guest_ip,
176 gateway_ip,
177 prefix_len,
178 dns_servers,
179 port_forwards,
180 shutdown,
181 stats,
182 stats_path,
183 bridge,
184 } = config;
185
186 let mut device = UnixgramDevice::new(socket, bridge, Arc::clone(&stats));
187
188 let config = Config::new(GATEWAY_MAC.into());
190 let mut iface = Interface::new(config, &mut device, smoltcp_now());
191 iface.update_ip_addrs(|addrs| {
192 let cidr = IpCidr::new(IpAddress::Ipv4(to_smoltcp_ipv4(gateway_ip)), prefix_len);
193 addrs.push(cidr).ok();
194 });
195 iface.set_any_ip(true);
200 let _ = iface
201 .routes_mut()
202 .add_default_ipv4_route(to_smoltcp_ipv4(gateway_ip));
203
204 let mut sockets = SocketSet::new(vec![]);
205
206 let dns_sockets = dns_servers
212 .iter()
213 .copied()
214 .filter_map(|server| {
215 let dns_rx =
216 udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY; 16], vec![0u8; 8192]);
217 let dns_tx =
218 udp::PacketBuffer::new(vec![udp::PacketMetadata::EMPTY; 16], vec![0u8; 8192]);
219 let mut dns_socket = udp::Socket::new(dns_rx, dns_tx);
220 let endpoint = IpEndpoint::new(IpAddress::Ipv4(to_smoltcp_ipv4(server)), 53);
221 dns_socket
222 .bind(endpoint)
223 .ok()
224 .map(|_| (sockets.add(dns_socket), server))
225 })
226 .collect();
227
228 Self {
229 device,
230 iface,
231 sockets,
232 dns_sockets,
233 guest_ip,
234 gateway_ip,
235 port_forwards,
236 pending_outbound: Vec::new(),
237 active_outbound: Vec::new(),
238 outbound_connectors: Arc::new(AtomicUsize::new(0)),
239 next_ephemeral: EPHEMERAL_BASE,
240 shutdown,
241 stats,
242 stats_path,
243 last_stats_write: std::time::Instant::now(),
244 }
245 }
246
247 fn run(&mut self) {
248 self.write_stats_snapshot();
249 loop {
250 if self.shutdown.load(Ordering::Relaxed) {
251 break;
252 }
253
254 let now = smoltcp_now();
255
256 self.device.drain();
258
259 self.accept_connections();
262 self.accept_outbound_flows();
263
264 self.poll_outbound_connectors();
267
268 self.iface.poll(now, &mut self.device, &mut self.sockets);
270
271 self.finish_aborted_connections();
274
275 self.promote_established();
277 self.promote_outbound_established();
278
279 self.proxy_data();
281
282 self.forward_dns();
284
285 self.cleanup();
287
288 self.maybe_write_stats_snapshot();
290
291 let delay = self
293 .iface
294 .poll_delay(now, &self.sockets)
295 .unwrap_or(smoltcp::time::Duration::from_millis(1));
296 std::thread::sleep(Duration::from_micros(delay.micros().min(5_000)));
297 }
298 self.write_stats_snapshot();
299 }
300
301 fn maybe_write_stats_snapshot(&mut self) {
302 if self.last_stats_write.elapsed() < STATS_WRITE_INTERVAL {
303 return;
304 }
305 self.last_stats_write = std::time::Instant::now();
306 self.write_stats_snapshot();
307 }
308
309 fn write_stats_snapshot(&self) {
310 let Some(path) = self.stats_path.as_deref() else {
311 return;
312 };
313 if let Err(e) = write_stats_file(path, self.stats.snapshot()) {
314 tracing::debug!(error = %e, path = %path.display(), "NetProxy: failed to write stats file");
315 }
316 }
317
318 fn accept_connections(&mut self) {
321 let mut new_conns: Vec<(usize, TcpStream, Ipv4Addr, u16)> = Vec::new();
324 for (i, pf) in self.port_forwards.iter_mut().enumerate() {
325 loop {
326 match pf.listener.accept() {
327 Ok((stream, _)) => {
328 stream.set_nonblocking(true).ok();
329 stream.set_nodelay(true).ok();
330 new_conns.push((i, stream, pf.guest_ip, pf.guest_port));
331 }
332 Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
333 Err(e) => {
334 tracing::warn!(error = %e, "Port-forward listener error");
335 break;
336 }
337 }
338 }
339 }
340
341 for (i, stream, guest_ip, guest_port) in new_conns {
343 let handle = self.open_guest_tcp(guest_ip, guest_port);
344 self.port_forwards[i].pending.push(PendingGuestConnection {
345 handle,
346 host_stream: stream,
347 started_at: std::time::Instant::now(),
348 });
349 tracing::debug!(
350 guest = %guest_ip,
351 port = guest_port,
352 handle = ?handle,
353 "NetProxy accepted host connection and initiated guest TCP connect"
354 );
355 }
356 }
357
358 fn open_guest_tcp(
360 &mut self,
361 guest_ip: Ipv4Addr,
362 guest_port: u16,
363 ) -> smoltcp::iface::SocketHandle {
364 let rx = tcp::SocketBuffer::new(vec![0u8; 65536]);
365 let tx = tcp::SocketBuffer::new(vec![0u8; 65536]);
366 let mut socket = tcp::Socket::new(rx, tx);
367
368 let local_port = self.next_ephemeral;
369 self.next_ephemeral = self.next_ephemeral.wrapping_add(1);
370 if self.next_ephemeral < EPHEMERAL_BASE {
371 self.next_ephemeral = EPHEMERAL_BASE;
372 }
373
374 let remote = IpEndpoint::new(IpAddress::Ipv4(to_smoltcp_ipv4(guest_ip)), guest_port);
375 socket
376 .connect(self.iface.context(), remote, local_port)
377 .ok();
378 socket.set_keep_alive(Some(smoltcp::time::Duration::from_secs(30)));
379 socket.set_timeout(Some(TCP_IDLE_TIMEOUT));
380
381 self.sockets.add(socket)
382 }
383
384 fn accept_outbound_flows(&mut self) {
387 let queued_flows: HashSet<_> = self
388 .device
389 .rx_queue
390 .iter()
391 .filter_map(|frame| outbound_syn_flow(frame, self.guest_ip, self.gateway_ip))
392 .collect();
393
394 for flow in queued_flows {
395 if self.outbound_flow_exists(flow) {
396 continue;
397 }
398 if self.pending_outbound.len() + self.active_outbound.len() >= MAX_OUTBOUND_CONNECTIONS
399 || self.outbound_connectors.load(Ordering::Relaxed) >= MAX_OUTBOUND_CONNECTIONS
400 {
401 tracing::warn!(
402 limit = MAX_OUTBOUND_CONNECTIONS,
403 "NetProxy outbound connection limit reached"
404 );
405 continue;
406 }
407
408 let connect_result = match spawn_outbound_connect(
409 flow,
410 Arc::clone(&self.outbound_connectors),
411 ) {
412 Ok(receiver) => receiver,
413 Err(error) => {
414 tracing::warn!(%error, ?flow, "NetProxy failed to spawn outbound connector");
415 continue;
416 }
417 };
418
419 let rx = tcp::SocketBuffer::new(vec![0u8; 65536]);
420 let tx = tcp::SocketBuffer::new(vec![0u8; 65536]);
421 let mut socket = tcp::Socket::new(rx, tx);
422 let endpoint = IpEndpoint::new(
423 IpAddress::Ipv4(to_smoltcp_ipv4(flow.remote_ip)),
424 flow.remote_port,
425 );
426 if let Err(error) = socket.listen(endpoint) {
427 tracing::warn!(?error, ?flow, "NetProxy failed to listen for outbound flow");
428 continue;
429 }
430 socket.set_keep_alive(Some(smoltcp::time::Duration::from_secs(30)));
431 socket.set_timeout(Some(TCP_IDLE_TIMEOUT));
432 let handle = self.sockets.add(socket);
433
434 self.pending_outbound.push(PendingOutboundConnection {
435 flow,
436 handle,
437 connect_result,
438 host_stream: None,
439 started_at: std::time::Instant::now(),
440 failed: false,
441 });
442 tracing::debug!(
443 ?flow,
444 ?handle,
445 "NetProxy discovered guest outbound TCP flow"
446 );
447 }
448 }
449
450 fn outbound_flow_exists(&self, flow: OutboundFlow) -> bool {
451 self.pending_outbound
452 .iter()
453 .any(|pending| pending.flow == flow)
454 || self
455 .active_outbound
456 .iter()
457 .any(|active| active.flow == flow)
458 }
459
460 fn poll_outbound_connectors(&mut self) {
464 for pending in &mut self.pending_outbound {
465 if pending.failed || pending.host_stream.is_some() {
466 continue;
467 }
468
469 match pending.connect_result.try_recv() {
470 Ok(Ok(stream)) => {
471 pending.host_stream = Some(stream);
472 tracing::debug!(flow = ?pending.flow, "NetProxy host TCP connection established");
473 }
474 Ok(Err(error)) => {
475 tracing::debug!(%error, flow = ?pending.flow, "NetProxy host TCP connection failed");
476 self.sockets.get_mut::<tcp::Socket>(pending.handle).abort();
477 pending.failed = true;
478 }
479 Err(TryRecvError::Empty) => {
480 if pending.started_at.elapsed()
481 > OUTBOUND_CONNECT_TIMEOUT + Duration::from_secs(1)
482 {
483 tracing::debug!(flow = ?pending.flow, "NetProxy host TCP connection timed out");
484 self.sockets.get_mut::<tcp::Socket>(pending.handle).abort();
485 pending.failed = true;
486 }
487 }
488 Err(TryRecvError::Disconnected) => {
489 self.sockets.get_mut::<tcp::Socket>(pending.handle).abort();
490 pending.failed = true;
491 }
492 }
493 }
494 }
495
496 fn promote_established(&mut self) {
499 for pf in &mut self.port_forwards {
500 let mut still_pending = Vec::new();
501 let mut to_remove = Vec::new();
502 for pending in pf.pending.drain(..) {
503 let socket = self.sockets.get::<tcp::Socket>(pending.handle);
504 use smoltcp::socket::tcp::State;
505 match socket.state() {
506 State::Established => {
507 tracing::debug!(handle = ?pending.handle, "NetProxy guest TCP connection established");
508 pf.active
509 .push(TcpProxyConnection::new(pending.handle, pending.host_stream));
510 }
511 State::Closed | State::TimeWait => {
512 tracing::debug!(handle = ?pending.handle, state = ?socket.state(), "NetProxy guest TCP connection closed before establishment");
513 to_remove.push(pending.handle);
514 }
515 _ if pending.started_at.elapsed() > OUTBOUND_CONNECT_TIMEOUT => {
516 tracing::debug!(handle = ?pending.handle, "NetProxy guest TCP connection timed out");
517 to_remove.push(pending.handle);
518 }
519 _ => {
520 still_pending.push(pending);
521 }
522 }
523 }
524 pf.pending = still_pending;
525 for handle in to_remove {
526 self.sockets.remove(handle);
527 }
528 }
529 }
530
531 fn promote_outbound_established(&mut self) {
532 use smoltcp::socket::tcp::State;
533
534 let mut still_pending = Vec::new();
535 let mut to_remove = Vec::new();
536 for mut pending in self.pending_outbound.drain(..) {
537 let state = self.sockets.get::<tcp::Socket>(pending.handle).state();
538 if pending.failed || matches!(state, State::Closed | State::TimeWait) {
539 to_remove.push(pending.handle);
540 continue;
541 }
542
543 if matches!(state, State::Established | State::CloseWait) {
544 if let Some(stream) = pending.host_stream.take() {
545 tracing::debug!(flow = ?pending.flow, handle = ?pending.handle, "NetProxy outbound TCP proxy active");
546 self.active_outbound.push(ActiveOutboundConnection {
547 flow: pending.flow,
548 proxy: TcpProxyConnection::new(pending.handle, stream),
549 });
550 continue;
551 }
552 }
553
554 still_pending.push(pending);
555 }
556 self.pending_outbound = still_pending;
557 for handle in to_remove {
558 self.sockets.remove(handle);
559 }
560 }
561
562 fn finish_aborted_connections(&mut self) {
563 for pf in &mut self.port_forwards {
564 let mut to_remove = Vec::new();
565 pf.active.retain(|connection| {
566 if connection.abort_pending {
567 to_remove.push(connection.handle);
568 false
569 } else {
570 true
571 }
572 });
573 for handle in to_remove {
574 self.sockets.remove(handle);
575 }
576 }
577
578 let mut to_remove = Vec::new();
579 self.active_outbound.retain(|connection| {
580 if connection.proxy.abort_pending {
581 to_remove.push(connection.proxy.handle);
582 false
583 } else {
584 true
585 }
586 });
587 for handle in to_remove {
588 self.sockets.remove(handle);
589 }
590 }
591
592 fn proxy_data(&mut self) {
595 for pf in &mut self.port_forwards {
596 for connection in &mut pf.active {
597 proxy_tcp_connection(&mut self.sockets, connection);
598 }
599 }
600 for connection in &mut self.active_outbound {
601 proxy_tcp_connection(&mut self.sockets, &mut connection.proxy);
602 }
603 }
604
605 fn forward_dns(&mut self) {
608 let next_query = self.dns_sockets.iter().find_map(|(handle, server)| {
609 let socket = self.sockets.get_mut::<udp::Socket>(*handle);
610 if !socket.can_recv() {
611 return None;
612 }
613 let (query, source) = socket.recv().ok()?;
614 Some((*handle, *server, query.to_vec(), source))
615 });
616 let Some((handle, dns_server, query, source)) = next_query else {
617 return;
618 };
619
620 match UdpSocket::bind("0.0.0.0:0") {
622 Ok(udp) => {
623 udp.set_read_timeout(Some(Duration::from_secs(2))).ok();
624 let dest = SocketAddrV4::new(dns_server, 53);
625 if udp.send_to(&query, dest).is_ok() {
626 let mut resp = vec![0u8; 4096];
627 if let Ok((n, _)) = udp.recv_from(&mut resp) {
628 let socket = self.sockets.get_mut::<udp::Socket>(handle);
629 socket.send_slice(&resp[..n], source).ok();
630 }
631 }
632 }
633 Err(e) => {
634 tracing::warn!(error = %e, "DNS forward: failed to bind host UDP socket");
635 }
636 }
637 }
638
639 fn cleanup(&mut self) {
642 use smoltcp::socket::tcp::State;
643 for pf in &mut self.port_forwards {
644 let mut to_remove = Vec::new();
646 pf.active.retain(|connection| {
647 let state = self.sockets.get::<tcp::Socket>(connection.handle).state();
648 if matches!(state, State::Closed | State::TimeWait) && !connection.abort_pending {
649 to_remove.push(connection.handle);
650 false
651 } else {
652 true
653 }
654 });
655 for handle in to_remove {
656 self.sockets.remove(handle);
657 }
658 }
659
660 let mut to_remove = Vec::new();
661 self.active_outbound.retain(|connection| {
662 let state = self
663 .sockets
664 .get::<tcp::Socket>(connection.proxy.handle)
665 .state();
666 if matches!(state, State::Closed | State::TimeWait) && !connection.proxy.abort_pending {
667 to_remove.push(connection.proxy.handle);
668 false
669 } else {
670 true
671 }
672 });
673 for handle in to_remove {
674 self.sockets.remove(handle);
675 }
676 }
677}
678
679fn outbound_syn_flow(
683 frame: &[u8],
684 expected_guest_ip: Ipv4Addr,
685 gateway_ip: Ipv4Addr,
686) -> Option<OutboundFlow> {
687 let ethernet = EthernetFrame::new_checked(frame).ok()?;
688 if ethernet.dst_addr() != GATEWAY_MAC || ethernet.ethertype() != EthernetProtocol::Ipv4 {
689 return None;
690 }
691
692 let ipv4 = Ipv4Packet::new_checked(ethernet.payload()).ok()?;
693 if ipv4.next_header() != IpProtocol::Tcp {
694 return None;
695 }
696 let guest_ip = Ipv4Addr::from(ipv4.src_addr().0);
697 let remote_ip = Ipv4Addr::from(ipv4.dst_addr().0);
698 if guest_ip != expected_guest_ip
699 || remote_ip == gateway_ip
700 || remote_ip.is_unspecified()
701 || remote_ip.is_multicast()
702 || remote_ip == Ipv4Addr::BROADCAST
703 {
704 return None;
705 }
706
707 let tcp = TcpPacket::new_checked(ipv4.payload()).ok()?;
708 if !tcp.syn() || tcp.ack() || tcp.src_port() == 0 || tcp.dst_port() == 0 {
709 return None;
710 }
711
712 Some(OutboundFlow {
713 guest_ip,
714 guest_port: tcp.src_port(),
715 remote_ip,
716 remote_port: tcp.dst_port(),
717 })
718}
719
720fn spawn_outbound_connect(
721 flow: OutboundFlow,
722 connector_count: Arc<AtomicUsize>,
723) -> io::Result<Receiver<io::Result<TcpStream>>> {
724 let (sender, receiver) = mpsc::sync_channel(1);
725 connector_count.fetch_add(1, Ordering::Relaxed);
726 let thread_count = Arc::clone(&connector_count);
727 let spawn = std::thread::Builder::new()
728 .name("a3s-netproxy-connect".to_string())
729 .spawn(move || {
730 let address = SocketAddr::V4(SocketAddrV4::new(flow.remote_ip, flow.remote_port));
731 let result =
732 TcpStream::connect_timeout(&address, OUTBOUND_CONNECT_TIMEOUT).and_then(|stream| {
733 stream.set_nonblocking(true)?;
734 let _ = stream.set_nodelay(true);
735 Ok(stream)
736 });
737 let _ = sender.send(result);
738 thread_count.fetch_sub(1, Ordering::Relaxed);
739 });
740
741 match spawn {
742 Ok(_) => Ok(receiver),
743 Err(error) => {
744 connector_count.fetch_sub(1, Ordering::Relaxed);
745 Err(error)
746 }
747 }
748}
749
750fn proxy_tcp_connection(sockets: &mut SocketSet<'static>, connection: &mut TcpProxyConnection) {
755 let handle = connection.handle;
756 let socket = sockets.get_mut::<tcp::Socket>(handle);
757
758 let mut guest_to_host_bytes = 0usize;
759 let mut host_write_error = None;
760 if socket.can_recv() {
761 let _ = socket.recv(|data| match connection.host_stream.write(data) {
762 Ok(0) if !data.is_empty() => {
763 host_write_error = Some(io::Error::from(io::ErrorKind::WriteZero));
764 (0, ())
765 }
766 Ok(written) => {
767 guest_to_host_bytes = written;
768 (written, ())
769 }
770 Err(error)
771 if matches!(
772 error.kind(),
773 io::ErrorKind::WouldBlock | io::ErrorKind::Interrupted
774 ) =>
775 {
776 (0, ())
777 }
778 Err(error) => {
779 host_write_error = Some(error);
780 (0, ())
781 }
782 });
783 }
784 if guest_to_host_bytes > 0 {
785 tracing::trace!(
786 ?handle,
787 bytes = guest_to_host_bytes,
788 "NetProxy forwarded guest -> host bytes"
789 );
790 }
791 if let Some(error) = host_write_error {
792 tracing::debug!(%error, ?handle, "NetProxy host write failed");
793 let _ = connection.host_stream.shutdown(Shutdown::Both);
794 socket.abort();
795 connection.abort_pending = true;
796 return;
797 }
798
799 if !connection.guest_read_closed && !socket.may_recv() {
800 let _ = connection.host_stream.shutdown(Shutdown::Write);
801 connection.guest_read_closed = true;
802 }
803
804 let mut host_to_guest_bytes = 0usize;
805 let mut host_eof = false;
806 let mut host_read_error = None;
807 if !connection.host_read_closed && socket.can_send() {
808 let _ = socket.send(|buffer| match connection.host_stream.read(buffer) {
809 Ok(0) => {
810 host_eof = true;
811 (0, ())
812 }
813 Ok(read) => {
814 host_to_guest_bytes = read;
815 (read, ())
816 }
817 Err(error)
818 if matches!(
819 error.kind(),
820 io::ErrorKind::WouldBlock | io::ErrorKind::Interrupted
821 ) =>
822 {
823 (0, ())
824 }
825 Err(error) => {
826 host_read_error = Some(error);
827 (0, ())
828 }
829 });
830 }
831 if host_to_guest_bytes > 0 {
832 tracing::trace!(
833 ?handle,
834 bytes = host_to_guest_bytes,
835 "NetProxy forwarded host -> guest bytes"
836 );
837 }
838 if let Some(error) = host_read_error {
839 tracing::debug!(%error, ?handle, "NetProxy host read failed");
840 let _ = connection.host_stream.shutdown(Shutdown::Both);
841 socket.abort();
842 connection.abort_pending = true;
843 } else if host_eof {
844 tracing::debug!(?handle, "NetProxy host side closed its write half");
845 connection.host_read_closed = true;
846 socket.close();
847 }
848}