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



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

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

#icmp_pingable?(ip) ⇒ Boolean

Returns:

  • (Boolean)


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

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.warn "[IPAM] Unable to icmp ping #{ip} because #{err.inspect}."
  true
end

#ns_resolvable?(ip) ⇒ Boolean

Returns:

  • (Boolean)


71
72
73
74
75
76
77
78
79
# File 'app/services/ipam/ping_random_db.rb', line 71

def ns_resolvable? ip
  begin
    name = Resolv.getname ip
    logger.warn "[IPAM] Found a DNS resolvable IP #{ip}, resolved name: #{name}."
    true
  rescue StandardError
    false
  end
end

#random_ip(range) ⇒ Object



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

def random_ip(range)
  IPAddr.new(range.sample, subnet.family)
end

#suggest_ipObject



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'app/services/ipam/ping_random_db.rb', line 85

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

  unless range.empty?
    logger.info "[IPAM] IP range from #{range.first.to_s} to #{range.last.to_s}, total: #{range.length}"
  end

  loop do
    if range.empty?
      errors.add(:subnet, _('no free IP could be found in our DB, enlarge subnet range'))
      return nil
    end

    candidate = random_ip(range)
    iterations += 1
    break if iterations >= MAX_ITERATIONS
    # try to match it
    ip = candidate.to_s
    logger.info "[IPAM] Searching for free IP - resolving #{ip}"

    # Check again if something has been changed.
    if !excluded_ips.include?(ip) && !subnet.known_ips.include?(ip)
      if tcp_pingable?(ip) || icmp_pingable?(ip)
        logger.warn("[IPAM] Found a pingable IP (#{ip}) address which not marked as known. Skipping it...")
      else
        unless ns_resolvable?(ip)
          logger.info("[IPAM] Found #{ip} in #{iterations} iterations")
          return ip
        end
      end
    end

    range -= [candidate]
  end

  logger.warn("[IPAM] 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)


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

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)


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
55
# File 'app/services/ipam/ping_random_db.rb', line 30

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 "[IPAM] Succesful telnet ping #{ip}, port #{port}"
  end

  bool
rescue
  true
end