arachni-reactor 0.1.0.beta5 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +23 -0
- data/LICENSE.md +1 -1
- data/README.md +4 -4
- data/lib/arachni/reactor/connection/error.rb +3 -3
- data/lib/arachni/reactor/connection/tls.rb +77 -21
- data/lib/arachni/reactor/connection.rb +101 -62
- data/lib/arachni/reactor/version.rb +1 -1
- data/lib/arachni/reactor.rb +76 -72
- data/spec/arachni/reactor/connection/tls_spec.rb +35 -19
- data/spec/arachni/reactor/connection_spec.rb +19 -3
- data/spec/arachni/reactor/iterator_spec.rb +1 -1
- data/spec/arachni/reactor/tasks/delayed_spec.rb +3 -1
- data/spec/arachni/reactor/tasks/periodic_spec.rb +3 -1
- data/spec/arachni/reactor/tasks_spec.rb +4 -4
- data/spec/support/helpers/utilities.rb +15 -1
- data/spec/support/lib/servers.rb +1 -1
- data/spec/support/servers/echo.rb +3 -3
- data/spec/support/servers/echo_tls.rb +3 -3
- data/spec/support/servers/echo_unix.rb +3 -3
- data/spec/support/servers/echo_unix_tls.rb +3 -3
- data/spec/support/shared/connection.rb +67 -149
- data/spec/support/shared/reactor.rb +4 -1
- metadata +33 -34
data/lib/arachni/reactor.rb
CHANGED
@@ -199,10 +199,9 @@ class Reactor
|
|
199
199
|
begin
|
200
200
|
Connection::Error.translate do
|
201
201
|
socket = options[:unix_socket] ?
|
202
|
-
connect_unix( options[:unix_socket] ) :
|
203
|
-
connect_tcp( options[:host], options[:port] )
|
202
|
+
connect_unix( options[:unix_socket] ) : connect_tcp
|
204
203
|
|
205
|
-
connection.configure socket, :client
|
204
|
+
connection.configure options.merge( socket: socket, role: :client )
|
206
205
|
attach connection
|
207
206
|
end
|
208
207
|
rescue Connection::Error => e
|
@@ -266,7 +265,7 @@ class Reactor
|
|
266
265
|
listen_unix( options[:unix_socket] ) :
|
267
266
|
listen_tcp( options[:host], options[:port] )
|
268
267
|
|
269
|
-
server.configure socket, :server, server_handler
|
268
|
+
server.configure options.merge( socket: socket, role: :server, server_handler: server_handler )
|
270
269
|
attach server
|
271
270
|
end
|
272
271
|
rescue Connection::Error => e
|
@@ -415,7 +414,7 @@ class Reactor
|
|
415
414
|
def schedule( &block )
|
416
415
|
fail_if_not_running
|
417
416
|
|
418
|
-
if
|
417
|
+
if in_same_thread?
|
419
418
|
block.call
|
420
419
|
else
|
421
420
|
next_tick(&block)
|
@@ -500,7 +499,7 @@ class Reactor
|
|
500
499
|
|
501
500
|
schedule do
|
502
501
|
connection.reactor = self
|
503
|
-
@connections[connection.
|
502
|
+
@connections[connection.to_io] = connection
|
504
503
|
connection.on_attach
|
505
504
|
end
|
506
505
|
end
|
@@ -517,7 +516,7 @@ class Reactor
|
|
517
516
|
|
518
517
|
schedule do
|
519
518
|
connection.on_detach
|
520
|
-
@connections.delete connection.
|
519
|
+
@connections.delete connection.to_io
|
521
520
|
connection.reactor = nil
|
522
521
|
end
|
523
522
|
end
|
@@ -525,7 +524,7 @@ class Reactor
|
|
525
524
|
# @return [Bool]
|
526
525
|
# `true` if the connection is attached, `false` otherwise.
|
527
526
|
def attached?( connection )
|
528
|
-
@connections.include? connection.
|
527
|
+
@connections.include? connection.to_io
|
529
528
|
end
|
530
529
|
|
531
530
|
private
|
@@ -552,25 +551,8 @@ class Reactor
|
|
552
551
|
'The host OS does not support UNIX-domain sockets.'
|
553
552
|
end
|
554
553
|
|
555
|
-
def process_connections
|
556
|
-
if @connections.empty?
|
557
|
-
sleep @max_tick_interval
|
558
|
-
return
|
559
|
-
end
|
560
|
-
|
561
|
-
# Get connections with available events - :read, :write, :error.
|
562
|
-
selected = select_connections
|
563
|
-
|
564
|
-
# Close connections that have errors.
|
565
|
-
[selected.delete(:error)].flatten.compact.each(&:close)
|
566
|
-
|
567
|
-
# Call the corresponding event on the connections.
|
568
|
-
selected.each { |event, connections| connections.each(&"_#{event}".to_sym) }
|
569
|
-
end
|
570
|
-
|
571
554
|
def determine_connection_options( *args )
|
572
555
|
options = {}
|
573
|
-
host = port = unix_socket = nil
|
574
556
|
|
575
557
|
if args[1].is_a? Integer
|
576
558
|
options[:host], options[:port], options[:handler], *handler_options = *args
|
@@ -599,26 +581,13 @@ class Reactor
|
|
599
581
|
|
600
582
|
# @return [Socket]
|
601
583
|
# Connected socket.
|
602
|
-
def connect_tcp
|
584
|
+
def connect_tcp
|
603
585
|
socket = Socket.new(
|
604
586
|
Socket::Constants::AF_INET,
|
605
587
|
Socket::Constants::SOCK_STREAM,
|
606
588
|
Socket::Constants::IPPROTO_IP
|
607
589
|
)
|
608
590
|
socket.do_not_reverse_lookup = true
|
609
|
-
|
610
|
-
# JRuby throws java.nio.channels.NotYetConnectedException even after
|
611
|
-
# it returns the socket from Kernel.select, so wait for it to connect
|
612
|
-
# before moving on.
|
613
|
-
if self.class.jruby?
|
614
|
-
socket.connect( Socket.sockaddr_in( port, host ) )
|
615
|
-
else
|
616
|
-
begin
|
617
|
-
socket.connect_nonblock( Socket.sockaddr_in( port, host ) )
|
618
|
-
rescue IO::WaitReadable, IO::WaitWritable, Errno::EINPROGRESS
|
619
|
-
end
|
620
|
-
end
|
621
|
-
|
622
591
|
socket
|
623
592
|
end
|
624
593
|
|
@@ -641,6 +610,22 @@ class Reactor
|
|
641
610
|
@connections.values.each(&:close)
|
642
611
|
end
|
643
612
|
|
613
|
+
def process_connections
|
614
|
+
if @connections.empty?
|
615
|
+
sleep @max_tick_interval
|
616
|
+
return
|
617
|
+
end
|
618
|
+
|
619
|
+
# Get connections with available events - :read, :write, :error.
|
620
|
+
selected = self.select_connections
|
621
|
+
|
622
|
+
# Close connections that have errors.
|
623
|
+
selected.delete(:error)&.each(&:close)
|
624
|
+
|
625
|
+
# Call the corresponding event on the connections.
|
626
|
+
selected.each { |event, connections| connections.each(&"_#{event}".to_sym) }
|
627
|
+
end
|
628
|
+
|
644
629
|
# @return [Hash]
|
645
630
|
#
|
646
631
|
# Connections grouped by their available events:
|
@@ -650,52 +635,71 @@ class Reactor
|
|
650
635
|
# {Connection#has_outgoing_data? outgoing buffer).
|
651
636
|
# * `:error`
|
652
637
|
def select_connections
|
653
|
-
|
638
|
+
r = []
|
639
|
+
w = []
|
640
|
+
e = []
|
641
|
+
|
642
|
+
@connections.values.each do |connection|
|
643
|
+
|
644
|
+
# Required for OSX as it connects immediately and then #select returns
|
645
|
+
# nothing as there's no activity, given that, OpenSSL doesn't get a chance
|
646
|
+
# to do its handshake so explicitly connect pending sockets, bypassing #select.
|
647
|
+
connection._connect if !connection.connected?
|
648
|
+
|
649
|
+
socket = connection.socket
|
650
|
+
|
651
|
+
e << socket
|
652
|
+
|
653
|
+
if connection.listener? || connection.connected?
|
654
|
+
r << socket
|
655
|
+
end
|
656
|
+
|
657
|
+
if connection.connected? && connection.has_outgoing_data?
|
658
|
+
w << socket
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
selected_sockets =
|
654
663
|
begin
|
655
664
|
Connection::Error.translate do
|
656
|
-
select(
|
657
|
-
read_sockets,
|
658
|
-
write_sockets,
|
659
|
-
read_sockets, # Read sockets are actually all sockets.
|
660
|
-
@select_timeout
|
661
|
-
)
|
665
|
+
select( r, w, e, @select_timeout )
|
662
666
|
end
|
663
|
-
rescue Connection::Error
|
667
|
+
rescue Connection::Error => e
|
668
|
+
nil
|
664
669
|
end
|
665
670
|
|
666
|
-
|
671
|
+
selected_sockets ||= [[],[],[]]
|
672
|
+
|
673
|
+
# SSL sockets maintain their own buffer whose state can't be checked by
|
674
|
+
# Kernel.select, leading to cases where the SSL buffer isn't empty,
|
675
|
+
# even though Kernel.select says that there's nothing to read.
|
676
|
+
#
|
677
|
+
# So force a read for SSL sockets to cover all our bases.
|
678
|
+
#
|
679
|
+
# This is apparent especially on JRuby.
|
680
|
+
if r.size != selected_sockets[0].size
|
681
|
+
(r - selected_sockets[0]).each do |socket|
|
682
|
+
next if !socket.is_a?( OpenSSL::SSL::SSLSocket )
|
683
|
+
selected_sockets[0] << socket
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
if selected_sockets[0].empty? && selected_sockets[1].empty? &&
|
688
|
+
selected_sockets[2].empty?
|
689
|
+
return {}
|
690
|
+
end
|
667
691
|
|
668
692
|
{
|
669
693
|
# Since these will be processed in order, it's better have the write
|
670
694
|
# ones first to flush the buffers ASAP.
|
671
|
-
write:
|
672
|
-
read:
|
673
|
-
error:
|
695
|
+
write: connections_from_sockets( selected_sockets[1] ),
|
696
|
+
read: connections_from_sockets( selected_sockets[0] ),
|
697
|
+
error: connections_from_sockets( selected_sockets[2] )
|
674
698
|
}
|
675
699
|
end
|
676
700
|
|
677
|
-
# @return [Array<Socket>]
|
678
|
-
# Sockets of all connections, we want to be ready to read at any time.
|
679
|
-
def read_sockets
|
680
|
-
@connections.keys
|
681
|
-
end
|
682
|
-
|
683
|
-
# @return [Array<Socket>]
|
684
|
-
# Sockets of connections with
|
685
|
-
# {Connection#has_outgoing_data? outgoing data}.
|
686
|
-
def write_sockets
|
687
|
-
@connections.map do |socket, connection|
|
688
|
-
next if !connection.has_outgoing_data?
|
689
|
-
socket
|
690
|
-
end.compact
|
691
|
-
end
|
692
|
-
|
693
701
|
def connections_from_sockets( sockets )
|
694
|
-
sockets.map { |s|
|
695
|
-
end
|
696
|
-
|
697
|
-
def connection_from_socket( socket )
|
698
|
-
@connections[socket]
|
702
|
+
sockets.map { |s| @connections[s.to_io] }
|
699
703
|
end
|
700
704
|
|
701
705
|
end
|
@@ -43,16 +43,32 @@ describe Arachni::Reactor::Connection::TLS do
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
+
before :each do
|
47
|
+
@accept_q = Queue.new
|
48
|
+
@accepted = nil
|
49
|
+
end
|
50
|
+
|
46
51
|
let(:unix_socket) { unix_connect( @unix_socket ) }
|
47
52
|
let(:unix_server_socket) { unix_server( port_to_socket( Servers.available_port ) ) }
|
48
53
|
|
49
|
-
let(:echo_client) {
|
54
|
+
let(:echo_client) { tcp_socket }
|
50
55
|
let(:echo_client_handler) { EchoClientTLS.new }
|
51
56
|
|
52
57
|
let(:peer_client_socket) { tcp_ssl_connect( host, port ) }
|
53
|
-
let(:peer_server_socket)
|
58
|
+
let(:peer_server_socket) do
|
59
|
+
s = tcp_ssl_server( host, port )
|
60
|
+
Thread.new do
|
61
|
+
begin
|
62
|
+
@accept_q << s.accept
|
63
|
+
rescue => e
|
64
|
+
ap e
|
65
|
+
end
|
66
|
+
end
|
67
|
+
s
|
68
|
+
end
|
69
|
+
let(:accepted) { @accepted ||= @accept_q.pop }
|
54
70
|
|
55
|
-
let(:client_socket) {
|
71
|
+
let(:client_socket) { tcp_socket }
|
56
72
|
let(:server_socket) { tcp_server( host, port ) }
|
57
73
|
|
58
74
|
let(:connection) { TLSHandler.new }
|
@@ -113,10 +129,12 @@ describe Arachni::Reactor::Connection::TLS do
|
|
113
129
|
reactor.listen( host, port, TLSHandler, options )
|
114
130
|
|
115
131
|
client.write data
|
132
|
+
sleep 1
|
133
|
+
|
116
134
|
reactor.stop
|
117
135
|
reactor.wait rescue Arachni::Reactor::Error::NotRunning
|
118
136
|
|
119
|
-
received_data.
|
137
|
+
expect(received_data).to eq data
|
120
138
|
end
|
121
139
|
end
|
122
140
|
|
@@ -244,7 +262,15 @@ describe Arachni::Reactor::Connection::TLS do
|
|
244
262
|
|
245
263
|
context 'when connecting to a server' do
|
246
264
|
let(:server) do
|
247
|
-
tcp_ssl_server( host, port, server_ssl_options )
|
265
|
+
s = tcp_ssl_server( host, port, server_ssl_options )
|
266
|
+
Thread.new do
|
267
|
+
begin
|
268
|
+
@accept_q << s.accept
|
269
|
+
rescue => e
|
270
|
+
# ap e
|
271
|
+
end
|
272
|
+
end
|
273
|
+
s
|
248
274
|
end
|
249
275
|
|
250
276
|
before :each do
|
@@ -258,9 +284,7 @@ describe Arachni::Reactor::Connection::TLS do
|
|
258
284
|
it 'connects successfully' do
|
259
285
|
received = nil
|
260
286
|
Thread.new do
|
261
|
-
|
262
|
-
received = s.gets
|
263
|
-
|
287
|
+
received = accepted.gets
|
264
288
|
reactor.stop
|
265
289
|
end
|
266
290
|
|
@@ -279,10 +303,6 @@ describe Arachni::Reactor::Connection::TLS do
|
|
279
303
|
|
280
304
|
context 'and no options have been provided' do
|
281
305
|
it "passes #{Arachni::Reactor::Connection::Error} to #on_error" do
|
282
|
-
Thread.new do
|
283
|
-
server.accept
|
284
|
-
end
|
285
|
-
|
286
306
|
connection = nil
|
287
307
|
reactor.run do
|
288
308
|
connection = reactor.connect( host, port, TLSHandler )
|
@@ -297,8 +317,7 @@ describe Arachni::Reactor::Connection::TLS do
|
|
297
317
|
it 'connects successfully' do
|
298
318
|
received = nil
|
299
319
|
t = Thread.new do
|
300
|
-
|
301
|
-
received = s.gets
|
320
|
+
received = accepted.gets
|
302
321
|
reactor.stop
|
303
322
|
end
|
304
323
|
|
@@ -307,16 +326,13 @@ describe Arachni::Reactor::Connection::TLS do
|
|
307
326
|
connection.write data
|
308
327
|
end
|
309
328
|
|
310
|
-
|
329
|
+
sleep 1
|
330
|
+
expect(received).to eq data
|
311
331
|
end
|
312
332
|
end
|
313
333
|
|
314
334
|
context 'and are invalid' do
|
315
335
|
it "passes #{Arachni::Reactor::Connection::Error} to #on_error" do
|
316
|
-
Thread.new do
|
317
|
-
server.accept
|
318
|
-
end
|
319
|
-
|
320
336
|
connection = nil
|
321
337
|
reactor.run do
|
322
338
|
connection = reactor.connect( host, port, TLSHandler, client_invalid_ssl_options )
|
@@ -39,16 +39,32 @@ describe Arachni::Reactor::Connection do
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
+
before :each do
|
43
|
+
@accept_q = Queue.new
|
44
|
+
@accepted = nil
|
45
|
+
end
|
46
|
+
|
42
47
|
let(:unix_socket) { unix_connect( @unix_socket ) }
|
43
48
|
let(:unix_server_socket) { unix_server( port_to_socket( Servers.available_port ) ) }
|
44
49
|
|
45
|
-
let(:echo_client) {
|
50
|
+
let(:echo_client) { tcp_socket }
|
46
51
|
let(:echo_client_handler) { EchoClient.new }
|
47
52
|
|
48
53
|
let(:peer_client_socket) { tcp_connect( host, port ) }
|
49
|
-
let(:peer_server_socket)
|
54
|
+
let(:peer_server_socket) do
|
55
|
+
s = tcp_server( host, port )
|
56
|
+
Thread.new do
|
57
|
+
begin
|
58
|
+
@accept_q << s.accept
|
59
|
+
rescue => e
|
60
|
+
ap e
|
61
|
+
end
|
62
|
+
end
|
63
|
+
s
|
64
|
+
end
|
65
|
+
let(:accepted) { @accepted ||= @accept_q.pop }
|
50
66
|
|
51
|
-
let(:client_socket) {
|
67
|
+
let(:client_socket) { tcp_socket }
|
52
68
|
let(:server_socket) { tcp_server( host, port ) }
|
53
69
|
|
54
70
|
let(:connection) { Handler.new }
|
@@ -35,7 +35,9 @@ describe Arachni::Reactor::Tasks::Delayed do
|
|
35
35
|
time = Time.now
|
36
36
|
task.call while called < 1
|
37
37
|
|
38
|
-
(Time.now - time).round(2)
|
38
|
+
elapsed = (Time.now - time).round(2)
|
39
|
+
elapsed.should >= 0.25
|
40
|
+
elapsed.should < 0.30
|
39
41
|
end
|
40
42
|
|
41
43
|
it 'calls #done' do
|
@@ -25,14 +25,14 @@ describe Arachni::Reactor::Tasks do
|
|
25
25
|
context 'when it includes the given task' do
|
26
26
|
it 'returns true' do
|
27
27
|
subject << task
|
28
|
-
subject.
|
28
|
+
expect(subject.include?( task )).to be_truthy
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
context 'when it does not includes the given task' do
|
33
33
|
it 'returns false' do
|
34
34
|
subject << task
|
35
|
-
subject.
|
35
|
+
expect(subject.include?( another_task )).to be_falsey
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -125,8 +125,8 @@ describe Arachni::Reactor::Tasks do
|
|
125
125
|
|
126
126
|
subject.call
|
127
127
|
|
128
|
-
called_one.should
|
129
|
-
called_two.should
|
128
|
+
called_one.should be_truthy
|
129
|
+
called_two.should be_truthy
|
130
130
|
end
|
131
131
|
|
132
132
|
it 'returns self' do
|
@@ -17,6 +17,16 @@ def tcp_connect( host, port )
|
|
17
17
|
TCPSocket.new( host, port )
|
18
18
|
end
|
19
19
|
|
20
|
+
def tcp_socket
|
21
|
+
socket = Socket.new(
|
22
|
+
Socket::Constants::AF_INET,
|
23
|
+
Socket::Constants::SOCK_STREAM,
|
24
|
+
Socket::Constants::IPPROTO_IP
|
25
|
+
)
|
26
|
+
socket.do_not_reverse_lookup = true
|
27
|
+
socket
|
28
|
+
end
|
29
|
+
|
20
30
|
def tcp_write( host, port, data )
|
21
31
|
s = tcp_connect( host, port )
|
22
32
|
s.write data
|
@@ -45,6 +55,10 @@ def tcp_server( host, port )
|
|
45
55
|
TCPServer.new( host, port )
|
46
56
|
end
|
47
57
|
|
58
|
+
def tcp_ssl_socket( host, port, options = {} )
|
59
|
+
convert_client_to_ssl( tcp_socket, options )
|
60
|
+
end
|
61
|
+
|
48
62
|
def tcp_ssl_connect( host, port, options = {} )
|
49
63
|
convert_client_to_ssl( tcp_connect( host, port ), options )
|
50
64
|
end
|
@@ -100,7 +114,7 @@ def convert_server_to_ssl( server, options = {} )
|
|
100
114
|
context.cert.version = 2
|
101
115
|
context.cert.serial = 1
|
102
116
|
|
103
|
-
context.cert.sign( context.key, OpenSSL::Digest::
|
117
|
+
context.cert.sign( context.key, OpenSSL::Digest::SHA256.new )
|
104
118
|
end
|
105
119
|
|
106
120
|
OpenSSL::SSL::SSLServer.new( server, context )
|
data/spec/support/lib/servers.rb
CHANGED
@@ -26,7 +26,7 @@ class Servers
|
|
26
26
|
return [host_for(name), port_for(name)] if server_info[:pid] && up?( name )
|
27
27
|
|
28
28
|
server_info[:pid] = Process.spawn(
|
29
|
-
|
29
|
+
RbConfig.ruby, RUNNER, server_info[:path], '-p', server_info[:port].to_s,
|
30
30
|
'-o', host_for( name )
|
31
31
|
)
|
32
32
|
|
@@ -4,10 +4,10 @@ loop do
|
|
4
4
|
Thread.new server.accept do |socket|
|
5
5
|
begin
|
6
6
|
loop do
|
7
|
-
next if
|
8
|
-
socket.write(
|
7
|
+
next if (data = socket.gets).to_s.empty?
|
8
|
+
socket.write( data )
|
9
9
|
end
|
10
|
-
rescue EOFError, Errno::EPIPE
|
10
|
+
rescue EOFError, Errno::EPIPE, Errno::ECONNRESET
|
11
11
|
socket.close
|
12
12
|
end
|
13
13
|
end
|
@@ -12,10 +12,10 @@ loop do
|
|
12
12
|
Thread.new do
|
13
13
|
begin
|
14
14
|
loop do
|
15
|
-
next if (
|
16
|
-
socket.write(
|
15
|
+
next if (data = socket.gets).to_s.empty?
|
16
|
+
socket.write( data )
|
17
17
|
end
|
18
|
-
rescue EOFError, Errno::EPIPE
|
18
|
+
rescue EOFError, Errno::EPIPE, Errno::ECONNRESET
|
19
19
|
socket.close
|
20
20
|
end
|
21
21
|
end
|
@@ -4,10 +4,10 @@ loop do
|
|
4
4
|
Thread.new server.accept do |socket|
|
5
5
|
begin
|
6
6
|
loop do
|
7
|
-
next if
|
8
|
-
socket.write(
|
7
|
+
next if (data = socket.gets).to_s.empty?
|
8
|
+
socket.write( data )
|
9
9
|
end
|
10
|
-
rescue EOFError, Errno::EPIPE
|
10
|
+
rescue EOFError, Errno::EPIPE, Errno::ECONNRESET
|
11
11
|
socket.close
|
12
12
|
end
|
13
13
|
end
|
@@ -12,10 +12,10 @@ loop do
|
|
12
12
|
Thread.new do
|
13
13
|
begin
|
14
14
|
loop do
|
15
|
-
next if (
|
16
|
-
socket.write(
|
15
|
+
next if (data = socket.gets).to_s.empty?
|
16
|
+
socket.write( data )
|
17
17
|
end
|
18
|
-
rescue EOFError, Errno::EPIPE
|
18
|
+
rescue EOFError, Errno::EPIPE, Errno::ECONNRESET
|
19
19
|
socket.close
|
20
20
|
end
|
21
21
|
end
|