class PacketGen::Packet

An object of type {Packet} handles a network packet. This packet may contain multiple protocol headers, starting from MAC layer or from Network (OSI) layer.

Creating a packet is fairly simple:

Packet.gen 'IP', src: '192.168.1.1', dst: '192.168.1.2'

Create a packet

Packets may be hand-made or parsed from a binary string:

Packet.gen('IP', src: '192.168.1.1', dst: '192.168.1.2').add('UDP', sport: 45000, dport: 23)
Packet.parse(binary_string)

Access packet information

pkt = Packet.gen('IP').add('UDP')
# read information
pkt.udp.sport
pkt.ip.ttl
# set information
pkt.udp.dport = 2323
pkt.ip.ttl = 1
pkt.ip(ttl: 1, id: 1234)

Save a packet to a file

pkt.write('file.pcapng')

Get packets

Packets may be captured from wire:

Packet.capture do |packet|
  do_some_stuffs
end
packets = Packet.capture(iface: 'eth0', max: 5)  # get 5 packets from eth0

Packets may also be read from a file:

packets = Packet.read(file.pcapng)

Save packets to a file

Packet.write 'file.pcapng', packets

@author Sylvain Daubert

Attributes

headers[R]

Get packet headers, ordered as they appear in the packet. @return [Array<Header::Base>]

Public Class Methods

capture(**kwargs, &block) click to toggle source

