# net/portprober.js

    1// Copyright 2013 Selenium committers2// Copyright 2013 Software Freedom Conservancy3//4// Licensed under the Apache License, Version 2.0 (the "License");5// you may not use this file except in compliance with the License.6//     You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing, software11// distributed under the License is distributed on an "AS IS" BASIS,12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13// See the License for the specific language governing permissions and14// limitations under the License.1516'use strict';1718var exec = require('child_process').exec,19    fs = require('fs'),20    net = require('net');2122var promise = require('../index').promise;232425/**26 * The IANA suggested ephemeral port range.27 * @type {{min: number, max: number}}28 * @const29 * @see http://en.wikipedia.org/wiki/Ephemeral_ports30 */31var DEFAULT_IANA_RANGE = {min: 49152, max: 65535};323334/**35 * The epheremal port range for the current system. Lazily computed on first36 * access.37 * @type {webdriver.promise.Promise.<{min: number, max: number}>}38 */39var systemRange = null;404142/**43 * Computes the ephemeral port range for the current system. This is based on44 * http://stackoverflow.com/a/924337.45 * @return {webdriver.promise.Promise.<{min: number, max: number}>} A promise46 *     that will resolve to the ephemeral port range of the current system.47 */48function findSystemPortRange() {49  if (systemRange) {50    return systemRange;51  }52  var range = process.platform === 'win32' ?53      findWindowsPortRange() : findUnixPortRange();54  return systemRange = range.thenCatch(function() {55    return DEFAULT_IANA_RANGE;56  });57}585960/**61 * Executes a command and returns its output if it succeeds.62 * @param {string} cmd The command to execute.63 * @return {!webdriver.promise.Promise<string>} A promise that will resolve64 *     with the command's stdout data.65 */66function execute(cmd) {67  var result = promise.defer();68  exec(cmd, function(err, stdout) {69    if (err) {70      result.reject(err);71    } else {72      result.fulfill(stdout);73    }74  });75  return result.promise;76}777879/**80 * Computes the ephemeral port range for a Unix-like system.81 * @return {!webdriver.promise.Promise<{min: number, max: number}>} A promise82 *     that will resolve with the ephemeral port range on the current system.83 */84function findUnixPortRange() {85  var cmd;86  if (process.platform === 'sunos') {87    cmd =88        '/usr/sbin/ndd /dev/tcp tcp_smallest_anon_port tcp_largest_anon_port';89  } else if (fs.existsSync('/proc/sys/net/ipv4/ip_local_port_range')) {90    // Linux91    cmd = 'cat /proc/sys/net/ipv4/ip_local_port_range';92  } else {93    cmd = 'sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last' +94        ' | sed -e "s/.*:\\s*//"';95  }9697  return execute(cmd).then(function(stdout) {98    if (!stdout || !stdout.length) return DEFAULT_IANA_RANGE;99    var range = stdout.trim().split(/\s+/).map(Number);100    if (range.some(isNaN)) return DEFAULT_IANA_RANGE;101    return {min: range[0], max: range[1]};102  });103}104105106/**107 * Computes the ephemeral port range for a Windows system.108 * @return {!webdriver.promise.Promise<{min: number, max: number}>} A promise109 *     that will resolve with the ephemeral port range on the current system.110 */111function findWindowsPortRange() {112  var deferredRange = promise.defer();113  // First, check if we're running on XP.  If this initial command fails,114  // we just fallback on the default IANA range.115  return execute('cmd.exe /c ver').then(function(stdout) {116    if (/Windows XP/.test(stdout)) {117      // TODO: Try to read these values from the registry.118      return {min: 1025, max: 5000};119    } else {120      return execute('netsh int ipv4 show dynamicport tcp').121          then(function(stdout) {122            /* > netsh int ipv4 show dynamicport tcp123              Protocol tcp Dynamic Port Range124              ---------------------------------125              Start Port : 49152126              Number of Ports : 16384127             */128            var range = stdout.split(/\n/).filter(function(line) {129              return /.*:\s*\d+/.test(line);130            }).map(function(line) {131              return Number(line.split(/:\s*/)[1]);132            });133134            return {135              min: range[0],136              max: range[0] + range[1]137            };138          });139    }140  });141}142143144/**145 * Tests if a port is free.146 * @param {number} port The port to test.147 * @param {string=} opt_host The bound host to test the {@code port} against.148 *     Defaults to {@code INADDR_ANY}.149 * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve150 *     with whether the port is free.151 */152function isFree(port, opt_host) {153  var result = promise.defer(function() {154    server.cancel();155  });156157  var server = net.createServer().on('error', function(e) {158    if (e.code === 'EADDRINUSE') {159      result.fulfill(false);160    } else {161      result.reject(e);162    }163  });164165  server.listen(port, opt_host, function() {166    server.close(function() {167      result.fulfill(true);168    });169  });170171  return result.promise;172}173174175/**176 * @param {string=} opt_host The bound host to test the {@code port} against.177 *     Defaults to {@code INADDR_ANY}.178 * @return {!webdriver.promise.Promise.<number>} A promise that will resolve179 *     to a free port. If a port cannot be found, the promise will be180 *     rejected.181 */182function findFreePort(opt_host) {183  return findSystemPortRange().then(function(range) {184    var attempts = 0;185    var deferredPort = promise.defer();186    findPort();187    return deferredPort.promise;188189    function findPort() {190      attempts += 1;191      if (attempts > 10) {192        deferredPort.reject(Error('Unable to find a free port'));193      }194195      var port = Math.floor(196          Math.random() * (range.max - range.min) + range.min);197      isFree(port, opt_host).then(function(isFree) {198        if (isFree) {199          deferredPort.fulfill(port);200        } else {201          findPort();202        }203      });204    }205  });206}207208209// PUBLIC API210211212exports.findFreePort = findFreePort;213exports.isFree = isFree;