fix: Remote-Verbindungen – Reverse-Proxy-Support, Shutdown-Handling, Script-Stabilität
- X-Forwarded-Host/Proto Header für korrekte WebSocket-URL hinter Reverse-Proxy - CancellationToken aus IHostApplicationLifetime für sauberen App-Shutdown - WebSocket-Close mit 3s Timeout statt endlosem Warten - PowerShell-Script: Synchroner Empfangs-Loop (Send/Receive-Funktionen) - TLS 1.2 erzwungen für ältere Windows-Versionen - remote_execute Tool auch ohne Workspace-Pfad verfügbar (includeRemote) - Debug-Logs entfernt
This commit is contained in:
1 parent
10da2bd964
commit
0eb9022803
3 files changed
+91
-72
No files matched your search
+29
-16
@@ -12,6 +12,8 @@ using Gott.Services;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Gott.Endpoints;
|
||||
|
||||
@@ -40,13 +42,16 @@ public static class RemoteEndpoint
|
||||
return;
|
||||
}
|
||||
|
||||
// App-Shutdown-Token verwenden damit WebSocket-Loops sauber beendet werden
|
||||
var appLifetime = ctx.RequestServices.GetRequiredService<IHostApplicationLifetime>();
|
||||
var ct = appLifetime.ApplicationStopping;
|
||||
|
||||
using var ws = await ctx.WebSockets.AcceptWebSocketAsync();
|
||||
var conn = mgr.Register(ws);
|
||||
Console.WriteLine($"[Remote] Client connected: {conn.Id}");
|
||||
|
||||
try
|
||||
{
|
||||
await ReceiveLoop(ws, conn, mgr);
|
||||
await ReceiveLoop(ws, conn, mgr, ct);
|
||||
}
|
||||
catch (WebSocketException)
|
||||
{
|
||||
@@ -54,7 +59,7 @@ public static class RemoteEndpoint
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Shutdown
|
||||
// Shutdown oder Ctrl+C
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -63,25 +68,29 @@ public static class RemoteEndpoint
|
||||
|
||||
if (ws.State == WebSocketState.Open || ws.State == WebSocketState.CloseReceived)
|
||||
{
|
||||
try { await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None); }
|
||||
try
|
||||
{
|
||||
using var closeCts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
|
||||
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", closeCts.Token);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ReceiveLoop(WebSocket ws, RemoteConnection conn, RemoteConnectionManager mgr)
|
||||
private static async Task ReceiveLoop(WebSocket ws, RemoteConnection conn, RemoteConnectionManager mgr, CancellationToken ct)
|
||||
{
|
||||
var buffer = new byte[64 * 1024]; // 64 KB Buffer
|
||||
var messageBuffer = new MemoryStream();
|
||||
|
||||
while (ws.State == WebSocketState.Open)
|
||||
while (ws.State == WebSocketState.Open && !ct.IsCancellationRequested)
|
||||
{
|
||||
WebSocketReceiveResult result;
|
||||
messageBuffer.SetLength(0);
|
||||
|
||||
do
|
||||
{
|
||||
result = await ws.ReceiveAsync(buffer, CancellationToken.None);
|
||||
result = await ws.ReceiveAsync(buffer, ct);
|
||||
if (result.MessageType == WebSocketMessageType.Close) return;
|
||||
messageBuffer.Write(buffer, 0, result.Count);
|
||||
} while (!result.EndOfMessage);
|
||||
@@ -89,6 +98,7 @@ public static class RemoteEndpoint
|
||||
if (result.MessageType != WebSocketMessageType.Text) continue;
|
||||
|
||||
var json = Encoding.UTF8.GetString(messageBuffer.GetBuffer(), 0, (int)messageBuffer.Length);
|
||||
|
||||
JsonElement msg;
|
||||
try { msg = JsonSerializer.Deserialize<JsonElement>(json); }
|
||||
catch { continue; }
|
||||
@@ -98,15 +108,13 @@ public static class RemoteEndpoint
|
||||
switch (type)
|
||||
{
|
||||
case "hello":
|
||||
// Client meldet sich mit Hostname + OS-Info
|
||||
conn.Hostname = msg.TryGetProperty("hostname", out var h) ? h.GetString() ?? "" : "";
|
||||
conn.Username = msg.TryGetProperty("username", out var u) ? u.GetString() ?? "" : "";
|
||||
conn.OsVersion = msg.TryGetProperty("os", out var o) ? o.GetString() ?? "" : "";
|
||||
Console.WriteLine($"[Remote] Client identified: {conn.Id} = {conn.Username}@{conn.Hostname} ({conn.OsVersion})");
|
||||
Console.WriteLine($"[Remote] Client connected: {conn.Username}@{conn.Hostname} ({conn.OsVersion})");
|
||||
break;
|
||||
|
||||
case "result":
|
||||
// Ergebnis eines Befehls
|
||||
var cmdResult = new RemoteCommandResult
|
||||
{
|
||||
ExitCode = msg.TryGetProperty("exitCode", out var ec) ? ec.GetInt32() : -1,
|
||||
@@ -117,9 +125,8 @@ public static class RemoteEndpoint
|
||||
break;
|
||||
|
||||
case "ping":
|
||||
// Heartbeat – antworte mit pong
|
||||
var pong = Encoding.UTF8.GetBytes("{\"type\":\"pong\"}");
|
||||
await ws.SendAsync(pong, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
await ws.SendAsync(pong, WebSocketMessageType.Text, true, ct);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -180,10 +187,16 @@ public static class RemoteEndpoint
|
||||
if (!File.Exists(scriptPath))
|
||||
return Results.NotFound("Script nicht gefunden");
|
||||
|
||||
// Host aus dem Request ermitteln für die WebSocket-URL
|
||||
var host = ctx.Request.Host.ToString();
|
||||
var scheme = ctx.Request.Scheme == "https" ? "wss" : "ws";
|
||||
var wsUrl = $"{scheme}://{host}/api/remote";
|
||||
// Host aus X-Forwarded-Host (Reverse-Proxy) oder Request.Host
|
||||
var host = ctx.Request.Headers["X-Forwarded-Host"].FirstOrDefault()
|
||||
?? ctx.Request.Host.ToString();
|
||||
|
||||
// Schema aus X-Forwarded-Proto oder Request.Scheme
|
||||
var httpScheme = ctx.Request.Headers["X-Forwarded-Proto"].FirstOrDefault()
|
||||
?? ctx.Request.Scheme;
|
||||
var wsScheme = httpScheme == "https" ? "wss" : "ws";
|
||||
|
||||
var wsUrl = $"{wsScheme}://{host}/api/remote";
|
||||
|
||||
var script = await File.ReadAllTextAsync(scriptPath);
|
||||
script = script.Replace("{{WS_URL}}", wsUrl);
|
||||
|
||||
Reference in new issue
Block a user