packetfu 1.1.9 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. data/bench/octets.rb +9 -9
  2. data/examples/100kpackets.rb +12 -12
  3. data/examples/ackscan.rb +16 -16
  4. data/examples/arp.rb +35 -35
  5. data/examples/arphood.rb +36 -36
  6. data/examples/dissect_thinger.rb +6 -6
  7. data/examples/new-simple-stats.rb +23 -23
  8. data/examples/packetfu-shell.rb +25 -25
  9. data/examples/simple-sniffer.rb +9 -9
  10. data/examples/simple-stats.rb +23 -23
  11. data/examples/slammer.rb +3 -3
  12. data/lib/packetfu.rb +127 -127
  13. data/lib/packetfu/capture.rb +169 -169
  14. data/lib/packetfu/config.rb +52 -52
  15. data/lib/packetfu/inject.rb +56 -56
  16. data/lib/packetfu/packet.rb +528 -528
  17. data/lib/packetfu/pcap.rb +579 -579
  18. data/lib/packetfu/protos/arp.rb +90 -90
  19. data/lib/packetfu/protos/arp/header.rb +158 -158
  20. data/lib/packetfu/protos/arp/mixin.rb +36 -36
  21. data/lib/packetfu/protos/eth.rb +44 -44
  22. data/lib/packetfu/protos/eth/header.rb +243 -243
  23. data/lib/packetfu/protos/eth/mixin.rb +3 -3
  24. data/lib/packetfu/protos/hsrp.rb +69 -69
  25. data/lib/packetfu/protos/hsrp/header.rb +107 -107
  26. data/lib/packetfu/protos/hsrp/mixin.rb +29 -29
  27. data/lib/packetfu/protos/icmp.rb +71 -71
  28. data/lib/packetfu/protos/icmp/header.rb +82 -82
  29. data/lib/packetfu/protos/icmp/mixin.rb +14 -14
  30. data/lib/packetfu/protos/invalid.rb +49 -49
  31. data/lib/packetfu/protos/ip.rb +69 -69
  32. data/lib/packetfu/protos/ip/header.rb +291 -291
  33. data/lib/packetfu/protos/ip/mixin.rb +40 -40
  34. data/lib/packetfu/protos/ipv6.rb +50 -50
  35. data/lib/packetfu/protos/ipv6/header.rb +188 -188
  36. data/lib/packetfu/protos/ipv6/mixin.rb +29 -29
  37. data/lib/packetfu/protos/tcp.rb +176 -176
  38. data/lib/packetfu/protos/tcp/ecn.rb +35 -35
  39. data/lib/packetfu/protos/tcp/flags.rb +74 -74
  40. data/lib/packetfu/protos/tcp/header.rb +268 -268
  41. data/lib/packetfu/protos/tcp/hlen.rb +32 -32
  42. data/lib/packetfu/protos/tcp/mixin.rb +46 -46
  43. data/lib/packetfu/protos/tcp/option.rb +321 -321
  44. data/lib/packetfu/protos/tcp/options.rb +95 -95
  45. data/lib/packetfu/protos/tcp/reserved.rb +35 -35
  46. data/lib/packetfu/protos/udp.rb +116 -116
  47. data/lib/packetfu/protos/udp/header.rb +91 -91
  48. data/lib/packetfu/protos/udp/mixin.rb +3 -3
  49. data/lib/packetfu/structfu.rb +280 -280
  50. data/lib/packetfu/utils.rb +226 -217
  51. data/lib/packetfu/version.rb +41 -41
  52. data/packetfu.gemspec +2 -1
  53. data/spec/ethpacket_spec.rb +48 -48
  54. data/spec/packet_spec.rb +57 -57
  55. data/spec/packet_subclasses_spec.rb +8 -8
  56. data/spec/packetfu_spec.rb +59 -59
  57. data/spec/structfu_spec.rb +268 -268
  58. data/spec/tcp_spec.rb +75 -75
  59. data/test/all_tests.rb +13 -13
  60. data/test/func_lldp.rb +3 -3
  61. data/test/ptest.rb +2 -2
  62. data/test/test_arp.rb +116 -116
  63. data/test/test_capture.rb +45 -45
  64. data/test/test_eth.rb +68 -68
  65. data/test/test_hsrp.rb +9 -9
  66. data/test/test_icmp.rb +52 -52
  67. data/test/test_inject.rb +18 -18
  68. data/test/test_invalid.rb +16 -16
  69. data/test/test_ip.rb +36 -36
  70. data/test/test_ip6.rb +48 -48
  71. data/test/test_octets.rb +21 -21
  72. data/test/test_packet.rb +154 -154
  73. data/test/test_pcap.rb +170 -170
  74. data/test/test_structfu.rb +97 -97
  75. data/test/test_tcp.rb +320 -320
  76. data/test/test_udp.rb +76 -76
  77. metadata +4 -3
@@ -1,59 +1,59 @@
1
1
  # -*- coding: binary -*-
2
2
  module PacketFu
3
3
 
4
- # The Config class holds various bits of useful default information
5
- # for packet creation. If initialized without arguments, @iface will be
6
- # set to ENV['IFACE'] or Pcap.lookupdev (or lo), and the @pcapfile will
7
- # be set to "/tmp/out.pcap" # (yes, it's Linux-biased, sorry, fixing
8
- # this is a TODO.)
9
- #
10
- # Any number of instance variables can be passed in to the intialize function (as a
11
- # hash), though only the expected network-related variables will be readable and
12
- # writeable directly.
13
- #
14
- # == Examples
15
- #
16
- # PacketFu::Config.new(:ip_saddr => "1.2.3.4").ip_saddr #=> "1.2.3.4"
17
- # PacketFu::Config.new(:foo=>"bar").foo #=> NomethodError: undefined method `foo'...
18
- #
19
- # The config() function, however, does provide access to custom variables:
20
- #
21
- # PacketFu::Config.new(:foo=>"bar").config[:foo] #=> "bar"
22
- # obj = PacketFu::Config.new(:foo=>"bar")
23
- # obj.config(:baz => "bat")
24
- # obj.config #=> {:iface=>"eth0", :baz=>"bat", :pcapfile=>"/tmp/out.pcap", :foo=>"bar"}
25
- class Config
26
- attr_accessor :eth_saddr, # The discovered eth_saddr
27
- :eth_daddr, # The discovered eth_daddr (ie, the gateway)
28
- :eth_src, # The discovered eth_src in binary form.
29
- :eth_dst, # The discovered eth_dst (gateway) in binary form.
30
- :ip_saddr, # The discovered ip_saddr
31
- :ip_src, # The discovered ip_src in binary form.
32
- :iface, # The declared interface.
33
- :pcapfile # A declared default file to write to.
34
-
35
- def initialize(args={})
36
- if Process.euid.zero?
37
- @iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
38
- end
39
- @pcapfile = "/tmp/out.pcap"
40
- args.each_pair { |k,v| self.instance_variable_set(("@#{k}"),v) }
41
- end
4
+ # The Config class holds various bits of useful default information
5
+ # for packet creation. If initialized without arguments, @iface will be
6
+ # set to ENV['IFACE'] or Pcap.lookupdev (or lo), and the @pcapfile will
7
+ # be set to "/tmp/out.pcap" # (yes, it's Linux-biased, sorry, fixing
8
+ # this is a TODO.)
9
+ #
10
+ # Any number of instance variables can be passed in to the intialize function (as a
11
+ # hash), though only the expected network-related variables will be readable and
12
+ # writeable directly.
13
+ #
14
+ # == Examples
15
+ #
16
+ # PacketFu::Config.new(:ip_saddr => "1.2.3.4").ip_saddr #=> "1.2.3.4"
17
+ # PacketFu::Config.new(:foo=>"bar").foo #=> NomethodError: undefined method `foo'...
18
+ #
19
+ # The config() function, however, does provide access to custom variables:
20
+ #
21
+ # PacketFu::Config.new(:foo=>"bar").config[:foo] #=> "bar"
22
+ # obj = PacketFu::Config.new(:foo=>"bar")
23
+ # obj.config(:baz => "bat")
24
+ # obj.config #=> {:iface=>"eth0", :baz=>"bat", :pcapfile=>"/tmp/out.pcap", :foo=>"bar"}
25
+ class Config
26
+ attr_accessor :eth_saddr, # The discovered eth_saddr
27
+ :eth_daddr, # The discovered eth_daddr (ie, the gateway)
28
+ :eth_src, # The discovered eth_src in binary form.
29
+ :eth_dst, # The discovered eth_dst (gateway) in binary form.
30
+ :ip_saddr, # The discovered ip_saddr
31
+ :ip_src, # The discovered ip_src in binary form.
32
+ :iface, # The declared interface.
33
+ :pcapfile # A declared default file to write to.
34
+
35
+ def initialize(args={})
36
+ if Process.euid.zero?
37
+ @iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
38
+ end
39
+ @pcapfile = "/tmp/out.pcap"
40
+ args.each_pair { |k,v| self.instance_variable_set(("@#{k}"),v) }
41
+ end
42
42
 
