fix(nodes): locate agents behind a proxy and retry missing countries [CI]
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline was successful
ci/woodpecker/push/dev-image Pipeline was successful

This commit is contained in:
justme committed 2026-10-18 00:06:39 +08:00
1 parent 1145a53004
commit 0d532caf08
13 files changed
+93 -15

No files matched your search

@@ -6,6 +6,17 @@ namespace Fleet.Server;
public sealed record EnrollmentContext(string? RemoteIp, JsonElement? ProductMeta);
/// <summary>
/// 节点的真实来源地址:前面有反向代理时,套接字上只有代理的地址,产品在这里决定信不信转发头。
/// 没有注册就用套接字地址。
/// </summary>
public interface IClientAddressSource
{
/// <param name="socket">套接字上看到的对端地址。</param>
/// <param name="header">按名字取请求头的所有值。</param>
string? Resolve(System.Net.IPAddress? socket, Func<string, IEnumerable<string>> header);
}
/// <summary>
/// 服务器的身份密钥:操作员用 passkey 把它的公钥签进操作员集合。有它的时候,认证挑战会带上服务器的签名,节点据此确认对面是真的服务器,
/// 并在这条链路上用应用层加密。没有注册就是旧的行为(没有服务器身份)。
@@ -38,7 +49,8 @@ public enum NodeEventKind
Heartbeat,
}
public sealed record NodeEvent(NodeEventKind Kind, NodeRecord Node, long AtMs, HelloParams? Hello = null, JsonElement? Extra = null, string? Reason = null);
/// <param name="RemoteIp">这条连接的来源地址(经过 <see cref="IClientAddressSource"/>);没有就是 null。</param>
public sealed record NodeEvent(NodeEventKind Kind, NodeRecord Node, long AtMs, HelloParams? Hello = null, JsonElement? Extra = null, string? Reason = null, string? RemoteIp = null);
public interface INodeEventSink
{
@@ -73,7 +73,7 @@ public static class FleetEnrollmentEndpoints
return Problem(StatusCodes.Status400BadRequest, FleetErrors.InvalidParams, "request body is not valid");
}
Result<EnrollResponse> result = await service.EnrollAsync(request, http.Connection.RemoteIpAddress?.ToString(), http.RequestAborted);
Result<EnrollResponse> result = await service.EnrollAsync(request, ClientIp(http), http.RequestAborted);
if (result.TryGet(out EnrollResponse? response, out FleetError? error))
{
return Results.Json(response, EnrollmentJsonContext.Default.EnrollResponse, statusCode: StatusCodes.Status201Created);
@@ -88,6 +88,11 @@ public static class FleetEnrollmentEndpoints
return Problem(status, error.Code, error.Message);
}
private static string? ClientIp(HttpContext http) =>
http.RequestServices.GetService<IClientAddressSource>() is { } source
? source.Resolve(http.Connection.RemoteIpAddress, name => http.Request.Headers[name].Select(v => v ?? ""))
: http.Connection.RemoteIpAddress?.ToString();
private static async Task<EnrollRequest?> ReadBodyAsync(HttpContext http, CancellationToken ct)
{
if (http.Request.ContentLength > MaxBodyBytes)
@@ -67,9 +67,9 @@ public sealed class FleetLinkHost : IFleetConnections, IStateNotifier
_connections.TryGetValue(node, out NodeConnection? c) ? c.Session : null;
/// <summary>处理一条 WebSocket 连接,直到连接结束。</summary>
public async Task RunConnectionAsync(WebSocket socket, CancellationToken ct)
public async Task RunConnectionAsync(WebSocket socket, string? remoteIp, CancellationToken ct)
{
using var connection = new NodeConnection(this, socket);
using var connection = new NodeConnection(this, socket, remoteIp);
await connection.RunAsync(ct);
}
@@ -128,7 +128,10 @@ public static class FleetServerExtensions
}
using System.Net.WebSockets.WebSocket socket = await http.WebSockets.AcceptWebSocketAsync();
await http.RequestServices.GetRequiredService<FleetLinkHost>().RunConnectionAsync(socket, http.RequestAborted);
string? remote = http.RequestServices.GetService<IClientAddressSource>() is { } source
? source.Resolve(http.Connection.RemoteIpAddress, name => http.Request.Headers[name].Select(v => v ?? ""))
: http.Connection.RemoteIpAddress?.ToString();
await http.RequestServices.GetRequiredService<FleetLinkHost>().RunConnectionAsync(socket, remote, http.RequestAborted);
});
return app;
}
@@ -102,7 +102,7 @@ internal sealed partial class NodeConnection
private async Task FireAsync(NodeEventKind kind, CancellationToken ct, HelloParams? hello = null, JsonElement? extra = null)
{
var nodeEvent = new NodeEvent(kind, _node!, _host.Time.GetUtcNow().ToUnixTimeMilliseconds(), hello, extra, kind == NodeEventKind.Offline ? _closeReason : null);
var nodeEvent = new NodeEvent(kind, _node!, _host.Time.GetUtcNow().ToUnixTimeMilliseconds(), hello, extra, kind == NodeEventKind.Offline ? _closeReason : null, _remoteIp);
foreach (INodeEventSink sink in _host.Sinks)
{
try
@@ -34,10 +34,13 @@ internal sealed partial class NodeConnection : IDisposable
private bool _helloDone;
private int _violations;
public NodeConnection(FleetLinkHost host, WebSocket socket)
private readonly string? _remoteIp;
public NodeConnection(FleetLinkHost host, WebSocket socket, string? remoteIp = null)
{
_host = host;
_socket = socket;
_remoteIp = remoteIp;
}
public NodeId NodeId => _node!.Id;