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.
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: ASCII-8BIT
3
+
4
+ require_relative 'options'
5
+
6
+ module DHCP
7
+
8
+ ## Class representing a DHCP packet (a request or a response)
9
+ ## for creating said packets, or for parsing them from a UDP
10
+ ## DHCP packet data payload.
11
+ class Packet
12
+ def initialize(opt={})
13
+ data = nil
14
+ if opt.is_a?(String)
15
+ data = opt
16
+ opt = {}
17
+ end
18
+ ## 1: Operation (BOOTREQUEST=1/BOOTREPLY=2)
19
+ @op = opt[:op]
20
+ raise "Invalid/unsupported operation type #{@op}" unless @op.nil? || @op == BOOTREQUEST || @op == BOOTREPLY
21
+ @htype_name = :htype_10mb_ethernet ## Only supported type currently...
22
+ @htype = HTYPE[@htype_name][0] ## 1: Hardware address type
23
+ @hlen = HTYPE[@htype_name][1] ## 1: Hardware address length
24
+ @hops = 0 ## 1: Client sets to zero, relays may increment
25
+ @xid = opt[:xid] || 0 ## 4: Client picks random 32-bit XID (session ID of sorts)
26
+ @secs = opt[:secs] || 0 ## 4: Seconds elapsed since client started transaction
27
+ @flags = opt[:flats] || 0 ## 2: Leftmost bit is the 'BROADCAST' flag (if set) - Others are zero (reserved for future use)
28
+
29
+ ## 4: "Client IP" -- Only set by client if client state is BOUND/RENEW/REBINDING and client can respond to ARP requests
30
+ @ciaddr = IPAddress::IPv4.new(opt[:ciaddr] || '0.0.0.0').data
31
+
32
+ ## 4: "Your IP" -- Server assigns IP to client
33
+ @yiaddr = IPAddress::IPv4.new(opt[:yiaddr] || '0.0.0.0').data
34
+
35
+ ## 4: "Server IP" -- IP of server to use in NEXT step of client bootstrap process
36
+ @siaddr = IPAddress::IPv4.new(opt[:siaddr] || '0.0.0.0').data
37
+
38
+ ## 4: "Gateway IP" -- Relay agent will set this to itself and modify replies
39
+ @giaddr = IPAddress::IPv4.new(opt[:giaddr] || '0.0.0.0').data
40
+
41
+ ## 16: Client hardware address (see htype and hlen)
42
+ @chaddr = (opt[:chaddr] || ('00' * @hlen)).gsub(%r{[ :._-]},'').downcase
43
+ raise 'Invalid client hardware address.' unless @chaddr.size == @hlen*2 && %r{\A[a-f0-9]{2}+\Z}.match(@chaddr)
44
+ @chaddr = @chaddr.scan(%r{..}m).map{|b| b.to_i(16).chr}.join
45
+
46
+ ## 64: Server host name (optional) as C-style null/zero terminated string (may instead contain options)
47
+ ## If provided by caller, do NOT include the C-style null/zero termination character.
48
+ @sname = opt[:sname] || ''
49
+ raise 'Invalid server host name string.' unless @sname.size < 64
50
+
51
+ ## 128: Boot file name (optional) as C-style null/zero terminated string (may instead contain options)
52
+ ## If provided by caller, do NOT include the C-style null/zero termination character.
53
+ @file = opt[:file] || ''
54
+ raise 'Invalid boot file name string.' unless @sname.size < 128
55
+
56
+ ## variable: Options - Up to 312 bytes in a 576-byte DHCP message - First four bytes are MAGIC
57
+ @options = '' ## Preserve any parsed packet's original binary option data - NOT set for non-parsed generated packets
58
+ @optlist = []
59
+
60
+ @type = nil
61
+ @type_name = 'UNKNOWN'
62
+ if opt[:type]
63
+ include_opt(DHCP.make_opt_name(:dhcp_message_type, opt[:type].is_a?(String) ? DHCP::MSG_STR_TO_TYPE[opt[:type]] : opt[:type]))
64
+ end
65
+
66
+ ## Default to BOOTREQUEST when generating a blank (invalid) packet:
67
+ @op = BOOTREQUEST if @op.nil?
68
+
69
+ ## If a packet was provided, parse it:
70
+ _parse(data) unless data.nil?
71
+ end
72
+ attr_reader :op, :htype_name, :htype, :hlen, :hops, :xid, :secs, :flags, :type, :type_name, :options, :optlist
73
+ attr_accessor :secs, :xid
74
+
75
+ ## Both #clone and #dup will call this:
76
+ def initialize_copy(orig)
77
+ self.ciaddr = orig.ciaddr
78
+ self.yiaddr = orig.yiaddr
79
+ self.siaddr = orig.siaddr
80
+ self.giaddr = orig.giaddr
81
+ @chaddr = orig.raw_chaddr.dup
82
+ @file = orig.file.dup
83
+ @sname = orig.sname.dup
84
+ @options = orig.options.dup
85
+ @optlist = []
86
+ orig.optlist.each do |opt|
87
+ @optlist << opt.dup
88
+ end
89
+ end
90
+
91
+ ## It is recommended that when creating a DHCP packet from scratch, use
92
+ ## include_opt(opt) instead so that the "end" option will be correctly
93
+ ## added or moved to the end. append_opt(opt) will not automatically
94
+ ## add an "end" nor will it move an existing "end" option, possibly
95
+ ## resulting in an invalid DHCP packet if not used carefully.
96
+ def append_opt(opt)
97
+ if opt.name == :dhcp_message_type
98
+ unless @type.nil?
99
+ raise "DHCP message type ALREADY SET in packet"
100
+ end
101
+ set_type(opt)
102
+ end
103
+ @optlist << opt
104
+ end
105
+
106
+ def sname
107
+ ## If the option overload is value 2 or 3, look for a :tftp_server_name option:
108
+ opt = get_option(:option_overload)
109
+ return @sname if opt.nil? || opt.get == 1
110
+ opt = get_option(:tftp_server_name)
111
+ return opt.nil? ? '' : opt.get
112
+ end
113
+
114
+ def sname=(val)
115
+ @sname=val
116
+ end
117
+
118
+ def file
119
+ ## If the option overload is value 1 or 3, look for a :bootfile_name option:
120
+ opt = get_option(:option_overload)
121
+ return @file if opt.nil? || opt.get == 2
122
+ opt = get_option(:bootfile_name)
123
+ return opt.nil? ? '' : opt.get
124
+ end
125
+
126
+ ## This is the best way to add an option to a DHCP packet:
127
+ def include_opt(opt)
128
+ list = @optlist
129
+ @options = ''
130
+ @optlist = []
131
+ list.each do |o|
132
+ ## This implementation currently doesn't support duplicate options yet:
133
+ raise "Duplicate option in packet." if o.name == opt.name
134
+ ## Skip/ignore the end option:
135
+ @optlist << o unless o.name == :end
136
+ end
137
+ append_opt(opt)
138
+ @optlist << Opt.new(255, :end)
139
+ end
140
+
141
+ def _find_htype(htype)
142
+ HTYPE.each do |name, htype|
143
+ if htype[0] == @htype
144
+ return name
145
+ end
146
+ end
147
+ return nil
148
+ end
149
+
150
+ def _parse(msg)
151
+ raise "Packet is too short (#{msg.size} < 241)" if (msg.size < 241)
152
+ @op = msg[0,1].ord
153
+ raise 'Invalid OP (expected BOOTREQUEST or BOOTREPLY)' if @op != BOOTREQUEST && @op != BOOTREPLY
154
+ self.htype = msg[1,1].ord ## This will do sanity checking and raise an exception on unsupported HTYPE
155
+ raise "Invalid hardware address length #{msg[2,1].ord} (expected #{@hlen})" if msg[2,1].ord != @hlen
156
+ @hops = msg[3,1].ord
157
+ @xid = msg[4,4].unpack('N')[0]
158
+ @secs = msg[8,2].unpack('n')[0]
159
+ @flags = msg[10,2].unpack('n')[0]
160
+ @ciaddr = msg[12,4]
161
+ @yiaddr = msg[16,4]
162
+ @siaddr = msg[20,4]
163
+ @giaddr = msg[24,4]
164
+ @chaddr = msg[28,16]
165
+ @sname = msg[44,64]
166
+ @file = msg[108,128]
167
+ magic = msg[236,4]
168
+ 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
169
+ @options = msg[240,msg.size-240]
170
+ @optlist = []
171
+ parse_opts(@options)
172
+ opt = get_option(:option_overload)
173
+ unless opt.nil?
174
+ ## RFC 2131: If "option overload" present, parse FILE field first, then SNAME (depending on overload value)
175
+ parse_opts(@file) if opt.get == 1 || opt.get == 3
176
+ parse_opts(@sname) if opt.get == 2 || opt.get == 3
177
+ raise "Invalid option overload value" if opt.val > 1 || opt.val > 3
178
+ end
179
+ opt = get_option(:dhcp_message_type)
180
+ raise "Not a valid DHCP packet (may be BOOTP): Missing DHCP MESSAGE TYPE" if opt.nil?
181
+ set_type(opt)
182
+ self
183
+ end
184
+
185
+ def set_type(opt)
186
+ @type = opt.get
187
+ if DHCP::MSG_TYPE_TO_OP.key?(@type)
188
+ @type_name = DHCP::MSG_TYPE_TO_STR[@type]
189
+ @op = DHCP::MSG_TYPE_TO_OP[@type] if @op.nil?
190
+ raise "Invalid OP #{@op} for #{@type_name}" unless @op == DHCP::MSG_TYPE_TO_OP[@type]
191
+ else
192
+ raise "Invalid or unsupported DHCP MESSAGE TYPE"
193
+ end
194
+ end
195
+
196
+ ## Look through a packet's options for the option in question:
197
+ def get_option(opt)
198
+ @optlist.each do |o|
199
+ return o if (opt.is_a?(Symbol) && o.name == opt) || (opt.is_a?(Fixnum) && o.opt == opt)
200
+ end
201
+ nil
202
+ end
203
+
204
+ def parse_opts(opts)
205
+ msg = opts.dup
206
+ while msg.size > 0
207
+ opt = msg[0,1].ord
208
+ if opt == 0
209
+ ## Don't add padding options to our list...
210
+ msg[0,1] = ''
211
+ elsif opt == 255
212
+ ## Options end... Assume all the rest is padding (if any)
213
+ @optlist << Opt.new(255, :end)
214
+ msg = ''
215
+ else
216
+ ## TODO: If an option value can't fit within a single option,
217
+ ## it may span several and the values should be merged. We
218
+ ## don't support this yet for parsing.
219
+ raise "Options end too soon" if msg.size == 1
220
+ len = msg[1,1].ord
221
+ raise "Options end too abruptly (expected #{len} more bytes, but found only #{msg.size - 2})" if msg.size < len + 2
222
+ val = msg[2,len]
223
+ msg[0,len+2] = ''
224
+ o = get_option(opt)
225
+ if o.nil?
226
+ o = DHCP::make_opt(opt)
227
+ if o.nil?
228
+ puts "WARNING: Ignoring unsupported option #{opt} (#{len} bytes)"
229
+ else
230
+ o.data = val unless len == 0
231
+ @optlist << o
232
+ end
233
+ else
234
+ ## See above TODO note...
235
+ puts "WARNING: Duplicate option #{opt} (#{o.name}) of #{len} bytes skipped/ignored"
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ def to_packet
242
+ packet =
243
+ @op.chr + @htype.chr + @hlen.chr + @hops.chr +
244
+ [@xid, @secs, @flags].pack('Nnn') +
245
+ @ciaddr + @yiaddr + @siaddr + @giaddr +
246
+ @chaddr + (0.chr * (16-@chaddr.size)) +
247
+ @sname + (0.chr * (64-@sname.size)) +
248
+ @file + (0.chr * (128-@file.size)) +
249
+ MAGIC +
250
+ @optlist.map{|x| x.to_opt}.join
251
+ packet + (packet.size < 300 ? 0.chr * (300 - packet.size) : '') ## Pad to minimum of 300 bytes - Minimum BOOTP/DHCP packet size (RFC 951) - Some devices will drop packets smaller than this.
252
+ end
253
+
254
+ def to_s
255
+ str = "op=#{@op} "
256
+ case @op
257
+ when BOOTREQUEST
258
+ str += '(BOOTREQUEST)'
259
+ when BOOTREPLY
260
+ str += '(BOOTREPLY)'
261
+ else
262
+ str += '(UNKNOWN)'
263
+ end
264
+ str += "\n"
265
+
266
+ str += "htype=#{@htype} "
267
+ found = false
268
+ HTYPE.each do |name, htype|
269
+ if htype[0] == @htype
270
+ found = true
271
+ str += name.to_s.upcase + "\n" + 'hlen=' + htype[1].to_s + "\n"
272
+ str += "*** INVALID HLEN #{@hlen} != #{htype[1]} ***\n" if @hlen != htype[1]
273
+ break
274
+ end
275
+ end
276
+ str += "UNKNOWN\nhlen=" + @hlen.to_s + "\n" unless found
277
+ str += "hops=#{@hops}\n"
278
+ str += "xid=#{@xid} (0x" + [@xid].pack('N').each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join + ")\n"
279
+ str += "secs=#{@secs}\n"
280
+ str += "flags=#{@flags} (" + (broadcast? ? 'BROADCAST' : 'NON-BROADCAST') + ")\n"
281
+ str += 'ciaddr=' + ciaddr + "\n"
282
+ str += 'yiaddr=' + yiaddr + "\n"
283
+ str += 'siaddr=' + siaddr + "\n"
284
+ str += 'giaddr=' + giaddr + "\n"
285
+ str += 'chaddr=' + chaddr + "\n"
286
+ str += "sname='#{@sname.sub(/\x00.*$/,'')}' (#{@sname.sub(/\x00.*$/,'').size})\n"
287
+ str += "file='#{@file.sub(/\x00.*$/,'')}' (#{@file.sub(/\x00.*$/,'').size})\n"
288
+ str += 'MAGIC: (0x' + MAGIC.each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join + ")\n"
289
+ str += "OPTIONS(#{@optlist.size}) = [\n "
290
+ str += @optlist.map{|x| x.to_s}.join(",\n ") + "\n]\n"
291
+ str += "DHCP_PACKET_TYPE='#{@type_name}' (#{@type}) " unless @type.nil?
292
+ str
293
+ end
294
+
295
+ def htype=(htype)
296
+ @htype_name = _find_htype(htype)
297
+ raise "Invalid/unsupported hardware type #{htype}" if @htype_name.nil?
298
+ @hlen = HTYPE[@htype_name][1]
299
+ @htype = HTYPE[@htype_name][0]
300
+ end
301
+
302
+ ## Broadcast flag:
303
+ def broadcast?
304
+ @flags & 0x8000 != 0
305
+ end
306
+ def broadcast!
307
+ @flags |= 0x8000
308
+ end
309
+
310
+ ## Hardware address (ethernet MAC style):
311
+ def chaddr
312
+ @chaddr[0,@hlen].each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join(':')
313
+ end
314
+ def raw_chaddr
315
+ @chaddr
316
+ end
317
+ def chaddr=(addr)
318
+ raise "Invalid hardware address" if addr.size - @hlen + 1 != @hlen * 2 || !/^(?:[a-fA-F0-9]{2}[ \.:_\-])*[a-fA-F0-9]{2}$/.match(addr)
319
+ @chaddr = addr.split(/[ .:_-]/).map{|b| b.to_i(16).chr}.join
320
+ end
321
+
322
+ ## IP accessors:
323
+ def ciaddr
324
+ IPAddress::IPv4::parse_data(@ciaddr).to_s
325
+ end
326
+ def ciaddr=(ip)
327
+ @ciaddr = IPAddress::IPv4.new(ip).data
328
+ end
329
+
330
+ def yiaddr
331
+ IPAddress::IPv4::parse_data(@yiaddr).to_s
332
+ end
333
+ def yiaddr=(ip)
334
+ @yiaddr = IPAddress::IPv4.new(ip).data
335
+ end
336
+
337
+ def siaddr
338
+ IPAddress::IPv4::parse_data(@siaddr).to_s
339
+ end
340
+ def siaddr=(ip)
341
+ @siaddr = IPAddress::IPv4.new(ip).data
342
+ end
343
+
344
+ def giaddr
345
+ IPAddress::IPv4::parse_data(@giaddr).to_s
346
+ end
347
+ def giaddr=(ip)
348
+ @giaddr = IPAddress::IPv4.new(ip).data
349
+ end
350
+ end
351
+
352
+ end
353
+
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: ASCII-8BIT
3
+
4
+ require_relative 'packet'
5
+
6
+ module DHCP # :nodoc:
7
+ class Server
8
+ def initialize(opt={})
9
+ @socket = nil ## UDP server socket
10
+ @interval = opt[:interval] || 0.5 ## Sleep interval
11
+ end
12
+ attr_reader :socket
13
+
14
+ ## Main server event loop (non-blocking):
15
+ def run_once
16
+ end
17
+
18
+ ## Main server event loop (blocking):
19
+ def run
20
+ loop do
21
+ resut = run_once
22
+ sleep @interval if !result ## Sleep if no data was received and no errors occured
23
+ end
24
+ end
25
+
26
+ ## Hand off raw UDP packet data here for parsing and dispatch:
27
+ def dispatch_packet(data, source_ip, source_port)
28
+ now = Time.now
29
+ Syslog.info("Packet (#{data.size} bytes) from [#{source_ip}]:#{source_port} received at #{now}")
30
+ if data.size < 300
31
+ Syslog.info("Ignoring small packet (less than BOOTP minimum size.")
32
+ return
33
+ end
34
+
35
+ packet = nil
36
+ begin
37
+ packet = DHCP::Packet.new(data)
38
+ rescue => e
39
+ show_packet(packet)
40
+ Syslog.err("Error parsing DHCP packet.")
41
+ return
42
+ end
43
+
44
+ relay = nil
45
+ if source_port == 67 ## DHCP relay via an intermediary
46
+ relay = true
47
+
48
+ ## Quick relay sanity-check on GIADDR:
49
+ if packet.giaddr == IPAddress.new('0.0.0.0')
50
+ Syslog.err("Packet from relay (port 67) has no GIADDR address set. Ignoring.")
51
+ return
52
+ end
53
+
54
+ unless relay_authorized?(source_ip, packet.giaddr)
55
+ Syslog.err("Ignoring DHCP packet from unauthorized relay [#{source_ip}].")
56
+ return
57
+ end
58
+ elsif source_port == 68 ## DHCP on directly attached subnet
59
+ relay = false
60
+
61
+ ## Quick relay sanity-check on GIADDR:
62
+ if packet.giaddr != IPAddress.new('0.0.0.0')
63
+ Syslog.err("Direct (non-relay) packet has set GIADDR to [#{packet.giaddr}] in violation of RFC. Ignoring.")
64
+ return
65
+ end
66
+ else
67
+ Syslog.err("Ignoring packet from UDP port other than 67 (relay) or 68 (direct)")
68
+ return
69
+ end
70
+
71
+ ## Ethernet hardware type sanity check:
72
+ if packet.htype != DHCP::HTYPE[:htype_10mb_ethernet][0] || packet.hlen != DHCP::HTYPE[:htype_10mb_ethernet][1]
73
+ Syslog.err("Request hardware type or length doesn't match ETHERNET type and length. Ignoring.")
74
+ return
75
+ end
76
+
77
+ if packet.op != DHCP::BOOTREQUEST
78
+ Syslog.err("Recived a non-BOOTREQUEST packet. Ignoring.")
79
+ return
80
+ end
81
+
82
+ ## Dispatch packet:
83
+ case packet.type
84
+ when DHCP::DHCPDISCOVER
85
+ handle_discover(packet, source_ip, relay)
86
+ when DHCP::DHCPREQUEST
87
+ handle_request(packet, source_ip, relay)
88
+ when DHCP::DHCPINFORM
89
+ handle_inform(packet, source_ip, relay)
90
+ when DHCP::DHCPRELEASE
91
+ handle_release(packet, source_ip, relay)
92
+ when DHCP::DHCPDECLINE
93
+ handle_decline(packet, source_ip, relay)
94
+ when DHCP::DHCPLEASEQUERY
95
+ handle_leasequery(packet, source_ip, relay)
96
+ when DHCP::DHCPOFFER, DHCP::DHCPACK, DHCP::DHCPNAK, DHCP::DHCPFORCERENEW, DHCP::DHCPLEASEUNASSIGNED, DHCP::DHCPLEASEACTIVE, DHCP::DHCPLEASEUNKNOWN
97
+ show_packet(packet)
98
+ Syslog.err("Packet type #{packet.type_name} in a BOOTREQUEST is invalid.")
99
+ else
100
+ show_packet(packet)
101
+ Syslog.err("Invalid, unknown, or unhandled DHCP packet type received.")
102
+ end
103
+ end
104
+
105
+ def relay_authorized?(source_ip, giaddr)
106
+ true
107
+ end
108
+
109
+ ## Handle DHCPDISCOVER packet:
110
+ def handle_discover(packet, source_ip, relay)
111
+ show_packet(packet)
112
+ Syslog.info("handle_discover")
113
+ end
114
+
115
+ ## Handle DHCPREQUEST packet:
116
+ def handle_request(packet, source_ip, relay)
117
+ show_packet(packet)
118
+ Syslog.info("handle_request")
119
+ end
120
+
121
+ ## Handle DHCPINFORM packet:
122
+ def handle_inform(packet, source_ip, relay)
123
+ show_packet(packet)
124
+ Syslog.info("handle_inform")
125
+ end
126
+
127
+ ## Handle DHCPDECLINE packet:
128
+ def handle_decline(packet, source_ip, relay)
129
+ show_packet(packet)
130
+ Syslog.info("handle_decline")
131
+ end
132
+
133
+ ## Handle DHCPLEASEQUERY packet:
134
+ def handle_leasequery(packet, source_ip, relay)
135
+ show_packet(packet)
136
+ Syslog.info("handle_leasequery")
137
+ end
138
+ end
139
+
140
+ end
141
+
data/lib/dhcp/version.rb CHANGED
@@ -1,8 +1,9 @@
1
- # -*- encoding: utf-8 -*-
1
+ #!/usr/bin/env ruby
2
+ # encoding: ASCII-8BIT
2
3
 
