import { expect, tap } from '@push.rocks/tapbundle'; import * as net from 'net'; import { PortProxy } from '../ts/smartproxy.portproxy.js'; let testServer: net.Server; let portProxy: PortProxy; const TEST_SERVER_PORT = 4000; const PROXY_PORT = 4001; const TEST_DATA = 'Hello through port proxy!'; // Helper function to create a test TCP server function createTestServer(port: number): Promise { return new Promise((resolve) => { const server = net.createServer((socket) => { socket.on('data', (data) => { // Echo the received data back socket.write(`Echo: ${data.toString()}`); }); socket.on('error', (error) => { console.error('[Test Server] Socket error:', error); }); }); server.listen(port, () => { console.log(`[Test Server] Listening on port ${port}`); resolve(server); }); }); } // Helper function to create a test client connection function createTestClient(port: number, data: string): Promise { return new Promise((resolve, reject) => { const client = new net.Socket(); let response = ''; client.connect(port, 'localhost', () => { console.log('[Test Client] Connected to server'); client.write(data); }); client.on('data', (chunk) => { response += chunk.toString(); client.end(); }); client.on('end', () => { resolve(response); }); client.on('error', (error) => { reject(error); }); }); } // Setup test environment tap.test('setup port proxy test environment', async () => { testServer = await createTestServer(TEST_SERVER_PORT); portProxy = new PortProxy({ fromPort: PROXY_PORT, toPort: TEST_SERVER_PORT, toHost: 'localhost', domains: [], sniEnabled: false, defaultAllowedIPs: ['127.0.0.1'] }); }); tap.test('should start port proxy', async () => { await portProxy.start(); expect(portProxy.netServer.listening).toBeTrue(); }); tap.test('should forward TCP connections and data to localhost', async () => { const response = await createTestClient(PROXY_PORT, TEST_DATA); expect(response).toEqual(`Echo: ${TEST_DATA}`); }); tap.test('should forward TCP connections to custom host', async () => { // Create a new proxy instance with a custom host const customHostProxy = new PortProxy({ fromPort: PROXY_PORT + 1, toPort: TEST_SERVER_PORT, toHost: '127.0.0.1', domains: [], sniEnabled: false, defaultAllowedIPs: ['127.0.0.1'] }); await customHostProxy.start(); const response = await createTestClient(PROXY_PORT + 1, TEST_DATA); expect(response).toEqual(`Echo: ${TEST_DATA}`); await customHostProxy.stop(); }); tap.test('should forward connections based on domain-specific target IP', async () => { // Create a second test server on a different port const TEST_SERVER_PORT_2 = TEST_SERVER_PORT + 100; const testServer2 = await createTestServer(TEST_SERVER_PORT_2); // Create a proxy with domain-specific target IPs const domainProxy = new PortProxy({ fromPort: PROXY_PORT + 2, toPort: TEST_SERVER_PORT, // default port toHost: 'localhost', // default host domains: [{ domain: 'domain1.test', allowedIPs: ['127.0.0.1'], targetIP: '127.0.0.1' }, { domain: 'domain2.test', allowedIPs: ['127.0.0.1'], targetIP: 'localhost' }], sniEnabled: false, // We'll test without SNI first since this is a TCP proxy test defaultAllowedIPs: ['127.0.0.1'] }); await domainProxy.start(); // Test default connection (should use default host) const response1 = await createTestClient(PROXY_PORT + 2, TEST_DATA); expect(response1).toEqual(`Echo: ${TEST_DATA}`); // Create another proxy with different default host const domainProxy2 = new PortProxy({ fromPort: PROXY_PORT + 3, toPort: TEST_SERVER_PORT, toHost: '127.0.0.1', domains: [], sniEnabled: false, defaultAllowedIPs: ['127.0.0.1'] }); await domainProxy2.start(); const response2 = await createTestClient(PROXY_PORT + 3, TEST_DATA); expect(response2).toEqual(`Echo: ${TEST_DATA}`); await domainProxy.stop(); await domainProxy2.stop(); await new Promise((resolve) => testServer2.close(() => resolve())); }); tap.test('should handle multiple concurrent connections', async () => { const concurrentRequests = 5; const requests = Array(concurrentRequests).fill(null).map((_, i) => createTestClient(PROXY_PORT, `${TEST_DATA} ${i + 1}`) ); const responses = await Promise.all(requests); responses.forEach((response, i) => { expect(response).toEqual(`Echo: ${TEST_DATA} ${i + 1}`); }); }); tap.test('should handle connection timeouts', async () => { const client = new net.Socket(); await new Promise((resolve) => { client.connect(PROXY_PORT, 'localhost', () => { // Don't send any data, just wait for timeout client.on('close', () => { resolve(); }); }); }); }); tap.test('should stop port proxy', async () => { await portProxy.stop(); expect(portProxy.netServer.listening).toBeFalse(); }); // Cleanup tap.test('should support optional source IP preservation in chained proxies', async () => { // Test 1: Without IP preservation (default behavior) const firstProxyDefault = new PortProxy({ fromPort: PROXY_PORT + 4, toPort: PROXY_PORT + 5, toHost: 'localhost', domains: [], sniEnabled: false, defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'] }); const secondProxyDefault = new PortProxy({ fromPort: PROXY_PORT + 5, toPort: TEST_SERVER_PORT, toHost: 'localhost', domains: [], sniEnabled: false, defaultAllowedIPs: ['127.0.0.1', '::ffff:127.0.0.1'] }); await secondProxyDefault.start(); await firstProxyDefault.start(); // This should work because we explicitly allow both IPv4 and IPv6 formats const response1 = await createTestClient(PROXY_PORT + 4, TEST_DATA); expect(response1).toEqual(`Echo: ${TEST_DATA}`); await firstProxyDefault.stop(); await secondProxyDefault.stop(); // Test 2: With IP preservation const firstProxyPreserved = new PortProxy({ fromPort: PROXY_PORT + 6, toPort: PROXY_PORT + 7, toHost: 'localhost', domains: [], sniEnabled: false, defaultAllowedIPs: ['127.0.0.1'], preserveSourceIP: true }); const secondProxyPreserved = new PortProxy({ fromPort: PROXY_PORT + 7, toPort: TEST_SERVER_PORT, toHost: 'localhost', domains: [], sniEnabled: false, defaultAllowedIPs: ['127.0.0.1'], preserveSourceIP: true }); await secondProxyPreserved.start(); await firstProxyPreserved.start(); // This should work with just IPv4 because source IP is preserved const response2 = await createTestClient(PROXY_PORT + 6, TEST_DATA); expect(response2).toEqual(`Echo: ${TEST_DATA}`); await firstProxyPreserved.stop(); await secondProxyPreserved.stop(); }); tap.test('cleanup port proxy test environment', async () => { await new Promise((resolve) => testServer.close(() => resolve())); }); process.on('exit', () => { if (testServer) { testServer.close(); } if (portProxy && portProxy.netServer) { portProxy.stop(); } }); export default tap.start();