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

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePacket

Returns a new instance of Packet.

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



134
135
136
# File 'lib/packetgen/packet.rb', line 134

def initialize
  @headers = []
end

Instance Attribute Details

#headersArray<Header::Base] (readonly)

Returns Array<Header::Base].

Returns:

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



58
59
60
# File 'lib/packetgen/packet.rb', line 58

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

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



92
93
94
95
96
97
98
99
100
# File 'lib/packetgen/packet.rb', line 92

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:

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



64
65
66
# File 'lib/packetgen/packet.rb', line 64

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

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



77
78
79
# File 'lib/packetgen/packet.rb', line 77

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. Mays read Pcap and Pcap-NG formats.

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

Parameters:

  • filename (String)

    PcapNG or Pcap file.

Returns:

Author:

  • Sylvain Daubert

  • Kent Gruber

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/packetgen/packet.rb', line 109

def self.read(filename)
  PcapNG::File.new.read_packets filename
rescue StandardError => 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

.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

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



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

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)

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



322
323
324
# File 'lib/packetgen/packet.rb', line 322

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

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



143
144
145
146
147
148
149
# File 'lib/packetgen/packet.rb', line 143

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

  header = klass.new(options.merge!(packet: self))
  add_header header
  self
end

#bodyTypes

Get packet body

Returns:

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



202
203
204
# File 'lib/packetgen/packet.rb', line 202

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)

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



209
210
211
# File 'lib/packetgen/packet.rb', line 209

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

#calcvoid

This method returns an undefined value.

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

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



195
196
197
198
# File 'lib/packetgen/packet.rb', line 195

def calc
  calc_length
  calc_checksum
end

#calc_checksumvoid

This method returns an undefined value.

Recalculate all packet checksums

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



179
180
181
182
183
# File 'lib/packetgen/packet.rb', line 179

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

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



187
188
189
190
191
# File 'lib/packetgen/packet.rb', line 187

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



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/packetgen/packet.rb', line 272

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 && next_header
      add_header(next_header, previous_header: prev_header)
    end
  end
rescue ArgumentError => ex
  raise FormatError, ex.message
end

#encapsulate(other, parsing: false) ⇒ self

Encapulate another packet in self

Parameters:

  • other (Packet)
  • parsing (Boolean) (defaults to: false)

    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.

Returns:

  • (self)

    self with new headers from other

Since:

  • 1.1.0



260
261
262
263
264
# File 'lib/packetgen/packet.rb', line 260

def encapsulate(other, parsing: false)
  other.headers.each_with_index do |h, i|
    add_header h, parsing: (i > 0) || parsing
  end
end

#insert(prev, protocol, options = {}) ⇒ self

Insert a header in packet

Parameters:

  • prev (Header)

    header after which insert new one

  • protocol (String)

    protocol to insert

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

    protocol specific options

Returns:

  • (self)

Raises:

  • (ArgumentError)

    unknown protocol

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



157
158
159
160
161
162
163
164
165
166
167
# File 'lib/packetgen/packet.rb', line 157

def insert(prev, protocol, options={})
  klass = check_protocol(protocol)

  nxt = prev.body
  header = klass.new(options.merge!(packet: self))
  add_header header, previous_header: prev
  idx = @headers.index(prev) + 1
  @headers[idx, 0] = header
  header[:body] = nxt
  self
end

#inspectString

Returns:

  • (String)

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



312
313
314
315
316
317
318
# File 'lib/packetgen/packet.rb', line 312

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

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



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

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

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/packetgen/packet.rb', line 293

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

#replyPacket

Forge a new packet from current one with all possible fields inverted. The new packet may be a reply to current one.

Returns:

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



338
339
340
341
# File 'lib/packetgen/packet.rb', line 338

def reply
  pkt = dup
  pkt.reply!
end

#reply!self

Invert all possible fields in packet to create a reply.

Returns:

  • (self)

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



328
329
330
331
332
333
# File 'lib/packetgen/packet.rb', line 328

def reply!
  @headers.each do |header|
    header.reply! if header.respond_to?(:reply!)
  end
  self
end

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

Write a PCapNG file to disk.

Parameters:

  • filename (String)

Returns:

See Also:

  • File

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



223
224
225
# File 'lib/packetgen/packet.rb', line 223

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

#to_sString

Get binary string

Returns:

  • (String)

Since:

  • 2.0.0

    Packet accessor has changed. When header class is in a namespace (for example Dot11::* header classes), to avoid clashes in names, such accessors are named namespace_class. For example Header::Dot11::Data header is now accessed through Packet#dot11_data and nor more Packet#data).



215
216
217
# File 'lib/packetgen/packet.rb', line 215

def to_s
  @headers.first.to_s
end

#to_w(iface = nil, calc: false, number: 1, interval: 1) ⇒ 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

  • calc (Boolean) (defaults to: false)

    call #calc on packet before sending it

  • number (Integer) (defaults to: 1)

    number of times to send the packets

  • interval (Integer, Float) (defaults to: 1)

    time, in seconds, between sending 2 packets

Since:

  • 2.1.4 add ‘calc`, `number` and `interval` parameters



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/packetgen/packet.rb', line 235

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