summaryrefslogtreecommitdiff
path: root/lib/dhcp/core.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dhcp/core.rb')
-rw-r--r--lib/dhcp/core.rb322
1 files changed, 322 insertions, 0 deletions
diff --git a/lib/dhcp/core.rb b/lib/dhcp/core.rb
new file mode 100644
index 0000000..5ed844c
--- /dev/null
+++ b/lib/dhcp/core.rb
@@ -0,0 +1,322 @@
+=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')
+
+ out << self.chaddr.pack('C*')
+
+
+ # 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 \ No newline at end of file