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


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

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


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

def body
  @body
end

#endianObject

Returns the value of attribute endian

Returns:

  • (Object)

    the current value of endian


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

def endian
  @endian
end

#headObject

Returns the value of attribute head

Returns:

  • (Object)

    the current value of head


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

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.


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

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.


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

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.


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

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.


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

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'


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

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.

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

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
      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.


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

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

#clearObject

Clears the contents of the PcapFile.


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

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.


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

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.


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

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.


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

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.


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

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)

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

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)

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

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.


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

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.

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

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.exists? 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.


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

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'


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

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