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
data/lib/caper/stat.rb
ADDED
@@ -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,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
|
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 "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
|
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
|
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 "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
|