use std::collections::VecDeque;
use std::io;
use std::net::Ipv4Addr;
use std::os::fd::RawFd;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc;
use crate::darwin::egress::HostEgress;
use crate::darwin::inbound_relay::InboundCommand;
use crate::darwin::tcp_bridge::TcpBridge;
use crate::datapath::FrameBuf;
use crate::direct_rx::FrameSink;
use super::fd::fd_write;
use super::intercept::process_inbound_cmd;
pub(super) const LOSSY_QUEUE_CAP: usize = 1024;
pub(super) const NOBUFS_RETRY_DELAY: Duration = Duration::from_millis(1);
const DRAIN_REPLY_BATCH: usize = 64;
const DRAIN_CMD_BATCH: usize = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum DeliveryClass {
Reliable,
Lossy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum WriteBlock {
WouldBlock,
NoBufs,
}
#[derive(Debug, Default)]
pub(super) struct GuestTxStats {
pub lossy_dropped: u64,
pub enobufs_events: u64,
pub would_block_events: u64,
pub short_writes: u64,
pub io_errors: u64,
pub sink_send_failures: u64,
pub gated_polls: u64,
pub queue_high_water: usize,
pub tx_frames: u64,
}
enum WriteOutcome {
Consumed,
Blocked(WriteBlock),
}
pub(super) struct GuestTx {
frame_sink: Option<Arc<dyn FrameSink>>,
queue: VecDeque<FrameBuf>,
blocked: Option<WriteBlock>,
pub(super) stats: GuestTxStats,
}
impl GuestTx {
pub(super) fn new(frame_sink: Option<Arc<dyn FrameSink>>) -> Self {
Self {
frame_sink,
queue: VecDeque::new(),
blocked: None,
stats: GuestTxStats::default(),
}
}
pub(super) fn has_backlog(&self) -> bool {
!self.queue.is_empty()
}
pub(super) fn awaits_writable(&self) -> bool {
self.has_backlog() && self.blocked == Some(WriteBlock::WouldBlock)
}
pub(super) fn awaits_retry(&self) -> bool {
self.has_backlog()
}
pub(super) fn send(&mut self, guest_fd: RawFd, frame: &[u8], class: DeliveryClass) {
if let Some(sink) = &self.frame_sink {
if sink.send(frame.to_vec()) {
self.stats.tx_frames += 1;
} else {
self.stats.sink_send_failures += 1;
tracing::debug!("Guest frame sink full, frame dropped ({class:?})");
}
return;
}
if self.queue.is_empty() {
match self.try_write(guest_fd, frame) {
WriteOutcome::Consumed => {}
WriteOutcome::Blocked(block) => {
self.blocked = Some(block);
self.push(frame);
}
}
return;
}
if class == DeliveryClass::Lossy && self.queue.len() >= LOSSY_QUEUE_CAP {
self.stats.lossy_dropped += 1;
tracing::debug!("Lossy queue cap ({LOSSY_QUEUE_CAP}) reached, dropping frame");
return;
}
self.push(frame);
}
pub(super) fn drain(&mut self, guest_fd: RawFd) {
self.blocked = None;
while let Some(frame) = self.queue.pop_front() {
match self.try_write(guest_fd, &frame) {
WriteOutcome::Consumed => {}
WriteOutcome::Blocked(block) => {
self.blocked = Some(block);
self.queue.push_front(frame);
break;
}
}
}
}
fn try_write(&mut self, guest_fd: RawFd, frame: &[u8]) -> WriteOutcome {
match fd_write(guest_fd, frame) {
Ok(n) if n >= frame.len() => {
self.stats.tx_frames += 1;
WriteOutcome::Consumed
}
Ok(n) => {
self.stats.short_writes += 1;
tracing::error!(
"Guest write: short datagram ({n}/{} bytes), dropping frame",
frame.len(),
);
WriteOutcome::Consumed
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
self.stats.would_block_events += 1;
WriteOutcome::Blocked(WriteBlock::WouldBlock)
}
Err(e) if e.raw_os_error() == Some(libc::ENOBUFS) => {
self.stats.enobufs_events += 1;
WriteOutcome::Blocked(WriteBlock::NoBufs)
}
Err(e) => {
self.stats.io_errors += 1;
tracing::warn!("Guest write error: {e}");
WriteOutcome::Consumed
}
}
}
fn push(&mut self, frame: &[u8]) {
self.queue.push_back(FrameBuf::from(frame.to_vec()));
self.stats.queue_high_water = self.stats.queue_high_water.max(self.queue.len());
}
pub(super) fn log_stats(&self, rx_frames: u64) {
let s = &self.stats;
tracing::info!(
queue_len = self.queue.len(),
blocked = ?self.blocked,
tx_frames = s.tx_frames,
rx_frames,
enobufs = s.enobufs_events,
would_block = s.would_block_events,
lossy_dropped = s.lossy_dropped,
short_writes = s.short_writes,
io_errors = s.io_errors,
sink_send_failures = s.sink_send_failures,
gated_polls = s.gated_polls,
queue_high_water = s.queue_high_water,
"guest-tx delivery counters"
);
}
}
pub(super) fn drain_reply_rx(
reply_rx: &mut mpsc::Receiver<Vec<u8>>,
guest_tx: &mut GuestTx,
guest_fd: RawFd,
) {
for _ in 0..DRAIN_REPLY_BATCH {
match reply_rx.try_recv() {
Ok(reply_frame) => {
guest_tx.send(guest_fd, &reply_frame, DeliveryClass::Lossy);
}
Err(_) => break,
}
}
}
pub(super) fn drain_cmd_rx(
cmd_rx: &mut mpsc::Receiver<InboundCommand>,
tcp_bridge: &mut TcpBridge,
egress: &mut HostEgress,
guest_ip: Ipv4Addr,
gateway_ip: Ipv4Addr,
guest_mac: Option<[u8; 6]>,
) {
for _ in 0..DRAIN_CMD_BATCH {
match cmd_rx.try_recv() {
Ok(cmd) => {
process_inbound_cmd(cmd, tcp_bridge, egress, guest_ip, gateway_ip, guest_mac);
}
Err(_) => break,
}
}
}