coderrr-rtunnel 0.3.9 → 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 (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