DIY-pcap 0.4.1 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -4
- data/DIY-pcap.gemspec +17 -17
- data/Gemfile +3 -3
- data/Rakefile +1 -1
- data/lib/DIY-pcap.rb +2 -2
- data/lib/diy/command.rb +7 -1
- data/lib/diy/controller.rb +10 -15
- data/lib/diy/live.rb +9 -2
- data/lib/diy/parser/mu/pcap/ethernet.rb +148 -148
- data/lib/diy/parser/mu/pcap/header.rb +75 -75
- data/lib/diy/parser/mu/pcap/io_pair.rb +67 -67
- data/lib/diy/parser/mu/pcap/io_wrapper.rb +76 -76
- data/lib/diy/parser/mu/pcap/ip.rb +61 -61
- data/lib/diy/parser/mu/pcap/ipv4.rb +257 -257
- data/lib/diy/parser/mu/pcap/ipv6.rb +148 -148
- data/lib/diy/parser/mu/pcap/packet.rb +104 -104
- data/lib/diy/parser/mu/pcap/pkthdr.rb +155 -155
- data/lib/diy/parser/mu/pcap/reader.rb +61 -61
- data/lib/diy/parser/mu/pcap/reader/http_family.rb +170 -170
- data/lib/diy/parser/mu/pcap/sctp.rb +367 -367
- data/lib/diy/parser/mu/pcap/sctp/chunk.rb +123 -123
- data/lib/diy/parser/mu/pcap/sctp/chunk/data.rb +134 -134
- data/lib/diy/parser/mu/pcap/sctp/chunk/init.rb +100 -100
- data/lib/diy/parser/mu/pcap/sctp/chunk/init_ack.rb +68 -68
- data/lib/diy/parser/mu/pcap/sctp/parameter.rb +110 -110
- data/lib/diy/parser/mu/pcap/sctp/parameter/ip_address.rb +48 -48
- data/lib/diy/parser/mu/pcap/stream_packetizer.rb +72 -72
- data/lib/diy/parser/mu/pcap/tcp.rb +505 -505
- data/lib/diy/parser/mu/pcap/udp.rb +69 -69
- data/lib/diy/parser/mu/scenario/pcap.rb +172 -172
- data/lib/diy/parser/mu/scenario/pcap/fields.rb +50 -50
- data/lib/diy/parser/mu/scenario/pcap/rtp.rb +71 -71
- data/lib/diy/parser/pcap.rb +109 -109
- data/lib/diy/version.rb +1 -1
- metadata +7 -9
@@ -1,155 +1,155 @@
|
|
1
|
-
# http://www.mudynamics.com
|
2
|
-
# http://labs.mudynamics.com
|
3
|
-
# http://www.pcapr.net
|
4
|
-
|
5
|
-
module Mu
|
6
|
-
class Pcap
|
7
|
-
|
8
|
-
class Pkthdr
|
9
|
-
attr_accessor :endian, :ts_sec, :ts_usec, :caplen, :len, :pkt, :pkt_raw
|
10
|
-
|
11
|
-
def initialize endian=BIG_ENDIAN, ts_sec=0, ts_usec=0, caplen=0, len=0, pkt=nil
|
12
|
-
@endian = endian
|
13
|
-
@ts_sec = ts_sec
|
14
|
-
@ts_usec = ts_usec
|
15
|
-
@caplen = caplen
|
16
|
-
@len = len
|
17
|
-
@pkt = pkt
|
18
|
-
@pkt_raw = pkt
|
19
|
-
end
|
20
|
-
|
21
|
-
FMT_NNNN = 'NNNN'
|
22
|
-
FMT_VVVV = 'VVVV'
|
23
|
-
def self.read io, endian=BIG_ENDIAN
|
24
|
-
if endian == BIG_ENDIAN
|
25
|
-
format = FMT_NNNN
|
26
|
-
elsif endian == LITTLE_ENDIAN
|
27
|
-
format = FMT_VVVV
|
28
|
-
end
|
29
|
-
bytes = io.read 16
|
30
|
-
if not bytes
|
31
|
-
raise EOFError, 'Missing PCAP packet header'
|
32
|
-
end
|
33
|
-
if not bytes.length == 16
|
34
|
-
raise ParseError, "Truncated PCAP packet header: expected 16 bytes, got #{bytes.length} bytes"
|
35
|
-
end
|
36
|
-
ts_sec, ts_usec, caplen, len = bytes.unpack format
|
37
|
-
pkt = io.read(caplen)
|
38
|
-
if not pkt
|
39
|
-
raise ParseError, 'Missing PCAP packet header packet'
|
40
|
-
end
|
41
|
-
if not pkt.length == caplen
|
42
|
-
raise ParseError, "Truncated PCAP packet header: expected #{pkthdr.caplen} bytes, got #{pkthdr.pkt.length} bytes"
|
43
|
-
end
|
44
|
-
pkthdr = Pkthdr.new endian, ts_sec, ts_usec, caplen, len, pkt
|
45
|
-
return pkthdr
|
46
|
-
end
|
47
|
-
|
48
|
-
def write io
|
49
|
-
if @pkt.is_a? String
|
50
|
-
pkt = @pkt
|
51
|
-
else
|
52
|
-
string_io = StringIO.new
|
53
|
-
@pkt.write string_io
|
54
|
-
pkt = string_io.string
|
55
|
-
end
|
56
|
-
len = pkt.length
|
57
|
-
bytes = [@ts_sec, @ts_usec, len, len].pack FMT_NNNN
|
58
|
-
io.write bytes
|
59
|
-
io.write pkt
|
60
|
-
end
|
61
|
-
|
62
|
-
def decode! endian, linktype
|
63
|
-
@pkt = case linktype
|
64
|
-
when DLT_NULL; Pkthdr.decode_null endian, @pkt
|
65
|
-
when DLT_EN10MB; Pkthdr.decode_en10mb @pkt
|
66
|
-
when DLT_RAW; raise NotImplementedError
|
67
|
-
when DLT_LINUX_SLL; Pkthdr.decode_linux_sll @pkt
|
68
|
-
else raise ParseError, "Unknown PCAP linktype: #{linktype}"
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# See http://wiki.wireshark.org/NullLoopback
|
73
|
-
# and epan/aftypes.h in wireshark code.
|
74
|
-
BSD_AF_INET6 = [
|
75
|
-
OPENBSD_AF_INET6 = 24,
|
76
|
-
FREEBSD_AF_INET6 = 28,
|
77
|
-
DARWIN_AF_INET6 = 30
|
78
|
-
]
|
79
|
-
|
80
|
-
def self.decode_null endian, bytes
|
81
|
-
Pcap.assert bytes.length >= 4, 'Truncated PCAP packet header: ' +
|
82
|
-
"expected at least 4 bytes, got #{bytes.length} bytes"
|
83
|
-
if endian == BIG_ENDIAN
|
84
|
-
format = 'N'
|
85
|
-
elsif endian == LITTLE_ENDIAN
|
86
|
-
format = 'V'
|
87
|
-
end
|
88
|
-
family = bytes[0, 4].unpack(format)[0]
|
89
|
-
bytes = bytes[4..-1]
|
90
|
-
ethernet = Ethernet.new
|
91
|
-
ethernet.src = '00:01:01:00:00:01'
|
92
|
-
ethernet.dst = '00:01:01:00:00:02'
|
93
|
-
ethernet.payload = ethernet.payload_raw = bytes
|
94
|
-
if family != Socket::AF_INET and family != Socket::AF_INET6 and
|
95
|
-
not BSD_AF_INET6.include?(family)
|
96
|
-
raise ParseError, "Unknown PCAP packet header family: #{family}"
|
97
|
-
end
|
98
|
-
begin
|
99
|
-
case family
|
100
|
-
when Socket::AF_INET
|
101
|
-
ethernet.type = Ethernet::ETHERTYPE_IP
|
102
|
-
ethernet.payload = IPv4.from_bytes ethernet.payload
|
103
|
-
when Socket::AF_INET6, FREEBSD_AF_INET6, OPENBSD_AF_INET6, DARWIN_AF_INET6
|
104
|
-
ethernet.type = Ethernet::ETHERTYPE_IP6
|
105
|
-
ethernet.payload = IPv6.from_bytes ethernet.payload
|
106
|
-
else
|
107
|
-
raise NotImplementedError
|
108
|
-
end
|
109
|
-
rescue ParseError => e
|
110
|
-
Pcap.warning e
|
111
|
-
end
|
112
|
-
return ethernet
|
113
|
-
end
|
114
|
-
|
115
|
-
def self.decode_en10mb bytes
|
116
|
-
return Ethernet.from_bytes(bytes)
|
117
|
-
end
|
118
|
-
|
119
|
-
def self.decode_linux_sll bytes
|
120
|
-
Pcap.assert bytes.length >= 16, 'Truncated PCAP packet header: ' +
|
121
|
-
"expected at least 16 bytes, got #{bytes.length} bytes"
|
122
|
-
packet_type, link_type, addr_len = bytes.unpack('nnn')
|
123
|
-
type = bytes[14, 2].unpack('n')[0]
|
124
|
-
bytes = bytes[16..-1]
|
125
|
-
ethernet = Ethernet.new
|
126
|
-
ethernet.type = type
|
127
|
-
ethernet.src = '00:01:01:00:00:01'
|
128
|
-
ethernet.dst = '00:01:01:00:00:02'
|
129
|
-
ethernet.payload = ethernet.payload_raw = bytes
|
130
|
-
begin
|
131
|
-
case type
|
132
|
-
when Ethernet::ETHERTYPE_IP
|
133
|
-
ethernet.payload = IPv4.from_bytes ethernet.payload
|
134
|
-
when Ethernet::ETHERTYPE_IP6
|
135
|
-
ethernet.payload = IPv6.from_bytes ethernet.payload
|
136
|
-
end
|
137
|
-
rescue ParseError => e
|
138
|
-
Pcap.warning e
|
139
|
-
end
|
140
|
-
return ethernet
|
141
|
-
end
|
142
|
-
|
143
|
-
def == other
|
144
|
-
return self.class == other.class &&
|
145
|
-
self.endian == other.endian &&
|
146
|
-
self.ts_sec == other.ts_sec &&
|
147
|
-
self.ts_usec == other.ts_usec &&
|
148
|
-
self.caplen == other.caplen &&
|
149
|
-
self.len == other.len &&
|
150
|
-
self.pkt == other.pkt
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
end
|
155
|
-
end
|
1
|
+
# http://www.mudynamics.com
|
2
|
+
# http://labs.mudynamics.com
|
3
|
+
# http://www.pcapr.net
|
4
|
+
|
5
|
+
module Mu
|
6
|
+
class Pcap
|
7
|
+
|
8
|
+
class Pkthdr
|
9
|
+
attr_accessor :endian, :ts_sec, :ts_usec, :caplen, :len, :pkt, :pkt_raw
|
10
|
+
|
11
|
+
def initialize endian=BIG_ENDIAN, ts_sec=0, ts_usec=0, caplen=0, len=0, pkt=nil
|
12
|
+
@endian = endian
|
13
|
+
@ts_sec = ts_sec
|
14
|
+
@ts_usec = ts_usec
|
15
|
+
@caplen = caplen
|
16
|
+
@len = len
|
17
|
+
@pkt = pkt
|
18
|
+
@pkt_raw = pkt
|
19
|
+
end
|
20
|
+
|
21
|
+
FMT_NNNN = 'NNNN'
|
22
|
+
FMT_VVVV = 'VVVV'
|
23
|
+
def self.read io, endian=BIG_ENDIAN
|
24
|
+
if endian == BIG_ENDIAN
|
25
|
+
format = FMT_NNNN
|
26
|
+
elsif endian == LITTLE_ENDIAN
|
27
|
+
format = FMT_VVVV
|
28
|
+
end
|
29
|
+
bytes = io.read 16
|
30
|
+
if not bytes
|
31
|
+
raise EOFError, 'Missing PCAP packet header'
|
32
|
+
end
|
33
|
+
if not bytes.length == 16
|
34
|
+
raise ParseError, "Truncated PCAP packet header: expected 16 bytes, got #{bytes.length} bytes"
|
35
|
+
end
|
36
|
+
ts_sec, ts_usec, caplen, len = bytes.unpack format
|
37
|
+
pkt = io.read(caplen)
|
38
|
+
if not pkt
|
39
|
+
raise ParseError, 'Missing PCAP packet header packet'
|
40
|
+
end
|
41
|
+
if not pkt.length == caplen
|
42
|
+
raise ParseError, "Truncated PCAP packet header: expected #{pkthdr.caplen} bytes, got #{pkthdr.pkt.length} bytes"
|
43
|
+
end
|
44
|
+
pkthdr = Pkthdr.new endian, ts_sec, ts_usec, caplen, len, pkt
|
45
|
+
return pkthdr
|
46
|
+
end
|
47
|
+
|
48
|
+
def write io
|
49
|
+
if @pkt.is_a? String
|
50
|
+
pkt = @pkt
|
51
|
+
else
|
52
|
+
string_io = StringIO.new
|
53
|
+
@pkt.write string_io
|
54
|
+
pkt = string_io.string
|
55
|
+
end
|
56
|
+
len = pkt.length
|
57
|
+
bytes = [@ts_sec, @ts_usec, len, len].pack FMT_NNNN
|
58
|
+
io.write bytes
|
59
|
+
io.write pkt
|
60
|
+
end
|
61
|
+
|
62
|
+
def decode! endian, linktype
|
63
|
+
@pkt = case linktype
|
64
|
+
when DLT_NULL; Pkthdr.decode_null endian, @pkt
|
65
|
+
when DLT_EN10MB; Pkthdr.decode_en10mb @pkt
|
66
|
+
when DLT_RAW; raise NotImplementedError
|
67
|
+
when DLT_LINUX_SLL; Pkthdr.decode_linux_sll @pkt
|
68
|
+
else raise ParseError, "Unknown PCAP linktype: #{linktype}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# See http://wiki.wireshark.org/NullLoopback
|
73
|
+
# and epan/aftypes.h in wireshark code.
|
74
|
+
BSD_AF_INET6 = [
|
75
|
+
OPENBSD_AF_INET6 = 24,
|
76
|
+
FREEBSD_AF_INET6 = 28,
|
77
|
+
DARWIN_AF_INET6 = 30
|
78
|
+
]
|
79
|
+
|
80
|
+
def self.decode_null endian, bytes
|
81
|
+
Pcap.assert bytes.length >= 4, 'Truncated PCAP packet header: ' +
|
82
|
+
"expected at least 4 bytes, got #{bytes.length} bytes"
|
83
|
+
if endian == BIG_ENDIAN
|
84
|
+
format = 'N'
|
85
|
+
elsif endian == LITTLE_ENDIAN
|
86
|
+
format = 'V'
|
87
|
+
end
|
88
|
+
family = bytes[0, 4].unpack(format)[0]
|
89
|
+
bytes = bytes[4..-1]
|
90
|
+
ethernet = Ethernet.new
|
91
|
+
ethernet.src = '00:01:01:00:00:01'
|
92
|
+
ethernet.dst = '00:01:01:00:00:02'
|
93
|
+
ethernet.payload = ethernet.payload_raw = bytes
|
94
|
+
if family != Socket::AF_INET and family != Socket::AF_INET6 and
|
95
|
+
not BSD_AF_INET6.include?(family)
|
96
|
+
raise ParseError, "Unknown PCAP packet header family: #{family}"
|
97
|
+
end
|
98
|
+
begin
|
99
|
+
case family
|
100
|
+
when Socket::AF_INET
|
101
|
+
ethernet.type = Ethernet::ETHERTYPE_IP
|
102
|
+
ethernet.payload = IPv4.from_bytes ethernet.payload
|
103
|
+
when Socket::AF_INET6, FREEBSD_AF_INET6, OPENBSD_AF_INET6, DARWIN_AF_INET6
|
104
|
+
ethernet.type = Ethernet::ETHERTYPE_IP6
|
105
|
+
ethernet.payload = IPv6.from_bytes ethernet.payload
|
106
|
+
else
|
107
|
+
raise NotImplementedError
|
108
|
+
end
|
109
|
+
rescue ParseError => e
|
110
|
+
Pcap.warning e
|
111
|
+
end
|
112
|
+
return ethernet
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.decode_en10mb bytes
|
116
|
+
return Ethernet.from_bytes(bytes)
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.decode_linux_sll bytes
|
120
|
+
Pcap.assert bytes.length >= 16, 'Truncated PCAP packet header: ' +
|
121
|
+
"expected at least 16 bytes, got #{bytes.length} bytes"
|
122
|
+
packet_type, link_type, addr_len = bytes.unpack('nnn')
|
123
|
+
type = bytes[14, 2].unpack('n')[0]
|
124
|
+
bytes = bytes[16..-1]
|
125
|
+
ethernet = Ethernet.new
|
126
|
+
ethernet.type = type
|
127
|
+
ethernet.src = '00:01:01:00:00:01'
|
128
|
+
ethernet.dst = '00:01:01:00:00:02'
|
129
|
+
ethernet.payload = ethernet.payload_raw = bytes
|
130
|
+
begin
|
131
|
+
case type
|
132
|
+
when Ethernet::ETHERTYPE_IP
|
133
|
+
ethernet.payload = IPv4.from_bytes ethernet.payload
|
134
|
+
when Ethernet::ETHERTYPE_IP6
|
135
|
+
ethernet.payload = IPv6.from_bytes ethernet.payload
|
136
|
+
end
|
137
|
+
rescue ParseError => e
|
138
|
+
Pcap.warning e
|
139
|
+
end
|
140
|
+
return ethernet
|
141
|
+
end
|
142
|
+
|
143
|
+
def == other
|
144
|
+
return self.class == other.class &&
|
145
|
+
self.endian == other.endian &&
|
146
|
+
self.ts_sec == other.ts_sec &&
|
147
|
+
self.ts_usec == other.ts_usec &&
|
148
|
+
self.caplen == other.caplen &&
|
149
|
+
self.len == other.len &&
|
150
|
+
self.pkt == other.pkt
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -1,61 +1,61 @@
|
|
1
|
-
# http://www.mudynamics.com
|
2
|
-
# http://labs.mudynamics.com
|
3
|
-
# http://www.pcapr.net
|
4
|
-
|
5
|
-
module Mu
|
6
|
-
class Pcap
|
7
|
-
|
8
|
-
class Reader
|
9
|
-
attr_accessor :pcap2scenario
|
10
|
-
|
11
|
-
FAMILY_TO_READER = {}
|
12
|
-
|
13
|
-
# Returns a reader instance of specified family. Returns nil when family is :none.
|
14
|
-
def self.reader family
|
15
|
-
if family == :none
|
16
|
-
return nil
|
17
|
-
end
|
18
|
-
|
19
|
-
if klass = FAMILY_TO_READER[family]
|
20
|
-
return klass.new
|
21
|
-
end
|
22
|
-
|
23
|
-
raise ArgumentError, "Unknown protocol family: '#{family}'"
|
24
|
-
end
|
25
|
-
|
26
|
-
# Returns family name
|
27
|
-
def family
|
28
|
-
raise NotImplementedError
|
29
|
-
end
|
30
|
-
|
31
|
-
# Notify parser of bytes written. Parser may update state
|
32
|
-
# to serve as a hint for subsequent reads.
|
33
|
-
def record_write bytes, state=nil
|
34
|
-
begin
|
35
|
-
do_record_write bytes, state
|
36
|
-
rescue
|
37
|
-
nil
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# Returns next complete message from byte stream or nil.
|
42
|
-
def read_message bytes, state=nil
|
43
|
-
read_message! bytes.dup, state
|
44
|
-
end
|
45
|
-
|
46
|
-
# Mutating form of read_message. Removes a complete message
|
47
|
-
# from input stream. Returns the message or nil if there.
|
48
|
-
# is not a complete message.
|
49
|
-
def read_message! bytes, state=nil
|
50
|
-
begin
|
51
|
-
do_read_message! bytes, state
|
52
|
-
rescue
|
53
|
-
nil
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
require 'mu/pcap/reader/http_family'
|
1
|
+
# http://www.mudynamics.com
|
2
|
+
# http://labs.mudynamics.com
|
3
|
+
# http://www.pcapr.net
|
4
|
+
|
5
|
+
module Mu
|
6
|
+
class Pcap
|
7
|
+
|
8
|
+
class Reader
|
9
|
+
attr_accessor :pcap2scenario
|
10
|
+
|
11
|
+
FAMILY_TO_READER = {}
|
12
|
+
|
13
|
+
# Returns a reader instance of specified family. Returns nil when family is :none.
|
14
|
+
def self.reader family
|
15
|
+
if family == :none
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
|
19
|
+
if klass = FAMILY_TO_READER[family]
|
20
|
+
return klass.new
|
21
|
+
end
|
22
|
+
|
23
|
+
raise ArgumentError, "Unknown protocol family: '#{family}'"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns family name
|
27
|
+
def family
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
# Notify parser of bytes written. Parser may update state
|
32
|
+
# to serve as a hint for subsequent reads.
|
33
|
+
def record_write bytes, state=nil
|
34
|
+
begin
|
35
|
+
do_record_write bytes, state
|
36
|
+
rescue
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns next complete message from byte stream or nil.
|
42
|
+
def read_message bytes, state=nil
|
43
|
+
read_message! bytes.dup, state
|
44
|
+
end
|
45
|
+
|
46
|
+
# Mutating form of read_message. Removes a complete message
|
47
|
+
# from input stream. Returns the message or nil if there.
|
48
|
+
# is not a complete message.
|
49
|
+
def read_message! bytes, state=nil
|
50
|
+
begin
|
51
|
+
do_read_message! bytes, state
|
52
|
+
rescue
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
require 'mu/pcap/reader/http_family'
|
@@ -1,170 +1,170 @@
|
|
1
|
-
# http://www.mudynamics.com
|
2
|
-
# http://labs.mudynamics.com
|
3
|
-
# http://www.pcapr.net
|
4
|
-
|
5
|
-
require 'mu/pcap/reader'
|
6
|
-
require 'stringio'
|
7
|
-
require 'zlib'
|
8
|
-
|
9
|
-
module Mu
|
10
|
-
class Pcap
|
11
|
-
class Reader
|
12
|
-
|
13
|
-
# Reader for HTTP family of protocols (HTTP/SIP/RTSP).
|
14
|
-
# Handles message boundaries and decompressing/dechunking payloads.
|
15
|
-
class HttpFamily < Reader
|
16
|
-
FAMILY = :http
|
17
|
-
FAMILY_TO_READER[FAMILY] = self
|
18
|
-
CRLF = "\r\n"
|
19
|
-
def family
|
20
|
-
FAMILY
|
21
|
-
end
|
22
|
-
|
23
|
-
def do_record_write bytes, state=nil
|
24
|
-
return if not state
|
25
|
-
if bytes =~ RE_REQUEST_LINE
|
26
|
-
method = $1
|
27
|
-
requests = state[:requests] ||= []
|
28
|
-
requests << method
|
29
|
-
end
|
30
|
-
end
|
31
|
-
private :do_record_write
|
32
|
-
|
33
|
-
RE_CONTENT_ENCODING = /^content-encoding:\s*(gzip|deflate)/i
|
34
|
-
RE_CHUNKED = /Transfer-Encoding:\s*chunked/i
|
35
|
-
RE_HEADERS_COMPLETE = /.*?\r\n\r\n/m
|
36
|
-
# Request line e.g. GET /index.html HTTP/1.1
|
37
|
-
RE_REQUEST_LINE = /\A([^ \t\r\n]+)[ \t]+([^ \t\r\n]+)[ \t]+(HTTP|SIP|RTSP)\/[\d.]+.*\r\n/
|
38
|
-
# Status line e.g. SIP/2.0 404 Authorization required
|
39
|
-
RE_STATUS_LINE = /\A((HTTP|SIP|RTSP)\/[\d.]+[ \t]+(\d+))\b.*\r\n/
|
40
|
-
|
41
|
-
RE_CONTENT_LENGTH = /^(Content-Length)(:\s*)(\d+)\r\n/i
|
42
|
-
RE_CONTENT_LENGTH_SIP = /^(Content-Length|l)(:\s*)(\d+)\r\n/i
|
43
|
-
|
44
|
-
|
45
|
-
def do_read_message! bytes, state=nil
|
46
|
-
case bytes
|
47
|
-
when RE_REQUEST_LINE
|
48
|
-
proto = $3
|
49
|
-
when RE_STATUS_LINE
|
50
|
-
proto = $2
|
51
|
-
status = $3.to_i
|
52
|
-
if state
|
53
|
-
requests = state[:requests] ||= []
|
54
|
-
if requests[0] == "HEAD"
|
55
|
-
reply_to_head = true
|
56
|
-
end
|
57
|
-
if status > 199
|
58
|
-
# We have a final response. Forget about request.
|
59
|
-
requests.shift
|
60
|
-
end
|
61
|
-
end
|
62
|
-
else
|
63
|
-
return nil # Not http family.
|
64
|
-
end
|
65
|
-
|
66
|
-
# Read headers
|
67
|
-
if bytes =~ RE_HEADERS_COMPLETE
|
68
|
-
headers = $&
|
69
|
-
rest = $'
|
70
|
-
else
|
71
|
-
return nil
|
72
|
-
end
|
73
|
-
message = [headers]
|
74
|
-
|
75
|
-
# Read payload.
|
76
|
-
if proto == 'SIP'
|
77
|
-
re_content_length = RE_CONTENT_LENGTH_SIP
|
78
|
-
else
|
79
|
-
re_content_length = RE_CONTENT_LENGTH
|
80
|
-
end
|
81
|
-
if reply_to_head
|
82
|
-
length = 0
|
83
|
-
elsif headers =~ RE_CHUNKED
|
84
|
-
# Read chunks, dechunking in runtime case.
|
85
|
-
raw, dechunked = get_chunks(rest)
|
86
|
-
if raw
|
87
|
-
length = raw.length
|
88
|
-
payload = raw
|
89
|
-
else
|
90
|
-
return nil # Last chunk not received.
|
91
|
-
end
|
92
|
-
elsif headers =~ re_content_length
|
93
|
-
length = $3.to_i
|
94
|
-
if rest.length >= length
|
95
|
-
payload = rest.slice(0,length)
|
96
|
-
else
|
97
|
-
return nil # Not enough bytes.
|
98
|
-
end
|
99
|
-
else
|
100
|
-
# XXX. When there is a payload and no content-length
|
101
|
-
# header HTTP RFC says to read until connection close.
|
102
|
-
length = 0
|
103
|
-
end
|
104
|
-
|
105
|
-
message << payload
|
106
|
-
|
107
|
-
# Consume message from input bytes.
|
108
|
-
message_len = headers.length + length
|
109
|
-
if bytes.length >= message_len
|
110
|
-
bytes.slice!(0, message_len)
|
111
|
-
return message.join
|
112
|
-
else
|
113
|
-
return nil # Not enough bytes.
|
114
|
-
end
|
115
|
-
end
|
116
|
-
private :do_read_message!
|
117
|
-
|
118
|
-
# Returns array containing raw and dechunked payload. Returns nil
|
119
|
-
# if payload cannot be completely read.
|
120
|
-
RE_CHUNK_SIZE_LINE = /\A([[:xdigit:]]+)\r\n?/
|
121
|
-
def get_chunks bytes
|
122
|
-
raw = []
|
123
|
-
dechunked = []
|
124
|
-
io = StringIO.new bytes
|
125
|
-
until io.eof?
|
126
|
-
# Read size line
|
127
|
-
size_line = io.readline
|
128
|
-
raw << size_line
|
129
|
-
if size_line =~ RE_CHUNK_SIZE_LINE
|
130
|
-
chunk_size = $1.to_i(16)
|
131
|
-
else
|
132
|
-
# Malformed chunk size line
|
133
|
-
$stderr.puts "malformed size line : #{size_line.inspect}"
|
134
|
-
return nil
|
135
|
-
end
|
136
|
-
|
137
|
-
# Read chunk data
|
138
|
-
chunk = io.read(chunk_size)
|
139
|
-
if chunk.size < chunk_size
|
140
|
-
# malformed/incomplete
|
141
|
-
$stderr.puts "malformed/incomplete #{chunk_size}"
|
142
|
-
return nil
|
143
|
-
end
|
144
|
-
raw << chunk
|
145
|
-
dechunked << chunk
|
146
|
-
# Get end-of-chunk CRLF
|
147
|
-
crlf = io.read(2)
|
148
|
-
if crlf == CRLF
|
149
|
-
raw << crlf
|
150
|
-
else
|
151
|
-
# CRLF has not arrived or, if this is the last chunk,
|
152
|
-
# we might be looking at the first two bytes of a trailer
|
153
|
-
# and we don't support trailers (see rfc 2616 sec3.6.1).
|
154
|
-
return nil
|
155
|
-
end
|
156
|
-
|
157
|
-
if chunk_size == 0
|
158
|
-
# Done. Return raw and dechunked payloads.
|
159
|
-
return raw.join, dechunked.join
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# EOF w/out reaching last chunk.
|
164
|
-
return nil
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
1
|
+
# http://www.mudynamics.com
|
2
|
+
# http://labs.mudynamics.com
|
3
|
+
# http://www.pcapr.net
|
4
|
+
|
5
|
+
require 'mu/pcap/reader'
|
6
|
+
require 'stringio'
|
7
|
+
require 'zlib'
|
8
|
+
|
9
|
+
module Mu
|
10
|
+
class Pcap
|
11
|
+
class Reader
|
12
|
+
|
13
|
+
# Reader for HTTP family of protocols (HTTP/SIP/RTSP).
|
14
|
+
# Handles message boundaries and decompressing/dechunking payloads.
|
15
|
+
class HttpFamily < Reader
|
16
|
+
FAMILY = :http
|
17
|
+
FAMILY_TO_READER[FAMILY] = self
|
18
|
+
CRLF = "\r\n"
|
19
|
+
def family
|
20
|
+
FAMILY
|
21
|
+
end
|
22
|
+
|
23
|
+
def do_record_write bytes, state=nil
|
24
|
+
return if not state
|
25
|
+
if bytes =~ RE_REQUEST_LINE
|
26
|
+
method = $1
|
27
|
+
requests = state[:requests] ||= []
|
28
|
+
requests << method
|
29
|
+
end
|
30
|
+
end
|
31
|
+
private :do_record_write
|
32
|
+
|
33
|
+
RE_CONTENT_ENCODING = /^content-encoding:\s*(gzip|deflate)/i
|
34
|
+
RE_CHUNKED = /Transfer-Encoding:\s*chunked/i
|
35
|
+
RE_HEADERS_COMPLETE = /.*?\r\n\r\n/m
|
36
|
+
# Request line e.g. GET /index.html HTTP/1.1
|
37
|
+
RE_REQUEST_LINE = /\A([^ \t\r\n]+)[ \t]+([^ \t\r\n]+)[ \t]+(HTTP|SIP|RTSP)\/[\d.]+.*\r\n/
|
38
|
+
# Status line e.g. SIP/2.0 404 Authorization required
|
39
|
+
RE_STATUS_LINE = /\A((HTTP|SIP|RTSP)\/[\d.]+[ \t]+(\d+))\b.*\r\n/
|
40
|
+
|
41
|
+
RE_CONTENT_LENGTH = /^(Content-Length)(:\s*)(\d+)\r\n/i
|
42
|
+
RE_CONTENT_LENGTH_SIP = /^(Content-Length|l)(:\s*)(\d+)\r\n/i
|
43
|
+
|
44
|
+
|
45
|
+
def do_read_message! bytes, state=nil
|
46
|
+
case bytes
|
47
|
+
when RE_REQUEST_LINE
|
48
|
+
proto = $3
|
49
|
+
when RE_STATUS_LINE
|
50
|
+
proto = $2
|
51
|
+
status = $3.to_i
|
52
|
+
if state
|
53
|
+
requests = state[:requests] ||= []
|
54
|
+
if requests[0] == "HEAD"
|
55
|
+
reply_to_head = true
|
56
|
+
end
|
57
|
+
if status > 199
|
58
|
+
# We have a final response. Forget about request.
|
59
|
+
requests.shift
|
60
|
+
end
|
61
|
+
end
|
62
|
+
else
|
63
|
+
return nil # Not http family.
|
64
|
+
end
|
65
|
+
|
66
|
+
# Read headers
|
67
|
+
if bytes =~ RE_HEADERS_COMPLETE
|
68
|
+
headers = $&
|
69
|
+
rest = $'
|
70
|
+
else
|
71
|
+
return nil
|
72
|
+
end
|
73
|
+
message = [headers]
|
74
|
+
|
75
|
+
# Read payload.
|
76
|
+
if proto == 'SIP'
|
77
|
+
re_content_length = RE_CONTENT_LENGTH_SIP
|
78
|
+
else
|
79
|
+
re_content_length = RE_CONTENT_LENGTH
|
80
|
+
end
|
81
|
+
if reply_to_head
|
82
|
+
length = 0
|
83
|
+
elsif headers =~ RE_CHUNKED
|
84
|
+
# Read chunks, dechunking in runtime case.
|
85
|
+
raw, dechunked = get_chunks(rest)
|
86
|
+
if raw
|
87
|
+
length = raw.length
|
88
|
+
payload = raw
|
89
|
+
else
|
90
|
+
return nil # Last chunk not received.
|
91
|
+
end
|
92
|
+
elsif headers =~ re_content_length
|
93
|
+
length = $3.to_i
|
94
|
+
if rest.length >= length
|
95
|
+
payload = rest.slice(0,length)
|
96
|
+
else
|
97
|
+
return nil # Not enough bytes.
|
98
|
+
end
|
99
|
+
else
|
100
|
+
# XXX. When there is a payload and no content-length
|
101
|
+
# header HTTP RFC says to read until connection close.
|
102
|
+
length = 0
|
103
|
+
end
|
104
|
+
|
105
|
+
message << payload
|
106
|
+
|
107
|
+
# Consume message from input bytes.
|
108
|
+
message_len = headers.length + length
|
109
|
+
if bytes.length >= message_len
|
110
|
+
bytes.slice!(0, message_len)
|
111
|
+
return message.join
|
112
|
+
else
|
113
|
+
return nil # Not enough bytes.
|
114
|
+
end
|
115
|
+
end
|
116
|
+
private :do_read_message!
|
117
|
+
|
118
|
+
# Returns array containing raw and dechunked payload. Returns nil
|
119
|
+
# if payload cannot be completely read.
|
120
|
+
RE_CHUNK_SIZE_LINE = /\A([[:xdigit:]]+)\r\n?/
|
121
|
+
def get_chunks bytes
|
122
|
+
raw = []
|
123
|
+
dechunked = []
|
124
|
+
io = StringIO.new bytes
|
125
|
+
until io.eof?
|
126
|
+
# Read size line
|
127
|
+
size_line = io.readline
|
128
|
+
raw << size_line
|
129
|
+
if size_line =~ RE_CHUNK_SIZE_LINE
|
130
|
+
chunk_size = $1.to_i(16)
|
131
|
+
else
|
132
|
+
# Malformed chunk size line
|
133
|
+
$stderr.puts "malformed size line : #{size_line.inspect}"
|
134
|
+
return nil
|
135
|
+
end
|
136
|
+
|
137
|
+
# Read chunk data
|
138
|
+
chunk = io.read(chunk_size)
|
139
|
+
if chunk.size < chunk_size
|
140
|
+
# malformed/incomplete
|
141
|
+
$stderr.puts "malformed/incomplete #{chunk_size}"
|
142
|
+
return nil
|
143
|
+
end
|
144
|
+
raw << chunk
|
145
|
+
dechunked << chunk
|
146
|
+
# Get end-of-chunk CRLF
|
147
|
+
crlf = io.read(2)
|
148
|
+
if crlf == CRLF
|
149
|
+
raw << crlf
|
150
|
+
else
|
151
|
+
# CRLF has not arrived or, if this is the last chunk,
|
152
|
+
# we might be looking at the first two bytes of a trailer
|
153
|
+
# and we don't support trailers (see rfc 2616 sec3.6.1).
|
154
|
+
return nil
|
155
|
+
end
|
156
|
+
|
157
|
+
if chunk_size == 0
|
158
|
+
# Done. Return raw and dechunked payloads.
|
159
|
+
return raw.join, dechunked.join
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# EOF w/out reaching last chunk.
|
164
|
+
return nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|