Packet is the parent class of EthPacket, IPPacket, UDPPacket, TCPPacket, and all other packets. It acts as both a singleton class, so things like Packet.parse can happen, and as an abstract class to provide subclasses some structure.

Methods
#
C
D
F
H
I
K
L
M
N
O
P
R
S
T
W
Attributes
[R] flavor
[RW] headers
[RW] iface
[RW] inspect_style
Class Public methods
can_parse?(str)

Packet subclasses must override this, since the Packet superclass can’t actually parse anything.

# File lib/packetfu/packet.rb, line 281
                def self.can_parse?(str)
                        false
                end
force_binary(str)

Force strings into binary.

# File lib/packetfu/packet.rb, line 21
                def self.force_binary(str)
                        str.force_encoding "binary" if str.respond_to? :force_encoding
                end
inherited(subclass)

Register subclasses in PacketFu.packet_class to do all kinds of neat things that obviates those long if/else trees for parsing. It’s pretty sweet.

# File lib/packetfu/packet.rb, line 16
                def self.inherited(subclass)
                        PacketFu.add_packet_class(subclass)
                end
layer()

Defines the layer this packet type lives at, based on the number of headers it requires. Note that this has little to do with the OSI model, since TCP/IP doesn’t really have Session and Presentation layers.

Ethernet and the like are layer 1, IP, IPv6, and ARP are layer 2, TCP, UDP, and other transport protocols are layer 3, and application protocols are at layer 4 or higher. InvalidPackets have an arbitrary layer 0 to distinguish them.

Because these don’t change much, it’s cheaper just to case through them, and only resort to counting headers if we don’t have a match — this makes adding protocols somewhat easier, but of course you can just override this method over there, too. This is merely optimized for the most likely protocols you see on the Internet.

# File lib/packetfu/packet.rb, line 250
                def self.layer
                        case self.name # Lol ran into case's fancy treatment of classes
                        when /InvalidPacket$/; 0
                        when /EthPacket$/; 1
                        when /IPPacket$/, /ARPPacket$/, /IPv6Packet$/; 2
                        when /TCPPacket$/, /UDPPacket$/, /ICMPPacket$/; 3
                        when /HSRPPacket$/; 4
                        else; self.new.headers.size
                        end
                end
layer_symbol()
# File lib/packetfu/packet.rb, line 265
                def self.layer_symbol
                        case self.layer
                        when 0; :invalid
                        when 1; :link
                        when 2; :internet
                        when 3; :transport
                        else; :application
                        end
                end
new(args={})

the Packet class should not be instantiated directly, since it’s an abstract class that real packet types inherit from. Sadly, this makes the Packet class more difficult to test directly.

# File lib/packetfu/packet.rb, line 466
                def initialize(args={})
                        if self.class.name =~ /(::|^)PacketFu::Packet$/
                                raise NoMethodError, "method `new' called for abstract class #{self.class.name}"
                        end
                        @inspect_style = args[:inspect_style] || PacketFu.inspect_style || :dissect
                        if args[:config]
                                args[:config].each_pair do |k,v|
                                        case k
                                        when :eth_daddr; @eth_header.eth_daddr=v if @eth_header
                                        when :eth_saddr; @eth_header.eth_saddr=v if @eth_header
                                        when :ip_saddr; @ip_header.ip_saddr=v               if @ip_header
                                        when :iface; @iface = v
                                        end
                                end
                        end
                end
parse(packet=nil,args={})

Parse() creates the correct packet type based on the data, and returns the apporpiate Packet subclass object.

There is an assumption here that all incoming packets are either EthPacket or InvalidPacket types. This will be addressed pretty soon.

If application-layer parsing is /not/ desired, that should be indicated explicitly with an argument of :parse_app => false. Otherwise, app-layer parsing will happen.

It is no longer neccisary to manually add packet types here.

