packetfu 1.0.0 → 1.0.2.pre

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.
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  == LICENSE
2
2
 
3
- Copyright (c) 2008-2010, Tod Beardsley
3
+ Copyright (c) 2008-2011, Tod Beardsley
4
4
  All rights reserved.
5
5
 
6
6
  Redistribution and use in source and binary forms, with or without
data/README CHANGED
@@ -10,8 +10,11 @@ PacketFu is rdoc-compatable. In the same directory as this file, run "rdoc" by i
10
10
  == Requirements
11
11
 
12
12
  PcapRub:
13
+
13
14
  $ svn co http://www.metasploit.com/svn/framework3/trunk/external/pcaprub
15
+
14
16
  or
17
+
15
18
  $ rvm gem install pcaprub
16
19
 
17
20
  Marshall Beddoe's PcapRub is required only for packet reading and writing from a network interfaces (which is a pretty big only). PcapRub itself relies on libpcap 0.9.8 or later for packet injection. PcapRub also requires root privilieges to access the interface directly.
@@ -43,6 +43,7 @@
43
43
  # 72 20 62 69 74 73 2e r bits.
44
44
  # => nil
45
45
 
46
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib/")
46
47
  require 'examples'
47
48
  require 'packetfu'
48
49
 
@@ -82,11 +83,12 @@ def packetfu_ascii_art
82
83
  EOM
83
84
  end
84
85
 
86
+ @pcaprub_loaded = PacketFu.pcaprub_loaded?
85
87
  # Displays a helpful banner.
86
88
  def banner
87
89
  packetfu_ascii_art
88
90
  puts ">>> PacketFu Shell #{PacketFu.version}."
89
- if Process.euid.zero? && @@pcaprub_loaded
91
+ if Process.euid.zero? && @pcaprub_loaded
90
92
  puts ">>> Use $packetfu_default.config for salient networking details."
91
93
  print "IP: %-15s Mac: %s" % [$packetfu_default.ip_saddr, $packetfu_default.eth_saddr]
92
94
  puts " Gateway: %s" % $packetfu_default.eth_daddr
@@ -103,9 +105,9 @@ end
103
105
 
104
106
  # Silly wlan0 workaround
105
107
  begin
106
- $packetfu_default = PacketFu::Config.new(Utils.whoami?) if(@@pcaprub_loaded && Process.euid.zero?)
108
+ $packetfu_default = PacketFu::Config.new(Utils.whoami?) if(@pcaprub_loaded && Process.euid.zero?)
107
109
  rescue RuntimeError
108
- $packetfu_default = PacketFu::Config.new(Utils.whoami?(:iface => 'wlan0')) if(@@pcaprub_loaded && Process.euid.zero?)
110
+ $packetfu_default = PacketFu::Config.new(Utils.whoami?(:iface => 'wlan0')) if(@pcaprub_loaded && Process.euid.zero?)
109
111
  end
110
112
 
111
113
  banner
data/lib/packetfu.rb CHANGED
@@ -4,8 +4,11 @@
4
4
  # :include: ../INSTALL
5
5
  # :include: ../LICENSE
6
6
 
7
- $: << File.expand_path(File.dirname(__FILE__))
8
- require "packetfu/structfu"
7
+ cwd = File.expand_path(File.dirname(__FILE__))
8
+
9
+ $: << cwd
10
+
11
+ require File.join(cwd,"packetfu","structfu")
9
12
  require "ipaddr"
10
13
  require 'rubygems' if RUBY_VERSION =~ /^1\.[0-8]/
11
14
 
@@ -15,94 +18,76 @@ module PacketFu
15
18
  @byte_order = :little
16
19
 
17
20
  # Checks if pcaprub is loaded correctly.
18
- @@pcaprub_loaded = false
19
-
21
+ @pcaprub_loaded = false
22
+
20
23
  # PacketFu works best with Pcaprub version 0.8-dev (at least)
21
24
  # The current (Aug 01, 2010) pcaprub gem is 0.9, so should be fine.
22
- def self.pcaprub_platform_require
25
+ def self.pcaprub_platform_require
23
26
  begin
24
27
  require 'pcaprub'
25
28
  rescue LoadError
