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.
- data/CHANGELOG +13 -0
- data/LICENSE +21 -0
- data/Manifest +48 -0
- data/README.markdown +40 -15
- data/Rakefile +31 -4
- data/bin/rtunnel_client +2 -1
- data/bin/rtunnel_server +2 -1
- data/lib/rtunnel.rb +20 -0
- data/lib/rtunnel/client.rb +308 -0
- data/lib/rtunnel/command_processor.rb +62 -0
- data/lib/rtunnel/command_protocol.rb +50 -0
- data/lib/rtunnel/commands.rb +233 -0
- data/lib/rtunnel/connection_id.rb +24 -0
- data/lib/rtunnel/core.rb +58 -0
- data/lib/rtunnel/crypto.rb +106 -0
- data/lib/rtunnel/frame_protocol.rb +34 -0
- data/lib/rtunnel/io_extensions.rb +54 -0
- data/lib/rtunnel/leak.rb +35 -0
- data/lib/rtunnel/rtunnel_client_cmd.rb +41 -0
- data/lib/rtunnel/rtunnel_server_cmd.rb +32 -0
- data/lib/rtunnel/server.rb +351 -0
- data/lib/rtunnel/socket_factory.rb +119 -0
- data/test/command_stubs.rb +77 -0
- data/test/protocol_mocks.rb +43 -0
- data/test/scenario_connection.rb +109 -0
- data/test/test_client.rb +48 -0
- data/test/test_command_protocol.rb +82 -0
- data/test/test_commands.rb +49 -0
- data/test/test_connection_id.rb +30 -0
- data/test/test_crypto.rb +127 -0
- data/test/test_frame_protocol.rb +109 -0
- data/test/test_io_extensions.rb +70 -0
- data/test/test_server.rb +70 -0
- data/test/test_socket_factory.rb +42 -0
- data/test/test_tunnel.rb +186 -0
- data/test_data/authorized_keys2 +4 -0
- data/test_data/known_hosts +4 -0
- data/test_data/random_rsa_key +27 -0
- data/test_data/ssh_host_dsa_key +12 -0
- data/test_data/ssh_host_rsa_key +27 -0
- data/tests/_ab_test.rb +16 -0
- data/tests/_stress_test.rb +96 -0
- data/tests/lo_http_server.rb +55 -0
- metadata +67 -27
- data/ab_test.rb +0 -23
- data/lib/client.rb +0 -150
- data/lib/cmds.rb +0 -166
- data/lib/core.rb +0 -58
- data/lib/rtunnel_client_cmd.rb +0 -23
- data/lib/rtunnel_server_cmd.rb +0 -18
- data/lib/server.rb +0 -197
- data/rtunnel.gemspec +0 -18
- data/rtunnel_client.rb +0 -3
- data/rtunnel_server.rb +0 -3
- 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
|
data/test/test_crypto.rb
ADDED
@@ -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
|
data/test/test_server.rb
ADDED
@@ -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
|