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.
- data/.gitignore +9 -0
- data/History.rdoc +31 -0
- data/LICENSE.ffi-pcap +23 -0
- data/README.rdoc +55 -0
- data/Rakefile +26 -0
- data/VERSION +1 -0
- data/examples/ipfw_divert.rb +45 -0
- data/examples/print_bytes.rb +17 -0
- data/examples/test_loop.rb +48 -0
- data/lib/caper.rb +40 -0
- data/lib/caper/addr.rb +19 -0
- data/lib/caper/bpf.rb +104 -0
- data/lib/caper/bsd.rb +96 -0
- data/lib/caper/capture_wrapper.rb +287 -0
- data/lib/caper/common_wrapper.rb +173 -0
- data/lib/caper/copy_handler.rb +36 -0
- data/lib/caper/crt.rb +13 -0
- data/lib/caper/data_link.rb +171 -0
- data/lib/caper/dead.rb +35 -0
- data/lib/caper/dumper.rb +53 -0
- data/lib/caper/error_buffer.rb +42 -0
- data/lib/caper/exceptions.rb +19 -0
- data/lib/caper/file_header.rb +24 -0
- data/lib/caper/in_addr.rb +7 -0
- data/lib/caper/interface.rb +27 -0
- data/lib/caper/live.rb +301 -0
- data/lib/caper/offline.rb +51 -0
- data/lib/caper/packet.rb +163 -0
- data/lib/caper/packet_header.rb +23 -0
- data/lib/caper/pcap.rb +250 -0
- data/lib/caper/stat.rb +56 -0
- data/lib/caper/time_val.rb +46 -0
- data/lib/caper/typedefs.rb +25 -0
- data/lib/caper/version.rb +4 -0
- data/spec/data_link_spec.rb +65 -0
- data/spec/dead_spec.rb +34 -0
- data/spec/dumps/http.pcap +0 -0
- data/spec/dumps/simple_tcp.pcap +0 -0
- data/spec/error_buffer_spec.rb +17 -0
- data/spec/file_header_spec.rb +28 -0
- data/spec/live_spec.rb +87 -0
- data/spec/offline_spec.rb +61 -0
- data/spec/packet_behaviors.rb +68 -0
- data/spec/packet_injection_spec.rb +38 -0
- data/spec/packet_spec.rb +111 -0
- data/spec/pcap_spec.rb +149 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/wrapper_behaviors.rb +110 -0
- data/tasks/rcov.rb +6 -0
- data/tasks/rdoc.rb +17 -0
- data/tasks/spec.rb +9 -0
- data/tasks/yard.rb +16 -0
- 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
|
data/spec/packet_spec.rb
ADDED
@@ -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
|
data/spec/pcap_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
+
|