Some asshole in my new house has decided to mess with our router settings, and I was tryna figure out why I cannot access administrative page. I thought they might have changed listening port... I didn't have testing tools, so I just made a script to scan all ports on an ip address or hostname.

It works on domain names, ip addresses, private networks, anything you like. It functions by trying to open a TCP socket with an endpoint of your choice.

Usage:
npm start address={string} concurrency={number} timeout={number}

address parameter:
endpoint we're scanning. could be an IP address, hostname, or something of such nature.

concurrency parameter (OPTIONAL):
amount of con-current scans you have. The more you have, the more strain it might put on your CPU. If you're scanning a remote address that has a big latency, i suggest keeping this high (1000+), if you're scanning a local network with little latency, i suggest keeping this low (<300). The reason I suggest like this, is how the event loop in NodeJS works, and polling IO events.

timeout parameter (OPTIONAL):
the socket timeout before deeming a port closed. I suggest not allowing this go below 500 milliseconds, especially if you're scanning a host in another country. If you're scanning in another country, keep it around a 1000 or 2000. If you're scanning a host on a local network, you could probably get away with 200, but I would never suggest going under 500 to get the best results.


Code:
const net = require('net')

const LINE_DIVIDER = '----------------'

const subjectAddress = readArgument('address')
const concurrentScanLimit = parseInt(readArgument('concurrency', 1000))
const sockTimeout = parseInt(readArgument('timeout', 700))


console.log(`Subject Address\t\t${subjectAddress}`)
console.log(`Concurrent Workers\t${concurrentScanLimit}`)
console.log(`Socket Test Timeout\t${sockTimeout}`)
console.log(LINE_DIVIDER)


// CLI Argument Validation
if(typeof subjectAddress !== 'string') {
  return console.error('\n\nThe \"address\" argument cannot be found. Add a process argument \"address={ip|host}\"')
}

if(typeof concurrentScanLimit !== 'number' || concurrentScanLimit <= 0) {
  return console.error('\n\nThe \"concurrency\" argument is invalid. Set it with a valid integer value that is greater than 0. Example, \"concurrency={integer}\"')
}

if(typeof sockTimeout !== 'number' || sockTimeout <= 0) {
  return console.error('\n\nThe \"timeout\" argument is invalid. Set it with a valid integer value that is greater than 0. It is measured in milliseconds. Argument example \"sockTimeout={integer}\"')
}


// Stores state for all of the worker statuses.
let workerStatuses = {  }
let openPorts = [ ]
let closedPorts = [ ]
let activeWorkerCount = 0


// creating the concurrency for scanning ports, concurrency is determined by
// concurrentScanLimit
for(let i = 0; i < concurrentScanLimit; i++) {
  workerStatuses[i] = {
    id: i,
    idPrint: `#${i}`,
    scannedPorts: [],
  }

  setImmediate(
    concurrencyWorker,
    workerStatuses[i],
    openPorts,
    closedPorts
  )
}

// function is run whenever a concurrencyWorker has started its duty
function concurrencyStartEvent(workerStatus)
{
  activeWorkerCount++
}

// function is run whenever a concurrencyWorker has completed its duty
function concurrencyEndEvent(workerStatus)
{
  activeWorkerCount--
  if(activeWorkerCount === 0) {
    // all concurrency workers have finished their execution

    console.log('Scan Complete')
    console.log(`${subjectAddress} has ${openPorts.length} open ports and ${closedPorts.length} closed ports`)
    if(openPorts.length > 0) {
      console.log(`Open ports: ${openPorts.join(', ')}`)
    }
  }
}


// this function will create a new worker for scanning ports.
async function concurrencyWorker(workerStatus, openPorts, closedPorts) {
  concurrencyStartEvent(workerStatus)

  while(true) {
    const port = pullPort()
    if(port === null) {
      break
    }

    const result = await testPort(port)
    workerStatus.scannedPorts.push(port)

    if(result === port) {
      openPorts.push(port)
    }
    else {
      closedPorts.push(port)
    }
  }

  concurrencyEndEvent(workerStatus)
}


function testPort(port) {
  return new Promise((resolve, reject) => {
    const sock = new net.Socket()
    sock.setTimeout(sockTimeout)

    sock.on('timeout', () => {
      resolve(null)
      sock.destroy()
    })

    sock.on('error', (err) => {
      resolve(null)
      sock.destroy()
    })

    sock.on('connect', () => {
      resolve(port)
      sock.end()
    })

    sock.connect(port, subjectAddress)
  })
}


function pullPort() {
  if(typeof global.portIncrement === 'undefined') {
    return global.portIncrement = 1
  }

  ++global.portIncrement

  if(global.portIncrement > 65535) {
    return null
  }

  return global.portIncrement
}

function readArgument(name, assumed = null)
{
  for(let arg of process.argv) {
    const [key, value] = arg.split('=', 2)
    if(typeof value === 'undefined' && key === name) {
      return true
    }
    else if(key === name) {
      return value
    }
  }

  return assumed
}