dhcp 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
-