26
29
  return false
27
30
  end
28
- @@pcaprub_loaded = true
29
- end
31
+ @pcaprub_loaded = true
32
+ end
30
33
 
31
34
  pcaprub_platform_require
32
- if @@pcaprub_loaded
35
+ if @pcaprub_loaded
33
36
  if Pcap.version !~ /[0-9]\.[7-9][0-9]?(-dev)?/ # Regex for 0.7-dev and beyond.
34
- @@pcaprub_loaded = false # Don't bother with broken versions
37
+ @pcaprub_loaded = false # Don't bother with broken versions
35
38
  raise LoadError, "PcapRub not at a minimum version of 0.8-dev"
36
39
  end
37
40
  require "packetfu/capture"
38
41
  require "packetfu/inject"
39
- else
40
- warn "Warning: Missing pcaprub, cannot load PacketFu::Capture or PacketFu::Inject"
41
42
  end
42
43
 
43
- end
44
-
45
- require "packetfu/pcap"
46
- require "packetfu/packet"
47
- require "packetfu/invalid"
48
- require "packetfu/eth"
49
- require "packetfu/ip"
50
- require "packetfu/arp"
51
- require "packetfu/icmp"
52
- require "packetfu/udp"
53
- require "packetfu/tcp"
54
- require "packetfu/ipv6" # This is pretty minimal.
55
- require "packetfu/utils"
56
- require "packetfu/config"
57
-
58
- module PacketFu
59
-
60
- VERSION = "1.0.0" # Version 1.0.0 was released July 31, 2010
44
+ def self.pcaprub_loaded?
45
+ @pcaprub_loaded
46
+ end
61
47
 
62
- # Returns the current version of PacketFu. Incremented every once
63
- # in a while, when I remember
64
- def self.version
65
- PacketFu::VERSION
48
+ # Returns an array of classes defined in PacketFu
49
+ def self.classes
50
+ constants.map { |const| const_get(const) if const_get(const).kind_of? Class}.compact
66
51
  end
67
52
 
68
- # Returns the version in a binary format for easy comparisons.
69
- def self.binarize_version(str)
70
- if(str.respond_to?(:split) && str =~ /^[0-9]+(\.([0-9]+)(\.[0-9]+)?)?$/)
71
- bin_major,bin_minor,bin_teeny = str.split(/\x2e/).map {|x| x.to_i}
72
- bin_version = (bin_major.to_i << 16) + (bin_minor.to_i << 8) + bin_teeny.to_i
73
- else
74
- raise ArgumentError, "Compare version malformed. Should be \x22x.y.z\x22"
53
+ def self.add_packet_class(klass)
54
+ raise "Need a class" unless klass.kind_of? Class
55
+ if klass.name !~ /[A-Za-z0-9]Packet/
56
+ raise "Packet classes should be named 'ProtoPacket'"
75
57
  end
58
+ @packet_classes ||= []
59
+ @packet_classes << klass
60
+ @packet_classes.sort! {|x,y| x.name <=> y.name}
76
61
  end
77
62
 
78
- # Returns true if the version is equal to or greater than the compare version.
79
- # If the current version of PacketFu is "0.3.1" for example:
80
- #
81
- # PacketFu.at_least? "0" # => true
82
- # PacketFu.at_least? "0.2.9" # => true
83
- # PacketFu.at_least? "0.3" # => true
84
- # PacketFu.at_least? "1" # => true after 1.0's release
85
- # PacketFu.at_least? "1.12" # => false
86
- # PacketFu.at_least? "2" # => false
87
- def self.at_least?(str)
88
- this_version = binarize_version(self.version)
89
- ask_version = binarize_version(str)
90
- this_version >= ask_version
63
+ def self.packet_classes
64
+ @packet_classes || []
91
65
  end
92
66
 
93
- # Returns true if the current version is older than the compare version.
94
- def self.older_than?(str)
95
- this_version = binarize_version(self.version)
96
- ask_version = binarize_version(str)
97
- this_version < ask_version
67
+ def self.packet_prefixes
68
+ return [] unless @packet_classes
69
+ @packet_classes.map {|p| p.to_s.split("::").last.to_s.downcase.gsub(/packet$/,"")}
98
70
  end
