Files
akanealw 99819b70ff
Some checks failed
Build and Push Docker Images (Trusted) / build-and-push (., docker/caddy/Dockerfile, caddy) (push) Has been cancelled
Build and Push Docker Images (Trusted) / build-and-push (., docker/l4-port-manager/Dockerfile, l4-port-manager) (push) Has been cancelled
Build and Push Docker Images (Trusted) / build-and-push (., docker/web/Dockerfile, web) (push) Has been cancelled
Tests / test (push) Has been cancelled
added caddy-proxy-manager for testing
2026-04-21 22:49:08 +00:00

162 lines
3.6 KiB
TypeScript
Executable File

/**
* Low-level TCP and UDP helpers for L4 proxy functional tests.
*
* Sends raw TCP connections and UDP datagrams to Caddy's L4 proxy ports
* and reads responses.
*/
import net from 'node:net';
import dgram from 'node:dgram';
export interface TcpResponse {
data: string;
connected: boolean;
}
/**
* Open a TCP connection to the given host:port, send a payload,
* and collect whatever comes back within the timeout window.
*/
export function tcpSend(
host: string,
port: number,
payload: string,
timeoutMs = 5_000
): Promise<TcpResponse> {
return new Promise((resolve, reject) => {
let data = '';
let connected = false;
const socket = net.createConnection({ host, port }, () => {
connected = true;
socket.write(payload);
});
socket.setTimeout(timeoutMs);
socket.on('data', (chunk) => {
data += chunk.toString();
});
socket.on('end', () => {
resolve({ data, connected });
});
socket.on('timeout', () => {
socket.destroy();
resolve({ data, connected });
});
socket.on('error', (err) => {
if (connected) {
resolve({ data, connected });
} else {
reject(err);
}
});
});
}
/**
* Test if a TCP port is accepting connections.
*/
export function tcpConnect(
host: string,
port: number,
timeoutMs = 5_000
): Promise<boolean> {
return new Promise((resolve) => {
const socket = net.createConnection({ host, port }, () => {
socket.destroy();
resolve(true);
});
socket.setTimeout(timeoutMs);
socket.on('timeout', () => {
socket.destroy();
resolve(false);
});
socket.on('error', () => {
resolve(false);
});
});
}
/**
* Poll until a TCP port accepts connections.
* Similar to waitForRoute() but for TCP.
*/
export async function waitForTcpRoute(
host: string,
port: number,
timeoutMs = 15_000
): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const ok = await tcpConnect(host, port, 2000);
if (ok) return;
await new Promise(r => setTimeout(r, 500));
}
throw new Error(`TCP port ${host}:${port} not ready after ${timeoutMs}ms`);
}
// ---------------------------------------------------------------------------
// UDP helpers
// ---------------------------------------------------------------------------
export interface UdpResponse {
data: string;
received: boolean;
}
/**
* Send a UDP datagram and wait for a response.
*/
export function udpSend(
host: string,
port: number,
payload: string,
timeoutMs = 5_000
): Promise<UdpResponse> {
return new Promise((resolve) => {
const socket = dgram.createSocket('udp4');
let received = false;
let data = '';
const timer = setTimeout(() => {
socket.close();
resolve({ data, received });
}, timeoutMs);
socket.on('message', (msg) => {
received = true;
data += msg.toString();
clearTimeout(timer);
socket.close();
resolve({ data, received });
});
socket.on('error', () => {
clearTimeout(timer);
socket.close();
resolve({ data, received });
});
socket.send(payload, port, host);
});
}
/**
* Poll until a UDP port responds to a test datagram.
*/
export async function waitForUdpRoute(
host: string,
port: number,
timeoutMs = 15_000
): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await udpSend(host, port, 'ping', 2000);
if (res.received) return;
await new Promise(r => setTimeout(r, 500));
}
throw new Error(`UDP port ${host}:${port} not ready after ${timeoutMs}ms`);
}