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/options.rb
ADDED
@@ -0,0 +1,652 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: ASCII-8BIT
|
3
|
+
#
|
4
|
+
# --
|
5
|
+
#
|
6
|
+
# Ruby DHCP module for parsing and creating IPv4 DHCP options
|
7
|
+
# - See http://www.aarongifford.com/computers/dhcp/
|
8
|
+
#
|
9
|
+
# --
|
10
|
+
#
|
11
|
+
# Written by Aaron D. Gifford - http://www.aarongifford.com/
|
12
|
+
#
|
13
|
+
# Copyright (c) 2010-2011 InfoWest, Inc. and Aaron D. Gifford
|
14
|
+
#
|
15
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
16
|
+
# of this software and associated documentation files (the "Software"), to deal
|
17
|
+
# in the Software without restriction, including without limitation the rights
|
18
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
19
|
+
# copies of the Software, and to permit persons to whom the Software is
|
20
|
+
# furnished to do so, subject to the following conditions:
|
21
|
+
#
|
22
|
+
# The above copyright notice and this permission notice shall be included in
|
23
|
+
# all copies or substantial portions of the Software.
|
24
|
+
#
|
25
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
26
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
27
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
28
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
29
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
30
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
31
|
+
# THE SOFTWARE.
|
32
|
+
#
|
33
|
+
# --
|
34
|
+
#
|
35
|
+
# NOTE: All strings in this module should be BINARY (ASCII-8BIT) encoded
|
36
|
+
# or things won't work correctly.
|
37
|
+
#
|
38
|
+
|
39
|
+
## Import DHCP module constants:
|
40
|
+
require_relative 'dhcp'
|
41
|
+
|
42
|
+
module DHCP
|
43
|
+
## Base class from which all DHCP options in a DHCP packet derive:
|
44
|
+
class Opt
|
45
|
+
def initialize(opt, name, ignore=nil)
|
46
|
+
@opt = opt
|
47
|
+
@name = name
|
48
|
+
end
|
49
|
+
attr_reader :opt, :name
|
50
|
+
|
51
|
+
def opt_header
|
52
|
+
"OPTION[#{opt},#{@name}]"
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
opt_header
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_opt
|
60
|
+
@opt.chr
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
## Class for DHCP options that contain data
|
66
|
+
class OptData < Opt
|
67
|
+
def initialize(opt, name, data=nil)
|
68
|
+
super(opt, name)
|
69
|
+
@data = data.nil? ? '' : data_to_bin(data)
|
70
|
+
end
|
71
|
+
attr_accessor :data
|
72
|
+
|
73
|
+
def data
|
74
|
+
@data
|
75
|
+
end
|
76
|
+
|
77
|
+
def data=(data)
|
78
|
+
@data = data.dup
|
79
|
+
self ## Chainable
|
80
|
+
end
|
81
|
+
|
82
|
+
def set(data)
|
83
|
+
self.data = data_to_bin(data)
|
84
|
+
self ## Chainable
|
85
|
+
end
|
86
|
+
|
87
|
+
def get
|
88
|
+
bin_to_data(@data)
|
89
|
+
end
|
90
|
+
|
91
|
+
def data_to_bin(data) ## Override this in subclasses to interpret data
|
92
|
+
data
|
93
|
+
end
|
94
|
+
|
95
|
+
def bin_to_data(data) ## Override this in subclasses to interpret data
|
96
|
+
data
|
97
|
+
end
|
98
|
+
|
99
|
+
def opt_header
|
100
|
+
super + "(#{data.size})"
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_s
|
104
|
+
opt_header + "='#{bin_to_data(@data)}'"
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_opt
|
108
|
+
super + @data.size.chr + @data
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
## Class for DHCP options containing a fixed number of bytes
|
114
|
+
class OptFixedData < OptData
|
115
|
+
@size = 0 ## Override this in subclasses
|
116
|
+
class << self
|
117
|
+
attr_accessor :size
|
118
|
+
end
|
119
|
+
|
120
|
+
def initialize(opt, name, data=nil)
|
121
|
+
super(opt, name, data)
|
122
|
+
## Prefill with zeros if needed:
|
123
|
+
@data = 0.chr * self.class.size if data.nil? && self.class.size > 0
|
124
|
+
end
|
125
|
+
|
126
|
+
def data=(data)
|
127
|
+
raise "Invalid size for #{self.class} (expected #{size} bytes, not #{data.size} bytes)" unless self.class.size == data.size
|
128
|
+
super(data)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
## Class for DHCP options that contain a lists (like lists of IPs)
|
133
|
+
class OptListData < OptData
|
134
|
+
include Enumerable
|
135
|
+
def initialize(opt, name, data=nil)
|
136
|
+
super(opt, name)
|
137
|
+
@size = 0
|
138
|
+
set(data) unless data.nil?
|
139
|
+
end
|
140
|
+
|
141
|
+
def data=(data)
|
142
|
+
set(split_data(data))
|
143
|
+
end
|
144
|
+
|
145
|
+
def get
|
146
|
+
split_data(@data) ## Splits and interprets binary data
|
147
|
+
end
|
148
|
+
|
149
|
+
def set(list)
|
150
|
+
list = [list] unless is_list?(list)
|
151
|
+
@data = ''
|
152
|
+
@size = 0
|
153
|
+
list.each do |item|
|
154
|
+
append(item)
|
155
|
+
end
|
156
|
+
self ## Chainable
|
157
|
+
end
|
158
|
+
|
159
|
+
def is_list?(list) ## Override if needed in child class
|
160
|
+
list.is_a?(Array)
|
161
|
+
end
|
162
|
+
|
163
|
+
def append(item)
|
164
|
+
@size += 1
|
165
|
+
@data += data_to_bin(item)
|
166
|
+
self ## Chainable
|
167
|
+
end
|
168
|
+
|
169
|
+
def split_data(data) ## Override in child class to split and interpret binary data
|
170
|
+
raise "Child class #{data.class} MUST override this"
|
171
|
+
end
|
172
|
+
|
173
|
+
def size
|
174
|
+
@size
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_s
|
178
|
+
opt_header + '=[' + map{|x| x.to_s}.join(',') + ']'
|
179
|
+
end
|
180
|
+
|
181
|
+
def each
|
182
|
+
split_data(@data).each do |item|
|
183
|
+
yield item
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
## Class for DHCP option suboptions:
|
189
|
+
class SubOpt < OptData
|
190
|
+
def opt_header
|
191
|
+
"suboption[#{opt}:#{@name}]"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
## Class for DHCP option suboptions containing lists
|
196
|
+
class SubOptList < OptListData
|
197
|
+
def opt_header
|
198
|
+
"suboption[#{opt}:#{@name}]"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
## Class for DHCP suboption for vendor specific information
|
203
|
+
class SubOptVSRInfo < SubOptList
|
204
|
+
def is_list?(list)
|
205
|
+
raise "Invalid suboption sublist/entry" unless list.is_a?(Array)
|
206
|
+
return false if list.size == 2 && list[0].is_a?(Fixnum) && list[1].is_a?(String)
|
207
|
+
list.each do |item|
|
208
|
+
raise "Invalid suboption sublistlist" unless item.is_a?(Array) && item.size == 2 && item[0].is_a?(Fixnum) && item[1].is_a?(String)
|
209
|
+
end
|
210
|
+
return true
|
211
|
+
end
|
212
|
+
|
213
|
+
def split_data(data)
|
214
|
+
data = data.dup
|
215
|
+
list = []
|
216
|
+
while data.size > 0
|
217
|
+
raise "Invalid suboption data" unless data.size >= 5
|
218
|
+
len = data[4,1].ord
|
219
|
+
raise "Invalid vendor-specific relay info. data length" unless data.size >= len + 5
|
220
|
+
list << [ data[0,4].unpack('N')[0], data[5,len] ]
|
221
|
+
data[0,5+len] = ''
|
222
|
+
end
|
223
|
+
list
|
224
|
+
end
|
225
|
+
|
226
|
+
def bin_to_data(data)
|
227
|
+
raise "Invalid data size" unless data.size >= 5 && data.size == data[4,1].ord + 5
|
228
|
+
[ data[0,1].ord, data[2,data.size-2] ]
|
229
|
+
end
|
230
|
+
|
231
|
+
def data_to_bin(data)
|
232
|
+
raise "Invalid data" unless data.is_a?(Array) && data.size == 2 && data[0].is_a?(Fixnum) && data[1].is_a?(String)
|
233
|
+
raise "Invalid data size" unless data[1].size < 256
|
234
|
+
data[0].chr + data[1].size.chr + data[1]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
## Class for DHCP options that contain sublists (like vendor specific information or relay agent information)
|
239
|
+
class OptSubList < OptListData
|
240
|
+
def is_list?(list)
|
241
|
+
raise "Invalid suboption list/entry" unless list.is_a?(Array)
|
242
|
+
return false if list.size == 2 && list[0].is_a?(Fixnum) && list[1].is_a?(String)
|
243
|
+
list.each do |item|
|
244
|
+
raise "Invalid suboption list" unless item.is_a?(Array) && item.size == 2 && item[0].is_a?(Fixnum) && item[1].is_a?(String)
|
245
|
+
end
|
246
|
+
return true
|
247
|
+
end
|
248
|
+
|
249
|
+
def split_data(data)
|
250
|
+
data = data.dup
|
251
|
+
list = []
|
252
|
+
while data.size > 0
|
253
|
+
raise "Invalid data size" unless data.size >= 2
|
254
|
+
len = data[1,1].ord
|
255
|
+
raise "Invalid data size" unless data.size >= len + 2
|
256
|
+
list << [ data[0,1].ord, data[2,len] ]
|
257
|
+
data[0,len+2] = ''
|
258
|
+
end
|
259
|
+
list
|
260
|
+
end
|
261
|
+
|
262
|
+
def bin_to_data(data)
|
263
|
+
raise "Invalid data size" unless data.size >= 2 && data.size == data[1,1].ord + 2
|
264
|
+
[ data[0,1].ord, data[2,data.size-2] ]
|
265
|
+
end
|
266
|
+
|
267
|
+
def data_to_bin(data)
|
268
|
+
raise "Invalid data" unless data.is_a?(Array) && data.size == 2 && data[0].is_a?(Fixnum) && data[1].is_a?(String)
|
269
|
+
raise "Invalid data size" unless data[1].size < 256
|
270
|
+
data[0].chr + data[1].size.chr + data[1]
|
271
|
+
end
|
272
|
+
|
273
|
+
def to_s
|
274
|
+
opt_header + "(#{@size})=[" + map do |i|
|
275
|
+
val = ''
|
276
|
+
name = case i[0]
|
277
|
+
when 1
|
278
|
+
val = i[1].scan(/./m).map{|b| b.unpack('H2')[0].upcase}.join(':')
|
279
|
+
'AgentCircuitID'
|
280
|
+
when 2
|
281
|
+
val = i[1].scan(/./m).map{|b| b.unpack('H2')[0].upcase}.join(':')
|
282
|
+
'AgentRemoteID'
|
283
|
+
when 9
|
284
|
+
val = (SubOptVSRInfo.new(9, :vendor_specific_relay_suboption).data=i[1]).to_s
|
285
|
+
'VendorSpecificRelaySuboption'
|
286
|
+
else
|
287
|
+
val = i[1].scan(/./m).map{|b| b.unpack('H2')[0].upcase}.join(':')
|
288
|
+
'Unknown'
|
289
|
+
end
|
290
|
+
"#{name}:#{i[0]}(#{i[1].size})='#{val}'"
|
291
|
+
end.join(',') + ']'
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
## Class for DHCP options that contain lists of fixed sized data
|
296
|
+
class OptListFixedData < OptListData
|
297
|
+
@item_size = 0 ## Override this in subclasses
|
298
|
+
class << self
|
299
|
+
attr_accessor :item_size
|
300
|
+
end
|
301
|
+
|
302
|
+
def split_data(data)
|
303
|
+
raise "Child class #{self.class} MUST override class item_size variable with non-zero value!" if self.class.item_size == 0
|
304
|
+
raise "Invalid data length #{data.size} (expected even multiple of #{self.class.item_size})" unless data.size % self.class.item_size == 0
|
305
|
+
list = []
|
306
|
+
data = data.dup
|
307
|
+
while data.size > 0
|
308
|
+
list << bin_to_data(data.slice!(0,self.class.item_size))
|
309
|
+
end
|
310
|
+
list
|
311
|
+
end
|
312
|
+
|
313
|
+
def data_to_bin(item) ## Override in child, but call super(item)
|
314
|
+
## with the resulting translated data after
|
315
|
+
## data translation so the size check is
|
316
|
+
## applied (or do a size check in the child):
|
317
|
+
raise "Invalid data item length #{item.size} (expected #{self.class.item_size})" unless item.size == self.class.item_size
|
318
|
+
item
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
## Class for DHCP options that contain a single IPv4 address
|
323
|
+
class OptIP < OptFixedData
|
324
|
+
@size = 4
|
325
|
+
|
326
|
+
def bin_to_data(data)
|
327
|
+
IPAddress::IPv4::parse_data(data).to_s
|
328
|
+
end
|
329
|
+
|
330
|
+
def data_to_bin(data)
|
331
|
+
IPAddress::IPv4.new(data).data ## Will raise exception if data is not a valid IP
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
## Class for DHCP options that contain a list of IPv4 addresses
|
336
|
+
class OptIPList < OptListFixedData
|
337
|
+
@item_size = 4
|
338
|
+
|
339
|
+
def bin_to_data(data)
|
340
|
+
IPAddress::IPv4::parse_data(data).to_s
|
341
|
+
end
|
342
|
+
|
343
|
+
def data_to_bin(data)
|
344
|
+
IPAddress::IPv4.new(data).data ## Will raise exception if data is not a valid IP
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
## Class for DHCP option 33 (static routes) - Use option 121 instead if possible
|
349
|
+
## WARNING: Option 33 can only handle class A, B, or C networks, not classless
|
350
|
+
## networks with an arbitrary netmask.
|
351
|
+
class OptStaticRoutes < OptListFixedData
|
352
|
+
@item_size = 8
|
353
|
+
|
354
|
+
def is_list?(list)
|
355
|
+
raise "Invalid route list/entry" unless list.is_a?(Array)
|
356
|
+
if list.size == 2
|
357
|
+
return false if list[0].is_a?(String) && list[1].is_a?(String)
|
358
|
+
return true if list[0].is_a?(Array) && list[1].is_a?(Array)
|
359
|
+
raise "Invalid route list/entry"
|
360
|
+
end
|
361
|
+
list.each do |item|
|
362
|
+
raise "Invalid route list" unless item.is_a?(Array) && item[0].is_a?(String) && item[1].is_a?(String)
|
363
|
+
end
|
364
|
+
return true
|
365
|
+
end
|
366
|
+
|
367
|
+
def data_to_bin(data)
|
368
|
+
raise "Invalid static route" unless data.is_a?(Array) && data.size == 2
|
369
|
+
net, gateway = *data
|
370
|
+
net = IPAddress::IPv4.new(net)
|
371
|
+
raise "Invalid classful static route network" unless net.network?
|
372
|
+
raise "Invalid classful static route network" unless (
|
373
|
+
(net.a? && net.prefix == 8 ) ||
|
374
|
+
(net.b? && net.prefix == 16) ||
|
375
|
+
(net.c? && net.prefix == 24)
|
376
|
+
)
|
377
|
+
gateway = IPAddress::IPv4.new("#{gateway}/#{net.prefix}")
|
378
|
+
raise "Invalid classful static route gateway" unless gateway.member?(net)
|
379
|
+
net.data + gateway.data
|
380
|
+
end
|
381
|
+
|
382
|
+
def bin_to_data(data)
|
383
|
+
[IPAddress::IPv4::parse_classful_data(data[0,4]).net.to_string, IPAddress::IPv4::parse_data(data[4,4]).to_s]
|
384
|
+
end
|
385
|
+
|
386
|
+
def to_s
|
387
|
+
opt_header + '=[' + map{|i| i[0] + '=>' + i[1]}.join(',') + ']'
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
## Class for DHCP options containing lists of IPv4 CIDR routes (like option 121 or MS's 249)
|
392
|
+
## See RFC 3442 "compact encoding" of destination
|
393
|
+
class OptRouteList < OptListData
|
394
|
+
def split_data(data)
|
395
|
+
data = data.dup
|
396
|
+
list = []
|
397
|
+
while data.size > 0
|
398
|
+
raise "Invalid binary data" unless data.size > 4 || data[0,1].ord > 32
|
399
|
+
octets = (data[0,1].ord + 7)/8
|
400
|
+
raise "Invalid binary data" unless data.size >= octets + 5
|
401
|
+
list << bin_to_data(data.slice!(0,octets+5))
|
402
|
+
end
|
403
|
+
list
|
404
|
+
end
|
405
|
+
|
406
|
+
def data_to_bin(data)
|
407
|
+
raise "Invalid classless static route" unless data.is_a?(Array) && data.size == 2
|
408
|
+
net, gateway = *data
|
409
|
+
raise "Invalid classless static route network" if net.index('/').nil?
|
410
|
+
net = IPAddress::IPv4.new(net)
|
411
|
+
raise "Invalid classless static route network" unless net.network?
|
412
|
+
gateway = IPAddress::IPv4.new("#{gateway}/#{net.prefix}")
|
413
|
+
raise "Invalid classless static route gateway" unless gateway.member?(net)
|
414
|
+
net.prefix.to_i.chr + net.data[0,(net.prefix+7)/8] + gateway.data
|
415
|
+
end
|
416
|
+
|
417
|
+
def bin_to_data(data)
|
418
|
+
raise "Invalid binary classless route data" unless data.size > 4 || data[0,1].ord > 32
|
419
|
+
maskbits = data[0,1].ord
|
420
|
+
octets = (maskbits+7)/8
|
421
|
+
raise "Invalid binary classless route data" unless data.size == octets + 5
|
422
|
+
dest = IPAddress::IPv4.parse_data(data[1,octets] + 0.chr * (4 - octets))
|
423
|
+
dest.prefix = maskbits
|
424
|
+
gateway = IPAddress::IPv4.parse_data(data[octets+1,4])
|
425
|
+
gateway.prefix = maskbits ## Unnecessary...
|
426
|
+
[dest.to_string, gateway.to_s]
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
## Class for boolean DHCP options
|
431
|
+
class OptBool < OptFixedData
|
432
|
+
@size = 1
|
433
|
+
|
434
|
+
def data_to_bin(data)
|
435
|
+
raise "Invalid boolean data #{data.class} (expected TrueClass or FalseClass)" unless data.is_a?(TrueClass) || data.is_a?(FalseClass)
|
436
|
+
data ? 1.chr : 0.chr
|
437
|
+
end
|
438
|
+
|
439
|
+
def bin_to_data(data)
|
440
|
+
raise "Invalid boolean binary data" if data.size != 1 || data.ord > 1
|
441
|
+
data.ord == 0 ? false : true
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
## Class for single-byte unsigned integer value DHCP options
|
446
|
+
## Also acts as parent class for fixed-sized multi-byte value
|
447
|
+
## DHCP options
|
448
|
+
class OptInt8 < OptFixedData
|
449
|
+
@size = 1
|
450
|
+
|
451
|
+
def data_to_bin(data)
|
452
|
+
raise "Invalid numeric data" unless data.is_a?(Fixnum) && data >= 0
|
453
|
+
raise "Invalid number" unless data == data & ([0xff] * self.class.size).inject(0){|sum,byte| sum<<8|byte}
|
454
|
+
bytes = ''
|
455
|
+
while data != 0
|
456
|
+
bytes = (data & 0xff).chr + bytes
|
457
|
+
data >>= 8
|
458
|
+
end
|
459
|
+
raise "Impossible: Numeric byte size #{bytes.size} exceeds #{self.class.size}" if bytes.size > self.class.size
|
460
|
+
0.chr * (self.class.size - bytes.size) + bytes
|
461
|
+
end
|
462
|
+
|
463
|
+
def bin_to_data(data)
|
464
|
+
data.each_byte.inject(0){|sum,byte| sum<<8|byte}
|
465
|
+
end
|
466
|
+
|
467
|
+
def to_s
|
468
|
+
opt_header + "=#{self.get}"
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
## Class for two-byte unsigned integer value DHCP options
|
473
|
+
class OptInt16 < OptInt8
|
474
|
+
@size = 2
|
475
|
+
end
|
476
|
+
|
477
|
+
## Class for four-byte unsigned integer value DHCP options
|
478
|
+
class OptInt32 < OptInt8
|
479
|
+
@size = 4
|
480
|
+
end
|
481
|
+
|
482
|
+
## Class for four-byte signed integer value DHCP options
|
483
|
+
class OptSInt32 < OptInt32
|
484
|
+
@size = 4
|
485
|
+
## Convert signed data to unsigned form
|
486
|
+
def data_to_bin(data)
|
487
|
+
super(data % 2**32)
|
488
|
+
end
|
489
|
+
|
490
|
+
## Convert unsigned form back to signed data
|
491
|
+
def bin_to_data(data)
|
492
|
+
(super(data) + 2**31) % 2**32 - 2**31
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
## Class for DHCP options containing a list of 8-bit integers (like
|
497
|
+
## lists of requested DHCP options). Also acts as parent class to
|
498
|
+
## lists of larger fixed-sized numeric types.
|
499
|
+
class OptInt8List < OptListFixedData
|
500
|
+
@item_size = 1
|
501
|
+
|
502
|
+
def bin_to_data(data)
|
503
|
+
data.each_byte.inject(0){|sum,byte| sum<<8|byte}
|
504
|
+
end
|
505
|
+
|
506
|
+
def data_to_bin(data)
|
507
|
+
raise "Invalid numeric data" unless data.is_a?(Fixnum) && data >= 0
|
508
|
+
raise "Invalid number" unless data == data & ([0xff] * self.class.item_size).inject(0){|sum,byte| sum<<8|byte}
|
509
|
+
bytes = ''
|
510
|
+
while data != 0
|
511
|
+
bytes = (data & 0xff).chr + bytes
|
512
|
+
data >>= 8
|
513
|
+
end
|
514
|
+
raise "Impossible: Numeric byte size #{bytes.size} exceeds #{self.class.item_size}" if bytes.size > self.class.item_size
|
515
|
+
0.chr * (self.class.item_size - bytes.size) + bytes
|
516
|
+
end
|
517
|
+
|
518
|
+
def to_s
|
519
|
+
opt_header + '=[' + map{|x| x.to_s}.join(',') + ']'
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
## Class for DHCP options containing a list of 16-bit unsigned integers:
|
524
|
+
class OptInt16List < OptInt8List
|
525
|
+
@item_size = 2
|
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 = data.gsub(/[ \.:_\-]/,'') ## Allow various octet separator characters (trim them out)
|
532
|
+
['0' * (data.size % 2)].pack('H*') ## Pad hex string to even multiple and translate to binary
|
533
|
+
end
|
534
|
+
|
535
|
+
def bin_to_data(data)
|
536
|
+
data.each_byte.map{|b| "%0.2X" % [b]}.join(':') ## Convert each byte to hex string and join bytes with ':'
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
## Class for DHCP options containing DNS host names
|
541
|
+
class OptHost < OptData
|
542
|
+
def data_to_bin(data)
|
543
|
+
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)
|
544
|
+
data
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
## Class for DHCP options containing DNS domain names
|
549
|
+
class OptDomain < OptData
|
550
|
+
def data_to_bin(data)
|
551
|
+
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)
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
## Options 0-18 and 254 are defined in RFC 1497 (BOOTP)
|
556
|
+
## TODO: Add in as yet unhandled options
|
557
|
+
OPTIONS = {
|
558
|
+
:pad => [ 0, Opt ], ## Pad (RFC 2132) - Padding option
|
559
|
+
:subnet_mask => [ 1, OptIP ],
|
560
|
+
:time_offset => [ 2, OptSInt32 ], ## Offset from GMT (signed 32-bit integer seconds)
|
561
|
+
:routers => [ 3, OptIPList ], ## Default gateway(s)
|
562
|
+
:time_servers => [ 4, OptIPList ],
|
563
|
+
:name_servers => [ 5, OptIPList ], ## IEN-116 name servers
|
564
|
+
:dns_servers => [ 6, OptIPList ], ## DNS server(s) (RFC-1034/1025)
|
565
|
+
:log_servers => [ 7, OptIPList ], ## Log server(s) (MIT-LCS UDP log servers)
|
566
|
+
:cookie_servers => [ 8, OptIPList ], ## Cookie/Quote-of-the-day (RFC 865) server(s)
|
567
|
+
:lpr_servers => [ 9, OptIPList ], ## LPR server(s) (RFC 1179)
|
568
|
+
:impress_servers => [ 10, OptIPList ], ## Impress server(s) (in pref. order)
|
569
|
+
:rlp_servers => [ 11, OptIPList ], ## RLP server(s) (RFC 887)
|
570
|
+
:host_name => [ 12, OptHost ], ## May or may not be qualified with local domain name (RFC 1035)
|
571
|
+
:boot_file_size => [ 13, OptInt16 ], ## Boot file size (number of 512-byte blocks as unsigned 16-bit integer)
|
572
|
+
:merit_dump_file => [ 14, OptData ], ## File name client should dump core to
|
573
|
+
:domain_name => [ 15, OptHost ], ## RFC 1034/1035 domain name
|
574
|
+
:swap_server => [ 16, OptIP ], ## Swap server
|
575
|
+
:root_path => [ 17, OptData ], ## Pathname to mount as root disk
|
576
|
+
: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)
|
577
|
+
:ip_forwarding => [ 19, OptBool ], ## Host should enable/disable IP forwarding (0=disable/1=enable)
|
578
|
+
:nonlocal_source_routing => [ 20, OptBool ], ## Enable/disable source routing
|
579
|
+
:max_dgram_reassembly_size => [ 22, OptInt16 ], ## Maximum Datagram Reassembly Size (RFC 1533) - Min. value 576
|
580
|
+
:default_ttl => [ 23, OptInt8 ], ## Default IP Time-to-live (TTL) (RFC 1533) - Value in the range 1..255 inclusive
|
581
|
+
:path_mtu_aging_timeout => [ 24, OptInt32 ], ## Path MTU Aging Timeout Option (RFC 2132) - Timeout to use when aging Path MTU values discovered according to RFC 1191
|
582
|
+
:path_mtu_plateau_table => [ 25, OptInt16List ], ## Path MTU Plateau Table Option (RFC 2132) - List of 16-bit unsigned integers ordered smallest to largest, minimum MTU value NOT smaller than 68, minimum of at least one list entry
|
583
|
+
:interface_mtu => [ 26, OptInt16 ], ## Interface MTU (RFC 1533) - Minimum value 68
|
584
|
+
:all_subnets_are_local => [ 27, OptBool ], ## All Subnets Are Local (RFC 1533) - 0 = client should assume some subnets of directly connected net(s) may have smaller MTUs, 1 = all subnets share single MTU value
|
585
|
+
:broadcast_address => [ 28, OptIP ], ## Broadcast Address (RFC 1533) - Client's broadcast IP on client's subnet
|
586
|
+
:perform_mask_discovery => [ 29, OptBool ], ## Perform Mask Discovery (RFC 1533) - 0 = client should perform mask discovery, 1 = client should not
|
587
|
+
:mask_supplier => [ 30, OptBool ], ## Mask Supplier (RFC 1533) - 0 = client should NOT respond to subnet mask requests using ICMP, 1 = client should respond
|
588
|
+
:perform_router_discovery => [ 31, OptBool ], ## Perform Router Discover (RFC 1265) - 0 = client should NOT perform router discovery, 1 = client should
|
589
|
+
:router_solicitation_address => [ 32, OptIP ], ## Router Solicitaion Address (RFC 1533) - IP address to which client transmits router solicitation requests
|
590
|
+
:static_routes => [ 33, OptStaticRoutes ], ## Static Route (RFC 15333) - List of static routes client should install in routing cache, listed in descending order of priority (if multiple routes to same dest. are specified) - Use option 121 instead - Must NOT specify default route with this! (Illegal destination '0.0.0.0' for this option.)
|
591
|
+
:arp_cache_timeout => [ 35, OptInt32 ], ## ARP Cache Timeout (RFC 1533) - Unsigned 32-bit integer timeout in seconds for ARP cache entries
|
592
|
+
:ethernet_encapsulation => [ 36, OptBool ], ## Ethernet Encapsulation (RFC 1533) - = 0 = use ethernet v2 RFC 894 encapsulation, 1 = use 802.3 RFC 1042 encapsulation
|
593
|
+
:tcp_default_ttl => [ 37, OptInt8 ], ## TCP Default TTL (RFC 1533) - Minimum value of 1
|
594
|
+
:tcp_keepalive_interval => [ 38, OptInt32 ], ## TCP Keepalive Interval (RFC 1533) - 0 = client should not generate keepalive messages unless requested by application - No. of seconds client should wait before sending keepalive messages on TCP connections
|
595
|
+
:ntp_servers => [ 42, OptIPList ],
|
596
|
+
:vendor_specific_information => [ 43, OptSubList ],
|
597
|
+
:netbios_name_server => [ 44, OptIPList ], ## NetBIOS name server list
|
598
|
+
:netbios_over_tcpip_node_type => [ 46, OptInt8 ], ## NetBIOS node type: 1=B-node, 2=P-node, 4=M-node, 8=H-node
|
599
|
+
:netbios_over_tcpip_scope => [ 47, OptData ], ## NetBIOS scope
|
600
|
+
:requested_ip_address => [ 50, OptIP ], ## Client's requested IP
|
601
|
+
:ip_address_lease_time => [ 51, OptInt32 ], ## How long the lease lasts
|
602
|
+
:option_overload => [ 52, OptInt8 ], ## Option Overload (RFC 2132) - 1, 2, or 3 == 'file' has options, 'sname' has options, both have options (RFC 2132)
|
603
|
+
:dhcp_message_type => [ 53, OptInt8 ], ## One of the above-defined DHCP MESSAGE TYPEs
|
604
|
+
:server_identifier => [ 54, OptIP ], ## How the client differentiates between DHCP servers
|
605
|
+
:parameter_request_list => [ 55, OptInt8List ], ## List of options the CLIENT is requesting in response
|
606
|
+
:message => [ 56, OptData ], ## Message in DHCPNAK or DHCPDECLINE saying why that response was sent
|
607
|
+
:maximum_dhcp_message_size => [ 57, OptInt16 ], ## Maximum DHCP Message Size (RFD 2132) - Client tells server max. message size it will accept. Minimum allowed value is 576 octets. Do NOT include in DHCPDECLINE messages. On an ethernet with a 1500-byte MTU, subtracting 20 bytes for IP overhead and 8 for UDP overhead, the maximum packet size to use would be 1472 bytes.
|
608
|
+
:vendor_class_identifier => [ 60, OptData ], ## For example, some MS boxes send "MSFT 98" or "MSFT 5.0"
|
609
|
+
:client_identifier => [ 61, OptHexString ], ## Client's identifier (client picks ANYTHING)
|
610
|
+
:netware_ip_domain_name => [ 62, OptData ], ## NetWare/IP Domain Name (RFC 2242)
|
611
|
+
:netware_ip_information => [ 63, OptSubList ], ## NetWare/IP Information (RFC 2242)
|
612
|
+
:nis_domain_name => [ 64, OptData ], ## Network Information Service+ Domain (RFC 2132)
|
613
|
+
:nis_servers => [ 65, OptIPList ], ## Network Information Service+ Servers (RFC 2132) (one or more IPs)
|
614
|
+
:tftp_server_name => [ 66, OptData ], ## TFTP Server Name (RFC 2132) - Used when the 'sname' field has been used for DHCP options (option 52 has value of 2 or 3)
|
615
|
+
:bootfile_name => [ 67, OptData ], ## Bootfile Name (RFC 2132) - Used when the 'file' field has been used for DHCP options (option 52 has value of 1 or 3)
|
616
|
+
:mobile_ip_home_agent => [ 68, OptIPList ], ## Mobile IP Home Agent (RFC 2132) list of IP addresses indicating mobile IP home agents available to the client in order of preference (zero or more IPs)
|
617
|
+
:smtp_servers => [ 69, OptIPList ],
|
618
|
+
:pop3_servers => [ 70, OptIPList ],
|
619
|
+
:client_fqdn => [ 81, OptData ], ## Client's requested FQDN (DHCP server could use to update dynamic DNS)
|
620
|
+
:relay_agent_information => [ 82, OptSubList ], ## VERY USEFUL with Cisco CMTS and Motorola Canopy
|
621
|
+
:isns_servers => [ 83, OptData ], ## RFC 4184 Internet Storage Name Servers DHCP option (primary and backup)
|
622
|
+
:authentication => [ 90, OptData ], ## RFC 3118 authentication option -- NOT IMPLEMENTED
|
623
|
+
:client_last_transaction_time => [ 91, OptInt32 ], ## RFC 4388 leasequery option
|
624
|
+
:associated_ip => [ 92, OptIPList ], ## RFC 4388 leasequery option
|
625
|
+
: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)
|
626
|
+
:tz_database => [ 101, OptData ], ## RFC 4833 timezone TZ-Database string (the name of a time zone in a database, like "America/Denver")
|
627
|
+
: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)
|
628
|
+
|
629
|
+
## START SITE-SPECIFIC OPTIONS (128..254 inclusive):
|
630
|
+
:ms_classless_static_routes => [ 249, OptRouteList ], ## Microsoft version of option 121 - does NOT ignore opt. 33 if present (differs from opt. 121)
|
631
|
+
:site_local_auto_proxy_config => [ 252, OptData ], ## WPAD site-local proxy configuration
|
632
|
+
## END SITE-SPECIFIC OPTIONS
|
633
|
+
|
634
|
+
:end => [ 255, Opt ] ## End (RFC 2132) Mark end of options in vendor field - subsequent bytes are pad options
|
635
|
+
}
|
636
|
+
|
637
|
+
## Create a new DHCP option object based on the symbolic name:
|
638
|
+
def self.make_opt_name(name, data=nil)
|
639
|
+
raise "Unknown/unhandled option '#{name}'" unless OPTIONS.key?(name)
|
640
|
+
OPTIONS[name][1].new(OPTIONS[name][0], name, data)
|
641
|
+
end
|
642
|
+
|
643
|
+
## Create a new DHCP option object based on the option number:
|
644
|
+
def self.make_opt(opt, data=nil)
|
645
|
+
OPTIONS.each do |name, info|
|
646
|
+
return info[1].new(info[0], name, data) if info[0] == opt
|
647
|
+
end
|
648
|
+
return nil
|
649
|
+
end
|
650
|
+
|
651
|
+
end
|
652
|
+
|