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.
- 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
|