feat(transport): Windows DSCP via qWAVE flows — PUNKTFUNK_DSCP now real on the wire there
Networking-audit deferred plan §4 (the qos.rs follow-up). On Windows set_tos_v4 succeeds but the stack strips the mark without a qWAVE flow, so PUNKTFUNK_DSCP=1 was a silent wire no-op there. Now (Apollo/Sunshine's approach): QOSCreateHandle once per process; QOSAddSocketToFlow per connected media socket — video → QOSTrafficTypeAudioVideo, audio → QOSTrafficTypeVoice (QOS_NON_ADAPTIVE_FLOW) — then best-effort QOSSetFlow(QOSSetOutgoingDSCPValue, 40/48) to pin the exact CS5/CS6 the other platforms mark. The pin lands for elevated processes (the host runs as the SYSTEM service — exactly where the video egress is) or under the "allow non-admin DSCP" policy; otherwise the traffic-type default marking stands (still WMM-useful). Gating + contract unchanged: opt-in via dscp_enabled(), every step debug-logs and continues. set_media_qos now returns an RAII QosFlow guard (QOSRemoveSocketFromFlow on drop) that must outlive the socket's traffic: stored in UdpTransport (declared before the socket, so drop order removes the flow first) and held for the stream's scope by the GameStream video/audio senders — whose tagging moved after connect(), since qWAVE derives the flow's 5-tuple from the connected socket (behavior-neutral on Linux). Off-Windows the guard is inert and never constructed. Validated: cargo check -p punktfunk-core --target x86_64-pc-windows-msvc green (the full host can't cross-check from Linux — aws-lc-sys needs MSVC tooling; it builds on-box via deploy-host.ps1). Remaining on the next Windows pass per plan: deploy to the RTX box and pktmon/Wireshark the client side — DSCP ≠ 0 on video egress with PUNKTFUNK_DSCP=1, 0 without. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -257,9 +257,9 @@ fn run(
|
||||
audio_cap: &std::sync::Mutex<Option<Box<dyn AudioCapturer>>>,
|
||||
) -> Result<()> {
|
||||
let sock = UdpSocket::bind(("0.0.0.0", AUDIO_PORT)).context("bind audio UDP")?;
|
||||
// Grow SO_SNDBUF/RCVBUF + opt-in DSCP/QoS-tag this as the audio class (PUNKTFUNK_DSCP=1).
|
||||
// Grow SO_SNDBUF/RCVBUF; the opt-in DSCP/QoS tag happens after connect below (Windows
|
||||
// qWAVE derives the flow from the connected 5-tuple).
|
||||
punktfunk_core::transport::grow_socket_buffers(&sock);
|
||||
punktfunk_core::transport::set_media_qos(&sock, punktfunk_core::transport::MediaClass::Audio);
|
||||
// The client pings the audio port (~every 500ms) so we learn where to send.
|
||||
sock.set_read_timeout(Some(Duration::from_secs(10)))?;
|
||||
tracing::info!(port = AUDIO_PORT, "audio: awaiting client ping");
|
||||
@@ -269,6 +269,10 @@ fn run(
|
||||
.context("audio: no client ping within 10s")?;
|
||||
sock.connect(client)
|
||||
.context("connect client audio endpoint")?;
|
||||
// Opt-in DSCP/QoS-tag this as the audio class (PUNKTFUNK_DSCP=1); the guard keeps the
|
||||
// Windows qWAVE flow alive for the whole stream (this function's scope IS the stream).
|
||||
let _qos_flow =
|
||||
punktfunk_core::transport::set_media_qos(&sock, punktfunk_core::transport::MediaClass::Audio);
|
||||
tracing::info!(%client, "audio: client endpoint learned");
|
||||
|
||||
// Reuse the persistent capturer when its channel count still matches (drain stale
|
||||
|
||||
@@ -95,10 +95,10 @@ fn run(
|
||||
encode::validate_dimensions(cfg.codec, cfg.width, cfg.height)
|
||||
.context("client-requested video mode")?;
|
||||
let sock = UdpSocket::bind(("0.0.0.0", VIDEO_PORT)).context("bind video UDP")?;
|
||||
// Grow SO_SNDBUF/RCVBUF (avoid host-side ENOBUFS at high bitrate) like the native plane, and
|
||||
// opt-in DSCP/QoS-tag this as the video class (PUNKTFUNK_DSCP=1).
|
||||
// Grow SO_SNDBUF/RCVBUF (avoid host-side ENOBUFS at high bitrate) like the native plane.
|
||||
// The opt-in DSCP/QoS tag happens after connect below (Windows qWAVE derives the flow from
|
||||
// the connected 5-tuple).
|
||||
punktfunk_core::transport::grow_socket_buffers(&sock);
|
||||
punktfunk_core::transport::set_media_qos(&sock, punktfunk_core::transport::MediaClass::Video);
|
||||
// The client pings the video port so we learn where to send; it re-pings until video
|
||||
// flows, so a missed early ping is fine.
|
||||
sock.set_read_timeout(Some(Duration::from_secs(10)))?;
|
||||
@@ -112,6 +112,10 @@ fn run(
|
||||
.context("video: no client ping within 10s")?;
|
||||
sock.connect(client)
|
||||
.context("connect client video endpoint")?;
|
||||
// Opt-in DSCP/QoS-tag this as the video class (PUNKTFUNK_DSCP=1); the guard keeps the
|
||||
// Windows qWAVE flow alive for the whole stream (this function's scope IS the stream).
|
||||
let _qos_flow =
|
||||
punktfunk_core::transport::set_media_qos(&sock, punktfunk_core::transport::MediaClass::Video);
|
||||
tracing::info!(%client, "video: client endpoint learned");
|
||||
// Short label for web-console stats captures: the client's peer IP.
|
||||
let client_label = client.ip().to_string();
|
||||
|
||||
Reference in New Issue
Block a user