caper 0.2.0

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 (53) hide show
  1. data/.gitignore +9 -0
  2. data/History.rdoc +31 -0
  3. data/LICENSE.ffi-pcap +23 -0
  4. data/README.rdoc +55 -0
  5. data/Rakefile +26 -0
  6. data/VERSION +1 -0
  7. data/examples/ipfw_divert.rb +45 -0
  8. data/examples/print_bytes.rb +17 -0
  9. data/examples/test_loop.rb +48 -0
  10. data/lib/caper.rb +40 -0
  11. data/lib/caper/addr.rb +19 -0
  12. data/lib/caper/bpf.rb +104 -0
  13. data/lib/caper/bsd.rb +96 -0
  14. data/lib/caper/capture_wrapper.rb +287 -0
  15. data/lib/caper/common_wrapper.rb +173 -0
  16. data/lib/caper/copy_handler.rb +36 -0
  17. data/lib/caper/crt.rb +13 -0
  18. data/lib/caper/data_link.rb +171 -0
  19. data/lib/caper/dead.rb +35 -0
  20. data/lib/caper/dumper.rb +53 -0
  21. data/lib/caper/error_buffer.rb +42 -0
  22. data/lib/caper/exceptions.rb +19 -0
  23. data/lib/caper/file_header.rb +24 -0
  24. data/lib/caper/in_addr.rb +7 -0
  25. data/lib/caper/interface.rb +27 -0
  26. data/lib/caper/live.rb +301 -0
  27. data/lib/caper/offline.rb +51 -0
  28. data/lib/caper/packet.rb +163 -0
  29. data/lib/caper/packet_header.rb +23 -0
  30. data/lib/caper/pcap.rb +250 -0
  31. data/lib/caper/stat.rb +56 -0
  32. data/lib/caper/time_val.rb +46 -0
  33. data/lib/caper/typedefs.rb +25 -0
  34. data/lib/caper/version.rb +4 -0
  35. data/spec/data_link_spec.rb +65 -0
  36. data/spec/dead_spec.rb +34 -0
  37. data/spec/dumps/http.pcap +0 -0
  38. data/spec/dumps/simple_tcp.pcap +0 -0
  39. data/spec/error_buffer_spec.rb +17 -0
  40. data/spec/file_header_spec.rb +28 -0
  41. data/spec/live_spec.rb +87 -0
  42. data/spec/offline_spec.rb +61 -0
  43. data/spec/packet_behaviors.rb +68 -0
  44. data/spec/packet_injection_spec.rb +38 -0
  45. data/spec/packet_spec.rb +111 -0
  46. data/spec/pcap_spec.rb +149 -0
  47. data/spec/spec_helper.rb +31 -0
  48. data/spec/wrapper_behaviors.rb +110 -0
  49. data/tasks/rcov.rb +6 -0
  50. data/tasks/rdoc.rb +17 -0
  51. data/tasks/spec.rb +9 -0
  52. data/tasks/yard.rb +16 -0
  53. metadata +140 -0
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Caper::Live do
4
+ describe "packet injection" do
5
+ before(:all) do
6
+ @pcap = Caper.open_live :device => PCAP_DEV,
7
+ :promisc => false,
8
+ :timeout => 100,
9
+ :snaplen => 8192
10
+ end
11
+
12
+ after(:all) do
13
+ @pcap.close
14
+ end
15
+
16
+ it "should detect when an invalid argument is supplied" do
17
+ lambda { @pcap.inject(Object.new)}.should raise_error(ArgumentError)
18
+ lambda { @pcap.inject(nil)}.should raise_error(ArgumentError)
19
+ lambda { @pcap.inject(1)}.should raise_error(ArgumentError)
20
+ lambda { @pcap.inject([])}.should raise_error(ArgumentError)
21
+ lambda { @pcap.inject(:foo => :bar)}.should raise_error(ArgumentError)
22
+ lambda {
23
+ @pcap.inject(FFI::MemoryPointer.new(10))
24
+ }.should raise_error(ArgumentError)
25
+ end
26
+
27
+ it "should allow injection of a String using inject()" do
28
+ test_data = "A" * 1024
29
+ @pcap.inject(test_data).should == test_data.size
30
+ end
31
+
32
+ it "should allow injection of a Packet using inject()" do
33
+ test_data = "B" * 512
34
+ @pcap.inject(Packet.from_string(test_data)).should == test_data.size
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+ require 'packet_behaviors'
3
+
4
+ describe Packet do
5
+ describe 'created using new() with a body string and nil header' do
6
+ before(:all) do
7
+ @test_body = "\xde\xad\xbe\xef"
8
+ @pkt = Packet.new(nil, @test_body)
9
+ end
10
+
11
+ it_should_behave_like "Caper::Packet composed"
12
+
13
+ end
14
+
15
+ describe 'created using new() with a Header and pointer to body' do
16
+ before(:all) do
17
+ @test_body = "\xde\xad\xbe\xef\xba\xbe"
18
+ l = @test_body.size
19
+ body = FFI::MemoryPointer.from_string(@test_body)
20
+ @pkt = Packet.new(PacketHeader.new(:caplen => l, :len => l), body)
21
+ end
22
+
23
+ it_should_behave_like "Caper::Packet composed"
24
+
25
+ end
26
+
27
+ describe 'created with from_string()' do
28
+ before(:all) do
29
+ @test_body = "\xde\xad\xbe\xef"
30
+ @pkt = Packet.from_string("\xde\xad\xbe\xef")
31
+ end
32
+
33
+ it_should_behave_like "Caper::Packet composed"
34
+
35
+ end
36
+
37
+
38
+ describe 'provided by a libpcap savefile using next()' do
39
+ before(:all) do
40
+ @pcap = Caper.open_offline(PCAP_TESTFILE)
41
+ @pkt = @pcap.next()
42
+ end
43
+
44
+ after(:all) do
45
+ @pcap.close()
46
+ end
47
+
48
+ it_should_behave_like "Caper::Packet populated"
49
+ end
50
+
51
+ describe 'provided by a libpcap savefile using loop()' do
52
+ before(:all) do
53
+ @pcap = Caper.open_offline(PCAP_TESTFILE)
54
+ @pkt = nil
55
+ # we use copy inside the loop because libpcap's loop() frees or reuses
56
+ # memory for packets after each call to the handler.
57
+ @pcap.loop(:count => 1) {|this,pkt| @pkt = pkt.copy }
58
+ end
59
+
60
+ after(:all) do
61
+ @pcap.close()
62
+ end
63
+
64
+ it_should_behave_like "Caper::Packet populated"
65
+ end
66
+
67
+
68
+ describe "error detection for new()" do
69
+ it "should raise an error when two nil values are supplied" do
70
+ lambda { Packet.new(nil,nil)}.should raise_error(Exception)
71
+ end
72
+
73
+ it "should raise an error for an invalid header" do
74
+ lambda {
75
+ Packet.new(Object.new, FFI::MemoryPointer.new(256))
76
+ }.should raise_error(Exception)
77
+ end
78
+
79
+ it "should raise an error for a nil header without a string body" do
80
+ lambda {
81
+ Packet.new(nil, FFI::MemoryPointer.new(256))
82
+ }.should raise_error(Exception)
83
+ end
84
+
85
+ it "should raise an error for a valid header but an invalid body pointer" do
86
+ lambda {
87
+ Packet.new(PacketHeader.new, "hellotest")
88
+ }.should raise_error(Exception)
89
+ end
90
+
91
+ it "should not raise an error for a PacketHeader and a pointer" do
92
+ lambda {
93
+ Packet.new(PacketHeader.new, FFI::MemoryPointer.new(256))
94
+ }.should_not raise_error(Exception)
95
+ end
96
+
97
+ it "should not raise an error for a pointer and a pointer" do
98
+ lambda {
99
+ Packet.new(FFI::MemoryPointer.new(20), FFI::MemoryPointer.new(256))
100
+ }.should_not raise_error(Exception)
101
+ end
102
+
103
+ it "should not raise an error for a nil and a string" do
104
+ lambda {
105
+ Packet.new(nil, "hellothere")
106
+ }.should_not raise_error(Exception)
107
+
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ describe Caper do
4
+ it "should define a VERSION constant" do
5
+ Caper.const_defined?('VERSION').should == true
6
+ end
7
+
8
+ it ".lib_version() should expose the libpcap version banner" do
9
+ Caper.lib_version.should_not be_nil
10
+ Caper.lib_version.should_not be_empty
11
+ end
12
+
13
+ it ".lib_version_number() should expose the libpcap version number only" do
14
+ Caper.lib_version_number.should_not be_nil
15
+ Caper.lib_version_number.should_not be_empty
16
+ Caper.lib_version_number.should =~ /^\d+\.\d+\.\d+$/
17
+ end
18
+
19
+ it ".lookupdev() should return a device deafult device" do
20
+ dev = Caper.lookupdev
21
+ dev.should_not be_nil
22
+ dev.should_not be_empty
23
+ end
24
+
25
+ it ".each_device() should enumerate over all usable interfaces" do
26
+ i = 0
27
+ Caper.each_device do |dev|
28
+ dev.should_not be_nil
29
+ Interface.should === dev
30
+ [true,false].include?(dev.loopback?).should == true
31
+ i+=1
32
+ end
33
+ i.should_not == 0
34
+ end
35
+
36
+ it ".device_names() should return names for all network interfaces" do
37
+ devs = Caper.device_names
38
+ Array.should === devs
39
+ i = 0
40
+ devs.each do |dev|
41
+ String.should === dev
42
+ dev.should_not be_nil
43
+ dev.should_not be_empty
44
+ i+=1
45
+ end
46
+ i.should_not == 0
47
+ devs.include?(PCAP_DEV).should == true
48
+ end
49
+
50
+ it ".dump_devices() should return name/network pairs for all interfaces" do
51
+ i = 0
52
+ devs = Caper.dump_devices
53
+ Array.should === devs
54
+ devs.each do |y|
55
+ y.size.should == 2
56
+ dev, net = y
57
+ String.should === dev
58
+ dev.should_not be_nil
59
+ dev.should_not be_empty
60
+ i+=1
61
+ end
62
+ i.should_not == 0
63
+ devs.select{|dev,net| not net.nil? }.should_not be_empty
64
+ devs.map{|dev,net| dev}.include?(PCAP_DEV).should == true
65
+ end
66
+
67
+ it ".open_live() should open a live pcap handler given a chosen device" do
68
+ lambda {
69
+ pcap = Caper.open_live(:device => PCAP_DEV)
70
+ pcap.device.should == PCAP_DEV
71
+ pcap.close
72
+ }.should_not raise_error(Exception)
73
+ end
74
+
75
+ it ".open_live() should open a live pcap handler using a default device" do
76
+ lambda {
77
+ # XXX Using Vista and wpcap.dll this breaks on me.
78
+ # The lookupdev for a default adapter result is '\', which is just
79
+ # wrong.
80
+ pcap = Caper.open_live()
81
+ pcap.should be_ready
82
+ pcap.close
83
+ }.should_not raise_error(Exception)
84
+ end
85
+
86
+ it ".open_dead() should open a dead pcap handler" do
87
+ lambda {
88
+ pcap = Caper.open_dead()
89
+ pcap.should be_ready
90
+ pcap.close
91
+ }.should_not raise_error(Exception)
92
+ end
93
+
94
+ it ".open_offline() should open a pcap dump file" do
95
+ lambda {
96
+ pcap = Caper.open_offline(PCAP_TESTFILE)
97
+ pcap.should be_ready
98
+ pcap.close
99
+ }.should_not raise_error(Exception)
100
+ end
101
+
102
+ it ".open_file() should work the same as .open_offline()" do
103
+ lambda {
104
+ pcap = Caper.open_offline(PCAP_TESTFILE)
105
+ pcap.should be_ready
106
+ pcap.close
107
+ }.should_not raise_error(Exception)
108
+ end
109
+
110
+ it ".open_live() should take a block and close the device after calling it" do
111
+ pcap = nil
112
+ ret = Caper.open_live(:device => PCAP_DEV) {|this|
113
+ Live.should === this
114
+ this.should be_ready
115
+ this.should_not be_closed
116
+ pcap = this
117
+ }
118
+ ret.should be_nil
119
+ pcap.should_not be_ready
120
+ pcap.should be_closed
121
+ end
122
+
123
+ it ".open_dead() should take a block and close the device after calling it" do
124
+ pcap = nil
125
+ ret = Caper.open_dead() {|this|
126
+ Dead.should === this
127
+ this.should be_ready
128
+ this.should_not be_closed
129
+ pcap = this
130
+ }
131
+ ret.should be_nil
132
+ pcap.should_not be_ready
133
+ ret.should be_nil
134
+ end
135
+
136
+ it ".open_file() should take a block and close the device after calling it" do
137
+ pcap = nil
138
+ ret = Caper.open_file(PCAP_TESTFILE) {|this|
139
+ Offline.should === this
140
+ this.should be_ready
141
+ this.should_not be_closed
142
+ pcap = this
143
+ }
144
+ ret.should be_nil
145
+ pcap.should_not be_ready
146
+ ret.should be_nil
147
+ end
148
+
149
+ end
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ gem 'rspec', '>=1.2.9'
3
+ require 'spec'
4
+
5
+ require 'caper'
6
+
7
+ include FFI
8
+ include Caper
9
+
10
+ PCAP_DEV = ENV['PCAP_DEV'] || 'lo0'
11
+ PCAP_TESTFILE = ENV['PCAP_TESTFILE'] || File.expand_path(File.join(File.dirname(__FILE__), 'dumps', 'simple_tcp.pcap'))
12
+ PCAP_TESTADDR = ENV['PCAP_TESTADDR'] || '127.0.0.1'
13
+
14
+ $test_ping_pid = nil
15
+
16
+ def start_traffic_generator
17
+ begin
18
+ if $test_ping_pid.nil?
19
+ $test_ping_pid = Process.fork{ `ping #{PCAP_TESTADDR}` }
20
+ end
21
+ rescue NotImplementedError
22
+ $test_ping_pid = nil
23
+ end
24
+ end
25
+
26
+ def stop_traffic_generator
27
+ if $test_ping_pid
28
+ Process.kill('TERM', $test_ping_pid)
29
+ $test_ping_pid = nil
30
+ end
31
+ end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+
4
+ shared_examples_for "Caper::CommonWrapper" do
5
+ it "should indicate readiness" do
6
+ @pcap.ready?.should == true
7
+ end
8
+
9
+ it "should have a datalink" do
10
+ datalink = @pcap.datalink
11
+ datalink.value.should_not be_nil
12
+ Numeric.should === datalink.value
13
+ datalink.name.should_not be_empty
14
+ end
15
+
16
+ it "should be able to open a dump file" do
17
+ lambda {
18
+ dumper = @pcap.open_dump(Tempfile.new(rand(0xffff).to_s).path)
19
+ Dumper.should === dumper
20
+ dumper.close
21
+ }.should_not raise_error(Exception)
22
+ end
23
+
24
+ it "should raise an exception when opening a bad dump file" do
25
+ lambda {
26
+ @pcap.open_dump(File.join('','obviously','not','there'))
27
+ }.should raise_error(Exception)
28
+ end
29
+
30
+ it "should return an empty String when an error has not occurred" do
31
+ @pcap.error.should be_empty
32
+ end
33
+
34
+ it "should be able to compile a filter" do
35
+ filter = @pcap.compile("ip")
36
+ filter.should_not be_nil
37
+ BPFProgram.should === filter
38
+ filter.bf_len.should > 0
39
+ end
40
+
41
+ it "should detect invalid filter syntax when compiling" do
42
+ lambda {
43
+ @pcap.compile("ip and totally bogus")
44
+ }.should raise_error(LibError)
45
+ end
46
+
47
+ it "should prevent double closes" do
48
+ @pcap.close
49
+ @pcap.should be_closed
50
+ @pcap.should_not be_ready
51
+
52
+ lambda {
53
+ @pcap.close
54
+ }.should_not raise_error(Exception)
55
+ end
56
+
57
+ end
58
+
59
+ shared_examples_for "Caper::CaptureWrapper" do
60
+
61
+ it "should pass packets to a block using loop()" do
62
+ i = 0
63
+ @pkt = nil
64
+ @pcap.loop(:count => 2) do |this, pkt|
65
+ this.should == @pcap
66
+ pkt.should_not be_nil
67
+ Packet.should === pkt
68
+ i+=1
69
+ end
70
+ i.should == 2
71
+ end
72
+
73
+ it "should be able to get the next packet" do
74
+ pkt = @pcap.next
75
+ pkt.should_not be_nil
76
+ end
77
+
78
+ it "should be able to break out of a pcap loop()" do
79
+ stopped = false
80
+ i = 0
81
+
82
+ @pcap.loop(:count => 3) do |this, pkt|
83
+ stopped = true
84
+ i+=1
85
+ this.stop
86
+ end
87
+
88
+ i.should == 1
89
+ stopped.should == true
90
+ end
91
+
92
+ it "should consume packets without a block passed to loop()" do
93
+ lambda { @pcap.loop(:count => 3) }.should_not raise_error(Exception)
94
+ end
95
+
96
+ it "should be able to set a filter" do
97
+ lambda {
98
+ @pcap.set_filter("ip")
99
+ }.should_not raise_error(Exception)
100
+ end
101
+
102
+ it "should detect invalid filter syntax in set_filter" do
103
+ lambda {
104
+ @pcap.set_filter("ip and totally bogus")
105
+ }.should raise_error(LibError)
106
+ end
107
+
108
+ it_should_behave_like "Caper::CommonWrapper"
109
+ end
110
+
@@ -0,0 +1,6 @@
1
+ require 'spec/rake/spectask'
2
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
3
+ spec.libs << 'lib' << 'spec'
4
+ spec.pattern = 'spec/**/*_spec.rb'
5
+ spec.rcov = true
6
+ end