packetfu 1.1.9 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. data/bench/octets.rb +9 -9
  2. data/examples/100kpackets.rb +12 -12
  3. data/examples/ackscan.rb +16 -16
  4. data/examples/arp.rb +35 -35
  5. data/examples/arphood.rb +36 -36
  6. data/examples/dissect_thinger.rb +6 -6
  7. data/examples/new-simple-stats.rb +23 -23
  8. data/examples/packetfu-shell.rb +25 -25
  9. data/examples/simple-sniffer.rb +9 -9
  10. data/examples/simple-stats.rb +23 -23
  11. data/examples/slammer.rb +3 -3
  12. data/lib/packetfu.rb +127 -127
  13. data/lib/packetfu/capture.rb +169 -169
  14. data/lib/packetfu/config.rb +52 -52
  15. data/lib/packetfu/inject.rb +56 -56
  16. data/lib/packetfu/packet.rb +528 -528
  17. data/lib/packetfu/pcap.rb +579 -579
  18. data/lib/packetfu/protos/arp.rb +90 -90
  19. data/lib/packetfu/protos/arp/header.rb +158 -158
  20. data/lib/packetfu/protos/arp/mixin.rb +36 -36
  21. data/lib/packetfu/protos/eth.rb +44 -44
  22. data/lib/packetfu/protos/eth/header.rb +243 -243
  23. data/lib/packetfu/protos/eth/mixin.rb +3 -3
  24. data/lib/packetfu/protos/hsrp.rb +69 -69
  25. data/lib/packetfu/protos/hsrp/header.rb +107 -107
  26. data/lib/packetfu/protos/hsrp/mixin.rb +29 -29
  27. data/lib/packetfu/protos/icmp.rb +71 -71
  28. data/lib/packetfu/protos/icmp/header.rb +82 -82
  29. data/lib/packetfu/protos/icmp/mixin.rb +14 -14
  30. data/lib/packetfu/protos/invalid.rb +49 -49
  31. data/lib/packetfu/protos/ip.rb +69 -69
  32. data/lib/packetfu/protos/ip/header.rb +291 -291
  33. data/lib/packetfu/protos/ip/mixin.rb +40 -40
  34. data/lib/packetfu/protos/ipv6.rb +50 -50
  35. data/lib/packetfu/protos/ipv6/header.rb +188 -188
  36. data/lib/packetfu/protos/ipv6/mixin.rb +29 -29
  37. data/lib/packetfu/protos/tcp.rb +176 -176
  38. data/lib/packetfu/protos/tcp/ecn.rb +35 -35
  39. data/lib/packetfu/protos/tcp/flags.rb +74 -74
  40. data/lib/packetfu/protos/tcp/header.rb +268 -268
  41. data/lib/packetfu/protos/tcp/hlen.rb +32 -32
  42. data/lib/packetfu/protos/tcp/mixin.rb +46 -46
  43. data/lib/packetfu/protos/tcp/option.rb +321 -321
  44. data/lib/packetfu/protos/tcp/options.rb +95 -95
  45. data/lib/packetfu/protos/tcp/reserved.rb +35 -35
  46. data/lib/packetfu/protos/udp.rb +116 -116
  47. data/lib/packetfu/protos/udp/header.rb +91 -91
  48. data/lib/packetfu/protos/udp/mixin.rb +3 -3
  49. data/lib/packetfu/structfu.rb +280 -280
  50. data/lib/packetfu/utils.rb +226 -217
  51. data/lib/packetfu/version.rb +41 -41
  52. data/packetfu.gemspec +2 -1
  53. data/spec/ethpacket_spec.rb +48 -48
  54. data/spec/packet_spec.rb +57 -57
  55. data/spec/packet_subclasses_spec.rb +8 -8
  56. data/spec/packetfu_spec.rb +59 -59
  57. data/spec/structfu_spec.rb +268 -268
  58. data/spec/tcp_spec.rb +75 -75
  59. data/test/all_tests.rb +13 -13
  60. data/test/func_lldp.rb +3 -3
  61. data/test/ptest.rb +2 -2
  62. data/test/test_arp.rb +116 -116
  63. data/test/test_capture.rb +45 -45
  64. data/test/test_eth.rb +68 -68
  65. data/test/test_hsrp.rb +9 -9
  66. data/test/test_icmp.rb +52 -52
  67. data/test/test_inject.rb +18 -18
  68. data/test/test_invalid.rb +16 -16
  69. data/test/test_ip.rb +36 -36
  70. data/test/test_ip6.rb +48 -48
  71. data/test/test_octets.rb +21 -21
  72. data/test/test_packet.rb +154 -154
  73. data/test/test_pcap.rb +170 -170
  74. data/test/test_structfu.rb +97 -97
  75. data/test/test_tcp.rb +320 -320
  76. data/test/test_udp.rb +76 -76
  77. metadata +4 -3
