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.
Files changed (53) hide show
  1. data/.gitignore +10 -0
  2. data/ChangeLog.rdoc +27 -0
  3. data/LICENSE.txt +23 -0
  4. data/README.rdoc +30 -0
  5. data/Rakefile +26 -0
  6. data/VERSION +1 -0
  7. data/examples/ipfw_divert.rb +49 -0
  8. data/examples/print_bytes.rb +17 -0
  9. data/lib/ffi-pcap.rb +1 -0
  10. data/lib/ffi/pcap.rb +42 -0
  11. data/lib/ffi/pcap/addr.rb +21 -0
  12. data/lib/ffi/pcap/bpf.rb +106 -0
  13. data/lib/ffi/pcap/bsd.rb +98 -0
  14. data/lib/ffi/pcap/capture_wrapper.rb +289 -0
  15. data/lib/ffi/pcap/common_wrapper.rb +175 -0
  16. data/lib/ffi/pcap/copy_handler.rb +38 -0
  17. data/lib/ffi/pcap/crt.rb +15 -0
  18. data/lib/ffi/pcap/data_link.rb +173 -0
  19. data/lib/ffi/pcap/dead.rb +37 -0
  20. data/lib/ffi/pcap/dumper.rb +55 -0
  21. data/lib/ffi/pcap/error_buffer.rb +44 -0
  22. data/lib/ffi/pcap/exceptions.rb +21 -0
  23. data/lib/ffi/pcap/file_header.rb +26 -0
  24. data/lib/ffi/pcap/in_addr.rb +9 -0
  25. data/lib/ffi/pcap/interface.rb +29 -0
  26. data/lib/ffi/pcap/live.rb +303 -0
  27. data/lib/ffi/pcap/offline.rb +53 -0
  28. data/lib/ffi/pcap/packet.rb +164 -0
  29. data/lib/ffi/pcap/packet_header.rb +24 -0
  30. data/lib/ffi/pcap/pcap.rb +252 -0
  31. data/lib/ffi/pcap/stat.rb +57 -0
  32. data/lib/ffi/pcap/time_val.rb +48 -0
  33. data/lib/ffi/pcap/typedefs.rb +27 -0
  34. data/lib/ffi/pcap/version.rb +6 -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 +124 -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 +21 -0
  53. 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
@@ -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