arachni-rpc 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cbbd82c15cfd3db423e6c200ca21a1bfc34ed6d7
4
+ data.tar.gz: 828f98fbc48632790ff1bb42f723e4e068c924a3
5
+ SHA512:
6
+ metadata.gz: 75c2287cb0aa85c1cdd66c25068a96db4f180bac1723fde3b46ad451c34afe9a7f45901c6f99b988bc5b4758e6f3fdf763b4f5b8b0ff8395b2cd7abd1c0e6e72
7
+ data.tar.gz: 1a5fe36d458864aec8f45871e6d650841a47eb0d70ce22f78726eafddaf129ded96c8efd8dbf44dc6ff574b763a57112f1d2e56f895f78d884fb7f326d7daa81
data/CHANGELOG.md CHANGED
@@ -1,9 +1,15 @@
1
1
  # ChangeLog
2
2
 
3
+ ## Version 0.2.0
4
+
5
+ - Added `Arachni::Reactor`-based RPC client/server implementation.
6
+
3
7
  ## Version 0.1.3
4
- - Removed ```Arachni::RPC::Request#do_not_defer``` and ```Arachni::RPC::Request#defer?```.
5
- - All RPC exceptions now inherit from ```RuntimeError``` instead of ```Exception```.
8
+
9
+ - Removed `Arachni::RPC::Request#do_not_defer` and `Arachni::RPC::Request#defer?`.
10
+ - All RPC exceptions now inherit from `RuntimeError` instead of `Exception`.
6
11
 
7
12
  ## Version 0.1.2
13
+
8
14
  - Code cleanup.
9
- - Arachni::RPC::Request#do_not_defer! => Arachni::RPC::Request#do_not_defer
15
+ - `Arachni::RPC::Request#do_not_defer!` => `Arachni::RPC::Request#do_not_defer`.
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # License
2
2
 
3
- Copyright (C) 2011-2012, Tasos Laskos <tasos.laskos@gmail.com>
3
+ Copyright (C) 2011-2014, Tasos Laskos <tasos.laskos@arachni-scanner.com>
4
4
  All rights reserved.
5
5
 
6
6
  Redistribution and use in source and binary forms, with or without modification,
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # Arachni-RPC
2
+
2
3
  <table>
3
4
  <tr>
4
5
  <th>Version</th>
5
- <td>0.1.2</td>
6
+ <td>0.2.0</td>
6
7
  </tr>
7
8
  <tr>
8
9
  <th>Github page</th>
@@ -14,7 +15,7 @@
14
15
  </tr>
15
16
  <tr>
16
17
  <th>Author</th>
17
- <td><a href="mailto:tasos.laskos@gmail.com">Tasos "Zapotek" Laskos</a></td>
18
+ <td><a href="mailto:tasos.laskos@arachni-scanner.com">Tasos "Zapotek" Laskos</a></td>
18
19
  </tr>
19
20
  <tr>
20
21
  <th>Twitter</th>
@@ -22,7 +23,7 @@
22
23
  </tr>
23
24
  <tr>
24
25
  <th>Copyright</th>
25
- <td>2011-2012</td>
26
+ <td>2011-2014</td>
26
27
  </tr>
27
28
  <tr>
28
29
  <th>License</th>
@@ -32,56 +33,49 @@
32
33
 
33
34
  ## Synopsis
34
35
 
35
- Arachni-RPC is a simple and lightweight Remote Procedure Call protocol which provides the basis for <a href="http://arachni.segfault.gr">Arachni</a>'s Grid infrastructure.
36
-
37
- This repository holds *only* the protocol specification although there currently are 2 (more like 1.5) available implementations:
36
+ Arachni-RPC is a simple and lightweight Remote Procedure Call protocol and implementation
37
+ which provides the basis for <a href="http://arachni-scanner.com">Arachni</a>'s
38
+ distributed infrastructure.
38
39
 
