coderrr-rtunnel 0.3.9 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/CHANGELOG +13 -0
  2. data/LICENSE +21 -0
  3. data/Manifest +48 -0
  4. data/README.markdown +40 -15
  5. data/Rakefile +31 -4
  6. data/bin/rtunnel_client +2 -1
  7. data/bin/rtunnel_server +2 -1
  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/test/command_stubs.rb +77 -0
  24. data/test/protocol_mocks.rb +43 -0
  25. data/test/scenario_connection.rb +109 -0
  26. data/test/test_client.rb +48 -0
  27. data/test/test_command_protocol.rb +82 -0
  28. data/test/test_commands.rb +49 -0
  29. data/test/test_connection_id.rb +30 -0
  30. data/test/test_crypto.rb +127 -0
  31. data/test/test_frame_protocol.rb +109 -0
  32. data/test/test_io_extensions.rb +70 -0
  33. data/test/test_server.rb +70 -0
  34. data/test/test_socket_factory.rb +42 -0
  35. data/test/test_tunnel.rb +186 -0
  36. data/test_data/authorized_keys2 +4 -0
  37. data/test_data/known_hosts +4 -0
  38. data/test_data/random_rsa_key +27 -0
  39. data/test_data/ssh_host_dsa_key +12 -0
  40. data/test_data/ssh_host_rsa_key +27 -0
  41. data/tests/_ab_test.rb +16 -0
  42. data/tests/_stress_test.rb +96 -0
  43. data/tests/lo_http_server.rb +55 -0
  44. metadata +67 -27
  45. data/ab_test.rb +0 -23
  46. data/lib/client.rb +0 -150
  47. data/lib/cmds.rb +0 -166
  48. data/lib/core.rb +0 -58
  49. data/lib/rtunnel_client_cmd.rb +0 -23
  50. data/lib/rtunnel_server_cmd.rb +0 -18
  51. data/lib/server.rb +0 -197
  52. data/rtunnel.gemspec +0 -18
  53. data/rtunnel_client.rb +0 -3
  54. data/rtunnel_server.rb +0 -3
  55. data/stress_test.rb +0 -68
