Class: DHCP::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/dhcp/server.rb

Constant Summary collapse

ZERO_IP =
IPAddress('0.0.0.0')

Instance Method Summary collapse

Constructor Details

#initialize(opt = {}) ⇒ Server

Returns a new instance of Server.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 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

Instance Method Details

#dispatch_packet(data, source_ip, source_port) ⇒ Object

Hand off raw UDP packet data here for parsing and dispatch:



65
66
67
68
69
70
71
72
73
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
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/dhcp/server.rb', line 65

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)
  rescue => e
    show_packet(packet) if @debug
    @log.debug("Error parsing DHCP packet.") if @debug
    return
  end

  relay = nil
  if source_port == 67     ## DHCP relay via an intermediary
    relay = true

    ## 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}].") if @debug
      return
    end
  elsif source_port == 68  ## DHCP on directly attached subnet
    relay = false

    ## 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, relay)
  when DHCP::DHCPREQUEST
    handle_request(packet, source_ip, relay)
  when DHCP::DHCPINFORM
    handle_inform(packet, source_ip, relay)
  when DHCP::DHCPRELEASE
    handle_release(packet, source_ip, relay)
  when DHCP::DHCPDECLINE
    handle_decline(packet, source_ip, relay)
  when DHCP::DHCPLEASEQUERY
    handle_leasequery(packet, source_ip, relay)
  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, relay) ⇒ Object

Handle DHCPDECLINE packet:



166
167
168
169
# File 'lib/dhcp/server.rb', line 166

def handle_decline(packet, source_ip, relay)
  show_packet(packet) if @debug
  @log.debug("handle_decline") if @debug
end

#handle_discover(packet, source_ip, relay) ⇒ Object

Handle DHCPDISCOVER packet:



148
149
150
151
# File 'lib/dhcp/server.rb', line 148

def handle_discover(packet, source_ip, relay)
  show_packet(packet) if @debug
  @log.debug("handle_discover") if @debug
end

#handle_inform(packet, source_ip, relay) ⇒ Object

Handle DHCPINFORM packet:



160
161
162
163
# File 'lib/dhcp/server.rb', line 160

def handle_inform(packet, source_ip, relay)
  show_packet(packet) if @debug
  @log.debug("handle_inform") if @debug
end

#handle_leasequery(packet, source_ip, relay) ⇒ Object

Handle DHCPLEASEQUERY packet:



172
173
174
175
# File 'lib/dhcp/server.rb', line 172

def handle_leasequery(packet, source_ip, relay)
  show_packet(packet) if @debug
  @log.debug("handle_leasequery") if @debug
end

#handle_request(packet, source_ip, relay) ⇒ Object

Handle DHCPREQUEST packet:



154
155
156
157
# File 'lib/dhcp/server.rb', line 154

def handle_request(packet, source_ip, relay)
  show_packet(packet) if @debug
  @log.debug("handle_request") if @debug
end

#relay_authorized?(source_ip, giaddr) ⇒ Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/dhcp/server.rb', line 143

def relay_authorized?(source_ip, giaddr)
  true
end

#runObject

Main server event loop (blocking):



49
50
51
52
53
54
# File 'lib/dhcp/server.rb', line 49

def run
  loop do
    result = run_once
    sleep @interval if !result  ## Sleep if no data was received and no errors occured
  end
end

#run_onceObject

Main server event single-iteration function (non-blocking):



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/dhcp/server.rb', line 30

def run_once
  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, 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

#show_packet(pk) ⇒ Object



56
57
58
59
60
61
62
# File 'lib/dhcp/server.rb', line 56

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