mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 04:43:36 +00:00
Fix status checks through jump hosts (#1045)
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import type { Client } from "ssh2";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import {
|
||||
supportsMetrics,
|
||||
isTcpPingEnabled,
|
||||
createConnectionLog,
|
||||
tcpPingThroughJumpHost,
|
||||
} from "./host-metrics-helpers.js";
|
||||
|
||||
describe("supportsMetrics", () => {
|
||||
@@ -66,3 +68,57 @@ describe("createConnectionLog", () => {
|
||||
expect("timestamp" in entry).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("tcpPingThroughJumpHost", () => {
|
||||
it("reports the final destination online when forwarding succeeds", async () => {
|
||||
const stream = { destroy: vi.fn() };
|
||||
const jumpClient = {
|
||||
end: vi.fn(),
|
||||
forwardOut: vi.fn((_src, _srcPort, host, port, callback) => {
|
||||
expect(host).toBe("private.example");
|
||||
expect(port).toBe(22);
|
||||
callback(undefined, stream);
|
||||
}),
|
||||
} as unknown as Pick<Client, "forwardOut" | "end">;
|
||||
|
||||
await expect(
|
||||
tcpPingThroughJumpHost(jumpClient, "private.example", 22),
|
||||
).resolves.toBe(true);
|
||||
expect(stream.destroy).toHaveBeenCalledOnce();
|
||||
expect(jumpClient.end).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("reports the final destination offline when forwarding fails", async () => {
|
||||
const jumpClient = {
|
||||
end: vi.fn(),
|
||||
forwardOut: vi.fn((_src, _srcPort, _host, _port, callback) => {
|
||||
callback(new Error("Connection refused"));
|
||||
}),
|
||||
} as unknown as Pick<Client, "forwardOut" | "end">;
|
||||
|
||||
await expect(
|
||||
tcpPingThroughJumpHost(jumpClient, "private.example", 22),
|
||||
).resolves.toBe(false);
|
||||
expect(jumpClient.end).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("reports the final destination offline when forwarding times out", async () => {
|
||||
vi.useFakeTimers();
|
||||
const jumpClient = {
|
||||
end: vi.fn(),
|
||||
forwardOut: vi.fn(),
|
||||
} as unknown as Pick<Client, "forwardOut" | "end">;
|
||||
|
||||
const result = tcpPingThroughJumpHost(
|
||||
jumpClient,
|
||||
"private.example",
|
||||
22,
|
||||
5000,
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(5000);
|
||||
|
||||
await expect(result).resolves.toBe(false);
|
||||
expect(jumpClient.end).toHaveBeenCalledOnce();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { LogEntry, ConnectionStage } from "../../types/connection-log.js";
|
||||
import type { Client } from "ssh2";
|
||||
|
||||
export type StatsCapableHost = {
|
||||
connectionType?: string;
|
||||
@@ -21,6 +22,32 @@ export function isTcpPingEnabled(statsConfig: TcpPingStatsConfig): boolean {
|
||||
return statsConfig.statusCheckEnabled && !statsConfig.disableTcpPing;
|
||||
}
|
||||
|
||||
export function tcpPingThroughJumpHost(
|
||||
jumpClient: Pick<Client, "forwardOut" | "end">,
|
||||
host: string,
|
||||
port: number,
|
||||
timeoutMs = 5000,
|
||||
): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
|
||||
const finish = (result: boolean) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
clearTimeout(timeout);
|
||||
jumpClient.end();
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
const timeout = setTimeout(() => finish(false), timeoutMs);
|
||||
|
||||
jumpClient.forwardOut("127.0.0.1", 0, host, port, (error, stream) => {
|
||||
stream?.destroy();
|
||||
finish(!error && !!stream);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function createConnectionLog(
|
||||
type: "info" | "success" | "warning" | "error",
|
||||
stage: ConnectionStage,
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
createConnectionLog,
|
||||
isTcpPingEnabled,
|
||||
supportsMetrics,
|
||||
tcpPingThroughJumpHost,
|
||||
} from "./host-metrics-helpers.js";
|
||||
import { applyAgentAuth } from "./terminal-auth-helpers.js";
|
||||
import {
|
||||
@@ -361,24 +362,45 @@ class PollingManager {
|
||||
}
|
||||
|
||||
try {
|
||||
let pingHost = refreshedHost.ip;
|
||||
const ct = refreshedHost.connectionType || "ssh";
|
||||
let pingPort: number;
|
||||
if (ct === "rdp") pingPort = refreshedHost.rdpPort ?? 3389;
|
||||
else if (ct === "vnc") pingPort = refreshedHost.vncPort ?? 5900;
|
||||
else if (ct === "telnet") pingPort = refreshedHost.telnetPort ?? 23;
|
||||
else pingPort = refreshedHost.port;
|
||||
|
||||
let isOnline: boolean;
|
||||
if (refreshedHost.jumpHosts && refreshedHost.jumpHosts.length > 0) {
|
||||
const firstJump = await fetchHostById(
|
||||
refreshedHost.jumpHosts[0].hostId,
|
||||
const proxyConfig: SOCKS5Config | null =
|
||||
refreshedHost.useSocks5 &&
|
||||
(refreshedHost.socks5Host ||
|
||||
(refreshedHost.socks5ProxyChain &&
|
||||
refreshedHost.socks5ProxyChain.length > 0))
|
||||
? {
|
||||
useSocks5: true,
|
||||
socks5Host: refreshedHost.socks5Host,
|
||||
socks5Port: refreshedHost.socks5Port,
|
||||
socks5Username: refreshedHost.socks5Username,
|
||||
socks5Password: refreshedHost.socks5Password,
|
||||
socks5ProxyChain: refreshedHost.socks5ProxyChain,
|
||||
}
|
||||
: null;
|
||||
const jumpClient = await createJumpHostChain(
|
||||
refreshedHost.jumpHosts,
|
||||
userId,
|
||||
proxyConfig,
|
||||
);
|
||||
if (firstJump) {
|
||||
pingHost = firstJump.ip;
|
||||
pingPort = firstJump.port;
|
||||
}
|
||||
isOnline = jumpClient
|
||||
? await tcpPingThroughJumpHost(
|
||||
jumpClient,
|
||||
refreshedHost.ip,
|
||||
pingPort,
|
||||
5000,
|
||||
)
|
||||
: false;
|
||||
} else {
|
||||
isOnline = await tcpPing(refreshedHost.ip, pingPort, 5000);
|
||||
}
|
||||
const isOnline = await tcpPing(pingHost, pingPort, 5000);
|
||||
const statusEntry: StatusEntry = {
|
||||
status: isOnline ? "online" : "offline",
|
||||
lastChecked: new Date().toISOString(),
|
||||
|
||||
Reference in New Issue
Block a user