UNPKG

7.67 kBJavaScriptView Raw
1var net = require('net')
2var Socket = net.Socket
3var async = require('async')
4var isNumberLike = require('is-number-like')
5var 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 */
35function 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 */
45function 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 */
68function 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 */
152function 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
247module.exports = {
248 findAPortInUse: findAPortInUse,
249 findAPortNotInUse: findAPortNotInUse,
250 checkPortStatus: checkPortStatus
251}