Class: PacketFu::PcapFile

Inherits:
Struct
  • Object
show all
Includes:
StructFu
Defined in:
lib/packetfu/pcap.rb

Overview

PcapFile is a complete libpcap file struct, made up of two elements, a PcapHeader and PcapPackets.

See wiki.wireshark.org/Development/LibpcapFileFormat

PcapFile also can behave as a singleton class, which is usually the better way to handle pcap files of really any size, since it doesn’t require storing packets before handing them off to a given block. This is really the way to go.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from StructFu

#clone, #set_endianness, #sz, #typecast

Methods inherited from Struct

#force_binary

Constructor Details

#initialize(args = {}) ⇒ PcapFile

Returns a new instance of PcapFile.



323
324
325
326
327
# File 'lib/packetfu/pcap.rb', line 323

def initialize(args={})
  init_fields(args)
  @filename = args.delete :filename
  super(args[:endian], args[:head], args[:body])
end

Instance Attribute Details

#bodyObject

Returns the value of attribute body

Returns:

  • (Object)

    the current value of body



238
239
240
# File 'lib/packetfu/pcap.rb', line 238

def body
  @body
end

#endianObject

Returns the value of attribute endian

Returns:

  • (Object)

    the current value of endian



238
239
240
# File 'lib/packetfu/pcap.rb', line 238

def endian
  @endian
end

#headObject

Returns the value of attribute head

Returns:

  • (Object)

    the current value of head



238
239
240
# File 'lib/packetfu/pcap.rb', line 238

def head
  @head
end

Class Method Details

.file_to_array(fname) ⇒ Object

Takes a filename, and an optional block. If a block is given, yield back the raw packet data from the given file. Otherwise, return an array of parsed packets.



245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/packetfu/pcap.rb', line 245

def read_packet_bytes(fname,&block)
  count = 0
  packets = [] unless block
  read(fname) do |packet|
    if block
      count += 1
      yield packet.data.to_s
    else
      packets << packet.data.to_s
    end
  end
  block ? count : packets
end

.read(fname, &block) ⇒ Object

Takes a given file name, and reads out the packets. If given a block, it will yield back a PcapPacket object per packet found.



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
# File 'lib/packetfu/pcap.rb', line 251

def read(fname,&block)
  file_header = PcapHeader.new
  pcap_packets = PcapPackets.new
  unless File.readable? fname
    raise ArgumentError, "Cannot read file `#{fname}'"
  end
  begin
  file_handle = File.open(fname, "rb")
  file_header.read file_handle.read(24)
  packet_count = 0
  pcap_packet = PcapPacket.new(:endian => file_header.endian)
  while pcap_packet.read file_handle.read(16) do
    len = pcap_packet.incl_len
    pcap_packet.data = StructFu::String.new.read(file_handle.read(len.to_i))
    packet_count += 1
    if pcap_packet.data.size < len.to_i
      warn "Packet ##{packet_count} is corrupted: expected #{len.to_i}, got #{pcap_packet.data.size}. Exiting."
      break
    end
    if block
      yield pcap_packet
    else
      pcap_packets << pcap_packet.clone
    end
  end
  ensure
    file_handle.close
  end
  block ? packet_count : pcap_packets
end

.read_packet_bytes(fname, &block) ⇒ Object

Takes a filename, and an optional block. If a block is given, yield back the raw packet data from the given file. Otherwise, return an array of parsed packets.



285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/packetfu/pcap.rb', line 285

def read_packet_bytes(fname,&block)
  count = 0
  packets = [] unless block
  read(fname) do |packet|
    if block
      count += 1
      yield packet.data.to_s
    else
      packets << packet.data.to_s
    end
  end
  block ? count : packets
end

.read_packets(fname, &block) ⇒ Object

Takes a filename, and an optional block. If a block is given, yield back parsed packets from the given file. Otherwise, return an array of parsed packets.

This is a brazillian times faster than the old methods of extracting packets from files.



307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/packetfu/pcap.rb', line 307

def read_packets(fname,&block)
  count = 0
  packets = [] unless block
  read_packet_bytes(fname) do |packet|
    if block
      count += 1
      yield Packet.parse(packet)
    else
      packets << Packet.parse(packet)
    end
  end
  block ? count : packets
end

Instance Method Details

#append(filename = 'out.pcap') ⇒ Object

Shorthand method for appending to a file. Can take either :file => ‘name.pcap’ or simply ‘name.pcap’



498
499
500
501
502
503
504
505
# File 'lib/packetfu/pcap.rb', line 498

def append(filename='out.pcap')
  if filename.kind_of?(Hash)
    f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
  else
    f = filename.to_s
  end
  self.to_file(:filename => f, :append => true)
end

#array_to_file(args = {}) ⇒ Object Also known as: a2f

Takes an array of packets (as generated by file_to_array), and writes them to a file. Valid arguments are:

:filename
:array      # Can either be an array of packet data, or a hash-value pair of timestamp => data.
:timestamp  # Sets an initial timestamp
:ts_inc     # Sets the increment between timestamps. Defaults to 1 second.
:append     # If true, then the packets are appended to the end of a file.


408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/packetfu/pcap.rb', line 408

