Class: PacketGen::Packet

Inherits:
Object
  • Object
show all
Defined in:
lib/packetgen/packet.rb

Overview

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePacket

Returns a new instance of Packet.



128
129
130
# File 'lib/packetgen/packet.rb', line 128

def initialize
  @headers = []
end

Instance Attribute Details

#headersArray<Header::Base] (readonly)

Returns Array<Header::Base].

Returns:



50
51
52
# File 'lib/packetgen/packet.rb', line 50

def headers
  @headers
end

Class Method Details

.capture(options = {}) {|packet| ... } ⇒ Array<Packet>

Capture packets

Parameters:

  • options (Hash) (defaults to: {})

    capture options

Options Hash (options):

  • :iface (String)

    interface on which capture packets on. Default: Use default interface lookup.

  • :max (Integer)

    maximum number of packets to capture

  • :timeout (Integer)

    maximum number of seconds before end of capture

  • :filter (String)

    bpf filter

  • :promiscuous (Boolean)

Yield Parameters:

  • packet (Packet)

    if a block is given, yield each captured packet

Returns:

  • (Array<Packet>)

    captured packet



84
85
86
87
88
89
90
91
92
# File 'lib/packetgen/packet.rb', line 84

def self.capture(options={})
  capture = Capture.new(options)
  if block_given?
    capture.start { |packet| yield packet }
  else
    capture.start
  end
  capture.packets
end

.gen(protocol, options = {}) ⇒ Packet

Create a new Packet

Parameters:

  • protocol (String)

    base protocol for packet

  • options (Hash) (defaults to: {})

    specific options for protocol

Returns:



56
57
58
# File 'lib/packetgen/packet.rb', line 56

def self.gen(protocol, options={})
  self.new.add protocol, options
end

.parse(binary_str, first_header: nil) ⇒ Packet

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'

Parameters:

  • binary_str (String)
  • first_header (String, nil) (defaults to: nil)

    First protocol header. nil means discover it!

Returns:

Raises:

  • (ArgumentError)

    first_header is an unknown header



69
70
71
# File 'lib/packetgen/packet.rb', line 69

def self.parse(binary_str, first_header: nil)
  new.parse binary_str, first_header: first_header
end

.read(filename) ⇒ Array<Packet>

Read packets from filename.

For more control, see PacketGen::PcapNG::File or PCAPRUB::Pcap.

Parameters:

  • filename (String)

    PcapNG or Pcap file.

Returns:

Author:

  • Sylvain Daubert

  • Kent Gruber



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/packetgen/packet.rb', line 101

def self.read(filename)
  begin
    PcapNG::File.new.read_packets filename
  rescue => e
    raise ArgumentError, e unless File.extname(filename.downcase) == '.pcap'
    packets = []
    PCAPRUB::Pcap.open_offline(filename).each_packet do |packet|
      next unless packet = PacketGen.parse(packet.to_s)  
      packets << packet
    end
    packets
  end
end

.write(filename, packets) ⇒ void

This method returns an undefined value.

Write packets to filename

For more options, see PacketGen::PcapNG::File.

Parameters:

  • filename (String)
  • packets (Array<Packet>)

    packets to write



121
122
123
124
125
# File 'lib/packetgen/packet.rb', line 121

def self.write(filename, packets)
  pf = PcapNG::File.new
  pf.array_to_file packets
  pf.to_f filename
end

Instance Method Details

#==(other) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


281
282
283
# File 'lib/packetgen/packet.rb', line 281

def ==(other)
  to_s == other.to_s
end

#add(protocol, options = {}) ⇒ self

Add a protocol on packet stack

Parameters:

  • protocol (String)
  • options (Hash) (defaults to: {})

    protocol specific options

Returns:

  • (self)

Raises:

  • (ArgumentError)

    unknown protocol



137
138
139
140
141
142
143
# File 'lib/packetgen/packet.rb', line 137

def add(protocol, options={})
  klass = check_protocol(protocol)

  header = klass.new(options)
  add_header header
  self
end

#bodyTypes

Get packet body

Returns:



178
179
180
# File 'lib/packetgen/packet.rb', line 178

def body
  @headers.last.body if @headers.last.respond_to? :body
end

#body=(str) ⇒ void

This method returns an undefined value.

Set packet body