3
4
  module DHCP # :nodoc:
4
5
  module Version # :nodoc:
5
- STRING = '0.0.1'
6
+ STRING = '0.0.3'
6
7
  end
7
8
  end
8
9
 
data/lib/dhcp.rb CHANGED
@@ -1 +1,4 @@
1
- require 'dhcp/dhcp'
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative 'dhcp/client'
4
+ require_relative 'dhcp/server'
metadata CHANGED
@@ -1,69 +1,69 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
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
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
10
5
  platform: ruby
11
- authors:
6
+ authors:
12
7
  - Aaron D. Gifford
13
8
  autorequire:
14
9
  bindir: bin
15
10
  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)
11
+ date: 2014-12-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ipaddress
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: A pure-ruby library for parsing and creating IPv4 DHCP packets (requests
28
+ or responses)
22
29
  email:
23
30
  executables: []
24
-
25
31
  extensions: []
26
-
27
32
  extra_rdoc_files: []
28
-
29
- files:
30
- - LICENSE
33
+ files:
34
+ - LICENSE.txt
35
+ - README.rdoc
31
36
  - Rakefile
32
- - README
37
+ - dhcp.rb.bak
33
38
  - lib/dhcp.rb
34
- - lib/dhcp/version.rb
39
+ - lib/dhcp/client.rb
35
40
  - lib/dhcp/dhcp.rb
36
- has_rdoc: true
41
+ - lib/dhcp/options.rb
42
+ - lib/dhcp/packet.rb
43
+ - lib/dhcp/server.rb
44
+ - lib/dhcp/version.rb
37
45
  homepage: http://www.aarongifford.com/computers/dhcp/
38
- licenses: []
39
-
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
40
49
  post_install_message:
41
50
  rdoc_options: []
42
-
43
- require_paths:
51
+ require_paths:
44
52
  - 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"
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
61
63
  requirements: []
62
-
63
64
  rubyforge_project: dhcp
64
- rubygems_version: 1.3.7
65
+ rubygems_version: 2.4.5
65
66
  signing_key:
66
- specification_version: 3
67
- summary: dhcp-0.0.1
67
+ specification_version: 4
68
+ summary: dhcp-0.0.3
68
69
  test_files: []
69
-