rtunnel 0.3.8 → 0.4.0
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/CHANGELOG +13 -0
- data/LICENSE +21 -0
- data/Manifest +48 -0
- data/README.markdown +84 -0
- data/Rakefile +45 -0
- data/bin/rtunnel_client +2 -1
- data/bin/rtunnel_server +2 -1
- data/lib/rtunnel/client.rb +308 -0
- data/lib/rtunnel/command_processor.rb +62 -0
- data/lib/rtunnel/command_protocol.rb +50 -0
- data/lib/rtunnel/commands.rb +233 -0
- data/lib/rtunnel/connection_id.rb +24 -0
- data/lib/rtunnel/core.rb +58 -0
- data/lib/rtunnel/crypto.rb +106 -0
- data/lib/rtunnel/frame_protocol.rb +34 -0
- data/lib/rtunnel/io_extensions.rb +54 -0
- data/lib/rtunnel/leak.rb +35 -0
- data/lib/rtunnel/rtunnel_client_cmd.rb +41 -0
- data/lib/rtunnel/rtunnel_server_cmd.rb +32 -0
- data/lib/rtunnel/server.rb +351 -0
- data/lib/rtunnel/socket_factory.rb +119 -0
- data/lib/rtunnel.rb +20 -0
- data/rtunnel.gemspec +51 -0
- data/spec/client_spec.rb +47 -0
- data/spec/cmds_spec.rb +127 -0
- data/spec/integration_spec.rb +105 -0
- data/spec/server_spec.rb +21 -0
- data/spec/spec_helper.rb +3 -0
- data/test/command_stubs.rb +77 -0
- data/test/protocol_mocks.rb +43 -0
- data/test/scenario_connection.rb +109 -0
- data/test/test_client.rb +48 -0
- data/test/test_command_protocol.rb +82 -0
- data/test/test_commands.rb +49 -0
- data/test/test_connection_id.rb +30 -0
- data/test/test_crypto.rb +127 -0
- data/test/test_frame_protocol.rb +109 -0
- data/test/test_io_extensions.rb +70 -0
- data/test/test_server.rb +70 -0
- data/test/test_socket_factory.rb +42 -0
- data/test/test_tunnel.rb +186 -0
- data/test_data/authorized_keys2 +4 -0
- data/test_data/known_hosts +4 -0
- data/test_data/random_rsa_key +27 -0
- data/test_data/ssh_host_dsa_key +12 -0
- data/test_data/ssh_host_rsa_key +27 -0
- data/tests/_ab_test.rb +16 -0
- data/tests/_stress_test.rb +96 -0
- data/tests/lo_http_server.rb +55 -0
- metadata +127 -31
- data/History.txt +0 -3
- data/Manifest.txt +0 -13
- data/README.txt +0 -362
- data/lib/client.rb +0 -185
- data/lib/cmds.rb +0 -166
- data/lib/core.rb +0 -53
- data/lib/rtunnel_client_cmd.rb +0 -25
- data/lib/rtunnel_server_cmd.rb +0 -20
- data/lib/server.rb +0 -181
- data/rtunnel_client.rb +0 -3
- data/rtunnel_server.rb +0 -3
@@ -0,0 +1,351 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'eventmachine'
|
5
|
+
|
6
|
+
|
7
|
+
# The RTunnel server class, managing control and connection servers.
|
8
|
+
class RTunnel::Server
|
9
|
+
include RTunnel
|
10
|
+
include RTunnel::CommandProcessor
|
11
|
+
include RTunnel::Logging
|
12
|
+
include RTunnel::ConnectionId
|
13
|
+
|
14
|
+
attr_reader :control_address, :control_host, :control_port
|
15
|
+
attr_reader :keep_alive_interval, :authorized_keys
|
16
|
+
attr_reader :lowest_listen_port, :highest_listen_port
|
17
|
+
attr_reader :tunnel_connections
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
process_options options
|
21
|
+
@tunnel_controls = {}
|
22
|
+
@tunnel_connections = {}
|
23
|
+
@tunnel_connections_by_control = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def start
|
27
|
+
return if @control_listener
|
28
|
+
@control_host = SocketFactory.host_from_address @control_address
|
29
|
+
@control_port = SocketFactory.port_from_address @control_address
|
30
|
+
start_server
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
return unless @control_listener
|
35
|
+
EventMachine.stop_server @control_listener
|
36
|
+
@control_listener = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def start_server
|
40
|
+
D "Control server on #{@control_host} port #{@control_port}"
|
41
|
+
@control_listener = EventMachine.start_server @control_host, @control_port,
|
42
|
+
Server::ControlConnection,
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates a listener on a certain port. The given block should create and
|
47
|
+
# return the listener. If a listener is already active on the given port,
|
48
|
+
# the current listener is closed, and the new listener is created after the
|
49
|
+
# old listener is closed.
|
50
|
+
def create_tunnel_listener(listen_port, control_connection, &creation_block)
|
51
|
+
if old_control = @tunnel_controls[listen_port]
|
52
|
+
D "Closing old listener on port #{listen_port}"
|
53
|
+
EventMachine.stop_server old_control.listener
|
54
|
+
end
|
55
|
+
|
56
|
+
EventMachine.next_tick do
|
57
|
+
next unless yield
|
58
|
+
|
59
|
+
@tunnel_controls[listen_port] = control_connection
|
60
|
+
redirect_tunnel_connections old_control, control_connection if old_control
|
61
|
+
on_remote_listen
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Registers a tunnel connection, so it can receive data.
|
66
|
+
def register_tunnel_connection(connection)
|
67
|
+
@tunnel_connections[connection.connection_id] = connection
|
68
|
+
control_connection = connection.control_connection
|
69
|
+
@tunnel_connections_by_control[control_connection] ||= Set.new
|
70
|
+
@tunnel_connections_by_control[control_connection] << connection
|
71
|
+
end
|
72
|
+
|
73
|
+
# De-registers a tunnel connection.
|
74
|
+
def deregister_tunnel_connection(connection)
|
75
|
+
@tunnel_connections.delete connection.connection_id
|
76
|
+
control_connection = connection.control_connection
|
77
|
+
@tunnel_connections_by_control[control_connection].delete connection
|
78
|
+
end
|
79
|
+
|
80
|
+
def redirect_tunnel_connections(old_control, new_control)
|
81
|
+
return unless old_connections = @tunnel_connections_by_control[old_control]
|
82
|
+
old_connections.each do |tunnel_connection|
|
83
|
+
tunnel_connection.control_connection = new_control
|
84
|
+
end
|
85
|
+
@tunnel_connections_by_control[new_control] ||= Set.new
|
86
|
+
@tunnel_connections_by_control[new_control] += old_connections
|
87
|
+
end
|
88
|
+
|
89
|
+
def on_remote_listen(&block)
|
90
|
+
if block
|
91
|
+
@on_remote_listen = block
|
92
|
+
elsif @on_remote_listen
|
93
|
+
@on_remote_listen.call
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
## option processing
|
98
|
+
|
99
|
+
def process_options(options)
|
100
|
+
[:control_address, :keep_alive_interval, :authorized_keys,
|
101
|
+
:lowest_listen_port, :highest_listen_port].each do |opt|
|
102
|
+
instance_variable_set "@#{opt}".to_sym,
|
103
|
+
RTunnel::Server.send("extract_#{opt}".to_sym, options[opt])
|
104
|
+
end
|
105
|
+
|
106
|
+
init_log :level => options[:log_level]
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.extract_control_address(address)
|
110
|
+
return "0.0.0.0:#{RTunnel::DEFAULT_CONTROL_PORT}" unless address
|
111
|
+
if address =~ /^\d+$/
|
112
|
+
host = nil
|
113
|
+
port = address.to_i
|
114
|
+
else
|
115
|
+
host = SocketFactory.host_from_address address
|
116
|
+
port = SocketFactory.port_from_address address
|
117
|
+
end
|
118
|
+
host = RTunnel.resolve_address(host || "0.0.0.0")
|
119
|
+
port ||= RTunnel::DEFAULT_CONTROL_PORT.to_s
|
120
|
+
"#{host}:#{port}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.extract_keep_alive_interval(interval)
|
124
|
+
interval || RTunnel::KEEP_ALIVE_INTERVAL
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.extract_authorized_keys(keys_file)
|
128
|
+
keys_file and Crypto.load_public_keys keys_file
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.extract_lowest_listen_port(port)
|
132
|
+
port || 0
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.extract_highest_listen_port(port)
|
136
|
+
port || 65535
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# A client connection to the server's control port.
|
142
|
+
class RTunnel::Server::ControlConnection < EventMachine::Connection
|
143
|
+
include RTunnel
|
144
|
+
include RTunnel::CommandProcessor
|
145
|
+
include RTunnel::CommandProtocol
|
146
|
+
include RTunnel::Logging
|
147
|
+
|
148
|
+
attr_reader :server, :listener
|
149
|
+
|
150
|
+
def initialize(server)
|
151
|
+
super()
|
152
|
+
|
153
|
+
@server = server
|
154
|
+
@tunnel_connections = server.tunnel_connections
|
155
|
+
@listener = nil
|
156
|
+
@keep_alive_timer = nil
|
157
|
+
@keep_alive_interval = server.keep_alive_interval
|
158
|
+
@in_hasher = @out_hasher = nil
|
159
|
+
|
160
|
+
init_log :to => @server
|
161
|
+
end
|
162
|
+
|
163
|
+
def post_init
|
164
|
+
@client_port, @client_host = *Socket.unpack_sockaddr_in(get_peername)
|
165
|
+
D "Established connection with #{@client_host} port #{@client_port}"
|
166
|
+
enable_keep_alives
|
167
|
+
end
|
168
|
+
|
169
|
+
def unbind
|
170
|
+
D "Lost connection from #{@client_host} port #{@client_port}"
|
171
|
+
disable_keep_alives
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
## Command processing
|
176
|
+
|
177
|
+
def process_remote_listen(address)
|
178
|
+
listen_host = SocketFactory.host_from_address address
|
179
|
+
listen_port = SocketFactory.port_from_address address
|
180
|
+
|
181
|
+
unless validate_remote_listen listen_host, listen_port
|
182
|
+
send_command SetSessionKeyCommand.new('NO')
|
183
|
+
return
|
184
|
+
end
|
185
|
+
|
186
|
+
@server.create_tunnel_listener listen_port, self do
|
187
|
+
D "Creating listener for #{listen_host} port #{listen_port}"
|
188
|
+
begin
|
189
|
+
@listener = EventMachine.start_server listen_host, listen_port,
|
190
|
+
Server::TunnelConnection, self,
|
191
|
+
listen_host, listen_port
|
192
|
+
rescue RuntimeError => e
|
193
|
+
# EventMachine raises 'no acceptor' if the listen address is invalid
|
194
|
+
E "Invalid listen address #{listen_host}"
|
195
|
+
@listener = nil
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
D "Listening on #{listen_host} port #{listen_port}"
|
200
|
+
end
|
201
|
+
|
202
|
+
# Verifies if a RemoteListenCommand should be honored.
|
203
|
+
def validate_remote_listen(host, port)
|
204
|
+
if @server.authorized_keys and @out_hasher.nil?
|
205
|
+
D "Asked to open listen socket by unauthorized client"
|
206
|
+
return false
|
207
|
+
end
|
208
|
+
|
209
|
+
if port < @server.lowest_listen_port or port > @server.highest_listen_port
|
210
|
+
D "Asked to listen to forbidden port"
|
211
|
+
return false
|
212
|
+
end
|
213
|
+
|
214
|
+
true
|
215
|
+
end
|
216
|
+
|
217
|
+
def process_send_data(tunnel_connection_id, data)
|
218
|
+
tunnel_connection = @tunnel_connections[tunnel_connection_id]
|
219
|
+
if tunnel_connection
|
220
|
+
D "Data: #{data.length} bytes coming from #{tunnel_connection_id}"
|
221
|
+
tunnel_connection.send_data data
|
222
|
+
else
|
223
|
+
W "Asked to send to unknown connection #{tunnel_connection_id}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def process_close_connection(tunnel_connection_id)
|
228
|
+
tunnel_connection = @tunnel_connections[tunnel_connection_id]
|
229
|
+
if tunnel_connection
|
230
|
+
D "Closed from tunneled end: #{tunnel_connection_id}"
|
231
|
+
tunnel_connection.close_from_tunnel
|
232
|
+
else
|
233
|
+
W "Asked to close unknown connection #{tunnel_connection_id}"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def process_generate_session_key(public_key_fp)
|
238
|
+
if @server.authorized_keys
|
239
|
+
if public_key = @server.authorized_keys[public_key_fp]
|
240
|
+
D "Authorized client key received, generating session key"
|
241
|
+
@out_hasher, @in_hasher = Crypto::Hasher.new, Crypto::Hasher.new
|
242
|
+
|
243
|
+
iokeys = StringIO.new
|
244
|
+
iokeys.write_varstring @in_hasher.key
|
245
|
+
iokeys.write_varstring @out_hasher.key
|
246
|
+
encrypted_keys = Crypto.encrypt_with_key public_key, iokeys.string
|
247
|
+
else
|
248
|
+
D("Rejecting unauthorized client key (%s authorized keys)" %
|
249
|
+
@server.authorized_keys.length)
|
250
|
+
encrypted_keys = 'NO'
|
251
|
+
end
|
252
|
+
else
|
253
|
+
D "Asked to generate session key, but no authorized keys set"
|
254
|
+
encrypted_keys = ''
|
255
|
+
end
|
256
|
+
send_command SetSessionKeyCommand.new(encrypted_keys)
|
257
|
+
self.incoming_command_hasher = @in_hasher if @in_hasher
|
258
|
+
self.outgoing_command_hasher = @out_hasher if @out_hasher
|
259
|
+
end
|
260
|
+
|
261
|
+
def receive_bad_frame(frame, exception)
|
262
|
+
case exception
|
263
|
+
when :bad_signature
|
264
|
+
D "Ignoring command with invalid signature"
|
265
|
+
when Exception
|
266
|
+
D "Ignoring malformed command."
|
267
|
+
D "Decoding exception: #{exception.class.name} - #{exception}\n" +
|
268
|
+
"#{exception.backtrace.join("\n")}\n"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
## Keep-Alives (preventing timeouts)
|
274
|
+
|
275
|
+
#:nodoc:
|
276
|
+
def send_command(command)
|
277
|
+
@last_command_time = Time.now
|
278
|
+
super
|
279
|
+
end
|
280
|
+
|
281
|
+
# Enables sending KeepAliveCommands every few seconds.
|
282
|
+
def enable_keep_alives
|
283
|
+
@last_command_time = Time.now
|
284
|
+
@keep_alive_timer =
|
285
|
+
EventMachine::PeriodicTimer.new(@keep_alive_interval / 2) do
|
286
|
+
keep_alive_if_needed
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Sends a KeepAlive command if no command was sent recently.
|
291
|
+
def keep_alive_if_needed
|
292
|
+
if Time.now - @last_command_time >= @keep_alive_interval
|
293
|
+
send_command KeepAliveCommand.new
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Disables sending KeepAlives.
|
298
|
+
def disable_keep_alives
|
299
|
+
return unless @keep_alive_timer
|
300
|
+
@keep_alive_timer.cancel
|
301
|
+
@keep_alive_timer = nil
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
# A connection to a tunnelled port.
|
307
|
+
class RTunnel::Server::TunnelConnection < EventMachine::Connection
|
308
|
+
include RTunnel
|
309
|
+
include RTunnel::Logging
|
310
|
+
|
311
|
+
attr_reader :connection_id
|
312
|
+
attr_accessor :control_connection
|
313
|
+
|
314
|
+
def initialize(control_connection, listen_host, listen_port)
|
315
|
+
# listen_host and listen_port are passed for logging purposes only
|
316
|
+
@listen_host = listen_host
|
317
|
+
@listen_port = listen_port
|
318
|
+
@control_connection = control_connection
|
319
|
+
@server = @control_connection.server
|
320
|
+
@hasher = nil
|
321
|
+
|
322
|
+
init_log :to => @server
|
323
|
+
end
|
324
|
+
|
325
|
+
def post_init
|
326
|
+
@connection_id = @server.new_connection_id
|
327
|
+
peer = Socket.unpack_sockaddr_in(get_peername).reverse.join ':'
|
328
|
+
D "Tunnel connection from #{peer} on #{@connection_id}"
|
329
|
+
@server.register_tunnel_connection self
|
330
|
+
@control_connection.send_command CreateConnectionCommand.new(@connection_id)
|
331
|
+
end
|
332
|
+
|
333
|
+
def unbind
|
334
|
+
unless @tunnel_closed
|
335
|
+
D "Closed from client end: #{@connection_id}"
|
336
|
+
close_command = CloseConnectionCommand.new(@connection_id)
|
337
|
+
@control_connection.send_command close_command
|
338
|
+
end
|
339
|
+
@server.deregister_tunnel_connection self
|
340
|
+
end
|
341
|
+
|
342
|
+
def close_from_tunnel
|
343
|
+
@tunnel_closed = true
|
344
|
+
close_connection_after_writing
|
345
|
+
end
|
346
|
+
|
347
|
+
def receive_data(data)
|
348
|
+
D "Data: #{data.length} bytes for #{@connection_id}"
|
349
|
+
@control_connection.send_command SendDataCommand.new(@connection_id, data)
|
350
|
+
end
|
351
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module RTunnel::SocketFactory
|
4
|
+
def self.split_address(address)
|
5
|
+
port_index = address.index /[^:]\:[^:]/
|
6
|
+
if port_index
|
7
|
+
[address[0, port_index + 1], address[port_index + 2, address.length]]
|
8
|
+
else
|
9
|
+
[address, nil]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.host_from_address(address)
|
14
|
+
address and split_address(address)[0]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.port_from_address(address)
|
18
|
+
address and (port_string = split_address(address)[1]) and port_string.to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.inbound?(options)
|
22
|
+
options[:inbound] or [:in_port, :in_host, :in_addr].any? { |k| options[k] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.bind_host(options)
|
26
|
+
options[:in_host] or host_from_address(options[:in_addr]) or '0.0.0.0'
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.bind_port(options)
|
30
|
+
options[:in_port] or port_from_address(options[:in_addr]) or 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.bind_socket_address(options)
|
34
|
+
Socket::pack_sockaddr_in bind_port(options), bind_host(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.connect_host(options)
|
38
|
+
options[:out_host] or host_from_address(options[:out_addr])
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.connect_port(options)
|
42
|
+
options[:out_port] or port_from_address(options[:out_addr])
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.connect_socket_address(options)
|
46
|
+
Socket::pack_sockaddr_in connect_port(options), connect_host(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.tcp?(options)
|
50
|
+
options[:tcp] or !options[:udp]
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.new_tcp_socket
|
54
|
+
Socket.new Socket::AF_INET, Socket::SOCK_STREAM, Socket::PF_UNSPEC
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.new_udp_socket
|
58
|
+
Socket.new Socket::AF_INET, Socket::SOCK_DGRAM, Socket::PF_UNSPEC
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.new_socket(options)
|
62
|
+
tcp?(options) ? new_tcp_socket : new_udp_socket
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.bind(socket, options)
|
66
|
+
socket.bind bind_socket_address(options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.connect(socket, options)
|
70
|
+
socket.connect connect_socket_address(options)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.set_options(socket, options)
|
74
|
+
if options[:no_delay]
|
75
|
+
socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true
|
76
|
+
socket.sync = true
|
77
|
+
end
|
78
|
+
|
79
|
+
if options[:reuse_addr]
|
80
|
+
socket.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
|
81
|
+
end
|
82
|
+
|
83
|
+
unless options[:reverse_lookup]
|
84
|
+
if socket.respond_to? :do_not_reverse_lookup
|
85
|
+
socket.do_not_reverse_lookup = true
|
86
|
+
else
|
87
|
+
# work around until the patch below actually gets committed:
|
88
|
+
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/2346
|
89
|
+
BasicSocket.do_not_reverse_lookup = true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# new sockets coming out of socket.accept will have the given options set
|
95
|
+
def self.set_options_on_accept_sockets(socket, options)
|
96
|
+
socket.instance_variable_set :@rtunnel_factory_options, options
|
97
|
+
def socket.accept(*args)
|
98
|
+
sock, addr = super
|
99
|
+
RTunnel::SocketFactory.set_options sock, @rtunnel_factory_options
|
100
|
+
return sock, addr
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.socket(options = {})
|
105
|
+
s = new_socket options
|
106
|
+
set_options s, options
|
107
|
+
if inbound? options
|
108
|
+
bind s, options
|
109
|
+
set_options_on_accept_sockets s, options
|
110
|
+
else
|
111
|
+
connect s, options
|
112
|
+
end
|
113
|
+
s
|
114
|
+
end
|
115
|
+
|
116
|
+
def socket(options = {})
|
117
|
+
RTunnel::SocketFactory.socket(options)
|
118
|
+
end
|
119
|
+
end
|
data/lib/rtunnel.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module RTunnel
|
2
|
+
end
|
3
|
+
|
4
|
+
require 'rtunnel/core.rb'
|
5
|
+
require 'rtunnel/io_extensions.rb'
|
6
|
+
require 'rtunnel/socket_factory.rb'
|
7
|
+
require 'rtunnel/frame_protocol.rb'
|
8
|
+
|
9
|
+
require 'rtunnel/commands.rb'
|
10
|
+
require 'rtunnel/command_processor.rb'
|
11
|
+
require 'rtunnel/command_protocol.rb'
|
12
|
+
require 'rtunnel/connection_id.rb'
|
13
|
+
require 'rtunnel/crypto.rb'
|
14
|
+
|
15
|
+
require 'rtunnel/client.rb'
|
16
|
+
require 'rtunnel/leak.rb'
|
17
|
+
require 'rtunnel/server.rb'
|
18
|
+
|
19
|
+
require 'rtunnel/rtunnel_client_cmd.rb'
|
20
|
+
require 'rtunnel/rtunnel_server_cmd.rb'
|
data/rtunnel.gemspec
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{rtunnel}
|
5
|
+
s.version = "0.4.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["coderrr"]
|
9
|
+
s.date = %q{2009-01-26}
|
10
|
+
s.description = %q{Reverse tunnel server and client.}
|
11
|
+
s.email = %q{coderrr.contact@gmail.com}
|
12
|
+
s.executables = ["rtunnel_client", "rtunnel_server"]
|
13
|
+
s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README.markdown", "bin/rtunnel_client", "bin/rtunnel_server", "lib/rtunnel.rb", "lib/rtunnel/client.rb", "lib/rtunnel/command_protocol.rb", "lib/rtunnel/commands.rb", "lib/rtunnel/core.rb", "lib/rtunnel/crypto.rb", "lib/rtunnel/frame_protocol.rb", "lib/rtunnel/io_extensions.rb", "lib/rtunnel/leak.rb", "lib/rtunnel/rtunnel_client_cmd.rb", "lib/rtunnel/rtunnel_server_cmd.rb", "lib/rtunnel/server.rb", "lib/rtunnel/socket_factory.rb", "lib/rtunnel/connection_id.rb", "lib/rtunnel/command_processor.rb"]
|
14
|
+
s.files = ["CHANGELOG", "LICENSE", "Manifest", "README.markdown", "Rakefile", "bin/rtunnel_client", "bin/rtunnel_server", "lib/rtunnel.rb", "lib/rtunnel/client.rb", "lib/rtunnel/command_protocol.rb", "lib/rtunnel/commands.rb", "lib/rtunnel/core.rb", "lib/rtunnel/crypto.rb", "lib/rtunnel/frame_protocol.rb", "lib/rtunnel/io_extensions.rb", "lib/rtunnel/leak.rb", "lib/rtunnel/rtunnel_client_cmd.rb", "lib/rtunnel/rtunnel_server_cmd.rb", "lib/rtunnel/server.rb", "lib/rtunnel/socket_factory.rb", "lib/rtunnel/connection_id.rb", "lib/rtunnel/command_processor.rb", "spec/client_spec.rb", "spec/cmds_spec.rb", "spec/integration_spec.rb", "spec/server_spec.rb", "spec/spec_helper.rb", "test/command_stubs.rb", "test/protocol_mocks.rb", "test/scenario_connection.rb", "test/test_client.rb", "test/test_command_protocol.rb", "test/test_commands.rb", "test/test_crypto.rb", "test/test_frame_protocol.rb", "test/test_io_extensions.rb", "test/test_server.rb", "test/test_socket_factory.rb", "test/test_tunnel.rb", "test/test_connection_id.rb", "test_data/known_hosts", "test_data/ssh_host_rsa_key", "test_data/random_rsa_key", "test_data/ssh_host_dsa_key", "test_data/authorized_keys2", "tests/_ab_test.rb", "tests/_stress_test.rb", "tests/lo_http_server.rb", "rtunnel.gemspec"]
|
15
|
+
s.has_rdoc = true
|
16
|
+
s.homepage = %q{http://github.com/coderrr/rtunnel}
|
17
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Rtunnel", "--main", "README.markdown"]
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.rubyforge_project = %q{coderrr}
|
20
|
+
s.rubygems_version = %q{1.3.1}
|
21
|
+
s.summary = %q{Reverse tunnel server and client.}
|
22
|
+
s.test_files = ["test/test_commands.rb", "test/test_frame_protocol.rb", "test/test_connection_id.rb", "test/test_tunnel.rb", "test/test_socket_factory.rb", "test/test_client.rb", "test/test_crypto.rb", "test/test_io_extensions.rb", "test/test_command_protocol.rb", "test/test_server.rb"]
|
23
|
+
|
24
|
+
if s.respond_to? :specification_version then
|
25
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
26
|
+
s.specification_version = 2
|
27
|
+
|
28
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
29
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.2"])
|
30
|
+
s.add_runtime_dependency(%q<net-ssh>, [">= 2.0.4"])
|
31
|
+
s.add_development_dependency(%q<echoe>, [">= 3.0.1"])
|
32
|
+
s.add_development_dependency(%q<rspec>, [">= 1.1.11"])
|
33
|
+
s.add_development_dependency(%q<simple-daemon>, [">= 0.1.2"])
|
34
|
+
s.add_development_dependency(%q<thin>, [">= 1.0.0"])
|
35
|
+
else
|
36
|
+
s.add_dependency(%q<eventmachine>, [">= 0.12.2"])
|
37
|
+
s.add_dependency(%q<net-ssh>, [">= 2.0.4"])
|
38
|
+
s.add_dependency(%q<echoe>, [">= 3.0.1"])
|
39
|
+
s.add_dependency(%q<rspec>, [">= 1.1.11"])
|
40
|
+
s.add_dependency(%q<simple-daemon>, [">= 0.1.2"])
|
41
|
+
s.add_dependency(%q<thin>, [">= 1.0.0"])
|
42
|
+
end
|
43
|
+
else
|
44
|
+
s.add_dependency(%q<eventmachine>, [">= 0.12.2"])
|
45
|
+
s.add_dependency(%q<net-ssh>, [">= 2.0.4"])
|
46
|
+
s.add_dependency(%q<echoe>, [">= 3.0.1"])
|
47
|
+
s.add_dependency(%q<rspec>, [">= 1.1.11"])
|
48
|
+
s.add_dependency(%q<simple-daemon>, [">= 0.1.2"])
|
49
|
+
s.add_dependency(%q<thin>, [">= 1.0.0"])
|
50
|
+
end
|
51
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
require 'client'
|
4
|
+
|
5
|
+
include RTunnel
|
6
|
+
describe RTunnel::Client, "addresses" do
|
7
|
+
def o(opts)
|
8
|
+
{:control_address => 'x.com', :remote_listen_address => '5555', :tunnel_to_address => '6666'}.merge opts
|
9
|
+
end
|
10
|
+
|
11
|
+
before :all do
|
12
|
+
String.class_eval do
|
13
|
+
alias_method :orig_replace_with_ip!, :replace_with_ip!
|
14
|
+
define_method :replace_with_ip! do
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
after :all do
|
21
|
+
String.class_eval do
|
22
|
+
alias_method :replace_with_ip!, :orig_replace_with_ip!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should use default control port if not specified" do
|
27
|
+
Client.new(o :control_address => 'asdf.net').
|
28
|
+
instance_eval { @control_address }.should == "asdf.net:#{DEFAULT_CONTROL_PORT}"
|
29
|
+
|
30
|
+
Client.new(o :control_address => 'asdf.net:1234').
|
31
|
+
instance_eval { @control_address }.should == "asdf.net:1234"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should use 0.0.0.0 for remote listen host if not specified" do
|
35
|
+
Client.new(o :control_address => 'asdf.net:1234', :remote_listen_address => '8888').
|
36
|
+
instance_eval { @remote_listen_address }.should == '0.0.0.0:8888'
|
37
|
+
|
38
|
+
Client.new(o :control_address => 'asdf.net:1234', :remote_listen_address => 'ip2.asdf.net:8888').
|
39
|
+
instance_eval { @remote_listen_address }.should == 'ip2.asdf.net:8888'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should use localhost for tunnel to address if not specified" do
|
43
|
+
Client.new(o :tunnel_to_address => '5555').
|
44
|
+
instance_eval { @tunnel_to_address }.should == 'localhost:5555'
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|