costan-rtunnel 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/CHANGELOG +13 -0
  2. data/LICENSE +21 -0
  3. data/Manifest +49 -0
  4. data/README.markdown +84 -0
  5. data/Rakefile +45 -0
  6. data/bin/rtunnel_client +4 -0
  7. data/bin/rtunnel_server +4 -0
  8. data/lib/rtunnel.rb +20 -0
  9. data/lib/rtunnel/client.rb +308 -0
  10. data/lib/rtunnel/command_processor.rb +62 -0
  11. data/lib/rtunnel/command_protocol.rb +50 -0
  12. data/lib/rtunnel/commands.rb +233 -0
  13. data/lib/rtunnel/connection_id.rb +24 -0
  14. data/lib/rtunnel/core.rb +58 -0
  15. data/lib/rtunnel/crypto.rb +106 -0
  16. data/lib/rtunnel/frame_protocol.rb +34 -0
  17. data/lib/rtunnel/io_extensions.rb +54 -0
  18. data/lib/rtunnel/leak.rb +35 -0
  19. data/lib/rtunnel/rtunnel_client_cmd.rb +41 -0
  20. data/lib/rtunnel/rtunnel_server_cmd.rb +32 -0
  21. data/lib/rtunnel/server.rb +351 -0
  22. data/lib/rtunnel/socket_factory.rb +119 -0
  23. data/spec/client_spec.rb +47 -0
  24. data/spec/cmds_spec.rb +127 -0
  25. data/spec/integration_spec.rb +105 -0
  26. data/spec/server_spec.rb +21 -0
  27. data/spec/spec_helper.rb +3 -0
  28. data/test/command_stubs.rb +77 -0
  29. data/test/protocol_mocks.rb +43 -0
  30. data/test/scenario_connection.rb +109 -0
  31. data/test/test_client.rb +48 -0
  32. data/test/test_command_protocol.rb +82 -0
  33. data/test/test_commands.rb +49 -0
  34. data/test/test_connection_id.rb +30 -0
  35. data/test/test_crypto.rb +127 -0
  36. data/test/test_frame_protocol.rb +109 -0
  37. data/test/test_io_extensions.rb +70 -0
  38. data/test/test_server.rb +70 -0
  39. data/test/test_socket_factory.rb +42 -0
  40. data/test/test_tunnel.rb +186 -0
  41. data/test_data/authorized_keys2 +4 -0
  42. data/test_data/known_hosts +4 -0
  43. data/test_data/random_rsa_key +27 -0
  44. data/test_data/ssh_host_dsa_key +12 -0
  45. data/test_data/ssh_host_rsa_key +27 -0
  46. data/tests/_ab_test.rb +16 -0
  47. data/tests/_stress_test.rb +96 -0
  48. data/tests/lo_http_server.rb +55 -0
  49. metadata +121 -0
