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.

A Packet is created using Packet.gen method. Headers may be added to this packet using #add. It may also be created from parsing a binary string, using Packet.parse method.

Information for each level is accessible through the associated header. Header is accessible through a method defined with its name at packet level (i.e. #ip for a IP header).

Packets may be written to files using #write:

pkt = PacketGen::Packet.gen('Eth')
pkt.write('file.pcapng')

Packets may be captured from network interfaces:

# Capture all packets from default interface. Never end.
PacketGen::Packet.capture do |packet|
  do_some_stuffs
end

# Get 5 packets from eth0 interface
packets = Packet.capture(iface: 'eth0', max: 5)

Finally, packets may also be read from a file:

packets = Packet.read(file.pcapng)

Examples:

Very simple packet

# Create a packet with a single IP header. IP source and destination addresses are set.
pkt = PacketGen::Packet.gen('IP', src: '192.168.1.1', dst: '192.168.1.2')

Parsing a binary string

# a IP/ICMP packet, as binary string
binary_string = "E\x00\x00\x1C\xAA`\x00\x00@\x01M-\xC0\xA8\x01\x01\xC0\xA8\x01\x02\x00\b;1abcd".b
pkt = PacketGen::Packet.parse(binary_string)
pkt.is?('IP')    #=> true
pkt.is?('ICMP')  #=> true

Accessing header information

pkt = PacketGen::Packet.gen('IP').add('UDP')
# read information
pkt.udp.sport #=> 0
pkt.ip.ttl    #=> 64
# set information
pkt.udp.dport = 2323
pkt.ip.ttl = 1
# Set multiple attributes at once
pkt.ip(ttl: 1, id: 1234)

Author:

  • Sylvain Daubert

  • LemonTree55

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePacket

Returns a new instance of Packet.


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

def initialize
  @headers = []
  @header_cache = {}
  @cache_headers = true
end

Instance Attribute Details

#cache_headersBoolean

Activaye or deactivate header cache (activated by default)

Returns:

  • (Boolean)

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

def cache_headers
  @cache_headers
end

#headersArray<Headerable> (readonly)

Get packet headers, ordered as they appear in the packet.

Returns:


62
63
64
# File 'lib/packetgen/packet.rb', line 62

def headers
  @headers
end

Class Method Details

.capture(**kwargs) {|packet, timestamp| ... } ⇒ Array<Packet>

Capture packets from wire. Same arguments as Capture#initialize

Yield Parameters:

  • packet (Packet, String)

    if a block is given, yield each captured packet (Packet or raw data String, depending on :parse option)

  • timestamp (Time)

    packet timestamp

Returns:

  • (Array<Packet>)

    captured packet

See Also:

Since:

  • 3.3.0 add packet timestamp as second yield parameter


96
97
98
99
100
101
102
103
104
# File 'lib/packetgen/packet.rb', line 96

def self.capture(**kwargs, &block)
  capture = Capture.new(**kwargs)
  if block
    capture.start(&block)
  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:


71
72
73
# File 'lib/packetgen/packet.rb', line 71

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


84
85
86
# File 'lib/packetgen/packet.rb', line 84

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

For more control (on Pcap-ng only), see PacketGen::PcapNG::File.

Parameters:

  • filename (String)

    PcapNG or Pcap file.

Returns:

Raises:

  • (ArgumentError)

    unknown file format

Author:

  • Sylvain Daubert

  • Kent Gruber - Pcap format

Since:

  • 2.0.0 Also read Pcap format.


115
116
117
118
119
120
121
# File 'lib/packetgen/packet.rb', line 115

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) ⇒ void

This method returns an undefined value.

Write packets to filename, as pcap or PcapNG file

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

Parameters:

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

    packets to write

Author:

  • Sylvain Daubert

  • LemonTree55 (pcap)

Since:

  • 4.0.0 write a pcap file if filename extension is .pcap


132
133
134
135
136
137
138
139
140
# File 'lib/packetgen/packet.rb', line 132

def self.write(filename, packets)
  if filename.end_with?('.pcap')
    Pcap.write(filename, packets)
  else
    pf = PcapNG::File.new
    pf.read_array(packets)
    pf.to_f(filename)
  end
end

Instance Method Details

#<<(header) ⇒ self

Append an already defined header to packet

Examples:

pkt = PacketGen.gen('Eth')
# Add a new header from its type
pkt.add('IP')
# Add a new pre-generated header
pkt << PacketGen::Header::TCP.new

Parameters:

Returns:

  • (self)

Raises:

  • (ArgumentError)

    unknown protocol

  • (BindingError)

    unknown binding

See Also:

Author:

  • LemonTree55

Since:

  • 4.1.0


424
425
426
427
# File 'lib/packetgen/packet.rb', line 424

def <<(header)
  add_header(header)
  self
end

#==(other) ⇒ Boolean

Check equality at binary level

Parameters:

Returns:

  • (Boolean)

370
371
372
# File 'lib/packetgen/packet.rb', line 370

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

#===(other) ⇒ Boolean

true if #== is true with another packet, or if other is a protocol name String, whose protocol is in Packet.

Parameters:

Returns:

  • (Boolean)

