dhcp 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/{LICENSE → LICENSE.txt} +1 -1
- data/{README → README.rdoc} +7 -3
- data/Rakefile +17 -7
- data/dhcp.rb.bak +1047 -0
- data/lib/dhcp/client.rb +24 -0
- data/lib/dhcp/dhcp.rb +37 -923
- data/lib/dhcp/options.rb +652 -0
- data/lib/dhcp/packet.rb +353 -0
- data/lib/dhcp/server.rb +141 -0
- data/lib/dhcp/version.rb +3 -2
- data/lib/dhcp.rb +4 -1
- metadata +48 -48
data/lib/dhcp/dhcp.rb
CHANGED
@@ -1,854 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
1
2
|
# encoding: ASCII-8BIT
|
2
|
-
#
|
3
|
-
# --
|
4
|
-
#
|
5
|
-
# Ruby DHCP module for parsing and creating IPv4 DHCP packets
|
6
|
-
# - See http://www.aarongifford.com/computers/dhcp/
|
7
|
-
#
|
8
|
-
# --
|
9
|
-
#
|
10
|
-
# Written by Aaron D. Gifford - http://www.aarongifford.com/
|
11
|
-
#
|
12
|
-
# Copyright (c) 2010-2011 InfoWest, Inc. and Aaron D. Gifford
|
13
|
-
#
|
14
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
15
|
-
# of this software and associated documentation files (the "Software"), to deal
|
16
|
-
# in the Software without restriction, including without limitation the rights
|
17
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
18
|
-
# copies of the Software, and to permit persons to whom the Software is
|
19
|
-
# furnished to do so, subject to the following conditions:
|
20
|
-
#
|
21
|
-
# The above copyright notice and this permission notice shall be included in
|
22
|
-
# all copies or substantial portions of the Software.
|
23
|
-
#
|
24
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
25
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
26
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
27
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
28
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
29
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
30
|
-
# THE SOFTWARE.
|
31
|
-
#
|
32
|
-
# --
|
33
|
-
#
|
34
|
-
# NOTE: All strings in this module should be BINARY (ASCII-8BIT) encoded
|
35
|
-
# or things won't work correctly.
|
36
|
-
#
|
37
|
-
|
38
|
-
## Monkeypatch String so it will have a working #ord method (for 1.8):
|
39
|
-
unless RUBY_VERSION >= '1.9.1'
|
40
|
-
class String
|
41
|
-
def ord
|
42
|
-
self[0]
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
3
|
|
47
4
|
## http://github.org/bluemonk/ipaddress - A very nice IP address utility gem
|
48
5
|
require 'ipaddress'
|
49
6
|
|
50
7
|
module DHCP
|
51
|
-
## Base class from which all DHCP options in a DHCP packet derive:
|
52
|
-
class Opt
|
53
|
-
def initialize(opt, name, ignore=nil)
|
54
|
-
@opt = opt
|
55
|
-
@name = name
|
56
|
-
end
|
57
|
-
attr_reader :opt, :name
|
58
|
-
|
59
|
-
def opt_header
|
60
|
-
"OPTION[#{opt}:#{@name}]"
|
61
|
-
end
|
62
|
-
|
63
|
-
def to_s
|
64
|
-
opt_header
|
65
|
-
end
|
66
|
-
|
67
|
-
def to_opt
|
68
|
-
@opt.chr
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
## Class for DHCP options that contain data
|
74
|
-
class OptData < Opt
|
75
|
-
def initialize(opt, name, data=nil)
|
76
|
-
super(opt, name)
|
77
|
-
@data = data.nil? ? '' : data_to_bin(data)
|
78
|
-
end
|
79
|
-
attr_accessor :data
|
80
|
-
|
81
|
-
def data
|
82
|
-
@data
|
83
|
-
end
|
84
|
-
|
85
|
-
def data=(data)
|
86
|
-
@data = data.dup
|
87
|
-
self ## Chainable
|
88
|
-
end
|
89
|
-
|
90
|
-
def set(data)
|
91
|
-
self.data = data_to_bin(data)
|
92
|
-
self ## Chainable
|
93
|
-
end
|
94
|
-
|
95
|
-
def get
|
96
|
-
bin_to_data(@data)
|
97
|
-
end
|
98
|
-
|
99
|
-
def data_to_bin(data) ## Override this in subclasses to interpret data
|
100
|
-
data
|
101
|
-
end
|
102
|
-
|
103
|
-
def bin_to_data(data) ## Override this in subclasses to interpret data
|
104
|
-
data
|
105
|
-
end
|
106
|
-
|
107
|
-
def opt_header
|
108
|
-
super + "(#{data.size})"
|
109
|
-
end
|
110
|
-
|
111
|
-
def to_s
|
112
|
-
opt_header + "='#{bin_to_data(@data)}'"
|
113
|
-
end
|
114
|
-
|
115
|
-
def to_opt
|
116
|
-
super + @data.size.chr + @data
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
|
121
|
-
## Class for DHCP options containing a fixed number of bytes
|
122
|
-
class OptFixedData < OptData
|
123
|
-
@size = 0 ## Override this in subclasses
|
124
|
-
class << self
|
125
|
-
attr_accessor :size
|
126
|
-
end
|
127
|
-
|
128
|
-
def initialize(opt, name, data=nil)
|
129
|
-
super(opt, name, data)
|
130
|
-
## Prefill with zeros if needed:
|
131
|
-
@data = 0.chr * self.class.size if data.nil? && self.class.size > 0
|
132
|
-
end
|
133
|
-
|
134
|
-
def data=(data)
|
135
|
-
raise "Invalid size for #{self.class} (expected #{size} bytes, not #{data.size} bytes)" unless self.class.size == data.size
|
136
|
-
super(data)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
## Class for DHCP options that contain a lists (like lists of IPs)
|
141
|
-
class OptListData < OptData
|
142
|
-
include Enumerable
|
143
|
-
def initialize(opt, name, data=nil)
|
144
|
-
super(opt, name)
|
145
|
-
@size = 0
|
146
|
-
set(data) unless data.nil?
|
147
|
-
end
|
148
|
-
|
149
|
-
def data=(data)
|
150
|
-
set(split_data(data))
|
151
|
-
end
|
152
|
-
|
153
|
-
def get
|
154
|
-
split_data(@data) ## Splits and interprets binary data
|
155
|
-
end
|
156
|
-
|
157
|
-
def set(list)
|
158
|
-
list = [list] unless is_list?(list)
|
159
|
-
@data = ''
|
160
|
-
@size = 0
|
161
|
-
list.each do |item|
|
162
|
-
append(item)
|
163
|
-
end
|
164
|
-
self ## Chainable
|
165
|
-
end
|
166
|
-
|
167
|
-
def is_list?(list) ## Override if needed in child class
|
168
|
-
list.is_a?(Array)
|
169
|
-
end
|
170
|
-
|
171
|
-
def append(item)
|
172
|
-
@size += 1
|
173
|
-
@data += data_to_bin(item)
|
174
|
-
self ## Chainable
|
175
|
-
end
|
176
|
-
|
177
|
-
def split_data(data) ## Override in child class to split and interpret binary data
|
178
|
-
raise "Child class #{data.class} MUST override this"
|
179
|
-
end
|
180
|
-
|
181
|
-
def size
|
182
|
-
@size
|
183
|
-
end
|
184
|
-
|
185
|
-
def to_s
|
186
|
-
opt_header + '=[' + map{|x| x.to_s}.join(',') + ']'
|
187
|
-
end
|
188
|
-
|
189
|
-
def each
|
190
|
-
split_data(@data).each do |item|
|
191
|
-
yield item
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
## Class for DHCP option suboptions:
|
197
|
-
class SubOpt < OptData
|
198
|
-
def opt_header
|
199
|
-
"suboption[#{opt}:#{@name}]"
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
## Class for DHCP option suboptions containing lists
|
204
|
-
class SubOptList < OptListData
|
205
|
-
def opt_header
|
206
|
-
"suboption[#{opt}:#{@name}]"
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
## Class for DHCP suboption for vendor specific information
|
211
|
-
class SubOptVSRInfo < SubOptList
|
212
|
-
def is_list?(list)
|
213
|
-
raise "Invalid suboption sublist/entry" unless list.is_a?(Array)
|
214
|
-
return false if list.size == 2 && list[0].is_a?(Fixnum) && list[1].is_a?(String)
|
215
|
-
list.each do |item|
|
216
|
-
raise "Invalid suboption sublistlist" unless item.is_a?(Array) && item.size == 2 && item[0].is_a?(Fixnum) && item[1].is_a?(String)
|
217
|
-
end
|
218
|
-
return true
|
219
|
-
end
|
220
|
-
|
221
|
-
def split_data(data)
|
222
|
-
data = data.dup
|
223
|
-
list = []
|
224
|
-
while data.size > 0
|
225
|
-
raise "Invalid suboption data" unless data.size >= 5
|
226
|
-
len = data[4,1].ord
|
227
|
-
raise "Invalid vendor-specific relay info. data length" unless data.size >= len + 5
|
228
|
-
list << [ data[0,4].unpack('N')[0], data[5,len] ]
|
229
|
-
data[0,5+len] = ''
|
230
|
-
end
|
231
|
-
list
|
232
|
-
end
|
233
|
-
|
234
|
-
def bin_to_data(data)
|
235
|
-
raise "Invalid data size" unless data.size >= 5 && data.size == data[4,1].ord + 5
|
236
|
-
[ data[0,1].ord, data[2,data.size-2] ]
|
237
|
-
end
|
238
|
-
|
239
|
-
def data_to_bin(data)
|
240
|
-
raise "Invalid data" unless data.is_a?(Array) && data.size == 2 && data[0].is_a?(Fixnum) && data[1].is_a?(String)
|
241
|
-
raise "Invalid data size" unless data[1].size < 256
|
242
|
-
data[0].chr + data[1].size.chr + data[1]
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
## Class for DHCP options that contain sublists (like vendor specific information or relay agent information)
|
247
|
-
class OptSubList < OptListData
|
248
|
-
def is_list?(list)
|
249
|
-
raise "Invalid suboption list/entry" unless list.is_a?(Array)
|
250
|
-
return false if list.size == 2 && list[0].is_a?(Fixnum) && list[1].is_a?(String)
|
251
|
-
list.each do |item|
|
252
|
-
raise "Invalid suboption list" unless item.is_a?(Array) && item.size == 2 && item[0].is_a?(Fixnum) && item[1].is_a?(String)
|
253
|
-
end
|
254
|
-
return true
|
255
|
-
end
|
256
|
-
|
257
|
-
def split_data(data)
|
258
|
-
data = data.dup
|
259
|
-
list = []
|
260
|
-
while data.size > 0
|
261
|
-
raise "Invalid data size" unless data.size >= 2
|
262
|
-
len = data[1,1].ord
|
263
|
-
raise "Invalid data size" unless data.size >= len + 2
|
264
|
-
list << [ data[0,1].ord, data[2,len] ]
|
265
|
-
data[0,len+2] = ''
|
266
|
-
end
|
267
|
-
list
|
268
|
-
end
|
269
|
-
|
270
|
-
def bin_to_data(data)
|
271
|
-
raise "Invalid data size" unless data.size >= 2 && data.size == data[1,1].ord + 2
|
272
|
-
[ data[0,1].ord, data[2,data.size-2] ]
|
273
|
-
end
|
274
|
-
|
275
|
-
def data_to_bin(data)
|
276
|
-
raise "Invalid data" unless data.is_a?(Array) && data.size == 2 && data[0].is_a?(Fixnum) && data[1].is_a?(String)
|
277
|
-
raise "Invalid data size" unless data[1].size < 256
|
278
|
-
data[0].chr + data[1].size.chr + data[1]
|
279
|
-
end
|
280
|
-
|
281
|
-
def to_s
|
282
|
-
opt_header + "(#{@size})=[" + map do |i|
|
283
|
-
val = ''
|
284
|
-
name = case i[0]
|
285
|
-
when 1
|
286
|
-
val = i[1].scan(/./m).map{|b| b.unpack('H2')[0].upcase}.join(':')
|
287
|
-
'AgentCircuitID'
|
288
|
-
when 2
|
289
|
-
val = i[1].scan(/./m).map{|b| b.unpack('H2')[0].upcase}.join(':')
|
290
|
-
'AgentRemoteID'
|
291
|
-
when 9
|
292
|
-
val = (SubOptVSRInfo.new(9, :vendor_specific_relay_suboption).data=i[1]).to_s
|
293
|
-
'VendorSpecificRelaySuboption'
|
294
|
-
else
|
295
|
-
val = i[1].scan(/./m).map{|b| b.unpack('H2')[0].upcase}.join(':')
|
296
|
-
'Unknown'
|
297
|
-
end
|
298
|
-
"#{name}:#{i[0]}(#{i[1].size})='#{val}'"
|
299
|
-
end.join(',') + ']'
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
## Class for DHCP options that contain lists of fixed sized data
|
304
|
-
class OptListFixedData < OptListData
|
305
|
-
@size = 0 ## Override this in subclasses
|
306
|
-
class << self
|
307
|
-
attr_accessor :size
|
308
|
-
end
|
309
|
-
|
310
|
-
def split_data(data)
|
311
|
-
raise "Child class #{self.class} MUST override class size variable with non-zero value!" if self.class.size == 0
|
312
|
-
raise "Invalid data length #{data.size} (expected even multiple of #{self.class.size})" unless data.size % self.class.size == 0
|
313
|
-
list = []
|
314
|
-
data = data.dup
|
315
|
-
while data.size > 0
|
316
|
-
list << bin_to_data(data.slice!(0,self.class.size))
|
317
|
-
end
|
318
|
-
list
|
319
|
-
end
|
320
|
-
|
321
|
-
def data_to_bin(item) ## Override in child, but call super(item)
|
322
|
-
## with the resulting translated data after
|
323
|
-
## data translation so the size check is
|
324
|
-
## applied (or do a size check in the child):
|
325
|
-
raise "Invalid data item length #{item.size} (expected #{self.class.size})" unless item.size == self.class.size
|
326
|
-
item
|
327
|
-
end
|
328
|
-
end
|
329
|
-
|
330
|
-
## Class for DHCP options that contain a single IPv4 address
|
331
|
-
class OptIP < OptFixedData
|
332
|
-
@size = 4
|
333
|
-
|
334
|
-
def bin_to_data(data)
|
335
|
-
IPAddress::IPv4::parse_data(data).to_s
|
336
|
-
end
|
337
|
-
|
338
|
-
def data_to_bin(data)
|
339
|
-
IPAddress::IPv4.new(data).data ## Will raise exception if data is not a valid IP
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
## Class for DHCP options that contain a list of IPv4 addresses
|
344
|
-
class OptIPList < OptListFixedData
|
345
|
-
@size = 4
|
346
|
-
|
347
|
-
def bin_to_data(data)
|
348
|
-
IPAddress::IPv4::parse_data(data).to_s
|
349
|
-
end
|
350
|
-
|
351
|
-
def data_to_bin(data)
|
352
|
-
IPAddress::IPv4.new(data).data ## Will raise exception if data is not a valid IP
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
## Class for DHCP option 33 (static routes) - Use option 121 instead if possible
|
357
|
-
## WARNING: Option 33 can only handle class A, B, or C networks, not classless
|
358
|
-
## networks with an arbitrary netmask.
|
359
|
-
class OptStaticRoutes < OptListFixedData
|
360
|
-
@size = 8
|
361
|
-
|
362
|
-
def is_list?(list)
|
363
|
-
raise "Invalid route list/entry" unless list.is_a?(Array)
|
364
|
-
if list.size == 2
|
365
|
-
return false if list[0].is_a?(String) && list[1].is_a?(String)
|
366
|
-
return true if list[0].is_a?(Array) && list[1].is_a?(Array)
|
367
|
-
raise "Invalid route list/entry"
|
368
|
-
end
|
369
|
-
list.each do |item|
|
370
|
-
raise "Invalid route list" unless item.is_a?(Array) && item[0].is_a?(String) && item[1].is_a?(String)
|
371
|
-
end
|
372
|
-
return true
|
373
|
-
end
|
374
|
-
|
375
|
-
def data_to_bin(data)
|
376
|
-
raise "Invalid static route" unless data.is_a?(Array) && data.size == 2
|
377
|
-
net, gateway = *data
|
378
|
-
net = IPAddress::IPv4.new(net)
|
379
|
-
raise "Invalid classful static route network" unless net.network?
|
380
|
-
raise "Invalid classful static route network" unless (
|
381
|
-
(net.a? && net.prefix == 8 ) ||
|
382
|
-
(net.b? && net.prefix == 16) ||
|
383
|
-
(net.c? && net.prefix == 24)
|
384
|
-
)
|
385
|
-
gateway = IPAddress::IPv4.new("#{gateway}/#{net.prefix}")
|
386
|
-
raise "Invalid classful static route gateway" unless gateway.member?(net)
|
387
|
-
net.data + gateway.data
|
388
|
-
end
|
389
|
-
|
390
|
-
def bin_to_data(data)
|
391
|
-
[IPAddress::IPv4::parse_classful_data(data[0,4]).net.to_string, IPAddress::IPv4::parse_data(data[4,4]).to_s]
|
392
|
-
end
|
393
|
-
|
394
|
-
def to_s
|
395
|
-
opt_header + '=[' + map{|i| i[0] + '=>' + i[1]}.join(',') + ']'
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
|
-
## Class for DHCP options containing lists of IPv4 CIDR routes (like option 121 or MS's 249)
|
400
|
-
## See RFC 3442 "compact encoding" of destination
|
401
|
-
class OptRouteList < OptListData
|
402
|
-
def split_data(data)
|
403
|
-
data = data.dup
|
404
|
-
list = []
|
405
|
-
while data.size > 0
|
406
|
-
raise "Invalid binary data" unless data.size > 4 || data[0,1].ord > 32
|
407
|
-
octets = (data[0,1].ord + 7)/8
|
408
|
-
raise "Invalid binary data" unless data.size >= octets + 5
|
409
|
-
list << bin_to_data(data.slice!(0,octets+5))
|
410
|
-
end
|
411
|
-
list
|
412
|
-
end
|
413
|
-
|
414
|
-
def data_to_bin(data)
|
415
|
-
raise "Invalid classless static route" unless data.is_a?(Array) && data.size == 2
|
416
|
-
net, gateway = *data
|
417
|
-
raise "Invalid classless static route network" if net.index('/').nil?
|
418
|
-
net = IPAddress::IPv4.new(net)
|
419
|
-
raise "Invalid classless static route network" unless net.network?
|
420
|
-
gateway = IPAddress::IPv4.new("#{gateway}/#{net.prefix}")
|
421
|
-
raise "Invalid classless static route gateway" unless gateway.member?(net)
|
422
|
-
net.prefix.to_i.chr + net.data[0,(net.prefix+7)/8] + gateway.data
|
423
|
-
end
|
424
|
-
|
425
|
-
def bin_to_data(data)
|
426
|
-
raise "Invalid binary classless route data" unless data.size > 4 || data[0,1].ord > 32
|
427
|
-
maskbits = data[0,1].ord
|
428
|
-
octets = (maskbits+7)/8
|
429
|
-
raise "Invalid binary classless route data" unless data.size == octets + 5
|
430
|
-
dest = IPAddress::IPv4.parse_data(data[1,octets] + 0.chr * (4 - octets))
|
431
|
-
dest.prefix = maskbits
|
432
|
-
gateway = IPAddress::IPv4.parse_data(data[octets+1,4])
|
433
|
-
gateway.prefix = maskbits ## Unnecessary...
|
434
|
-
## Should an "Invalid classless static route" exception be raised
|
435
|
-
## here if gateway is not a member of the destination network?
|
436
|
-
[dest.to_string, gateway.to_s]
|
437
|
-
end
|
438
|
-
end
|
439
|
-
|
440
|
-
## Class for boolean DHCP options
|
441
|
-
class OptBool < OptFixedData
|
442
|
-
@size = 1
|
443
|
-
|
444
|
-
def data_to_bin(data)
|
445
|
-
raise "Invalid boolean data #{data.class} (expected TrueClass or FalseClass)" unless data.is_a?(TrueClass) || data.is_a?(FalseClass)
|
446
|
-
data ? 1.chr : 0.chr
|
447
|
-
end
|
448
|
-
|
449
|
-
def bin_to_data(data)
|
450
|
-
raise "Invalid boolean binary data" if data.size != 1 || data.ord > 1
|
451
|
-
data.ord == 0 ? false : true
|
452
|
-
end
|
453
|
-
end
|
454
|
-
|
455
|
-
## Class for single-byte unsigned integer value DHCP options
|
456
|
-
## Also acts as parent class for multi-byte value DHCP options
|
457
|
-
class OptByte < OptFixedData
|
458
|
-
@size = 1
|
459
|
-
|
460
|
-
def data_to_bin(data)
|
461
|
-
raise "Invalid numeric data" unless data.is_a?(Fixnum) && data >= 0
|
462
|
-
raise "Invalid number" unless data == data & ([0xff] * self.class.size).inject(0){|sum,byte| sum<<8|byte}
|
463
|
-
bytes = ''
|
464
|
-
while data != 0
|
465
|
-
bytes = (data & 0xff).chr + bytes
|
466
|
-
data >>= 8
|
467
|
-
end
|
468
|
-
raise "Impossible: Numeric byte size #{bytes.size} exceeds #{self.class.size}" if bytes.size > self.class.size
|
469
|
-
0.chr * (self.class.size - bytes.size) + bytes
|
470
|
-
end
|
471
|
-
|
472
|
-
def bin_to_data(data)
|
473
|
-
data.each_byte.inject(0){|sum,byte| sum<<8|byte}
|
474
|
-
end
|
475
|
-
|
476
|
-
def to_s
|
477
|
-
opt_header + "=#{self.get}"
|
478
|
-
end
|
479
|
-
end
|
480
|
-
|
481
|
-
## Class for two-byte unsigned integer value DHCP options
|
482
|
-
class OptInt16 < OptByte
|
483
|
-
@size = 2
|
484
|
-
end
|
485
|
-
|
486
|
-
## Class for four-byte unsigned integer value DHCP options
|
487
|
-
class OptInt32 < OptByte
|
488
|
-
@size = 4
|
489
|
-
end
|
490
|
-
|
491
|
-
## Class for four-byte signed integer value DHCP options
|
492
|
-
class OptSInt32 < OptInt32
|
493
|
-
@size = 4
|
494
|
-
def data_to_bin(data)
|
495
|
-
super(data % 2**32)
|
496
|
-
end
|
497
|
-
|
498
|
-
def bin_to_data(data)
|
499
|
-
(super(data) + 2**31) % 2**32 - 2**31
|
500
|
-
end
|
501
|
-
end
|
502
|
-
|
503
|
-
## Class for DHCP options containing a list of single byte integers (i.e. lists of requested DHCP options)
|
504
|
-
class OptByteList < OptListFixedData
|
505
|
-
@size = 1
|
506
|
-
|
507
|
-
def bin_to_data(data)
|
508
|
-
data.each_byte.inject(0){|sum,byte| sum<<8|byte}
|
509
|
-
end
|
510
|
-
|
511
|
-
def data_to_bin(data)
|
512
|
-
raise "Invalid numeric data" unless data.is_a?(Fixnum) && data >= 0
|
513
|
-
raise "Invalid number" unless data == data & ([0xff] * self.class.size).inject(0){|sum,byte| sum<<8|byte}
|
514
|
-
bytes = ''
|
515
|
-
while data != 0
|
516
|
-
bytes = (data & 0xff).chr + bytes
|
517
|
-
data >>= 8
|
518
|
-
end
|
519
|
-
raise "Impossible: Numeric byte size #{bytes.size} exceeds #{self.class.size}" if bytes.size > self.class.size
|
520
|
-
0.chr * (self.class.size - bytes.size) + bytes
|
521
|
-
end
|
522
|
-
|
523
|
-
def to_s
|
524
|
-
opt_header + '=[' + map{|x| x.to_s}.join(',') + ']'
|
525
|
-
end
|
526
|
-
end
|
527
|
-
|
528
|
-
## Class for DHCP options containing data that is most often displayed as a string of hexadecimal digit pairs joined by colons (i.e. ethernet MAC addresses)
|
529
|
-
class OptHexString < OptData
|
530
|
-
def data_to_bin(data)
|
531
|
-
data.split(/[ \.:\-]/).map{|b| [('0'+b)[-2,2]].pack('H2')}.join
|
532
|
-
end
|
533
|
-
|
534
|
-
def bin_to_data(data)
|
535
|
-
data.scan(/./m).map{|b| b.unpack('H2')[0].upcase}.join(':')
|
536
|
-
end
|
537
|
-
end
|
538
|
-
|
539
|
-
## Class for DHCP options containing DNS host names
|
540
|
-
class OptHost < OptData
|
541
|
-
def data_to_bin(data)
|
542
|
-
raise "Invalid host name" unless /^(?:[a-zA-Z0-9][a-zA-Z0-9-]{0,62}\.)*[a-zA-Z0-9][a-zA-Z0-9-]{0,62}$/.match(data)
|
543
|
-
data
|
544
|
-
end
|
545
|
-
end
|
546
|
-
|
547
|
-
## Class for DHCP options containing DNS domain names
|
548
|
-
class OptDomain < OptData
|
549
|
-
def data_to_bin(data)
|
550
|
-
raise "Invalid domain name" unless /^(?:[a-zA-Z0-9][a-zA-Z0-9-]{0,62}\.)*[a-zA-Z0-9][a-zA-Z0-9-]{0,62}\.?$/.match(data)
|
551
|
-
end
|
552
|
-
end
|
553
|
-
|
554
|
-
## Class representing a DHCP packet (a request or a response):
|
555
|
-
class Packet
|
556
|
-
def initialize(op=nil)
|
557
|
-
raise "Invalid/unsupported operation type #{op}" unless op.nil? || op == BOOTREQUEST || op == BOOTREPLY
|
558
|
-
@op = op || BOOTREQUEST ## 1: Operation (BOOTREQUEST=1/BOOTREPLY=2)
|
559
|
-
@htype_name = :htype_10mb_ethernet
|
560
|
-
@htype = HTYPE[@htype_name][0] ## 1: Hardware address type
|
561
|
-
@hlen = HTYPE[@htype_name][1] ## 1: Hardware address length
|
562
|
-
@hops = 0 ## 1: Client sets to zero, relays may increment
|
563
|
-
@xid = 0 ## 4: Client picks random XID (session ID of sorts)
|
564
|
-
@secs = 0 ## 4: Seconds elapsed since client started transaction
|
565
|
-
@flags = 0 ## 2: Leftmost bit is the 'BROADCAST' flag (if set) - Others are zero (reserved for future use)
|
566
|
-
@ciaddr = 0.chr * 4 ## 4: "Client IP" -- Only set by client if client state is BOUND/RENEW/REBINDING and client can respond to ARP requests
|
567
|
-
@yiaddr = 0.chr * 4 ## 4: "Your IP" -- Server assigns IP to client
|
568
|
-
@siaddr = 0.chr * 4 ## 4: "Server IP" -- IP of server to use in NEXT step of client bootstrap process
|
569
|
-
@giaddr = 0.chr * 4 ## 4: "Gateway IP" -- Relay agent will set this to itself and modify replies
|
570
|
-
## 16: Client hardware address (see htype and hlen)
|
571
|
-
@chaddr = 0.chr * @hlen ## ^^^ See note above ^^^
|
572
|
-
@sname = '' ## 64: Server host name (optional) as C-style null/zero terminated string (may instead contain options)
|
573
|
-
@file = '' ## 128: Boot file name (optional) as C-style null/zero terminated string (may instead contain options)
|
574
|
-
@options = '' ## variable: Options - Up to 312 bytes in a 576-byte DHCP message - First four bytes are MAGIC
|
575
|
-
@optlist = []
|
576
|
-
@type = nil ## Unknown until set
|
577
|
-
@type_name = 'UNKNOWN'
|
578
|
-
end
|
579
|
-
attr_reader :op, :htype_name, :htype, :hlen, :hops, :xid, :secs, :flags, :type, :type_name, :options, :optlist
|
580
|
-
attr_accessor :secs, :xid, :sname, :file
|
581
|
-
|
582
|
-
def initialize_copy(orig)
|
583
|
-
self.ciaddr = orig.ciaddr
|
584
|
-
self.yiaddr = orig.yiaddr
|
585
|
-
self.siaddr = orig.siaddr
|
586
|
-
self.giaddr = orig.giaddr
|
587
|
-
@chaddr = orig.raw_chaddr.dup
|
588
|
-
@file = orig.file.dup
|
589
|
-
@sname = orig.sname.dup
|
590
|
-
@options = orig.options.dup
|
591
|
-
@optlist = []
|
592
|
-
orig.optlist.each do |opt|
|
593
|
-
@optlist << opt.dup
|
594
|
-
end
|
595
|
-
end
|
596
|
-
|
597
|
-
def append_opt(opt)
|
598
|
-
if opt.name == :dhcp_message_type
|
599
|
-
unless @type.nil?
|
600
|
-
raise "DHCP message type ALREADY SET in packet"
|
601
|
-
end
|
602
|
-
set_type(opt)
|
603
|
-
end
|
604
|
-
@optlist << opt
|
605
|
-
end
|
606
|
-
|
607
|
-
def _find_htype(htype)
|
608
|
-
HTYPE.each do |name, htype|
|
609
|
-
if htype[0] == @htype
|
610
|
-
return name
|
611
|
-
end
|
612
|
-
end
|
613
|
-
return nil
|
614
|
-
end
|
615
|
-
|
616
|
-
def parse(msg)
|
617
|
-
raise "Packet is too short (#{msg.size} < 241)" if (msg.size < 241)
|
618
|
-
@op = msg[0,1].ord
|
619
|
-
raise 'Invalid OP (expected BOOTREQUEST or BOOTREPLY)' if @op != BOOTREQUEST && @op != BOOTREPLY
|
620
|
-
self.htype = msg[1,1].ord ## This will do sanity checking and raise an exception on unsupported HTYPE
|
621
|
-
raise "Invalid hardware address length #{msg[2,1].ord} (expected #{@hlen})" if msg[2,1].ord != @hlen
|
622
|
-
@hops = msg[3,1].ord
|
623
|
-
@xid = msg[4,4].unpack('N')[0]
|
624
|
-
@secs = msg[8,2].unpack('n')[0]
|
625
|
-
@flags = msg[10,2].unpack('n')[0]
|
626
|
-
@ciaddr = msg[12,4]
|
627
|
-
@yiaddr = msg[16,4]
|
628
|
-
@siaddr = msg[20,4]
|
629
|
-
@giaddr = msg[24,4]
|
630
|
-
@chaddr = msg[28,16]
|
631
|
-
@sname = msg[44,64]
|
632
|
-
@file = msg[108,128]
|
633
|
-
magic = msg[236,4]
|
634
|
-
raise "Invalid DHCP OPTION MAGIC #{magic.each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join(':')} != #{MAGIC.each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join(':')}" if magic != MAGIC
|
635
|
-
@options = msg[240,msg.size-240]
|
636
|
-
@optlist = []
|
637
|
-
parse_opts(@options)
|
638
|
-
opt = get_option(:option_overload)
|
639
|
-
unless opt.nil?
|
640
|
-
## RFC 2131: If "option overload" present, parse FILE field first, then SNAME (depending on overload value)
|
641
|
-
parse_opts(@file) if opt.get == 1 || opt.get == 3
|
642
|
-
parse_opts(@sname) if opt.get == 2 || opt.get == 3
|
643
|
-
raise "Invalid option overload value" if opt.val > 1 || opt.val > 3
|
644
|
-
end
|
645
|
-
opt = get_option(:dhcp_message_type)
|
646
|
-
raise "Not a valid DHCP packet (may be BOOTP): Missing DHCP MESSAGE TYPE" if opt.nil?
|
647
|
-
set_type(opt)
|
648
|
-
end
|
649
|
-
|
650
|
-
def set_type(opt)
|
651
|
-
@type = opt.get
|
652
|
-
case @type
|
653
|
-
when DHCPDISCOVER
|
654
|
-
@type_name = 'DHCPDISCOVER'
|
655
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREQUEST
|
656
|
-
when DHCPOFFER
|
657
|
-
@type_name = 'DHCPOFFER'
|
658
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREPLY
|
659
|
-
when DHCPREQUEST
|
660
|
-
@type_name = 'DHCPREQUEST'
|
661
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREQUEST
|
662
|
-
when DHCPDECLINE
|
663
|
-
@type_name = 'DHCPDECLINE'
|
664
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREQUEST
|
665
|
-
when DHCPACK
|
666
|
-
@type_name = 'DHCPACK'
|
667
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREPLY
|
668
|
-
when DHCPNAK
|
669
|
-
@type_name = 'DHCPNAK'
|
670
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREPLY
|
671
|
-
when DHCPRELEASE
|
672
|
-
@type_name = 'DHCPRELEASE'
|
673
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREQUEST
|
674
|
-
when DHCPINFORM
|
675
|
-
@type_name = 'DHCPINFORM'
|
676
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREQUEST
|
677
|
-
when DHCPFORCERENEW
|
678
|
-
@type_name = 'DHCPFORCERENEW'
|
679
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREPLY
|
680
|
-
when DHCPLEASEQUERY
|
681
|
-
@type_name = 'DHCPLEASEQUERY'
|
682
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREQUEST
|
683
|
-
when DHCPLEASEUNASSIGNED
|
684
|
-
@type_name = 'DHCPLEASEUNASSIGNED'
|
685
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREPLY
|
686
|
-
when DHCPLEASEUNKNOWN
|
687
|
-
@type_name = 'DHCPLEASEUNKNOWN'
|
688
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREPLY
|
689
|
-
when DHCPLEASEACTIVE
|
690
|
-
@type_name = 'DHCPLEASEACTIVE'
|
691
|
-
raise "Invalid OP #{@op} for #{@type_name}" unless @op == BOOTREPLY
|
692
|
-
else
|
693
|
-
raise "Invalid DHCP MESSAGE TYPE" if opt.val < 1 || opt.val > 8
|
694
|
-
end
|
695
|
-
end
|
696
|
-
|
697
|
-
## Look through a packet's options for the option in question:
|
698
|
-
def get_option(opt)
|
699
|
-
@optlist.each do |o|
|
700
|
-
return o if (opt.is_a?(Symbol) && o.name == opt) || (opt.is_a?(Fixnum) && o.opt == opt)
|
701
|
-
end
|
702
|
-
nil
|
703
|
-
end
|
704
|
-
|
705
|
-
def parse_opts(opts)
|
706
|
-
msg = opts.dup
|
707
|
-
while msg.size > 0
|
708
|
-
opt = msg[0,1].ord
|
709
|
-
if opt == 0
|
710
|
-
## Don't add padding options to our list...
|
711
|
-
msg[0,1] = ''
|
712
|
-
elsif opt == 255
|
713
|
-
## Options end... Assume all the rest is padding (if any)
|
714
|
-
@optlist << Opt.new(255, :end)
|
715
|
-
msg = ''
|
716
|
-
else
|
717
|
-
## TODO: If an option value can't fit within a single option,
|
718
|
-
## it may span several and the values should be merged. We
|
719
|
-
## don't support this yet for parsing.
|
720
|
-
raise "Options end too soon" if msg.size == 1
|
721
|
-
len = msg[1,1].ord
|
722
|
-
raise "Options end too abruptly (expected #{len} more bytes, but found only #{msg.size - 2})" if msg.size < len + 2
|
723
|
-
val = msg[2,len]
|
724
|
-
msg[0,len+2] = ''
|
725
|
-
o = get_option(opt)
|
726
|
-
if o.nil?
|
727
|
-
o = DHCP::make_opt(opt)
|
728
|
-
if o.nil?
|
729
|
-
puts "WARNING: Ignoring unsupported option #{opt} (#{len} bytes)"
|
730
|
-
else
|
731
|
-
o.data = val unless len == 0
|
732
|
-
@optlist << o
|
733
|
-
end
|
734
|
-
else
|
735
|
-
## See above TODO note...
|
736
|
-
puts "WARNING: Duplicate option #{opt} (#{o.name}) of #{len} bytes skipped/ignored"
|
737
|
-
end
|
738
|
-
end
|
739
|
-
end
|
740
|
-
end
|
741
|
-
|
742
|
-
def to_packet
|
743
|
-
packet =
|
744
|
-
@op.chr + @htype.chr + @hlen.chr + @hops.chr +
|
745
|
-
[@xid, @secs, @flags].pack('Nnn') +
|
746
|
-
@ciaddr + @yiaddr + @siaddr + @giaddr +
|
747
|
-
@chaddr + (0.chr * (16-@chaddr.size)) +
|
748
|
-
@sname + (0.chr * (64-@sname.size)) +
|
749
|
-
@file + (0.chr * (128-@file.size)) +
|
750
|
-
MAGIC +
|
751
|
-
@optlist.map{|x| x.to_opt}.join
|
752
|
-
packet + (packet.size < 300 ? 0.chr * (300 - packet.size) : '') ## Pad to minimum of 300 bytes (BOOTP min. packet size)
|
753
|
-
end
|
754
|
-
|
755
|
-
def to_s
|
756
|
-
str = "op=#{@op} "
|
757
|
-
case @op
|
758
|
-
when BOOTREQUEST
|
759
|
-
str += '(BOOTREQUEST)'
|
760
|
-
when BOOTREPLY
|
761
|
-
str += '(BOOTREPLY)'
|
762
|
-
else
|
763
|
-
str += '(UNKNOWN)'
|
764
|
-
end
|
765
|
-
str += "\n"
|
766
|
-
|
767
|
-
str += "htype=#{@htype} "
|
768
|
-
found = false
|
769
|
-
HTYPE.each do |name, htype|
|
770
|
-
if htype[0] == @htype
|
771
|
-
found = true
|
772
|
-
str += name.to_s.upcase + "\n" + 'hlen=' + htype[1].to_s + "\n"
|
773
|
-
str += "*** INVALID HLEN #{@hlen} != #{htype[1]} ***\n" if @hlen != htype[1]
|
774
|
-
break
|
775
|
-
end
|
776
|
-
end
|
777
|
-
str += "UNKNOWN\nhlen=" + @hlen.to_s + "\n" unless found
|
778
|
-
str += "hops=#{@hops}\n"
|
779
|
-
str += "xid=#{@xid} (0x" + [@xid].pack('N').each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join + ")\n"
|
780
|
-
str += "secs=#{@secs}\n"
|
781
|
-
str += "flags=#{@flags} (" + (broadcast? ? 'BROADCAST' : 'NON-BROADCAST') + ")\n"
|
782
|
-
str += 'ciaddr=' + ciaddr + "\n"
|
783
|
-
str += 'yiaddr=' + yiaddr + "\n"
|
784
|
-
str += 'siaddr=' + siaddr + "\n"
|
785
|
-
str += 'giaddr=' + giaddr + "\n"
|
786
|
-
str += 'chaddr=' + chaddr + "\n"
|
787
|
-
str += "sname='#{@sname.sub(/\x00.*$/,'')}' (#{@sname.sub(/\x00.*$/,'').size})\n"
|
788
|
-
str += "file='#{@file.sub(/\x00.*$/,'')}' (#{@file.sub(/\x00.*$/,'').size})\n"
|
789
|
-
str += 'MAGIC: (0x' + MAGIC.each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join + ")\n"
|
790
|
-
str += "OPTIONS(#{@optlist.size}) = [\n "
|
791
|
-
str += @optlist.map{|x| x.to_s}.join(",\n ") + "\n]\n"
|
792
|
-
str += "DHCP_PACKET_TYPE='#{@type_name}' (#{@type}) " unless @type.nil?
|
793
|
-
str
|
794
|
-
end
|
795
|
-
|
796
|
-
def htype=(htype)
|
797
|
-
@htype_name = _find_htype(htype)
|
798
|
-
raise "Invalid/unsupported hardware type #{htype}" if @htype_name.nil?
|
799
|
-
@hlen = HTYPE[@htype_name][1]
|
800
|
-
@htype = HTYPE[@htype_name][0]
|
801
|
-
end
|
802
|
-
|
803
|
-
## Broadcast flag:
|
804
|
-
def broadcast?
|
805
|
-
@flags & 0x8000 != 0
|
806
|
-
end
|
807
|
-
def broadcast!
|
808
|
-
@flags |= 0x8000
|
809
|
-
end
|
810
|
-
|
811
|
-
## Hardware address (ethernet MAC style):
|
812
|
-
def chaddr
|
813
|
-
@chaddr[0,@hlen].each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join(':')
|
814
|
-
end
|
815
|
-
def raw_chaddr
|
816
|
-
@chaddr
|
817
|
-
end
|
818
|
-
def chaddr=(addr)
|
819
|
-
raise "Invalid hardware address" if addr.size - @hlen + 1 != @hlen * 2 || !/^(?:[a-fA-F0-9]{2}[ \.:_\-])*[a-fA-F0-9]{2}$/.match(addr)
|
820
|
-
@chaddr = addr.split(/[ .:_-]/).map{|b| b.to_i(16).chr}.join
|
821
|
-
end
|
822
|
-
|
823
|
-
## IP accessors:
|
824
|
-
def ciaddr
|
825
|
-
IPAddress::IPv4::parse_data(@ciaddr).to_s
|
826
|
-
end
|
827
|
-
def ciaddr=(ip)
|
828
|
-
@ciaddr = IPAddress::IPv4.new(ip).data
|
829
|
-
end
|
830
|
-
|
831
|
-
def yiaddr
|
832
|
-
IPAddress::IPv4::parse_data(@yiaddr).to_s
|
833
|
-
end
|
834
|
-
def yiaddr=(ip)
|
835
|
-
@yiaddr = IPAddress::IPv4.new(ip).data
|
836
|
-
end
|
837
|
-
|
838
|
-
def siaddr
|
839
|
-
IPAddress::IPv4::parse_data(@siaddr).to_s
|
840
|
-
end
|
841
|
-
def siaddr=(ip)
|
842
|
-
@siaddr = IPAddress::IPv4.new(ip).data
|
843
|
-
end
|
844
|
-
|
845
|
-
def giaddr
|
846
|
-
IPAddress::IPv4::parse_data(@giaddr).to_s
|
847
|
-
end
|
848
|
-
def giaddr=(ip)
|
849
|
-
@giaddr = IPAddress::IPv4.new(ip).data
|
850
|
-
end
|
851
|
-
end
|
852
8
|
|
853
9
|
## BOOTP TYPES:
|
854
10
|
BOOTREQUEST = 1
|
@@ -875,87 +31,45 @@ module DHCP
|
|
875
31
|
DHCPLEASEUNKNOWN = 12
|
876
32
|
DHCPLEASEACTIVE = 13
|
877
33
|
|
878
|
-
##
|
879
|
-
|
34
|
+
## Map message type string to integer type:
|
35
|
+
MSG_STR_TO_TYPE = {
|
36
|
+
'DHCPDISCOVER' => DHCPDISCOVER,
|
37
|
+
'DHCPOFFER' => DHCPOFFER,
|
38
|
+
'DHCPREQUEST' => DHCPREQUEST,
|
39
|
+
'DHCPDECLINE' => DHCPDECLINE,
|
40
|
+
'DHCPACK' => DHCPACK,
|
41
|
+
'DHCPNAK' => DHCPNAK,
|
42
|
+
'DHCPRELEASE' => DHCPRELEASE,
|
43
|
+
'DHCPINFORM' => DHCPINFORM,
|
44
|
+
'DHCPFORCERENEW' => DHCPFORCERENEW,
|
45
|
+
'DHCPLEASEQUERY' => DHCPLEASEQUERY,
|
46
|
+
'DHCPLEASEUNASSIGNED' => DHCPLEASEUNASSIGNED,
|
47
|
+
'DHCPLEASEUNKNOWN' => DHCPLEASEUNKNOWN,
|
48
|
+
'DHCPLEASEACTIVE' => DHCPLEASEACTIVE
|
49
|
+
}
|
880
50
|
|
881
|
-
##
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
:domain_name => [ 15, OptHost ], ## RFC 1034/1035 domain name
|
900
|
-
:swap_server => [ 16, OptIP ], ## Swap server
|
901
|
-
:root_path => [ 17, OptData ], ## Pathname to mount as root disk
|
902
|
-
:extensions_path => [ 18, OptData ], ## TFTP-available file containing info to be interpreted the same way as 64-byte vendor-extension field in a BOOTP response with some exceptions (See RFC 1497)
|
903
|
-
:ip_forwarding => [ 19, OptBool ], ## Host should enable/disable IP forwarding (0=disable/1=enable)
|
904
|
-
:nonlocal_source_routing => [ 20, OptBool ], ## Enable/disable source routing
|
905
|
-
:interface_mtu => [ 26, OptInt16 ],
|
906
|
-
:broadcast_address => [ 28, OptIP ],
|
907
|
-
:perform_mask_discovery => [ 29, OptBool ], ## This server always sets to NO/FALSE
|
908
|
-
:mask_supplier => [ 30, OptBool ], ## This server always sets to NO/FALSE
|
909
|
-
:perform_router_discovery => [ 31, OptBool ], ## This server always sets to NO/FALSE - RFC 1265
|
910
|
-
:router_solicitation_address => [ 32, OptIP ],
|
911
|
-
:static_routes => [ 33, OptStaticRoutes ], ## Use option 121 instead - Must NOT specify default route with this
|
912
|
-
:arp_cache_timeout => [ 35, OptInt32 ], ## Unsigned integer no. of seconds for ARP cache timeout
|
913
|
-
:ethernet_encapsulation => [ 36, OptBool ], ## 0/false = Eth. v2 RFC 894 encapsulation, 1/true = 802.3 RFC 1042 encapsulation
|
914
|
-
:ntp_servers => [ 42, OptIPList ],
|
915
|
-
:vendor_specific_information => [ 43, OptSubList ],
|
916
|
-
:netbios_name_server => [ 44, OptIPList ], ## NetBIOS name server list
|
917
|
-
:netbios_over_tcpip_node_type => [ 46, OptByte ], ## NetBIOS node type: 1=B-node, 2=P-node, 4=M-node, 8=H-node
|
918
|
-
:netbios_over_tcpip_scope => [ 47, OptData ], ## NetBIOS scope
|
919
|
-
:requested_ip_address => [ 50, OptIP ], ## Client's requested IP
|
920
|
-
:ip_address_lease_time => [ 51, OptInt32 ], ## How long the lease lasts
|
921
|
-
:option_overload => [ 52, OptByte ], ## 1, 2, or 3 == 'file' has options, 'sname' has options, both have options
|
922
|
-
:dhcp_message_type => [ 53, OptByte ], ## One of the above-defined DHCP MESSAGE TYPEs
|
923
|
-
:server_identifier => [ 54, OptIP ], ## How the client differentiates between DHCP servers
|
924
|
-
:parameter_request_list => [ 55, OptByteList ], ## List of options the CLIENT is requesting in response
|
925
|
-
:message => [ 56, OptData ], ## Message in DHCPNAK or DHCPDECLINE saying why that response was sent
|
926
|
-
:maximum_dhcp_message_size => [ 57, OptInt16 ], ## Client tells server max. message size it will accept
|
927
|
-
:vendor_class_identifier => [ 60, OptData ], ## MS boxes send "MSFT 98" or "MSFT 5.0"
|
928
|
-
:client_identifier => [ 61, OptHexString ], ## Client's identifier (client picks ANYTHING)
|
929
|
-
:smtp_servers => [ 69, OptIPList ],
|
930
|
-
:tftp_server_name => [ 66, OptData ], ## TFTP 'sname' value if 'sname' is overloaded with options
|
931
|
-
:bootfile_name => [ 67, OptData ], ## File name in 'file' if 'file' is overloaded with options
|
932
|
-
:pop3_servers => [ 70, OptIPList ],
|
933
|
-
:client_fqdn => [ 81, OptData ], ## Client's requested FQDN (DHCP server could use to update dynamic DNS)
|
934
|
-
:relay_agent_information => [ 82, OptSubList ], ## VERY USEFUL with Cisco CMTS and Motorola Canopy
|
935
|
-
:isns_servers => [ 83, OptData ], ## RFC 4184 Internet Storage Name Servers DHCP option (primary and backup)
|
936
|
-
:authentication => [ 90, OptData ], ## RFC 3118 authentication option -- NOT IMPLEMENTED
|
937
|
-
:client_last_transaction_time => [ 91, OptInt32 ], ## RFC 4388 leasequery option
|
938
|
-
:associated_ip => [ 92, OptIPList ], ## RFC 4388 leasequery option
|
939
|
-
:tz_posix => [ 100, OptData ], ## RFC 4833 timezone TZ-POSIX string (a POSIX time zone string like "MST7MDT6,M3.2.0/02:00,M11.1.0/02:00" which specifies an offset of 7 hours behind UTC during standard time, 6 during daylight time, with daylight beginning the 2nd Sunday in March at 2:00 AM local time and continuing until the 1st Sunday in November at 2:00 AM local time)
|
940
|
-
:tz_database => [ 101, OptData ], ## RFC 4833 timezone TZ-Database string (the name of a time zone in a database, like "America/Denver")
|
941
|
-
:classless_static_routes => [ 121, OptRouteList ], ## RFC 3442 classless static routes - obsoletes option 33 - Ignore opt. 33 if 121 is present - Should specify default routes using option 3 if this option is also present (can specify them in this option too) so if a client ignores 121, a default route will still be set up -- If client requests CLASSLESS STATIC ROUTES and either ROUTERS and/or STATIC ROUTES, ONLY respond with this option (see p. 6 RFC 3442)
|
942
|
-
## START SITE-SPECIFIC OPTIONS (128..254 inclusive):
|
943
|
-
:ms_classless_static_routes => [ 249, OptRouteList ], ## Microsoft version of option 121 - does NOT ignore opt. 33 if present (differs from opt. 121)
|
944
|
-
:site_local_auto_proxy_config => [ 252, OptData ], ## WPAD site-local proxy configuration
|
945
|
-
## END SITE-SPECIFIC OPTIONS
|
946
|
-
:end => [ 255, Opt ]
|
51
|
+
## Map message integer type to string:
|
52
|
+
MSG_TYPE_TO_STR = MSG_STR_TO_TYPE.invert
|
53
|
+
|
54
|
+
## Map message type to correct packet operation (BOOTREQUEST/BOOTREPLY):
|
55
|
+
MSG_TYPE_TO_OP = {
|
56
|
+
DHCPDISCOVER => BOOTREQUEST,
|
57
|
+
DHCPOFFER => BOOTREPLY,
|
58
|
+
DHCPREQUEST => BOOTREQUEST,
|
59
|
+
DHCPDECLINE => BOOTREPLY,
|
60
|
+
DHCPACK => BOOTREPLY,
|
61
|
+
DHCPNAK => BOOTREPLY,
|
62
|
+
DHCPRELEASE => BOOTREQUEST,
|
63
|
+
DHCPINFORM => BOOTREQUEST,
|
64
|
+
DHCPFORCERENEW => BOOTREPLY,
|
65
|
+
DHCPLEASEQUERY => BOOTREQUEST,
|
66
|
+
DHCPLEASEUNASSIGNED => BOOTREPLY,
|
67
|
+
DHCPLEASEUNKNOWN => BOOTREPLY,
|
68
|
+
DHCPLEASEACTIVE => BOOTREPLY
|
947
69
|
}
|
948
70
|
|
949
|
-
|
950
|
-
|
951
|
-
OPTIONS[name][1].new(OPTIONS[name][0], name, data)
|
952
|
-
end
|
71
|
+
## DHCP MAGIC:
|
72
|
+
MAGIC = [99, 130, 83, 99].pack('C4')
|
953
73
|
|
954
|
-
def self.make_opt(opt, data=nil)
|
955
|
-
OPTIONS.each do |name, info|
|
956
|
-
return info[1].new(info[0], name, data) if info[0] == opt
|
957
|
-
end
|
958
|
-
return nil
|
959
|
-
end
|
960
74
|
end
|
961
75
|
|