Class: IPAM::PingRandomDb

Inherits:
Base
  • Object
show all
Defined in:
app/services/ipam/ping_random_db.rb

Constant Summary collapse

MAX_ITERATIONS =

Safety check not to spend much CPU time when there are no many free IPs left. This gives up in about a second on Ryzen 1700 running with Ruby 2.4.

100_000

Instance Method Summary collapse

Instance Method Details

#generatorObject



6
7
8
# File 'app/services/ipam/ping_random_db.rb', line 6

def generator
  @generator ||= Random.new(mac ? mac.delete(':').to_i(16) : Random.new_seed)
end

#icmp_pingable?(ip) ⇒ Boolean

Returns:

  • (Boolean)


56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'app/services/ipam/ping_random_db.rb', line 56

def icmp_pingable? ip
  # Always shell to ping, instead of using net-ping
  if RUBY_PLATFORM =~ /mingw/
    # Windows uses different options for ping and does not have /dev/null
    system("ping -n 2 -w 1000 #{ip} > NUL")
  else
    # Default to Linux ping options and send to /dev/null
    system("ping -c 2 -W 1 #{ip} > /dev/null")
  end
rescue => err
  logger.debug "Unable to icmp ping #{ip} because #{err.inspect}."
  true
end

#random_ip(range) ⇒ Object



10
11
12
# File 'app/services/ipam/ping_random_db.rb', line 10

def random_ip(range)
  IPAddr.new(generator.rand(range.first.to_i..range.last.to_i), subnet.family)
end

#suggest_ipObject



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'app/services/ipam/ping_random_db.rb', line 74

def suggest_ip
  iterations = 0
  # Remove IPs already excluded or known.
  range = subnet_range.to_a - excluded_ips.to_a
  range -= subnet.known_ips.to_a

  loop do
    # next random IP from the sequence generated by MAC seed
    candidate = random_ip(range)
    iterations += 1
    break if iterations >= MAX_ITERATIONS
    # try to match it
    ip = candidate.to_s
    # Check again if something has been changed.
    if !excluded_ips.include?(ip) && !subnet.known_ips.include?(ip)
      logger.debug "Searching for free IP - pinging #{ip}."
      if tcp_pingable?(ip) || icmp_pingable?(ip)
        logger.warn("Found a pingable IP (#{ip}) address which not marked as known. Skipping it...")
      else
        logger.debug("Found #{ip} in #{iterations} iterations")
        return ip
      end
    end
  end
  logger.debug("Not suggesting IP Address for #{subnet} as no free IP found in reasonable time (#{iterations} iterations)")
  errors.add(:subnet, _('no random free IP could be found in our DB, enlarge subnet range'))
  nil
end

#tcp_pingable?(ip) ⇒ Boolean

Returns:

  • (Boolean)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'app/services/ipam/ping_random_db.rb', line 14

def tcp_pingable?(ip)

  ports = [7, 22, 80, 443]

  begin
    ports.each do |port|
      if tcp_pingable_by_port?(ip, port)
        return true
      end
    end
    false
  end

end

#tcp_pingable_by_port?(ip, port) ⇒ Boolean

Returns:

  • (Boolean)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'app/services/ipam/ping_random_db.rb', line 29

def tcp_pingable_by_port?(ip, port)
  # This code is from net-ping, and stripped down for use here

  # Whether or not Errno::ECONNREFUSED is considered a successful ping
  @service_check = true
  @timeout = 1
  bool = false

  begin
    bool = Socket.tcp(ip, port, connect_timeout: @timeout) { true }
  rescue Errno::ECONNREFUSED
    if @service_check
      bool = true
    end
  rescue Exception
    bool = false
  end

  if bool
    logger.info "Succesful telnet ping #{ip}, port #{port}"
  end

  bool
rescue
  true
end