1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
|
=begin
**
** core.rb
** 02/OCT/2007
** ETD-Software
** - Daniel Martin Gomez <etd[-at-]nomejortu.com>
**
** Desc:
** This file provides a set of classes to work with the DHCP protocol. They
** provide low level access to all the fields and internals of the protocol.
**
** See the provided rdoc comments for further information.
**
** Version:
** v1.0 [02/October/2007]: first released
** v1.1 [31/October/2007]: file moved under /dhcp/ directory and renamed to
** core.rb. Now it only contains core classes to
** encapsulate DHCP messages
**
** License:
** Please see dhcp.rb or LICENSE.txt for copyright and licensing information.
**
=end
module DHCP
# -------------------------------------------------------------- dhcp messages
class Message
attr_accessor :op, :htype, :hlen, :hops
attr_accessor :xid
attr_accessor :secs, :flags
attr_accessor :ciaddr, :yiaddr, :siaddr, :giaddr, :chaddr
attr_accessor :options
alias == eql?
def Message.from_udp_payload(data)
values = data.unpack('C4Nn2N4C16C192NC*')
params = {
:op => values.shift,
:htype => values.shift,
:hlen => values.shift,
:hops => values.shift,
:xid => values.shift,
:secs => values.shift,
:flags => values.shift,
:ciaddr => values.shift,
:yiaddr => values.shift,
:siaddr => values.shift,
:giaddr => values.shift,
:chaddr => values.slice!(0..15)
}
# sname and file
not_used = values.slice!(0..191)
return nil unless ($DHCP_MAGIC == values.shift)
#default message class
msg_class = Message
#default option class
opt_class = Option
params[:options] = []
next_opt = values.shift
while(next_opt != $DHCP_END)
p = {
:type => next_opt,
:len => values.shift
}
p[:payload] = values.slice!(0..p[:len]-1)
# check what is the type of dhcp option
opt_class = $DHCP_MSG_OPTIONS[p[:type]]
if(opt_class.nil?)
puts '-------------------- please further investigate!!'
puts p[:type]
puts '-------------------- /'
opt_class == Option
end
if (opt_class == MessageTypeOption)
msg_class = $DHCP_MSG_CLASSES[p[:payload].first]
end
params[:options] << opt_class.new(p)
next_opt = values.shift
end
if(msg_class.nil?)
puts '-------------------- please further investigate!!'
p params[:options]
puts '-------------------- /'
opt_class == Option
end
msg_class.new(params)
end
def initialize(params = {})
# message operation and options. We need at least an operation and a
# MessageTypeOption to create a DHCP message!!
if (([:op, :options] & params.keys).size != 2)
raise ArgumentError('you need to specify at least values for :op and :options')
end
self.op = params[:op]
self.options = params[:options]
found = false
self.options.each do |opt|
next unless opt.class == MessageTypeOption
found = true
end
raise ArgumentError(':options must include a MessageTypeOption') unless found
#hardware type and length of the hardware address
self.htype = params.fetch(:htype, $DHCP_HTYPE_ETHERNET)
self.hlen = params.fetch(:hlen, $DHCP_HLEN_ETHERNET)
# client sets to zero. relay agents may modify
self.hops = params.fetch(:hops, 0x00)
# initialize a random transaction ID
self.xid = params.fetch(:xid, rand(2**32))
# seconds elapsed, flags
self.secs = params.fetch(:secs, 0x0000)
self.flags = params.fetch(:flags, 0x0000)
# client, you, next server and relay agent addresses
self.ciaddr = params.fetch(:ciaddr, 0x00000000)
self.yiaddr = params.fetch(:yiaddr, 0x00000000)
self.siaddr = params.fetch(:siaddr, 0x00000000)
self.giaddr = params.fetch(:giaddr, 0x00000000)
if (params.key?(:chaddr))
self.chaddr = params[:chaddr]
raise 'chaddr field should be of 16 bytes' unless self.chaddr.size == 16
else
mac = `/sbin/ifconfig | grep HWaddr | cut -c39- | head -1`.chomp.strip.gsub(/:/,'')
self.chaddr = [mac].pack('H*').unpack('CCCCCC')
self.chaddr += [0x00]*(16-self.chaddr.size)
end
end
def pack()
out = [
self.op, self.htype, self.hlen, self.hops,
self.xid,
self.secs, self.flags,
self.ciaddr,
self.yiaddr,
self.siaddr,
self.giaddr
].pack('C4Nn2N4')
if self.chaddr.size >= 16
out << self.chaddr.pack('C16')
else
out << (self.chaddr + [0x00]*(16-self.chaddr.size)).pack('C16')
end
# sname and file
out << ([0x00]*192).pack('C192')
out << [$DHCP_MAGIC].pack('N')
self.options.each do |option|
out << option.pack
end
out << [$DHCP_END].pack('C')
# add padding up to 300 bytes
if out.size < 300
out << ([$DHCP_PAD]*(300-out.size)).pack('C*')
end
return out
end
def eql?(obj)
# objects must be of the same class
return false unless (self.class == obj.class)
vars1 = self.instance_variables
# first make sure that the :options var is equal
opt1 = self.instance_variable_get('@options')
opt2 = obj.instance_variable_get('@options')
return false unless opt1.eql?(opt2)
vars1.delete('@options')
# check all the other instance vairables
vars1.each do |var|
return false unless (self.instance_variable_get(var) == obj.instance_variable_get(var))
end
return true
end
def to_s
out = "DHCP Message\r\n"
out << "\tFIELDS:\r\n"
out << "\t\tTransaction ID = #{self.xid}\r\n"
out << "\t\tClient IP address = #{[self.ciaddr].pack('N').unpack('C4').join('.')}\r\n"
out << "\t\tYour IP address = #{[self.yiaddr].pack('N').unpack('C4').join('.')}\r\n"
out << "\t\tNext server IP address = #{[self.siaddr].pack('N').unpack('C4').join('.')}\r\n"
out << "\t\tRelay agent IP address = #{[self.giaddr].pack('N').unpack('C4').join('.')}\r\n"
out << "\t\tHardware address = #{self.chaddr.slice(0..(self.hlen-1)).collect do |b| b.to_s(16).upcase.rjust(2,'0') end.join(':')}\r\n"
out << "\tOPT:\r\n"
self.options.each do |opt|
out << "\t\t #{opt.to_s}\r\n"
end
return out
end
end
# Client broadcast to locate available servers.
class Discover < Message
def initialize(params={})
params[:op] = $DHCP_OP_REQUEST
# if an :options field is provided, we use it, otherwise, a default is set
params[:options] = params.fetch(:options, [MessageTypeOption.new, ParameterRequestListOption.new])
super(params)
end
end
# Server to client in response to DHCPDISCOVER with offer of configuration
# parameters.
#
# By default an ACK message will contain a Server Identifier (0.0.0.0) and
# a Domain Name ('nomejortu.com') option.
class Offer < Message
def initialize(params={})
params[:op] = $DHCP_OP_REPLY
params[:options] = params.fetch(:options, [
MessageTypeOption.new({:payload=>$DHCP_MSG_OFFER}),
ServerIdentifierOption.new,
DomainNameOption.new
])
super(params)
end
end
# Client message to servers either (a) requesting offered parameters from one
# server and implicitly declining offers from all others, (b) confirming
# correctness of previously allocated address after, e.g., system reboot, or
# (c) extending the lease on a particular network address.
class Request < Message
def initialize(params={})
params[:op] = $DHCP_OP_REQUEST
params[:options] = params.fetch(:options, [MessageTypeOption.new({:payload=>$DHCP_MSG_REQUEST}), ParameterRequestListOption.new])
super(params)
end
end
#
# DHCPDECLINE - Client to server indicating network address is already
# in use.
# Server to client with configuration parameters, including committed network
# address.
#
# By default an ACK message will contain a Server Identifier (0.0.0.0) and
# a Domain Name ('nomejortu.com') option.
class ACK < Message
def initialize(params={})
params[:op] = $DHCP_OP_REPLY
params[:options] = params.fetch(:options, [
MessageTypeOption.new({:payload=>$DHCP_MSG_ACK}),
ServerIdentifierOption.new,
DomainNameOption.new
])
super(params)
end
end
# DHCPNAK - Server to client indicating client's notion of network
# address is incorrect (e.g., client has moved to new
# subnet) or client's lease as expired
# Client to server relinquishing network address and cancelling remaining
# lease.
#
# By default an ACK message will contain a Server Identifier (0.0.0.0)
class Release < Message
def initialize(params={})
params[:op] = $DHCP_OP_REQUEST
params[:options] = params.fetch(:options, [
MessageTypeOption.new({:payload=>$DHCP_MSG_RELEASE}),
ServerIdentifierOption.new
])
super(params)
end
end
# Client to server, asking only for local configuration parameters; client
# already has externally configured network address.
class Inform < Message
def initialize(params={})
params[:op] = $DHCP_OP_REQUEST
params[:options] = params.fetch(:options, [MessageTypeOption.new({:payload=>$DHCP_MSG_INFORM}), ParameterRequestListOption.new])
super(params)
end
end
# ------------------------------------ map from values of fields to class names
$DHCP_MSG_CLASSES = {
$DHCP_MSG_DISCOVER => Discover,
$DHCP_MSG_OFFER => Offer,
$DHCP_MSG_REQUEST => Request,
# $DHCP_MSG_DECLINE= 0x04
$DHCP_MSG_ACK => ACK,
# $DHCP_MSG_NACK= 0x06
$DHCP_MSG_RELEASE => Release,
$DHCP_MSG_INFORM => Inform
}
end
|