dhcp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/LICENSE +22 -0
  2. data/README +22 -0
  3. data/Rakefile +47 -0
  4. data/lib/dhcp/dhcp.rb +961 -0
  5. data/lib/dhcp/version.rb +8 -0
  6. data/lib/dhcp.rb +1 -0
  7. metadata +69 -0
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010,2011 InfoWest, Inc. and Aaron D. Gifford
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,22 @@
1
+
2
+ == DHCP Ruby Library
3
+
4
+ DHCP is a pure Ruby library for parsing and creating IPv4 DHCP packets,
5
+ requests or responses. No socket operations are included in the library.
6
+ Just packet parsing and binary packet creating.
7
+
8
+ *WARNING* A working release is not yet available. This is under development!
9
+
10
+ === REQUIREMENTS
11
+ Using the DHCP library depends on the following other gems:
12
+
13
+ * ipaddress gem
14
+
15
+ === AUTHORS and CONTRIBUTORS
16
+
17
+ * Aaron D. Gifford - http://www.aarongifford.com/
18
+
19
+ === LICENSE
20
+
21
+ Released under the MIT license. See the file 'LICENSE' for copyright and license detail.
22
+
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'pathname'
4
+ require 'rubygems'
5
+ require 'rake/gempackagetask'
6
+ require 'rake/rdoctask'
7
+
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
9
+ require 'dhcp/version'
10
+
11
+ gemspec = Gem::Specification.new do |s|
12
+ s.name = 'dhcp'
13
+ s.version = DHCP::Version::STRING
14
+ s.authors = [ 'Aaron D. Gifford' ]
15
+ s.homepage = 'http://www.aarongifford.com/computers/dhcp/'
16
+ s.summary = "dhcp-#{DHCP::Version::STRING}"
17
+ s.description = 'A pure-ruby library for parsing and creating IPv4 DHCP packets (requests or responses)'
18
+
19
+ s.rubygems_version = DHCP::Version::STRING
20
+ s.rubyforge_project = 'dhcp'
21
+
22
+ s.files = Pathname.glob([
23
+ '*',
24
+ 'lib/*',
25
+ 'lib/*/*',
26
+ 'bin/*'
27
+ ]).select{|x| File.file?(x)}.map{|x| x.to_s}
28
+ s.require_paths = [ 'lib' ]
29
+ s.executables = Pathname.glob(['bin/*']).select{|x| File.file?(x)}.map{|x| x.to_s}
30
+ end
31
+
32
+ Rake::GemPackageTask.new(gemspec) do |pkg|
33
+ pkg.need_zip = true
34
+ pkg.need_tar = true
35
+ end
36
+
37
+ Rake::RDocTask.new do |rdoc|
38
+ rdoc.main = 'README'
39
+ rdoc.rdoc_dir = 'doc'
40
+ rdoc.rdoc_files.include('README', 'lib/**/*.rb')
41
+ end
42
+
43
+ task :default => [
44
+ 'pkg/dhcp-' + DHCP::Version::STRING + '.gem',
45
+ :rdoc
46
+ ]
47
+
data/lib/dhcp/dhcp.rb ADDED
@@ -0,0 +1,961 @@
1
+ # 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
+
47
+ ## http://github.org/bluemonk/ipaddress - A very nice IP address utility gem
48
+ require 'ipaddress'
49
+
50
+ 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
+
853
+ ## BOOTP TYPES:
854
+ BOOTREQUEST = 1
855
+ BOOTREPLY = 2
856
+
857
+ ## HARDWARE TYPES: [htype code, hlen length]
858
+ HTYPE = {
859
+ :htype_10mb_ethernet => [ 1, 6 ]
860
+ }
861
+
862
+ ## DHCP MESSAGE TYPES:
863
+ DHCPDISCOVER = 1
864
+ DHCPOFFER = 2
865
+ DHCPREQUEST = 3
866
+ DHCPDECLINE = 4
867
+ DHCPACK = 5
868
+ DHCPNAK = 6
869
+ DHCPRELEASE = 7
870
+ DHCPINFORM = 8
871
+ DHCPFORCERENEW = 9 ## RFC 3203
872
+ ## LEASEQUERY extensions:
873
+ DHCPLEASEQUERY = 10
874
+ DHCPLEASEUNASSIGNED = 11
875
+ DHCPLEASEUNKNOWN = 12
876
+ DHCPLEASEACTIVE = 13
877
+
878
+ ## OPTIONS:
879
+ MAGIC = [99, 130, 83, 99].pack('C4')
880
+
881
+ ## Options 0-18 and 254 are defined in RFC 1497 (BOOTP)
882
+ ## TODO: Add in as yet unhandled options
883
+ OPTIONS = {
884
+ :pad => [ 0, Opt ],
885
+ :subnet_mask => [ 1, OptIP ],
886
+ :time_offset => [ 2, OptSInt32 ], ## Offset from GMT (signed 32-bit integer seconds)
887
+ :routers => [ 3, OptIPList ], ## Default gateway(s)
888
+ :time_servers => [ 4, OptIPList ],
889
+ :name_servers => [ 5, OptIPList ], ## IEN-116 name servers
890
+ :dns_servers => [ 6, OptIPList ], ## DNS server(s) (RFC-1034/1025)
891
+ :log_servers => [ 7, OptIPList ], ## Log server(s) (MIT-LCS UDP log servers)
892
+ :cookie_servers => [ 8, OptIPList ], ## Cookie/Quote-of-the-day (RFC 865) server(s)
893
+ :lpr_servers => [ 9, OptIPList ], ## LPR server(s) (RFC 1179)
894
+ :impress_servers => [ 10, OptIPList ], ## Impress server(s) (in pref. order)
895
+ :rlp_servers => [ 11, OptIPList ], ## RLP server(s) (RFC 887)
896
+ :host_name => [ 12, OptHost ], ## May or may not be qualified with local domain name (RFC 1035)
897
+ :boot_file_size => [ 13, OptInt16 ], ## Boot file size (number of 512-byte blocks as unsigned 16-bit integer)
898
+ :merit_dump_file => [ 14, OptData ], ## File name client should dump core to
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 ]
947
+ }
948
+
949
+ def self.make_opt_name(name, data=nil)
950
+ raise "Unknown/unhandled option '#{name}'" unless OPTIONS.key?(name)
951
+ OPTIONS[name][1].new(OPTIONS[name][0], name, data)
952
+ end
953
+
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
+ end
961
+
@@ -0,0 +1,8 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module DHCP # :nodoc:
4
+ module Version # :nodoc:
5
+ STRING = '0.0.1'
6
+ end
7
+ end
8
+
data/lib/dhcp.rb ADDED
@@ -0,0 +1 @@
1
+ require 'dhcp/dhcp'
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dhcp
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Aaron D. Gifford
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-18 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: A pure-ruby library for parsing and creating IPv4 DHCP packets (requests or responses)
22
+ email:
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - LICENSE
31
+ - Rakefile
32
+ - README
33
+ - lib/dhcp.rb
34
+ - lib/dhcp/version.rb
35
+ - lib/dhcp/dhcp.rb
36
+ has_rdoc: true
37
+ homepage: http://www.aarongifford.com/computers/dhcp/
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ segments:
51
+ - 0
52
+ version: "0"
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ requirements: []
62
+
63
+ rubyforge_project: dhcp
64
+ rubygems_version: 1.3.7
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: dhcp-0.0.1
68
+ test_files: []
69
+