udphole

Basic UDP wormhole proxy
git clone git://git.finwo.net/app/udphole
Log | Files | Refs | README | LICENSE

connect-drop-unknown.js (9138B)


      1 const path = require('path');
      2 const dgram = require('dgram');
      3 const {
      4   spawnDaemon,
      5   killDaemon,
      6   killAllDaemons,
      7   connectApi,
      8   apiCommand,
      9   createUdpEchoServer,
     10   TIMEOUT
     11 } = require('./helpers');
     12 
     13 const CONFIG_PATH = path.join(__dirname, 'config-tcp.ini');
     14 const API_PORT = 9123;
     15 
     16 function sendAndRecvUdp(srcPort, dstPort, message) {
     17   return new Promise((resolve, reject) => {
     18     const sock = dgram.createSocket('udp4');
     19     let resolved = false;
     20     
     21     const timer = setTimeout(() => {
     22       if (!resolved) {
     23         resolved = true;
     24         sock.close();
     25         resolve(null);
     26       }
     27     }, TIMEOUT);
     28     
     29     sock.on('message', (msg, rinfo) => {
     30       if (!resolved) {
     31         resolved = true;
     32         clearTimeout(timer);
     33         sock.close();
     34         resolve({ data: msg.toString(), rinfo });
     35       }
     36     });
     37     
     38     sock.bind(srcPort, '127.0.0.1', () => {
     39       const buf = Buffer.from(message);
     40       sock.send(buf, 0, buf.length, dstPort, '127.0.0.1', (err) => {
     41         if (err && !resolved) {
     42           resolved = true;
     43           clearTimeout(timer);
     44           sock.close();
     45           reject(err);
     46         }
     47       });
     48     });
     49     
     50     sock.on('error', (err) => {
     51       if (!resolved) {
     52         resolved = true;
     53         clearTimeout(timer);
     54         sock.close();
     55         reject(err);
     56       }
     57     });
     58   });
     59 }
     60 
     61 function sendUdpFromPort(srcPort, dstPort, message) {
     62   return new Promise((resolve, reject) => {
     63     const sock = dgram.createSocket('udp4');
     64     sock.bind(srcPort, '127.0.0.1', () => {
     65       const buf = Buffer.from(message);
     66       sock.send(buf, 0, buf.length, dstPort, '127.0.0.1', (err) => {
     67         if (err) {
     68           sock.close();
     69           reject(err);
     70         } else {
     71           setTimeout(() => {
     72             sock.close();
     73             resolve();
     74           }, 50);
     75         }
     76       });
     77     });
     78     sock.on('error', reject);
     79   });
     80 }
     81 
     82 async function runTest() {
     83   let daemon = null;
     84   let apiSock = null;
     85   let echoServer = null;
     86   let returnCode = 0;
     87 
     88   console.log('=== Connect Socket Drop Unknown Test ===');
     89   console.log('Testing: connect mode sockets drop traffic from unknown remote addresses\n');
     90 
     91   try {
     92     console.log('1. Spawning daemon...');
     93     daemon = await spawnDaemon(CONFIG_PATH);
     94     console.log(`   Daemon started (PID: ${daemon.pid})`);
     95 
     96     console.log('2. Connecting to API...');
     97     apiSock = await connectApi(API_PORT);
     98     console.log('   Connected to API');
     99 
    100     console.log('3. Authenticating...');
    101     let resp = await apiCommand(apiSock, 'auth', 'finwo', 'testsecret');
    102     console.log(`   Auth response: ${resp}`);
    103     if (resp !== 'OK') throw new Error('Authentication failed');
    104 
    105     console.log('4. Creating session...');
    106     resp = await apiCommand(apiSock, 'session.create', 'test-connect-drop', '60');
    107     console.log(`   Session create: ${resp}`);
    108 
    109     console.log('5. Creating listen socket...');
    110     resp = await apiCommand(apiSock, 'session.socket.create.listen', 'test-connect-drop', 'listen');
    111     const listenPort = resp[0];
    112     console.log(`   Listen socket port: ${listenPort}`);
    113 
    114     console.log('6. Starting echo server...');
    115     echoServer = await createUdpEchoServer();
    116     console.log(`   Echo server on port: ${echoServer.port}`);
    117 
    118     console.log('7. Creating connect socket to echo server...');
    119     resp = await apiCommand(apiSock, 'session.socket.create.connect', 'test-connect-drop', 'connect', '127.0.0.1', echoServer.port);
    120     const connectPort = resp[0];
    121     console.log(`   Connect socket local port: ${connectPort}`);
    122 
    123     console.log('8. Creating forward: listen -> connect...');
    124     resp = await apiCommand(apiSock, 'session.forward.create', 'test-connect-drop', 'listen', 'connect');
    125     console.log(`   Forward create: ${resp}`);
    126 
    127     console.log('9. Creating forward: connect -> listen (for echo responses)...');
    128     resp = await apiCommand(apiSock, 'session.forward.create', 'test-connect-drop', 'connect', 'listen');
    129     console.log(`   Forward create: ${resp}`);
    130 
    131     console.log('10. Waiting for session to initialize...');
    132     await new Promise(r => setTimeout(r, 100));
    133 
    134     console.log('\n=== Step 1: Happy path - active to listen, through proxy, back to active ===');
    135     console.log('11. Sending "step1-test" from port 50001 to listen socket...');
    136 
    137     const recvPromise1 = sendAndRecvUdp(50001, listenPort, 'step1-test');
    138     console.log('    Sent "step1-test", waiting for response...');
    139 
    140     let messages = echoServer.getMessages();
    141     let start = Date.now();
    142     while (messages.length === 0 && Date.now() - start < TIMEOUT) {
    143       await new Promise(r => setTimeout(r, 50));
    144       messages = echoServer.getMessages();
    145     }
    146 
    147     if (messages.length === 0) {
    148       throw new Error('Timeout: no message received by echo server in step 1');
    149     }
    150 
    151     const msgStep1 = messages[0];
    152     console.log(`    Echo server received: "${msgStep1.data}" from ${msgStep1.rinfo.address}:${msgStep1.rinfo.port}`);
    153 
    154     if (msgStep1.data !== 'step1-test') {
    155       throw new Error(`Expected "step1-test", got "${msgStep1.data}"`);
    156     }
    157     console.log('    ✓ Echo server received the packet');
    158 
    159     echoServer.clearMessages();
    160 
    161     const responseToSender1 = await recvPromise1;
    162     if (responseToSender1 === null) {
    163       throw new Error('Timeout: no response received back at sender port 50001');
    164     }
    165     console.log(`    ✓ Sender received echo response: "${responseToSender1.data}" from ${responseToSender1.rinfo.address}:${responseToSender1.rinfo.port}`);
    166 
    167     if (responseToSender1.data !== 'step1-test') {
    168       throw new Error(`Expected echo "step1-test", got "${responseToSender1.data}"`);
    169     }
    170 
    171     console.log('\n=== Step 2: Verify connect socket drops unknown source ===');
    172     console.log('12. Sending "step2-direct" directly to connect socket from port 50002...');
    173 
    174     const recvPromise2 = sendAndRecvUdp(50002, connectPort, 'step2-direct');
    175     console.log('    Sent "step2-direct", waiting for response...');
    176 
    177     messages = echoServer.getMessages();
    178     start = Date.now();
    179     while (Date.now() - start < 500) {
    180       await new Promise(r => setTimeout(r, 50));
    181       messages = echoServer.getMessages();
    182     }
    183 
    184     if (messages.length > 0) {
    185       console.log(`    ✗ FAIL: Echo server received packet from connect socket (should have been dropped)`);
    186       console.log(`      Received: "${messages[0].data}" from ${messages[0].rinfo.address}:${messages[0].rinfo.port}`);
    187       throw new Error('Connect socket did not drop traffic from unknown source');
    188     }
    189     console.log('    ✓ Echo server did NOT receive the packet (correctly dropped)');
    190 
    191     const responseToSender2 = await recvPromise2;
    192     if (responseToSender2 !== null) {
    193       console.log(`    ✗ FAIL: Sender received response from connect socket (should have been dropped)`);
    194       console.log(`      Received: "${responseToSender2.data}" from ${responseToSender2.rinfo.address}:${responseToSender2.rinfo.port}`);
    195       throw new Error('Connect socket did not drop traffic - response sent back to sender');
    196     }
    197     console.log('    ✓ Sender did NOT receive any response (correctly dropped)');
    198     console.log('\n✓ PASS: Connect socket correctly dropped traffic from unknown source');
    199 
    200     console.log('\n=== Step 3: Verify listen->connect forward still works ===');
    201     echoServer.clearMessages();
    202 
    203     console.log('13. Sending "step3-retry" from port 50001 to listen socket again...');
    204 
    205     const recvPromise3 = sendAndRecvUdp(50001, listenPort, 'step3-retry');
    206     console.log('    Sent "step3-retry", waiting for response...');
    207 
    208     messages = echoServer.getMessages();
    209     start = Date.now();
    210     while (messages.length === 0 && Date.now() - start < TIMEOUT) {
    211       await new Promise(r => setTimeout(r, 50));
    212       messages = echoServer.getMessages();
    213     }
    214 
    215     if (messages.length === 0) {
    216       throw new Error('Timeout: no message received by echo server in step 3');
    217     }
    218 
    219     const msgStep3 = messages[0];
    220     console.log(`    Echo server received: "${msgStep3.data}" from ${msgStep3.rinfo.address}:${msgStep3.rinfo.port}`);
    221 
    222     if (msgStep3.data !== 'step3-retry') {
    223       throw new Error(`Expected "step3-retry", got "${msgStep3.data}"`);
    224     }
    225     console.log('    ✓ Echo server received the packet');
    226 
    227     echoServer.clearMessages();
    228 
    229     const responseToSender3 = await recvPromise3;
    230     if (responseToSender3 === null) {
    231       throw new Error('Timeout: no response received back at sender port 50001 in step 3');
    232     }
    233     console.log(`    ✓ Sender received echo response: "${responseToSender3.data}" from ${responseToSender3.rinfo.address}:${responseToSender3.rinfo.port}`);
    234 
    235     if (responseToSender3.data !== 'step3-retry') {
    236       throw new Error(`Expected echo "step3-retry", got "${responseToSender3.data}"`);
    237     }
    238 
    239     console.log('\n✓ PASS: Connect socket drop unknown test completed successfully');
    240 
    241   } catch (err) {
    242     console.error(`\n✗ FAIL: ${err.message}`);
    243     returnCode = 1;
    244   } finally {
    245     if (echoServer) echoServer.socket.close();
    246     if (apiSock) apiSock.end();
    247     if (daemon) await killDaemon(daemon);
    248     await killAllDaemons();
    249     process.exit(returnCode);
    250   }
    251 }
    252 
    253 runTest();