@@ -0,0 +1,109 @@
1
+ require 'rtunnel'
2
+
3
+ require 'test/unit'
4
+
5
+ require 'test/protocol_mocks.rb'
6
+
7
+ # Send mock for frames.
8
+ class EmSendFramesMock < EmSendMock
9
+ include RTunnel::FrameProtocol
10
+ end
11
+
12
+ # Receive mock for frames.
13
+ class EmReceiveFramesMock < EmReceiveMock
14
+ include RTunnel::FrameProtocol
15
+ object_name :frame
16
+ end
17
+
18
+ class FrameProtocolTest < Test::Unit::TestCase
19
+ def setup
20
+ super
21
+ @send_mock = EmSendFramesMock.new
22
+ end
23
+
24
+ def teardown
25
+ super
26
+ end
27
+
28
+ def continuous_data_test(frames)
29
+ truncated_data_test frames, []
30
+ end
31
+
32
+ def truncated_data_test(frames, sub_lengths)
33
+ frames.each { |frame| @send_mock.send_frame frame }
34
+ in_string = @send_mock.string
35
+ in_strings, i = [], 0
36
+ sub_lengths.each do |sublen|
37
+ in_strings << in_string[i, sublen]
38
+ i += sublen
39
+ end
40
+ in_strings << in_string[i..-1] if i < in_string.length
41
+ out_frames = EmReceiveFramesMock.new(@send_mock.string).replay.frames
42
+ assert_equal frames, out_frames
43
+ end
44
+
45
+ def test_empty_frame
46
+ continuous_data_test ['']
47
+ end
48
+
49
+ def test_byte_frame
50
+ continuous_data_test ['F']
51
+ end
52
+
53
+ def test_string_frame
54
+ continuous_data_test [(32...128).to_a.pack('C*')]
55
+ end
56
+
57
+ def test_multiple_frames
58
+ continuous_data_test [(32...128).to_a.pack('C*'), '', 'F', '', '1234567890']
59
+ end
60
+
61
+ def test_truncated_border
62
+ truncated_data_test ['A', 'A'], [1, 0, 2, 0]
63
+ end
64
+
65
+ def test_truncated_border_and_joined_data_size
66
+ truncated_data_test ['A', 'A'], [1, 1, 1, 1]
67
+ end
68
+
69
+ def test_truncated_size
70
+ long_frame = (32...128).to_a.pack('C*') * 5
71
+ truncated_data_test [long_frame], [1]
72
+ end
73
+
74
+ def test_truncated_size_and_data
75
+ long_frame = (32...128).to_a.pack('C*') * 5
76
+ truncated_data_test [long_frame], [1, 16]
77
+ end
78
+
79
+ def test_badass
80
+ # TODO(not_me): this test takes 4 seconds; replace with more targeted tests
81
+
82
+ # build the badass string
83
+ s2_frame = 'qwertyuiopasdfgh' * 8 * 128 # 16384 characters, size is 3 bytes
84
+ @send_mock.send_frame s2_frame
85
+ s2_string = @send_mock.string
86
+ s2_count = 3
87
+ send_string = s2_string * s2_count
88
+ ex_frames = [s2_frame] * s2_count
89
+
90
+ # build cut points in a string
91
+ s2_points = [0, 1, 2, 3, 4, 5, 127, 128, 8190, 16381, 16382, 16383]
92
+ cut_points = []
93
+ 0.upto(s2_count - 1) do |i|
94
+ cut_points += s2_points.map { |p| p + i * s2_string.length }
95
+ end
96
+
97
+ # try all combinations of cutting up the string in 4 pieces
98
+ 0.upto(cut_points.length - 1) do |i|
99
+ (i + 1).upto(cut_points.length - 1) do |j|
100
+ (j + 1).upto(cut_points.length - 1) do |k|
101
+ packets = [0...cut_points[i], cut_points[i]...cut_points[j],
102
+ cut_points[j]...cut_points[k], cut_points[k]..-1].
103
+ map { |r| send_string[r] }
104
+ assert_equal ex_frames, EmReceiveFramesMock.new(packets).replay.frames
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,70 @@
1
+ require 'rtunnel'
2
+
3
+ require 'stringio'
4
+ require 'test/unit'
5
+
6
+ class IOExtensionsTest < Test::Unit::TestCase
7
+ def setup
8
+ @test_sizes = [0, 1, 10, 127, 128, (1 << 20), (1 << 45) / 3, (1 << 62) / 5]
9
+ @test_strings = ['', 'a',
10
+ (32..127).to_a.map { |c| c.chr}.join,
11
+ (32..127).to_a.map { |c| c.chr}.join * 511]
12
+ @str = StringIO.new
13
+ end
14
+
15
+ def test_varsizes_independent
16
+ @test_sizes.each do |size|
17
+ @str = StringIO.new
18
+ @str.write_varsize size
19
+ @str.rewind
20
+ assert_equal size, @str.read_varsize
21
+ end
22
+ end
23
+
24
+ def test_varsizes_appended
25
+ @test_sizes.each { |size| @str.write_varsize size }
26
+ @str.rewind
27
+ @test_sizes.each do |size|
28
+ assert_equal size, @str.read_varsize
29
+ end
30
+ end
31
+
32
+ def test_varsize_error
33
+ @str.write_varsize((1 << 62) / 3)
34
+ vs = @str.string
35
+ 0.upto(vs.length - 1) do |truncated_len|
36
+ @str = StringIO.new
37
+ @str.write vs[0, truncated_len]
38
+ @str.rewind
39
+ assert_raise(RTunnel::TruncatedDataError) { @str.read_varsize }
40
+ end
41
+ end
42
+
43
+ def test_varstring_independent
44
+ @test_strings.each do |str|
45
+ @str = StringIO.new
46
+ @str.write_varstring str
47
+ @str.rewind
48
+ assert_equal str, @str.read_varstring
49
+ end
50
+ end
51
+
52
+ def test_varstring_appended
53
+ @test_strings.each { |str| @str.write_varstring str }
54
+ @str.rewind
55
+ @test_strings.each do |str|
56
+ assert_equal str, @str.read_varstring
57
+ end
58
+ end
59
+
60
+ def test_varstring_error
61
+ @str.write_varstring 'This will be truncated'
62
+ vs = @str.string
63
+ 0.upto(vs.length - 1) do |truncated_len|
64
+ @str = StringIO.new
65
+ @str.write vs[0, truncated_len]
66
+ @str.rewind
67
+ assert_raise(RTunnel::TruncatedDataError) { @str.read_varstring }
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,70 @@
1
+ require 'rtunnel'
2
+
3
+ require 'resolv'
4
+ require 'test/unit'
5
+
6
+
7
+ class ServerTest < Test::Unit::TestCase
8
+ def setup
9
+ super
10
+ @server = RTunnel::Server.new(:control_address => 'localhost')
11
+ @localhost_addr = Resolv.getaddress 'localhost'
12
+ end
13
+
14
+ def test_options
15
+ server = RTunnel::Server
16
+ assert_equal "18.241.3.100:#{RTunnel::DEFAULT_CONTROL_PORT}",
17
+ server.extract_control_address('18.241.3.100')
18
+ assert_equal "18.241.3.100:9199",
19
+ server.extract_control_address('18.241.3.100:9199')
20
+ assert_equal "#{@localhost_addr}:#{RTunnel::DEFAULT_CONTROL_PORT}",
21
+ server.extract_control_address('localhost')
22
+ assert_equal "#{@localhost_addr}:9199",
23
+ server.extract_control_address('localhost:9199')
24
+ assert_equal "0.0.0.0:9199",
25
+ server.extract_control_address('9199')
26
+
27
+ assert_equal RTunnel::KEEP_ALIVE_INTERVAL,
28
+ server.extract_keep_alive_interval(nil)
29
+ assert_equal 29, server.extract_keep_alive_interval(29)
30
+
31
+ assert_equal 0, server.extract_lowest_listen_port(nil)
32
+ assert_equal 29, server.extract_lowest_listen_port(29)
33
+ assert_equal 65535, server.extract_highest_listen_port(nil)
34
+ assert_equal 29, server.extract_highest_listen_port(29)
35
+
36
+ assert_equal nil, server.extract_authorized_keys(nil)
37
+ keyset = server.extract_authorized_keys 'test_data/known_hosts'
38
+ assert_equal RTunnel::Crypto::KeySet, keyset.class
39
+ assert keyset.length != 0, 'No key read from the known_hosts file'
40
+ end
41
+
42
+ def test_validate_remote_listen
43
+ # remove Connection's new override, don't want event_machine here
44
+ EventMachine::Connection.class_eval do
45
+ class << self
46
+ alias_method :backup_new, :new
47
+ remove_method :new
48
+ end
49
+ end
50
+
51
+ server = RTunnel::Server.new(:control_address => 'localhost',
52
+ :lowest_listen_port => 91,
53
+ :highest_listen_port => 105)
54
+ connection = RTunnel::Server::ControlConnection.new server
55
+
56
+ assert !connection.validate_remote_listen('localhost', 80)
57
+ assert connection.validate_remote_listen('localhost', 91)
58
+ assert connection.validate_remote_listen('localhost', 105)
59
+ assert !connection.validate_remote_listen('localhost', 90)
60
+ assert !connection.validate_remote_listen('localhost', 106)
61
+
62
+ # re-instate Connection's new override
63
+ EventMachine::Connection.class_eval do
64
+ class << self
65
+ alias_method :new, :backup_new
66
+ remove_method :backup_new
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,42 @@
1
+ require 'rtunnel'
2
+
3
+ require 'test/unit'
4
+
5
+ class SocketFactoryTest < Test::Unit::TestCase
6
+ SF = RTunnel::SocketFactory
7
+
8
+ def setup
9
+
10
+ end
11
+
12
+ def teardown
13
+
14
+ end
15
+
16
+ def test_host_from_address
17
+ assert_equal nil, SF.host_from_address(nil)
18
+ assert_equal '127.0.0.1', SF.host_from_address('127.0.0.1')
19
+ assert_equal '127.0.0.1', SF.host_from_address('127.0.0.1:1234')
20
+ assert_equal 'fe80::1%lo0', SF.host_from_address('fe80::1%lo0')
21
+ assert_equal 'fe80::1%lo0', SF.host_from_address('fe80::1%lo0:19020')
22
+ end
23
+
24
+ def test_port_from_address
25
+ assert_equal nil, SF.port_from_address(nil)
26
+ assert_equal nil, SF.port_from_address('127.0.0.1')
27
+ assert_equal 1234, SF.port_from_address('127.0.0.1:1234')
28
+ assert_equal nil, SF.port_from_address('fe80::1%lo0')
29
+ assert_equal 19020, SF.port_from_address('fe80::1%lo0:19020')
30
+ end
31
+
32
+ def test_inbound
33
+ assert SF.inbound?(:in_port => 1)
34
+ assert SF.inbound?(:in_host => '1')
35
+ assert SF.inbound?(:in_addr => '1')
36
+ assert SF.inbound?(:inbound => true)
37
+ assert !SF.inbound?(:out_port => 1)
38
+ assert !SF.inbound?(:out_host => '1')
39
+ assert !SF.inbound?(:out_addr => '1')
40
+ end
41
+ end
42
+
@@ -0,0 +1,186 @@
1
+ require 'rtunnel'
2
+
3
+ require 'test/unit'
4
+
5
+ require 'rubygems'
6
+ require 'eventmachine'
7
+
8
+ require 'test/scenario_connection.rb'
9
+
10
+ # Integration tests ensuring that we can start a tunnel.
11
+ class TunnelTest < Test::Unit::TestCase
12
+ def setup
13
+ super
14
+
15
+ @connection_time = 0.001
16
+ @secure_connection_time = 0.5
17
+ @log_level = 'debug'
18
+ @local_host = '127.0.0.1'
19
+ @listen_port = 21335
20
+ @tunnel_port = 21336
21
+ @control_port = 21337
22
+ @key_file = 'test_data/ssh_host_rsa_key'
23
+ @hosts_file = 'test_data/known_hosts'
24
+
25
+ @tunnel_server = new_server
26
+ @tunnel_client = new_client
27
+ end
28
+
29
+ def new_server(extra_options = {})
30
+ RTunnel::Server.new({
31
+ :control_address => "#{@local_host}:#{@control_port}",
32
+ :log_level => @log_level
33
+ }.merge(extra_options))
34
+ end
35
+
36
+ def new_client(extra_options = {})
37
+ RTunnel::Client.new({
38
+ :control_address => "#{@local_host}:#{@control_port}",
39
+ :remote_listen_address => "#{@local_host}:#{@listen_port}",
40
+ :tunnel_to_address => "#{@local_host}:#{@tunnel_port}",
41
+ :log_level => @log_level
42
+ }.merge(extra_options))
43
+ end
44
+
45
+ def tunnel_test(connection_time = nil)
46
+ @stop_proc = proc do
47
+ @tunnel_client.stop
48
+ @tunnel_server.stop
49
+ @stop_proc = nil
50
+ end
51
+
52
+ EventMachine::run do
53
+ @tunnel_server.start
54
+ @tunnel_client.start
55
+
56
+ if connection_time
57
+ EventMachine.add_timer(connection_time) { yield }
58
+ else
59
+ EventMachine.next_tick { yield }
60
+ end
61
+ end
62
+ end
63
+
64
+ def test_client_driven_tunnel
65
+ tunnel_test do
66
+ @tunnel_server.on_remote_listen do
67
+ EventMachine::start_server @local_host, @tunnel_port,
68
+ ScenarioConnection, self, [[:recv, 'Hello'], [:send, 'World'],
69
+ [:unbind], [:stop, @stop_proc]]
70
+
71
+ EventMachine::connect @local_host, @listen_port,
72
+ ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
73
+ [:close]]
74
+ end
75
+ end
76
+ end
77
+
78
+ def test_server_driven_tunnel
79
+ tunnel_test do
80
+ @tunnel_server.on_remote_listen do
81
+ EventMachine::start_server @local_host, @tunnel_port,
82
+ ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
83
+ [:close]]
84
+
85
+ EventMachine::connect @local_host, @listen_port,
86
+ ScenarioConnection, self, [[:recv, 'Hello'], [:send, 'World'],
87
+ [:unbind], [:stop]]
88
+ end
89
+ end
90
+ end
91
+
92
+ def test_two_tunnels
93
+ start_second = lambda do
94
+ @tunnel_client.stop
95
+ @tunnel_client.start
96
+ @tunnel_server.on_remote_listen do
97
+ EventMachine::connect @local_host, @listen_port,
98
+ ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
99
+ [:unbind], [:stop, @stop_proc]]
100
+ end
101
+ end
102
+
103
+ tunnel_test do
104
+ @tunnel_server.on_remote_listen do
105
+ EventMachine::start_server @local_host, @tunnel_port,
106
+ ScenarioConnection, self, [[:recv, 'Hello'], [:send, 'World'],
107
+ [:close], [:recv, 'Hello'], [:send, 'World'], [:close]]
108
+
109
+ EventMachine::connect @local_host, @listen_port,
110
+ ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
111
+ [:proc, start_second], [:unbind]]
112
+ end
113
+ end
114
+ end
115
+
116
+ def test_secure_tunnel
117
+ @tunnel_server = new_server :authorized_keys => @hosts_file
118
+ @tunnel_client = new_client :private_key => @key_file
119
+ tunnel_test do
120
+ @tunnel_server.on_remote_listen do
121
+ EventMachine::start_server @local_host, @tunnel_port,
122
+ ScenarioConnection, self, [[:recv, 'Hello'], [:send, 'World'],
123
+ [:unbind], [:stop, @stop_proc]]
124
+
125
+ EventMachine::connect @local_host, @listen_port,
126
+ ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
127
+ [:close]]
128
+ end
129
+ end
130
+ end
131
+
132
+ def test_secure_async_tunnel
133
+ @tunnel_server = new_server :authorized_keys => @hosts_file
134
+ @tunnel_client = new_client :private_key => @key_file
135
+ tunnel_test do
136
+ @tunnel_server.on_remote_listen do
137
+ EventMachine::start_server @local_host, @tunnel_port,
138
+ ScenarioConnection, self, [[:send, 'World'], [:recv, 'Hello'],
139
+ [:unbind], [:stop, @stop_proc]]
140
+
141
+ EventMachine::connect @local_host, @listen_port,
142
+ ScenarioConnection, self, [[:send, 'Hello'], [:recv, 'World'],
143
+ [:close]]
144
+ end
145
+ end
146
+ end
147
+
148
+ def test_bad_listen_address
149
+ @tunnel_server = new_server
150
+ @tunnel_client = new_client :remote_listen_address =>
151
+ "18.70.0.160:#{@listen_port}"
152
+
153
+ tunnel_test do
154
+ EventMachine::start_server @local_host, @tunnel_port,
155
+ ScenarioConnection, self, []
156
+
157
+ EventMachine::connect @local_host, @listen_port,
158
+ ScenarioConnection, self, [[:unbind], [:stop, @stop_proc]]
159
+ end
160
+ end
161
+
162
+ # TODO: fix this
163
+ def test_secure_server_rejects_unsecure_client
164
+ @tunnel_server = new_server :authorized_keys => @hosts_file
165
+ tunnel_test(@secure_connection_time) do
166
+ EventMachine::start_server @local_host, @tunnel_port,
167
+ ScenarioConnection, self, []
168
+
169
+ EventMachine::connect @local_host, @listen_port,
170
+ ScenarioConnection, self, [[:unbind], [:stop, @stop_proc]]
171
+ end
172
+ end
173
+
174
+ # hrmf... this is testing security and its not 100% reliable... but I can't think of a better way
175
+ def test_secure_server_rejects_unauthorized_key
176
+ @tunnel_server = new_server :authorized_keys => @hosts_file
177
+ @tunnel_client = new_client :private_key => 'test_data/random_rsa_key'
178
+ tunnel_test(@secure_connection_time) do
179
+ EventMachine::start_server @local_host, @tunnel_port,
180
+ ScenarioConnection, self, []
181
+
182
+ EventMachine::connect @local_host, @listen_port,
183
+ ScenarioConnection, self, [[:unbind], [:stop, @stop_proc]]
184
+ end
185
+ end
186
+ end