ffi-pcap 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 +10 -0
- data/ChangeLog.rdoc +27 -0
- data/LICENSE.txt +23 -0
- data/README.rdoc +30 -0
- data/Rakefile +26 -0
- data/VERSION +1 -0
- data/examples/ipfw_divert.rb +49 -0
- data/examples/print_bytes.rb +17 -0
- data/lib/ffi-pcap.rb +1 -0
- data/lib/ffi/pcap.rb +42 -0
- data/lib/ffi/pcap/addr.rb +21 -0
- data/lib/ffi/pcap/bpf.rb +106 -0
- data/lib/ffi/pcap/bsd.rb +98 -0
- data/lib/ffi/pcap/capture_wrapper.rb +289 -0
- data/lib/ffi/pcap/common_wrapper.rb +175 -0
- data/lib/ffi/pcap/copy_handler.rb +38 -0
- data/lib/ffi/pcap/crt.rb +15 -0
- data/lib/ffi/pcap/data_link.rb +173 -0
- data/lib/ffi/pcap/dead.rb +37 -0
- data/lib/ffi/pcap/dumper.rb +55 -0
- data/lib/ffi/pcap/error_buffer.rb +44 -0
- data/lib/ffi/pcap/exceptions.rb +21 -0
- data/lib/ffi/pcap/file_header.rb +26 -0
- data/lib/ffi/pcap/in_addr.rb +9 -0
- data/lib/ffi/pcap/interface.rb +29 -0
- data/lib/ffi/pcap/live.rb +303 -0
- data/lib/ffi/pcap/offline.rb +53 -0
- data/lib/ffi/pcap/packet.rb +164 -0
- data/lib/ffi/pcap/packet_header.rb +24 -0
- data/lib/ffi/pcap/pcap.rb +252 -0
- data/lib/ffi/pcap/stat.rb +57 -0
- data/lib/ffi/pcap/time_val.rb +48 -0
- data/lib/ffi/pcap/typedefs.rb +27 -0
- data/lib/ffi/pcap/version.rb +6 -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 +124 -0
- data/tasks/rcov.rb +6 -0
- data/tasks/rdoc.rb +17 -0
- data/tasks/spec.rb +9 -0
- data/tasks/yard.rb +21 -0
- metadata +157 -0
data/spec/dead_spec.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'wrapper_behaviors'
|
3
|
+
|
4
|
+
describe Dead do
|
5
|
+
before(:each) do
|
6
|
+
@pcap = Dead.new()
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:each) do
|
10
|
+
@pcap.close
|
11
|
+
end
|
12
|
+
|
13
|
+
it_should_behave_like "FFI::PCap::CommonWrapper"
|
14
|
+
|
15
|
+
describe "yielding to a block" do
|
16
|
+
# Note we also test all the behaviors here together instead of seperately.
|
17
|
+
Dead.new() do |this|
|
18
|
+
@pcap = this
|
19
|
+
|
20
|
+
it "should be in a ready state in the block" do
|
21
|
+
@pcap.should be_ready
|
22
|
+
@pcap.should_not be_closed
|
23
|
+
end
|
24
|
+
|
25
|
+
it_should_behave_like "FFI::PCap::CommonWrapper"
|
26
|
+
|
27
|
+
@pcap.close
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
Binary file
|
Binary file
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ErrorBuffer do
|
4
|
+
before(:all) do
|
5
|
+
@errbuf = ErrorBuffer.create
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should have a size of 256" do
|
9
|
+
@errbuf.size.should == 256
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return an error message with to_s" do
|
13
|
+
@errbuf.to_s.should be_empty
|
14
|
+
FFI::PCap.pcap_open_offline("/this/file/wont/exist/#{rand(0xFFFF)}", @errbuf )
|
15
|
+
@errbuf.to_s.should_not be_empty
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FileHeader do
|
4
|
+
before(:all) do
|
5
|
+
@file_header = FileHeader.new( :raw => File.read(PCAP_TESTFILE) )
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should parse a pcap file correctly" do
|
9
|
+
@file_header.magic.should == 0xa1b2c3d4
|
10
|
+
@file_header.version_major.should == 2
|
11
|
+
@file_header.version_minor.should == 4
|
12
|
+
@file_header.thiszone.should == 0
|
13
|
+
@file_header.sigfigs.should == 0
|
14
|
+
@file_header.snaplen.should == 96
|
15
|
+
@file_header.linktype.should == 1
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return a file format version string" do
|
19
|
+
String.should === @file_header.version
|
20
|
+
@file_header.version.should == "2.4"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return a DataLink for the linktype using datalink()" do
|
24
|
+
DataLink.should === @file_header.datalink
|
25
|
+
(@file_header.datalink == :en10mb).should == true
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/spec/live_spec.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'wrapper_behaviors'
|
3
|
+
require 'packet_behaviors'
|
4
|
+
|
5
|
+
describe Live do
|
6
|
+
before(:each) do
|
7
|
+
@pcap = Live.new(
|
8
|
+
:device => PCAP_DEV,
|
9
|
+
:promisc => true,
|
10
|
+
:timeout => 1000
|
11
|
+
)
|
12
|
+
start_traffic_generator()
|
13
|
+
end
|
14
|
+
|
15
|
+
after(:each) do
|
16
|
+
stop_traffic_generator()
|
17
|
+
@pcap.close
|
18
|
+
end
|
19
|
+
|
20
|
+
it_should_behave_like "FFI::PCap::CaptureWrapper"
|
21
|
+
|
22
|
+
it "should support non-blocking mode" do
|
23
|
+
@pcap.non_blocking = true
|
24
|
+
@pcap.should be_non_blocking
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should provide statistics about packets received/dropped" do
|
28
|
+
i = 0
|
29
|
+
@pcap.loop {|this,pkt| @pcap.stop if (i += 1) == 10 }
|
30
|
+
i.should_not == 0
|
31
|
+
stats = @pcap.stats
|
32
|
+
Stat.should === stats
|
33
|
+
stats.received.should > 0
|
34
|
+
stats.received.should >= 10
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should yield packets with a timestamp using loop()" do
|
38
|
+
i = 0
|
39
|
+
@pkt = nil
|
40
|
+
@pcap.loop(:count => 2) do |this, pkt|
|
41
|
+
this.should == @pcap
|
42
|
+
pkt.should_not be_nil
|
43
|
+
Packet.should === pkt
|
44
|
+
(Time.now - pkt.time).should_not > 1000
|
45
|
+
i+=1
|
46
|
+
end
|
47
|
+
i.should == 2
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
describe "live packets" do
|
52
|
+
before(:all) do
|
53
|
+
@pcap = Live.new(
|
54
|
+
:device => PCAP_DEV,
|
55
|
+
:promisc => true
|
56
|
+
)
|
57
|
+
@pkt = @pcap.next()
|
58
|
+
start_traffic_generator()
|
59
|
+
end
|
60
|
+
|
61
|
+
after(:all) do
|
62
|
+
stop_traffic_generator()
|
63
|
+
@pcap.close
|
64
|
+
end
|
65
|
+
|
66
|
+
it_should_behave_like "FFI::PCap::Packet populated"
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "yielding to a block" do
|
71
|
+
# Note we also test all the behaviors here together instead of seperately.
|
72
|
+
Offline.new(PCAP_TESTFILE) do |this|
|
73
|
+
@pcap = this
|
74
|
+
|
75
|
+
it "should be in a ready state in the block" do
|
76
|
+
@pcap.should be_ready
|
77
|
+
@pcap.should_not be_closed
|
78
|
+
end
|
79
|
+
|
80
|
+
start_traffic_generator()
|
81
|
+
it_should_behave_like "FFI::PCap::CaptureWrapper"
|
82
|
+
stop_traffic_generator()
|
83
|
+
@pcap.close
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'wrapper_behaviors'
|
3
|
+
|
4
|
+
describe Offline do
|
5
|
+
before(:each) do
|
6
|
+
@pcap = Offline.new(PCAP_TESTFILE)
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:each) do
|
10
|
+
@pcap.close
|
11
|
+
end
|
12
|
+
|
13
|
+
it_should_behave_like "FFI::PCap::CaptureWrapper"
|
14
|
+
|
15
|
+
it "should return a nil from next() at the end of the dump file" do
|
16
|
+
i = 0
|
17
|
+
@pcap.loop {|this,pkt| i+=1 }
|
18
|
+
i.should > 0
|
19
|
+
@pcap.next.should be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should yield packets with a timestamp using loop()" do
|
23
|
+
i = 0
|
24
|
+
@pkt = nil
|
25
|
+
@pcap.loop(:count => 2) do |this, pkt|
|
26
|
+
this.should == @pcap
|
27
|
+
pkt.should_not be_nil
|
28
|
+
Packet.should === pkt
|
29
|
+
pkt.time.to_i.should > 0
|
30
|
+
i+=1
|
31
|
+
end
|
32
|
+
i.should == 2
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should supply a file version" do
|
36
|
+
@pcap.file_version.should =~ /^\d+\.\d+$/
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should indicate whether it is endian swapped" do
|
40
|
+
[true,false].include?(@pcap.swapped?).should == true
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "yielding to a block" do
|
44
|
+
|
45
|
+
# Note we also test all the behaviors here together instead of seperately.
|
46
|
+
Offline.new(PCAP_TESTFILE) do |this|
|
47
|
+
@pcap = this
|
48
|
+
|
49
|
+
it "should be in a ready state in the block" do
|
50
|
+
@pcap.should be_ready
|
51
|
+
@pcap.should_not be_closed
|
52
|
+
end
|
53
|
+
|
54
|
+
it_should_behave_like "FFI::PCap::CaptureWrapper"
|
55
|
+
|
56
|
+
@pcap.close
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
shared_examples_for "FFI::PCap::Packet" do
|
3
|
+
it "should supply a way to get a pointer for the body" do
|
4
|
+
@pkt.body_ptr.should_not be_nil
|
5
|
+
::FFI::Pointer.should === @pkt.body_ptr
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should supply a way to get a String for the body" do
|
9
|
+
@pkt.body.should_not be_nil
|
10
|
+
String.should === @pkt.body
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should supply a timestamp as a Time object" do
|
14
|
+
@pkt.time.should_not be_nil
|
15
|
+
Time.should === @pkt.time
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should allow time timestamp to be changed" do
|
19
|
+
t = Time.now
|
20
|
+
lambda {@pkt.time = t}.should_not raise_error(Exception)
|
21
|
+
@pkt.time.should == t
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return a deep copy of itself with copy()" do
|
25
|
+
cp = @pkt.copy()
|
26
|
+
cp.object_id.should_not == @pkt.object_id
|
27
|
+
cp.body_ptr.object_id.should_not == @pkt.body_ptr.object_id
|
28
|
+
cp.body.should == @pkt.body
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
shared_examples_for "FFI::PCap::Packet populated" do
|
33
|
+
it_should_behave_like "FFI::PCap::Packet"
|
34
|
+
|
35
|
+
it "should have a non-zero packet length in the header" do
|
36
|
+
@pkt.length.should_not == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should have a non-zero captured length in the header" do
|
40
|
+
@pkt.captured.should_not == 0
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should have a non-empty body" do
|
44
|
+
@pkt.body.should_not be_empty
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should have a non-null body pointer" do
|
48
|
+
@pkt.body_ptr.should_not be_null
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
shared_examples_for "FFI::PCap::Packet composed" do
|
54
|
+
it "should return the expected header" do
|
55
|
+
@pkt.header.should be_kind_of(PacketHeader)
|
56
|
+
@pkt.header.len.should == @test_body.size
|
57
|
+
@pkt.header.caplen.should == @test_body.size
|
58
|
+
@pkt.header.timestamp.to_time.to_i.should == 0
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should return the expected body String" do
|
62
|
+
@pkt.body.should == @test_body
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should return a pointer to the expected body String" do
|
66
|
+
@pkt.body_ptr.read_string(@pkt.caplen).should == @test_body
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FFI::PCap::Live do
|
4
|
+
describe "packet injection" do
|
5
|
+
before(:all) do
|
6
|
+
@pcap = FFI::PCap.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 "FFI::PCap::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 "FFI::PCap::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 "FFI::PCap::Packet composed"
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
describe 'provided by a libpcap savefile using next()' do
|
39
|
+
before(:all) do
|
40
|
+
@pcap = FFI::PCap.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 "FFI::PCap::Packet populated"
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'provided by a libpcap savefile using loop()' do
|
52
|
+
before(:all) do
|
53
|
+
@pcap = FFI::PCap.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 "FFI::PCap::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
|