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('eth0') do |packet|
  do_some_stuffs
end
packets = Packet.capture('eth0', max: 5)  # get 5 packets

Packets may also be read from a file:

packets = Packet.read(file.pcapng)

Save packets to a file

Packet.write 'file.pcapng', packets

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePacket

Returns a new instance of Packet.



160
161
162
# File 'lib/packetgen/packet.rb', line 160

def initialize
  @headers = []
end

Instance Attribute Details

#headersArray<Header::Base] (readonly)

Returns Array<Header::Base].

Returns:

  • (Array<Header::Base])

    Array<Header::Base]



48
49
50
# File 'lib/packetgen/packet.rb', line 48

def headers
  @headers
end

Class Method Details

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

Capture packets from iface

Parameters:

  • iface (String)

    interface name

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

    capture options

Options Hash (options):

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



128
129
130
131
132
133
134
135
136
# File 'lib/packetgen/packet.rb', line 128

def self.capture(iface, options={})
  capture = Capture.new(iface, 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:



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

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



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/packetgen/packet.rb', line 67

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

  if first_header.nil?
    # No decoding forced for first header. Have to guess it!
    Header.all.each do |hklass|
      hdr = hklass.new
      hdr.read binary_str
      # First header is found when:
      # * for one known header,
      # * it exists a known binding with a upper header
      hklass.known_headers.each do |nh, bindings|
        bindings.each do |binding|
          if hdr.send(binding.key) == binding.value
            first_header = hklass.to_s.gsub(/.*::/, '')
            break
          end
          break unless first_header.nil?
        end
      end
      break unless first_header.nil?
    end
    if first_header.nil?
      raise ParseError, 'cannot identify first header in string'
    end
  end

  pkt.add(first_header)
  pkt.headers.last.read binary_str

  # Decode upper headers recursively
  decode_packet_bottom_up = true
  while decode_packet_bottom_up do
    last_known_hdr = pkt.headers.last
    last_known_hdr.class.known_headers.each do |nh, bindings|
      bindings.each do |binding|
        if last_known_hdr.send(binding.key) == binding.value
          str = last_known_hdr.body
          pkt.add nh.to_s.gsub(/.*::/, '')
          pkt.headers.last.read str
          break
        end
      end
      break unless last_known_hdr == pkt.headers.last
    end
    decode_packet_bottom_up = (pkt.headers.last != last_known_hdr)
  end

  pkt
end

.read(filename) ⇒ Array<Packet>

Read packets from filename.

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

Parameters:

  • filename (String)

    PcapNG file

Returns:



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

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



153
154
155
156
157
# File 'lib/packetgen/packet.rb', line 153

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)


288
289
290
# File 'lib/packetgen/packet.rb', line 288

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



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

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

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

#bodyStructFu

Get packet body

Returns:



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

def body
  @headers.last.body
end

#body=(str) ⇒ void

This method returns an undefined value.

Set packet body

Parameters:

  • str (String)


217
218
219
# File 'lib/packetgen/packet.rb', line 217

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

#calcvoid

This method returns an undefined value.

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



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

def calc
  calc_length
  calc_checksum
end

#calc_checksumvoid

This method returns an undefined value.

Recalculate all packet checksums



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

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



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

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



263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/packetgen/packet.rb', line 263

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)
    add_header(next_header, prev_header) if prev_header and next_header
  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



253
254
255
# File 'lib/packetgen/packet.rb', line 253

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

#inspectString

Returns:

  • (String)


278
279
280
281
282
283
284
# File 'lib/packetgen/packet.rb', line 278

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



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

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

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

Write a PCapNG file to disk.

Parameters:

  • filename (String)

Returns:

See Also:

  • File


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

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

#to_sString

Get binary string

Returns:

  • (String)


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

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



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

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