packetfu 1.0.0 → 1.0.2.pre

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