43
- # Returns all instance variables as a hash (including custom variables set at initialization).
44
- def config(arg=nil)
45
- if arg
46
- arg.each_pair {|k,v| self.instance_variable_set(("@" + k.to_s).intern, v)}
47
- else
48
- config_hash = {}
49
- self.instance_variables.each do |v|
50
- key = v.to_s.gsub(/^@/,"").to_sym
51
- config_hash[key] = self.instance_variable_get(v)
52
- end
53
- config_hash
54
- end
55
- end
43
+ # Returns all instance variables as a hash (including custom variables set at initialization).
44
+ def config(arg=nil)
45
+ if arg
46
+ arg.each_pair {|k,v| self.instance_variable_set(("@" + k.to_s).intern, v)}
47
+ else
48
+ config_hash = {}
49
+ self.instance_variables.each do |v|
50
+ key = v.to_s.gsub(/^@/,"").to_sym
51
+ config_hash[key] = self.instance_variable_get(v)
52
+ end
53
+ config_hash
54
+ end
55
+ end
56
56
 
57
- end
57
+ end
58
58
 
59
59
  end
@@ -1,66 +1,66 @@
1
1
  # -*- coding: binary -*-
2
2
  module PacketFu
3
3
 
4
- # The Inject class handles injecting arrays of binary data on the wire.
5
- #
6
- # To inject single packets, use PacketFu::Packet.to_w() instead.
7
- class Inject
8
- attr_accessor :array, :stream, :show_live # Leave these public and open.
9
- attr_reader :iface, :snaplen, :promisc, :timeout # Cant change after the init.
4
+ # The Inject class handles injecting arrays of binary data on the wire.
5
+ #
6
+ # To inject single packets, use PacketFu::Packet.to_w() instead.
7
+ class Inject
8
+ attr_accessor :array, :stream, :show_live # Leave these public and open.
9
+ attr_reader :iface, :snaplen, :promisc, :timeout # Cant change after the init.
10
10
 
11
- def initialize(args={})
12
- @array = [] # Where the packet array goes.
13
- @stream = [] # Where the stream goes.
14
- @iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
15
- @snaplen = args[:snaplen] || 0xffff
16
- @promisc = args[:promisc] || false # Sensible for some Intel wifi cards
17
- @timeout = args[:timeout] || 1
18
- @show_live = nil
19
- end
11
+ def initialize(args={})
12
+ @array = [] # Where the packet array goes.
13
+ @stream = [] # Where the stream goes.
14
+ @iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
15
+ @snaplen = args[:snaplen] || 0xffff
16
+ @promisc = args[:promisc] || false # Sensible for some Intel wifi cards
17
+ @timeout = args[:timeout] || 1
18
+ @show_live = nil
19
+ end
20
20
 
21
- # Takes an array, and injects them onto an interface. Note that
22
- # complete packets (Ethernet headers on down) are expected.
23
- #
24
- # === Parameters
25
- #
26
- # :array || arr
27
- # An array of binary data (usually packet.to_s style).
28
- # :int || sleep
29
- # Number of seconds to sleep between injections (in float format)
30
- # :show_live || :live
31
- # If true, puts data about what was injected to stdout.
32
- #
33
- # === Example
34
- #
35
- # inj = PacketFu::Inject.new
36
- # inj.array_to_wire(:array => [pkt1, pkt2, pkt3], :sleep => 0.1)
37
- #
38
- def array_to_wire(args={})
39
- pkt_array = args[:array] || args[:arr] || @array
40
- interval = args[:int] || args[:sleep]
41
- show_live = args[:show_live] || args[:live] || @show_live
21
+ # Takes an array, and injects them onto an interface. Note that
22
+ # complete packets (Ethernet headers on down) are expected.
23
+ #
24
+ # === Parameters
25
+ #
26
+ # :array || arr
27
+ # An array of binary data (usually packet.to_s style).
28
+ # :int || sleep
29
+ # Number of seconds to sleep between injections (in float format)
30
+ # :show_live || :live
31
+ # If true, puts data about what was injected to stdout.
32
+ #
33
+ # === Example
34
+ #
35
+ # inj = PacketFu::Inject.new
36
+ # inj.array_to_wire(:array => [pkt1, pkt2, pkt3], :sleep => 0.1)
37
+ #
38
+ def array_to_wire(args={})
39
+ pkt_array = args[:array] || args[:arr] || @array
40
+ interval = args[:int] || args[:sleep]
41
+ show_live = args[:show_live] || args[:live] || @show_live
42
42
 
43
- @stream = Pcap.open_live(@iface,@snaplen,@promisc,@timeout)
44
- pkt_count = 0
45
- pkt_array.each do |pkt|
46
- @stream.inject(pkt)
47
- sleep interval if interval
48
- pkt_count +=1
49
- puts "Sent Packet \##{pkt_count} (#{pkt.size})" if show_live
50
- end
51
- # Return # of packets sent, array size, and array total size
52
- [pkt_count, pkt_array.size, pkt_array.join.size]
53
- end
43
+ @stream = Pcap.open_live(@iface,@snaplen,@promisc,@timeout)
44
+ pkt_count = 0
45
+ pkt_array.each do |pkt|
46
+ @stream.inject(pkt)
47
+ sleep interval if interval
48
+ pkt_count +=1
49
+ puts "Sent Packet \##{pkt_count} (#{pkt.size})" if show_live
50
+ end
51
+ # Return # of packets sent, array size, and array total size
52
+ [pkt_count, pkt_array.size, pkt_array.join.size]
53
+ end
54
54
 