39
- - <a href="http://github.com/Arachni/arachni-rpc-em">Arachni-RPC EM</a> -- Uses EventMachine for network related operations and provides both a client and a server, this is the one used by Arachni.
40
- - <a href="http://github.com/Arachni/arachni-rpc-pure">Arachni-RPC Pure</a> -- Provides a synchronous client using pure Ruby OpenSSL sockets and has no 3rd party dependencies.
40
+ (Based on the [Arachni::Reactor](https://github.com/Arachni/arachni-reactor) framework.)
41
41
 
42
42
  ## Features
43
43
 
44
- - Extremely lightweight
45
- - Very simple design
46
- - Token-based authentication
44
+ - Extremely lightweight.
45
+ - Very simple design.
46
+ - TLS encryption.
47
+ - Configurable serializer.
48
+ - Can intercept RPC responses and translate them into native objects for
49
+ when using serializers that only support basic types, like JSON or MessagePack.
50
+ - Token-based authentication.
51
+ - Pure-Ruby.
52
+ - Multi-platform, tested on:
53
+ - Linux
54
+ - OSX
55
+ - Windows
47
56
 
48
57
  ## Installation
49
58
 
50
- I can't think of a lot of uses for manually installing the protocol specification
51
- (it'll most likely be installed as a dependency for some other project) but in case you want to some instructions follow.
52
-
53
- ### Gem
54
-
55
- ```gem install arachni-rpc```
56
-
57
- ### Source
58
-
59
- If you want to clone the repository and work with the source code:
60
-
61
- git co git://github.com/arachni/arachni-rpc.git
62
- cd arachni-rpc
63
- rake install
64
-
59
+ gem install arachni-rpc
65
60
 
66
61
  ## Running the Specs
67
62
 
68
- In order to run the specs you must first install RSpec:
69
- gem install rspec
70
-
71
- Then:
72
-
63
+ bundle install
73
64
  rake spec
74
65
 
75
66
  ## Protocol specifications
76
67
 
77
- You can find that information in the [Wiki](https://github.com/Arachni/arachni-rpc/wiki).
68
+ You can find the RPC protocol specification at the
69
+ [Wiki](https://github.com/Arachni/arachni-rpc/wiki).
78
70
 
79
71
  ## Bug reports/Feature requests
80
- Please send your feedback using Github's issue system at
72
+
73
+ Please send your feedback using GitHub's issue system at
81
74
  [http://github.com/arachni/arachni-rpc/issues](http://github.com/arachni/arachni-rpc/issues).
82
75
 
83
76
 
84
77
  ## License
85
- Arachni-RPC is provided under the 3-clause BSD license.<br/>
78
+
79
+ Arachni-RPC is provided under the 3-clause BSD license.
86
80
  See the [LICENSE](file.LICENSE.html) file for more information.
87
81
 
data/Rakefile CHANGED
@@ -19,38 +19,35 @@ end
19
19
 
20
20
  task default: [ :build, :spec ]
21
21
 
22
- desc "Generate docs"
22
+ desc 'Generate docs'
23
23
  task :docs do
24
- outdir = "../arachni-rpc-pages"
25
- sh "mkdir #{outdir}" if !File.directory?( outdir )
24
+ outdir = "../arachni-rpc-docs"
25
+ sh "rm -rf #{outdir}"
26
+ sh "mkdir -p #{outdir}"
26
27
 
27
- sh "yardoc --verbose --title \
28
- \"Arachni-RPC\" \
29
- lib/* -o #{outdir} \
30
- - CHANGELOG.md LICENSE.md"
28
+ sh "yardoc -o #{outdir}"
31
29
 
32
-
33
- sh "rm -rf .yard*"
30
+ sh "rm -rf .yardoc"
34
31
  end
35
32
 
36
- desc "Cleaning..."
33
+ desc 'Clean up'
37
34
  task :clean do
38
- sh "rm *.gem || true"
35
+ sh 'rm *.gem || true'
39
36
  end
40
37
 
41
- desc "Build the arachni-rpc gem."
42
- task :build => [ :clean ] do
43
- sh "gem build arachni-rpc.gemspec"
38
+ desc 'Build the arachni-rpc gem.'
39
+ task build: [ :clean ] do
40
+ sh 'gem build arachni-rpc.gemspec'
44
41
  end
45
42
 
46
- desc "Build and install the arachni gem."
47
- task :install => [ :build ] do
43
+ desc 'Build and install the arachni gem.'
44
+ task install: [ :build ] do
48
45
  sh "gem install arachni-rpc-#{Arachni::RPC::VERSION}.gem"
49
46
  end
50
47
 
51
- desc "Push a new version to Rubygems"
52
- task :publish => [ :build ] do
48
+ desc 'Push a new version to Rubygems'
49
+ task publish: [ :build ] do
53
50
  sh "git tag -a v#{Arachni::RPC::VERSION} -m 'Version #{Arachni::RPC::VERSION}'"
54
51
  sh "gem push arachni-rpc-#{Arachni::RPC::VERSION}.gem"
55
52
  end
56
- task :release => [ :publish ]
53
+ task release: [ :publish ]
data/lib/arachni/rpc.rb CHANGED
@@ -6,12 +6,8 @@
6
6
 
7
7
  =end
8
8
 
9
- require 'set'
10
-
11
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'version' )
12
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'exceptions' )
13
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'message' )
14
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'request' )
15
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'response' )
16
- require File.join( File.expand_path( File.dirname( __FILE__ ) ), 'rpc', 'remote_object_mapper' )
9
+ require 'arachni/reactor'
17
10
 
11
+ %w(version exceptions message request response proxy protocol client server).each do |f|
12
+ require_relative "rpc/#{f}"
13
+ end
@@ -0,0 +1,236 @@
1
+ =begin
2
+
3
+ This file is part of the Arachni-RPC project and may be subject to
4
+ redistribution and commercial restrictions. Please see the Arachni-RPC EM
5
+ web site for more information on licensing and terms of use.
6
+
7
+ =end
8
+
9
+ module Arachni
10
+ module RPC
11
+
12
+ require_relative 'client/handler'
13
+
14
+ # Simple RPC client capable of:
15
+ #
16
+ # * TLS encryption.
17
+ # * Asynchronous and synchronous requests.
18
+ # * Handling remote asynchronous calls that defer their result.
19
+ #
20
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
21
+ class Client
22
+
23
+ # Default amount of connections to maintain in the re-use pool.
24
+ DEFAULT_CONNECTION_POOL_SIZE = 1
25
+
26
+ # @return [Hash]
27
+ # Options hash.
28
+ attr_reader :opts
29
+
30
+ # @return [Integer]
31
+ # Amount of connections in the pool.
32
+ attr_reader :connection_count
33
+
34
+ # @example Example options:
35
+ #
36
+ # {
37
+ # :host => 'localhost',
38
+ # :port => 7331,
39
+ #
40
+ # # optional authentication token, if it doesn't match the one
41
+ # # set on the server-side you'll be getting exceptions.
42
+ # :token => 'superdupersecret',
43
+ #
44
+ # :serializer => Marshal,
45
+ #
46
+ # :max_retries => 0,
47
+ #
48
+ # # In order to enable peer verification one must first provide
49
+ # # the following:
50
+ # # SSL CA certificate
51
+ # :ssl_ca => cwd + '/../spec/pems/cacert.pem',
52
+ # # SSL private key
53
+ # :ssl_pkey => cwd + '/../spec/pems/client/key.pem',
54
+ # # SSL certificate
55
+ # :ssl_cert => cwd + '/../spec/pems/client/cert.pem'
56
+ # }
57
+ #
58
+ # @param [Hash] opts
59
+ # @option opts [String] :host Hostname/IP address.
60
+ # @option opts [Integer] :port Port number.
61
+ # @option opts [String] :socket Path to UNIX domain socket.
62
+ # @option opts [Integer] :connection_pool_size (1)
63
+ # Amount of connections to keep open.
64
+ # @option opts [String] :token Optional authentication token.
65
+ # @option opts [.dump, .load] :serializer (YAML)
66
+ # Serializer to use for message transmission.
67
+ # @option opts [Integer] :max_retries
68
+ # How many times to retry failed requests.
69
+ # @option opts [String] :ssl_ca SSL CA certificate.
70
+ # @option opts [String] :ssl_pkey SSL private key.
71
+ # @option opts [String] :ssl_cert SSL certificate.
72
+ def initialize( opts )
73
+ @opts = opts.merge( role: :client )
74
+ @token = @opts[:token]
75
+
76
+ @host, @port = @opts[:host], @opts[:port].to_i
77
+ @socket = @opts[:socket]
78
+
79
+ if !@socket && !(@host || @port)
80
+ fail ArgumentError, 'Needs either a :socket or :host and :port options.'
81
+ end
82
+
83
+ @port = @port.to_i
84
+
85
+ if @host && @port <= 0
86
+ fail ArgumentError, "Invalid port: #{@port}"
87
+ end
88
+
89
+ @pool_size = @opts[:connection_pool_size] || DEFAULT_CONNECTION_POOL_SIZE
90
+
91
+ @reactor = Reactor.global
92
+
93
+ @connections = @reactor.create_queue
94
+ @connection_count = 0
95
+ end
96
+
97
+ # Connection factory, will re-use or create new connections as needed to
98
+ # accommodate the workload.
99
+ #
100
+ # @param [Block] block
101
+ # Block to be passed a {Handler connection}.
102
+ #
103
+ # @return [Boolean]
104
+ # `true` if a new connection had to be established, `false` if an existing
105
+ # one was re-used.
106
+ def connect( &block )
107
+ if @connections.empty? && @connection_count < @pool_size
108
+ opts = @socket ? @socket : [@host, @port]
109
+ block.call @reactor.connect( *[opts, Handler, @opts.merge( client: self )].flatten )
110
+ increment_connection_counter
111
+ return true
112
+ end
113
+
114
+ pop_block = proc do |conn|
115
+ # Some connections may have died while they were waiting in the
116
+ # queue, get rid of them and start all over in case the queue has
117
+ # been emptied.
118
+ if !conn.done?
119
+ connection_failed conn
120
+ connect( &block )
121
+ next
122
+ end
123
+
124
+ block.call conn
125
+ end
126
+
127
+ @connections.pop( &pop_block )
128
+
129
+ false
130
+ end
131
+
132
+ # Close all connections.
133
+ def close
134
+ @reactor.on_tick do |task|
135
+ @connections.pop(&:close_without_retry)
136
+ task.done if @connections.empty?
137
+ end
138
+ end
139
+
140
+ def increment_connection_counter
141
+ @connection_count += 1
142
+ end
143
+
144
+ # {Handler#done? Finished} {Handler}s push themselves here to be re-used.
145
+ #
146
+ # @param [Handler] connection
147
+ def push_connection( connection )
148
+ @connections << connection
149
+ end
150
+
151
+ # Handles failed connections.
152
+ #
153
+ # @param [Handler] connection
154
+ def connection_failed( connection )
155
+ @connection_count -= 1
156
+ connection.close_without_retry
157
+ end
158
+
159
+ # Calls a remote method and grabs the result.
160
+ #
161
+ # There are 2 ways to perform a call, async (non-blocking) and sync (blocking).
162
+ #
163
+ # @example To perform an async call you need to provide a block to handle the result.
164
+ #
165
+ # server.call( 'handler.method', arg1, arg2 ) do |res|
166
+ # do_stuff( res )
167
+ # end
168
+ #
169
+ # @example To perform a sync (blocking), call without a block.
170
+ #
171
+ # res = server.call( 'handler.method', arg1, arg2 )
172
+ #
173
+ # @param [String] msg
174
+ # RPC message in the form of `handler.method`.
175
+ # @param [Array] args
176
+ # Collection of arguments to be passed to the method.
177
+ # @param [Block] block
178
+ def call( msg, *args, &block )
179
+ req = Request.new(
180
+ message: msg,
181
+ args: args,
182
+ callback: block,
183
+ token: @token
184
+ )
185
+
186
+ block_given? ? call_async( req ) : call_sync( req )
187
+ end
188
+
189
+ private
190
+
191
+ def set_exception( req, e )
192
+ msg = @socket ? " for '#{@socket}'." : " for '#{@host}:#{@port}'."
193
+
194
+ exc = (e.is_a?( Reactor::Connection::Error::SSL ) ?
195
+ Exceptions::SSLPeerVerificationFailed : Exceptions::ConnectionError
196
+ ).new( e.to_s + msg )
197
+
198
+ exc.set_backtrace e.backtrace
199
+ req.callback.call exc
200
+ end
201
+
202
+ def call_async( req, &block )
203
+ req.callback = block if block_given?
204
+
205
+ begin
206
+ connect do |connection|
207
+ error = (connection.is_a?( Exception ) and connection) || connection.error
208
+ next set_exception( req, error ) if error
209
+
210
+ connection.send_request( req )
211
+ end
212
+ rescue => e
213
+ set_exception( req, e )
214
+ end
215
+ end
216
+
217
+ def call_sync( req )
218
+ # If we're in the Reactor thread use a Fiber and if we're not use a Thread.
219
+ if @reactor.in_same_thread?
220
+ fail 'Cannot perform synchronous calls when running in the ' +
221
+ "#{Arachni::Reactor} loop."
222
+ end
223
+
224
+ q = Queue.new
225
+ call_async( req ) { |obj| q << obj }
226
+ ret = q.pop
227
+
228
+ raise ret if ret.is_a?( Exception )
229
+
230
+ ret
231
+ end
232
+
233
+ end
234
+
235
+ end
236
+ end