root/lib/proxy/dhcp/subnet.rb @ ad2b4651
ad8bb0c7 | Greg Sutcliffe | require 'checks'
|
|
eb305390 | Ohad Levy | require 'ipaddr'
|
|
require 'proxy/dhcp/monkey_patches' unless IPAddr.new.respond_to?('to_range')
|
|||
9a387048 | Greg Sutcliffe | require 'proxy/dhcp/monkey_patch_subnet' unless Array.new.respond_to?('rotate')
|
|
eb305390 | Ohad Levy | require 'proxy/validations'
|
|
b4982e24 | Greg Sutcliffe | require 'socket'
|
|
9a387048 | Greg Sutcliffe | require 'timeout'
|
|
require 'tmpdir'
|
|||
eb305390 | Ohad Levy | ||
module Proxy::DHCP
|
|||
# Represents a DHCP Subnet
|
|||
class Subnet
|
|||
456a6573 | Ohad Levy | attr_reader :network, :netmask, :server, :timestamp
|
|
eb305390 | Ohad Levy | attr_accessor :options
|
|
include Proxy::DHCP
|
|||
include Proxy::Log
|
|||
include Proxy::Validations
|
|||
def initialize server, network, netmask
|
|||
b215329a | Ohad Levy | @server = validate_server server
|
|
@network = validate_ip network
|
|||
@netmask = validate_ip netmask
|
|||
@options = {}
|
|||
2080b2eb | Greg Sutcliffe | @records = []
|
|
b215329a | Ohad Levy | @timestamp = Time.now
|
|
@loaded = false
|
|||
raise Proxy::DHCP::Error, "Unable to Add Subnet" unless @server.add_subnet(self)
|
|||
eb305390 | Ohad Levy | end
|
|
def include? ip
|
|||
IPAddr.new(to_s).include?(ip.is_a?(IPAddr) ? ip : IPAddr.new(ip))
|
|||
end
|
|||
def to_s
|
|||
"#{network}/#{netmask}"
|
|||
end
|
|||
9a387048 | Greg Sutcliffe | def cidr
|
|
IPAddr.new(netmask).to_i.to_s(2).count("1")
|
|||
end
|
|||
eb305390 | Ohad Levy | def range
|
|
r=valid_range
|
|||
"#{r.first.to_s}-#{r.last.to_s}"
|
|||
end
|
|||
def clear
|
|||
2080b2eb | Greg Sutcliffe | @records = []
|
|
eb305390 | Ohad Levy | @loaded = false
|
|
end
|
|||
def loaded?
|
|||
@loaded
|
|||
end
|
|||
def size
|
|||
2080b2eb | Greg Sutcliffe | @records.size
|
|
eb305390 | Ohad Levy | end
|
|
def load
|
|||
2b9164ca | Ohad Levy | self.clear
|
|
eb305390 | Ohad Levy | return false if loaded?
|
|
@loaded = true
|
|||
server.loadSubnetData self
|
|||
b215329a | Ohad Levy | logger.debug "Lazy loaded #{to_s} records"
|
|
eb305390 | Ohad Levy | end
|
|
def reload
|
|||
clear
|
|||
self.load
|
|||
end
|
|||
def records
|
|||
self.load if not loaded?
|
|||
2080b2eb | Greg Sutcliffe | @records
|
|
eb305390 | Ohad Levy | end
|
|
def [] record
|
|||
ad2b4651 | Greg Sutcliffe | records_for record, :all
|
|
end
|
|||
def has_mac? mac, type
|
|||
r = case type
|
|||
when :reservation
|
|||
reservations
|
|||
when :lease
|
|||
leases
|
|||
else
|
|||
records
|
|||
end.reverse_each {|r| return r if r.mac == mac.downcase}
|
|||
eb305390 | Ohad Levy | return false
|
|
end
|
|||
ad2b4651 | Greg Sutcliffe | def has_ip? ip, type
|
|
r = case type
|
|||
when :reservation
|
|||
reservations
|
|||
when :lease
|
|||
leases
|
|||
else
|
|||
records
|
|||
end.reverse_each {|r| return r if r.ip == ip}
|
|||
2080b2eb | Greg Sutcliffe | return false
|
|
eb305390 | Ohad Levy | end
|
|
# adds a record to a subnet
|
|||
def add_record record
|
|||
2080b2eb | Greg Sutcliffe | # Record all leases, since the definition of a duplicate depends on whether we
|
|
# are searching by ip or mac. Arrays have fixed sort order so we can rely on this
|
|||
# being the order they were read from the file
|
|||
@records.push record
|
|||
logger.debug "Added #{record} to #{to_s}"
|
|||
return true
|
|||
eb305390 | Ohad Levy | end
|
|
9a387048 | Greg Sutcliffe | def get_index_and_lock filename
|
|
# Store for use in the unlock method
|
|||
@filename = "#{Dir::tmpdir}/#{filename}"
|
|||
@lockfile = "#{@filename}.lock"
|
|||
# Loop if the file is locked
|
|||
Timeout::timeout(30) { sleep 0.1 while File.exists? @lockfile }
|
|||
# Touch the lock the file
|
|||
File.open(@lockfile, "w") {}
|
|||
@file = File.new(@filename,'r+') rescue File.new(@filename,'w+')
|
|||
# this returns the index in the file
|
|||
return @file.readlines.first.to_i rescue 0
|
|||
end
|
|||
def set_index_and_unlock index
|
|||
@file.reopen(@filename,'w')
|
|||
@file.write index
|
|||
@file.close
|
|||
File.delete @lockfile
|
|||
end
|
|||
eb305390 | Ohad Levy | # returns the next unused IP Address in a subnet
|
|
# Pings the IP address as well (just in case its not in Proxy::DHCP)
|
|||
440a88a9 | Ohad Levy | def unused_ip args = {}
|
|
a3ab6426 | Ohad Levy | # first check if we already have a record for this host
|
|
# if we do, we can simply reuse the same ip address.
|
|||
54ee3a8a | Sean Handley | if args[:mac] and r=has_mac?(args[:mac]) and valid_range(args).include?(r.ip)
|
|
a3ab6426 | Ohad Levy | logger.debug "Found an existing dhcp record #{r}, reusing..."
|
|
return r.ip
|
|||
end
|
|||
440a88a9 | Ohad Levy | free_ips = valid_range(args) - records.collect{|r| r.ip}
|
|
eb305390 | Ohad Levy | if free_ips.empty?
|
|
logger.warn "No free IPs at #{to_s}"
|
|||
return nil
|
|||
else
|
|||
9a387048 | Greg Sutcliffe | @index = 0
|
|
begin
|
|||
# Read and lock the storage file
|
|||
stored_index = get_index_and_lock("foreman-proxy_#{network}_#{cidr}.tmp")
|
|||
free_ips.rotate(stored_index).each do |ip|
|
|||
85098a3c | Matt Jarvis | logger.debug "Searching for free IP - pinging #{ip}"
|
|
9a387048 | Greg Sutcliffe | if tcp_pingable?(ip) or icmp_pingable?(ip)
|
|
logger.info "Found a pingable IP(#{ip}) address which does not have a Proxy::DHCP record"
|
|||
else
|
|||
85098a3c | Matt Jarvis | logger.debug "Found free IP #{ip} out of a total of #{free_ips.size} free IPs"
|
|
9a387048 | Greg Sutcliffe | @index = free_ips.index(ip)+1
|
|
return ip
|
|||
end
|
|||
eb305390 | Ohad Levy | end
|
|
9a387048 | Greg Sutcliffe | logger.warn "No free IPs at #{to_s}"
|
|
rescue Exception => e
|
|||
logger.debug e.message
|
|||
ensure
|
|||
# ensure we unlock the storage file
|
|||
set_index_and_unlock @index
|
|||
eb305390 | Ohad Levy | end
|
|
8756567d | Paul Kelly | nil
|
|
eb305390 | Ohad Levy | end
|
|
end
|
|||
def delete record
|
|||
2080b2eb | Greg Sutcliffe | if @records.delete(record).nil?
|
|
85098a3c | Matt Jarvis | raise Proxy::DHCP::Error, "Removing a Proxy::DHCP Record which doesn't exist"
|
|
eb305390 | Ohad Levy | end
|
|
end
|
|||
440a88a9 | Ohad Levy | def valid_range args = {}
|
|
logger.debug "trying to find an ip address, we got #{args.inspect}"
|
|||
if args[:from] and (from=validate_ip(args[:from])) and args[:to] and (to=validate_ip(args[:to]))
|
|||
raise Proxy::DHCP::Error, "Range does not belong to provided subnet" unless self.include?(from) and self.include?(to)
|
|||
from = IPAddr.new(from)
|
|||
to = IPAddr.new(to)
|
|||
85098a3c | Matt Jarvis | raise Proxy::DHCP::Error, "#{from} can't be lower IP address than #{to} - change the order?" if from > to
|
|
440a88a9 | Ohad Levy | from..to
|
|
else
|
|||
IPAddr.new(to_s).to_range
|
|||
eb305390 | Ohad Levy | # remove broadcast and network address
|
|
440a88a9 | Ohad Levy | end.map(&:to_s) - [network, broadcast]
|
|
eb305390 | Ohad Levy | end
|
|
def inspect
|
|||
self
|
|||
end
|
|||
def reservations
|
|||
records.collect{|r| r if r.kind == "reservation"}.compact
|
|||
end
|
|||
def leases
|
|||
records.collect{|r| r if r.kind == "lease"}.compact
|
|||
end
|
|||
ad2b4651 | Greg Sutcliffe | def records_for record, type
|
|
self.load if not loaded?
|
|||
return has_mac?(record, type) if (validate_mac(record) rescue nil)
|
|||
return has_ip?(record, type) if (validate_ip(record) rescue nil)
|
|||
end
|
|||
def reservation_for record
|
|||
records_for record, :reservation
|
|||
end
|
|||
def lease_for record
|
|||
records_for record, :lease
|
|||
end
|
|||
50e38ad1 | Ohad Levy | def <=> other
|
|
network <=> other.network
|
|||
end
|
|||
440a88a9 | Ohad Levy | def broadcast
|
|
IPAddr.new(to_s).to_range.last.to_s
|
|||
end
|
|||
8756567d | Paul Kelly | private
|
|
def tcp_pingable? ip
|
|||
b4982e24 | Greg Sutcliffe | # This code is from net-ping, and stripped down for use here
|
|
# We don't need all the ldap dependencies net-ping brings in
|
|||
@service_check = true
|
|||
@port = 7
|
|||
@timeout = 1
|
|||
@exception = nil
|
|||
bool = false
|
|||
tcp = nil
|
|||
begin
|
|||
Timeout.timeout(@timeout){
|
|||
begin
|
|||
tcp = TCPSocket.new(ip, @port)
|
|||
rescue Errno::ECONNREFUSED => err
|
|||
if @service_check
|
|||
bool = true
|
|||
else
|
|||
@exception = err
|
|||
end
|
|||
rescue Exception => err
|
|||
@exception = err
|
|||
else
|
|||
bool = true
|
|||
end
|
|||
}
|
|||
rescue Timeout::Error => err
|
|||
@exception = err
|
|||
ensure
|
|||
tcp.close if tcp
|
|||
end
|
|||
bool
|
|||
8756567d | Paul Kelly | rescue
|
|
# We failed to check this address so we should not use it
|
|||
true
|
|||
end
|
|||
def icmp_pingable? ip
|
|||
b4982e24 | Greg Sutcliffe | # Always shell to ping, instead of using net-ping
|
|
system("ping -c 1 -W 1 #{ip} > /dev/null")
|
|||
8756567d | Paul Kelly | rescue
|
|
# We failed to check this address so we should not use it
|
|||
true
|
|||
end
|
|||
def privileged_user
|
|||
(PLATFORM =~ /linux/i and Process.uid == 0) or PLATFORM =~ /mingw/
|
|||
end
|
|||
eb305390 | Ohad Levy | end
|
|
end
|