DIY-pcap 0.4.1 → 0.4.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 +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
|