@@ -10,6 +10,8 @@ const ora = require('ora');
1010const fs = require ( 'fs' ) ;
1111const path = require ( 'path' ) ;
1212const http = require ( 'http' ) ;
13+ const net = require ( 'net' ) ;
14+ const dgram = require ( 'dgram' ) ;
1315const { spawnSync } = require ( 'child_process' ) ;
1416const IranConnectivityAnalyzer = require ( './iran_connectivity' ) ;
1517const { TunnelRecommendationEngine } = require ( './tunnel_recommendations' ) ;
@@ -354,6 +356,13 @@ class IranCheckCLI {
354356 }
355357 }
356358 async printDetailedReport ( results ) {
359+ const mtrBadge = ( connection ) => {
360+ if ( ! connection ) return '✗' ;
361+ if ( ! connection . mtrAvailable ) return '✗' ;
362+ if ( typeof connection . mtrLossPercent !== 'number' ) return '✗' ;
363+ return connection . mtrLossPercent <= 20 ? '✓' : '✗' ;
364+ } ;
365+
357366 console . log ( chalk . blue ( '\n📊 Detailed report:' ) ) ;
358367 console . log ( chalk . gray ( '═' . repeat ( 80 ) ) ) ;
359368 // Summary statistics
@@ -366,7 +375,9 @@ class IranCheckCLI {
366375 if ( results . successfulProviders . length > 0 ) {
367376 console . log ( chalk . green ( '\n✅ Successful providers:' ) ) ;
368377 results . successfulProviders . forEach ( ( provider , index ) => {
369- console . log ( ` ${ index + 1 } . ${ provider . name } (score: ${ provider . bestScore } )` ) ;
378+ const badge = mtrBadge ( provider . bestConnection ) ;
379+ const loss = typeof provider . bestConnection ?. mtrLossPercent === 'number' ? `${ provider . bestConnection . mtrLossPercent } %` : 'N/A' ;
380+ console . log ( ` ${ index + 1 } . ${ provider . name } (MTR: ${ badge } , loss: ${ loss } )` ) ;
370381 } ) ;
371382 }
372383 // Failed providers
@@ -383,7 +394,9 @@ class IranCheckCLI {
383394 if ( provider . successfulConnections . length > 0 ) {
384395 console . log ( chalk . green ( `\n ${ provider . name } :` ) ) ;
385396 console . log ( ` • Successful paths: ${ provider . successfulConnections . length } ` ) ;
386- console . log ( ` • Best score: ${ provider . connectivityScore } ` ) ;
397+ const badge = mtrBadge ( provider . bestConnection ) ;
398+ const loss = typeof provider . bestConnection ?. mtrLossPercent === 'number' ? `${ provider . bestConnection . mtrLossPercent } %` : 'N/A' ;
399+ console . log ( ` • MTR test result: ${ badge } (loss: ${ loss } )` ) ;
387400 const groupedByRange = provider . successfulConnections . reduce ( ( acc , conn ) => {
388401 const range = conn . testedCidr || 'Unknown range' ;
389402 if ( ! acc [ range ] ) acc [ range ] = 0 ;
@@ -428,6 +441,7 @@ class IranCheckCLI {
428441 console . log ( ` • BGP prefix: ${ conn . bgpPrefix ?? 'N/A' } ` ) ;
429442 console . log ( ` • BGP registry: ${ conn . bgpRegistry ?? 'N/A' } ` ) ;
430443 }
444+ console . log ( ` • Stage 4 (MTR): ${ conn . stageResults ?. mtr || 'skipped' } ` ) ;
431445 console . log ( ` • Target reachability: ${ conn . targetReachability || 0 } %` ) ;
432446 }
433447 }
@@ -606,6 +620,46 @@ class IranCheckCLI {
606620 console . log ( chalk . cyan ( `🧾 Total providers: ${ providers . length } ` ) ) ;
607621 }
608622}
623+
624+ function startProbeListener ( options = { } ) {
625+ const host = options . host || '0.0.0.0' ;
626+ const port = Number ( options . port || 9000 ) ;
627+ const protocol = String ( options . protocol || 'both' ) . toLowerCase ( ) ;
628+ const tcpEnabled = protocol === 'tcp' || protocol === 'both' ;
629+ const udpEnabled = protocol === 'udp' || protocol === 'both' ;
630+
631+ if ( ! tcpEnabled && ! udpEnabled ) {
632+ throw new Error ( 'Protocol must be tcp, udp, or both' ) ;
633+ }
634+
635+ if ( tcpEnabled ) {
636+ const tcpServer = net . createServer ( ( socket ) => {
637+ socket . setEncoding ( 'utf8' ) ;
638+ socket . on ( 'data' , ( data ) => {
639+ const payload = String ( data || '' ) . trim ( ) ;
640+ socket . write ( `OK TCP port=${ port } payload="${ payload || 'empty' } "\n` ) ;
641+ } ) ;
642+ socket . on ( 'error' , ( ) => { } ) ;
643+ } ) ;
644+ tcpServer . listen ( port , host , ( ) => {
645+ console . log ( chalk . green ( `✅ TCP listener active on ${ host } :${ port } ` ) ) ;
646+ } ) ;
647+ }
648+
649+ if ( udpEnabled ) {
650+ const udpServer = dgram . createSocket ( 'udp4' ) ;
651+ udpServer . on ( 'message' , ( msg , rinfo ) => {
652+ const payload = String ( msg || '' ) . trim ( ) ;
653+ const response = Buffer . from ( `OK UDP port=${ port } payload="${ payload || 'empty' } "` ) ;
654+ udpServer . send ( response , rinfo . port , rinfo . address ) ;
655+ } ) ;
656+ udpServer . bind ( port , host , ( ) => {
657+ console . log ( chalk . green ( `✅ UDP listener active on ${ host } :${ port } ` ) ) ;
658+ } ) ;
659+ }
660+
661+ console . log ( chalk . cyan ( `ℹ️ Listener running (protocol=${ protocol } ). Press Ctrl+C to stop.` ) ) ;
662+ }
609663// Commander.js setup
610664program
611665 . name ( 'iran-check' )
@@ -637,6 +691,16 @@ program
637691 banner : options . banner
638692 } ) ;
639693 } ) ;
694+ program
695+ . command ( 'listener' )
696+ . description ( 'Start local TCP/UDP echo listener for Iranian-side port/protocol testing' )
697+ . option ( '--host <host>' , 'Bind host' , '0.0.0.0' )
698+ . option ( '--port <port>' , 'Bind port' , '9000' )
699+ . option ( '--protocol <protocol>' , 'tcp | udp | both' , 'both' )
700+ . action ( ( options ) => {
701+ startProbeListener ( options ) ;
702+ } ) ;
703+
640704program
641705 . command ( 'providers' )
642706 . description ( 'Show provider list' )
0 commit comments