spcap 0.0.2 → 0.0.3
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 +2 -0
- data/README.md +4 -1
- data/lib/spcap.rb +7 -1
- data/lib/spcap/exceptions.rb +4 -0
- data/lib/spcap/factory.rb +4 -1
- data/lib/spcap/{ipadress.rb → ipaddress.rb} +9 -2
- data/lib/spcap/ippacket.rb +4 -9
- data/lib/spcap/packet.rb +1 -2
- data/lib/spcap/scopy.rb +50 -0
- data/lib/spcap/stream.rb +87 -0
- data/lib/spcap/tcppacket.rb +12 -7
- data/lib/spcap/version.rb +1 -1
- data/test/lib/spcap/file_test.rb +45 -4
- data/test/pcap_files/sample.pcap +0 -0
- data/test/test_helper.rb +1 -0
- metadata +8 -4
- data/lib/spcap/file.rb +0 -48
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Spcap
|
2
2
|
|
3
|
-
|
3
|
+
Simple Pcap stream handler without native extention.
|
4
|
+
This gem respect original interface of ruby-pcap module
|
5
|
+
|
6
|
+
[](http://badge.fury.io/rb/spcap)
|
4
7
|
|
5
8
|
## Installation
|
6
9
|
|
data/lib/spcap.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
require 'log4r'
|
2
2
|
require 'spcap/version'
|
3
|
+
require 'spcap/exceptions'
|
3
4
|
require 'spcap/logger'
|
5
|
+
require 'spcap/ipaddress'
|
4
6
|
require 'spcap/packet'
|
5
|
-
require 'spcap/
|
7
|
+
require 'spcap/ippacket'
|
8
|
+
require 'spcap/tcppacket'
|
9
|
+
require 'spcap/factory'
|
10
|
+
require 'spcap/stream'
|
11
|
+
require 'spcap/scopy'
|
6
12
|
|
7
13
|
module Spcap
|
8
14
|
# Your code goes here...
|
data/lib/spcap/factory.rb
CHANGED
@@ -13,7 +13,10 @@ module Spcap
|
|
13
13
|
return TCPPacket.new(time,raw_data,len,linklayer_header_type)
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
|
+
p = Packet.new(time,raw_data,len,linklayer_header_type)
|
18
|
+
Logger.warn "Spcap::Factory only support TCP over IPv4 packet other packet are dropped Packet Headher : [#{raw_data[0,16].unpack("H*").first}]"
|
19
|
+
return p
|
17
20
|
end
|
18
21
|
end
|
19
22
|
end
|
@@ -6,11 +6,18 @@ module Spcap
|
|
6
6
|
@address = address
|
7
7
|
end
|
8
8
|
|
9
|
+
# Return true if two addresses are the same address.
|
10
|
+
def <=>(other)
|
11
|
+
@address <=> other.address
|
12
|
+
end
|
13
|
+
|
9
14
|
# Return true if two addresses are the same address.
|
10
15
|
def ==(other)
|
11
|
-
@address
|
16
|
+
@address == other.address
|
12
17
|
end
|
13
18
|
|
19
|
+
def eql?(other) ; self == other ; end
|
20
|
+
|
14
21
|
def hash
|
15
22
|
@address.hash
|
16
23
|
end
|
@@ -21,7 +28,7 @@ module Spcap
|
|
21
28
|
to_num_s
|
22
29
|
end
|
23
30
|
|
24
|
-
Return the value of IP address as integer.
|
31
|
+
# Return the value of IP address as integer.
|
25
32
|
def to_i
|
26
33
|
@address.unpackt("N").first
|
27
34
|
end
|
data/lib/spcap/ippacket.rb
CHANGED
@@ -1,24 +1,19 @@
|
|
1
1
|
module Spcap
|
2
2
|
class IPPacket < Packet
|
3
|
-
attr_reader :src,:dst,:
|
3
|
+
attr_reader :src,:dst,:ip_hlen,:ip_len
|
4
|
+
|
4
5
|
def initialize(time,data,len,datalink)
|
5
6
|
super(time,data,len,datalink)
|
6
7
|
@src = IPAddress.new(@raw_data[12,4])
|
7
8
|
@dst = IPAddress.new(data[16,4])
|
8
|
-
@
|
9
|
+
@ip_hlen = @raw_data.getbyte(0) & 0x0F
|
9
10
|
@ip_len = @raw_data[2,2].unpack("n").first
|
10
11
|
|
11
12
|
end
|
12
13
|
|
13
|
-
|
14
|
-
# Return header length. (Unit: 4 octets)
|
15
|
-
def ip_hlen
|
16
|
-
@raw_data.getbyte(0) & 0x0F
|
17
|
-
end
|
18
|
-
|
19
14
|
# Return data part as String.
|
20
15
|
def ip_data
|
21
|
-
@raw_data[ip_hlen,self.caplen-ip_hlen]
|
16
|
+
@raw_data[ip_hlen*4,self.caplen-ip_hlen*4]
|
22
17
|
end
|
23
18
|
|
24
19
|
#
|
data/lib/spcap/packet.rb
CHANGED
data/lib/spcap/scopy.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Spcap
|
2
|
+
class Scopy < Stream
|
3
|
+
CURRENT_OUT_FILENAME = 'pcapcopy.tmp'
|
4
|
+
FILENAME_EXTENTION = '.pcap'
|
5
|
+
def initialize(istream,opt = {})
|
6
|
+
@tmpname = CURRENT_OUT_FILENAME
|
7
|
+
@wpath = opt[:wpath] || ''
|
8
|
+
@prefix = opt[:prefix] || ''
|
9
|
+
@limit = opt[:limit] || 10000
|
10
|
+
@counter = 0
|
11
|
+
@ostream = new_file
|
12
|
+
super(istream)
|
13
|
+
packing = @unpack_16 + @unpack_16 + @unpack_32 + @unpack_32 + @unpack_32 + @unpack_32
|
14
|
+
@header = @magic_number + [@major_version, @minor_version, 0, 0, @snapshot_length,
|
15
|
+
@linklayer_header_type].pack(packing)
|
16
|
+
end
|
17
|
+
|
18
|
+
def read(size)
|
19
|
+
buf = @istream.read(size)
|
20
|
+
@ostream.write(buf)
|
21
|
+
return buf
|
22
|
+
end
|
23
|
+
|
24
|
+
def new_file ; File.new(File.expand_path(backup_name,@wpath),"w") ; end
|
25
|
+
|
26
|
+
def backup_name
|
27
|
+
@prefix + Time.now.strftime("%Y%m%d%H%M%S%6N") + FILENAME_EXTENTION
|
28
|
+
end
|
29
|
+
|
30
|
+
def switch_out
|
31
|
+
@ostream.close
|
32
|
+
@ostream = new_file
|
33
|
+
@ostream.write(@header)
|
34
|
+
end
|
35
|
+
|
36
|
+
def finalize
|
37
|
+
@ostream.close
|
38
|
+
end
|
39
|
+
|
40
|
+
def next
|
41
|
+
pkt = super
|
42
|
+
@counter += 1
|
43
|
+
if @counter == @limit
|
44
|
+
switch_out
|
45
|
+
@counter = 0
|
46
|
+
end
|
47
|
+
return pkt
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/spcap/stream.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
module Spcap
|
2
|
+
class Stream
|
3
|
+
# File format :
|
4
|
+
# File header
|
5
|
+
# 4 Magic number
|
6
|
+
# 2,2 Major version Minor version
|
7
|
+
# 4 Time zone offset always set to 0
|
8
|
+
# 4 Time stamp accuracy always set to 0
|
9
|
+
# 4 Snapshot length
|
10
|
+
# 4 Link-layer header type
|
11
|
+
|
12
|
+
MagicNumber = ["A1B2C3D4"].pack("H*")
|
13
|
+
|
14
|
+
def initialize(istream)
|
15
|
+
@istream = istream
|
16
|
+
@magic_number = read(4)
|
17
|
+
if @magic_number == MagicNumber
|
18
|
+
@unpack_16 = "n"
|
19
|
+
@unpack_32 = "N"
|
20
|
+
else
|
21
|
+
@unpack_16 = "v"
|
22
|
+
@unpack_32 = "V"
|
23
|
+
end
|
24
|
+
@major_version, @minor_version = read16, read16
|
25
|
+
read(8) # flush unused time_zone_offset_always_0, timestamp_accuracy_always_0,
|
26
|
+
@snapshot_length = read32
|
27
|
+
@linklayer_header_type = read32
|
28
|
+
# if header type is not ethernet raise an error !!
|
29
|
+
raise InitializeException, "Not PCAP ethernet stream is not supported"if @linklayer_header_type != 1
|
30
|
+
|
31
|
+
end
|
32
|
+
def close
|
33
|
+
@istream.close
|
34
|
+
end
|
35
|
+
def read(size)
|
36
|
+
buf = @istream.read(size)
|
37
|
+
return buf
|
38
|
+
end
|
39
|
+
|
40
|
+
def read16
|
41
|
+
buf = read(2)
|
42
|
+
buf.unpack(@unpack_16).first
|
43
|
+
end
|
44
|
+
|
45
|
+
def read32
|
46
|
+
buf = read(4)
|
47
|
+
buf.unpack(@unpack_32).first
|
48
|
+
end
|
49
|
+
# Packets header
|
50
|
+
# 4 Time stamp, seconds value
|
51
|
+
# 4 Time stamp, microseconds value
|
52
|
+
# 4 Length of captured packet data
|
53
|
+
# 4 Un-truncated length of the packet data
|
54
|
+
def each
|
55
|
+
until(@istream.eof?)
|
56
|
+
p = self.next
|
57
|
+
yield p unless p.nil?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def eof? ; @istream.eof? ; end
|
62
|
+
|
63
|
+
def next
|
64
|
+
time = Time.at(read32,read32)
|
65
|
+
caplen = read32
|
66
|
+
len = read32
|
67
|
+
# TODO : move Ethernet parsing in Packet class constructor
|
68
|
+
src_mac_address = read(6)
|
69
|
+
dst_mac_address = read(6)
|
70
|
+
protocol_type = read(2).unpack("n").first
|
71
|
+
raw_data = read(caplen-14)
|
72
|
+
if protocol_type == 0x0800
|
73
|
+
p = Factory.get_packet(time,raw_data,len,@linklayer_header_type)
|
74
|
+
if p.nil?
|
75
|
+
Logger.warn "Spcap::Factory return nil packet"
|
76
|
+
else
|
77
|
+
return p
|
78
|
+
end
|
79
|
+
else
|
80
|
+
# ignore non IPv4 packets
|
81
|
+
Logger.info "Non-IPv4 packets are ignored Protocol = #{protocol_type}"
|
82
|
+
end
|
83
|
+
return nil
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
data/lib/spcap/tcppacket.rb
CHANGED
@@ -5,11 +5,16 @@ module Spcap
|
|
5
5
|
super(time,data,len,datalink)
|
6
6
|
end
|
7
7
|
|
8
|
+
def channel ; [[src,sport],[dst,dport]] ; end
|
9
|
+
def full_session ; channel.sort ; end
|
10
|
+
def session ; full_session.hash ; end
|
11
|
+
def direction ; full_session == channel ; end
|
12
|
+
|
8
13
|
# Return acknowledgement number.
|
9
14
|
def tcp_ack ; ip_data[8,4].unpack("N").first ; end
|
10
15
|
|
11
16
|
# Return data part as String.
|
12
|
-
def tcp_data ; ip_data[tcp_off,tcp_data_len] ; end
|
17
|
+
def tcp_data ; ip_data[tcp_off*4,tcp_data_len] ; end
|
13
18
|
|
14
19
|
# Return length of data part.
|
15
20
|
def tcp_data_len ; ip_len - ( ip_hlen * 4 ) - (tcp_hlen * 4) ; end
|
@@ -34,12 +39,12 @@ module Spcap
|
|
34
39
|
end
|
35
40
|
|
36
41
|
# Return true if flag is set.
|
37
|
-
def tcp_fin? ;
|
38
|
-
def tcp_syn? ;
|
39
|
-
def tcp_rst? ;
|
40
|
-
def tcp_psh? ;
|
41
|
-
def tcp_ack? ;
|
42
|
-
def tcp_urg? ;
|
42
|
+
def tcp_fin? ; flag?(7) ; end
|
43
|
+
def tcp_syn? ; flag?(6) ; end
|
44
|
+
def tcp_rst? ; flag?(5) ; end
|
45
|
+
def tcp_psh? ; flag?(4) ; end
|
46
|
+
def tcp_ack? ; flag?(3) ; end
|
47
|
+
def tcp_urg? ; flag?(2) ; end
|
43
48
|
|
44
49
|
# Return TCP data offset (header length). (Unit: 4-octets)
|
45
50
|
def tcp_hlen ; ( ( ip_data.getbyte(12) & 0XF0) / 16 ) ; end
|
data/lib/spcap/version.rb
CHANGED
data/test/lib/spcap/file_test.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require_relative '../../test_helper'
|
2
2
|
|
3
|
-
describe Spcap::
|
4
|
-
subject { Spcap::
|
3
|
+
describe Spcap::Stream do
|
4
|
+
subject { Spcap::Stream.new(File.open('test/pcap_files/sample.pcap')) }
|
5
5
|
|
6
|
-
it "must be a Spcap::
|
7
|
-
subject.must_be_instance_of(Spcap::
|
6
|
+
it "must be a Spcap::Stream" do
|
7
|
+
subject.must_be_instance_of(Spcap::Stream)
|
8
8
|
end
|
9
9
|
|
10
10
|
it "must handle each without error" do
|
@@ -13,3 +13,44 @@ describe Spcap::File do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
end
|
16
|
+
|
17
|
+
describe Spcap::Scopy do
|
18
|
+
subject { Spcap::Scopy.new(File.open('test/pcap_files/sample.pcap')) }
|
19
|
+
before do
|
20
|
+
@counter=0
|
21
|
+
end
|
22
|
+
it "must be a Spcap::Scopy" do
|
23
|
+
subject.must_be_instance_of(Spcap::Scopy)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "must handle each without error" do
|
27
|
+
subject.each{|p| @counter += 1}
|
28
|
+
@counter.must_equal(37)
|
29
|
+
subject.must_respond_to(:each)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
describe Spcap::TCPPacket do
|
35
|
+
|
36
|
+
subject { packet = nil
|
37
|
+
src = Spcap::Scopy.new(File.open('test/pcap_files/sample.pcap'))
|
38
|
+
packet = src.next
|
39
|
+
packet
|
40
|
+
}
|
41
|
+
before do
|
42
|
+
@src = Spcap::IPAddress.new([174,100,92,122].pack("CCCC"))
|
43
|
+
@sport = 45671
|
44
|
+
@dst = Spcap::IPAddress.new([174,100,92,106].pack("CCCC"))
|
45
|
+
@dport = 111
|
46
|
+
@channel = [[@src,@sport],[@dst,@dport]]
|
47
|
+
@full_session = @channel.sort
|
48
|
+
end
|
49
|
+
it "must be a Spcap::TCPPacket" do
|
50
|
+
subject.must_be_instance_of(Spcap::TCPPacket)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "must handle full_session without error" do
|
54
|
+
subject.full_session.must_equal(@full_session)
|
55
|
+
end
|
56
|
+
end
|
Binary file
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spcap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-10-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -56,16 +56,19 @@ files:
|
|
56
56
|
- README.md
|
57
57
|
- Rakefile
|
58
58
|
- lib/spcap.rb
|
59
|
+
- lib/spcap/exceptions.rb
|
59
60
|
- lib/spcap/factory.rb
|
60
|
-
- lib/spcap/
|
61
|
-
- lib/spcap/ipadress.rb
|
61
|
+
- lib/spcap/ipaddress.rb
|
62
62
|
- lib/spcap/ippacket.rb
|
63
63
|
- lib/spcap/logger.rb
|
64
64
|
- lib/spcap/packet.rb
|
65
|
+
- lib/spcap/scopy.rb
|
66
|
+
- lib/spcap/stream.rb
|
65
67
|
- lib/spcap/tcppacket.rb
|
66
68
|
- lib/spcap/version.rb
|
67
69
|
- spcap.gemspec
|
68
70
|
- test/lib/spcap/file_test.rb
|
71
|
+
- test/pcap_files/sample.pcap
|
69
72
|
- test/test_helper.rb
|
70
73
|
homepage: https://github.com/brodier/spcap
|
71
74
|
licenses:
|
@@ -95,4 +98,5 @@ summary: Pure ruby gem without native exstension that handle pcap file produce b
|
|
95
98
|
pcap library or tcpdump
|
96
99
|
test_files:
|
97
100
|
- test/lib/spcap/file_test.rb
|
101
|
+
- test/pcap_files/sample.pcap
|
98
102
|
- test/test_helper.rb
|
data/lib/spcap/file.rb
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
module Spcap
|
2
|
-
class File
|
3
|
-
# File format :
|
4
|
-
# File header
|
5
|
-
# 4 Magic number
|
6
|
-
# 2,2 Major version Minor version
|
7
|
-
# 4 Time zone offset always set to 0
|
8
|
-
# 4 Time stamp accuracy always set to 0
|
9
|
-
# 4 Snapshot length
|
10
|
-
# 4 Link-layer header type
|
11
|
-
|
12
|
-
MagicNumber = ["A1B2C3D4"].pack("H*")
|
13
|
-
|
14
|
-
def initialize(istream)
|
15
|
-
@istream = istream
|
16
|
-
magic_number = istream.read(4)
|
17
|
-
if magic_number == MagicNumber
|
18
|
-
@unpack_16 = "n"
|
19
|
-
@unpack_32 = "N"
|
20
|
-
else
|
21
|
-
@unpack_16 = "v"
|
22
|
-
@unpack_32 = "V"
|
23
|
-
end
|
24
|
-
@major_version, @minor_version = read16, read16
|
25
|
-
@istream.read(8) # flush unused time_zone_offset_always_0, timestamp_accuracy_always_0,
|
26
|
-
@snapshot_length = read32
|
27
|
-
@linklayer_header_type = read32
|
28
|
-
end
|
29
|
-
|
30
|
-
def read16 ; @istream.read(2).unpack(@unpack_16).first ; end
|
31
|
-
|
32
|
-
def read32 ; @istream.read(4).unpack(@unpack_32).first ; end
|
33
|
-
# Packets header
|
34
|
-
# 4 Time stamp, seconds value
|
35
|
-
# 4 Time stamp, microseconds value
|
36
|
-
# 4 Length of captured packet data
|
37
|
-
# 4 Un-truncated length of the packet data
|
38
|
-
def each
|
39
|
-
until(@istream.eof?)
|
40
|
-
time = Time.at(read32,read32)
|
41
|
-
caplen = read32
|
42
|
-
len = read32
|
43
|
-
raw_data = @istream.read(caplen)
|
44
|
-
yield Packet.new(time,raw_data,len,@linklayer_header_type)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|