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

Constant Summary collapse

INSPECT_MAX_WIDTH =
70

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePacket

Returns a new instance of Packet.



152
153
154
# File 'lib/packetgen/packet.rb', line 152

def initialize
  @headers = []
end

Instance Attribute Details

#headersArray<Header::Base] (readonly)

Returns Array<Header::Base].

Returns:

  • (Array<Header::Base])

    Array<Header::Base]



43
44
45
# File 'lib/packetgen/packet.rb', line 43

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



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

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:



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

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



65
66
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
# File 'lib/packetgen/packet.rb', line 65

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, binding|
        if hdr.send(binding.key) == binding.value
          first_header = hklass.to_s.gsub(/.*::/, '')
          break
        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, 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
    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:



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

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



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

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)


274
275
276
# File 'lib/packetgen/packet.rb', line 274

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



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/packetgen/packet.rb', line 161

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

  header = klass.new(options)
  prev_header = @headers.last
  if prev_header
    binding = prev_header.class.known_headers[klass]
    if binding.nil?
      msg = "#{prev_header.class} knowns no layer association with #{protocol}. "
      msg << "Try #{prev_header.class}.bind_layer(PacketGen::Header::#{protocol}, "
      msg << "#{prev_header.class.to_s.gsub(/(.*)::/, '').downcase}_proto_field: "
      msg << "value_for_#{protocol.downcase})"
      raise ArgumentError, msg
    end
    prev_header[binding.key].read binding.value
    prev_header.body = header
  end
  header.packet = self
  @headers << header
  unless respond_to? protocol.downcase
    self.class.class_eval "def #{protocol.downcase}(arg=nil);" \
                          "header('#{protocol}', arg); end"
  end
  self
end

#bodyStructFu

Get packet body

Returns:



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

def body
  @headers.last.body
end

#body=(str) ⇒ void

This method returns an undefined value.

Set packet body

Parameters:

  • (String)


227
228
229
# File 'lib/packetgen/packet.rb', line 227

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

#calcvoid

This method returns an undefined value.

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



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

def calc
  calc_length
  calc_sum
end

#calc_lengthvoid

This method returns an undefined value.

Recalculate all packet length fields



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

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

#calc_sumvoid

This method returns an undefined value.

Recalculate all packet checksums



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

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

#inspectString

Returns:

  • (String)


260
261
262
263
264
265
266
267
268
269
270
# File 'lib/packetgen/packet.rb', line 260

def inspect
  str = dashed_line(self.class)
  @headers.each do |header|
    str << dashed_line(header.class, 2)
    header.to_h.each do |attr, value|
      next if attr == :body
      str << inspect_line(attr, value, 2)
    end
  end
  str << inspect_body
end

#is?(protocol) ⇒ Boolean

Check if a protocol header is embedded in packet

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

    unknown protocol



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

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


241
242
243
# File 'lib/packetgen/packet.rb', line 241

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

#to_sString

Get binary string

Returns:

  • (String)


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

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



249
250
251
252
253
254
255
256
257
# File 'lib/packetgen/packet.rb', line 249

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.class.to_s.gsub(/.*::/, '')
    raise WireError, "don't known how to send a #{type} packet on wire"
  end
end