import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as net from 'net'; import * as dgram from 'dgram'; import { VpnClient, VpnServer } from '../ts/index.js'; import type { IVpnClientOptions, IVpnServerOptions, IVpnKeypair, IVpnServerConfig } from '../ts/index.js'; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- async function findFreePort(): Promise { const server = net.createServer(); await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); const port = (server.address() as net.AddressInfo).port; await new Promise((resolve) => server.close(() => resolve())); return port; } async function findFreeUdpPort(): Promise { const sock = dgram.createSocket('udp4'); await new Promise((resolve) => sock.bind(0, '127.0.0.1', resolve)); const port = (sock.address() as net.AddressInfo).port; await new Promise((resolve) => sock.close(resolve)); return port; } function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } async function waitFor( fn: () => Promise, timeoutMs: number = 10000, pollMs: number = 500, ): Promise { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { if (await fn()) return; await delay(pollMs); } throw new Error(`waitFor timed out after ${timeoutMs}ms`); } // --------------------------------------------------------------------------- // Test state // --------------------------------------------------------------------------- let server: VpnServer; let wsPort: number; let quicPort: number; let keypair: IVpnKeypair; // --------------------------------------------------------------------------- // Tests: QUIC-only server + QUIC client // --------------------------------------------------------------------------- tap.test('setup: start VPN server in QUIC mode', async () => { quicPort = await findFreeUdpPort(); const options: IVpnServerOptions = { transport: { transport: 'stdio' }, }; server = new VpnServer(options); const started = await server['bridge'].start(); expect(started).toBeTrue(); keypair = await server.generateKeypair(); const serverConfig: IVpnServerConfig = { listenAddr: `127.0.0.1:${quicPort}`, privateKey: keypair.privateKey, publicKey: keypair.publicKey, subnet: '10.9.0.0/24', transportMode: 'quic', keepaliveIntervalSecs: 3, }; await server['bridge'].sendCommand('start', { config: serverConfig }); const status = await server.getStatus(); expect(status.state).toEqual('connected'); }); tap.test('QUIC client connects and gets IP', async () => { const options: IVpnClientOptions = { transport: { transport: 'stdio' }, }; const client = new VpnClient(options); const started = await client.start(); expect(started).toBeTrue(); const result = await client.connect({ serverUrl: `127.0.0.1:${quicPort}`, serverPublicKey: keypair.publicKey, transport: 'quic', keepaliveIntervalSecs: 3, }); expect(result.assignedIp).toBeTypeofString(); expect(result.assignedIp).toStartWith('10.9.0.'); const clientStatus = await client.getStatus(); expect(clientStatus.state).toEqual('connected'); // Verify server sees the client await waitFor(async () => { const clients = await server.listClients(); return clients.length >= 1; }); await client.stop(); }); tap.test('teardown: stop QUIC server', async () => { await server.stop(); await delay(500); }); // --------------------------------------------------------------------------- // Tests: dual-mode server (both) + auto client // --------------------------------------------------------------------------- let dualServer: VpnServer; let dualWsPort: number; let dualQuicPort: number; let dualKeypair: IVpnKeypair; tap.test('setup: start VPN server in both mode', async () => { dualWsPort = await findFreePort(); dualQuicPort = await findFreeUdpPort(); const options: IVpnServerOptions = { transport: { transport: 'stdio' }, }; dualServer = new VpnServer(options); const started = await dualServer['bridge'].start(); expect(started).toBeTrue(); dualKeypair = await dualServer.generateKeypair(); const serverConfig: IVpnServerConfig = { listenAddr: `127.0.0.1:${dualWsPort}`, privateKey: dualKeypair.privateKey, publicKey: dualKeypair.publicKey, subnet: '10.10.0.0/24', transportMode: 'both', quicListenAddr: `127.0.0.1:${dualQuicPort}`, keepaliveIntervalSecs: 3, }; await dualServer['bridge'].sendCommand('start', { config: serverConfig }); const status = await dualServer.getStatus(); expect(status.state).toEqual('connected'); }); tap.test('auto client connects to dual-mode server (QUIC preferred)', async () => { const options: IVpnClientOptions = { transport: { transport: 'stdio' }, }; const client = new VpnClient(options); const started = await client.start(); expect(started).toBeTrue(); // "auto" mode (default): tries QUIC first at same host:port, falls back to WS // Since the WS port and QUIC port differ, auto will try QUIC on WS port (fail), // then fall back to WebSocket const result = await client.connect({ serverUrl: `ws://127.0.0.1:${dualWsPort}`, serverPublicKey: dualKeypair.publicKey, // transport defaults to 'auto' keepaliveIntervalSecs: 3, }); expect(result.assignedIp).toBeTypeofString(); expect(result.assignedIp).toStartWith('10.10.0.'); const clientStatus = await client.getStatus(); expect(clientStatus.state).toEqual('connected'); await waitFor(async () => { const clients = await dualServer.listClients(); return clients.length >= 1; }); await client.stop(); }); tap.test('explicit QUIC client connects to dual-mode server', async () => { const options: IVpnClientOptions = { transport: { transport: 'stdio' }, }; const client = new VpnClient(options); const started = await client.start(); expect(started).toBeTrue(); const result = await client.connect({ serverUrl: `127.0.0.1:${dualQuicPort}`, serverPublicKey: dualKeypair.publicKey, transport: 'quic', keepaliveIntervalSecs: 3, }); expect(result.assignedIp).toBeTypeofString(); expect(result.assignedIp).toStartWith('10.10.0.'); const clientStatus = await client.getStatus(); expect(clientStatus.state).toEqual('connected'); await client.stop(); }); tap.test('keepalive exchange over QUIC', async () => { const options: IVpnClientOptions = { transport: { transport: 'stdio' }, }; const client = new VpnClient(options); await client.start(); await client.connect({ serverUrl: `127.0.0.1:${dualQuicPort}`, serverPublicKey: dualKeypair.publicKey, transport: 'quic', keepaliveIntervalSecs: 3, }); // Wait for keepalive exchange await delay(8000); const clientStats = await client.getStatistics(); expect(clientStats.keepalivesSent).toBeGreaterThanOrEqual(1); expect(clientStats.keepalivesReceived).toBeGreaterThanOrEqual(1); await client.stop(); }); tap.test('teardown: stop dual-mode server', async () => { await dualServer.stop(); await delay(500); }); export default tap.start();