costan-rtunnel 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 (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