99
71
 
100
- # Returns true if the current version is newer than the compare version.
101
- def self.newer_than?(str)
102
- return false if str == self.version
103
- !self.older_than?(str)
104
- end
72
+ end
105
73
 
74
+ def require_protos(cwd)
75
+ protos_dir = File.join(cwd, "packetfu", "protos")
76
+ Dir.new(protos_dir).each do |fname|
77
+ next unless fname[/\.rb$/]
78
+ begin
79
+ require File.join(protos_dir,fname)
80
+ rescue
81
+ warn "Warning: Could not load `#{fname}'. Skipping."
82
+ end
83
+ end
106
84
  end
107
85
 
86
+ require File.join(cwd,"packetfu","version")
87
+ require File.join(cwd,"packetfu","pcap")
88
+ require File.join(cwd,"packetfu","packet")
89
+ require_protos(cwd)
90
+ require File.join(cwd,"packetfu","utils")
91
+ require File.join(cwd,"packetfu","config")
92
+
108
93
  # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
@@ -1,88 +1,54 @@
1
1
  module PacketFu
2
2
 
3
3
  # Packet is the parent class of EthPacket, IPPacket, UDPPacket, TCPPacket, and all
4
- # other packets.
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.
5
7
  class Packet
8
+
6
9
  attr_reader :flavor # Packet Headers are responsible for their own specific flavor methods.
7
10
  attr_accessor :headers # All packets have a header collection, useful for determining protocol trees.
8
11
  attr_accessor :iface # Default inferface to send packets to
9
12
 
13
+ # Register subclasses in PacketFu.packet_class to do all kinds of neat things
14
+ # that obviates those long if/else trees for parsing. It's pretty sweet.
15
+ def self.inherited(subclass)
16
+ PacketFu.add_packet_class(subclass)
17
+ end
18
+
10
19
  # Force strings into binary.
11
20
  def self.force_binary(str)
12
21
  str.force_encoding "binary" if str.respond_to? :force_encoding
13
22
  end
14
23
 
15
24
  # Parse() creates the correct packet type based on the data, and returns the apporpiate
16
- # Packet subclass.
25
+ # Packet subclass object.
17
26
  #
18
27
  # There is an assumption here that all incoming packets are either EthPacket
19
- # or InvalidPacket types.
28
+ # or InvalidPacket types. This will be addressed pretty soon.
29
+ #
30
+ # If application-layer parsing is /not/ desired, that should be indicated explicitly
31
+ # with an argument of :parse_app => false. Otherwise, app-layer parsing will happen.
20
32
  #
21
- # New packet types should get an entry here.
22
- def self.parse(packet,args={})
33
+ # It is no longer neccisary to manually add packet types here.
34
+ def self.parse(packet=nil,args={})
35
+ parse_app = true if(args[:parse_app].nil? or args[:parse_app])
23
36
  force_binary(packet)
24
- if packet.size >= 14 # Min size for Ethernet. No check for max size, yet.
25
- case packet[12,2] # Check the Eth protocol field.
26
- when "\x08\x00" # It's IP.
27
- if 1.respond_to? :ord
28
- ipv = packet[14,1][0].ord >> 4
29
- else
30
- ipv = packet[14,1][0] >> 4
31
- end
32
- case ipv # Check the IP version field.
33
- when 4; # It's IPv4.
34
- case packet[23,1] # Check the IP protocol field.
35
- when "\x06"; p = TCPPacket.new # Returns a TCPPacket.
36
- when "\x11"; p = UDPPacket.new # Returns a UDPPacket.
37
- when "\x01"; p = ICMPPacket.new # Returns an ICMPPacket.
38
- else; p = IPPacket.new # Returns an IPPacket since we can't tell the transport layer.
39
- end
40
- else; p = IPPacket.new # Returns an EthPacket since we don't know any other IP version.
41
- end
42
- when "\x08\x06" # It's arp
43
- if packet.size >= 28 # Min size for complete arp
44
- p = ARPPacket.new
45
- else; p = EthPacket.new # Returns an EthPacket since we can't deal with tiny arps.
46
- end
47
- when "\x86\xdd" # It's IPv6
48
- if packet.size >= 54 # Min size for a complete IPv6 packet.
49
- p = IPv6Packet.new
50
- else; p = EthPacket.new # Returns an EthPacket since we can't deal with tiny Ipv6.
51
- end
52
- else; p = EthPacket.new # Returns an EthPacket since we can't tell the network layer.
53
- end
37
+ if parse_app
38
+ classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}
54
39
  else
