arachni-reactor 0.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.md +29 -0
- data/README.md +79 -0
- data/Rakefile +53 -0
- data/lib/arachni/reactor.rb +679 -0
- data/lib/arachni/reactor/connection.rb +302 -0
- data/lib/arachni/reactor/connection/callbacks.rb +73 -0
- data/lib/arachni/reactor/connection/error.rb +114 -0
- data/lib/arachni/reactor/connection/peer_info.rb +92 -0
- data/lib/arachni/reactor/connection/tls.rb +107 -0
- data/lib/arachni/reactor/global.rb +26 -0
- data/lib/arachni/reactor/iterator.rb +251 -0
- data/lib/arachni/reactor/queue.rb +91 -0
- data/lib/arachni/reactor/tasks.rb +107 -0
- data/lib/arachni/reactor/tasks/base.rb +59 -0
- data/lib/arachni/reactor/tasks/delayed.rb +35 -0
- data/lib/arachni/reactor/tasks/one_off.rb +30 -0
- data/lib/arachni/reactor/tasks/periodic.rb +60 -0
- data/lib/arachni/reactor/tasks/persistent.rb +31 -0
- data/lib/arachni/reactor/version.rb +15 -0
- data/spec/arachni/reactor/connection/tls_spec.rb +332 -0
- data/spec/arachni/reactor/connection_spec.rb +58 -0
- data/spec/arachni/reactor/iterator_spec.rb +203 -0
- data/spec/arachni/reactor/queue_spec.rb +91 -0
- data/spec/arachni/reactor/tasks/base.rb +8 -0
- data/spec/arachni/reactor/tasks/delayed_spec.rb +54 -0
- data/spec/arachni/reactor/tasks/one_off_spec.rb +51 -0
- data/spec/arachni/reactor/tasks/periodic_spec.rb +40 -0
- data/spec/arachni/reactor/tasks/persistent_spec.rb +39 -0
- data/spec/arachni/reactor/tasks_spec.rb +136 -0
- data/spec/arachni/reactor_spec.rb +20 -0
- data/spec/arachni/reactor_tls_spec.rb +20 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/fixtures/handlers/echo_client.rb +34 -0
- data/spec/support/fixtures/handlers/echo_client_tls.rb +10 -0
- data/spec/support/fixtures/handlers/echo_server.rb +12 -0
- data/spec/support/fixtures/handlers/echo_server_tls.rb +8 -0
- data/spec/support/fixtures/pems/cacert.pem +37 -0
- data/spec/support/fixtures/pems/client/cert.pem +37 -0
- data/spec/support/fixtures/pems/client/foo-cert.pem +39 -0
- data/spec/support/fixtures/pems/client/foo-key.pem +51 -0
- data/spec/support/fixtures/pems/client/key.pem +51 -0
- data/spec/support/fixtures/pems/server/cert.pem +37 -0
- data/spec/support/fixtures/pems/server/key.pem +51 -0
- data/spec/support/helpers/paths.rb +23 -0
- data/spec/support/helpers/utilities.rb +117 -0
- data/spec/support/lib/server_option_parser.rb +29 -0
- data/spec/support/lib/servers.rb +133 -0
- data/spec/support/lib/servers/runner.rb +13 -0
- data/spec/support/servers/echo.rb +14 -0
- data/spec/support/servers/echo_tls.rb +22 -0
- data/spec/support/servers/echo_unix.rb +14 -0
- data/spec/support/servers/echo_unix_tls.rb +22 -0
- data/spec/support/shared/connection.rb +778 -0
- data/spec/support/shared/reactor.rb +785 -0
- data/spec/support/shared/task.rb +21 -0
- metadata +141 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Arachni::Reactor project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Arachni::Reactor
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Arachni
|
10
|
+
class Reactor
|
11
|
+
class Connection
|
12
|
+
|
13
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
14
|
+
module PeerInfo
|
15
|
+
|
16
|
+
# @param [Bool] resolve
|
17
|
+
# Resolve IP address to hostname.
|
18
|
+
# @return [Hash]
|
19
|
+
# Peer address information:
|
20
|
+
#
|
21
|
+
# * IP socket:
|
22
|
+
# * Without `resolve`:
|
23
|
+
#
|
24
|
+
# {
|
25
|
+
# protocol: 'AF_INET',
|
26
|
+
# port: 10314,
|
27
|
+
# hostname: '127.0.0.1',
|
28
|
+
# ip_address: '127.0.0.1'
|
29
|
+
# }
|
30
|
+
#
|
31
|
+
# * With `resolve`:
|
32
|
+
#
|
33
|
+
# {
|
34
|
+
# protocol: 'AF_INET',
|
35
|
+
# port: 10314,
|
36
|
+
# hostname: 'localhost',
|
37
|
+
# ip_address: '127.0.0.1'
|
38
|
+
# }
|
39
|
+
#
|
40
|
+
# * UNIX-domain socket:
|
41
|
+
#
|
42
|
+
# {
|
43
|
+
# protocol: 'AF_UNIX',
|
44
|
+
# path: '/tmp/my-socket'
|
45
|
+
# }
|
46
|
+
def peer_address_info( resolve = false )
|
47
|
+
if Arachni::Reactor.supports_unix_sockets? && to_io.is_a?( UNIXSocket )
|
48
|
+
{
|
49
|
+
protocol: to_io.peeraddr.first,
|
50
|
+
path: to_io.path
|
51
|
+
}
|
52
|
+
else
|
53
|
+
protocol, port, hostname, ip_address = to_io.peeraddr( resolve )
|
54
|
+
|
55
|
+
{
|
56
|
+
protocol: protocol,
|
57
|
+
port: port,
|
58
|
+
hostname: hostname,
|
59
|
+
ip_address: ip_address
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [String]
|
65
|
+
# Peer's IP address or socket path.
|
66
|
+
def peer_address
|
67
|
+
peer_ip_address || peer_address_info[:path]
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [String]
|
71
|
+
# Peer's IP address.
|
72
|
+
def peer_ip_address
|
73
|
+
peer_address_info[:ip_address]
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [String]
|
77
|
+
# Peer's hostname.
|
78
|
+
def peer_hostname
|
79
|
+
peer_address_info(true)[:hostname]
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [String]
|
83
|
+
# Peer's port.
|
84
|
+
def peer_port
|
85
|
+
peer_address_info[:port]
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Arachni::Reactor project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Arachni::Reactor
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
module Arachni
|
10
|
+
class Reactor
|
11
|
+
class Connection
|
12
|
+
|
13
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
14
|
+
module TLS
|
15
|
+
|
16
|
+
# Converts the {#socket} to an SSL one.
|
17
|
+
#
|
18
|
+
# @param [Hash] options
|
19
|
+
# @option [String] :certificate
|
20
|
+
# Path to a PEM certificate.
|
21
|
+
# @option [String] :private_key
|
22
|
+
# Path to a PEM private key.
|
23
|
+
# @option [String] :ca
|
24
|
+
# Path to a PEM CA.
|
25
|
+
def start_tls( options = {} )
|
26
|
+
if @socket.is_a? OpenSSL::SSL::SSLSocket
|
27
|
+
@ssl_context = @socket.context
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
@ssl_context = OpenSSL::SSL::SSLContext.new
|
32
|
+
@ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
33
|
+
|
34
|
+
if options[:certificate] && options[:private_key]
|
35
|
+
|
36
|
+
@ssl_context.cert =
|
37
|
+
OpenSSL::X509::Certificate.new( File.open( options[:certificate] ) )
|
38
|
+
@ssl_context.key =
|
39
|
+
OpenSSL::PKey::RSA.new( File.open( options[:private_key] ) )
|
40
|
+
|
41
|
+
@ssl_context.ca_file = options[:ca]
|
42
|
+
@ssl_context.verify_mode =
|
43
|
+
OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
44
|
+
|
45
|
+
elsif @role == :server
|
46
|
+
@ssl_context.key = OpenSSL::PKey::RSA.new( 2048 )
|
47
|
+
@ssl_context.cert = OpenSSL::X509::Certificate.new
|
48
|
+
@ssl_context.cert.subject = OpenSSL::X509::Name.new( [['CN', 'localhost']] )
|
49
|
+
@ssl_context.cert.issuer = @ssl_context.cert.subject
|
50
|
+
@ssl_context.cert.public_key = @ssl_context.key
|
51
|
+
@ssl_context.cert.not_before = Time.now
|
52
|
+
@ssl_context.cert.not_after = Time.now + 60 * 60 * 24
|
53
|
+
|
54
|
+
@ssl_context.cert.sign( @ssl_context.key, OpenSSL::Digest::SHA1.new )
|
55
|
+
end
|
56
|
+
|
57
|
+
if @role == :server
|
58
|
+
@socket = OpenSSL::SSL::SSLServer.new( @socket, @ssl_context )
|
59
|
+
else
|
60
|
+
@socket = OpenSSL::SSL::SSLSocket.new( @socket, @ssl_context )
|
61
|
+
@socket.sync_close = true
|
62
|
+
|
63
|
+
begin
|
64
|
+
@socket.connect_nonblock
|
65
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
@socket
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Accepts a new SSL client connection.
|
75
|
+
#
|
76
|
+
# @return [OpenSSL::SSL::SSLSocket, nil]
|
77
|
+
# New connection or `nil` if the socket isn't ready to accept new
|
78
|
+
# connections yet.
|
79
|
+
#
|
80
|
+
# @private
|
81
|
+
def socket_accept
|
82
|
+
socket = nil
|
83
|
+
begin
|
84
|
+
socket = to_io.accept_nonblock
|
85
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
socket = OpenSSL::SSL::SSLSocket.new(
|
90
|
+
socket,
|
91
|
+
@ssl_context
|
92
|
+
)
|
93
|
+
socket.sync_close = true
|
94
|
+
|
95
|
+
begin
|
96
|
+
socket.accept_nonblock
|
97
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
98
|
+
end
|
99
|
+
|
100
|
+
socket
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of the Arachni::Reactor project and may be subject to
|
4
|
+
redistribution and commercial restrictions. Please see the Arachni::Reactor
|
5
|
+
web site for more information on licensing and terms of use.
|
6
|
+
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'singleton'
|
10
|
+
|
11
|
+
module Arachni
|
12
|
+
class Reactor
|
13
|
+
|
14
|
+
# **Do not use directly!**
|
15
|
+
#
|
16
|
+
# Use the {Reactor} class methods to manage a globally accessible {Reactor}
|
17
|
+
# instance.
|
18
|
+
#
|
19
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
20
|
+
# @private
|
21
|
+
class Global < Reactor
|
22
|
+
include Singleton
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
|
2
|
+
=begin
|
3
|
+
|
4
|
+
This file is part of the Arachni::Reactor project and may be subject to
|
5
|
+
redistribution and commercial restrictions. Please see the Arachni::Reactor
|
6
|
+
web site for more information on licensing and terms of use.
|
7
|
+
|
8
|
+
=end
|
9
|
+
|
10
|
+
module Arachni
|
11
|
+
class Reactor
|
12
|
+
|
13
|
+
# @note Pretty much an `EventMachine::Iterator` rip-off.
|
14
|
+
#
|
15
|
+
# A simple iterator for concurrent asynchronous work.
|
16
|
+
#
|
17
|
+
# Unlike Ruby's built-in iterators, the end of the current iteration cycle is
|
18
|
+
# signaled manually, instead of happening automatically after the yielded block
|
19
|
+
# finishes executing.
|
20
|
+
#
|
21
|
+
# @example Direct initialization.
|
22
|
+
#
|
23
|
+
# Iterator.new( reactor, 0..10 ).each { |num, iterator| iterator.next }
|
24
|
+
#
|
25
|
+
# @example Reactor factory.
|
26
|
+
#
|
27
|
+
# reactor.create_iterator( 0..10 ).each { |num, iterator| iterator.next }
|
28
|
+
#
|
29
|
+
# @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
30
|
+
class Iterator
|
31
|
+
|
32
|
+
# @return [Reactor]
|
33
|
+
attr_reader :reactor
|
34
|
+
|
35
|
+
# @return [Integer]
|
36
|
+
attr_reader :concurrency
|
37
|
+
|
38
|
+
# @example Create a new parallel async iterator with specified concurrency.
|
39
|
+
#
|
40
|
+
# i = Iterator.new( reactor, 1..100, 10 )
|
41
|
+
#
|
42
|
+
# @param [Reactor] reactor
|
43
|
+
# @param [#to_a] list
|
44
|
+
# List to iterate.
|
45
|
+
# @param [Integer] concurrency
|
46
|
+
# Parallel workers to spawn.
|
47
|
+
def initialize( reactor, list, concurrency = 1 )
|
48
|
+
raise ArgumentError, 'argument must be an array' unless list.respond_to?(:to_a)
|
49
|
+
raise ArgumentError, 'concurrency must be bigger than zero' unless concurrency > 0
|
50
|
+
|
51
|
+
@reactor = reactor
|
52
|
+
@list = list.to_a.dup
|
53
|
+
@concurrency = concurrency
|
54
|
+
|
55
|
+
@started = false
|
56
|
+
@ended = false
|
57
|
+
end
|
58
|
+
|
59
|
+
# Change the concurrency of this iterator. Workers will automatically be
|
60
|
+
# spawned or destroyed to accommodate the new concurrency level.
|
61
|
+
#
|
62
|
+
# @param [Integer] val
|
63
|
+
# New concurrency.
|
64
|
+
def concurrency=( val )
|
65
|
+
old = @concurrency
|
66
|
+
@concurrency = val
|
67
|
+
|
68
|
+
spawn_workers if val > old && @started && !@ended
|
69
|
+
|
70
|
+
val
|
71
|
+
end
|
72
|
+
|
73
|
+
# @example Iterate over a set of items using the specified block or proc.
|
74
|
+
#
|
75
|
+
# Iterator.new( reactor, 1..100 ).each do |num, iterator|
|
76
|
+
# puts num
|
77
|
+
# iterator.next
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# @example An optional second proc is invoked after the iteration is complete.
|
81
|
+
#
|
82
|
+
# Iterator.new( reactor, 1..100 ).each(
|
83
|
+
# proc { |num, iterator| iterator.next },
|
84
|
+
# proc { puts 'all done' }
|
85
|
+
# )
|
86
|
+
def each( foreach = nil, after = nil, &block )
|
87
|
+
raise ArgumentError, 'Proc or Block required for iteration.' unless foreach ||= block
|
88
|
+
raise RuntimeError, 'Cannot iterate over an iterator more than once.' if @started or @ended
|
89
|
+
|
90
|
+
@started = true
|
91
|
+
@pending = 0
|
92
|
+
@workers = 0
|
93
|
+
|
94
|
+
all_done = proc do
|
95
|
+
after.call if after && @ended && @pending == 0
|
96
|
+
end
|
97
|
+
|
98
|
+
@process_next = proc do
|
99
|
+
if @ended || @workers > @concurrency
|
100
|
+
@workers -= 1
|
101
|
+
else
|
102
|
+
if @list.empty?
|
103
|
+
@ended = true
|
104
|
+
@workers -= 1
|
105
|
+
|
106
|
+
all_done.call
|
107
|
+
else
|
108
|
+
item = @list.shift
|
109
|
+
@pending += 1
|
110
|
+
|
111
|
+
is_done = false
|
112
|
+
on_done = proc do
|
113
|
+
raise RuntimeError, 'Already completed this iteration.' if is_done
|
114
|
+
is_done = true
|
115
|
+
|
116
|
+
@pending -= 1
|
117
|
+
|
118
|
+
if @ended
|
119
|
+
all_done.call
|
120
|
+
else
|
121
|
+
@reactor.next_tick(&@process_next)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class << on_done
|
126
|
+
alias :next :call
|
127
|
+
end
|
128
|
+
|
129
|
+
foreach.call(item, on_done)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
spawn_workers
|
135
|
+
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
# @example Collect the results of an asynchronous iteration into an array.
|
140
|
+
#
|
141
|
+
# Iterator.new( reactor, %w(one two three four), 2 ).map(
|
142
|
+
# proc do |string, iterator|
|
143
|
+
# iterator.return( string.size )
|
144
|
+
# end,
|
145
|
+
# proc do |results|
|
146
|
+
# p results
|
147
|
+
# end
|
148
|
+
# )
|
149
|
+
#
|
150
|
+
# @param [Proc] foreach
|
151
|
+
# `Proc` to handle each entry.
|
152
|
+
# @param [Proc] after
|
153
|
+
# `Proc` to handle the results.
|
154
|
+
def map( foreach, after )
|
155
|
+
index = 0
|
156
|
+
|
157
|
+
inject( [],
|
158
|
+
proc do |results, item, iter|
|
159
|
+
i = index
|
160
|
+
index += 1
|
161
|
+
|
162
|
+
is_done = false
|
163
|
+
on_done = proc do |res|
|
164
|
+
raise RuntimeError, 'Already returned a value for this iteration.' if is_done
|
165
|
+
is_done = true
|
166
|
+
|
167
|
+
results[i] = res
|
168
|
+
iter.return(results)
|
169
|
+
end
|
170
|
+
|
171
|
+
class << on_done
|
172
|
+
alias :return :call
|
173
|
+
def next
|
174
|
+
raise NoMethodError, 'Must call #return on a map iterator.'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
foreach.call( item, on_done )
|
179
|
+
end,
|
180
|
+
|
181
|
+
proc do |results|
|
182
|
+
after.call(results)
|
183
|
+
end
|
184
|
+
)
|
185
|
+
end
|
186
|
+
|
187
|
+
# @example Inject the results of an asynchronous iteration onto a given object.
|
188
|
+
#
|
189
|
+
# Iterator.new( reactor, %w(one two three four), 2 ).inject( {},
|
190
|
+
# proc do |hash, string, iterator|
|
191
|
+
# hash.merge!( string => string.size )
|
192
|
+
# iterator.return( hash )
|
193
|
+
# end,
|
194
|
+
# proc do |results|
|
195
|
+
# p results
|
196
|
+
# end
|
197
|
+
# )
|
198
|
+
#
|
199
|
+
# @param [Object] object
|
200
|
+
# @param [Proc] foreach
|
201
|
+
# `Proc` to handle each entry.
|
202
|
+
# @param [Proc] after
|
203
|
+
# `Proc` to handle the results.
|
204
|
+
def inject( object, foreach, after )
|
205
|
+
each(
|
206
|
+
proc do |item, iter|
|
207
|
+
is_done = false
|
208
|
+
on_done = proc do |res|
|
209
|
+
raise RuntimeError, 'Already returned a value for this iteration.' if is_done
|
210
|
+
is_done = true
|
211
|
+
|
212
|
+
object = res
|
213
|
+
iter.next
|
214
|
+
end
|
215
|
+
|
216
|
+
class << on_done
|
217
|
+
alias :return :call
|
218
|
+
def next
|
219
|
+
raise NoMethodError, 'Must call #return on an inject iterator.'
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
foreach.call( object, item, on_done )
|
224
|
+
end,
|
225
|
+
|
226
|
+
proc do
|
227
|
+
after.call(object)
|
228
|
+
end
|
229
|
+
)
|
230
|
+
end
|
231
|
+
|
232
|
+
private
|
233
|
+
|
234
|
+
# Spawn workers to consume items from the iterator's enumerator based on the
|
235
|
+
# current concurrency level.
|
236
|
+
def spawn_workers
|
237
|
+
@reactor.next_tick( &proc { |task|
|
238
|
+
next if @workers >= @concurrency || @ended
|
239
|
+
|
240
|
+
@workers += 1
|
241
|
+
@process_next.call
|
242
|
+
@reactor.next_tick(&task.to_proc)
|
243
|
+
})
|
244
|
+
|
245
|
+
nil
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
end
|