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:
2026-07-10 15:59:23 +02:00
parent e9b2eacf87
commit 9afcbcd307
7 changed files with 210 additions and 22 deletions
+18 -5
View File
@@ -446,6 +446,9 @@ pub fn spawn_data_punch(sock: UdpSocket, stop: std::sync::Arc<std::sync::atomic:
}
pub struct UdpTransport {
/// qWAVE flow guard (Windows, opt-in DSCP): declared before `socket` so drop order removes
/// the flow membership before the socket closes. Always `None` off-Windows.
_qos_flow: Option<super::qos::QosFlow>,
socket: UdpSocket,
}
@@ -464,10 +467,14 @@ impl UdpTransport {
socket.connect(peer)?;
super::qos::grow_socket_buffers(&socket);
// The native data plane is video-dominant — tag it as the video class (opt-in via
// PUNKTFUNK_DSCP). Each end marks its own egress.
super::qos::set_media_qos(&socket, super::qos::MediaClass::Video);
// PUNKTFUNK_DSCP). Each end marks its own egress; the socket is connected by now, as
// the Windows qWAVE flow requires.
let qos_flow = super::qos::set_media_qos(&socket, super::qos::MediaClass::Video);
socket.set_nonblocking(true)?;
Ok(UdpTransport { socket })
Ok(UdpTransport {
_qos_flow: qos_flow,
socket,
})
}
/// Host side of the data plane for clients that may sit behind NAT / a stateful inter-VLAN
@@ -524,9 +531,15 @@ impl UdpTransport {
socket.connect(target.as_deref().unwrap_or(fallback_peer))?;
socket.set_read_timeout(None)?;
super::qos::grow_socket_buffers(&socket);
super::qos::set_media_qos(&socket, super::qos::MediaClass::Video);
let qos_flow = super::qos::set_media_qos(&socket, super::qos::MediaClass::Video);
socket.set_nonblocking(true)?;
Ok((UdpTransport { socket }, punched))
Ok((
UdpTransport {
_qos_flow: qos_flow,
socket,
},
punched,
))
}
/// A second handle to the data socket, for sending hole-punch keepalives ([`PUNCH_MAGIC`])