packetfu 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
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