packetfu 1.1.9 → 1.1.10

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