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,56 @@
1
+ module Caper
2
+
3
+ # As returned by pcap_stats()
4
+ #
5
+ # See pcap_stat struct in pcap.h.
6
+ class Stat < FFI::Struct
7
+ include FFI::DRY::StructHelper
8
+
9
+ dsl_layout do
10
+ field :ps_recv, :uint, :desc => "number of packets received"
11
+ field :ps_drop, :uint, :desc => "numer of packets dropped"
12
+ field :ps_ifdrop, :uint, :desc => "drops by interface (not yet supported)"
13
+ # bs_capt field intentionally left off (WIN32 only)
14
+ end
15
+
16
+ alias received ps_recv
17
+ alias dropped ps_drop
18
+ alias interface_dropped ps_ifdrop
19
+
20
+ end
21
+
22
+ # As returned by pcap_stats_ex() (MSDOS only)
23
+ #
24
+ # See pcap_stat_ex struct in pcap.h
25
+ class StatEx < FFI::Struct
26
+ include FFI::DRY::StructHelper
27
+
28
+ dsl_layout do
29
+ field :rx_packets, :ulong, :desc => "total packets received"
30
+ field :tx_packets, :ulong, :desc => "total packets transmitted"
31
+ field :rx_bytes, :ulong, :desc => "total bytes received"
32
+ field :tx_bytes, :ulong, :desc => "total bytes transmitted"
33
+ field :rx_errors, :ulong, :desc => "bad packets received"
34
+ field :tx_errors, :ulong, :desc => "packet transmit problems"
35
+ field :rx_dropped, :ulong, :desc => "no space in Rx buffers"
36
+ field :tx_dropped, :ulong, :desc => "no space available for Tx"
37
+ field :multicast, :ulong, :desc => "multicast packets received"
38
+ field :collisions, :ulong
39
+
40
+ # detailed rx errors
41
+ field :rx_length_errors, :ulong
42
+ field :rx_over_errors, :ulong, :desc => "ring buff overflow"
43
+ field :rx_crc_errors, :ulong, :desc => "pkt with crc error"
44
+ field :rx_frame_errors, :ulong, :desc => "frame alignment errors"
45
+ field :rx_fifo_errors, :ulong, :desc => "fifo overrun"
46
+ field :rx_missed_errors, :ulong, :desc => "missed packet"
47
+
48
+ # detailed tx_errors
49
+ field :tx_aborted_errors, :ulong
50
+ field :tx_carrier_errors, :ulong
51
+ field :tx_fifo_errors, :ulong
52
+ field :tx_heartbeat_errors, :ulong
53
+ field :tx_window_errors, :ulong
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,46 @@
1
+ module Caper
2
+ class TimeVal < FFI::Struct
3
+ include FFI::DRY::StructHelper
4
+
5
+ dsl_layout do
6
+ field :tv_sec, :time_t
7
+ field :tv_usec, :suseconds_t
8
+ end
9
+
10
+ def initialize(*args)
11
+ if args.size == 1 and (t=args[0]).kind_of?(Time)
12
+ self.time = t
13
+ else
14
+ super(*args)
15
+ end
16
+ end
17
+
18
+ alias sec tv_sec
19
+ alias usec tv_usec
20
+
21
+ # Returns the time value as a ruby Time object.
22
+ #
23
+ # @return [Time]
24
+ # A ruby time object derived from this TimeVal.
25
+ def time
26
+ Time.at(self.tv_sec, self.tv_usec)
27
+ end
28
+
29
+ alias to_time time
30
+
31
+ # Sets the time value from a ruby Time object
32
+ #
33
+ # @param [Time] t
34
+ # A ruby time object from which to set the time.
35
+ #
36
+ # @return [Time]
37
+ # Returns the same Time object supplied per convention.
38
+ #
39
+ def time=(t)
40
+ self.tv_sec = t.tv_sec
41
+ self.tv_usec = t.tv_usec
42
+ return t
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ module Caper
2
+ typedef :pointer, :FILE
3
+
4
+ typedef :int, :bpf_int32
5
+ typedef :uint, :bpf_uint32
6
+
7
+ enum :pcap_direction_t, [
8
+ :pcap_d_inout,
9
+ :pcap_d_in,
10
+ :pcap_d_out
11
+ ]
12
+
13
+ # For Win32-only pcap_setmode()
14
+ enum :pcap_w32_modes_enum, [ :capt, :stat, :mon ]
15
+
16
+ typedef :pointer, :pcap_t
17
+ typedef :pointer, :pcap_dumper_t
18
+ typedef :pointer, :pcap_addr_t
19
+
20
+ # add some of the more temperamental FFI types if needed
21
+ [ [:long, :time_t],
22
+ [:uint32, :suseconds_t],
23
+ ].each {|t, d| begin; find_type(d); rescue TypeError; typedef t,d; end }
24
+
25
+ end
@@ -0,0 +1,4 @@
1
+ module Caper
2
+ # caper version
3
+ VERSION = '0.1.2'
4
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe DataLink do
4
+ before(:all) do
5
+ @datalink = DataLink.new(0)
6
+ end
7
+
8
+ it "should map datalink names to datalink layer type values" do
9
+ DataLink.name_to_val(:en10mb).should == 1
10
+ end
11
+
12
+ it "should map datalink layer type values to datalink names" do
13
+ DataLink.val_to_name(1).should == "EN10MB"
14
+ end
15
+
16
+ it "should be initialized from a pcap datalink value" do
17
+ @datalink.name.should == 'NULL'
18
+ end
19
+
20
+ it "should support initialization from a pcap datalink name symbol" do
21
+ @datalink = DataLink.new(:null)
22
+ DataLink.should === @datalink
23
+ end
24
+
25
+ it "should support initialization from a pcap datalink name string" do
26
+ dl = DataLink.new('en10mb')
27
+ DataLink.should === dl
28
+ end
29
+
30
+ it "should allow equality comparison against numeric values" do
31
+ (@datalink == 0).should == true
32
+ (@datalink == 1).should == false
33
+ end
34
+
35
+ it "should allow equality comparison against String names" do
36
+ (@datalink == "null").should == true
37
+ (@datalink == "en10mb").should == false
38
+ end
39
+
40
+ it "should allow equality comparison against Symbol names" do
41
+ (@datalink == :null).should == true
42
+ (@datalink == :en10mb).should == false
43
+ end
44
+
45
+ it "should allow comparison against another DataLink" do
46
+ (@datalink == DataLink.new(0)).should == true
47
+ (@datalink == DataLink.new(1)).should == false
48
+ end
49
+
50
+ it "should still compare correctly against any other object" do
51
+ (@datalink == Object.new).should == false
52
+ end
53
+
54
+ it "should have a description" do
55
+ @datalink.description.should_not be_empty
56
+ end
57
+
58
+ it "should convert to an Integer for the DLT value" do
59
+ @datalink.to_i.should == 0
60
+ end
61
+
62
+ it "should convert to a String for the DLT name" do
63
+ @datalink.to_s.should == 'NULL'
64
+ end
65
+ end
@@ -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 "Caper::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 "Caper::CommonWrapper"
26
+
27
+ @pcap.close
28
+ end
29
+
30
+
31
+ end
32
+
33
+ end
34
+
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
+ Caper.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
@@ -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 "Caper::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 "Caper::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 "Caper::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 "Caper::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 "Caper::CaptureWrapper"
55
+
56
+ @pcap.close
57
+ end
58
+
59
+ end
60
+ end
61
+
@@ -0,0 +1,68 @@
1
+
2
+ shared_examples_for "Caper::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 "Caper::Packet populated" do
33
+ it_should_behave_like "Caper::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 "Caper::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