def array_to_file(args={})
  if args.kind_of? Hash
    filename = args[:filename] || args[:file] || args[:f]
    arr = args[:array] || args[:arr] || args[:a]
    ts = args[:timestamp] || args[:ts] || Time.now.to_i
    ts_inc = args[:timestamp_increment] || args[:ts_inc] || 1
    append = !!args[:append]
  elsif args.kind_of? Array
    arr = args
    filename = append = nil
  else
    raise ArgumentError, "Unknown argument. Need either a Hash or Array."
  end
  unless arr.kind_of? Array
    raise ArgumentError, "Need an array to read packets from"
  end
  arr.each_with_index do |p,i|
    if p.kind_of? Hash # Binary timestamps are included
      this_ts = p.keys.first.dup
      this_incl_len = p.values.first.size
      this_orig_len = this_incl_len
      this_data = p.values.first
    else # it's an array
      this_ts = Timestamp.new(:endian => self[:endian], :sec => ts + (ts_inc * i)).to_s
      this_incl_len = p.to_s.size
      this_orig_len = this_incl_len
      this_data = p.to_s
    end
    this_pkt = PcapPacket.new({:endian => self[:endian],
                              :timestamp => this_ts,
                              :incl_len => this_incl_len,
                              :orig_len => this_orig_len,
                              :data => this_data }
                             )
    self[:body] << this_pkt
  end
  if filename
    self.to_f(:filename => filename, :append => append)
  else
    self
  end
end

#array_to_file!(arr) ⇒ Object Also known as: a2f!

Just like array_to_file, but clears any existing packets from the array first.



454
455
456
457
# File 'lib/packetfu/pcap.rb', line 454

def array_to_file!(arr)
  clear
  array_to_file(arr)
end

#clearObject

Clears the contents of the PcapFile.



342
343
344
# File 'lib/packetfu/pcap.rb', line 342

def clear
  self[:body].clear
end

#file_to_array(args = {}) ⇒ Object Also known as: f2a

file_to_array() translates a libpcap file into an array of packets. Note that this strips out pcap timestamps – if you’d like to retain timestamps and other libpcap file information, you will want to use read() instead.



386
387
388
389
390
391
392
393
394
395
396
# File 'lib/packetfu/pcap.rb', line 386

def file_to_array(args={})
  filename = args[:filename] || args[:file] || args[:f]
  if filename
    self.read! File.open(filename, "rb") {|f| f.read}
  end
  if args[:keep_timestamps] || args[:keep_ts] || args[:ts]
    self[:body].map {|x| {x.timestamp.to_s => x.data.to_s} }
  else
    self[:body].map {|x| x.data.to_s}
  end
end

#init_fields(args = {}) ⇒ Object

Called by initialize to set the initial fields.



330
331
332
333
334
# File 'lib/packetfu/pcap.rb', line 330

def init_fields(args={})
  args[:head] = PcapHeader.new(:endian => args[:endian]).read(args[:head])
  args[:body] = PcapPackets.new(:endian => args[:endian]).read(args[:body])
  return args
end

#read(str) ⇒ Object

Reads a string to populate the object. Note that this appends new packets to any existing packets in the PcapFile.



348
349
350
351
352
353
# File 'lib/packetfu/pcap.rb', line 348

def read(str)
  force_binary(str)
  self[:head].read str[0,24]
  self[:body].read str
  self
end

#read!(str) ⇒ Object

Clears the contents of the PcapFile prior to reading in a new string.



356
357
358
359
360
# File 'lib/packetfu/pcap.rb', line 356

def read!(str)
  clear
  force_binary(str)
  self.read str
end

#read_packet_bytes(fname = @filename, &block) ⇒ Object

Calls the class method with this object’s @filename

Raises:

  • (ArgumentError)


371
372
373
374
# File 'lib/packetfu/pcap.rb', line 371

def read_packet_bytes(fname=@filename,&block)
  raise ArgumentError, "Need a file" unless fname
  return self.class.read_packet_bytes(fname, &block)
end

#read_packets(fname = @filename, &block) ⇒ Object

Calls the class method with this object’s @filename

Raises:

  • (ArgumentError)


377
378
379
380
# File 'lib/packetfu/pcap.rb', line 377

def read_packets(fname=@filename,&block)
  raise ArgumentError, "Need a file" unless fname
  return self.class.read_packets(fname, &block)
end

#readfile(file) ⇒ Object

A shorthand method for opening a file and reading in the packets. Note that readfile clears any existing packets, since that seems to be the typical use.



365
366
367
368
# File 'lib/packetfu/pcap.rb', line 365

def readfile(file)
  fdata = File.open(file, "rb") {|f| f.read}
  self.read! fdata
end

#to_file(args = {}) ⇒ Object Also known as: to_f

Writes the PcapFile to a file. Takes the following arguments:

:filename # The file to write to.
:append   # If set to true, the packets are appended to the file, rather than overwriting.


465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/packetfu/pcap.rb', line 465

def to_file(args={})
  filename = args[:filename] || args[:file] || args[:f]
  unless (!filename.nil? || filename.kind_of?(String))
    raise ArgumentError, "Need a :filename for #{self.class}"
  end
  append = args[:append]
  if append
    if File.exist? filename
      File.open(filename,'ab') {|file| file.write(self.body.to_s)}
    else
      File.open(filename,'wb') {|file| file.write(self.to_s)}
    end
  else
    File.open(filename,'wb') {|file| file.write(self.to_s)}
  end
  [filename, self.body.sz, self.body.size]
end

#to_sObject

Returns the object in string form.



337
338
339
# File 'lib/packetfu/pcap.rb', line 337

def to_s
  self[:head].to_s + self[:body].map {|p| p.to_s}.join
end

#write(filename = 'out.pcap') ⇒ Object

Shorthand method for writing to a file. Can take either :file => ‘name.pcap’ or simply ‘name.pcap’



487
488
489
490
491
492
493
494
# File 'lib/packetfu/pcap.rb', line 487

def write(filename='out.pcap')
  if filename.kind_of?(Hash)
    f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
  else
    f = filename.to_s
  end
  self.to_file(:filename => f.to_s, :append => false)
end