55
- # Equivalent to array_to_wire
56
- def a2w(args={})
57
- array_to_wire(args)
58
- end
55
+ # Equivalent to array_to_wire
56
+ def a2w(args={})
57
+ array_to_wire(args)
58
+ end
59
59
 
60
- # Equivalent to array_to_wire
61
- def inject(args={})
62
- array_to_wire(args)
63
- end
60
+ # Equivalent to array_to_wire
61
+ def inject(args={})
62
+ array_to_wire(args)
63
+ end
64
64
 
65
- end
65
+ end
66
66
  end
@@ -1,534 +1,534 @@
1
1
  # -*- coding: binary -*-
2
2
  module PacketFu
3
3
 
4
- # Packet is the parent class of EthPacket, IPPacket, UDPPacket, TCPPacket, and all
5
- # other packets. It acts as both a singleton class, so things like
6
- # Packet.parse can happen, and as an abstract class to provide
7
- # subclasses some structure.
8
- class Packet
9
-
10
- attr_reader :flavor # Packet Headers are responsible for their own specific flavor methods.
11
- attr_accessor :headers # All packets have a header collection, useful for determining protocol trees.
12
- attr_accessor :iface # Default inferface to send packets to
13
- attr_accessor :inspect_style # Default is :dissect, can also be :hex or :default
14
-
15
- # Register subclasses in PacketFu.packet_class to do all kinds of neat things
16
- # that obviates those long if/else trees for parsing. It's pretty sweet.
17
- def self.inherited(subclass)
18
- PacketFu.add_packet_class(subclass)
19
- end
20
-
21
- # Force strings into binary.
22
- def self.force_binary(str)
23
- str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding
24
- end
25
-
26
- # Parse() creates the correct packet type based on the data, and returns the apporpiate
27
- # Packet subclass object.
28
- #
29
- # There is an assumption here that all incoming packets are either EthPacket
30
- # or InvalidPacket types. This will be addressed pretty soon.
31
- #
32
- # If application-layer parsing is /not/ desired, that should be indicated explicitly
33
- # with an argument of :parse_app => false. Otherwise, app-layer parsing will happen.
34
- #
35
- # It is no longer neccisary to manually add packet types here.
36
- def self.parse(packet=nil,args={})
37
- parse_app = true if(args[:parse_app].nil? or args[:parse_app])
38
- force_binary(packet)
39
- if parse_app
40
- classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}
41
- else
42
- classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}.reject {|pclass| pclass.layer_symbol == :application}
43
- end
44
- p = classes.sort {|x,y| x.layer <=> y.layer}.last.new
45
- parsed_packet = p.read(packet,args)
46
- end
47
-
48
- def handle_is_identity(ptype)
49
- idx = PacketFu.packet_prefixes.index(ptype.to_s.downcase)
50
- if idx
51
- self.kind_of? PacketFu.packet_classes[idx]
52
- else
53
- raise NoMethodError, "Undefined method `is_#{ptype}?' for #{self.class}."
54
- end
55
- end
56
-
57
- # Get the binary string of the entire packet.
58
- def to_s
59
- @headers[0].to_s
60
- end
61
-
62
- # In the event of no proper decoding, at least send it to the inner-most header.
63
- def write(io)
64
- @headers[0].write(io)
65
- end
66
-
67
- # Get the outermost payload (body) of the packet; this is why all packet headers
68
- # should have a body type.
69
- def payload
70
- @headers.last.body
71
- end
72
-
73
- # Set the outermost payload (body) of the packet.
74
- def payload=(args)
75
- @headers.last.body=(args)
76
- end
77
-
78
- # Converts a packet to libpcap format. Bit of a hack?
79
- def to_pcap(args={})
80
- p = PcapPacket.new(:endian => args[:endian],
81
- :timestamp => Timestamp.new.to_s,
82
- :incl_len => self.to_s.size,
83
- :orig_len => self.to_s.size,
84
- :data => self)
85
- end
86
-
87
- # Put the entire packet into a libpcap file. XXX: this is a
88
- # hack for now just to confirm that packets are getting created
89
- # correctly. Now with append! XXX: Document this!
90
- def to_f(filename=nil,mode='w')
91
- filename ||= 'out.pcap'
92
- mode = mode.to_s[0,1] + "b"
93
- raise ArgumentError, "Unknown mode: #{mode.to_s}" unless mode =~ /^[wa]/
94
- if(mode == 'w' || !(File.exists?(filename)))
95
- data = [PcapHeader.new, self.to_pcap].map {|x| x.to_s}.join
96
- else
97
- data = self.to_pcap
98
- end
99
- File.open(filename, mode) {|f| f.write data}
100
- return [filename, 1, data.size]
101
- end
102
-
103
- # Put the entire packet on the wire by creating a temporary PacketFu::Inject object.
104
- # TODO: Do something with auto-checksumming?
105
- def to_w(iface=nil)
106
- iface = (iface || self.iface || PacketFu::Config.new.config[:iface]).to_s
107
- inj = PacketFu::Inject.new(:iface => iface)
108
- inj.array = [@headers[0].to_s]
109
- inj.inject
110
- end
111
-
112
- # Recalculates all the calcuated fields for all headers in the packet.
113
- # This is important since read() wipes out all the calculated fields
114
- # such as length and checksum and what all.
115
- def recalc(arg=:all)
116
- case arg
117
- when :ip
118
- ip_recalc(:all)
119
- when :icmp
120
- icmp_recalc(:all)
121
- when :udp
122
- udp_recalc(:all)
123
- when :tcp
124
- tcp_recalc(:all)
125
- when :all
126
- ip_recalc(:all) if @ip_header
127
- icmp_recalc(:all) if @icmp_header
128
- udp_recalc(:all) if @udp_header
129
- tcp_recalc(:all) if @tcp_header
130
- else
131
- raise ArgumentError, "Recalculating `#{arg}' unsupported. Try :all"
132
- end
133
- @headers[0]
134
- end
135
-
136
- # Read() takes (and trusts) the io input and shoves it all into a well-formed Packet.
137
- # Note that read is a destructive process, so any existing data will be lost.
138
- #
139
- # A note on the :strip => true argument: If :strip is set, defined lengths of data will
140
- # be believed, and any trailers (such as frame check sequences) will be chopped off. This
141
- # helps to ensure well-formed packets, at the cost of losing perhaps important FCS data.
142
- #
143
- # If :strip is false, header lengths are /not/ believed, and all data will be piped in.
144
- # When capturing from the wire, this is usually fine, but recalculating the length before
145
- # saving or re-transmitting will absolutely change the data payload; FCS data will become
146
- # part of the TCP data as far as tcp_len is concerned. Some effort has been made to preserve
147
- # the "real" payload for the purposes of checksums, but currently, it's impossible to seperate
148
- # new payload data from old trailers, so things like pkt.payload += "some data" will not work
149
- # correctly.
150
- #
151
- # So, to summarize; if you intend to alter the data, use :strip. If you don't, don't. Also,
152
- # this is a horrid hack. Stripping is useful (and fun!), but the default behavior really
153
- # should be to create payloads correctly, and /not/ treat extra FCS data as a payload.
154
- #
155
- # Finally, packet subclasses should take two arguments: the string that is the data
156
- # to be transmuted into a packet, as well as args. This superclass method is merely
157
- # concerned with handling args common to many packet formats (namely, fixing packets
158
- # on the fly)
159
- def read(args={})
160
- if args[:fix] || args[:recalc]
161
- ip_recalc(:ip_sum) if self.is_ip?
162
- recalc(:tcp) if self.is_tcp?
163
- recalc(:udp) if self.is_udp?
164
- end
165
- end
166
-
167
- # Packets are bundles of lots of objects, so copying them
168
- # is a little complicated -- a dup of a packet is actually
169
- # full of pass-by-reference stuff in the @headers, so
170
- # if you change one, you're changing all this copies, too.
171
- #
172
- # Normally, this doesn't seem to be a big deal, and it's
173
- # a pretty decent performance tradeoff. But, if you're going
174
- # to be creating a template packet to base a bunch of slightly
175
- # different ones off of (like a fuzzer might), you'll want
176
- # to use clone()
177
- def clone
178
- Packet.parse(self.to_s)
179
- end
180
-
181
- # If two packets are represented as the same binary string, and
182
- # they're both actually PacketFu packets of the same sort, they're equal.
183
- #
184
- # The intuitive result is that a packet of a higher layer (like DNSPacket)
185
- # can be equal to a packet of a lower level (like UDPPacket) as long as
186
- # the bytes are equal (this can come up if a transport-layer packet has
187
- # a hand-crafted payload that is identical to what would have been created
188
- # by using an application layer packet)
189
- def ==(other)
190
- return false unless other.kind_of? self.class
191
- return false unless other.respond_to? :to_s
192
- self.to_s == other.to_s
193
- end
194
-
195
- # Peek provides summary data on packet contents.
196
- #
197
- # Each packet type should provide a peek_format.
198
- def peek(args={})
199
- idx = @headers.reverse.map {|h| h.respond_to? peek_format}.index(true)
200
- if idx
201
- @headers.reverse[idx].peek_format
202
- else
203
- peek_format
204
- end
205
- end
206
-
207
- # The peek_format is used to display a single line
208
- # of packet data useful for eyeballing. It should not exceed
209
- # 80 characters. The Packet superclass defines an example
210
- # peek_format, but it should hardly ever be triggered, since
211
- # peek traverses the @header list in reverse to find a suitable
212
- # format.
213
- #
214
- # === Format
215
- #
216
- # * A one or two character protocol initial. It should be unique
217
- # * The packet size
218
- # * Useful data in a human-usable form.
219
- #
220
- # Ideally, related peek_formats will all line up with each other
221
- # when printed to the screen.
222
- #
223
- # === Example
224
- #
225
- # tcp_packet.peek
226
- # #=> "T 1054 10.10.10.105:55000 -> 192.168.145.105:80 [......] S:adc7155b|I:8dd0"
227
- # tcp_packet.peek.size
228
- # #=> 79
229
- #
230
- def peek_format
231
- peek_data = ["? "]
232
- peek_data << "%-5d" % self.to_s.size
233
- peek_data << "%68s" % self.to_s[0,34].unpack("H*")[0]
234
- peek_data.join
235
- end
236
-
237
- # Defines the layer this packet type lives at, based on the number of headers it
238
- # requires. Note that this has little to do with the OSI model, since TCP/IP
239
- # doesn't really have Session and Presentation layers.
240
- #
241
- # Ethernet and the like are layer 1, IP, IPv6, and ARP are layer 2,
242
- # TCP, UDP, and other transport protocols are layer 3, and application
243
- # protocols are at layer 4 or higher. InvalidPackets have an arbitrary
244
- # layer 0 to distinguish them.
245
- #
246
- # Because these don't change much, it's cheaper just to case through them,
247
- # and only resort to counting headers if we don't have a match -- this
248
- # makes adding protocols somewhat easier, but of course you can just
249
- # override this method over there, too. This is merely optimized
250
- # for the most likely protocols you see on the Internet.
251
- def self.layer
252
- case self.name # Lol ran into case's fancy treatment of classes
253
- when /InvalidPacket$/; 0
254
- when /EthPacket$/; 1
255
- when /IPPacket$/, /ARPPacket$/, /LLDPPacket$/, /IPv6Packet$/; 2
256
- when /TCPPacket$/, /UDPPacket$/, /ICMPPacket$/; 3
257
- when /HSRPPacket$/; 4
258
- else; self.new.headers.size
259
- end
260
- end
261
-
262
- def layer
263
- self.class.layer
264
- end
265
-
266
- def self.layer_symbol
267
- case self.layer
268
- when 0; :invalid
269
- when 1; :link
270
- when 2; :internet
271
- when 3; :transport
272
- else; :application
273
- end
274
- end
275
-
276
- def layer_symbol
277
- self.class.layer_symbol
278
- end
279
-
280
- # Packet subclasses must override this, since the Packet superclass
281
- # can't actually parse anything.
282
- def self.can_parse?(str)
283
- false
284
- end
285
-
286
- # Hexify provides a neatly-formatted dump of binary data, familar to hex readers.
287
- def hexify(str)
288
- str.force_encoding(Encoding::BINARY) if str.respond_to? :force_encoding
289
- hexascii_lines = str.to_s.unpack("H*")[0].scan(/.{1,32}/)
290
- regex = Regexp.new('[\x00-\x1f\x7f-\xff]', nil, 'n')
291
- chars = str.to_s.gsub(regex,'.')
292
- chars_lines = chars.scan(/.{1,16}/)
293
- ret = []
294
- hexascii_lines.size.times {|i| ret << "%-48s %s" % [hexascii_lines[i].gsub(/(.{2})/,"\\1 "),chars_lines[i]]}
295
- ret.join("\n")
296
- end
297
-
298
- # If @inspect_style is :default (or :ugly), the inspect output is the usual
299
- # inspect.
300
- #
301
- # If @inspect_style is :hex (or :pretty), the inspect output is
302
- # a much more compact hexdump-style, with a shortened set of packet header
303
- # names at the top.
304
- #
305
- # If @inspect_style is :dissect (or :verbose), the inspect output is the
306
- # longer, but more readable, dissection of the packet. This is the default.
307
- #
308
- # TODO: Have an option for colors. Everyone loves colorized irb output.
309
- def inspect_hex(arg=0)
310
- case arg
311
- when :layers
312
- ret = []
313
- @headers.size.times do |i|
314
- ret << hexify(@headers[i])
315
- end
316
- ret
317
- when (0..9)
318
- if @headers[arg]
319
- hexify(@headers[arg])
320
- else
321
- nil
322
- end
323
- when :all
324
- inspect_hex(0)
325
- end
326
- end
327
-
328
- def dissection_table
329
- table = []
330
- @headers.each_with_index do |header,table_idx|
331
- proto = header.class.name.sub(/^.*::/,"")
332
- table << [proto,[]]
333
- header.class.members.each do |elem|
334
- elem_sym = elem.to_sym # to_sym needed for 1.8
335
- next if elem_sym == :body
336
- elem_type_value = []
337
- elem_type_value[0] = elem
338
- readable_element = "#{elem}_readable"
339
- if header.respond_to? readable_element
340
- elem_type_value[1] = header.send(readable_element)
341
- else
342
- elem_type_value[1] = header.send(elem)
343
- end
344
- elem_type_value[2] = header[elem.to_sym].class.name
345
- table[table_idx][1] << elem_type_value
346
- end
347
- end
348
- table
349
- if @headers.last.members.map {|x| x.to_sym }.include? :body
350
- body_part = [:body, self.payload, @headers.last.body.class.name]
351
- end
352
- table << body_part
353
- end
354
-
355
- # Renders the dissection_table suitable for screen printing. Can take
356
- # one or two arguments. If just the one, only that layer will be displayed
357
- # take either a range or a number -- if a range, only protos within
358
- # that range will be rendered. If an integer, only that proto
359
- # will be rendered.
360
- def dissect
361
- dtable = self.dissection_table
362
- hex_body = nil
363
- if dtable.last.kind_of?(Array) and dtable.last.first == :body
364
- body = dtable.pop
365
- hex_body = hexify(body[1])
366
- end
367
- elem_widths = [0,0,0]
368
- dtable.each do |proto_table|
369
- proto_table[1].each do |elems|
370
- elems.each_with_index do |e,i|
371
- width = e.size
372
- elem_widths[i] = width if width > elem_widths[i]
373
- end
374
- end
375
- end
376
- total_width = elem_widths.inject(0) {|sum,x| sum+x}
377
- table = ""
378
- dtable.each do |proto|
379
- table << "--"
380
- table << proto[0]
381
- if total_width > proto[0].size
382
- table << ("-" * (total_width - proto[0].size + 2))
383
- else
384
- table << ("-" * (total_width + 2))
385
- end
386
- table << "\n"
387
- proto[1].each do |elems|
388
- table << " "
389
- elems_table = []
390
- (0..2).each do |i|
391
- elems_table << ("%-#{elem_widths[i]}s" % elems[i])
392
- end
393
- table << elems_table.join("\s")
394
- table << "\n"
395
- end
396
- end
397
- if hex_body && !hex_body.empty?
398
- table << "-" * 66
399
- table << "\n"
400
- table << "00-01-02-03-04-05-06-07-08-09-0a-0b-0c-0d-0e-0f---0123456789abcdef\n"
401
- table << "-" * 66
402
- table << "\n"
403
- table << hex_body
404
- end
405
- table
406
- end
407
-
408
- alias :orig_kind_of? :kind_of?
409
-
410
- def kind_of?(klass)
411
- return true if orig_kind_of? klass
412
- packet_types = proto.map {|p| PacketFu.const_get("#{p}Packet")}
413
- match = false
414
- packet_types.each do |p|
415
- if p.ancestors.include? klass
416
- match = true
417
- break
418
- end
419
- end
420
- return match
421
- end
422
-
423
- # For packets, inspect is overloaded as inspect_hex(0).
424
- # Not sure if this is a great idea yet, but it sure makes
425
- # the irb output more sane.
426
- #
427
- # If you hate this, you can run PacketFu.toggle_inspect to return
428
- # to the typical (and often unreadable) Object#inspect format.
429
- def inspect
430
- case @inspect_style
431
- when :dissect
432
- self.dissect
433
- when :hex
434
- self.proto.join("|") + "\n" + self.inspect_hex
435
- else
436
- super
437
- end
438
- end
439
-
440
- # Returns the size of the packet (as a binary string)
441
- def size
442
- self.to_s.size
443
- end
444
-
445
- # Returns an array of protocols contained in this packet. For example:
446
- #
447
- # t = PacketFu::TCPPacket.new
448
- # => 00 1a c5 00 00 00 00 1a c5 00 00 00 08 00 45 00 ..............E.
449
- # 00 28 3c ab 00 00 ff 06 7f 25 00 00 00 00 00 00 .(<......%......
450
- # 00 00 93 5e 00 00 ad 4f e4 a4 00 00 00 00 50 00 ...^...O......P.
451
- # 40 00 4a 92 00 00 @.J...
452
- # t.proto
453
- # => ["Eth", "IP", "TCP"]
454
- #
455
- def proto
456
- type_array = []
457
- self.headers.each {|header| type_array << header.class.to_s.split('::').last.gsub(/Header$/,'')}
458
- type_array
459
- end
460
-
461
- alias_method :protocol, :proto
462
- alias_method :length, :size
463
-
464
- # the Packet class should not be instantiated directly, since it's an
465
- # abstract class that real packet types inherit from. Sadly, this
466
- # makes the Packet class more difficult to test directly.
467
- def initialize(args={})
468
- if self.class.name =~ /(::|^)PacketFu::Packet$/
469
- raise NoMethodError, "method `new' called for abstract class #{self.class.name}"
470
- end
471
- @inspect_style = args[:inspect_style] || PacketFu.inspect_style || :dissect
472
- if args[:config]
473
- args[:config].each_pair do |k,v|
474
- case k
475
- when :eth_daddr; @eth_header.eth_daddr=v if @eth_header
476
- when :eth_saddr; @eth_header.eth_saddr=v if @eth_header
477
- when :ip_saddr; @ip_header.ip_saddr=v if @ip_header
478
- when :iface; @iface = v
479
- end
480
- end
481
- end
482
- end
483
-
484
- # Delegate to PacketFu's inspect_style, since the
485
- # class variable name is the same. Yay for namespace
486
- # pollution!
487
- def inspect_style=()
488
- PacketFu.inspect_style(arg)
489
- end
490
-
491
- #method_missing() delegates protocol-specific field actions to the apporpraite
492
- #class variable (which contains the associated packet type)
493
- #This register-of-protocols style switch will work for the
494
- #forseeable future (there aren't /that/ many packet types), and it's a handy
495
- #way to know at a glance what packet types are supported.
496
- def method_missing(sym, *args, &block)
497
- case sym.to_s
498
- when /^is_([a-zA-Z0-9]+)\?/
499
- ptype = $1
500
- if PacketFu.packet_prefixes.index(ptype)
501
- self.send(:handle_is_identity, $1)
502
- else
503
- super
504
- end
505
- when /^([a-zA-Z0-9]+)_.+/
506
- ptype = $1
507
- if PacketFu.packet_prefixes.index(ptype)
508
- self.instance_variable_get("@#{ptype}_header").send(sym,*args, &block)
509
- else
510
- super
511
- end
512
- else
513
- super
514
- end
515
- end
516
-
517
- def respond_to?(sym, include_private = false)
518
- if sym.to_s =~ /^(invalid|eth|arp|ip|icmp|udp|hsrp|tcp|ipv6)_/
519
- self.instance_variable_get("@#{$1}_header").respond_to? sym
520
- elsif sym.to_s =~ /^is_([a-zA-Z0-9]+)\?/
521
- if PacketFu.packet_prefixes.index($1)
522
- true
523
- else
524
- super
525
- end
526
- else
527
- super
528
- end
529
- end
530
-
531
- end # class Packet
4
+ # Packet is the parent class of EthPacket, IPPacket, UDPPacket, TCPPacket, and all
5
+ # other packets. It acts as both a singleton class, so things like
6
+ # Packet.parse can happen, and as an abstract class to provide
7
+ # subclasses some structure.
8
+ class Packet
9
+
10
+ attr_reader :flavor # Packet Headers are responsible for their own specific flavor methods.
11
+ attr_accessor :headers # All packets have a header collection, useful for determining protocol trees.
12
+ attr_accessor :iface # Default inferface to send packets to
13
+ attr_accessor :inspect_style # Default is :dissect, can also be :hex or :default
14
+
15
+ # Register subclasses in PacketFu.packet_class to do all kinds of neat things
16
+ # that obviates those long if/else trees for parsing. It's pretty sweet.
17
+ def self.inherited(subclass)
18
+ PacketFu.add_packet_class(subclass)
19
+ end
20
+
21
+ # Force strings into binary.
22
+ def self.force_binary(str)
23
+ str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding
24
+ end
25
+
26
+ # Parse() creates the correct packet type based on the data, and returns the apporpiate
27
+ # Packet subclass object.
28
+ #
29
+ # There is an assumption here that all incoming packets are either EthPacket
30
+ # or InvalidPacket types. This will be addressed pretty soon.
31
+ #
32
+ # If application-layer parsing is /not/ desired, that should be indicated explicitly
33
+ # with an argument of :parse_app => false. Otherwise, app-layer parsing will happen.
34
+ #
35
+ # It is no longer neccisary to manually add packet types here.
36
+ def self.parse(packet=nil,args={})
37
+ parse_app = true if(args[:parse_app].nil? or args[:parse_app])
38
+ force_binary(packet)
39
+ if parse_app
40
+ classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}
41
+ else
42
+ classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}.reject {|pclass| pclass.layer_symbol == :application}
43
+ end
44
+ p = classes.sort {|x,y| x.layer <=> y.layer}.last.new
45
+ parsed_packet = p.read(packet,args)
46
+ end
47
+
48
+ def handle_is_identity(ptype)
49
+ idx = PacketFu.packet_prefixes.index(ptype.to_s.downcase)
50
+ if idx
51
+ self.kind_of? PacketFu.packet_classes[idx]
52
+ else
53
+ raise NoMethodError, "Undefined method `is_#{ptype}?' for #{self.class}."
54
+ end
55
+ end
56
+
57
+ # Get the binary string of the entire packet.
58
+ def to_s
59
+ @headers[0].to_s
60
+ end
61
+
62
+ # In the event of no proper decoding, at least send it to the inner-most header.
63
+ def write(io)
64
+ @headers[0].write(io)
65
+ end
66
+
67
+ # Get the outermost payload (body) of the packet; this is why all packet headers
68
+ # should have a body type.
69
+ def payload
70
+ @headers.last.body
71
+ end
72
+
73
+ # Set the outermost payload (body) of the packet.
74
+ def payload=(args)
75
+ @headers.last.body=(args)
76
+ end
77
+
78
+ # Converts a packet to libpcap format. Bit of a hack?
79
+ def to_pcap(args={})
80
+ p = PcapPacket.new(:endian => args[:endian],
81
+ :timestamp => Timestamp.new.to_s,
82
+ :incl_len => self.to_s.size,
83
+ :orig_len => self.to_s.size,
84
+ :data => self)
85
+ end
86
+
87
+ # Put the entire packet into a libpcap file. XXX: this is a
88
+ # hack for now just to confirm that packets are getting created
89
+ # correctly. Now with append! XXX: Document this!
90
+ def to_f(filename=nil,mode='w')
91
+ filename ||= 'out.pcap'
92
+ mode = mode.to_s[0,1] + "b"
93
+ raise ArgumentError, "Unknown mode: #{mode.to_s}" unless mode =~ /^[wa]/
94
+ if(mode == 'w' || !(File.exists?(filename)))
95
+ data = [PcapHeader.new, self.to_pcap].map {|x| x.to_s}.join
96
+ else
97
+ data = self.to_pcap
98
+ end
99
+ File.open(filename, mode) {|f| f.write data}
100
+ return [filename, 1, data.size]
101
+ end
102
+
103
+ # Put the entire packet on the wire by creating a temporary PacketFu::Inject object.
104
+ # TODO: Do something with auto-checksumming?
105
+ def to_w(iface=nil)
106
+ iface = (iface || self.iface || PacketFu::Config.new.config[:iface]).to_s
107
+ inj = PacketFu::Inject.new(:iface => iface)
108
+ inj.array = [@headers[0].to_s]
109
+ inj.inject
110
+ end
111
+
112
+ # Recalculates all the calcuated fields for all headers in the packet.
113
+ # This is important since read() wipes out all the calculated fields
114
+ # such as length and checksum and what all.
115
+ def recalc(arg=:all)
116
+ case arg
117
+ when :ip
118
+ ip_recalc(:all)
119
+ when :icmp
120
+ icmp_recalc(:all)
121
+ when :udp
122
+ udp_recalc(:all)
123
+ when :tcp
124
+ tcp_recalc(:all)
125
+ when :all
126
+ ip_recalc(:all) if @ip_header
127
+ icmp_recalc(:all) if @icmp_header
128
+ udp_recalc(:all) if @udp_header
129
+ tcp_recalc(:all) if @tcp_header
130
+ else
131
+ raise ArgumentError, "Recalculating `#{arg}' unsupported. Try :all"
132
+ end
133
+ @headers[0]
134
+ end
135
+
136
+ # Read() takes (and trusts) the io input and shoves it all into a well-formed Packet.
137
+ # Note that read is a destructive process, so any existing data will be lost.
138
+ #
139
+ # A note on the :strip => true argument: If :strip is set, defined lengths of data will
140
+ # be believed, and any trailers (such as frame check sequences) will be chopped off. This
141
+ # helps to ensure well-formed packets, at the cost of losing perhaps important FCS data.
142
+ #
143
+ # If :strip is false, header lengths are /not/ believed, and all data will be piped in.
144
+ # When capturing from the wire, this is usually fine, but recalculating the length before
145
+ # saving or re-transmitting will absolutely change the data payload; FCS data will become
146
+ # part of the TCP data as far as tcp_len is concerned. Some effort has been made to preserve
147
+ # the "real" payload for the purposes of checksums, but currently, it's impossible to seperate
148
+ # new payload data from old trailers, so things like pkt.payload += "some data" will not work
149
+ # correctly.
150
+ #
151
+ # So, to summarize; if you intend to alter the data, use :strip. If you don't, don't. Also,
152
+ # this is a horrid hack. Stripping is useful (and fun!), but the default behavior really
153
+ # should be to create payloads correctly, and /not/ treat extra FCS data as a payload.
154
+ #
155
+ # Finally, packet subclasses should take two arguments: the string that is the data
156
+ # to be transmuted into a packet, as well as args. This superclass method is merely
157
+ # concerned with handling args common to many packet formats (namely, fixing packets
158
+ # on the fly)
159
+ def read(args={})
160
+ if args[:fix] || args[:recalc]
161
+ ip_recalc(:ip_sum) if self.is_ip?
162
+ recalc(:tcp) if self.is_tcp?
163
+ recalc(:udp) if self.is_udp?
164
+ end
165
+ end
166
+
167
+ # Packets are bundles of lots of objects, so copying them
168
+ # is a little complicated -- a dup of a packet is actually
169
+ # full of pass-by-reference stuff in the @headers, so
170
+ # if you change one, you're changing all this copies, too.
171
+ #
172
+ # Normally, this doesn't seem to be a big deal, and it's
173
+ # a pretty decent performance tradeoff. But, if you're going
174
+ # to be creating a template packet to base a bunch of slightly
175
+ # different ones off of (like a fuzzer might), you'll want
176
+ # to use clone()
177
+ def clone
178
+ Packet.parse(self.to_s)
179
+ end
180
+
181
+ # If two packets are represented as the same binary string, and
182
+ # they're both actually PacketFu packets of the same sort, they're equal.
183
+ #
184
+ # The intuitive result is that a packet of a higher layer (like DNSPacket)
185
+ # can be equal to a packet of a lower level (like UDPPacket) as long as
186
+ # the bytes are equal (this can come up if a transport-layer packet has
187
+ # a hand-crafted payload that is identical to what would have been created
188
+ # by using an application layer packet)
189
+ def ==(other)
190
+ return false unless other.kind_of? self.class
191
+ return false unless other.respond_to? :to_s
192
+ self.to_s == other.to_s
193
+ end
194
+
195
+ # Peek provides summary data on packet contents.
196
+ #
197
+ # Each packet type should provide a peek_format.
198
+ def peek(args={})
199
+ idx = @headers.reverse.map {|h| h.respond_to? peek_format}.index(true)
200
+ if idx
201
+ @headers.reverse[idx].peek_format
202
+ else
203
+ peek_format
204
+ end
205
+ end
206
+
207
+ # The peek_format is used to display a single line
208
+ # of packet data useful for eyeballing. It should not exceed
209
+ # 80 characters. The Packet superclass defines an example
210
+ # peek_format, but it should hardly ever be triggered, since
211
+ # peek traverses the @header list in reverse to find a suitable
212
+ # format.
213
+ #
214
+ # === Format
215
+ #
216
+ # * A one or two character protocol initial. It should be unique
217
+ # * The packet size
218
+ # * Useful data in a human-usable form.
219
+ #
220
+ # Ideally, related peek_formats will all line up with each other
221
+ # when printed to the screen.
222
+ #
223
+ # === Example
224
+ #
225
+ # tcp_packet.peek
226
+ # #=> "T 1054 10.10.10.105:55000 -> 192.168.145.105:80 [......] S:adc7155b|I:8dd0"
227
+ # tcp_packet.peek.size
228
+ # #=> 79
229
+ #
230
+ def peek_format
231
+ peek_data = ["? "]
232
+ peek_data << "%-5d" % self.to_s.size
233
+ peek_data << "%68s" % self.to_s[0,34].unpack("H*")[0]
234
+ peek_data.join
235
+ end
236
+
237
+ # Defines the layer this packet type lives at, based on the number of headers it
238
+ # requires. Note that this has little to do with the OSI model, since TCP/IP
239
+ # doesn't really have Session and Presentation layers.
240
+ #
241
+ # Ethernet and the like are layer 1, IP, IPv6, and ARP are layer 2,
242
+ # TCP, UDP, and other transport protocols are layer 3, and application
243
+ # protocols are at layer 4 or higher. InvalidPackets have an arbitrary
244
+ # layer 0 to distinguish them.
245
+ #
246
+ # Because these don't change much, it's cheaper just to case through them,
247
+ # and only resort to counting headers if we don't have a match -- this
248
+ # makes adding protocols somewhat easier, but of course you can just
249
+ # override this method over there, too. This is merely optimized
250
+ # for the most likely protocols you see on the Internet.
251
+ def self.layer
252
+ case self.name # Lol ran into case's fancy treatment of classes
253
+ when /InvalidPacket$/; 0
254
+ when /EthPacket$/; 1
255
+ when /IPPacket$/, /ARPPacket$/, /LLDPPacket$/, /IPv6Packet$/; 2
256
+ when /TCPPacket$/, /UDPPacket$/, /ICMPPacket$/; 3
257
+ when /HSRPPacket$/; 4
258
+ else; self.new.headers.size
259
+ end
260
+ end
261
+
262
+ def layer
263
+ self.class.layer
264
+ end
265
+
266
+ def self.layer_symbol
267
+ case self.layer
268
+ when 0; :invalid
269
+ when 1; :link
270
+ when 2; :internet
271
+ when 3; :transport
272
+ else; :application
273
+ end
274
+ end
275
+
276
+ def layer_symbol
277
+ self.class.layer_symbol
278
+ end
279
+
280
+ # Packet subclasses must override this, since the Packet superclass
281
+ # can't actually parse anything.
282
+ def self.can_parse?(str)
283
+ false
284
+ end
285
+
286
+ # Hexify provides a neatly-formatted dump of binary data, familar to hex readers.
287
+ def hexify(str)
288
+ str.force_encoding(Encoding::BINARY) if str.respond_to? :force_encoding
289
+ hexascii_lines = str.to_s.unpack("H*")[0].scan(/.{1,32}/)
290
+ regex = Regexp.new('[\x00-\x1f\x7f-\xff]', nil, 'n')
291
+ chars = str.to_s.gsub(regex,'.')
292
+ chars_lines = chars.scan(/.{1,16}/)
293
+ ret = []
294
+ hexascii_lines.size.times {|i| ret << "%-48s %s" % [hexascii_lines[i].gsub(/(.{2})/,"\\1 "),chars_lines[i]]}
295
+ ret.join("\n")
296
+ end
297
+
298
+ # If @inspect_style is :default (or :ugly), the inspect output is the usual
299
+ # inspect.
300
+ #
301
+ # If @inspect_style is :hex (or :pretty), the inspect output is
302
+ # a much more compact hexdump-style, with a shortened set of packet header
303
+ # names at the top.
304
+ #
305
+ # If @inspect_style is :dissect (or :verbose), the inspect output is the
306
+ # longer, but more readable, dissection of the packet. This is the default.
307
+ #
308
+ # TODO: Have an option for colors. Everyone loves colorized irb output.
309
+ def inspect_hex(arg=0)
310
+ case arg
311
+ when :layers
312
+ ret = []
313
+ @headers.size.times do |i|
314
+ ret << hexify(@headers[i])
315
+ end
316
+ ret
317
+ when (0..9)
318
+ if @headers[arg]
319
+ hexify(@headers[arg])
320
+ else
321
+ nil
322
+ end
323
+ when :all
324
+ inspect_hex(0)
325
+ end
326
+ end
327
+
328
+ def dissection_table
329
+ table = []
330
+ @headers.each_with_index do |header,table_idx|
331
+ proto = header.class.name.sub(/^.*::/,"")
332
+ table << [proto,[]]
333
+ header.class.members.each do |elem|
334
+ elem_sym = elem.to_sym # to_sym needed for 1.8
335
+ next if elem_sym == :body
336
+ elem_type_value = []
337
+ elem_type_value[0] = elem
338
+ readable_element = "#{elem}_readable"
339
+ if header.respond_to? readable_element
340
+ elem_type_value[1] = header.send(readable_element)
341
+ else
342
+ elem_type_value[1] = header.send(elem)
343
+ end
344
+ elem_type_value[2] = header[elem.to_sym].class.name
345
+ table[table_idx][1] << elem_type_value
346
+ end
347
+ end
348
+ table
349
+ if @headers.last.members.map {|x| x.to_sym }.include? :body
350
+ body_part = [:body, self.payload, @headers.last.body.class.name]
351
+ end
352
+ table << body_part
353
+ end
354
+
355
+ # Renders the dissection_table suitable for screen printing. Can take
356
+ # one or two arguments. If just the one, only that layer will be displayed
357
+ # take either a range or a number -- if a range, only protos within
358
+ # that range will be rendered. If an integer, only that proto
359
+ # will be rendered.
360
+ def dissect
361
+ dtable = self.dissection_table
362
+ hex_body = nil
363
+ if dtable.last.kind_of?(Array) and dtable.last.first == :body
364
+ body = dtable.pop
365
+ hex_body = hexify(body[1])
366
+ end
367
+ elem_widths = [0,0,0]
368
+ dtable.each do |proto_table|
369
+ proto_table[1].each do |elems|
370
+ elems.each_with_index do |e,i|
371
+ width = e.size
372
+ elem_widths[i] = width if width > elem_widths[i]
373
+ end
374
+ end
375
+ end
376
+ total_width = elem_widths.inject(0) {|sum,x| sum+x}
377
+ table = ""
378
+ dtable.each do |proto|
379
+ table << "--"
380
+ table << proto[0]
381
+ if total_width > proto[0].size
382
+ table << ("-" * (total_width - proto[0].size + 2))
383
+ else
384
+ table << ("-" * (total_width + 2))
385
+ end
386
+ table << "\n"
387
+ proto[1].each do |elems|
388
+ table << " "
389
+ elems_table = []
390
+ (0..2).each do |i|
391
+ elems_table << ("%-#{elem_widths[i]}s" % elems[i])
392
+ end
393
+ table << elems_table.join("\s")
394
+ table << "\n"
395
+ end
396
+ end
397
+ if hex_body && !hex_body.empty?
398
+ table << "-" * 66
399
+ table << "\n"
400
+ table << "00-01-02-03-04-05-06-07-08-09-0a-0b-0c-0d-0e-0f---0123456789abcdef\n"
401
+ table << "-" * 66
402
+ table << "\n"
403
+ table << hex_body
404
+ end
405
+ table
406
+ end
407
+
408
+ alias :orig_kind_of? :kind_of?
409
+
410
+ def kind_of?(klass)
411
+ return true if orig_kind_of? klass
412
+ packet_types = proto.map {|p| PacketFu.const_get("#{p}Packet")}
413
+ match = false
414
+ packet_types.each do |p|
415
+ if p.ancestors.include? klass
416
+ match = true
417
+ break
418
+ end
419
+ end
420
+ return match
421
+ end
422
+
423
+ # For packets, inspect is overloaded as inspect_hex(0).
424
+ # Not sure if this is a great idea yet, but it sure makes
425
+ # the irb output more sane.
426
+ #
427
+ # If you hate this, you can run PacketFu.toggle_inspect to return
428
+ # to the typical (and often unreadable) Object#inspect format.
429
+ def inspect
430
+ case @inspect_style
431
+ when :dissect
432
+ self.dissect
433
+ when :hex
434
+ self.proto.join("|") + "\n" + self.inspect_hex
435
+ else
436
+ super
437
+ end
438
+ end
439
+
440
+ # Returns the size of the packet (as a binary string)
441
+ def size
442
+ self.to_s.size
443
+ end
444
+
445
+ # Returns an array of protocols contained in this packet. For example:
446
+ #
447
+ # t = PacketFu::TCPPacket.new
448
+ # => 00 1a c5 00 00 00 00 1a c5 00 00 00 08 00 45 00 ..............E.
449
+ # 00 28 3c ab 00 00 ff 06 7f 25 00 00 00 00 00 00 .(<......%......
450
+ # 00 00 93 5e 00 00 ad 4f e4 a4 00 00 00 00 50 00 ...^...O......P.
451
+ # 40 00 4a 92 00 00 @.J...
452
+ # t.proto
453
+ # => ["Eth", "IP", "TCP"]
454
+ #
455
+ def proto
456
+ type_array = []
457
+ self.headers.each {|header| type_array << header.class.to_s.split('::').last.gsub(/Header$/,'')}
458
+ type_array
459
+ end
460
+
461
+ alias_method :protocol, :proto
462
+ alias_method :length, :size
463
+
464
+ # the Packet class should not be instantiated directly, since it's an
465
+ # abstract class that real packet types inherit from. Sadly, this
466
+ # makes the Packet class more difficult to test directly.
467
+ def initialize(args={})
468
+ if self.class.name =~ /(::|^)PacketFu::Packet$/
469
+ raise NoMethodError, "method `new' called for abstract class #{self.class.name}"
470
+ end
471
+ @inspect_style = args[:inspect_style] || PacketFu.inspect_style || :dissect
472
+ if args[:config]
473
+ args[:config].each_pair do |k,v|
474
+ case k
475
+ when :eth_daddr; @eth_header.eth_daddr=v if @eth_header
476
+ when :eth_saddr; @eth_header.eth_saddr=v if @eth_header
477
+ when :ip_saddr; @ip_header.ip_saddr=v if @ip_header
478
+ when :iface; @iface = v
479
+ end
480
+ end
481
+ end
482
+ end
483
+
484
+ # Delegate to PacketFu's inspect_style, since the
485
+ # class variable name is the same. Yay for namespace
486
+ # pollution!
487
+ def inspect_style=()
488
+ PacketFu.inspect_style(arg)
489
+ end
490
+
491
+ #method_missing() delegates protocol-specific field actions to the apporpraite
492
+ #class variable (which contains the associated packet type)
493
+ #This register-of-protocols style switch will work for the
494
+ #forseeable future (there aren't /that/ many packet types), and it's a handy
495
+ #way to know at a glance what packet types are supported.
496
+ def method_missing(sym, *args, &block)
497
+ case sym.to_s
498
+ when /^is_([a-zA-Z0-9]+)\?/
499
+ ptype = $1
500
+ if PacketFu.packet_prefixes.index(ptype)
501
+ self.send(:handle_is_identity, $1)
502
+ else
503
+ super
504
+ end
505
+ when /^([a-zA-Z0-9]+)_.+/
506
+ ptype = $1
507
+ if PacketFu.packet_prefixes.index(ptype)
508
+ self.instance_variable_get("@#{ptype}_header").send(sym,*args, &block)
509
+ else
510
+ super
511
+ end
512
+ else
513
+ super
514
+ end
515
+ end
516
+
517
+ def respond_to?(sym, include_private = false)
518
+ if sym.to_s =~ /^(invalid|eth|arp|ip|icmp|udp|hsrp|tcp|ipv6)_/
519
+ self.instance_variable_get("@#{$1}_header").respond_to? sym
520
+ elsif sym.to_s =~ /^is_([a-zA-Z0-9]+)\?/
521
+ if PacketFu.packet_prefixes.index($1)
522
+ true
523
+ else
524
+ super
525
+ end
526
+ else
527
+ super
528
+ end
529
+ end
530
+
531
+ end # class Packet
532
532
  end
533
533
 
534
534
  # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby