├── .circleci └── config.yml ├── .eslintrc ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example └── portscan.js ├── lib ├── portscanner.js └── promisify.js ├── package.json └── test.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:7.10 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: npm install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run tests! 37 | - run: npm test 38 | 39 | 40 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | {"extends": "standard"} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | *.sublime-* 4 | .git* 5 | example/ 6 | test.js 7 | yarn.lock 8 | .eslintrc 9 | .npmignore 10 | *.tgz 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.1.0 2 | 3 | ## New 4 | 5 | * [[`ed4682bd3c`](https://github.com/baalexander/node-portscanner/commit/ed4682bd3c)] - Accept ports as strings (laggingreflex) 6 | 7 | # 2.0.0 8 | 9 | ## Breaking Changes 10 | 11 | * Ports must be numbers. Some of the new changes are based on the assumption that ports must be numbers. 12 | 13 | ## New 14 | 15 | * [[`84db394996`](https://github.com/baalexander/node-portscanner/commit/84db394996)] - promise support (laggingreflex) 16 | * [[`3b90e2ca74`](https://github.com/baalexander/node-portscanner/commit/3b90e2ca74)] - Improve arguments parsing (laggingreflex) 17 | * [[`eb345da68c`](https://github.com/baalexander/node-portscanner/commit/eb345da68c)] - Make host parameter optional (Maciej Dudzinski) 18 | * [[`2ed556b159`](https://github.com/baalexander/node-portscanner/commit/2ed556b159)] [[`99483dc28e`](https://github.com/baalexander/node-portscanner/commit/99483dc28e)] [#26](https://github.com/baalexander/node-portscanner/issues/26) - Add support for checking array of ports instead of range (Maciej Dudzinski) 19 | 20 | ## Fixes 21 | 22 | * [[`da8ff250bd`](https://github.com/baalexander/node-portscanner/commit/da8ff250bd)] [#23](https://github.com/baalexander/node-portscanner/issues/23) - handle ports range provided in reverse order (laggingreflex) 23 | * [[`4c7a88436f`](https://github.com/baalexander/node-portscanner/commit/4c7a88436f)] - Update async module for use this module in strict mode (Luca Pau) 24 | * [[`04fea5dd4d`](https://github.com/baalexander/node-portscanner/commit/04fea5dd4d)] - Preserve error message from socket's error event (Jan Melcher) 25 | 26 | ## Misc 27 | 28 | * [[`0beeca8cbd`](https://github.com/baalexander/node-portscanner/commit/0beeca8cbd)] - Implement JavaScript Standard Style Guide (laggingreflex) 29 | * [[`da1ce58319`](https://github.com/baalexander/node-portscanner/commit/da1ce58319)] - more tests for different argument signatures (laggingreflex) 30 | * [[`9fdbdc38d7`](https://github.com/baalexander/node-portscanner/commit/9fdbdc38d7)] - Add basic unit tests with Ava runner (Maciej Dudzinski) 31 | 32 | # 1.2.0 33 | 34 | Some recent changes in 1.1.0 [[`3b90e2ca74`](https://github.com/baalexander/node-portscanner/commit/3b90e2ca74)] [[`eb345da68c`](https://github.com/baalexander/node-portscanner/commit/eb345da68c)] were based on the assumption that the ports would always be of the type numbers. This broke portscanner in some cases [#49](https://github.com/baalexander/node-portscanner/issues/49) [#50](https://github.com/baalexander/node-portscanner/issues/50). All changes were reverted back to 1.0.0 and only some very critical fixes [[`4c7a88436f`](https://github.com/baalexander/node-portscanner/commit/4c7a88436f)] [[`04fea5dd4d`](https://github.com/baalexander/node-portscanner/commit/04fea5dd4d)] were applied to this version. 35 | 36 | 37 | ## Fixes 38 | 39 | * [[`4c7a88436f`](https://github.com/baalexander/node-portscanner/commit/4c7a88436f)] - Update async module for use this module in strict mode (Luca Pau) 40 | * [[`04fea5dd4d`](https://github.com/baalexander/node-portscanner/commit/04fea5dd4d)] - Preserve error message from socket's error event (Jan Melcher) 41 | 42 | ## Misc 43 | 44 | * [[`69c648c740`](https://github.com/baalexander/node-portscanner/commit/69c648c740)] - tests - ports as strings (laggingreflex) 45 | * [[`9fdbdc38d7`](https://github.com/baalexander/node-portscanner/commit/9fdbdc38d7)] - Add basic unit tests with Ava runner (Maciej Dudzinski) 46 | 47 | 48 | # 1.1.1 49 | 50 | Reverted to 1.0.0 51 | 52 | # 1.1.0 53 | 54 | Some breaking changes were introduced in this version which have since been reverted in 1.1.1. 55 | 56 | Please update to either 1.1.1 or 2.0.0. 57 | 58 | # 1.0.0 59 | 60 | * [[`2463e64a0a`](https://github.com/baalexander/node-portscanner/commit/2463e64a0a)] - 1.0.0 (Sean Massa) 61 | * [[`21aa98b632`](https://github.com/baalexander/node-portscanner/commit/21aa98b632)] - update readme (Sean Massa) 62 | * [[`5fe4ab4a69`](https://github.com/baalexander/node-portscanner/commit/5fe4ab4a69)] - Merge pull request #21 from jdwilliams15/master (Sean Massa) 63 | * [[`725afef7b4`](https://github.com/baalexander/node-portscanner/commit/725afef7b4)] - fix indent (jdwilliams15) 64 | * [[`053b56e455`](https://github.com/baalexander/node-portscanner/commit/053b56e455)] - fixed indentation (jdwilliams15) 65 | * [[`b1dd496633`](https://github.com/baalexander/node-portscanner/commit/b1dd496633)] - Changed socket error handler to handle 'ECONNREFUSED'. In event of ECONNREFUSED the port is available (jdwilliams15) 66 | * [[`512cfdbf78`](https://github.com/baalexander/node-portscanner/commit/512cfdbf78)] - 0.2.3 (Sean Massa) 67 | * [[`5526b8b4eb`](https://github.com/baalexander/node-portscanner/commit/5526b8b4eb)] - Merge pull request #19 from thomseddon/fix-end (Sean Massa) 68 | * [[`a854ec6bd6`](https://github.com/baalexander/node-portscanner/commit/a854ec6bd6)] - Use socket.destroy() not socket.end() on successful connection (Thom Seddon) 69 | * [[`c747ffa9de`](https://github.com/baalexander/node-portscanner/commit/c747ffa9de)] - 0.2.2 (Sean Massa) 70 | * [[`4a1f8f811b`](https://github.com/baalexander/node-portscanner/commit/4a1f8f811b)] - Merge pull request #16 from baalexander/fix-port-finding (Sean Massa) 71 | * [[`ff51ebe871`](https://github.com/baalexander/node-portscanner/commit/ff51ebe871)] - fix port reporting (Sean Massa) 72 | * [[`e9070e85ca`](https://github.com/baalexander/node-portscanner/commit/e9070e85ca)] - 0.2.1 (Sean Massa) 73 | * [[`809b7760ad`](https://github.com/baalexander/node-portscanner/commit/809b7760ad)] - 0.2.0 (Sean Massa) 74 | * [[`87f35e5b87`](https://github.com/baalexander/node-portscanner/commit/87f35e5b87)] - Merge pull request #14 from baalexander/localhost-127.0.0.1 (Sean Massa) 75 | * [[`0b72b83cab`](https://github.com/baalexander/node-portscanner/commit/0b72b83cab)] - switch out localhost for 127.0.0.1 (Sean Massa) 76 | * [[`4407d6f701`](https://github.com/baalexander/node-portscanner/commit/4407d6f701)] - Merge pull request #13 from skilesare/master (Sean Massa) 77 | * [[`3ed682ad7a`](https://github.com/baalexander/node-portscanner/commit/3ed682ad7a)] - Update portscanner.js (skilesare) 78 | * [[`c1544a1bb3`](https://github.com/baalexander/node-portscanner/commit/c1544a1bb3)] - Update portscanner.js (skilesare) 79 | * [[`d3b029c384`](https://github.com/baalexander/node-portscanner/commit/d3b029c384)] - Adds @EndangeredMassa as a package maintainer. (Brandon Alexander) 80 | * [[`8f5559b1fe`](https://github.com/baalexander/node-portscanner/commit/8f5559b1fe)] - Merge pull request #7 from EndangeredMassa/smassa/timeout (Brandon Alexander) 81 | * [[`16d0db3944`](https://github.com/baalexander/node-portscanner/commit/16d0db3944)] - added options param to checkPortStatus; supports host and timeout (Sean Massa) 82 | * [[`b8acb18a08`](https://github.com/baalexander/node-portscanner/commit/b8acb18a08)] - exposed errors for checkPortStatus (Sean Massa) 83 | * [[`381769162d`](https://github.com/baalexander/node-portscanner/commit/381769162d)] - Updates version to 0.1.3. (Brandon Alexander) 84 | * [[`38cb922d1c`](https://github.com/baalexander/node-portscanner/commit/38cb922d1c)] - Uses callback in listen() instead of a timeout. (Brandon Alexander) 85 | * [[`aefd8ccad1`](https://github.com/baalexander/node-portscanner/commit/aefd8ccad1)] - Merge pull request #4 from DennisKehrig/master (Brandon Alexander) 86 | * [[`5b58f03421`](https://github.com/baalexander/node-portscanner/commit/5b58f03421)] - Call socket.destroy() on timeout (Dennis Kehrig) 87 | * [[`45028c6e3e`](https://github.com/baalexander/node-portscanner/commit/45028c6e3e)] - Updates version to 0.1.2. (Brandon Alexander) 88 | * [[`a60a248a2a`](https://github.com/baalexander/node-portscanner/commit/a60a248a2a)] - Fixes multiple callbacks when checking port status. (Brandon Alexander) 89 | * [[`19a8c1df2c`](https://github.com/baalexander/node-portscanner/commit/19a8c1df2c)] - Updates to v0.1.1. (Brandon Alexander) 90 | * [[`c81ae8d6e4`](https://github.com/baalexander/node-portscanner/commit/c81ae8d6e4)] - Checks range of ports one at a time. (Brandon Alexander) 91 | * [[`fe9726773d`](https://github.com/baalexander/node-portscanner/commit/fe9726773d)] - Only returns status of a port after connection closed. (Brandon Alexander) 92 | * [[`af6c474f38`](https://github.com/baalexander/node-portscanner/commit/af6c474f38)] - Ignores example and test directories in NPM. (Brandon Alexander) 93 | * [[`78be727cb4`](https://github.com/baalexander/node-portscanner/commit/78be727cb4)] - Initial release to NPM. (Brandon Alexander) 94 | * [[`cc71a028cb`](https://github.com/baalexander/node-portscanner/commit/cc71a028cb)] - Renames port finding functions for clarity. (Brandon Alexander) 95 | * [[`bb0356a82e`](https://github.com/baalexander/node-portscanner/commit/bb0356a82e)] - Quits scanning ports when a matching port has been found. (Brandon Alexander) 96 | * [[`24224b8148`](https://github.com/baalexander/node-portscanner/commit/24224b8148)] - Destroys the socket on error instead of end. (Brandon Alexander) 97 | * [[`e2c4448293`](https://github.com/baalexander/node-portscanner/commit/e2c4448293)] - Updates README since not yet ready for NPM. (Brandon Alexander) 98 | * [[`5cca315f8b`](https://github.com/baalexander/node-portscanner/commit/5cca315f8b)] - Packages up port scanner for NPM. (Brandon Alexander) 99 | * [[`8c1f11e76c`](https://github.com/baalexander/node-portscanner/commit/8c1f11e76c)] - Adds JSDocs and updates example code. (Brandon Alexander) 100 | * [[`4b432ba950`](https://github.com/baalexander/node-portscanner/commit/4b432ba950)] - Set max port range to 65535. (Brandon Alexander) 101 | * [[`3a78761e4f`](https://github.com/baalexander/node-portscanner/commit/3a78761e4f)] - Adds README and MIT license. (Brandon Alexander) 102 | * [[`e232efc85f`](https://github.com/baalexander/node-portscanner/commit/e232efc85f)] - Checks a range of ports for first open or closed port. (Brandon Alexander) 103 | * [[`8568c23e7c`](https://github.com/baalexander/node-portscanner/commit/8568c23e7c)] - Initial commit checks status of a specified port. (Brandon Alexander) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Brandon Ace Alexander 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # portscanner 2 | 3 | [![npm](https://img.shields.io/npm/v/portscanner.svg)](https://www.npmjs.com/package/portscanner) 4 | [![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) 5 | 6 | The portscanner module is 7 | an asynchronous JavaScript port scanner for Node.js. 8 | 9 | Portscanner can check a port, 10 | or range of ports, 11 | for 'open' or 'closed' statuses. 12 | 13 | [Looking for maintainer](https://github.com/baalexander/node-portscanner/issues/25)! 14 | 15 | ## Install 16 | 17 | ```bash 18 | npm install portscanner 19 | ``` 20 | 21 | ## Usage 22 | 23 | A brief example: 24 | 25 | ```javascript 26 | var portscanner = require('portscanner') 27 | 28 | // Checks the status of a single port 29 | portscanner.checkPortStatus(3000, '127.0.0.1', function(error, status) { 30 | // Status is 'open' if currently in use or 'closed' if available 31 | console.log(status) 32 | }) 33 | 34 | // Find the first available port. Asynchronously checks, so first port 35 | // determined as available is returned. 36 | portscanner.findAPortNotInUse(3000, 3010, '127.0.0.1', function(error, port) { 37 | console.log('AVAILABLE PORT AT: ' + port) 38 | }) 39 | 40 | // Find the first port in use or blocked. Asynchronously checks, so first port 41 | // to respond is returned. 42 | portscanner.findAPortInUse(3000, 3010, '127.0.0.1', function(error, port) { 43 | console.log('PORT IN USE AT: ' + port) 44 | }) 45 | 46 | // You can also pass array of ports to check 47 | portscanner.findAPortInUse([3000, 3005, 3006], '127.0.0.1', function(error, port) { 48 | console.log('PORT IN USE AT: ' + port) 49 | }) 50 | 51 | // And skip host param. Default is '127.0.0.1' 52 | portscanner.findAPortNotInUse(3000, 4000, function(error, port) { 53 | console.log('PORT IN USE AT: ' + port) 54 | }) 55 | 56 | // And use promises 57 | portscanner.findAPortNotInUse(3000, 4000).then(function(port) { 58 | console.log('PORT IN USE AT: ' + port) 59 | }) 60 | ``` 61 | 62 | The example directory contains a more detailed example. 63 | 64 | ## Test 65 | 66 | ```sh 67 | npm test 68 | ``` 69 | 70 | ## Future 71 | 72 | Please create issues or pull requests 73 | for port scanning related features 74 | you'd like to see included. 75 | 76 | ## License (MIT) 77 | 78 | [MIT](LICENSE) 79 | 80 | -------------------------------------------------------------------------------- /example/portscan.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | var portscanner = require('../lib/portscanner.js') 3 | 4 | // Sets up an HTTP server listening on port 3005 5 | var server = http.createServer(function (request, response) { 6 | }) 7 | 8 | server.listen(3005, '127.0.0.1', function () { 9 | // Checks the status of an individual port. 10 | portscanner.checkPortStatus(3005, '127.0.0.1', function (error, status) { 11 | // Status should be 'open' since the HTTP server is listening on that port 12 | console.log('Status at port 3005 is ' + status) 13 | if (error) console.error(error) 14 | }) 15 | portscanner.checkPortStatus(3000, '127.0.0.1', function (error, status) { 16 | // Status should be 'closed' since no service is listening on that port. 17 | console.log('Status at port 3000 is ' + status) 18 | if (error) console.error(error) 19 | }) 20 | 21 | // Finds a port that a service is listening on 22 | portscanner.findAPortInUse(3000, 3010, '127.0.0.1', function (error, port) { 23 | // Port should be 3005 as the HTTP server is listening on that port 24 | console.log('Found an open port at ' + port) 25 | if (error) console.error(error) 26 | }) 27 | // Finds a port no service is listening on 28 | portscanner.findAPortNotInUse(3000, 3010, '127.0.0.1', function (error, port) { 29 | // Will return any number between 3000 and 3010 (inclusive), that's not 3005. 30 | // The order is unknown as the port status checks are asynchronous. 31 | console.log('Found a closed port at ' + port) 32 | if (error) console.error(error) 33 | }) 34 | }) 35 | 36 | -------------------------------------------------------------------------------- /lib/portscanner.js: -------------------------------------------------------------------------------- 1 | var net = require('net') 2 | var Socket = net.Socket 3 | var async = require('async') 4 | var isNumberLike = require('is-number-like') 5 | var promisify = require('./promisify') 6 | 7 | /** 8 | * Finds the first port with a status of 'open', implying the port is in use and 9 | * there is likely a service listening on it. 10 | */ 11 | /** 12 | * @param {Number} startPort - Port to begin status check on (inclusive). 13 | * @param {Number} [endPort=65535] - Last port to check status on (inclusive). 14 | * @param {String} [host='127.0.0.1'] - Host of where to scan. 15 | * @param {findPortCallback} [callback] - Function to call back with error or results. 16 | * @returns {Promise} 17 | * @example 18 | * // scans through 3000 to 3002 (inclusive) 19 | * portscanner.findAPortInUse(3000, 3002, '127.0.0.1', console.log) 20 | * // returns a promise in the absence of a callback 21 | * portscanner.findAPortInUse(3000, 3002, '127.0.0.1').then(console.log) 22 | * @example 23 | * // scans through 3000 to 65535 on '127.0.0.1' 24 | * portscanner.findAPortInUse(3000, console.log) 25 | */ 26 | /** 27 | * @param {Array} postList - Array of ports to check status on. 28 | * @param {String} [host='127.0.0.1'] - Host of where to scan. 29 | * @param {findPortCallback} [callback] - Function to call back with error or results. 30 | * @returns {Promise} 31 | * @example 32 | * // scans 3000 and 3002 only, not 3001. 33 | * portscanner.findAPortInUse([3000, 3002], console.log) 34 | */ 35 | function findAPortInUse () { 36 | var params = [].slice.call(arguments) 37 | params.unshift('open') 38 | return findAPortWithStatus.apply(null, params) 39 | } 40 | 41 | /** 42 | * Finds the first port with a status of 'closed', implying the port is not in 43 | * use. Accepts identical parameters as {@link findAPortInUse} 44 | */ 45 | function findAPortNotInUse () { 46 | var params = [].slice.call(arguments) 47 | params.unshift('closed') 48 | return findAPortWithStatus.apply(null, params) 49 | } 50 | 51 | /** 52 | * Checks the status of an individual port. 53 | */ 54 | /** 55 | * @param {Number} port - Port to check status on. 56 | * @param {String} [host='127.0.0.1'] - Host of where to scan. 57 | * @param {checkPortCallback} [callback] - Function to call back with error or results. 58 | * @returns {Promise} 59 | */ 60 | /** 61 | * @param {Number} port - Port to check status on. 62 | * @param {Object} [opts={}] - Options object. 63 | * @param {String} [opts.host='127.0.0.1'] - Host of where to scan. 64 | * @param {Number} [opts.timeout=400] - Connection timeout in ms. 65 | * @param {checkPortCallback} [callback] - Function to call back with error or results. 66 | * @returns {Promise} 67 | */ 68 | function checkPortStatus (port) { 69 | var args, host, opts, callback 70 | 71 | args = [].slice.call(arguments, 1) 72 | 73 | if (typeof args[0] === 'string') { 74 | host = args[0] 75 | } else if (typeof args[0] === 'object') { 76 | opts = args[0] 77 | } else if (typeof args[0] === 'function') { 78 | callback = args[0] 79 | } 80 | 81 | if (typeof args[1] === 'object') { 82 | opts = args[1] 83 | } else if (typeof args[1] === 'function') { 84 | callback = args[1] 85 | } 86 | 87 | if (typeof args[2] === 'function') { 88 | callback = args[2] 89 | } 90 | 91 | if (!callback) return promisify(checkPortStatus, arguments) 92 | 93 | opts = opts || {} 94 | 95 | host = host || opts.host || '127.0.0.1' 96 | 97 | var timeout = opts.timeout || 400 98 | var connectionRefused = false 99 | 100 | var socket = new Socket() 101 | var status = null 102 | var error = null 103 | 104 | // Socket connection established, port is open 105 | socket.on('connect', function () { 106 | status = 'open' 107 | socket.destroy() 108 | }) 109 | 110 | // If no response, assume port is not listening 111 | socket.setTimeout(timeout) 112 | socket.on('timeout', function () { 113 | status = 'closed' 114 | error = new Error('Timeout (' + timeout + 'ms) occurred waiting for ' + host + ':' + port + ' to be available') 115 | socket.destroy() 116 | }) 117 | 118 | // Assuming the port is not open if an error. May need to refine based on 119 | // exception 120 | socket.on('error', function (exception) { 121 | if (exception.code !== 'ECONNREFUSED') { 122 | error = exception 123 | } else { 124 | connectionRefused = true 125 | } 126 | status = 'closed' 127 | }) 128 | 129 | // Return after the socket has closed 130 | socket.on('close', function (exception) { 131 | if (exception && !connectionRefused) { error = error || exception } else { error = null } 132 | callback(error, status) 133 | }) 134 | 135 | socket.connect(port, host) 136 | } 137 | /** 138 | * Callback for {@link checkPortStatus} 139 | * @callback checkPortCallback 140 | * @param {Error|null} error - Any error that occurred while port scanning, or null. 141 | * @param {String} status - Status: 'open' if the port is in use, 'closed' if the port is available. 142 | */ 143 | 144 | /** 145 | * Internal helper function used by {@link findAPortInUse} and {@link findAPortNotInUse} 146 | * to find a port from a range or a list with a specific status. 147 | */ 148 | /** 149 | * @param {String} status - Status to check. 150 | * @param {...params} params - Params as passed exactly to {@link findAPortInUse} and {@link findAPortNotInUse}. 151 | */ 152 | function findAPortWithStatus (status) { 153 | var params, startPort, endPort, portList, host, opts, callback 154 | 155 | params = [].slice.call(arguments, 1) 156 | 157 | if (params[0] instanceof Array) { 158 | portList = params[0] 159 | } else if (isNumberLike(params[0])) { 160 | startPort = parseInt(params[0], 10) 161 | } 162 | 163 | if (typeof params[1] === 'function') { 164 | callback = params[1] 165 | } else if (typeof params[1] === 'string') { 166 | host = params[1] 167 | } else if (typeof params[1] === 'object') { 168 | opts = params[1] 169 | } else if (isNumberLike(params[1])) { 170 | endPort = parseInt(params[1], 10) 171 | } 172 | 173 | if (typeof params[2] === 'string') { 174 | host = params[2] 175 | } else if (typeof params[2] === 'object') { 176 | opts = params[2] 177 | } else if (typeof params[2] === 'function') { 178 | callback = params[2] 179 | } 180 | 181 | if (typeof params[3] === 'function') { 182 | callback = params[3] 183 | } 184 | 185 | if (!callback) return promisify(findAPortWithStatus, arguments) 186 | 187 | opts = opts || {} 188 | 189 | host = host || opts.host 190 | 191 | if (startPort && endPort && endPort < startPort) { 192 | // WARNING: endPort less than startPort. Using endPort as startPort & vice versa. 193 | var tempStartPort = startPort 194 | startPort = endPort 195 | endPort = tempStartPort 196 | } 197 | 198 | endPort = endPort || 65535 199 | 200 | var foundPort = false 201 | var numberOfPortsChecked = 0 202 | var port = portList ? portList[0] : startPort 203 | 204 | // Returns true if a port with matching status has been found or if checked 205 | // the entire range of ports 206 | var hasFoundPort = function () { 207 | return foundPort || numberOfPortsChecked === (portList ? portList.length : endPort - startPort + 1) 208 | } 209 | 210 | // Checks the status of the port 211 | var checkNextPort = function (callback) { 212 | checkPortStatus(port, host, opts, function (error, statusOfPort) { 213 | numberOfPortsChecked++ 214 | if (statusOfPort === status) { 215 | foundPort = true 216 | callback(error) 217 | } else { 218 | port = portList ? portList[numberOfPortsChecked] : port + 1 219 | callback(null) 220 | } 221 | }) 222 | } 223 | 224 | // Check the status of each port until one with a matching status has been 225 | // found or the range of ports has been exhausted 226 | async.until(hasFoundPort, checkNextPort, function (error) { 227 | if (error) { 228 | callback(error, port) 229 | } else if (foundPort) { 230 | callback(null, port) 231 | } else { 232 | callback(null, false) 233 | } 234 | }) 235 | } 236 | /** 237 | * Callback for {@link findAPortWithStatus}, and by that extension, for {@link findAPortInUse} and {@link findAPortNotInUse}. 238 | * @callback findPortCallback 239 | * @param {Error|null} error - Any error that occurred while port scanning, or null. 240 | * @param {Number|Boolean} port - The first open port found. Note, this is the first port that returns status as 'open', not necessarily the first open port checked. If no open port is found, the value is false. 241 | */ 242 | 243 | /** 244 | * @exports portscanner 245 | */ 246 | 247 | module.exports = { 248 | findAPortInUse: findAPortInUse, 249 | findAPortNotInUse: findAPortNotInUse, 250 | checkPortStatus: checkPortStatus 251 | } 252 | -------------------------------------------------------------------------------- /lib/promisify.js: -------------------------------------------------------------------------------- 1 | module.exports = promisify 2 | 3 | function promisify (fn, args) { 4 | if (typeof Promise === 'undefined') { 5 | throw new Error('Please run in a Promise supported environment or provide a callback') 6 | } 7 | return new Promise(function (resolve, reject) { 8 | args = [].slice.call(args).concat([callback]) 9 | fn.apply(null, args) 10 | 11 | function callback (error, port) { 12 | if (error || port === false) { 13 | reject(error || new Error('No open port found')) 14 | } else { 15 | resolve(port) 16 | } 17 | } 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "portscanner", 3 | "description": "Asynchronous port scanner for Node.js", 4 | "scripts": { 5 | "coverage": "nyc npm run test", 6 | "test": "ava", 7 | "lint": "standard" 8 | }, 9 | "keywords": [ 10 | "portscanner", 11 | "port", 12 | "scanner", 13 | "checker", 14 | "status" 15 | ], 16 | "version": "2.2.0", 17 | "preferGlobal": false, 18 | "homepage": "https://github.com/baalexander/node-portscanner", 19 | "author": [ 20 | "Brandon Alexander (https://github.com/baalexander)", 21 | "Sean Massa (http://massalabs.com)" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/baalexander/node-portscanner.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/baalexander/node-portscanner/issues" 29 | }, 30 | "directories": { 31 | "lib": "./lib" 32 | }, 33 | "main": "./lib/portscanner.js", 34 | "dependencies": { 35 | "async": "^2.6.0", 36 | "is-number-like": "^1.0.3" 37 | }, 38 | "devDependencies": { 39 | "ava": "^0.25.0", 40 | "nyc": "^11.3.0", 41 | "eslint": "^3.10.2", 42 | "eslint-config-standard": "^6.2.1", 43 | "standard": "^8.5.0" 44 | }, 45 | "engines": { 46 | "node": ">=0.4", 47 | "npm": ">=1.0.0" 48 | }, 49 | "license": "MIT" 50 | } 51 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import net from 'net' 2 | import test from 'ava' 3 | import portScanner from '.' 4 | 5 | initialize() 6 | checkPortStatus() 7 | findPortInUse() 8 | findPortNotInUse() 9 | promise() 10 | reverseOrder() 11 | portsAsStrings() 12 | 13 | function initialize () { 14 | test.before.cb('Set #1 test server', t => { 15 | const server = net.createServer() 16 | server.listen(3000, '127.0.0.1', t.end) 17 | }) 18 | test.before.cb('Set #2 test server', t => { 19 | const server2 = net.createServer() 20 | server2.listen(2999, '127.0.0.1', t.end) 21 | }) 22 | } 23 | 24 | function checkPortStatus () { 25 | test.cb('checkPortStatus - taken', t => { 26 | t.plan(2) 27 | 28 | portScanner.checkPortStatus(3000, '127.0.0.1', (error, port) => { 29 | t.is(error, null) 30 | t.is(port, 'open') 31 | t.end() 32 | }) 33 | }) 34 | test.cb('checkPortStatus - taken (with host in options)', t => { 35 | t.plan(2) 36 | 37 | portScanner.checkPortStatus(3000, { host: '127.0.0.1' }, (error, port) => { 38 | t.is(error, null) 39 | t.is(port, 'open') 40 | t.end() 41 | }) 42 | }) 43 | test.cb('checkPortStatus - taken (with host as string and options)', t => { 44 | t.plan(2) 45 | 46 | portScanner.checkPortStatus(3000, '127.0.0.1', { timeout: 400 }, (error, port) => { 47 | t.is(error, null) 48 | t.is(port, 'open') 49 | t.end() 50 | }) 51 | }) 52 | test.cb('checkPortStatus - taken (without host)', t => { 53 | t.plan(2) 54 | 55 | portScanner.checkPortStatus(3000, (error, port) => { 56 | t.is(error, null) 57 | t.is(port, 'open') 58 | t.end() 59 | }) 60 | }) 61 | test.cb('checkPortStatus - free', t => { 62 | t.plan(2) 63 | 64 | portScanner.checkPortStatus(3001, '127.0.0.1', (error, port) => { 65 | t.is(error, null) 66 | t.is(port, 'closed') 67 | t.end() 68 | }) 69 | }) 70 | test.cb('checkPortStatus - free (with host in options argument)', t => { 71 | t.plan(2) 72 | 73 | portScanner.checkPortStatus(3001, { host: '127.0.0.1' }, (error, port) => { 74 | t.is(error, null) 75 | t.is(port, 'closed') 76 | t.end() 77 | }) 78 | }) 79 | test.cb('checkPortStatus - free (with host as string and options)', t => { 80 | t.plan(2) 81 | 82 | portScanner.checkPortStatus(3001, '127.0.0.1', { timeout: 400 }, (error, port) => { 83 | t.is(error, null) 84 | t.is(port, 'closed') 85 | t.end() 86 | }) 87 | }) 88 | test.cb('checkPortStatus - free (without host)', t => { 89 | t.plan(2) 90 | 91 | portScanner.checkPortStatus(3001, (error, port) => { 92 | t.is(error, null) 93 | t.is(port, 'closed') 94 | t.end() 95 | }) 96 | }) 97 | test.cb('checkPortStatus - timeout', t => { 98 | t.plan(2) 99 | 100 | portScanner.checkPortStatus(3001, { host: '127.0.0.1', timeout: 1 }, (error, port) => { 101 | t.is(error, null) 102 | t.is(port, 'closed') 103 | t.end() 104 | }) 105 | }) 106 | test.cb('checkPortStatus - error', t => { 107 | t.plan(1) 108 | 109 | portScanner.checkPortStatus(3001, '127.0.0.0', (error, port) => { 110 | t.is(error.code, 'ENETUNREACH') 111 | t.end() 112 | }) 113 | }) 114 | } 115 | 116 | function findPortInUse () { 117 | test.cb('findAPortInUse - taken port in range', t => { 118 | t.plan(2) 119 | 120 | portScanner.findAPortInUse(3000, 3010, '127.0.0.1', (error, port) => { 121 | t.is(error, null) 122 | t.is(port, 3000) 123 | t.end() 124 | }) 125 | }) 126 | test.cb('findAPortInUse - taken port in range (without host)', t => { 127 | t.plan(2) 128 | 129 | portScanner.findAPortInUse(3000, 3010, (error, port) => { 130 | t.is(error, null) 131 | t.is(port, 3000) 132 | t.end() 133 | }) 134 | }) 135 | test.cb('findAPortInUse - taken port in range (with host in options)', t => { 136 | t.plan(2) 137 | 138 | portScanner.findAPortInUse(3000, 3010, { host: '127.0.0.1' }, (error, port) => { 139 | t.is(error, null) 140 | t.is(port, 3000) 141 | t.end() 142 | }) 143 | }) 144 | test.cb('findAPortInUse - taken port in range (without endPort)', t => { 145 | t.plan(2) 146 | 147 | portScanner.findAPortInUse(3000, '127.0.0.1', (error, port) => { 148 | t.is(error, null) 149 | t.is(port, 3000) 150 | t.end() 151 | }) 152 | }) 153 | test.cb('findAPortInUse - taken port in range (without endPort and host) ', t => { 154 | t.plan(2) 155 | 156 | portScanner.findAPortInUse(3000, (error, port) => { 157 | t.is(error, null) 158 | t.is(port, 3000) 159 | t.end() 160 | }) 161 | }) 162 | test.cb('findAPortInUse - taken port in range (without endPort and host in options)', t => { 163 | t.plan(2) 164 | 165 | portScanner.findAPortInUse(3000, { host: '127.0.0.1' }, (error, port) => { 166 | t.is(error, null) 167 | t.is(port, 3000) 168 | t.end() 169 | }) 170 | }) 171 | test.cb('findAPortInUse - taken port in range - ports as array', t => { 172 | t.plan(2) 173 | 174 | portScanner.findAPortInUse([2999, 3000, 3001], '127.0.0.1', (error, port) => { 175 | t.is(error, null) 176 | t.is(port, 2999) 177 | t.end() 178 | }) 179 | }) 180 | test.cb('findAPortInUse - taken port in range - ports as array', t => { 181 | t.plan(2) 182 | 183 | portScanner.findAPortInUse([2999, 3000, 3001], '127.0.0.1', (error, port) => { 184 | t.is(error, null) 185 | t.is(port, 2999) 186 | t.end() 187 | }) 188 | }) 189 | test.cb('findAPortInUse - taken port in range - ports as array (without host)', t => { 190 | t.plan(2) 191 | 192 | portScanner.findAPortInUse([2999, 3000, 3001], (error, port) => { 193 | t.is(error, null) 194 | t.is(port, 2999) 195 | t.end() 196 | }) 197 | }) 198 | test.cb('findAPortInUse - taken port in range - ports as array (with host on options)', t => { 199 | t.plan(2) 200 | 201 | portScanner.findAPortInUse([2999, 3000, 3001], { host: '127.0.0.1' }, (error, port) => { 202 | t.is(error, null) 203 | t.is(port, 2999) 204 | t.end() 205 | }) 206 | }) 207 | test.cb('findAPortInUse - all ports in range free', t => { 208 | t.plan(2) 209 | 210 | portScanner.findAPortInUse(3001, 3010, '127.0.0.1', (error, port) => { 211 | t.is(error, null) 212 | t.false(port) 213 | t.end() 214 | }) 215 | }) 216 | test.cb('findAPortInUse - all ports in range free - ports as array', t => { 217 | t.plan(2) 218 | 219 | portScanner.findAPortInUse([3001, 3005, 3008], '127.0.0.1', (error, port) => { 220 | t.is(error, null) 221 | t.false(port) 222 | t.end() 223 | }) 224 | }) 225 | test.cb('findAPortInUse - all ports in range free - ports as array (with host in options)', t => { 226 | t.plan(2) 227 | 228 | portScanner.findAPortInUse([3001, 3005, 3008], { host: '127.0.0.1' }, (error, port) => { 229 | t.is(error, null) 230 | t.false(port) 231 | t.end() 232 | }) 233 | }) 234 | test.cb('findAPortInUse - all ports in range free - ports as array (without host)', t => { 235 | t.plan(2) 236 | 237 | portScanner.findAPortInUse([3001, 3005, 3008], (error, port) => { 238 | t.is(error, null) 239 | t.false(port) 240 | t.end() 241 | }) 242 | }) 243 | test('findAPortInUse - no promise in env', t => { 244 | t.plan(1) 245 | 246 | var oldPromise = Promise 247 | 248 | // eslint-disable-next-line 249 | Promise = undefined 250 | 251 | t.throws(() => portScanner.findAPortInUse(3001, 3010, { host: '127.0.0.1' })) 252 | 253 | // eslint-disable-next-line 254 | Promise = oldPromise 255 | }) 256 | } 257 | 258 | function findPortNotInUse () { 259 | test.cb('findAPortNotInUse - start from free port', t => { 260 | t.plan(2) 261 | 262 | portScanner.findAPortNotInUse(3001, 3010, '127.0.0.1', (error, port) => { 263 | t.is(error, null) 264 | t.is(port, 3001) 265 | t.end() 266 | }) 267 | }) 268 | test.cb('findAPortNotInUse - start from taken port', t => { 269 | t.plan(2) 270 | 271 | portScanner.findAPortNotInUse(3000, 3010, '127.0.0.1', (error, port) => { 272 | t.is(error, null) 273 | t.is(port, 3001) 274 | t.end() 275 | }) 276 | }) 277 | test.cb('findAPortNotInUse - start from taken port (with host in options)', t => { 278 | t.plan(2) 279 | 280 | portScanner.findAPortNotInUse(3000, 3010, { host: '127.0.0.1' }, (error, port) => { 281 | t.is(error, null) 282 | t.is(port, 3001) 283 | t.end() 284 | }) 285 | }) 286 | test.cb('findAPortNotInUse - all ports in range taken', t => { 287 | t.plan(2) 288 | 289 | portScanner.findAPortNotInUse(2999, 3000, '127.0.0.1', (error, port) => { 290 | t.is(error, null) 291 | t.false(port) 292 | t.end() 293 | }) 294 | }) 295 | test.cb('findAPortNotInUse - all ports in range taken (with host in options)', t => { 296 | t.plan(2) 297 | 298 | portScanner.findAPortNotInUse(2999, 3000, { host: '127.0.0.1' }, (error, port) => { 299 | t.is(error, null) 300 | t.false(port) 301 | t.end() 302 | }) 303 | }) 304 | test.cb('findAPortNotInUse - with array as parameter', t => { 305 | t.plan(2) 306 | 307 | portScanner.findAPortNotInUse([3000, 3002, 2999], '127.0.0.1', (error, port) => { 308 | t.is(error, null) 309 | t.is(port, 3002) 310 | t.end() 311 | }) 312 | }) 313 | test.cb('findAPortNotInUse - with array as parameter (with host in options)', t => { 314 | t.plan(2) 315 | 316 | portScanner.findAPortNotInUse([3000, 3002, 2999], { host: '127.0.0.1' }, (error, port) => { 317 | t.is(error, null) 318 | t.is(port, 3002) 319 | t.end() 320 | }) 321 | }) 322 | test('findAPortNotInUse - promise (error) (no promise in env)', t => { 323 | t.plan(1) 324 | 325 | var oldPromise = Promise 326 | 327 | // eslint-disable-next-line 328 | Promise = undefined 329 | 330 | t.throws(() => portScanner.findAPortNotInUse(3001, 3010, { host: '127.0.0.1' })) 331 | 332 | // eslint-disable-next-line 333 | Promise = oldPromise 334 | }) 335 | } 336 | 337 | function promise () { 338 | test.cb('findAPortNotInUse - promise', t => { 339 | t.plan(1) 340 | 341 | portScanner.findAPortNotInUse(3000, 3010, '127.0.0.1').then(port => { 342 | t.is(port, 3001) 343 | t.end() 344 | }) 345 | }) 346 | test.cb('findAPortNotInUse - promise (without host)', t => { 347 | t.plan(1) 348 | 349 | portScanner.findAPortNotInUse(3000, 3010).then(port => { 350 | t.is(port, 3001) 351 | t.end() 352 | }) 353 | }) 354 | test.cb('findAPortNotInUse - promise (with host in options)', t => { 355 | t.plan(1) 356 | 357 | portScanner.findAPortNotInUse(3000, 3010, { host: '127.0.0.1' }).then(port => { 358 | t.is(port, 3001) 359 | t.end() 360 | }) 361 | }) 362 | test.cb('findAPortNotInUse - promise (without host and endPort)', t => { 363 | t.plan(1) 364 | 365 | portScanner.findAPortNotInUse(3000).then(port => { 366 | t.is(port, 3001) 367 | t.end() 368 | }) 369 | }) 370 | test.cb('findAPortInUse - promise', t => { 371 | t.plan(1) 372 | 373 | portScanner.findAPortInUse(3000, 3010, '127.0.0.1').then(port => { 374 | t.is(port, 3000) 375 | t.end() 376 | }) 377 | }) 378 | test.cb('findAPortInUse - promise (with host in options)', t => { 379 | t.plan(1) 380 | 381 | portScanner.findAPortInUse(3000, 3010, { host: '127.0.0.1' }).then(port => { 382 | t.is(port, 3000) 383 | t.end() 384 | }) 385 | }) 386 | test.cb('findAPortInUse - promise (error)', t => { 387 | t.plan(1) 388 | 389 | portScanner.findAPortInUse(3001, 3010, '127.0.0.1').catch(err => { 390 | t.not(err, null) 391 | t.end() 392 | }) 393 | }) 394 | test.cb('findAPortInUse - promise (error) (with host in options)', t => { 395 | t.plan(1) 396 | 397 | portScanner.findAPortInUse(3001, 3010, { host: '127.0.0.1' }).catch(err => { 398 | t.not(err, null) 399 | t.end() 400 | }) 401 | }) 402 | } 403 | 404 | function reverseOrder () { 405 | test.cb('findAPortNotInUse - ports in reverse order, lowest one being in use', t => { 406 | t.plan(2) 407 | 408 | portScanner.findAPortNotInUse(3005, 3000, '127.0.0.1', (error, port) => { 409 | t.is(error, null) 410 | t.is(port, 3001) 411 | t.end() 412 | }) 413 | }) 414 | test.cb('findAPortNotInUse - ports in reverse order, highest one being in use', t => { 415 | t.plan(2) 416 | 417 | portScanner.findAPortNotInUse(3000, 2995, '127.0.0.1', (error, port) => { 418 | t.is(error, null) 419 | t.is(port, 2995) 420 | t.end() 421 | }) 422 | }) 423 | } 424 | 425 | function portsAsStrings () { 426 | test.cb('checkPortStatus - taken (port as a string)', t => { 427 | t.plan(2) 428 | 429 | portScanner.checkPortStatus('3000', '127.0.0.1', (error, port) => { 430 | t.is(error, null) 431 | t.is(port, 'open') 432 | t.end() 433 | }) 434 | }) 435 | test.cb('checkPortStatus - taken (without host) (port as a string)', t => { 436 | t.plan(2) 437 | 438 | portScanner.checkPortStatus('3000', (error, port) => { 439 | t.is(error, null) 440 | t.is(port, 'open') 441 | t.end() 442 | }) 443 | }) 444 | test.cb('checkPortStatus - free (port as a string)', t => { 445 | t.plan(2) 446 | 447 | portScanner.checkPortStatus('3001', '127.0.0.1', (error, port) => { 448 | t.is(error, null) 449 | t.is(port, 'closed') 450 | t.end() 451 | }) 452 | }) 453 | test.cb('checkPortStatus - free (without host) (port as a string)', t => { 454 | t.plan(2) 455 | 456 | portScanner.checkPortStatus('3001', (error, port) => { 457 | t.is(error, null) 458 | t.is(port, 'closed') 459 | t.end() 460 | }) 461 | }) 462 | test.cb('findPortInUse - taken port in range (startPort as a string)', t => { 463 | t.plan(2) 464 | 465 | portScanner.findAPortInUse('2990', 3010, '127.0.0.1', (error, port) => { 466 | t.is(error, null) 467 | t.is(port, 2999) 468 | t.end() 469 | }) 470 | }) 471 | test.cb('findPortInUse - taken port in range (endPort as a string)', t => { 472 | t.plan(2) 473 | 474 | portScanner.findAPortInUse(2990, '3010', '127.0.0.1', (error, port) => { 475 | t.is(error, null) 476 | t.is(port, 2999) 477 | t.end() 478 | }) 479 | }) 480 | test.cb('findPortInUse - taken port in range (startPort and endPort as strings)', t => { 481 | t.plan(2) 482 | 483 | portScanner.findAPortInUse('3000', '3010', '127.0.0.1', (error, port) => { 484 | t.is(error, null) 485 | t.is(port, 3000) 486 | t.end() 487 | }) 488 | }) 489 | } 490 | --------------------------------------------------------------------------------