55
- p = InvalidPacket.new # Not the right size for Ethernet (jumbo frames are okay)
40
+ classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}.reject {|pclass| pclass.layer_symbol == :application}
56
41
  end
42
+ p = classes.sort {|x,y| x.layer <=> y.layer}.last.new
57
43
  parsed_packet = p.read(packet,args)
58
- return parsed_packet
59
44
  end
60
45
 
61
- #method_missing() delegates protocol-specific field actions to the apporpraite
62
- #class variable (which contains the associated packet type)
63
- #This register-of-protocols style switch will work for the
64
- #forseeable future (there aren't /that/ many packet types), and it's a handy
65
- #way to know at a glance what packet types are supported.
66
- def method_missing(sym, *args)
67
- case sym.to_s
68
- when /^invalid_/
69
- @invalid_header.send(sym,*args)
70
- when /^eth_/
71
- @eth_header.send(sym,*args)
72
- when /^arp_/
73
- @arp_header.send(sym,*args)
74
- when /^ip_/
75
- @ip_header.send(sym,*args)
76
- when /^icmp_/
77
- @icmp_header.send(sym,*args)
78
- when /^udp_/
79
- @udp_header.send(sym,*args)
80
- when /^tcp_/
81
- @tcp_header.send(sym,*args)
82
- when /^ipv6_/
83
- @ipv6_header.send(sym,*args)
46
+ def handle_is_identity(ptype)
47
+ idx = PacketFu.packet_prefixes.index(ptype.to_s.downcase)
48
+ if idx
49
+ self.kind_of? PacketFu.packet_classes[idx]
84
50
  else
85
- raise NoMethodError, "Unknown method `#{sym}' for this packet object."
51
+ raise NoMethodError, "Undefined method `is_#{ptype}?' for #{self.class}."
86
52
  end
87
53
  end
88
54
 
@@ -168,13 +134,6 @@ module PacketFu
168
134
  # Read() takes (and trusts) the io input and shoves it all into a well-formed Packet.
169
135
  # Note that read is a destructive process, so any existing data will be lost.
170
136
  #
171
- # TODO: This giant if tree is a mess, and worse, is decieving. You need to define
172
- # actions both here and in parse(). All read() does is make a (good) guess as to
173
- # what @headers to expect, and reads data to them.
174
- #
175
- # To take strings and turn them into packets without knowing ahead of time what kind of
176
- # packet it is, use Packet.parse instead; parse() handles the figuring-out part.
177
- #
178
137
  # A note on the :strip => true argument: If :strip is set, defined lengths of data will
179
138
  # be believed, and any trailers (such as frame check sequences) will be chopped off. This
180
139
  # helps to ensure well-formed packets, at the cost of losing perhaps important FCS data.
@@ -188,123 +147,18 @@ module PacketFu
188
147
  # correctly.
189
148
  #
190
149
  # So, to summarize; if you intend to alter the data, use :strip. If you don't, don't. Also,
191
- # this is a horrid XXX hack. Stripping is useful (and fun!), but the default behavior really
150
+ # this is a horrid hack. Stripping is useful (and fun!), but the default behavior really
192
151
  # should be to create payloads correctly, and /not/ treat extra FCS data as a payload.
193
152
  #
