diff --git a/Data/Agent/Roles/ReverseTunnel/tunnel_Powershell.py b/Data/Agent/Roles/ReverseTunnel/tunnel_Powershell.py index 97cbd4d0..db516fb1 100644 --- a/Data/Agent/Roles/ReverseTunnel/tunnel_Powershell.py +++ b/Data/Agent/Roles/ReverseTunnel/tunnel_Powershell.py @@ -179,5 +179,8 @@ class PowershellChannel: pass # Include exit code in the close reason for debugging. exit_suffix = f" (exit={self._exit_code})" if self._exit_code is not None else "" - await self._send_close(code, (reason or "powershell_exit") + exit_suffix) - self.role._log(f"reverse_tunnel ps channel stopped channel={self.channel_id} reason={reason or 'exit'}") + close_reason = (reason or "powershell_exit") + exit_suffix + await self._send_close(code, close_reason) + self.role._log( + f"reverse_tunnel ps channel stopped channel={self.channel_id} reason={close_reason}" + ) diff --git a/Data/Agent/Roles/role_ReverseTunnel.py b/Data/Agent/Roles/role_ReverseTunnel.py index bd0ceb97..7ca61a20 100644 --- a/Data/Agent/Roles/role_ReverseTunnel.py +++ b/Data/Agent/Roles/role_ReverseTunnel.py @@ -521,6 +521,14 @@ class Role: self._log(f"reverse_tunnel connection failed tunnel_id={tunnel.tunnel_id}: {exc}", error=True) await self._emit_status({"tunnel_id": tunnel.tunnel_id, "agent_id": self.ctx.agent_id, "status": "error", "reason": "connect_failed"}) finally: + try: + ws = tunnel.websocket + self._log( + f"reverse_tunnel ws closing tunnel_id={tunnel.tunnel_id} " + f"code={getattr(ws, 'close_code', None)} reason={getattr(ws, 'close_reason', None)}" + ) + except Exception: + pass await self._shutdown_tunnel(tunnel) async def _pump_sender(self, tunnel: ActiveTunnel) -> None: @@ -562,6 +570,7 @@ class Role: f"reverse_tunnel websocket closed tunnel_id={tunnel.tunnel_id} " f"code={ws.close_code} reason={ws.close_reason}" ) + tunnel.stop_reason = ws.close_reason or "ws_closed" break except asyncio.CancelledError: pass diff --git a/Data/Engine/web-interface/src/Devices/ReverseTunnel/Powershell.jsx b/Data/Engine/web-interface/src/Devices/ReverseTunnel/Powershell.jsx index c798c7f7..97f455ec 100644 --- a/Data/Engine/web-interface/src/Devices/ReverseTunnel/Powershell.jsx +++ b/Data/Engine/web-interface/src/Devices/ReverseTunnel/Powershell.jsx @@ -241,8 +241,13 @@ export default function ReverseTunnelPowershell({ device }) { setPolling(true); pollTimerRef.current = setTimeout(async () => { const resp = await emitAsync(socket, "ps_poll", {}); - if (resp?.error) { - // Suppress warming/errors in UI; rely on session chips. + if (resp?.error) { + stopPolling(); + disconnectSocket(); + setPsStatus({}); + setTunnel(null); + setSessionState("error"); + return; } if (Array.isArray(resp?.output) && resp.output.length) { appendOutput(resp.output.join("")); @@ -251,8 +256,7 @@ export default function ReverseTunnelPowershell({ device }) { setPsStatus(resp.status); if (resp.status.closed) { setSessionState("closed"); - setStatusSeverity("warning"); - setStatusMessage(resp.status.close_reason || "Session closed"); + setTunnel(null); stopPolling(); return; } @@ -263,7 +267,7 @@ export default function ReverseTunnelPowershell({ device }) { pollLoop(socket, tunnelId); }, 520); }, - [appendOutput, emitAsync, stopPolling] + [appendOutput, emitAsync, stopPolling, disconnectSocket] ); const handleDisconnect = useCallback(() => { @@ -280,9 +284,8 @@ export default function ReverseTunnelPowershell({ device }) { } stopPolling(); disconnectSocket(); + setTunnel(null); setSessionState("closed"); - setStatusSeverity("info"); - setStatusMessage("Session closed by operator."); }, [disconnectSocket, stopPolling, tunnel?.tunnel_id]); const handleResize = useCallback(() => { @@ -328,6 +331,8 @@ export default function ReverseTunnelPowershell({ device }) { socket.on("connect_error", () => { setStatusSeverity("warning"); setStatusMessage("Tunnel namespace unavailable."); + setTunnel(null); + setSessionState("error"); }); socket.on("disconnect", () => { @@ -336,6 +341,7 @@ export default function ReverseTunnelPowershell({ device }) { setSessionState("disconnected"); setStatusSeverity("warning"); setStatusMessage("Socket disconnected."); + setTunnel(null); } });