mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-05 15:41:23 +03:00
The send and recv arms shared one counter, and an ICMP error on a connected socket is reported once and then cleared — so the steady state is an alternation: the send succeeds and clears the counter, the next recv reports the error and finds the counter at 1, and logs. Every error still wrote a line, at the ~100/s the previous commit set out to stop, while the persistent-failure and recovery branches were unreachable. Use one LogThrottle per direction instead of a hand-rolled counter. That removes the shared state the bug lived in, drops a third throttling mechanism in favour of the one already added, and leaves the surrounding `if let Err` untouched rather than reshaping it into a match. Also fix test_udp_uat's socket-error arm, the untreated twin of the punch_udp site: it had no backoff at all, so a persistent error re-armed recv immediately and spun the loop at CPU speed, one warn line per iteration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ExUfAkYbq8UC9pQCiLy8TQ
193 lines
7.8 KiB
Rust
193 lines
7.8 KiB
Rust
use hbb_common::{
|
|
anyhow,
|
|
bytes::{Bytes, BytesMut},
|
|
bytes_codec::BytesCodec,
|
|
config, log,
|
|
tcp::{DynTcpStream, FramedStream},
|
|
tokio::{self, net::UdpSocket, sync::mpsc, sync::oneshot},
|
|
tokio_util, ResultType, Stream,
|
|
};
|
|
use kcp_sys::{
|
|
endpoint::KcpEndpoint,
|
|
packet_def::{KcpPacket, KcpPacketHeader},
|
|
stream,
|
|
};
|
|
use std::{net::SocketAddr, sync::Arc};
|
|
|
|
pub struct KcpStream {
|
|
_endpoint: KcpEndpoint,
|
|
stop_sender: Option<oneshot::Sender<()>>,
|
|
}
|
|
|
|
const KCP_IO_ERR_LOG_INTERVAL: std::time::Duration = std::time::Duration::from_secs(5);
|
|
static KCP_SEND_ERR_LOG: hbb_common::log_throttle::LogThrottle =
|
|
hbb_common::log_throttle::LogThrottle::new(KCP_IO_ERR_LOG_INTERVAL);
|
|
static KCP_RECV_ERR_LOG: hbb_common::log_throttle::LogThrottle =
|
|
hbb_common::log_throttle::LogThrottle::new(KCP_IO_ERR_LOG_INTERVAL);
|
|
|
|
impl KcpStream {
|
|
// Engage KCP's built-in congestion control (nc=0) unless disabled by option: pure turbo
|
|
// (nc=1) keeps blasting a full 1024-segment window through loss, which on constrained
|
|
// links amplifies brief loss into a spiral users experience as stalls or drops. This is
|
|
// sender-side only, so no wire negotiation is needed and either peer may run either
|
|
// profile. Requires kcp-sys from the `rustdesk-patches` branch, which wires the config
|
|
// factory into connection setup (on older revs the factory was stored but never consulted).
|
|
fn apply_kcp_config(endpoint: &mut KcpEndpoint) {
|
|
if crate::get_kcp_cc_enabled() {
|
|
endpoint.set_kcp_config_factory(Box::new(|conv| {
|
|
let mut config = kcp_sys::ffi_safe::KcpConfig::new_turbo(conv);
|
|
config.nc = Some(0);
|
|
config
|
|
}));
|
|
}
|
|
}
|
|
|
|
fn create_framed(stream: stream::KcpStream, local_addr: Option<SocketAddr>) -> Stream {
|
|
Stream::Tcp(FramedStream(
|
|
tokio_util::codec::Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
|
local_addr.unwrap_or(config::Config::get_any_listen_addr(true)),
|
|
None,
|
|
0,
|
|
))
|
|
}
|
|
|
|
pub async fn accept(
|
|
udp_socket: Arc<UdpSocket>,
|
|
timeout: std::time::Duration,
|
|
init_packet: Option<BytesMut>,
|
|
) -> ResultType<(Self, Stream)> {
|
|
let mut endpoint = KcpEndpoint::new();
|
|
Self::apply_kcp_config(&mut endpoint);
|
|
endpoint.run().await;
|
|
|
|
let (input, output) = (
|
|
endpoint.input_sender(),
|
|
endpoint
|
|
.output_receiver()
|
|
.ok_or_else(|| anyhow::anyhow!("Failed to get output receiver"))?,
|
|
);
|
|
let (stop_sender, stop_receiver) = oneshot::channel();
|
|
if let Some(packet) = init_packet {
|
|
if packet.len() >= std::mem::size_of::<KcpPacketHeader>() {
|
|
input.send(packet.into()).await?;
|
|
}
|
|
}
|
|
Self::kcp_io(udp_socket.clone(), input, output, stop_receiver).await;
|
|
|
|
let conn_id = tokio::time::timeout(timeout, endpoint.accept()).await??;
|
|
if let Some(stream) = stream::KcpStream::new(&endpoint, conn_id) {
|
|
Ok((
|
|
Self {
|
|
_endpoint: endpoint,
|
|
stop_sender: Some(stop_sender),
|
|
},
|
|
Self::create_framed(stream, udp_socket.local_addr().ok()),
|
|
))
|
|
} else {
|
|
Err(anyhow::anyhow!("Failed to create KcpStream"))
|
|
}
|
|
}
|
|
|
|
pub async fn connect(
|
|
udp_socket: Arc<UdpSocket>,
|
|
timeout: std::time::Duration,
|
|
) -> ResultType<(Self, Stream)> {
|
|
let mut endpoint = KcpEndpoint::new();
|
|
Self::apply_kcp_config(&mut endpoint);
|
|
endpoint.run().await;
|
|
|
|
let (input, output) = (
|
|
endpoint.input_sender(),
|
|
endpoint
|
|
.output_receiver()
|
|
.ok_or_else(|| anyhow::anyhow!("Failed to get output receiver"))?,
|
|
);
|
|
let (stop_sender, stop_receiver) = oneshot::channel();
|
|
Self::kcp_io(udp_socket.clone(), input, output, stop_receiver).await;
|
|
|
|
let conn_id = endpoint.connect(timeout, 0, 0, Bytes::new()).await?;
|
|
if let Some(stream) = stream::KcpStream::new(&endpoint, conn_id) {
|
|
Ok((
|
|
Self {
|
|
_endpoint: endpoint,
|
|
stop_sender: Some(stop_sender),
|
|
},
|
|
Self::create_framed(stream, udp_socket.local_addr().ok()),
|
|
))
|
|
} else {
|
|
Err(anyhow::anyhow!("Failed to create KcpStream"))
|
|
}
|
|
}
|
|
|
|
async fn kcp_io(
|
|
udp_socket: Arc<UdpSocket>,
|
|
input: mpsc::Sender<KcpPacket>,
|
|
mut output: mpsc::Receiver<KcpPacket>,
|
|
mut stop_receiver: oneshot::Receiver<()>,
|
|
) {
|
|
let udp = udp_socket.clone();
|
|
tokio::spawn(async move {
|
|
let mut buf = vec![0; 1500];
|
|
// A connected UDP socket surfaces ICMP port-unreachable as an error on
|
|
// send/recv (WSAECONNRESET 10054 on Windows, ECONNREFUSED on Linux). For UDP
|
|
// these are advisory: a stray ICMP from a NAT rebind glitch or a momentary
|
|
// peer hiccup does not mean the path is dead, and KCP retransmits through it.
|
|
// Treat socket errors as packet loss instead of tearing the session down;
|
|
// a truly dead link is reaped by the KCP pong timeout / app-level timeouts.
|
|
// The short sleep prevents a persistently failing socket from busy-spinning.
|
|
//
|
|
// These repeat every 10ms while the socket stays broken, so throttle the line —
|
|
// debug output is written to the log file. One throttle per direction: an ICMP
|
|
// error on a connected socket is reported once and cleared, so the steady state is
|
|
// an alternation (send succeeds, the next recv reports the error), and a shared
|
|
// counter would be reset by the succeeding direction on every cycle.
|
|
loop {
|
|
tokio::select! {
|
|
_ = &mut stop_receiver => {
|
|
log::debug!("KCP io loop received stop signal");
|
|
break;
|
|
}
|
|
Some(data) = output.recv() => {
|
|
if let Err(e) = udp.send(&data.inner()).await {
|
|
if let Some(n) = KCP_SEND_ERR_LOG.due() {
|
|
log::debug!("KCP send error x{n} (treated as loss), last: {e}");
|
|
}
|
|
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
|
}
|
|
}
|
|
result = udp.recv_from(&mut buf) => {
|
|
match result {
|
|
Ok((size, _)) => {
|
|
if size < std::mem::size_of::<KcpPacketHeader>() {
|
|
continue;
|
|
}
|
|
input
|
|
.send(BytesMut::from(&buf[..size]).into())
|
|
.await.ok();
|
|
}
|
|
Err(e) => {
|
|
if let Some(n) = KCP_RECV_ERR_LOG.due() {
|
|
log::debug!("KCP recv error x{n} (treated as loss), last: {e}");
|
|
}
|
|
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
|
}
|
|
}
|
|
}
|
|
else => {
|
|
log::debug!("KCP endpoint input closed");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
impl Drop for KcpStream {
|
|
fn drop(&mut self) {
|
|
if let Some(sender) = self.stop_sender.take() {
|
|
let _ = sender.send(());
|
|
}
|
|
}
|
|
}
|