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.
Files changed (61) hide show
  1. data/CHANGELOG +13 -0
  2. data/LICENSE +21 -0
  3. data/Manifest +48 -0
  4. data/README.markdown +84 -0
  5. data/Rakefile +45 -0
  6. data/bin/rtunnel_client +2 -1
  7. data/bin/rtunnel_server +2 -1
  8. data/lib/rtunnel/client.rb +308 -0
  9. data/lib/rtunnel/command_processor.rb +62 -0
  10. data/lib/rtunnel/command_protocol.rb +50 -0
  11. data/lib/rtunnel/commands.rb +233 -0
  12. data/lib/rtunnel/connection_id.rb +24 -0
  13. data/lib/rtunnel/core.rb +58 -0
  14. data/lib/rtunnel/crypto.rb +106 -0
  15. data/lib/rtunnel/frame_protocol.rb +34 -0
  16. data/lib/rtunnel/io_extensions.rb +54 -0
  17. data/lib/rtunnel/leak.rb +35 -0
  18. data/lib/rtunnel/rtunnel_client_cmd.rb +41 -0
  19. data/lib/rtunnel/rtunnel_server_cmd.rb +32 -0
  20. data/lib/rtunnel/server.rb +351 -0
  21. data/lib/rtunnel/socket_factory.rb +119 -0
  22. data/lib/rtunnel.rb +20 -0
  23. data/rtunnel.gemspec +51 -0
  24. data/spec/client_spec.rb +47 -0
  25. data/spec/cmds_spec.rb +127 -0
  26. data/spec/integration_spec.rb +105 -0
  27. data/spec/server_spec.rb +21 -0
  28. data/spec/spec_helper.rb +3 -0
  29. data/test/command_stubs.rb +77 -0
  30. data/test/protocol_mocks.rb +43 -0
  31. data/test/scenario_connection.rb +109 -0
  32. data/test/test_client.rb +48 -0
  33. data/test/test_command_protocol.rb +82 -0
  34. data/test/test_commands.rb +49 -0
  35. data/test/test_connection_id.rb +30 -0
  36. data/test/test_crypto.rb +127 -0
  37. data/test/test_frame_protocol.rb +109 -0
  38. data/test/test_io_extensions.rb +70 -0
  39. data/test/test_server.rb +70 -0
  40. data/test/test_socket_factory.rb +42 -0
  41. data/test/test_tunnel.rb +186 -0
  42. data/test_data/authorized_keys2 +4 -0
  43. data/test_data/known_hosts +4 -0
  44. data/test_data/random_rsa_key +27 -0
  45. data/test_data/ssh_host_dsa_key +12 -0
  46. data/test_data/ssh_host_rsa_key +27 -0
  47. data/tests/_ab_test.rb +16 -0
  48. data/tests/_stress_test.rb +96 -0
  49. data/tests/lo_http_server.rb +55 -0
  50. metadata +127 -31
  51. data/History.txt +0 -3
  52. data/Manifest.txt +0 -13
  53. data/README.txt +0 -362
  54. data/lib/client.rb +0 -185
  55. data/lib/cmds.rb +0 -166
  56. data/lib/core.rb +0 -53
  57. data/lib/rtunnel_client_cmd.rb +0 -25
  58. data/lib/rtunnel_server_cmd.rb +0 -20
  59. data/lib/server.rb +0 -181
  60. data/rtunnel_client.rb +0 -3
  61. 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
@@ -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