Parameters:

  • str (String)


185
186
187
# File 'lib/packetgen/packet.rb', line 185

def body=(str)
  @headers.last.body = str
end

#calcvoid

This method returns an undefined value.

Recalculate all calculatable fields (for now: length and checksum)



171
172
173
174
# File 'lib/packetgen/packet.rb', line 171

def calc
  calc_length
  calc_checksum
end

#calc_checksumvoid

This method returns an undefined value.

Recalculate all packet checksums



155
156
157
158
159
# File 'lib/packetgen/packet.rb', line 155

def calc_checksum
  @headers.reverse.each do |header|
    header.calc_checksum if header.respond_to? :calc_checksum
  end
end

#calc_lengthvoid

This method returns an undefined value.

Recalculate all packet length fields



163
164
165
166
167
# File 'lib/packetgen/packet.rb', line 163

def calc_length
  @headers.each do |header|
    header.calc_length if header.respond_to? :calc_length
  end
end

#decapsulate(*headers) ⇒ self

Remove headers from self

Parameters:

Returns:

  • (self)

    self with some headers removed

Raises:

Since:

  • 1.1.0



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/packetgen/packet.rb', line 231

def decapsulate(*headers)
  headers.each do |header|
    idx = @headers.index(header)
    raise FormatError, 'header not in packet!' if idx.nil?

    prev_header = idx > 0 ? @headers[idx - 1] : nil
    next_header = (idx+1) < @headers.size ? @headers[idx + 1] : nil
    @headers.delete_at(idx)
    if prev_header and next_header
      add_header(next_header, previous_header: prev_header)
    end
  end
rescue ArgumentError => ex
  raise FormatError, ex.message
end

#encapsulate(other) ⇒ self

Encapulate another packet in self

Parameters:

Returns:

  • (self)

    self with new headers from other

Since:

  • 1.1.0



221
222
223
# File 'lib/packetgen/packet.rb', line 221

def encapsulate(other)
  other.headers.each { |h| add_header h }
end

#inspectString

Returns:

  • (String)


271
272
273
274
275
276
277
# File 'lib/packetgen/packet.rb', line 271

def inspect
  str = Inspect.dashed_line(self.class)
  @headers.each do |header|
    str << header.inspect
  end
  str << Inspect.inspect_body(body)
end

#is?(protocol) ⇒ Boolean

Check if a protocol header is embedded in packet

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

    unknown protocol



148
149
150
151
# File 'lib/packetgen/packet.rb', line 148

def is?(protocol)
  klass = check_protocol protocol
  @headers.any? { |h| h.is_a? klass }
end

#parse(binary_str, first_header: nil) ⇒ Packet

Parse a binary string and populate Packet from it.

Parameters:

  • binary_str (String)
  • first_header (String, nil) (defaults to: nil)

    First protocol header. nil means discover it!

Returns:

Raises:

  • (ArgumentError)

    first_header is an unknown header



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/packetgen/packet.rb', line 252

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)
    if first_header.nil?
      raise ParseError, 'cannot identify first header in string'
    end
  end
  add first_header
  @headers[-1, 1] = @headers.last.read(binary_str)

  # Decode upper headers recursively
  decode_bottom_up
  self
end

#to_f(filename) ⇒ Array Also known as: write

Write a PCapNG file to disk.

Parameters:

  • filename (String)

Returns:

See Also:

  • File


199
200
201
# File 'lib/packetgen/packet.rb', line 199

def to_f(filename)
  File.new.array_to_file(filename: filename, array: [self])
end

#to_sString

Get binary string

Returns:

  • (String)


191
192
193
# File 'lib/packetgen/packet.rb', line 191

def to_s
  @headers.first.to_s
end

#to_w(iface = nil) ⇒ void

This method returns an undefined value.

send packet on wire. Use first header #to_w method.

Parameters:

  • iface (String) (defaults to: nil)

    interface name. Default to first non-loopback interface



207
208
209
210
211
212
213
214
215
# File 'lib/packetgen/packet.rb', line 207

def to_w(iface=nil)
  iface ||= PacketGen.default_iface
  if @headers.first.respond_to? :to_w
    @headers.first.to_w(iface)
  else
    type = @headers.first.protocol_name
    raise WireError, "don't known how to send a #{type} packet on wire"
  end
end