# File lib/packetfu/packet.rb, line 35
                def self.parse(packet=nil,args={})
                        parse_app = true if(args[:parse_app].nil? or args[:parse_app])
                        force_binary(packet)
                        if parse_app
                                classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}
                        else
                                classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}.reject {|pclass| pclass.layer_symbol == :application}
                        end
                        p = classes.sort {|x,y| x.layer <=> y.layer}.last.new
                        parsed_packet = p.read(packet,args)
                end
Instance Public methods
==(other)

If two packets are represented as the same binary string, and they’re both actually PacketFu packets of the same sort, they’re equal.

The intuitive result is that a packet of a higher layer (like DNSPacket) can be equal to a packet of a lower level (like UDPPacket) as long as the bytes are equal (this can come up if a transport-layer packet has a hand-crafted payload that is identical to what would have been created by using an application layer packet)

# File lib/packetfu/packet.rb, line 188
                def ==(other)
                        return false unless other.kind_of? self.class
                        return false unless other.respond_to? :to_s
                        self.to_s == other.to_s
                end
clone()

Packets are bundles of lots of objects, so copying them is a little complicated — a dup of a packet is actually full of pass-by-reference stuff in the @headers, so if you change one, you’re changing all this copies, too.

Normally, this doesn’t seem to be a big deal, and it’s a pretty decent performance tradeoff. But, if you’re going to be creating a template packet to base a bunch of slightly different ones off of (like a fuzzer might), you’ll want to use clone()

# File lib/packetfu/packet.rb, line 176
                def clone
                        Packet.parse(self.to_s)
                end
dissect()

Renders the dissection_table suitable for screen printing. Can take one or two arguments. If just the one, only that layer will be displayed take either a range or a number — if a range, only protos within that range will be rendered. If an integer, only that proto will be rendered.

# File lib/packetfu/packet.rb, line 359
                def dissect
                        dtable = self.dissection_table
                        hex_body = nil
                        if dtable.last.kind_of?(Array) and dtable.last.first == :body
                                body = dtable.pop 
                                hex_body = hexify(body[1])
                        end
                        elem_widths = [0,0,0]
                        dtable.each do |proto_table|
                                proto_table[1].each do |elems|
                                        elems.each_with_index do |e,i|
                                                width = e.size
                                                elem_widths[i] = width if width > elem_widths[i]
                                        end
                                end
                        end
                        total_width = elem_widths.inject(0) {|sum,x| sum+x} 
                        table = ""
                        dtable.each do |proto|
                                table << "--"
                                table << proto[0] 
                                if total_width > proto[0].size
                                        table << ("-" * (total_width - proto[0].size + 2))
                                else
                                        table << ("-" * (total_width + 2))
                                end
                                table << "\n"
                                proto[1].each do |elems|
                                        table << "  "
                                        elems_table = []
                                        (0..2).each do |i|
                                                elems_table << ("%-#{elem_widths[i]}s" % elems[i])
                                        end
                                        table << elems_table.join("\s")
                                        table << "\n"
                                end
                        end
                        if hex_body && !hex_body.empty?
                                table << "-" * 66
                                table << "\n"
                                table << "00-01-02-03-04-05-06-07-08-09-0a-0b-0c-0d-0e-0f---0123456789abcdef\n"
                                table << "-" * 66
                                table << "\n"
                                table << hex_body
                        end
                        table
                end
dissection_table()
# File lib/packetfu/packet.rb, line 327
                def dissection_table
                        table = []
                        @headers.each_with_index do |header,table_idx|
                                proto = header.class.name.sub(/^.*::/,"")
                                table << [proto,[]]
                                header.class.members.each do |elem|
                                        elem_sym = elem.to_sym # to_sym needed for 1.8
                                        next if elem_sym == :body 
                                        elem_type_value = []
                                        elem_type_value[0] = elem
                                        readable_element = "#{elem}_readable"
                                        if header.respond_to? readable_element
                                                elem_type_value[1] = header.send(readable_element)
                                        else
                                                elem_type_value[1] = header.send(elem)
                                        end
                                        elem_type_value[2] = header[elem.to_sym].class.name 
                                        table[table_idx][1] << elem_type_value
                                end
                        end
                        table
                        if @headers.last.members.include? :body
                                body_part = [:body, self.payload, @headers.last.body.class.name]
                        end
                        table << body_part
                end