194
- # Update: This scheme is so lame. Need to fix. Seriously.
195
- # Update: still sucks. Really.
196
- def read(io,args={})
197
- begin
198
- if io.size >= 14
199
- @eth_header.read(io)
200
- eth_proto_num = io[12,2].unpack("n")[0]
201
- if eth_proto_num == 0x0800 # It's IP.
202
- if 1.respond_to? :ord
203
- ipv = io[14].ord
204
- else
205
- ipv = io[14]
206
- end
207
- ip_hlen=(ipv & 0x0f) * 4
208
- ip_ver=(ipv >> 4) # It's IPv4. Other versions, all bets are off!
209
- if ip_ver == 4
210
- ip_proto_num = io[23,1].unpack("C")[0]
211
- @ip_header.read(io[14,ip_hlen])
212
- if ip_proto_num == 0x06 # It's TCP.
213
- tcp_len = io[16,2].unpack("n")[0] - 20
214
- if args[:strip] # Drops trailers like frame check sequence (FCS). Often desired for cleaner packets.
215
- tcp_all = io[ip_hlen+14,tcp_len] # Believe the tcp_len value; chop off anything that's not in range.
216
- else
217
- tcp_all = io[ip_hlen+14,0xffff] # Don't believe the tcp_len value; suck everything up.
218
- end
219
- tcp_hlen = ((tcp_all[12,1].unpack("C")[0]) >> 4) * 4
220
- if tcp_hlen.to_i >= 20
221
- @tcp_header.read(tcp_all)
222
- @ip_header.body = @tcp_header
223
- else # It's a TCP packet with an impossibly small hlen, so it can't be real TCP. Abort! Abort!
224
- @ip_header.body = io[16,io.size-16]
225
- end
226
- elsif ip_proto_num == 0x11 # It's UDP.
227
- udp_len = io[16,2].unpack("n")[0] - 20
228
- if args[:strip] # Same deal as with TCP. We might have stuff at the end of the packet that's not part of the payload.
229
- @udp_header.read(io[ip_hlen+14,udp_len])
230
- else # ... Suck it all up. BTW, this will change the lengths if they are ever recalc'ed. Bummer.
231
- @udp_header.read(io[ip_hlen+14,0xffff])
232
- end
233
- @ip_header.body = @udp_header
234
- elsif ip_proto_num == 1 # It's ICMP
235
- @icmp_header.read(io[ip_hlen+14,0xffff])
236
- @ip_header.body = @icmp_header
237
- else # It's an IP packet for a protocol we don't have a decoder for.
238
- @ip_header.body = io[16,io.size-16]
239
- end
240
- else # It's not IPv4, so no idea what should come next. Just dump it all into an ip_header and ip payload.
241
- @ip_header.read(io[14,ip_hlen])
242
- @ip_header.body = io[16,io.size-16]
243
- end
244
- @eth_header.body = @ip_header
245
- elsif eth_proto_num == 0x0806 # It's ARP
246
- @arp_header.read(io[14,0xffff]) # You'll nearly have a trailer and you'll never know what size.
247
- @eth_header.body=@arp_header
248
- @eth_header.body
249
- elsif eth_proto_num == 0x86dd # It's IPv6
250
- @ipv6_header.read(io[14,0xffff])
251
- @eth_header.body=@ipv6_header
252
- else # It's an Ethernet packet for a protocol we don't have a decoder for
253
- @eth_header.body = io[14,io.size-14]
254
- end
255
- if (args[:fix] || args[:recalc])
256
- # Unfortunately, we cannot simply recalc with abandon, since
257
- # we may have unaccounted trailers that will sneak into the checksum.
258
- # The better way to handle this is to put trailers in their own
259
- # StructFu field, but I'm not a-gonna right now. :/
260
- ip_recalc(:ip_sum) if respond_to? :ip_header
261
- recalc(:tcp) if respond_to? :tcp_header
262
- recalc(:udp) if respond_to? :udp_header
263
- end
264
- else # You're not big enough for Ethernet.
265
- @invalid_header.read(io)
266
- end
267
- # @headers[0]
268
- self
269
- rescue ::Exception => e
270
- # remove last header
271
- # nested_types = self.headers.collect {|header| header.class}
272
- # nested_types.pop # whatever this packet type is, we weren't able to parse it
273
- self.headers.pop
274
- return_header_type = self.headers[self.headers.length-1].class.to_s
275
- retklass = PacketFu::InvalidPacket
276
- seekpos = 0
277
- target_header = @invalid_header
278
- case return_header_type.to_s
279
- when "PacketFu::EthHeader"
280
- retklass = PacketFu::EthPacket
281
- seekpos = 0x0e
282
- target_header = @eth_header
283
- when "PacketFu::IPHeader"
284
- retklass = PacketFu::IPPacket
285
- seekpos = 0x0e + @ip_header.ip_hl * 4
286
- target_header = @ip_header
287
- when "PacketFu::TCPHeader"
288
- retklass = PacketFu::TCPPacket
289
- seekpos = 0x0e + @ip_header.ip_hl * 4 + @tcpheader.tcp_hlen
290
- target_header = @tcp_header
291
- when "PacketFu::UDPHeader"
292
- retklass = PacketFu::UDPPacket
293
- when "PacketFu::ARPHeader"
294
- retklass = PacketFu::ARPPacket
295
- when "PacketFu::ICMPHeader"
296
- retklass = PacketFu::ICMPPacket
297
- when "PacketFu::IPv6Header"
298
- retklass = PacketFu::IPv6Packet
299
- else
300
- end
301
-
302
- io = io[seekpos,io.length - seekpos]
303
- target_header.body = io
304
- p = retklass.new
305
- p.headers = self.headers
306
- p
307
- raise e if $debug
153
+ # Finally, packet subclasses should take two arguments: the string that is the data
154
+ # to be transmuted into a packet, as well as args. This superclass method is merely
155
+ # concerned with handling args common to many packet formats (namely, fixing packets
156
+ # on the fly)
157
+ def read(args={})
158
+ if args[:fix] || args[:recalc]
159
+ ip_recalc(:ip_sum) if self.is_ip?
160
+ recalc(:tcp) if self.is_tcp?
161
+ recalc(:udp) if self.is_udp?
308
162
  end
