spcap 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/spcap.png)](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
|