Since:

  • 3.1.2


378
379
380
381
382
383
384
385
386
387
# File 'lib/packetgen/packet.rb', line 378

def ===(other)
  case other
  when PacketGen::Packet
    self == other
  when String
    is?(other)
  else
    false
  end
end

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

Add a protocol header in packet.

Examples:

pkt = PacketGen::Packet.gen('Eth')
# Add a IP header
pkt.add('IP')
# Add a TCP header, with some attributes and body set
pkt.add('TCP', dport: 80, seqnum: 123456, body: "abcd".b)

Parameters:

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

    protocol specific options

Returns:

  • (self)

Raises:

  • (ArgumentError)

    unknown protocol

  • (BindingError)

    unknown binding

See Also:


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

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

#bodyHeaderable, ...

Get packet body. If packet (i.e. last header) has no :body field, return nil.

Returns:


232
233
234
# File 'lib/packetgen/packet.rb', line 232

def body
  last_header[:body] if last_header.respond_to?(:body)
end

#body=(str) ⇒ void

Note:

To set a Headerable object, prefer ##<<

This method returns an undefined value.

Set packet body

Parameters:

  • str (String)

    Binary string

Raises:

  • (Error)

    Packet (i.e. last header) has no :body field.

See Also:

Since:

  • 4.1.0 raise Error if no body on packet


243
244
245
246
247
# File 'lib/packetgen/packet.rb', line 243

def body=(str)
  raise Error, 'no body in last headeré' unless last_header.respond_to?(:body)

  last_header.body = str
end

#calcvoid

This method returns an undefined value.

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

Author:

  • LemonTree55


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

def calc
  headers.reverse_each do |header|
    header.calc_length if header.respond_to?(:calc_length)
    header.calc_checksum if header.respond_to?(:calc_checksum)
  end
end

#calc_checksumvoid

This method returns an undefined value.

Recalculate all packet checksums


206
207
208
209
210
# File 'lib/packetgen/packet.rb', line 206

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


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

def calc_length
  headers.reverse_each do |header|
    header.calc_length if header.respond_to?(:calc_length)
  end
end

#decapsulate(*hdrs) ⇒ self

Remove headers from self

Examples:

# IP/IP encapsulation
pkt = PacketGen::Packet.gen('IP', id: 1).add('IP', id:2)
# Remove outer IP header
pkt.decapsulate(pkt.ip(1))
pkt.ip.id #=> 2

Parameters:

Returns:

  • (self)

    self with some headers removed

Raises:

Since:

  • 1.1.0


321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/packetgen/packet.rb', line 321

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
  invalidate_header_cache
  self
rescue ArgumentError => e
  raise FormatError, e.message
end

#encapsulate(other, parsing: false) ⇒ self

Encapulate another packet in self

Examples:

# Create a first IP packet
ip1 = PacketGen::Packet.gen('IP', id: 1)
# Create second IP packet, to encapsulate in first
ip2 = PacketGen.gen('IP', id: 2)
ip1.encapsulate(ip2)
ip1.ip(2) == ip2.ip

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 updated with new headers from other

Raises:

Since:

  • 1.1.0


303
304
305
306
307
# File 'lib/packetgen/packet.rb', line 303

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 = {}) ⇒ 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:


178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/packetgen/packet.rb', line 178

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

#inspectString

Get packet as a pretty formatted string.

Returns:

  • (String)

359
360
361
362
363
364
365
# File 'lib/packetgen/packet.rb', line 359

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.

Examples:

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

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

    unknown protocol


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

def is?(protocol)
  klass = check_protocol(protocol)
  headers.any?(klass)
end

#parse(binary_str, first_header: nil) ⇒ self

Parse a binary string and populate Packet from it.

Parameters:

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

    First protocol header name. nil means discover it!

Returns:

  • (self)

Raises:


340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/packetgen/packet.rb', line 340

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

#replyPacket

Note:

Only modify headers responding to #reply!.

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


405
406
407
408
# File 'lib/packetgen/packet.rb', line 405

def reply
  pkt = dup
  pkt.reply!
end

#reply!self

Note:

Only modify headers responding to #reply!.

Invert all possible attributes in packet to create a reply.

Returns:

  • (self)

Since:

  • 2.7.0


393
394
395
396
397
398
# File 'lib/packetgen/packet.rb', line 393

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 packet to a PCapNG file on disk.

Parameters:

  • filename (String)

Returns:

See Also:

  • File

259
260
261
# File 'lib/packetgen/packet.rb', line 259

def to_f(filename)
  PcapNG::File.new.read_array([self]).to_f(filename)
end

#to_sString

Get binary string (i.e. binary string sent on or received from network).

Returns:

  • (String)

251
252
253
# File 'lib/packetgen/packet.rb', line 251

def to_s
  first_header.to_s
end

#to_w(iface = nil, calc: true, number: 1, interval: 1) ⇒ void

This method returns an undefined value.

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

Parameters:

  • iface (String, nil) (defaults to: nil)

    interface name. Default to first non-loopback interface

  • calc (Boolean) (defaults to: true)

    if true, 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

  • 3.0.0 calc defaults to true


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

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