solutious-stella 0.5.5
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/CHANGES.txt +36 -0
- data/README.textile +162 -0
- data/Rakefile +88 -0
- data/bin/stella +12 -0
- data/bin/stella.bat +12 -0
- data/lib/daemonize.rb +56 -0
- data/lib/pcaplet.rb +180 -0
- data/lib/stella.rb +101 -0
- data/lib/stella/adapter/ab.rb +337 -0
- data/lib/stella/adapter/base.rb +106 -0
- data/lib/stella/adapter/httperf.rb +305 -0
- data/lib/stella/adapter/pcap_watcher.rb +221 -0
- data/lib/stella/adapter/proxy_watcher.rb +76 -0
- data/lib/stella/adapter/siege.rb +341 -0
- data/lib/stella/cli.rb +258 -0
- data/lib/stella/cli/agents.rb +73 -0
- data/lib/stella/cli/base.rb +55 -0
- data/lib/stella/cli/language.rb +18 -0
- data/lib/stella/cli/localtest.rb +78 -0
- data/lib/stella/cli/sysinfo.rb +16 -0
- data/lib/stella/cli/watch.rb +278 -0
- data/lib/stella/command/base.rb +40 -0
- data/lib/stella/command/localtest.rb +358 -0
- data/lib/stella/data/domain.rb +82 -0
- data/lib/stella/data/http.rb +131 -0
- data/lib/stella/logger.rb +84 -0
- data/lib/stella/response.rb +85 -0
- data/lib/stella/storable.rb +201 -0
- data/lib/stella/support.rb +276 -0
- data/lib/stella/sysinfo.rb +257 -0
- data/lib/stella/test/definition.rb +79 -0
- data/lib/stella/test/run/summary.rb +70 -0
- data/lib/stella/test/stats.rb +114 -0
- data/lib/stella/text.rb +64 -0
- data/lib/stella/text/resource.rb +38 -0
- data/lib/utils/crypto-key.rb +84 -0
- data/lib/utils/domainutil.rb +47 -0
- data/lib/utils/escape.rb +302 -0
- data/lib/utils/fileutil.rb +78 -0
- data/lib/utils/httputil.rb +266 -0
- data/lib/utils/mathutil.rb +15 -0
- data/lib/utils/stats.rb +88 -0
- data/lib/utils/textgraph.rb +267 -0
- data/lib/utils/timerutil.rb +58 -0
- data/lib/win32/Console.rb +970 -0
- data/lib/win32/Console/ANSI.rb +305 -0
- data/support/kvm.h +91 -0
- data/support/ruby-pcap-takuma-notes.txt +19 -0
- data/support/ruby-pcap-takuma-patch.txt +30 -0
- data/support/text/en.yaml +80 -0
- data/support/text/nl.yaml +7 -0
- data/support/useragents.txt +75 -0
- data/tests/01-util_test.rb +0 -0
- data/tests/02-stella-util_test.rb +42 -0
- data/tests/10-stella_test.rb +104 -0
- data/tests/11-stella-storable_test.rb +68 -0
- data/tests/60-stella-command_test.rb +248 -0
- data/tests/80-stella-cli_test.rb +45 -0
- data/tests/spec-helper.rb +31 -0
- metadata +165 -0
@@ -0,0 +1,305 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Stella
|
4
|
+
module Adapter
|
5
|
+
|
6
|
+
#Usage: httperf [-hdvV] [--add-header S] [--burst-length N] [--client N/N]
|
7
|
+
# [--close-with-reset] [--debug N] [--failure-status N]
|
8
|
+
# [--help] [--hog] [--http-version S] [--max-connections N]
|
9
|
+
# [--max-piped-calls N] [--method S] [--no-host-hdr]
|
10
|
+
# [--num-calls N] [--num-conns N] [--period [d|u|e]T1[,T2]]
|
11
|
+
# [--port N] [--print-reply [header|body]] [--print-request [header|body]]
|
12
|
+
# [--rate X] [--recv-buffer N] [--retry-on-failure] [--send-buffer N]
|
13
|
+
# [--server S] [--server-name S] [--session-cookies]
|
14
|
+
# [--ssl] [--ssl-ciphers L] [--ssl-no-reuse]
|
15
|
+
# [--think-timeout X] [--timeout X] [--uri S] [--verbose] [--version]
|
16
|
+
# [--wlog y|n,file] [--wsess N,N,X] [--wsesslog N,X,file]
|
17
|
+
# [--wset N,X]
|
18
|
+
#
|
19
|
+
class Httperf < Stella::Adapter::Base
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
attr_accessor :hog, :server, :uri, :num_conns, :num_calls, :rate, :timeout, :think_timeout, :port
|
24
|
+
attr_accessor :burst_length, :client, :close_with_reset, :debug, :failure_status
|
25
|
+
attr_accessor :help, :http_version, :max_connections, :max_piped_calls, :method, :no_host_hdr
|
26
|
+
attr_accessor :period, :print_reply, :print_request, :recv_buffer, :retry_on_failure, :send_buffer
|
27
|
+
attr_accessor :server_name, :session_cookies, :ssl, :ssl_ciphers, :ssl_no_reuse, :verbose
|
28
|
+
|
29
|
+
attr_writer :version, :add_header, :wlog, :wsess, :wsesslog, :wset
|
30
|
+
|
31
|
+
def initialize(options={}, arguments=[])
|
32
|
+
|
33
|
+
@name = 'httperf'
|
34
|
+
@private_variables = ['private_variables', 'name', 'arguments', 'load_factor', 'working_directory']
|
35
|
+
@load_factor = 1
|
36
|
+
|
37
|
+
super(options, arguments)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
def error
|
43
|
+
(File.exists? stderr_path) ? FileUtil.read_file(stderr_path) : "Unknown error"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Before calling run
|
47
|
+
def before
|
48
|
+
|
49
|
+
|
50
|
+
end
|
51
|
+
def command
|
52
|
+
raise CommandNotReady.new(self.class.to_s) unless ready?
|
53
|
+
|
54
|
+
command = "#{@name} "
|
55
|
+
|
56
|
+
instance_variables.each do |name|
|
57
|
+
canon = name.tr('@', '') # instance_variables returns '@name'
|
58
|
+
next if @private_variables.member?(canon)
|
59
|
+
|
60
|
+
# It's important that we take the value from the getter method
|
61
|
+
# because it applies the load factor.
|
62
|
+
value = self.send(canon)
|
63
|
+
if (value.is_a? Array)
|
64
|
+
value.each { |el| command << "--#{canon.tr('_', '-')} #{EscapeUtil.shell_single_word(el.to_s)} " }
|
65
|
+
else
|
66
|
+
command << "--#{canon.tr('_', '-')} #{EscapeUtil.shell_single_word(value.to_s)} "
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
command << (@arguments.map { |uri| "'#{uri}'" }).join(' ') unless @arguments.empty?
|
72
|
+
command
|
73
|
+
end
|
74
|
+
|
75
|
+
# After calling run
|
76
|
+
def after
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
def process_arguments(arguments)
|
82
|
+
|
83
|
+
opts = OptionParser.new
|
84
|
+
opts.on('--hog') do @hog = true end
|
85
|
+
opts.on('--server=S', String) do |v| @server = v end
|
86
|
+
opts.on('--server-name=S', String) do |v| @server_name = v end
|
87
|
+
opts.on('--port=N', Integer) do |v| @port = v end
|
88
|
+
opts.on('--uri=S', String) do |v| @uri = v end
|
89
|
+
opts.on('--num-conns=N', Integer) do |v| @num_conns = v end
|
90
|
+
opts.on('--num-calls=N', Integer) do |v| @num_calls = v end
|
91
|
+
opts.on('--rate=N', Integer) do |v| @rate = v end
|
92
|
+
opts.on('--timeout=N', Integer) do |v| @timeout = v end
|
93
|
+
opts.on('--think-timeout=N', Integer) do |v| @think_timeout = v end
|
94
|
+
|
95
|
+
opts.on('-h', '--help') do |v| @help = true end
|
96
|
+
opts.on('-v', '--verbose') do |v| @verbose = true end
|
97
|
+
opts.on('-V', '--version') do |v| @version = true end
|
98
|
+
opts.on('--close-with-reset') do |v| @close_with_reset = true end
|
99
|
+
opts.on('--session-cookies') do |v| @session_cookies = true end
|
100
|
+
opts.on('--ssl') do |v| @ssl = true end
|
101
|
+
opts.on('--ssl-ciphers') do |v| @ssl_ciphers = true end
|
102
|
+
opts.on('--ssl-no-reuse') do |v| @ssl_no_reuse = true end
|
103
|
+
opts.on('--no-host-hdr') do |v| @no_host_hdr = true end
|
104
|
+
opts.on('--retry-on-failure') do |v| @retry_on_failure = true end
|
105
|
+
|
106
|
+
opts.on('--add-header=S', String) do |v| @add_header ||= []; @add_header << v; end
|
107
|
+
opts.on('--burst-length=N', Integer) do |v| @burst_length = v end
|
108
|
+
opts.on('--client=S', String) do |v| @client = v end
|
109
|
+
opts.on('-d N', '--debug=N', Integer) do |v| @debug ||= 0; @debug = v end
|
110
|
+
opts.on('--failure-status=N', Integer) do |v| @failure_status = v end
|
111
|
+
|
112
|
+
opts.on('--http-version=S', String) do |v| @http_version = v end
|
113
|
+
|
114
|
+
opts.on('--max-connections=N', Integer) do |v| @max_connections = v end
|
115
|
+
opts.on('--max-piped-calls=N', Integer) do |v| @max_piped_calls = v end
|
116
|
+
opts.on('--method=S', String) do |v| @method = v end
|
117
|
+
|
118
|
+
opts.on('--period=S', String) do |v| @period = v end # TODO: Requires parsing
|
119
|
+
opts.on('--print-reply=[S]', String) do |v| @print_reply = v end
|
120
|
+
opts.on('--print-request=[S]', String) do |v| @print_request = v end
|
121
|
+
|
122
|
+
opts.on('--recv-buffer=N', Integer) do |v| @recv_buffer = v end
|
123
|
+
opts.on('--send-buffer=N', Integer) do |v| @send_buffer = v end
|
124
|
+
|
125
|
+
|
126
|
+
opts.on('--wlog=S', String) do |v| @wlog = Stella::Util::expand_str(v) end
|
127
|
+
opts.on('--wsess=S', String) do |v| @wsess = Stella::Util::expand_str(v, Integer) end
|
128
|
+
opts.on('--wsesslog=S', String) do |v| @wsesslog = Stella::Util::expand_str(v) end
|
129
|
+
opts.on('--wset=S', String) do |v| @wset = Stella::Util::expand_str(v) end
|
130
|
+
|
131
|
+
if @wsess
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
# parse! removes the options it finds.
|
136
|
+
# It also fails when it finds unknown switches (i.e. -X)
|
137
|
+
# Which should leave only the remaining arguments (URIs in this case)
|
138
|
+
opts.parse!(arguments)
|
139
|
+
|
140
|
+
self.arguments = arguments
|
141
|
+
|
142
|
+
rescue OptionParser::InvalidOption => ex
|
143
|
+
# We want to replace this text so we grab just the name of the argument
|
144
|
+
badarg = ex.message.gsub('invalid option: ', '')
|
145
|
+
raise InvalidArgument.new(badarg)
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def version
|
150
|
+
vsn = 0
|
151
|
+
Stella::Util.capture_output("#{@name} --version") do |stdout, stderr|
|
152
|
+
stdout.join.scan(/httperf\-([\d\.]+)\s/) { |v| vsn = v[0] }
|
153
|
+
end
|
154
|
+
vsn
|
155
|
+
end
|
156
|
+
|
157
|
+
# loadtest
|
158
|
+
#
|
159
|
+
# True or false: is the call to siege a load test? If it's a call to help or version or
|
160
|
+
# to display the config this with return false. It's no reason for someone to make this
|
161
|
+
# call through Stella but it's here for goodness sake.
|
162
|
+
def loadtest?
|
163
|
+
@uri && !@uri.empty?
|
164
|
+
end
|
165
|
+
def ready?
|
166
|
+
@name && !instance_variables.empty?
|
167
|
+
end
|
168
|
+
|
169
|
+
def add_header(name=false, value=false)
|
170
|
+
# This is a hack since we have an instance variable called add_header.
|
171
|
+
# I figure this is the best of two evils because I'd rather keep the
|
172
|
+
# instance variable naming consistent.
|
173
|
+
return @add_header if !name && !value
|
174
|
+
@add_header ||= []
|
175
|
+
@add_header << "#{name}: #{value}"
|
176
|
+
end
|
177
|
+
|
178
|
+
def user_agent=(list=[])
|
179
|
+
return unless list && !list.empty?
|
180
|
+
list = list.to_ary
|
181
|
+
list.each do |agent|
|
182
|
+
add_header("User-Agent", agent)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
def vusers
|
186
|
+
@wsess[1]
|
187
|
+
end
|
188
|
+
def vuser_rate
|
189
|
+
"#{vusers}/#{rate}"
|
190
|
+
end
|
191
|
+
|
192
|
+
def vusers=(v)
|
193
|
+
0
|
194
|
+
end
|
195
|
+
def requests
|
196
|
+
@num_conns || (@wsess[0] * @wsess[1])
|
197
|
+
end
|
198
|
+
def requests=(v)
|
199
|
+
0
|
200
|
+
end
|
201
|
+
def vuser_requests
|
202
|
+
0
|
203
|
+
end
|
204
|
+
def wsess
|
205
|
+
@wsess.join(',')
|
206
|
+
end
|
207
|
+
|
208
|
+
def wset
|
209
|
+
@wset.join(',')
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
def wsesslog
|
214
|
+
@wsesslog.join(',')
|
215
|
+
end
|
216
|
+
def wlog
|
217
|
+
@wlog.join(',')
|
218
|
+
end
|
219
|
+
|
220
|
+
#def concurrent
|
221
|
+
# (@concurrent * @load_factor).to_i
|
222
|
+
#end
|
223
|
+
#def concurrent_f
|
224
|
+
# (@concurrent * @load_factor).to_f
|
225
|
+
#end
|
226
|
+
#def reps
|
227
|
+
# @reps
|
228
|
+
#end
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
# Siege writes the summary to STDERR
|
233
|
+
def summary_file
|
234
|
+
File.new(stdout_path) if File.exists?(stdout_path)
|
235
|
+
end
|
236
|
+
|
237
|
+
def rc_file
|
238
|
+
File.join(@working_directory, "siegerc")
|
239
|
+
end
|
240
|
+
|
241
|
+
def log_file
|
242
|
+
File.join(@working_directory, "siege.log")
|
243
|
+
end
|
244
|
+
|
245
|
+
def uris_file
|
246
|
+
File.join(@working_directory, File.basename(@file))
|
247
|
+
end
|
248
|
+
|
249
|
+
# httperf --hog --timeout=30 --client=0/1 --server=127.0.0.1 --port=5600 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=5 --num-calls=1
|
250
|
+
# httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE
|
251
|
+
# Maximum connect burst length: 1
|
252
|
+
#
|
253
|
+
# Total: connections 5 requests 5 replies 5 test-duration 0.513 s
|
254
|
+
#
|
255
|
+
# Connection rate: 9.7 conn/s (102.7 ms/conn, <=1 concurrent connections)
|
256
|
+
# Connection time [ms]: min 102.1 avg 102.7 max 104.1 median 102.5 stddev 0.8
|
257
|
+
# Connection time [ms]: connect 0.2
|
258
|
+
# Connection length [replies/conn]: 1.000
|
259
|
+
#
|
260
|
+
# Request rate: 9.7 req/s (102.7 ms/req)
|
261
|
+
# Request size [B]: 62.0
|
262
|
+
#
|
263
|
+
# Reply rate [replies/s]: min 0.0 avg 0.0 max 0.0 stddev 0.0 (0 samples)
|
264
|
+
# Reply time [ms]: response 102.3 transfer 0.1
|
265
|
+
# Reply size [B]: header 136.0 content 96.0 footer 0.0 (total 232.0)
|
266
|
+
# Reply status: 1xx=0 2xx=5 3xx=0 4xx=0 5xx=0
|
267
|
+
#
|
268
|
+
# CPU time [s]: user 0.12 system 0.39 (user 22.5% system 75.3% total 97.8%)
|
269
|
+
# Net I/O: 2.8 KB/s (0.0*10^6 bps)
|
270
|
+
#
|
271
|
+
# Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
|
272
|
+
# Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0
|
273
|
+
|
274
|
+
def summary
|
275
|
+
return unless summary_file
|
276
|
+
|
277
|
+
raw = summary_file.readlines.join
|
278
|
+
stats = Stella::Test::Run::Summary.new
|
279
|
+
|
280
|
+
raw.scan(/Request rate: (\d+?\.\d+?) req.s .(\d+?\.\d+?) ms.req./) do |rate,time|
|
281
|
+
stats.transaction_rate = rate.to_f
|
282
|
+
stats.response_time = (time.to_f) / 1000
|
283
|
+
end
|
284
|
+
|
285
|
+
raw.scan(/connections (\d+?) requests (\d+?) replies (\d+?) test-duration (\d+\.\d+?) s/) do |conn,req,rep,time|
|
286
|
+
stats.elapsed_time = time.to_f
|
287
|
+
stats.successful = rep.to_i
|
288
|
+
stats.failed = conn.to_i - rep.to_i # maybe this should be from the Error line
|
289
|
+
stats.transactions = conn.to_i
|
290
|
+
end
|
291
|
+
|
292
|
+
raw.scan(/header (\d+\.\d+?)\s+.+?\s+.total (\d+\.\d+?)./) do |h,t|
|
293
|
+
stats.data_transferred = ((t.to_f || 0 ) / 1_048_576).to_f
|
294
|
+
stats.headers_transferred = ((h.to_f || 0 ) / 1_048_576).to_f
|
295
|
+
end
|
296
|
+
stats.vusers = self.vusers
|
297
|
+
|
298
|
+
stats
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
|
2
|
+
require 'webrick'
|
3
|
+
require 'stringio'
|
4
|
+
require 'net/dns/packet'
|
5
|
+
|
6
|
+
|
7
|
+
module Stella
|
8
|
+
module Adapter
|
9
|
+
# Make sure Stella's lib directory is before the system defined ones.
|
10
|
+
# We are using a modified version of pcaplet.rb.
|
11
|
+
require 'pcap'
|
12
|
+
require 'observer'
|
13
|
+
|
14
|
+
# Stella::Adapter::PcapWatcher
|
15
|
+
#
|
16
|
+
# Record HTTP or DNS events with Pcap (TCP sniffer). This requires ruby-pcap and the C pcap
|
17
|
+
# library as well as root acceess (TCP packet sniffing requires root privileges). If you're
|
18
|
+
# running Ruby 1.9, JRuby, or Windows this will not be available on your system.
|
19
|
+
# To sniff traffic, you must be on either the machine sending the requests or the machine
|
20
|
+
# receiving the requests.
|
21
|
+
class PcapWatcher
|
22
|
+
include Observable
|
23
|
+
|
24
|
+
# Building Ruby::Pcap with Ruby 1.9.1
|
25
|
+
# RSTRING()->len ia now RSTRING_LEN(), ...
|
26
|
+
# see: http://gnufied.org/2007/12/21/mysql-c-bindings-for-ruby-19/#comment-3133
|
27
|
+
# see: http://www.rubyinside.com/ruby-1-9-1-preview-released-why-its-a-big-deal-1280.html#comment-37223
|
28
|
+
# TRAP_BEG and TRAP_END are also fucked. But the fix is not clear.
|
29
|
+
# Basically Ruby::PCap is not ready for 1.9
|
30
|
+
# See: http://d.hatena.ne.jp/takuma104/20080210/1202638583
|
31
|
+
|
32
|
+
# Network interface device ID. eri0, en0, lo0, etc... /sbin/ifconfig -a will tell you.
|
33
|
+
attr_accessor :device
|
34
|
+
# Buffer size, in bytes, to read from each packet (default: 1500)
|
35
|
+
attr_accessor :snaplen
|
36
|
+
# Port of the machine sending requests (default: 80)
|
37
|
+
attr_accessor :sport
|
38
|
+
# Port of the machine receiving requests (default: 80)
|
39
|
+
attr_accessor :dport
|
40
|
+
# dns or http
|
41
|
+
attr_accessor :service
|
42
|
+
# udp or tcp
|
43
|
+
attr_accessor :protocol
|
44
|
+
# Maximum number of packets to sniff
|
45
|
+
attr_accessor :maxpacks
|
46
|
+
|
47
|
+
attr_reader :pcaplet
|
48
|
+
|
49
|
+
def initialize(options={})
|
50
|
+
# The proper service name for dns is "domain"
|
51
|
+
@service = options[:service] || 'http'
|
52
|
+
@service = 'domain' if options[:service] == 'dns'
|
53
|
+
|
54
|
+
if @service == 'domain'
|
55
|
+
@protocol = 'udp'
|
56
|
+
else
|
57
|
+
@protocol = options[:protocol] || 'tcp'
|
58
|
+
end
|
59
|
+
|
60
|
+
@dport = options[:port] || Socket::getservbyname(@service)
|
61
|
+
@sport = options[:port] || @dport
|
62
|
+
|
63
|
+
@device = options[:device] || guess_device
|
64
|
+
@snaplen = options[:snaplen] || 10000 # 10KB
|
65
|
+
@maxpacks = options[:maxpacks] || 100_000
|
66
|
+
|
67
|
+
Stella::LOGGER.info("Watching interface #{@device} for #{@service} activity on #{@protocol} port #{@dport}")
|
68
|
+
end
|
69
|
+
|
70
|
+
def guess_device
|
71
|
+
# NOTE: This should be passed in as a value, not called from the global
|
72
|
+
case Stella::SYSINFO.implementation
|
73
|
+
when :osx
|
74
|
+
"en1" # Pcap.lookupdev returns en0
|
75
|
+
else
|
76
|
+
Pcap.lookupdev
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def run
|
81
|
+
|
82
|
+
if (respond_to? "monitor_#{@service}")
|
83
|
+
self.send("monitor_#{@service}")
|
84
|
+
else
|
85
|
+
raise "Unknown service type (#{@service})"
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
# monitor_domain
|
93
|
+
#
|
94
|
+
# Use Ruby-Pcap to sniff packets off the network interface.
|
95
|
+
#
|
96
|
+
# DNS monitor based on: http://www.linuxjournal.com/article/9614
|
97
|
+
# Install http://rubyforge.org/projects/net-dns
|
98
|
+
#
|
99
|
+
# NOTE: Is there a better way to match up a request packet with a
|
100
|
+
# response packet?
|
101
|
+
# We keep connect a request with the response using the domain name.
|
102
|
+
# It's possible that two (or more) requests to be made for the same domain
|
103
|
+
# at the same time and the responses could be mixed up. This will affect
|
104
|
+
# the exact response time but probably not by much.
|
105
|
+
def monitor_domain
|
106
|
+
|
107
|
+
@pcaplet = Pcaplet.new(:device => @device, :count => @maxpacks)
|
108
|
+
|
109
|
+
req_filter = Pcap::Filter.new("#{@protocol} and dst port #{@dport}", @pcaplet.capture)
|
110
|
+
resp_filter = Pcap::Filter.new("#{@protocol} and src port #{@dport}", @pcaplet.capture)
|
111
|
+
@pcaplet.add_filter(req_filter | resp_filter)
|
112
|
+
@pcaplet.each_packet do |packet|
|
113
|
+
data = packet.udp_data
|
114
|
+
case packet
|
115
|
+
when req_filter
|
116
|
+
dobj = Stella::Data::DomainRequest.new(data)
|
117
|
+
dobj.time = packet.time
|
118
|
+
dobj.client_ip = packet.ip_src
|
119
|
+
dobj.server_ip = packet.ip_dst
|
120
|
+
|
121
|
+
changed
|
122
|
+
notify_observers(:domain_request, dobj)
|
123
|
+
|
124
|
+
when resp_filter
|
125
|
+
dobj = Stella::Data::DomainResponse.new(data)
|
126
|
+
dobj.time = packet.time
|
127
|
+
dobj.client_ip = packet.ip_dst
|
128
|
+
dobj.server_ip = packet.ip_src
|
129
|
+
|
130
|
+
changed
|
131
|
+
notify_observers(:domain_response, dobj)
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
rescue Interrupt
|
137
|
+
after
|
138
|
+
exit
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def monitor_http
|
143
|
+
|
144
|
+
@pcaplet = Pcaplet.new(:device => @device, :count => @maxpacks)
|
145
|
+
|
146
|
+
begin
|
147
|
+
req_filter = Pcap::Filter.new("#{@protocol} and dst port #{@dport}", @pcaplet.capture)
|
148
|
+
resp_filter = Pcap::Filter.new("#{@protocol} and src port #{@sport}", @pcaplet.capture)
|
149
|
+
@pcaplet.add_filter(req_filter | resp_filter)
|
150
|
+
@pcaplet.each_packet do |packet|
|
151
|
+
data = packet.tcp_data
|
152
|
+
next if data.nil?
|
153
|
+
|
154
|
+
|
155
|
+
# NOTE: With HTTP 1.1 keep alive connections, multiple requests can be passed
|
156
|
+
# through single connection. This makes it difficult to match responses with
|
157
|
+
# requests.
|
158
|
+
# NOTE: We don't parse the body of POST and PUT requests because the data can
|
159
|
+
# be (and likely is), split across numerous packets. We also only grab 1500
|
160
|
+
# bytes from each packet.
|
161
|
+
# NOTE: The hostname is taken from the Host header. Requests made without
|
162
|
+
# this header (including HTTP 1.0) will contain the local hostname instead.
|
163
|
+
# TODO: resolve the hostname from the IP address.
|
164
|
+
# There are some helpful methods for doing some of this stuff:
|
165
|
+
# http://www.goto.info.waseda.ac.jp/~fukusima/ruby/pcap/doc/TCPPacket.html
|
166
|
+
case packet
|
167
|
+
when req_filter
|
168
|
+
next unless data and data =~ /^(GET|POST|HEAD|DELETE|PUT)\s+(.+?)\s+(HTTP.+?)$/
|
169
|
+
dobj = Stella::Data::HTTPRequest.new(data.gsub(/\r?\n/, $/)) # Use the system's line terminators
|
170
|
+
dobj.time = packet.time
|
171
|
+
dobj.client_ip = packet.ip_src
|
172
|
+
dobj.server_ip = packet.ip_dst
|
173
|
+
|
174
|
+
changed
|
175
|
+
notify_observers(:http_request, dobj)
|
176
|
+
|
177
|
+
when resp_filter
|
178
|
+
# NOTE: Some responses do not contain a body in the first packet.
|
179
|
+
# TODO: investigate further. Try: http://www.ruby-doc.org/core/classes/Enumerable.html
|
180
|
+
next unless data and data =~ /^(HTTP.+)$/
|
181
|
+
dobj = Stella::Data::HTTPResponse.new(data.gsub(/\r?\n/, $/))
|
182
|
+
dobj.time = packet.time
|
183
|
+
dobj.client_ip = packet.ip_dst
|
184
|
+
dobj.server_ip = packet.ip_src
|
185
|
+
|
186
|
+
changed
|
187
|
+
notify_observers(:http_response, dobj)
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
rescue Interrupt
|
193
|
+
after
|
194
|
+
exit
|
195
|
+
rescue => ex
|
196
|
+
Stella::LOGGER.error(ex)
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
def after
|
203
|
+
STDOUT.flush
|
204
|
+
stat = @pcaplet.capture.stats
|
205
|
+
if stat
|
206
|
+
Stella::LOGGER.info("#{$/}#{stat.recv} packets received by filter");
|
207
|
+
Stella::LOGGER.info("#{stat.drop} packets dropped by kernel", ''); # with an extra line
|
208
|
+
end
|
209
|
+
STDOUT.flush
|
210
|
+
@pcaplet.capture.close
|
211
|
+
delete_observers
|
212
|
+
rescue
|
213
|
+
|
214
|
+
# Ignore errors
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|