Capture packets from wire. Same arguments as {Capture#initialize} @see Capture#initialize @yieldparam [Packet,String] packet if a block is given, yield each

captured packet (Packet or raw data String, depending on +:parse+ option)

@return [Array<Packet>] captured packet

# File lib/packetgen/packet.rb, line 82
def self.capture(**kwargs, &block)
  capture = Capture.new(**kwargs)
  if block
    capture.start(&block)
  else
    capture.start
  end
  capture.packets
end
gen(protocol, options={}) click to toggle source

Create a new Packet @param [String] protocol base protocol for packet @param [Hash] options specific options for protocol @return [Packet]

# File lib/packetgen/packet.rb, line 59
def self.gen(protocol, options={})
  self.new.add protocol, options
end
new() click to toggle source

@private

# File lib/packetgen/packet.rb, line 122
def initialize
  @headers = []
end
parse(binary_str, first_header: nil) click to toggle source

Parse a binary string and generate a Packet from it.

# auto-detect first header
Packet.parse str
# force decoding a Ethernet header for first header
Packet.parse str, first_header: 'Eth'

@param [String] binary_str @param [String,nil] first_header First protocol header. nil means discover it! @return [Packet] @raise [ArgumentError] first_header is an unknown header

# File lib/packetgen/packet.rb, line 72
def self.parse(binary_str, first_header: nil)
  new.parse binary_str, first_header: first_header
end
read(filename) click to toggle source

Read packets from filename. May read Pcap and Pcap-NG formats.

For more control (on Pcap-ng only), see {PcapNG::File}. @param [String] filename PcapNG or Pcap file. @return [Array<Packet>] @raise [ArgumentError] unknown file format @author Sylvain Daubert @author Kent Gruber - Pcap format @since 2.0.0 Also read Pcap format.

# File lib/packetgen/packet.rb, line 101
def self.read(filename)
  PcapNG::File.new.read_packets(filename)
rescue StandardError => e
  raise ArgumentError, e unless File.extname(filename.downcase) == '.pcap'

  Pcap.read(filename)
end
write(filename, packets) click to toggle source

Write packets to filename

For more options, see {PcapNG::File}. @param [String] filename @param [Array<Packet>] packets packets to write @return [void]

# File lib/packetgen/packet.rb, line 115
def self.write(filename, packets)
  pf = PcapNG::File.new
  pf.array_to_file packets
  pf.to_f filename
end

Public Instance Methods

==(other) click to toggle source

@param [Packet] other @return [Boolean]

# File lib/packetgen/packet.rb, line 313
def ==(other)
  to_s == other.to_s
end
===(other) click to toggle source

@param [Packet] other @return [Boolean] @since 3.1.2

# File lib/packetgen/packet.rb, line 320
def ===(other)
  case other
  when PacketGen::Packet
    self == other
  when String
    is? other
  else
    false
  end
end
add(protocol, options={}) click to toggle source

Add a protocol header in packet. @param [String] protocol @param [Hash] options protocol specific options @return [self] @raise [BindingError] unknown protocol

# File lib/packetgen/packet.rb, line 131
def add(protocol, options={})
  klass = check_protocol(protocol)

  # options[:packet]= self is speedier than options.merge(packet: self)
  options[:packet] = self
  header = klass.new(options)
  add_header header
  self
end
body() click to toggle source

Get packet body @return [Types]

# File lib/packetgen/packet.rb, line 197
def body
  last_header[:body] if last_header.respond_to? :body
end
body=(str) click to toggle source

Set packet body @param [String] str @return [void]

# File lib/packetgen/packet.rb, line 204
def body=(str)
  last_header.body = str
end
calc() click to toggle source

Recalculate all calculatable fields (for now: length and checksum) @return [void]

# File lib/packetgen/packet.rb, line 190
def calc
  calc_length
  calc_checksum
end
calc_checksum() click to toggle source

Recalculate all packet checksums @return [void]

# File lib/packetgen/packet.rb, line 174
def calc_checksum
  headers.reverse_each do |header|
    header.calc_checksum if header.respond_to? :calc_checksum
  end
end
calc_length() click to toggle source

Recalculate all packet length fields @return [void]

# File lib/packetgen/packet.rb, line 182
def calc_length
  headers.reverse_each do |header|
    header.calc_length if header.respond_to? :calc_length
  end
end
decapsulate(*hdrs) click to toggle source

Remove headers from self @param [Array<Header>] hdrs @return [self] self with some headers removed @raise [FormatError] any headers not in self @raise [BindingError] removed headers result in an unknown binding @since 1.1.0

# File lib/packetgen/packet.rb, line 267
def decapsulate(*hdrs)
  hdrs.each do |hdr|
    prev_hdr = previous_header(hdr)
    next_hdr = next_header(hdr)
    headers.delete(hdr)
    add_header(next_hdr, previous_header: prev_hdr) if prev_hdr && next_hdr
  end
rescue ArgumentError => e
  raise FormatError, e.message
end
encapsulate(other, parsing: false) click to toggle source

Encapulate another packet in self @param [Packet] other @param [Boolean] parsing set to true to not update last current header field

from binding with first other's one. Use only when current header field
has its value set accordingly.

@return [self] self with new headers from other @raise [BindingError] do not known how to encapsulate @since 1.1.0

# File lib/packetgen/packet.rb, line 255
def encapsulate(other, parsing: false)
  other.headers.each_with_index do |h, i|
    add_header h, parsing: i.positive? || parsing
  end
end
insert(prev, protocol, options={}) click to toggle source

Insert a header in packet @param [Header] prev header after which insert new one @param [String] protocol protocol to insert @param [Hash] options protocol specific options @return [self] @raise [BindingError] unknown protocol

# File lib/packetgen/packet.rb, line 147
def insert(prev, protocol, options={})
  klass = check_protocol(protocol)

  nxt = prev.body
  # options[:packet]= self is speedier than options.merge(packet: self)
  options[:packet] = self
  header = klass.new(options)
  add_header header, previous_header: prev
  idx = headers.index(prev) + 1
  headers[idx, 0] = header
  header[:body] = nxt
  self
end
inspect() click to toggle source

Get packet as a pretty formatted string. @return [String]

# File lib/packetgen/packet.rb, line 303
def inspect
  str = Inspect.dashed_line(self.class)
  headers.each do |header|
    str << header.inspect
  end
  str << Inspect.inspect_body(body)
end
is?(protocol) click to toggle source

Check if a protocol header is embedded in packet.

pkt = PacketGen.gen('IP').add('UDP')
pkt.is?('IP')   #=> true
pkt.is?('TCP')  #=> false

@return [Boolean] @raise [ArgumentError] unknown protocol

# File lib/packetgen/packet.rb, line 167
def is?(protocol)
  klass = check_protocol protocol
  headers.any? { |h| h.is_a? klass }
end
parse(binary_str, first_header: nil) click to toggle source

Parse a binary string and populate Packet from it. @param [String] binary_str @param [String,nil] first_header First protocol header. nil means discover it! @return [Packet] self @raise [ParseError] first_header is an unknown header @raise [BindingError] unknwon binding between some headers

# File lib/packetgen/packet.rb, line 284
def parse(binary_str, first_header: nil)
  headers.clear

  if first_header.nil?
    # No decoding forced for first header. Have to guess it!
    first_header = guess_first_header(binary_str)
    raise ParseError, "cannot identify first header in string: #{binary_str.inspect}" if first_header.nil?
  end

  add first_header
  headers[-1, 1] = last_header.read(binary_str)

  # Decode upper headers recursively
  decode_bottom_up
  self
end
reply() click to toggle source

Forge a new packet from current one with all possible fields inverted. The new packet may be a reply to current one. @return [Packet] @since 2.7.0

# File lib/packetgen/packet.rb, line 345
def reply
  pkt = dup
  pkt.reply!
end
reply!() click to toggle source

Invert all possible fields in packet to create a reply. @return [self] @since 2.7.0

# File lib/packetgen/packet.rb, line 334
def reply!
  headers.each do |header|
    header.reply! if header.respond_to?(:reply!)
  end
  self
end
to_f(filename) click to toggle source

Write packet to a PCapNG file on disk. @param [String] filename @return [Array] see return from {PcapNG::File#to_file} @see File

# File lib/packetgen/packet.rb, line 218
def to_f(filename)
  PcapNG::File.new.read_array([self]).to_f(filename)
end
Also aliased as: write
to_s() click to toggle source

Get binary string (i.e. binary string sent on or received from network). @return [String]

# File lib/packetgen/packet.rb, line 210
def to_s
  first_header.to_s
end
to_w(iface=nil, calc: true, number: 1, interval: 1) click to toggle source

Send packet on wire. Use first header #to_w method. @param [String] iface interface name. Default to first non-loopback interface @param [Boolean] calc if true, call {#calc} on packet before sending it. @param [Integer] number number of times to send the packets @param [Integer,Float] interval time, in seconds, between sending 2 packets @return [void] @since 2.1.4 add `calc`, `number` and `interval` parameters @since 3.0.0 calc defaults to true

# File lib/packetgen/packet.rb, line 231
def to_w(iface=nil, calc: true, number: 1, interval: 1)
  iface ||= PacketGen.default_iface

  if first_header.respond_to? :to_w
    self.calc if calc

    number.times do
      first_header.to_w(iface)
      sleep interval if number > 1
    end
  else
    type = first_header.protocol_name
    raise WireError, "don't known how to send a #{type} packet on wire"
  end
end
write(filename)
Alias for: to_f

Private Instance Methods

add_header(header, previous_header: nil, parsing: false) click to toggle source

Add a header to packet @param [Headerable] header @param [Headerable] previous_header @param [Boolean] parsing @return [void] @raise [BindingError]

# File lib/packetgen/packet.rb, line 440
def add_header(header, previous_header: nil, parsing: false)
  prev_header = previous_header || last_header
  add_to_previous_header(prev_header, header, parsing) if prev_header

  header.packet = self
  headers << header unless previous_header

  return if respond_to? header.method_name

  add_magic_header_method header
end
add_magic_header_method(header) click to toggle source

Add method to access header @param [Header::Base] header @return [void]

# File lib/packetgen/packet.rb, line 469
def add_magic_header_method(header)
  self.instance_eval "def #{header.method_name}(arg=nil);" \
                     "header(#{header.class}, arg); end"
end
add_to_previous_header(prev_header, header, parsing) click to toggle source

Bind header to prev_header. @param [Headerable] prev_header @param [Headerable] header @param [Boolean] parsing @return [void] @raise [BindingError]

# File lib/packetgen/packet.rb, line 458
def add_to_previous_header(prev_header, header, parsing)
  bindings = prev_header.class.known_headers[header.class] || prev_header.class.known_headers[header.class.superclass]
  raise BindingError.new(prev_header, header) if bindings.nil?

  bindings.set(prev_header) if !bindings.empty? && !parsing
  prev_header[:body] = header
end
check_protocol(protocol) click to toggle source

check if protocol is known @param [String] protocol @raise [ArgumentError] unknown protocol

# File lib/packetgen/packet.rb, line 427
def check_protocol(protocol)
  klass = Header.get_header_class_by_name(protocol)
  raise ArgumentError, "unknown #{protocol} protocol" if klass.nil?

  klass
end
decode_bottom_up() click to toggle source

Decode packet bottom up @return [void]

# File lib/packetgen/packet.rb, line 496
def decode_bottom_up
  loop do
    last_known_hdr = last_header
    break if !last_known_hdr.respond_to?(:body) || last_known_hdr.body.empty?

    nh = search_upper_header(last_known_hdr)
    break if nh.nil?

    nheader = nh.new(packet: self)
    nheader = nheader.read(last_known_hdr.body)
    next unless nheader.parse?

    add_header nheader, parsing: true
    break if last_header == last_known_hdr
  end
end
first_header() click to toggle source

Give first header of packet @return [Header::Base]

# File lib/packetgen/packet.rb, line 363
def first_header
  headers.first
end
guess_first_header(binary_str) click to toggle source

Try to guess header from binary_str @param [String] binary_str @return [String] header/protocol name

# File lib/packetgen/packet.rb, line 477
def guess_first_header(binary_str)
  first_header = nil
  Header.all.each do |hklass|
    hdr = hklass.new(packet: self)
    # #read may return another object (more specific class)
    hdr = hdr.read(binary_str)
    # First header is found when, for one known header,
    # * +#parse?+ is true
    # * it exists a known binding with a upper header
    next unless hdr.parse?

    first_header = hklass.to_s.gsub(/.*::/, '') if search_upper_header(hdr)
    break unless first_header.nil?
  end
  first_header
end
header(klass, arg) click to toggle source

@overload header(klass, layer=1)

@param [Class] klass
@param [Integer] layer

@overload header(klass, options={})

@param [String] klass
@param [Hash] options
@raise [ArgumentError] unknown option

@return [Header::Base]

# File lib/packetgen/packet.rb, line 410
def header(klass, arg)
  layer = arg.is_a?(Integer) ? arg : 1
  header = headers.select { |h| h.is_a? klass }[layer - 1]
  return header unless arg.is_a? Hash

  arg.each do |key, value|
    raise ArgumentError, "unknown #{key} attribute for #{klass}" unless header.respond_to? "#{key}="

    header.send "#{key}=", value
  end

  header
end
header_index(hdr) click to toggle source

Give header index in packet @param [Headerable] hdr @return [Integer] @raise [FormatError] hdr not in packet

# File lib/packetgen/packet.rb, line 377
def header_index(hdr)
  idx = headers.index(hdr)
  raise FormatError, 'header not in packet!' if idx.nil?

  idx
end
initialize_copy(_other) click to toggle source

Dup +@headers+ instance variable. Internally used by #dup and #clone @return [void]

# File lib/packetgen/packet.rb, line 354
def initialize_copy(_other)
  @headers = headers.map(&:dup)
  headers.each do |header|
    add_magic_header_method header
  end
end
last_header() click to toggle source

Give last header of packet @return [Header::Base]

# File lib/packetgen/packet.rb, line 369
def last_header
  headers.last
end
next_header(hdr) click to toggle source

Give header next hdr in packet @param [Headerable] hdr @return [Headerable,nil] May return nil if hdr is the last header in packet @raise [FormatError] hdr not in packet

# File lib/packetgen/packet.rb, line 397
def next_header(hdr)
  idx = header_index(hdr)
  (idx + 1) < headers.size ? headers[idx + 1] : nil
end
previous_header(hdr) click to toggle source

Give header previous hdr in packet @param [Headerable] hdr @return [Headerable,nil] May return nil if hdr is the first header in packet @raise [FormatError] hdr not in packet

# File lib/packetgen/packet.rb, line 388
def previous_header(hdr)
  idx = header_index(hdr)
  idx.positive? ? headers[idx - 1] : nil
end
search_upper_header(hdr) click to toggle source

Search a upper header for hdr @param [Header::Base] hdr @return [void] @yieldparam [Header::Base] found upper header

# File lib/packetgen/packet.rb, line 517
def search_upper_header(hdr)
  hdr.class.known_headers.each do |nh, bindings|
    return nh if bindings.check?(hdr)
  end

  nil
end