309
163
  end
310
164
 
@@ -318,8 +172,60 @@ module PacketFu
318
172
  peek_data.join
319
173
  end
320
174
 
175
+ # Defines the layer this packet type lives at, based on the number of headers it
176
+ # requires. Note that this has little to do with the OSI model, since TCP/IP
177
+ # doesn't really have Session and Presentation layers.
178
+ #
179
+ # Ethernet and the like are layer 1, IP, IPv6, and ARP are layer 2,
180
+ # TCP, UDP, and other transport protocols are layer 3, and application
181
+ # protocols are at layer 4 or higher. InvalidPackets have an arbitrary
182
+ # layer 0 to distinguish them.
183
+ #
184
+ # Because these don't change much, it's cheaper just to case through them,
185
+ # and only resort to counting headers if we don't have a match -- this
186
+ # makes adding protocols somewhat easier, but of course you can just
187
+ # override this method over there, too. This is merely optimized
188
+ # for the most likely protocols you see on the Internet.
189
+ def self.layer
190
+ case self.name # Lol ran into case's fancy treatment of classes
191
+ when /InvalidPacket$/; 0
192
+ when /EthPacket$/; 1
193
+ when /IPPacket$/, /ARPPacket$/, /IPv6Packet$/; 2
194
+ when /TCPPacket$/, /UDPPacket$/, /ICMPPacket$/; 3
195
+ when /HSRPPacket$/; 4
196
+ else; self.new.headers.size
197
+ end
198
+ end
199
+
200
+ def layer
201
+ self.class.layer
202
+ end
203
+
204
+ def self.layer_symbol
205
+ case self.layer
206
+ when 0; :invalid
207
+ when 1; :link
208
+ when 2; :internet
209
+ when 3; :transport
210
+ else; :application
211
+ end
212
+ end
213
+
214
+ def layer_symbol
215
+ self.class.layer_symbol
216
+ end
217
+
218
+ # Packet subclasses must override this, since the Packet superclass
219
+ # can't actually parse anything.
220
+ def self.can_parse?(str)
221
+ false
222
+ end
223
+
321
224
  # Hexify provides a neatly-formatted dump of binary data, familar to hex readers.
322
225
  def hexify(str)
226
+ if str.respond_to? :force_encoding
227
+ str.force_encoding("ASCII-8BIT")
228
+ end
323
229
  hexascii_lines = str.to_s.unpack("H*")[0].scan(/.{1,32}/)
324
230
  chars = str.to_s.gsub(/[\x00-\x1f\x7f-\xff]/,'.')
