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:
@@ -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`])
|
||||
|
||||
Reference in New Issue
Block a user