Class: DHCP::Packet
- Inherits:
-
Object
- Object
- DHCP::Packet
- Defined in:
- lib/dhcp/packet.rb
Overview
Class representing a DHCP packet (a request or a response) for creating said packets, or for parsing them from a UDP DHCP packet data payload.
Instance Attribute Summary collapse
-
#flags ⇒ Object
readonly
Returns the value of attribute flags.
-
#hlen ⇒ Object
readonly
Returns the value of attribute hlen.
-
#hops ⇒ Object
readonly
Returns the value of attribute hops.
-
#htype ⇒ Object
Returns the value of attribute htype.
-
#htype_name ⇒ Object
readonly
Returns the value of attribute htype_name.
-
#op ⇒ Object
readonly
Returns the value of attribute op.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#optlist ⇒ Object
readonly
Returns the value of attribute optlist.
-
#secs ⇒ Object
Returns the value of attribute secs.
-
#type ⇒ Object
readonly
Returns the value of attribute type.
-
#type_name ⇒ Object
readonly
Returns the value of attribute type_name.
-
#xid ⇒ Object
Returns the value of attribute xid.
Instance Method Summary collapse
- #_find_htype(htype) ⇒ Object
- #_parse(msg) ⇒ Object
-
#append_opt(opt) ⇒ Object
It is recommended that when creating a DHCP packet from scratch, use include_opt(opt) instead so that the “end” option will be correctly added or moved to the end.
- #broadcast! ⇒ Object
-
#broadcast? ⇒ Boolean
Broadcast flag:.
-
#chaddr ⇒ Object
Hardware address (ethernet MAC style):.
- #chaddr=(addr) ⇒ Object
-
#ciaddr ⇒ Object
IP accessors:.
- #ciaddr=(ip) ⇒ Object
- #file ⇒ Object
-
#get_option(opt) ⇒ Object
Look through a packet’s options for the option in question:.
- #giaddr ⇒ Object
- #giaddr=(ip) ⇒ Object
-
#include_opt(opt) ⇒ Object
This is the best way to add an option to a DHCP packet:.
-
#initialize(opt = {}) ⇒ Packet
constructor
A new instance of Packet.
-
#initialize_copy(orig) ⇒ Object
Both #clone and #dup will call this:.
- #parse_opts(opts) ⇒ Object
- #raw_chaddr ⇒ Object
- #set_type(opt) ⇒ Object
- #siaddr ⇒ Object
- #siaddr=(ip) ⇒ Object
- #sname ⇒ Object
- #sname=(val) ⇒ Object
- #to_packet ⇒ Object
- #to_s ⇒ Object
- #yiaddr ⇒ Object
- #yiaddr=(ip) ⇒ Object
Constructor Details
#initialize(opt = {}) ⇒ Packet
Returns a new instance of Packet.
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 |
# File 'lib/dhcp/packet.rb', line 12 def initialize(opt={}) data = nil if opt.is_a?(String) data = opt opt = {} end ## 1: Operation (BOOTREQUEST=1/BOOTREPLY=2) @op = opt[:op] raise "Invalid/unsupported operation type #{@op}" unless @op.nil? || @op == BOOTREQUEST || @op == BOOTREPLY @htype_name = :htype_10mb_ethernet ## Only supported type currently... @htype = HTYPE[@htype_name][0] ## 1: Hardware address type @hlen = HTYPE[@htype_name][1] ## 1: Hardware address length @hops = 0 ## 1: Client sets to zero, relays may increment @xid = opt[:xid] || 0 ## 4: Client picks random 32-bit XID (session ID of sorts) @secs = opt[:secs] || 0 ## 4: Seconds elapsed since client started transaction @flags = opt[:flats] || 0 ## 2: Leftmost bit is the 'BROADCAST' flag (if set) - Others are zero (reserved for future use) ## 4: "Client IP" -- Only set by client if client state is BOUND/RENEW/REBINDING and client can respond to ARP requests @ciaddr = IPAddress::IPv4.new(opt[:ciaddr] || '0.0.0.0').data ## 4: "Your IP" -- Server assigns IP to client @yiaddr = IPAddress::IPv4.new(opt[:yiaddr] || '0.0.0.0').data ## 4: "Server IP" -- IP of server to use in NEXT step of client bootstrap process @siaddr = IPAddress::IPv4.new(opt[:siaddr] || '0.0.0.0').data ## 4: "Gateway IP" -- Relay agent will set this to itself and modify replies @giaddr = IPAddress::IPv4.new(opt[:giaddr] || '0.0.0.0').data ## 16: Client hardware address (see htype and hlen) @chaddr = (opt[:chaddr] || ('00' * @hlen)).gsub(%r{[ :._-]},'').downcase raise 'Invalid client hardware address.' unless @chaddr.size == @hlen*2 && %r{\A[a-f0-9]{2}+\Z}.match(@chaddr) @chaddr = @chaddr.scan(%r{..}m).map{|b| b.to_i(16).chr}.join ## 64: Server host name (optional) as C-style null/zero terminated string (may instead contain options) ## If provided by caller, do NOT include the C-style null/zero termination character. @sname = opt[:sname] || '' raise 'Invalid server host name string.' unless @sname.size < 64 ## 128: Boot file name (optional) as C-style null/zero terminated string (may instead contain options) ## If provided by caller, do NOT include the C-style null/zero termination character. @file = opt[:file] || '' raise 'Invalid boot file name string.' unless @sname.size < 128 ## variable: Options - Up to 312 bytes in a 576-byte DHCP message - First four bytes are MAGIC @options = '' ## Preserve any parsed packet's original binary option data - NOT set for non-parsed generated packets @optlist = [] @type = nil @type_name = 'UNKNOWN' if opt[:type] include_opt(DHCP.make_opt_name(:dhcp_message_type, opt[:type].is_a?(String) ? DHCP::MSG_STR_TO_TYPE[opt[:type]] : opt[:type])) end ## Default to BOOTREQUEST when generating a blank (invalid) packet: @op = BOOTREQUEST if @op.nil? ## If a packet was provided, parse it: _parse(data) unless data.nil? end |
Instance Attribute Details
#flags ⇒ Object (readonly)
Returns the value of attribute flags.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def flags @flags end |
#hlen ⇒ Object (readonly)
Returns the value of attribute hlen.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def hlen @hlen end |
#hops ⇒ Object (readonly)
Returns the value of attribute hops.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def hops @hops end |
#htype ⇒ Object
Returns the value of attribute htype.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def htype @htype end |
#htype_name ⇒ Object (readonly)
Returns the value of attribute htype_name.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def htype_name @htype_name end |
#op ⇒ Object (readonly)
Returns the value of attribute op.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def op @op end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def @options end |
#optlist ⇒ Object (readonly)
Returns the value of attribute optlist.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def optlist @optlist end |
#secs ⇒ Object
Returns the value of attribute secs.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def secs @secs end |
#type ⇒ Object (readonly)
Returns the value of attribute type.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def type @type end |
#type_name ⇒ Object (readonly)
Returns the value of attribute type_name.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def type_name @type_name end |
#xid ⇒ Object
Returns the value of attribute xid.
72 73 74 |
# File 'lib/dhcp/packet.rb', line 72 def xid @xid end |
Instance Method Details
#_find_htype(htype) ⇒ Object
141 142 143 144 145 146 147 148 |
# File 'lib/dhcp/packet.rb', line 141 def _find_htype(htype) HTYPE.each do |name, htype| if htype[0] == @htype return name end end return nil end |
#_parse(msg) ⇒ Object
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 |
# File 'lib/dhcp/packet.rb', line 150 def _parse(msg) raise "Packet is too short (#{msg.size} < 241)" if (msg.size < 241) @op = msg[0,1].ord raise 'Invalid OP (expected BOOTREQUEST or BOOTREPLY)' if @op != BOOTREQUEST && @op != BOOTREPLY self.htype = msg[1,1].ord ## This will do sanity checking and raise an exception on unsupported HTYPE raise "Invalid hardware address length #{msg[2,1].ord} (expected #{@hlen})" if msg[2,1].ord != @hlen @hops = msg[3,1].ord @xid = msg[4,4].unpack('N')[0] @secs = msg[8,2].unpack('n')[0] @flags = msg[10,2].unpack('n')[0] @ciaddr = msg[12,4] @yiaddr = msg[16,4] @siaddr = msg[20,4] @giaddr = msg[24,4] @chaddr = msg[28,16] @sname = msg[44,64] @file = msg[108,128] magic = msg[236,4] 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 @options = msg[240,msg.size-240] @optlist = [] parse_opts(@options) opt = get_option(:option_overload) unless opt.nil? ## RFC 2131: If "option overload" present, parse FILE field first, then SNAME (depending on overload value) parse_opts(@file) if opt.get == 1 || opt.get == 3 parse_opts(@sname) if opt.get == 2 || opt.get == 3 raise "Invalid option overload value" if opt.val > 1 || opt.val > 3 end opt = get_option(:dhcp_message_type) raise "Not a valid DHCP packet (may be BOOTP): Missing DHCP MESSAGE TYPE" if opt.nil? set_type(opt) self end |
#append_opt(opt) ⇒ Object
It is recommended that when creating a DHCP packet from scratch, use include_opt(opt) instead so that the “end” option will be correctly added or moved to the end. append_opt(opt) will not automatically add an “end” nor will it move an existing “end” option, possibly resulting in an invalid DHCP packet if not used carefully.
96 97 98 99 100 101 102 103 104 |
# File 'lib/dhcp/packet.rb', line 96 def append_opt(opt) if opt.name == :dhcp_message_type unless @type.nil? raise "DHCP message type ALREADY SET in packet" end set_type(opt) end @optlist << opt end |
#broadcast! ⇒ Object
306 307 308 |
# File 'lib/dhcp/packet.rb', line 306 def broadcast! @flags |= 0x8000 end |
#broadcast? ⇒ Boolean
Broadcast flag:
303 304 305 |
# File 'lib/dhcp/packet.rb', line 303 def broadcast? @flags & 0x8000 != 0 end |
#chaddr ⇒ Object
Hardware address (ethernet MAC style):
311 312 313 |
# File 'lib/dhcp/packet.rb', line 311 def chaddr @chaddr[0,@hlen].each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join(':') end |
#chaddr=(addr) ⇒ Object
317 318 319 320 |
# File 'lib/dhcp/packet.rb', line 317 def chaddr=(addr) raise "Invalid hardware address" if addr.size - @hlen + 1 != @hlen * 2 || !/^(?:[a-fA-F0-9]{2}[ \.:_\-])*[a-fA-F0-9]{2}$/.match(addr) @chaddr = addr.split(/[ .:_-]/).map{|b| b.to_i(16).chr}.join end |
#ciaddr ⇒ Object
IP accessors:
323 324 325 |
# File 'lib/dhcp/packet.rb', line 323 def ciaddr IPAddress::IPv4::parse_data(@ciaddr).to_s end |
#ciaddr=(ip) ⇒ Object
326 327 328 |
# File 'lib/dhcp/packet.rb', line 326 def ciaddr=(ip) @ciaddr = IPAddress::IPv4.new(ip).data end |
#file ⇒ Object
118 119 120 121 122 123 124 |
# File 'lib/dhcp/packet.rb', line 118 def file ## If the option overload is value 1 or 3, look for a :bootfile_name option: opt = get_option(:option_overload) return @file if opt.nil? || opt.get == 2 opt = get_option(:bootfile_name) return opt.nil? ? '' : opt.get end |
#get_option(opt) ⇒ Object
Look through a packet’s options for the option in question:
197 198 199 200 201 202 |
# File 'lib/dhcp/packet.rb', line 197 def get_option(opt) @optlist.each do |o| return o if (opt.is_a?(Symbol) && o.name == opt) || (opt.is_a?(Fixnum) && o.opt == opt) end nil end |
#giaddr ⇒ Object
344 345 346 |
# File 'lib/dhcp/packet.rb', line 344 def giaddr IPAddress::IPv4::parse_data(@giaddr).to_s end |
#giaddr=(ip) ⇒ Object
347 348 349 |
# File 'lib/dhcp/packet.rb', line 347 def giaddr=(ip) @giaddr = IPAddress::IPv4.new(ip).data end |
#include_opt(opt) ⇒ Object
This is the best way to add an option to a DHCP packet:
127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/dhcp/packet.rb', line 127 def include_opt(opt) list = @optlist @options = '' @optlist = [] list.each do |o| ## This implementation currently doesn't support duplicate options yet: raise "Duplicate option in packet." if o.name == opt.name ## Skip/ignore the end option: @optlist << o unless o.name == :end end append_opt(opt) @optlist << Opt.new(255, :end) end |
#initialize_copy(orig) ⇒ Object
Both #clone and #dup will call this:
76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/dhcp/packet.rb', line 76 def initialize_copy(orig) self.ciaddr = orig.ciaddr self.yiaddr = orig.yiaddr self.siaddr = orig.siaddr self.giaddr = orig.giaddr @chaddr = orig.raw_chaddr.dup @file = orig.file.dup @sname = orig.sname.dup @options = orig..dup @optlist = [] orig.optlist.each do |opt| @optlist << opt.dup end end |
#parse_opts(opts) ⇒ Object
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 |
# File 'lib/dhcp/packet.rb', line 204 def parse_opts(opts) msg = opts.dup while msg.size > 0 opt = msg[0,1].ord if opt == 0 ## Don't add padding options to our list... msg[0,1] = '' elsif opt == 255 ## Options end... Assume all the rest is padding (if any) @optlist << Opt.new(255, :end) msg = '' else ## TODO: If an option value can't fit within a single option, ## it may span several and the values should be merged. We ## don't support this yet for parsing. raise "Options end too soon" if msg.size == 1 len = msg[1,1].ord raise "Options end too abruptly (expected #{len} more bytes, but found only #{msg.size - 2})" if msg.size < len + 2 val = msg[2,len] msg[0,len+2] = '' o = get_option(opt) if o.nil? o = DHCP::make_opt(opt) if o.nil? puts "WARNING: Ignoring unsupported option #{opt} (#{len} bytes)" else o.data = val unless len == 0 @optlist << o end else ## See above TODO note... puts "WARNING: Duplicate option #{opt} (#{o.name}) of #{len} bytes skipped/ignored" end end end end |
#raw_chaddr ⇒ Object
314 315 316 |
# File 'lib/dhcp/packet.rb', line 314 def raw_chaddr @chaddr end |
#set_type(opt) ⇒ Object
185 186 187 188 189 190 191 192 193 194 |
# File 'lib/dhcp/packet.rb', line 185 def set_type(opt) @type = opt.get if DHCP::MSG_TYPE_TO_OP.key?(@type) @type_name = DHCP::MSG_TYPE_TO_STR[@type] @op = DHCP::MSG_TYPE_TO_OP[@type] if @op.nil? raise "Invalid OP #{@op} for #{@type_name}" unless @op == DHCP::MSG_TYPE_TO_OP[@type] else raise "Invalid or unsupported DHCP MESSAGE TYPE" end end |
#siaddr ⇒ Object
337 338 339 |
# File 'lib/dhcp/packet.rb', line 337 def siaddr IPAddress::IPv4::parse_data(@siaddr).to_s end |
#siaddr=(ip) ⇒ Object
340 341 342 |
# File 'lib/dhcp/packet.rb', line 340 def siaddr=(ip) @siaddr = IPAddress::IPv4.new(ip).data end |
#sname ⇒ Object
106 107 108 109 110 111 112 |
# File 'lib/dhcp/packet.rb', line 106 def sname ## If the option overload is value 2 or 3, look for a :tftp_server_name option: opt = get_option(:option_overload) return @sname if opt.nil? || opt.get == 1 opt = get_option(:tftp_server_name) return opt.nil? ? '' : opt.get end |
#sname=(val) ⇒ Object
114 115 116 |
# File 'lib/dhcp/packet.rb', line 114 def sname=(val) @sname=val end |
#to_packet ⇒ Object
241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/dhcp/packet.rb', line 241 def to_packet packet = @op.chr + @htype.chr + @hlen.chr + @hops.chr + [@xid, @secs, @flags].pack('Nnn') + @ciaddr + @yiaddr + @siaddr + @giaddr + @chaddr + (0.chr * (16-@chaddr.size)) + @sname + (0.chr * (64-@sname.size)) + @file + (0.chr * (128-@file.size)) + MAGIC + @optlist.map{|x| x.to_opt}.join 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. end |
#to_s ⇒ Object
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 |
# File 'lib/dhcp/packet.rb', line 254 def to_s str = "op=#{@op} " case @op when BOOTREQUEST str += '(BOOTREQUEST)' when BOOTREPLY str += '(BOOTREPLY)' else str += '(UNKNOWN)' end str += "\n" str += "htype=#{@htype} " found = false HTYPE.each do |name, htype| if htype[0] == @htype found = true str += name.to_s.upcase + "\n" + 'hlen=' + htype[1].to_s + "\n" str += "*** INVALID HLEN #{@hlen} != #{htype[1]} ***\n" if @hlen != htype[1] break end end str += "UNKNOWN\nhlen=" + @hlen.to_s + "\n" unless found str += "hops=#{@hops}\n" str += "xid=#{@xid} (0x" + [@xid].pack('N').each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join + ")\n" str += "secs=#{@secs}\n" str += "flags=#{@flags} (" + (broadcast? ? 'BROADCAST' : 'NON-BROADCAST') + ")\n" str += 'ciaddr=' + ciaddr + "\n" str += 'yiaddr=' + yiaddr + "\n" str += 'siaddr=' + siaddr + "\n" str += 'giaddr=' + giaddr + "\n" str += 'chaddr=' + chaddr + "\n" str += "sname='#{@sname.sub(/\x00.*$/,'')}' (#{@sname.sub(/\x00.*$/,'').size})\n" str += "file='#{@file.sub(/\x00.*$/,'')}' (#{@file.sub(/\x00.*$/,'').size})\n" str += 'MAGIC: (0x' + MAGIC.each_byte.map{|b| ('0'+b.to_s(16).upcase)[-2,2]}.join + ")\n" str += "OPTIONS(#{@optlist.size}) = [\n " str += @optlist.map{|x| x.to_s}.join(",\n ") + "\n]\n" str += "DHCP_PACKET_TYPE='#{@type_name}' (#{@type}) " unless @type.nil? str end |
#yiaddr ⇒ Object
330 331 332 |
# File 'lib/dhcp/packet.rb', line 330 def yiaddr IPAddress::IPv4::parse_data(@yiaddr).to_s end |
#yiaddr=(ip) ⇒ Object
333 334 335 |
# File 'lib/dhcp/packet.rb', line 333 def yiaddr=(ip) @yiaddr = IPAddress::IPv4.new(ip).data end |