@@ -16,33 +16,33 @@ require 'packetfu'
16
16
  # Takes a file name, parses the packets, and records the packet
17
17
  # type based on its PacketFu class.
18
18
  def count_packet_types(file)
19
- file = File.open(file) {|f| f.read}
20
- stats = {}
21
- count = 0
22
- pcapfile = PacketFu::PcapPackets.new
23
- pcapfile.read(file)
24
- pcapfile.each do |p|
25
- # Now it's a PacketFu packet struct.
26
- pkt = PacketFu::Packet.parse(p.data)
27
- kind = pkt.class.to_s.split("::").last
28
- if stats[kind]
29
- stats[kind] += 1
30
- else
31
- stats[kind] = 0
32
- end
33
- count += 1
34
- break if count >= 1_000
35
- end
36
- stats.each_pair { |k,v| puts "%-12s: %4d" % [k,v] }
19
+ file = File.open(file) {|f| f.read}
20
+ stats = {}
21
+ count = 0
22
+ pcapfile = PacketFu::PcapPackets.new
23
+ pcapfile.read(file)
24
+ pcapfile.each do |p|
25
+ # Now it's a PacketFu packet struct.
26
+ pkt = PacketFu::Packet.parse(p.data)
27
+ kind = pkt.class.to_s.split("::").last
28
+ if stats[kind]
29
+ stats[kind] += 1
30
+ else
31
+ stats[kind] = 0
32
+ end
33
+ count += 1
34
+ break if count >= 1_000
35
+ end
36
+ stats.each_pair { |k,v| puts "%-12s: %4d" % [k,v] }
37
37
  end
38
38
 
39
39
  if File.readable?(infile = (ARGV[0] || 'in.pcap'))
40
- title = "Packets by packet type in '#{infile}'"
41
- puts title
42
- puts "-" * title.size
43
- count_packet_types(infile)
40
+ title = "Packets by packet type in '#{infile}'"
41
+ puts title
42
+ puts "-" * title.size
43
+ count_packet_types(infile)
44
44
  else
45
- raise RuntimeError, "Need an infile, like so: #{$0} in.pcap"
45
+ raise RuntimeError, "Need an infile, like so: #{$0} in.pcap"
46
46
  end
47
47
 
48
48
 
@@ -14,7 +14,7 @@ include PacketFu
14
14
  slammer = "\004\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\334\311\260B\353\016" + "\001\001\001\001\001\001\001p\256B\001p\256B\220\220\220\220\220\220\220\220h\334\311\260B\270\001\001" + "\001\0011\311\261\030P\342\3755\001\001\001\005P\211\345Qh.dllhel32hkernQhounthickChGetTf" + "\271llQh32.dhws2_f\271etQhsockf\271toQhsend\276\030\020\256B\215E\324P\377\026P\215E\340P\215E\360P\377" + "\026P\276\020\020\256B\213\036\213\003=U\213\354Qt\005\276\034\020\256B\377\026\377\3201\311QQP\201\361" + "\003\001\004\233\201\361\001\001\001\001Q\215E\314P\213E\300P\377\026j\021j\002j\002\377\320P\215E\304P" + "\213E\300P\377\026\211\306\t\333\201\363<a\331\377\213E\264\215\f@\215\024\210\301\342\004\001\302\301" + "\342\b)\302\215\004\220\001\330\211E\264j\020\215E\260P1\311Qf\201\361x\001Q\215E\003P\213E\254P\377\326" + "\353\312"
15
15
 
16
16
  def rand_source_ip
17
- [rand(0xffffffff)].pack("N")
17
+ [rand(0xffffffff)].pack("N")
18
18
  end
19
19
 
20
20
  kill_packet = UDPPacket.new
@@ -26,9 +26,9 @@ kill_packet.recalc
26
26
  kill_packet.payload = slammer
27
27
 
28
28
  if action == 'file'.downcase
29
- puts kill_packet.to_f
29
+ puts kill_packet.to_f
30
30
  else
