class DHCP::Server
Constants
- ALLONES_IP
- ZERO_IP
Public Class Methods
new(opt={})
click to toggle source
# File lib/dhcp/server.rb, line 12 def initialize(opt={}) @interval = opt[:interval] || 0.5 ## Sleep (seconds) if no data @log = opt[:log] || Syslog ## Logging object (should be open already) @server_ip = opt[:ip] || '0.0.0.0' ## Listen on this IP @server_port = opt[:port] || 67 ## Listen on this UDP port @debug = opt[:debug] || false ## Bind to UDP server port: @log.info("Starting DHCP on [#{@server_ip}]:#{@server_port} server at #{Time.now}") @sock = UDPSocket.new @sock.do_not_reverse_lookup = true @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true) ## Permit sending to broadcast address unless @sock.bind(@server_ip, 67) raise "Failed to bind" end end
Public Instance Methods
dispatch_packet(data, source_ip, source_port)
click to toggle source
Hand off raw UDP packet data here for parsing and dispatch:
# File lib/dhcp/server.rb, line 74 def dispatch_packet(data, source_ip, source_port) now = Time.now @log.debug("Packet (#{data.size} bytes) from [#{source_ip}]:#{source_port} received at #{now}") if data.size < 300 @log.debug("Ignoring small packet (less than BOOTP minimum size.") if @debug return end packet = nil begin packet = DHCP::Packet.new(data) packet.logs.each do |msg| @log.warn("DHCP Packet Parser Log Message: #{msg}") end rescue => e if packet.nil? @log.debug("Packet was not created from data: #{data.inspect} (#{data.size})") if @debug else show_packet(packet) if @debug end @log.error("Error parsing DHCP packet: #{e}") return end if source_port == 67 ## DHCP relay via an intermediary (relayed) ## Quick relay sanity-check on GIADDR: if packet.giaddr == ZERO_IP @log.debug("Packet from relay (port 67) has no GIADDR address set. Ignoring.") if @debug return end unless relay_authorized?(source_ip, packet.giaddr) @log.debug("Ignoring DHCP packet from unauthorized relay [#{source_ip}] with GIADDR set to [#{packet.giaddr}].") if @debug return end elsif source_port == 68 ## DHCP via directly attached network (no relay) ## Quick relay sanity-check on GIADDR: if packet.giaddr != ZERO_IP @log.debug("Direct (non-relay) packet has set GIADDR to [#{packet.giaddr}] in violation of RFC. Ignoring.") if @debug return end else @log.debug("Ignoring packet from UDP port other than 67 (relay) or 68 (direct)") if @debug return end ## Ethernet hardware type sanity check: if packet.htype != DHCP::HTYPE[:htype_10mb_ethernet][0] || packet.hlen != DHCP::HTYPE[:htype_10mb_ethernet][1] @log.debug("Request hardware type or length doesn't match ETHERNET type and length. Ignoring.") if @debug return end if packet.op != DHCP::BOOTREQUEST @log.debug("Recived a non-BOOTREQUEST packet. Ignoring.") if @debug return end ## Dispatch packet: case packet.type when DHCP::DHCPDISCOVER handle_discover(packet, source_ip, source_port) when DHCP::DHCPREQUEST handle_request(packet, source_ip, source_port) when DHCP::DHCPINFORM handle_inform(packet, source_ip, source_port) when DHCP::DHCPRELEASE handle_release(packet, source_ip, source_port) when DHCP::DHCPDECLINE handle_decline(packet, source_ip, source_port) when DHCP::DHCPLEASEQUERY handle_leasequery(packet, source_ip, source_port) when DHCP::DHCPOFFER, DHCP::DHCPACK, DHCP::DHCPNAK, DHCP::DHCPFORCERENEW, DHCP::DHCPLEASEUNASSIGNED, DHCP::DHCPLEASEACTIVE, DHCP::DHCPLEASEUNKNOWN show_packet(packet) if @debug @log.debug("Packet type #{packet.type_name} in a BOOTREQUEST is invalid.") if @debug else show_packet(packet) if @debug @log.debug("Invalid, unknown, or unhandled DHCP packet type received.") if @debug end end
handle_decline(packet, source_ip, source_port)
click to toggle source
Handle DHCPDECLINE packet:
# File lib/dhcp/server.rb, line 177 def handle_decline(packet, source_ip, source_port) show_packet(packet) if @debug @log.debug("handle_decline") if @debug end
handle_discover(packet, source_ip, source_port)
click to toggle source
Handle DHCPDISCOVER packet:
# File lib/dhcp/server.rb, line 159 def handle_discover(packet, source_ip, source_port) show_packet(packet) if @debug @log.debug("handle_discover") if @debug end
handle_inform(packet, source_ip, source_port)
click to toggle source
Handle DHCPINFORM packet:
# File lib/dhcp/server.rb, line 171 def handle_inform(packet, source_ip, source_port) show_packet(packet) if @debug @log.debug("handle_inform") if @debug end
handle_leasequery(packet, source_ip, source_port)
click to toggle source
Handle DHCPLEASEQUERY packet:
# File lib/dhcp/server.rb, line 183 def handle_leasequery(packet, source_ip, source_port) show_packet(packet) if @debug @log.debug("handle_leasequery") if @debug end
handle_release(packet, source_ip, source_port)
click to toggle source
Handle DHCPRELEASE packet:
# File lib/dhcp/server.rb, line 189 def handle_release(packet, source_ip, source_port) show_packet(packet) if @debug @log.debug("handle_release") if @debug end
handle_request(packet, source_ip, source_port)
click to toggle source
Handle DHCPREQUEST packet:
# File lib/dhcp/server.rb, line 165 def handle_request(packet, source_ip, source_port) show_packet(packet) if @debug @log.debug("handle_request") if @debug end
run()
click to toggle source
Main server event loop (blocking):
# File lib/dhcp/server.rb, line 58 def run loop do result = run_once sleep @interval if !result ## Sleep if no data was received and no errors occured end end
run_once()
click to toggle source
Main server event single-iteration function (non-blocking):
# File lib/dhcp/server.rb, line 38 def run_once raise "Cannot run after DHCP server has been shut down!" if @sock.nil? r,w,e = IO.select([@sock], nil, [@sock], 0) if !r.nil? && r.size == 1 data, src = @sock.recvfrom_nonblock(1500) if data.bytesize < 300 @log.debug("Ignoring packet smaller than BOOTP minimum size") if @debug else dispatch_packet(data, IPAddress(src[3]), src[1]) end return true end if !e.nil? && e.size == 1 ## TODO: Handle errors... raise "Unhandled error on socket" end return false end
send_response(packet, dest_ip, dest_port)
click to toggle source
# File lib/dhcp/server.rb, line 194 def send_response(packet, dest_ip, dest_port) data = packet.to_packet dest_ip = ALLONES_IP if dest_ip == ZERO_IP if @debug @log.debug(">>> SENDING DHCP #{packet.type_name} (#{packet.type}) PACKET to [#{dest_ip}]:#{dest_port}") packet.to_s.gsub(/\/,'\\').gsub(/[^\x20-\x7e\n]/){|x| '\x' + x.unpack('H2')[0].upcase}.split("\n").each do |i| @log.debug("..." + i) end @log.debug("<<< END OF PACKET <<<") end @sock.send(data, 0, dest_ip.to_s, dest_port) end
show_packet(pk)
click to toggle source
# File lib/dhcp/server.rb, line 65 def show_packet(pk) @log.debug(">>> PACKET: #{pk.type} '#{pk.type_name}' at #{Time.now} >>>") pk.to_s.gsub(/\/,'\\').gsub(/[^\x20-\x7e\n]/){|x| '\x' + x.unpack('H2')[0].upcase}.split("\n").each do |i| @log.debug("..." + i) end @log.debug("<<< END OF PACKET <<<") end
shutdown()
click to toggle source
# File lib/dhcp/server.rb, line 29 def shutdown unless @sock.nil? @sock.close @sock = nil @log.info("Stopping DHCP on [#{@server_ip}]:#{@server_port} server at #{Time.now}") end end