325
231
  chars_lines = chars.scan(/.{1,16}/)
@@ -375,6 +281,21 @@ module PacketFu
375
281
  end
376
282
  end
377
283
 
284
+ alias :orig_kind_of? :kind_of?
285
+
286
+ def kind_of?(klass)
287
+ return true if orig_kind_of? klass
288
+ packet_types = proto.map {|p| PacketFu.const_get("#{p}Packet")}
289
+ match = false
290
+ packet_types.each do |p|
291
+ if p.ancestors.include? klass
292
+ match = true
293
+ break
294
+ end
295
+ end
296
+ return match
297
+ end
298
+
378
299
  # For packets, inspect is overloaded as inspect_hex(0).
379
300
  # Not sure if this is a great idea yet, but it sure makes
380
301
  # the irb output more sane.
@@ -407,29 +328,6 @@ module PacketFu
407
328
  end
408
329
 
409
330
  alias_method :protocol, :proto
410
-
411
- # Returns true if this is an Invalid packet. Else, false.
412
- def is_invalid? ; self.proto.include? "Invalid"; end
413
- # Returns true if this is an Ethernet packet. Else, false.
414
- def is_ethernet? ; self.proto.include? "Eth"; end
415
- alias_method :is_eth?, :is_ethernet?
416
- # Returns true if this is an IP packet. Else, false.
417
- def is_ip? ; self.proto.include? "IP"; end
418
- # Returns true if this is an TCP packet. Else, false.
419
- def is_tcp? ; self.proto.include? "TCP"; end
420
- # Returns true if this is an UDP packet. Else, false.
421
- def is_udp? ; self.proto.include? "UDP"; end
422
- # Returns true if this is an ARP packet. Else, false.
423
- def is_arp? ; self.proto.include? "ARP"; end
424
- # Returns true if this is an IPv6 packet. Else, false.
425
- def is_ipv6? ; self.proto.include? "IPv6" ; end
426
- # Returns true if this is an ICMP packet. Else, false.
427
- def is_icmp? ; self.proto.include? "ICMP" ; end
428
- # Returns true if this is an IPv6 packet. Else, false.
429
- def is_ipv6? ; self.proto.include? "IPv6" ; end
430
- # Returns true if the outermost layer has data. Else, false.
431
- def has_data? ; self.payload.size.zero? ? false : true ; end
432
-
433
331
  alias_method :length, :size
434
332
 
435
333
  def initialize(args={})
@@ -445,6 +343,46 @@ module PacketFu
445
343
  end
446
344
  end
447
345
 
346
+ #method_missing() delegates protocol-specific field actions to the apporpraite
347
+ #class variable (which contains the associated packet type)
348
+ #This register-of-protocols style switch will work for the
349
+ #forseeable future (there aren't /that/ many packet types), and it's a handy
350
+ #way to know at a glance what packet types are supported.
351
+ def method_missing(sym, *args, &block)
352
+ case sym.to_s
353
+ when /^is_([a-zA-Z0-9]+)\?/
354
+ ptype = $1
355
+ if PacketFu.packet_prefixes.index(ptype)
356
+ self.send(:handle_is_identity, $1)
357
+ else
358
+ super
359
+ end
360
+ when /^([a-zA-Z0-9]+)_.+/
361
+ ptype = $1
362
+ if PacketFu.packet_prefixes.index(ptype)
363
+ self.instance_variable_get("@#{ptype}_header").send(sym,*args, &block)
364
+ else
365
+ super
366
+ end
367
+ else
368
+ super
369
+ end
370
+ end
371
+
372
+ def respond_to?(sym, include_private = false)
373
+ if sym.to_s =~ /^(invalid|eth|arp|ip|icmp|udp|hsrp|tcp|ipv6)_/
374
+ self.instance_variable_get("@#{$1}_header").respond_to? sym
375
+ elsif sym.to_s =~ /^is_([a-zA-Z0-9]+)\?/
376
+ if PacketFu.packet_prefixes.index($1)
377
+ true
378
+ else
379
+ super
380
+ end
381
+ else
382
+ super
383
+ end
384
+ end
385
+
448
386
  end # class Packet
449
387
 
450
388
  @@inspect_style = :pretty