31
- puts kill_packet.to_w(action.downcase)
31
+ puts kill_packet.to_w(action.downcase)
32
32
  end
33
33
 
34
34
 
@@ -13,133 +13,133 @@ require 'rubygems' if RUBY_VERSION =~ /^1\.[0-8]/
13
13
 
14
14
  module PacketFu
15
15
 
16
- # Picks up all the protocols defined in the protos subdirectory
17
- def self.require_protos(cwd)
18
- protos_dir = File.join(cwd, "packetfu", "protos")
19
- Dir.new(protos_dir).each do |fname|
20
- next unless fname[/\.rb$/]
21
- begin
22
- require File.join(protos_dir,fname)
23
- rescue
24
- warn "Warning: Could not load `#{fname}'. Skipping."
25
- end
26
- end
27
- end
28
-
29
- # Deal with Ruby's encoding by ignoring it.
30
- def self.force_binary(str)
31
- str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding
32
- end
33
-
34
- # Sets the expected byte order for a pcap file. See PacketFu::Read.set_byte_order
35
- @byte_order = :little
36
-
37
- # Checks if pcaprub is loaded correctly.
38
- @pcaprub_loaded = false
39
-
40
- # PacketFu works best with Pcaprub version 0.8-dev (at least)
41
- # The current (Aug 01, 2010) pcaprub gem is 0.9, so should be fine.
42
- def self.pcaprub_platform_require
43
- begin
44
- require 'pcaprub'
45
- rescue LoadError
46
- return false
47
- end
48
- @pcaprub_loaded = true
49
- end
50
-
51
- pcaprub_platform_require
52
-
53
- if @pcaprub_loaded
54
- pcaprub_regex = /[0-9]\.([8-9]|[1-7][0-9])(-dev)?/ # Regex for 0.8 and beyond.
55
- if Pcap.version !~ pcaprub_regex
56
- @pcaprub_loaded = false # Don't bother with broken versions
57
- raise LoadError, "PcapRub not at a minimum version of 0.8-dev"
58
- end
59
- require "packetfu/capture"
60
- require "packetfu/inject"
61
- end
62
-
63
- # Returns the status of pcaprub
64
- def self.pcaprub_loaded?
65
- @pcaprub_loaded
66
- end
67
-
68
- # Returns an array of classes defined in PacketFu
69
- def self.classes
70
- constants.map { |const| const_get(const) if const_get(const).kind_of? Class}.compact
71
- end
72
-
73
- # Adds the class to PacketFu's list of packet classes -- used in packet parsing.
74
- def self.add_packet_class(klass)
75
- raise "Need a class" unless klass.kind_of? Class
76
- if klass.name !~ /[A-Za-z0-9]Packet/
77
- raise "Packet classes should be named 'ProtoPacket'"
78
- end
79
- @packet_classes ||= []
80
- @packet_classes << klass
81
- @packet_classes_dirty = true
82
- @packet_classes.sort! {|x,y| x.name <=> y.name}
83
- end
84
-
85
- # Presumably, there may be a time where you'd like to remove a packet class.
86
- def self.remove_packet_class(klass)
87
- raise "Need a class" unless klass.kind_of? Class
88
- @packet_classes ||= []
89
- @packet_classes.delete klass
90
- @packet_classes_dirty = true
91
- @packet_classes
92
- end
93
-
94
- # Returns an array of packet classes
95
- def self.packet_classes
96
- @packet_classes || []
97
- end
98
-
99
- # Returns an array of packet types by packet prefix.
100
- def self.packet_prefixes
101
- return [] if @packet_classes.nil?
102
- return @packet_class_prefixes if @packet_classes_dirty == false
103
- @packet_classes_dirty = false
104
- @packet_class_prefixes = @packet_classes.map {|p| p.to_s.split("::").last.to_s.downcase.gsub(/packet$/,"")}
105
- return @packet_class_prefixes
106
- end
107
-
108
- # The current inspect style. One of :hex, :dissect, or :default
109
- # Note that :default means Ruby's default, which is usually
110
- # far too long to be useful.
111
- def self.inspect_style
112
- @inspect_style ||= :dissect
113
- end
114
-
115
- # Setter for PacketFu's @inspect_style
116
- def self.inspect_style=(arg)
117
- @inspect_style = case arg
118
- when :hex, :pretty
119
- :hex
120
- when :dissect, :verbose
121
- :dissect
122
- when :default, :ugly
123
- :default
124
- else
125
- :dissect
126
- end
127
- end
128
-
129
- # Switches inspect styles in a round-robin fashion between
130
- # :dissect, :default, and :hex
131
- def toggle_inspect
132
- case @inspect_style
133
- when :hex, :pretty
134
- @inspect_style = :dissect
135
- when :dissect, :verbose
136
- @inspect_style = :default
137
- when :default, :ugly
138
- @inspect_style = :hex
139
- else
140
- @inspect_style = :dissect
141
- end
142
- end
16
+ # Picks up all the protocols defined in the protos subdirectory
17
+ def self.require_protos(cwd)
18
+ protos_dir = File.join(cwd, "packetfu", "protos")
19
+ Dir.new(protos_dir).each do |fname|
20
+ next unless fname[/\.rb$/]
21
+ begin
22
+ require File.join(protos_dir,fname)
23
+ rescue
24
+ warn "Warning: Could not load `#{fname}'. Skipping."
25
+ end
26
+ end
27
+ end
28
+
29
+ # Deal with Ruby's encoding by ignoring it.
30
+ def self.force_binary(str)
31
+ str.force_encoding Encoding::BINARY if str.respond_to? :force_encoding
32
+ end
33
+
34
+ # Sets the expected byte order for a pcap file. See PacketFu::Read.set_byte_order
35
+ @byte_order = :little
36
+
37
+ # Checks if pcaprub is loaded correctly.
38
+ @pcaprub_loaded = false
39
+
40
+ # PacketFu works best with Pcaprub version 0.8-dev (at least)
41
+ # The current (Aug 01, 2010) pcaprub gem is 0.9, so should be fine.
42
+ def self.pcaprub_platform_require
43
+ begin
44
+ require 'pcaprub'
45
+ rescue LoadError
46
+ return false
47
+ end
48
+ @pcaprub_loaded = true
49
+ end
50
+
51
+ pcaprub_platform_require
52
+
53
+ if @pcaprub_loaded
54
+ pcaprub_regex = /[0-9]\.([8-9]|[1-7][0-9])(-dev)?/ # Regex for 0.8 and beyond.
55
+ if Pcap.version !~ pcaprub_regex
56
+ @pcaprub_loaded = false # Don't bother with broken versions
57
+ raise LoadError, "PcapRub not at a minimum version of 0.8-dev"
58
+ end
59
+ require "packetfu/capture"
60
+ require "packetfu/inject"
61
+ end
62
+
63
+ # Returns the status of pcaprub
64
+ def self.pcaprub_loaded?
65
+ @pcaprub_loaded
66
+ end
67
+
68
+ # Returns an array of classes defined in PacketFu
69
+ def self.classes
70
+ constants.map { |const| const_get(const) if const_get(const).kind_of? Class}.compact
71
+ end
72
+
73
+ # Adds the class to PacketFu's list of packet classes -- used in packet parsing.
74
+ def self.add_packet_class(klass)
75
+ raise "Need a class" unless klass.kind_of? Class
76
+ if klass.name !~ /[A-Za-z0-9]Packet/
77
+ raise "Packet classes should be named 'ProtoPacket'"
78
+ end
79
+ @packet_classes ||= []
80
+ @packet_classes << klass
81
+ @packet_classes_dirty = true
82
+ @packet_classes.sort! {|x,y| x.name <=> y.name}
83
+ end
84
+
85
+ # Presumably, there may be a time where you'd like to remove a packet class.
86
+ def self.remove_packet_class(klass)
87
+ raise "Need a class" unless klass.kind_of? Class
88
+ @packet_classes ||= []
89
+ @packet_classes.delete klass
90
+ @packet_classes_dirty = true
91
+ @packet_classes
92
+ end
93
+
94
+ # Returns an array of packet classes
95
+ def self.packet_classes
96
+ @packet_classes || []
97
+ end
98
+
99
+ # Returns an array of packet types by packet prefix.
100
+ def self.packet_prefixes
101
+ return [] if @packet_classes.nil?
102
+ return @packet_class_prefixes if @packet_classes_dirty == false
103
+ @packet_classes_dirty = false
104
+ @packet_class_prefixes = @packet_classes.map {|p| p.to_s.split("::").last.to_s.downcase.gsub(/packet$/,"")}
105
+ return @packet_class_prefixes
106
+ end
107
+
108
+ # The current inspect style. One of :hex, :dissect, or :default
109
+ # Note that :default means Ruby's default, which is usually
110
+ # far too long to be useful.
111
+ def self.inspect_style
112
+ @inspect_style ||= :dissect
113
+ end
114
+
115
+ # Setter for PacketFu's @inspect_style
116
+ def self.inspect_style=(arg)
117
+ @inspect_style = case arg
118
+ when :hex, :pretty
119
+ :hex
120
+ when :dissect, :verbose
121
+ :dissect
122
+ when :default, :ugly
123
+ :default
124
+ else
125
+ :dissect
126
+ end
127
+ end
128
+
129
+ # Switches inspect styles in a round-robin fashion between
130
+ # :dissect, :default, and :hex
131
+ def toggle_inspect
132
+ case @inspect_style
133
+ when :hex, :pretty
134
+ @inspect_style = :dissect
135
+ when :dissect, :verbose
136
+ @inspect_style = :default
137
+ when :default, :ugly
138
+ @inspect_style = :hex
139
+ else
140
+ @inspect_style = :dissect
141
+ end
142
+ end
143
143
 
