packetfu 1.1.2 → 1.1.3

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