handle_is_identity(ptype)
# File lib/packetfu/packet.rb, line 47
                def handle_is_identity(ptype)
                        idx = PacketFu.packet_prefixes.index(ptype.to_s.downcase)
                        if idx
                                self.kind_of? PacketFu.packet_classes[idx]
                        else
                                raise NoMethodError, "Undefined method `is_#{ptype}?' for #{self.class}."
                        end
                end
hexify(str)

Hexify provides a neatly-formatted dump of binary data, familar to hex readers.

# File lib/packetfu/packet.rb, line 286
                def hexify(str)
                        str.force_encoding("ASCII-8BIT") if str.respond_to? :force_encoding
                        hexascii_lines = str.to_s.unpack("H*")[0].scan(/.{1,32}/)
                        regex = Regexp.new('[\x00-\x1f\x7f-\xff]', nil, 'n')
                        chars = str.to_s.gsub(regex,'.')
                        chars_lines = chars.scan(/.{1,16}/)
                        ret = []
                        hexascii_lines.size.times {|i| ret << "%-48s  %s" % [hexascii_lines[i].gsub(/(.{2})/,"\\1 "),chars_lines[i]]}
                        ret.join("\n")
                end
inspect()

For packets, inspect is overloaded as inspect_hex(0). Not sure if this is a great idea yet, but it sure makes the irb output more sane.

If you hate this, you can run PacketFu.toggle_inspect to return to the typical (and often unreadable) Object#inspect format.

# File lib/packetfu/packet.rb, line 428
                def inspect
                        case @inspect_style
                        when :dissect
                                self.dissect
                        when :hex
                                self.proto.join("|") + "\n" + self.inspect_hex
                        else
                                super
                        end
                end
inspect_hex(arg=0)

If @inspect_style is :default (or :ugly), the inspect output is the usual inspect.

If @inspect_style is :hex (or :pretty), the inspect output is a much more compact hexdump-style, with a shortened set of packet header names at the top.

If @inspect_style is :dissect (or :verbose), the inspect output is the longer, but more readable, dissection of the packet. This is the default.

TODO: Have an option for colors. Everyone loves colorized irb output.

# File lib/packetfu/packet.rb, line 308
                def inspect_hex(arg=0)
                        case arg
                        when :layers
                                ret = []
                                @headers.size.times do |i|
                                        ret << hexify(@headers[i])
                                end
                                ret
                        when (0..9)
                                if @headers[arg]
                                        hexify(@headers[arg])
                                else
                                        nil
                                end
                        when :all
                                inspect_hex(0)
                        end
                end
inspect_style=()

Delegate to PacketFu’s inspect_style, since the class variable name is the same. Yay for namespace pollution!

# File lib/packetfu/packet.rb, line 486
                def inspect_style=()
                        PacketFu.inspect_style(arg)
                end
kind_of?(klass)
This method is also aliased as orig_kind_of?
# File lib/packetfu/packet.rb, line 409
                def kind_of?(klass)
                        return true if orig_kind_of? klass
                        packet_types = proto.map {|p| PacketFu.const_get("#{p}Packet")}
                        match = false
                        packet_types.each do |p|
                                if p.ancestors.include? klass
                                        match =  true
                                        break
                                end
                        end
                        return match
                end
layer()
# File lib/packetfu/packet.rb, line 261
                def layer
                        self.class.layer
                end
layer_symbol()
# File lib/packetfu/packet.rb, line 275
                def layer_symbol
                        self.class.layer_symbol
                end
length()

Alias for size

method_missing(sym, *args, &block)

method_missing() delegates protocol-specific field actions to the apporpraite class variable (which contains the associated packet type) This register-of-protocols style switch will work for the forseeable future (there aren’t /that/ many packet types), and it’s a handy way to know at a glance what packet types are supported.

# File lib/packetfu/packet.rb, line 495
                def method_missing(sym, *args, &block)
                        case sym.to_s
                        when /^is_([a-zA-Z0-9]+)\?/
                                ptype = $1
                                if PacketFu.packet_prefixes.index(ptype)
                                        self.send(:handle_is_identity, $1)
                                else
                                        super
                                end
                        when /^([a-zA-Z0-9]+)_.+/
                                ptype = $1
                                if PacketFu.packet_prefixes.index(ptype)
                                        self.instance_variable_get("@#{ptype}_header").send(sym,*args, &block)
                                else
                                        super
                                end
                        else
                                super
                        end
                end
orig_kind_of?(klass)

Alias for kind_of?

payload()

Get the outermost payload (body) of the packet; this is why all packet headers should have a body type.

# File lib/packetfu/packet.rb, line 68
                def payload
                        @headers.last.body
                end
payload=(args)

Set the outermost payload (body) of the packet.

# File lib/packetfu/packet.rb, line 73
                def payload=(args)
                        @headers.last.body=(args)
                end
peek(args={})

Peek provides summary data on packet contents.

Each packet type should provide a peek_format.

# File lib/packetfu/packet.rb, line 197
                def peek(args={})
                        idx = @headers.reverse.map {|h| h.respond_to? peek_format}.index(true)
                        if idx
                                @headers.reverse[idx].peek_format
                        else
                                peek_format
                        end
                end
peek_format()

The peek_format is used to display a single line of packet data useful for eyeballing. It should not exceed 80 characters. The Packet superclass defines an example peek_format, but it should hardly ever be triggered, since peek traverses the @header list in reverse to find a suitable format.

Format

  * A one or two character protocol initial. It should be unique
  * The packet size
  * Useful data in a human-usable form.

Ideally, related peek_formats will all line up with each other when printed to the screen.

Example

   tcp_packet.peek
   #=> "T  1054 10.10.10.105:55000   ->   192.168.145.105:80 [......] S:adc7155b|I:8dd0"
   tcp_packet.peek.size
   #=> 79
# File lib/packetfu/packet.rb, line 229
                def peek_format
                        peek_data = ["?  "]
                        peek_data << "%-5d" % self.to_s.size
                        peek_data << "%68s" % self.to_s[0,34].unpack("H*")[0]
                        peek_data.join
                end
proto()

Returns an array of protocols contained in this packet. For example:

  t = PacketFu::TCPPacket.new
  => 00 1a c5 00 00 00 00 1a c5 00 00 00 08 00 45 00   ..............E.
  00 28 3c ab 00 00 ff 06 7f 25 00 00 00 00 00 00   .(<......%......
  00 00 93 5e 00 00 ad 4f e4 a4 00 00 00 00 50 00   ...^...O......P.
  40 00 4a 92 00 00                                 @.J...
  t.proto
  => ["Eth", "IP", "TCP"]
This method is also aliased as protocol
# File lib/packetfu/packet.rb, line 454
                def proto
                        type_array = []
                        self.headers.each {|header| type_array << header.class.to_s.split('::').last.gsub(/Header$/,'')}
                        type_array
                end
protocol()

Alias for proto

read(args={})

Read() takes (and trusts) the io input and shoves it all into a well-formed Packet. Note that read is a destructive process, so any existing data will be lost.

A note on the :strip => true argument: If :strip is set, defined lengths of data will be believed, and any trailers (such as frame check sequences) will be chopped off. This helps to ensure well-formed packets, at the cost of losing perhaps important FCS data.

If :strip is false, header lengths are /not/ believed, and all data will be piped in. When capturing from the wire, this is usually fine, but recalculating the length before saving or re-transmitting will absolutely change the data payload; FCS data will become part of the TCP data as far as tcp_len is concerned. Some effort has been made to preserve the “real” payload for the purposes of checksums, but currently, it’s impossible to seperate new payload data from old trailers, so things like pkt.payload += “some data” will not work correctly.

So, to summarize; if you intend to alter the data, use :strip. If you don’t, don’t. Also, this is a horrid hack. Stripping is useful (and fun!), but the default behavior really should be to create payloads correctly, and /not/ treat extra FCS data as a payload.

Finally, packet subclasses should take two arguments: the string that is the data to be transmuted into a packet, as well as args. This superclass method is merely concerned with handling args common to many packet formats (namely, fixing packets on the fly)

# File lib/packetfu/packet.rb, line 158
                def read(args={})
                        if args[:fix] || args[:recalc]
                                ip_recalc(:ip_sum) if self.is_ip?
                                recalc(:tcp) if self.is_tcp?
                                recalc(:udp) if self.is_udp?
                        end
                end
recalc(arg=:all)

Recalculates all the calcuated fields for all headers in the packet. This is important since read() wipes out all the calculated fields such as length and checksum and what all.

# File lib/packetfu/packet.rb, line 114
                def recalc(arg=:all)
                        case arg
                        when :ip
                                ip_recalc(:all)
                        when :icmp
                                icmp_recalc(:all)
                        when :udp
                                udp_recalc(:all)
                        when :tcp
                                tcp_recalc(:all)
                        when :all
                                ip_recalc(:all) if @ip_header
                                icmp_recalc(:all) if @icmp_header
                                udp_recalc(:all) if @udp_header
                                tcp_recalc(:all) if @tcp_header
                        else
                                raise ArgumentError, "Recalculating `#{arg}' unsupported. Try :all"
                        end
                        @headers[0]
                end
respond_to?(sym, include_private = false)
# File lib/packetfu/packet.rb, line 516
                def respond_to?(sym, include_private = false)
                        if sym.to_s =~ /^(invalid|eth|arp|ip|icmp|udp|hsrp|tcp|ipv6)_/
                                self.instance_variable_get("@#{$1}_header").respond_to? sym
                        elsif sym.to_s =~ /^is_([a-zA-Z0-9]+)\?/
                                if PacketFu.packet_prefixes.index($1)
                                        true
                                else
                                        super
                                end
                        else
                                super
                        end
                end
size()

Returns the size of the packet (as a binary string)

This method is also aliased as length
# File lib/packetfu/packet.rb, line 440
                def size
                        self.to_s.size
                end
to_f(filename=nil,mode='w')

Put the entire packet into a libpcap file. XXX: this is a hack for now just to confirm that packets are getting created correctly. Now with append! XXX: Document this!

# File lib/packetfu/packet.rb, line 89
                def to_f(filename=nil,mode='w')
                        filename ||= 'out.pcap'
                        mode = mode.to_s[0,1] + "b"
                        raise ArgumentError, "Unknown mode: #{mode.to_s}" unless mode =~ /^[wa]/
                        if(mode == 'w' || !(File.exists?(filename)))
                                data = [PcapHeader.new, self.to_pcap].map {|x| x.to_s}.join
                        else
                                data = self.to_pcap
                        end
                        File.open(filename, mode) {|f| f.write data}
                        return [filename, 1, data.size]
                end
to_pcap(args={})

Converts a packet to libpcap format. Bit of a hack?

# File lib/packetfu/packet.rb, line 78
                def to_pcap(args={})
                        p = PcapPacket.new(:endian => args[:endian],
                                                                                                :timestamp => Timestamp.new.to_s,
                                                                                                :incl_len => self.to_s.size,
                                                                                                :orig_len => self.to_s.size,
                                                                                                :data => self)
                end
to_s()

Get the binary string of the entire packet.

# File lib/packetfu/packet.rb, line 57
                def to_s
                        @headers[0].to_s
                end
to_w(iface=nil)

Put the entire packet on the wire by creating a temporary PacketFu::Inject object. TODO: Do something with auto-checksumming?

# File lib/packetfu/packet.rb, line 104
                def to_w(iface=nil)
                        iface = (iface || self.iface || PacketFu::Config.new.config[:iface]).to_s
                        inj = PacketFu::Inject.new(:iface => iface)
                        inj.array = [@headers[0].to_s]
                        inj.inject
                end
write(io)

In the event of no proper decoding, at least send it to the inner-most header.

# File lib/packetfu/packet.rb, line 62
                def write(io)
                        @headers[0].write(io)
                end