144
144
 
145
145
  end
@@ -1,173 +1,173 @@
1
1
  # -*- coding: binary -*-
2
2
  module PacketFu
3
3
 
4
- # The Capture class is used to construct PcapRub objects in order to collect
5
- # packets from an interface.
6
- #
7
- # This class requires PcapRub. In addition, you will need root (or root-like) privileges
8
- # in order to capture from the interface.
9
- #
10
- # Note, on some wireless cards, setting :promisc => true will disable capturing.
11
- #
12
- # == Example
13
- #
14
- # # Typical use
15
- # cap = PacketFu::Capture.new(:iface => 'eth0', :promisc => true)
16
- # cap.start
17
- # sleep 10
18
- # cap.save
19
- # first_packet = cap.array[0]
20
- #
21
- # # Tcpdump-like use
22
- # cap = PacketFu::Capture.new(:start => true)
23
- # cap.show_live(:save => true, :filter => 'tcp and not port 22')
24
- #
25
- # == See Also
26
- #
27
- # Read, Write
28
- class Capture
29
- attr_accessor :array, :stream # Leave these public and open.
30
- attr_reader :iface, :snaplen, :promisc, :timeout, :filter
31
-
32
- def initialize(args={})
33
- @array = [] # Where the packet array goes.
34
- @stream = [] # Where the stream goes.
35
- @iface = (args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo").to_s
36
- @snaplen = args[:snaplen] || 0xffff
37
- @promisc = args[:promisc] || false # Sensible for some Intel wifi cards
38
- @timeout = args[:timeout] || 1
39
- @filter = args[:filter] || args[:bpf]
40
- setup_params(args)
41
- end
42
-
43
- # Used by new().
44
- def setup_params(args={})
45
- filter = args[:filter] || args[:bpf] || @filter
46
- start = args[:start] || false
47
- capture if start
48
- bpf(:filter=>filter) if filter
49
- end
50
-
51
- # capture() initializes the @stream varaible. Valid arguments are:
52
- #
53
- # :filter
54
- # Provide a bpf filter to enable for the capture. For example, 'ip and not tcp'
55
- # :start
56
- # When true, start capturing packets to the @stream variable. Defaults to true
57
- def capture(args={})
58
- if Process.euid.zero?
59
- filter = args[:filter] || args[:bpf] || @filter
60
- start = args[:start] || true
61
- if start
62
- begin
63
- @stream = Pcap.open_live(@iface,@snaplen,@promisc,@timeout)
64
- rescue RuntimeError
65
- $stderr.print "Are you sure you're root? Error: "
66
- raise
67
- end
68
- bpf(:filter=>filter) if filter
69
- else
70
- @stream = []
71
- end
72
- @stream
73
- else
74
- raise RuntimeError,"Not root, so can't capture packets. Error: "
75
- end
76
- end
77
-
78
- # start() is equivalent to capture().
79
- def start(args={})
80
- capture(args)
81
- end
82
-
83
- # clear() clears the @stream and @array variables, essentially starting the
84
- # capture session over. Valid arguments are:
85
- #
86
- # :array
87
- # If true, the @array is cleared.
88
- # :stream
89
- # If true, the @stream is cleared.
90
- def clear(args={})
91
- array = args[:array] || true
92
- stream = args[:stream] || true
93
- @array = [] if array
94
- @stream = [] if stream
95
- end
96
-
97
- # bpf() sets a bpf filter on a capture session. Valid arugments are:
98
- #
99
- # :filter
100
- # Provide a bpf filter to enable for the capture. For example, 'ip and not tcp'
101
- def bpf(args={})
102
- filter = args[:filter] || args[:bpf] || @filter
103
- capture if @stream.class == Array
104
- @stream.setfilter(filter) if filter
105
- @filter = filter
106
- end
107
-
108
- alias :filter :bpf
109
-
110
- # wire_to_array() saves a packet stream as an array of binary strings. From here,
111
- # packets may accessed by other functions. Note that the wire_to_array empties
112
- # the stream, so multiple calls will append new packets to @array.
113
- # Valid arguments are:
114
- #
115
- # :filter
116
- # Provide a bpf filter to apply to packets moving from @stream to @array.
117
- def wire_to_array(args={})
118
- filter = args[:filter] || args[:bpf] || @filter
119
- bpf(:filter=>filter) if filter
120
-
121
- while this_pkt = @stream.next
122
- @array << this_pkt
123
- end
124
- @array.size
125
- end
126
-
127
- # next() exposes the Stream object's next method to the outside world.
128
- def next
129
- return @stream.next
130
- end
131
-
132
- # w2a() is a equivalent to wire_to_array()
133
- def w2a(args={})
134
- wire_to_array(args)
135
- end
136
-
137
- # save() is a equivalent to wire_to_array()
138
- def save(args={})
139
- wire_to_array(args)
140
- end
141
-
142
- # show_live() is a method to capture packets and display peek() data to stdout. Valid arguments are:
143
- #
144
- # :filter
145
- # Provide a bpf filter to captured packets.
146
- # :save
147
- # Save the capture in @array
148
- # :verbose
149
- # TODO: Not implemented yet; do more than just peek() at the packets.
150
- # :quiet
151
- # TODO: Not implemented yet; do less than peek() at the packets.
152
- def show_live(args={})
153
- filter = args[:filter] || args[:bpf] || @filter
154
- save = args[:save]
155
- verbose = args[:verbose] || args[:v] || false
156
- quiet = args[:quiet] || args[:q] || false # Setting q and v doesn't make a lot of sense but hey.
157
-
158
- # Ensure the capture's started.
159
- if @stream.class == Array
160
- capture
161
- end
162
-
163
- @stream.setfilter(filter) if filter
164
- while true
165
- @stream.each do |pkt|
166
- puts Packet.parse(pkt).peek
167
- @array << pkt if args[:save]
168
- end
169
- end
170
- end
171
-
172
- end
4
+ # The Capture class is used to construct PcapRub objects in order to collect
5
+ # packets from an interface.
6
+ #
7
+ # This class requires PcapRub. In addition, you will need root (or root-like) privileges
8
+ # in order to capture from the interface.
9
+ #
10
+ # Note, on some wireless cards, setting :promisc => true will disable capturing.
11
+ #
12
+ # == Example
13
+ #
14
+ # # Typical use
15
+ # cap = PacketFu::Capture.new(:iface => 'eth0', :promisc => true)
16
+ # cap.start
17
+ # sleep 10
18
+ # cap.save
19
+ # first_packet = cap.array[0]
20
+ #
21
+ # # Tcpdump-like use
22
+ # cap = PacketFu::Capture.new(:start => true)
23
+ # cap.show_live(:save => true, :filter => 'tcp and not port 22')
24
+ #
25
+ # == See Also
26
+ #
27
+ # Read, Write
28
+ class Capture
29
+ attr_accessor :array, :stream # Leave these public and open.
30
+ attr_reader :iface, :snaplen, :promisc, :timeout, :filter
31
+
32
+ def initialize(args={})
33
+ @array = [] # Where the packet array goes.
34
+ @stream = [] # Where the stream goes.
35
+ @iface = (args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo").to_s
36
+ @snaplen = args[:snaplen] || 0xffff
37
+ @promisc = args[:promisc] || false # Sensible for some Intel wifi cards
38
+ @timeout = args[:timeout] || 1
39
+ @filter = args[:filter] || args[:bpf]
40
+ setup_params(args)
41
+ end
42
+
43
+ # Used by new().
44
+ def setup_params(args={})
45
+ filter = args[:filter] || args[:bpf] || @filter
46
+ start = args[:start] || false
47
+ capture if start
48
+ bpf(:filter=>filter) if filter
49
+ end
50
+
51
+ # capture() initializes the @stream varaible. Valid arguments are:
52
+ #
53
+ # :filter
54
+ # Provide a bpf filter to enable for the capture. For example, 'ip and not tcp'
55
+ # :start
56
+ # When true, start capturing packets to the @stream variable. Defaults to true
57
+ def capture(args={})
58
+ if Process.euid.zero?
59
+ filter = args[:filter] || args[:bpf] || @filter
60
+ start = args[:start] || true
61
+ if start
62
+ begin
63
+ @stream = Pcap.open_live(@iface,@snaplen,@promisc,@timeout)
64
+ rescue RuntimeError
65
+ $stderr.print "Are you sure you're root? Error: "
66
+ raise
67
+ end
68
+ bpf(:filter=>filter) if filter
69
+ else
70
+ @stream = []
71
+ end
72
+ @stream
73
+ else
74
+ raise RuntimeError,"Not root, so can't capture packets. Error: "
75
+ end
76
+ end
77
+
78
+ # start() is equivalent to capture().
79
+ def start(args={})
80
+ capture(args)
81
+ end
82
+
83
+ # clear() clears the @stream and @array variables, essentially starting the
84
+ # capture session over. Valid arguments are:
85
+ #
86
+ # :array
87
+ # If true, the @array is cleared.
88
+ # :stream
89
+ # If true, the @stream is cleared.
90
+ def clear(args={})
91
+ array = args[:array] || true
92
+ stream = args[:stream] || true
93
+ @array = [] if array
94
+ @stream = [] if stream
95
+ end
96
+
97
+ # bpf() sets a bpf filter on a capture session. Valid arugments are:
98
+ #
99
+ # :filter
100
+ # Provide a bpf filter to enable for the capture. For example, 'ip and not tcp'
101
+ def bpf(args={})
102
+ filter = args[:filter] || args[:bpf] || @filter
103
+ capture if @stream.class == Array
104
+ @stream.setfilter(filter) if filter
105
+ @filter = filter
106
+ end
107
+
108
+ alias :filter :bpf
109
+
110
+ # wire_to_array() saves a packet stream as an array of binary strings. From here,
111
+ # packets may accessed by other functions. Note that the wire_to_array empties
112
+ # the stream, so multiple calls will append new packets to @array.
113
+ # Valid arguments are:
114
+ #
115
+ # :filter
116
+ # Provide a bpf filter to apply to packets moving from @stream to @array.
117
+ def wire_to_array(args={})
118
+ filter = args[:filter] || args[:bpf] || @filter
119
+ bpf(:filter=>filter) if filter
120
+
121
+ while this_pkt = @stream.next
122
+ @array << this_pkt
123
+ end
124
+ @array.size
125
+ end
126
+
127
+ # next() exposes the Stream object's next method to the outside world.
128
+ def next
129
+ return @stream.next
130
+ end
131
+
132
+ # w2a() is a equivalent to wire_to_array()
133
+ def w2a(args={})
134
+ wire_to_array(args)
135
+ end
136
+
137
+ # save() is a equivalent to wire_to_array()
138
+ def save(args={})
139
+ wire_to_array(args)
140
+ end
141
+
142
+ # show_live() is a method to capture packets and display peek() data to stdout. Valid arguments are:
143
+ #
144
+ # :filter
145
+ # Provide a bpf filter to captured packets.
146
+ # :save
147
+ # Save the capture in @array
148
+ # :verbose
149
+ # TODO: Not implemented yet; do more than just peek() at the packets.
150
+ # :quiet
151
+ # TODO: Not implemented yet; do less than peek() at the packets.
152
+ def show_live(args={})
153
+ filter = args[:filter] || args[:bpf] || @filter
154
+ save = args[:save]
155
+ verbose = args[:verbose] || args[:v] || false
156
+ quiet = args[:quiet] || args[:q] || false # Setting q and v doesn't make a lot of sense but hey.
157
+
158
+ # Ensure the capture's started.
159
+ if @stream.class == Array
160
+ capture
161
+ end
162
+
163
+ @stream.setfilter(filter) if filter
164
+ while true
165
+ @stream.each do |pkt|
166
+ puts Packet.parse(pkt).peek
167
+ @array << pkt if args[:save]
168
+ end
169
+ end
170
+ end
171
+
172
+ end
173
173
  end