fix(link): ride out short path blackouts: 30s handshake, 180s idle limit, 90s keepalive, faster probe reconnect [CI]
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline failed
ci/woodpecker/push/dev-image unknown status

This commit is contained in:
justme committed 2026-10-09 13:46:59 +08:00
1 parent be04045c8c
commit 0873acf66a
3 files changed
+20 -9

No files matched your search

@@ -78,8 +78,9 @@ public sealed class PeerHub
{ {
private const int DedupeWindow = 1024; private const int DedupeWindow = 1024;
/// <summary>超过这么久没有收到 Probe 的任何帧就认为会话已死(Probe 每 30 秒至少发一次时钟同步)。</summary> /// <summary>超过这么久没有收到 Probe 的任何帧就认为会话已死(Probe 每 30 秒至少发一次时钟同步)。
private static readonly TimeSpan IdleLimit = TimeSpan.FromSeconds(75); /// 要比线路可能的中断长得多:差的线路上几十秒甚至一两分钟的黑洞时有发生(TCP RTT 能到 30 秒),75 秒就关会话会把一次中断变成几分钟的数据空白(要重新握手,而握手在差线路上也慢)。</summary>
private static readonly TimeSpan IdleLimit = TimeSpan.FromSeconds(180);
private readonly RpcRouter<PeerContext> _router; private readonly RpcRouter<PeerContext> _router;
private readonly SessionRegistry _sessions; private readonly SessionRegistry _sessions;
+15 -6
View File
@@ -29,7 +29,8 @@ public sealed class PeerListenerOptions
public int MaxSessions { get; init; } = 8; public int MaxSessions { get; init; } = 8;
public TimeSpan HandshakeTimeout { get; init; } = TimeSpan.FromSeconds(10); /// <summary>敲门、TLS、挑战应答整个握手的上限。线路差的时候(RTT 好几秒、还丢包)握手本来就要很久,10 秒做不完就会一直连不上;敲门已经挡住了不认识的连接,放宽不会放大被占连接的风险。</summary>
public TimeSpan HandshakeTimeout { get; init; } = PeerTimings.Handshake;
} }
/// <summary> /// <summary>
@@ -209,7 +210,8 @@ public sealed class PeerListener
} }
/// <summary>发起点对点会话(Probe 一侧):按 SPKI 指纹固定对端证书,用身份私钥签名,派生同样的数据面凭据。</summary> /// <summary>发起点对点会话(Probe 一侧):按 SPKI 指纹固定对端证书,用身份私钥签名,派生同样的数据面凭据。</summary>
/// <summary>点对点会话的套接字:开启 TCP keepalive(空闲 15 秒后每 5 秒探测,连续 3 次无响应就断开,约 30 秒),主机丢失或路径被切断时连接不会一直挂着。</summary> /// <summary>点对点会话的套接字:开启 TCP keepalive(空闲 30 秒后每 10 秒探测,连续 6 次无响应才断开,约 90 秒),主机真的没了的时候连接不会一直挂着;
/// 但线路短暂中断(几十秒的黑洞)不能把会话杀掉——线路中断本来就是要测的东西,会话得扛过去,恢复之后接着测。</summary>
internal static class PeerSockets internal static class PeerSockets
{ {
public static void EnableKeepAlive(Socket socket) public static void EnableKeepAlive(Socket socket)
@@ -217,9 +219,9 @@ internal static class PeerSockets
try try
{ {
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 15); socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 30);
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 5); socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 10);
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 3); socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 6);
} }
catch (Exception ex) when (ex is SocketException or ArgumentException or PlatformNotSupportedException) catch (Exception ex) when (ex is SocketException or ArgumentException or PlatformNotSupportedException)
{ {
@@ -228,6 +230,13 @@ internal static class PeerSockets
} }
} }
/// <summary>点对点会话的时间常数,Probe 和 Agent 两侧共用。</summary>
public static class PeerTimings
{
/// <summary>建立会话(连接、敲门、TLS、挑战应答)的总时限。</summary>
public static readonly TimeSpan Handshake = TimeSpan.FromSeconds(30);
}
public static class PeerConnector public static class PeerConnector
{ {
public static async Task<(PeerSessionInfo Info, TlsFrameTransport Transport)> ConnectAsync( public static async Task<(PeerSessionInfo Info, TlsFrameTransport Transport)> ConnectAsync(
@@ -240,7 +249,7 @@ public static class PeerConnector
PeerSockets.EnableKeepAlive(client.Client); PeerSockets.EnableKeepAlive(client.Client);
try 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); using CancellationTokenSource linked = CancellationTokenSource.CreateLinkedTokenSource(ct, timeout.Token);
await client.ConnectAsync(host, port, linked.Token); await client.ConnectAsync(host, port, linked.Token);
Stream stream = client.GetStream(); Stream stream = client.GetStream();
@@ -47,7 +47,8 @@ public sealed class AgentConnection
public async Task RunAsync(CancellationToken ct) 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) while (!ct.IsCancellationRequested)
{ {
try try