module Unobtainium::Support::PortScanner
A port scanner for finding a free port for running e.g. a selenium or appium server.
Constants
- MAX_RETRIES
Retry a port this many times before failing
- RETRY_DELAY
Delay each retry by this many seconds before trying again
Public Instance Methods
port_open?(host, port, domains = %i[INET INET6])
click to toggle source
Returns true if the port is open on the host, false otherwise. @param host [String] host name or IP address @param port [Integer] port number (1..65535) @param domains [Array/Symbol] :INET, :INET6, etc. or an Array of
these. Any from Socket::Constants::AF_* work. Defaults to %i[INET INET6].
# File lib/unobtainium/support/port_scanner.rb, line 46 def port_open?(host, port, domains = %i[INET INET6]) if port < 1 or port > 65535 raise ArgumentError, "Port must be in range 1..65535!" end test_domains = nil if domains.is_a? Array test_domains = domains.dup else test_domains = [domains] end test_domains.each do |domain| if not DOMAINS.include?(domain) raise ArgumentError, "Domains must be one of #{DOMAINS}, or an Array "\ "of them, but #{domain} isn't!" end end # Test a socket for each domain test_domains.each do |domain| addr = get_addr(host, port, domain) if addr.nil? next end if test_sockaddr(addr, domain) return true end end return false end
scan(host, *args)
click to toggle source
Scan a mixture of ranges and arrays of ports for a given host. Return those that are open or closed, depending on the options given.
# File lib/unobtainium/support/port_scanner.rb, line 84 def scan(host, *args) # Argument checks if args.empty? raise ArgumentError, "Need at least one port to scan!" end args.each do |item| if not item.respond_to?(:each) and not item.respond_to?(:to_i) raise ArgumentError, "The argument '#{item}' to #scan is not a "\ "Range, Array or convertible to Integer, aborting!" end end # If the last argument is a Hash, treat it as options. opts = {} if args.last.is_a? Hash opts = args.pop end opts = { for: :open, amount: :all }.merge(opts) if not %i[all first].include?(opts[:amount]) raise ArgumentError, ":amount must be one of :all, :first!" end if not %i[open closed available].include?(opts[:for]) raise ArgumentError, ":for must beone of :open, :closed, :available!" end return run_scan(host, opts, *args) end
Private Instance Methods
get_addr(host, port, domain)
click to toggle source
Create an address for the domain. That's a little convoluted, but it avoids errors with trying to use INET addresses with INET6 and vice versa.
# File lib/unobtainium/support/port_scanner.rb, line 163 def get_addr(host, port, domain) begin infos = Addrinfo.getaddrinfo(host, port, domain, :STREAM) infos.each do |info| if info.pfamily == Socket.const_get('PF_' + domain.to_s) return info.to_sockaddr end end rescue SocketError # Host does not resolve in this domain return nil end return nil end
run_scan(host, opts, *args)
click to toggle source
# File lib/unobtainium/support/port_scanner.rb, line 116 def run_scan(host, opts, *args) results = [] # Iteratively scan all arguments args.each do |item| if item.respond_to?(:to_i) item_i = item.to_i if test_port(host, item_i, opts[:for]) results << item_i if opts[:amount] == :first return results end end next end item.each do |port| if not test_port(host, port, opts[:for]) next end results << port if opts[:amount] == :first return results end end end return results end
test_port(host, port, test_for)
click to toggle source
# File lib/unobtainium/support/port_scanner.rb, line 147 def test_port(host, port, test_for) open = port_open?(host, port) if open and :open == test_for return true end if not open and %i[closed available].include?(test_for) return true end return false end
test_sockaddr(addr, domain)
click to toggle source
Test a particular sockaddr
# File lib/unobtainium/support/port_scanner.rb, line 180 def test_sockaddr(addr, domain) sock = Socket.new(domain, :STREAM) connected = false tries = MAX_RETRIES loop do begin sock.connect_nonblock(addr) rescue Errno::EINPROGRESS tries -= 1 if tries <= 0 # That's it, we've got enough. break end # The result of select doesn't matter. What matters is that we wait # for sock to become usable, or for the timeout to occur. IO.select([sock], [sock], nil, RETRY_DELAY) rescue Errno::EISCONN connected = true break rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH # Could not connect break rescue Errno::EINVAL, Errno::EAFNOSUPPORT # Unsupported protocol break rescue Errno::EADDRNOTAVAIL # Address not available break end end sock.close return connected end