@@ -0,0 +1,49 @@
1
+ require 'rtunnel'
2
+
3
+ require 'stringio'
4
+ require 'test/unit'
5
+
6
+ require 'test/command_stubs.rb'
7
+
8
+ class CommandsTest < Test::Unit::TestCase
9
+ include CommandStubs
10
+
11
+ def setup
12
+ super
13
+ @str = StringIO.new
14
+ end
15
+
16
+ CommandStubs.command_names.each do |cmd|
17
+ define_method "test_#{cmd}_encode" do
18
+ command = self.send "generate_#{cmd}"
19
+ command.encode @str
20
+ @str.rewind
21
+ decoded_command = RTunnel::Command.decode @str
22
+ self.send "verify_#{cmd}", decoded_command
23
+ assert_equal "", @str.read, "Command #{cmd} did not consume its entire outpt"
24
+ end
25
+
26
+ define_method "test_#{cmd}_to_encoded_str" do
27
+ command = self.send "generate_#{cmd}"
28
+ command.encode @str
29
+ @str.rewind
30
+ assert_equal @str.read, command.to_encoded_str
31
+ end
32
+ end
33
+
34
+ def test_all_encodes
35
+ sequence = CommandStubs.command_test_sequence
36
+ sequence.each { |cmd| self.send("generate_#{cmd}").encode @str }
37
+ @str.rewind
38
+ sequence.each do |cmd|
39
+ command = RTunnel::Command.decode(@str)
40
+ self.send "verify_#{cmd}", command
41
+ end
42
+ assert_equal "", @str.read
43
+ end
44
+
45
+ def test_codes
46
+ # TODO(not_me): it'd be nice to have more than a smoke test here
47
+ assert_equal String, RTunnel::Command.printable_codes.class
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ require 'rtunnel'
2
+
3
+ require 'test/unit'
4
+
5
+ class ConnectionIdTest < Test::Unit::TestCase
6
+ class CidWrapper
7
+ include RTunnel::ConnectionId
8
+ end
9
+
10
+ def setup
11
+ @server = CidWrapper.new
12
+ end
13
+
14
+ def test_ids_are_unique
15
+ n_ids = 1024
16
+ ids = (0...n_ids).map { @server.new_connection_id }
17
+ assert_equal n_ids, ids.uniq.length
18
+ end
19
+
20
+ def test_id_sequences_are_not_trivial
21
+ n_servers = 256
22
+ n_ids = 16
23
+ sequences = (0...n_servers).map { CidWrapper.new }.map do |server|
24
+ (0...n_ids).map { server.new_connection_id }
25
+ end
26
+ 0.upto(n_ids - 1) do |i|
27
+ assert_equal n_servers, sequences.map { |seq| seq[i] }.uniq.length
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,127 @@
1
+ require 'rtunnel'
2
+
3
+ require 'openssl'
4
+ require 'test/unit'
5
+
6
+ class CryptoTest < Test::Unit::TestCase
7
+ C = RTunnel::Crypto
8
+
9
+ @@rsa_key_path = 'test_data/ssh_host_rsa_key'
10
+ @@dsa_key_path = 'test_data/ssh_host_dsa_key'
11
+ @@known_hosts_path = 'test_data/known_hosts'
12
+ @@authorized_keys_path = 'test_data/authorized_keys2'
13
+
14
+ def test_read_private_key
15
+ key = C.read_private_key @@rsa_key_path
16
+ assert_equal File.read(@@rsa_key_path), key.to_pem
17
+ assert_equal OpenSSL::PKey::RSA, key.class
18
+
19
+ key = C.read_private_key @@dsa_key_path
20
+ assert_equal File.read(@@dsa_key_path), key.to_pem
21
+ assert_equal OpenSSL::PKey::DSA, key.class
22
+ end
23
+
24
+ def test_read_known_hosts
25
+ keys = C.read_authorized_keys @@known_hosts_path
26
+ verify_authorized_keys keys
27
+ end
28
+
29
+ def test_read_authorized_keys
30
+ keys = C.read_authorized_keys @@authorized_keys_path
31
+ verify_authorized_keys keys
32
+ end
33
+
34
+ def verify_authorized_keys(keys)
35
+ assert_equal 4, keys.length
36
+ assert_equal [OpenSSL::PKey::RSA] * 3 + [OpenSSL::PKey::DSA],
37
+ keys.map { |k| k.class }
38
+ assert_equal C.read_private_key(@@rsa_key_path).public_key.to_pem,
39
+ keys[1].to_pem
40
+ assert_equal C.read_private_key(@@dsa_key_path).public_key.to_pem,
41
+ keys[3].to_pem
42
+ end
43
+
44
+ def test_key_fingerprint
45
+ keys = C.read_authorized_keys @@known_hosts_path
46
+
47
+ assert_equal 4, keys.map { |k| C.key_fingerprint k }.uniq.length
48
+ keys.each { |k| assert_equal C.key_fingerprint(k), C.key_fingerprint(k) }
49
+ end
50
+
51
+ def test_load_public_keys
52
+ keyset = C.load_public_keys @@known_hosts_path
53
+ rsa_key = C.read_private_key @@rsa_key_path
54
+ dsa_key = C.read_private_key @@dsa_key_path
55
+
56
+ assert_equal rsa_key.public_key.to_pem,
57
+ keyset[C.key_fingerprint(rsa_key)].to_pem
58
+ assert_equal dsa_key.public_key.to_pem,
59
+ keyset[C.key_fingerprint(dsa_key)].to_pem
60
+ end
61
+
62
+ def test_key_encryption
63
+ test_data = 'qwertyuiopasdfghjklzxcvbnm' * 2
64
+ rsa_key = C.read_private_key @@rsa_key_path
65
+ dsa_key = C.read_private_key @@rsa_key_path
66
+
67
+ [rsa_key, dsa_key].each do |key|
68
+ encrypted_data = C.encrypt_with_key key.public_key, test_data
69
+ decrypted_data = C.decrypt_with_key key, encrypted_data
70
+
71
+ assert_equal test_data, decrypted_data
72
+ 0.upto(test_data.length - 4) do |i|
73
+ assert !encrypted_data.index(test_data[i, 4]),
74
+ 'Encryption did not wipe the original pattern'
75
+ end
76
+ end
77
+ end
78
+
79
+ def test_encryption_depends_on_key
80
+ num_keys = 16
81
+ test_data = 'qwertyuiopasdfghjklzxcvbnm' * 2
82
+ keys = (0...num_keys).map { OpenSSL::PKey::RSA.generate 1024, 35 }
83
+ assert_equal num_keys, keys.map { |k| C.encrypt_with_key k, test_data }.
84
+ uniq.length
85
+ end
86
+
87
+ def test_hasher_random_keys_are_random
88
+ num_keys = 1024
89
+ assert_equal num_keys, (0...num_keys).map { C::Hasher.random_key }.
90
+ uniq.length
91
+ end
92
+
93
+ def test_hasher_random_keys_yield_random_results
94
+ num_keys = 1024
95
+ test_data = 'qwertyuiopasdfghjklzxcvbnm' * 2
96
+ assert_equal num_keys, (0...num_keys).map { C::Hasher.new.hash test_data }.
97
+ uniq.length
98
+ end
99
+
100
+ def test_hasher_hashes_are_finite
101
+ num_blocks = 64
102
+ block = 'qwertyuiopasdfghjklzxcvbnm'
103
+
104
+ hasher = C::Hasher.new
105
+ hash_length = hasher.hash('').length
106
+ 1.upto(num_blocks) { |n| assert_equal hash_length, hasher.hash(block * n).
107
+ length }
108
+ end
109
+
110
+ def test_hasher_has_state
111
+ num_blocks = 1024
112
+ block = 'qwertyuiopasdfghjklzxcvbnm'
113
+ hasher = C::Hasher.new
114
+
115
+ assert_equal num_blocks, (0...num_blocks).map { hasher.hash block}.uniq.
116
+ length
117
+ end
118
+
119
+ def test_hasher_is_reproducible
120
+ hasher = C::Hasher.new
121
+ hasher2 = C::Hasher.new hasher.key
122
+
123
+ num_blocks = 128
124
+ block = 'qwertyuiopasdfghjklzxcvbnm'
125
+ 1.upto(num_blocks) { assert_equal hasher.hash(block), hasher2.hash(block) }
126
+ end
127
+ end
@@ -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