Files
punktfunk/crates/punktfunk-core/src/transport/mod.rs
T
enricobuehler 1a559e8d5e feat(core): scale the receive path to the new multi-Gbps ceiling
- REPLAY_WINDOW 32768 -> 131072: the anti-replay bitmap covered the
  120 ms loss window only to ~2 Gbps; the client now delivers ~4.8 Gbps
  wire, where a late-but-valid Wi-Fi-retried datagram would have been
  dropped as 'older than the window' — false loss. 16 KiB/session
  covers ~12 Gbps.
- RECV_BATCH 32 -> 128: syscall rate stays ~3.4k/s at 430k pkt/s and
  each pump iteration drains the kernel buffer deeper (ring 64->256 KB,
  client sessions only). flush_backlog's iteration cap rescaled to keep
  its ~190 MB guard equivalent.
- PUNKTFUNK_GSO gate is now value-aware: '=0' used to ENABLE GSO on
  Linux (presence check) while disabling Windows USO. GSO stays OPT-IN,
  deliberately: A/B'd twice today — it cuts send-thread CPU ~30% but
  its 16-packet line-rate trains cost delivered throughput on a
  constrained fabric (2.5GbE-hop pair: peak 2453 -> 1908 Mbps and 0.4%
  loss at a rate sendmmsg carries clean). Flipping the default belongs
  with pace-aware chunk spacing (plan Phase 1.2/1.3). docs-site row
  corrected to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 19:22:40 +02:00

80 lines
4.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Pluggable packet I/O. The hot path calls [`Transport::send`] / [`Transport::recv`]
//! directly — no async runtime is involved.
mod loopback;
mod qos;
#[cfg(windows)]
mod qos_windows;
mod udp;
pub use loopback::{loopback_pair, LoopbackTransport};
pub use qos::{grow_socket_buffers, set_dscp_default, set_media_qos, MediaClass, QosFlow};
/// Windows-only: reusable USO (UDP Send Offload) batch send for callers that own their own connected
/// socket (the GameStream video sender) rather than going through [`UdpTransport`].
#[cfg(target_os = "windows")]
pub use udp::send_uso_all;
pub use udp::{spawn_data_punch, UdpTransport, PUNCH_MAGIC};
/// A datagram transport. `recv` is non-blocking: it returns `Ok(None)` when no packet
/// is currently available, so the caller (decode/present thread) never blocks here.
pub trait Transport: Send + Sync {
/// Send one packet. `Ok(true)` = handed to the kernel; `Ok(false)` = dropped locally because
/// the send buffer was momentarily full (WouldBlock) — a non-fatal loss the FEC/keyframe path
/// recovers, surfaced so the caller can count it (`packets_send_dropped`) instead of it being
/// invisible. `Err` = a real send failure.
fn send(&self, packet: &[u8]) -> std::io::Result<bool>;
/// Send a whole frame's packets in as few syscalls as possible, returning how many were
/// handed to the kernel (the caller counts `packets.len() - sent` as send-buffer drops). This
/// is the 1 Gbps+ lever: the [`UdpTransport`](super::UdpTransport) override uses `sendmmsg`
/// (~64 packets/syscall) instead of one `send` each — at ~125k pkt/s that is the difference
/// between ~2k and ~125k syscalls/sec. The default is the scalar `send` loop (correct for the
/// loopback transport and non-Linux builds). On a full send buffer it stops early and reports
/// the partial count rather than blocking — same lossy, FEC-protected contract as `send`.
fn send_batch(&self, packets: &[&[u8]]) -> std::io::Result<usize> {
let mut sent = 0;
for p in packets {
if self.send(p)? {
sent += 1;
}
}
Ok(sent)
}
/// Send a frame's equal-size packets using UDP Generic Segmentation Offload where available:
/// one `sendmsg` hands the kernel a big buffer it splits into `gso_size` UDP datagrams, building
/// ~1 GSO skb per ≤64 segments instead of one skb per packet. This is the multi-Gbps lever —
/// research shows ~2.4× throughput at equal CPU and ~40× fewer syscalls, and that `sendmmsg`
/// batching alone is insufficient (it still builds one skb per datagram). The
/// [`UdpTransport`](super::UdpTransport) Linux override implements it (opt-in via
/// `PUNKTFUNK_GSO=1` pending pace-aware chunk spacing — see the `gso` module doc — with
/// auto-fallback on any GSO error); the default just delegates to [`send_batch`](Self::send_batch),
/// correct for loopback and non-Linux. Same lossy, FEC-protected short-count contract as `send_batch`.
fn send_gso(&self, packets: &[&[u8]]) -> std::io::Result<usize> {
self.send_batch(packets)
}
fn recv(&self) -> std::io::Result<Option<Vec<u8>>>;
/// Receive up to `out.len()` datagrams in as few syscalls as possible, writing each into its
/// `out[i]` buffer (sized ≥ a max datagram) and its byte count into `lens[i]`; returns how many
/// arrived (`0` = none available; non-blocking). The recv counterpart of [`send_batch`]: the
/// [`UdpTransport`](super::UdpTransport) override uses `recvmmsg` into a caller-owned, reused
/// buffer ring — no per-packet allocation or syscall at line rate. The default does a single
/// scalar [`recv`](Self::recv) into `out[0]` (correct for the loopback transport + non-Linux).
fn recv_batch(&self, out: &mut [Vec<u8>], lens: &mut [usize]) -> std::io::Result<usize> {
if out.is_empty() {
return Ok(0);
}
match self.recv()? {
Some(pkt) => {
let n = pkt.len().min(out[0].len());
out[0][..n].copy_from_slice(&pkt[..n]);
lens[0] = n;
Ok(1)
}
None => Ok(0),
}
}
}