From 0873acf66af19c1ab21926f66b23e9791435793f Mon Sep 17 00:00:00 2001 From: JustMe Date: Fri, 9 Oct 2026 13:46:59 +0800 Subject: [PATCH] fix(link): ride out short path blackouts: 30s handshake, 180s idle limit, 90s keepalive, faster probe reconnect [CI] --- .../agent/src/MoShiro.Agent/Peer/PeerHub.cs | 5 +++-- backend/fleet/src/Fleet.Link/Peer.cs | 21 +++++++++++++------ .../src/MoShiro.Probe/Link/AgentConnection.cs | 3 ++- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/backend/agent/src/MoShiro.Agent/Peer/PeerHub.cs b/backend/agent/src/MoShiro.Agent/Peer/PeerHub.cs index 69812ac..67769a9 100644 --- a/backend/agent/src/MoShiro.Agent/Peer/PeerHub.cs +++ b/backend/agent/src/MoShiro.Agent/Peer/PeerHub.cs @@ -78,8 +78,9 @@ public sealed class PeerHub { private const int DedupeWindow = 1024; - /// 超过这么久没有收到 Probe 的任何帧就认为会话已死(Probe 每 30 秒至少发一次时钟同步)。 - private static readonly TimeSpan IdleLimit = TimeSpan.FromSeconds(75); + /// 超过这么久没有收到 Probe 的任何帧就认为会话已死(Probe 每 30 秒至少发一次时钟同步)。 + /// 要比线路可能的中断长得多:差的线路上几十秒甚至一两分钟的黑洞时有发生(TCP RTT 能到 30 秒),75 秒就关会话会把一次中断变成几分钟的数据空白(要重新握手,而握手在差线路上也慢)。 + private static readonly TimeSpan IdleLimit = TimeSpan.FromSeconds(180); private readonly RpcRouter _router; private readonly SessionRegistry _sessions; diff --git a/backend/fleet/src/Fleet.Link/Peer.cs b/backend/fleet/src/Fleet.Link/Peer.cs index ba94a2b..7a20f7a 100644 --- a/backend/fleet/src/Fleet.Link/Peer.cs +++ b/backend/fleet/src/Fleet.Link/Peer.cs @@ -29,7 +29,8 @@ public sealed class PeerListenerOptions public int MaxSessions { get; init; } = 8; - public TimeSpan HandshakeTimeout { get; init; } = TimeSpan.FromSeconds(10); + /// 敲门、TLS、挑战应答整个握手的上限。线路差的时候(RTT 好几秒、还丢包)握手本来就要很久,10 秒做不完就会一直连不上;敲门已经挡住了不认识的连接,放宽不会放大被占连接的风险。 + public TimeSpan HandshakeTimeout { get; init; } = PeerTimings.Handshake; } /// @@ -209,7 +210,8 @@ public sealed class PeerListener } /// 发起点对点会话(Probe 一侧):按 SPKI 指纹固定对端证书,用身份私钥签名,派生同样的数据面凭据。 -/// 点对点会话的套接字:开启 TCP keepalive(空闲 15 秒后每 5 秒探测,连续 3 次无响应就断开,约 30 秒),主机丢失或路径被切断时连接不会一直挂着。 +/// 点对点会话的套接字:开启 TCP keepalive(空闲 30 秒后每 10 秒探测,连续 6 次无响应才断开,约 90 秒),主机真的没了的时候连接不会一直挂着; +/// 但线路短暂中断(几十秒的黑洞)不能把会话杀掉——线路中断本来就是要测的东西,会话得扛过去,恢复之后接着测。 internal static class PeerSockets { public static void EnableKeepAlive(Socket socket) @@ -217,9 +219,9 @@ internal static class PeerSockets try { socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); - socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 15); - socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 5); - socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 3); + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 30); + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 10); + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 6); } catch (Exception ex) when (ex is SocketException or ArgumentException or PlatformNotSupportedException) { @@ -228,6 +230,13 @@ internal static class PeerSockets } } +/// 点对点会话的时间常数,Probe 和 Agent 两侧共用。 +public static class PeerTimings +{ + /// 建立会话(连接、敲门、TLS、挑战应答)的总时限。 + public static readonly TimeSpan Handshake = TimeSpan.FromSeconds(30); +} + public static class PeerConnector { public static async Task<(PeerSessionInfo Info, TlsFrameTransport Transport)> ConnectAsync( @@ -240,7 +249,7 @@ public static class PeerConnector PeerSockets.EnableKeepAlive(client.Client); try { - using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10), time); + using var timeout = new CancellationTokenSource(PeerTimings.Handshake, time); using CancellationTokenSource linked = CancellationTokenSource.CreateLinkedTokenSource(ct, timeout.Token); await client.ConnectAsync(host, port, linked.Token); Stream stream = client.GetStream(); diff --git a/backend/probe/src/MoShiro.Probe/Link/AgentConnection.cs b/backend/probe/src/MoShiro.Probe/Link/AgentConnection.cs index dcc3c74..43ede3e 100644 --- a/backend/probe/src/MoShiro.Probe/Link/AgentConnection.cs +++ b/backend/probe/src/MoShiro.Probe/Link/AgentConnection.cs @@ -47,7 +47,8 @@ public sealed class AgentConnection public async Task RunAsync(CancellationToken ct) { - var backoff = new Backoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(20)); + // 重连的间隔别拉太长:每次尝试本来就要等好几秒(握手上限 30 秒),线路一恢复要尽快接上;带抖动,不会和别的节点撞在一起 + var backoff = new Backoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(8)); while (!ct.IsCancellationRequested) { try