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.


125
126
127
128
129
# File 'lib/packetgen/packet.rb', line 125

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

Instance Attribute Details

#cache_headersBoolean

Activaye or deactivate header cache (activated by default)

Returns:

  • (Boolean)

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

def cache_headers
  @cache_headers
end

#headersArray<Header::Base> (readonly)

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

Returns:


53
54
55
# File 'lib/packetgen/packet.rb', line 53

def headers
  @headers
end

Class Method Details

.capture(**kwargs) {|packet| ... } ⇒ 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)

Returns:

  • (Array<Packet>)

    captured packet

See Also:


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

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:


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

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


75
76
77
# File 'lib/packetgen/packet.rb', line 75

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.


104
105
106
107
108
109
110
# File 'lib/packetgen/packet.rb', line 104

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

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

Parameters:

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

    packets to write


118
119
120
121
122
# File 'lib/packetgen/packet.rb', line 118

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)

319
320
321
# File 'lib/packetgen/packet.rb', line 319

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

#===(other) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)

Since:

  • 3.1.2


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

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.

Parameters:

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

    protocol specific options

Returns:

  • (self)

Raises:


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

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

#bodyTypes

Get packet body

Returns:


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

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

#body=(str) ⇒ void

This method returns an undefined value.

Set packet body

Parameters:

  • str (String)

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

def body=(str)
  last_header.body = str
end

#calcvoid

This method returns an undefined value.

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


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


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


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

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

Parameters:

Returns:

  • (self)

    self with some headers removed

Raises:

Since:

  • 1.1.0


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

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

Raises:

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


152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/packetgen/packet.rb', line 152

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)

309
310
311
312
313
314
315
# File 'lib/packetgen/packet.rb', line 309

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.

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

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

    unknown protocol


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

def is?(protocol)
  klass = check_protocol protocol
  headers.any?(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:


290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/packetgen/packet.rb', line 290

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

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


351
352
353
354
# File 'lib/packetgen/packet.rb', line 351

def reply
  pkt = dup
  pkt.reply!
end

#reply!self

Invert all possible fields in packet to create a reply.

Returns:

  • (self)

Since:

  • 2.7.0


340
341
342
343
344
345
# File 'lib/packetgen/packet.rb', line 340

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

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

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)

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

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) (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


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

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