rtunnel 0.3.8 → 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 +84 -0
- data/Rakefile +45 -0
- data/bin/rtunnel_client +2 -1
- data/bin/rtunnel_server +2 -1
- 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/lib/rtunnel.rb +20 -0
- data/rtunnel.gemspec +51 -0
- data/spec/client_spec.rb +47 -0
- data/spec/cmds_spec.rb +127 -0
- data/spec/integration_spec.rb +105 -0
- data/spec/server_spec.rb +21 -0
- data/spec/spec_helper.rb +3 -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 +127 -31
- data/History.txt +0 -3
- data/Manifest.txt +0 -13
- data/README.txt +0 -362
- data/lib/client.rb +0 -185
- data/lib/cmds.rb +0 -166
- data/lib/core.rb +0 -53
- data/lib/rtunnel_client_cmd.rb +0 -25
- data/lib/rtunnel_server_cmd.rb +0 -20
- data/lib/server.rb +0 -181
- data/rtunnel_client.rb +0 -3
- data/rtunnel_server.rb +0 -3
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
class RTunnel::Command
|
4
|
+
# Associates command codes with the classes implementing them.
|
5
|
+
class Registry
|
6
|
+
def initialize
|
7
|
+
@classes = {}
|
8
|
+
@codes = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(klass, command_code)
|
12
|
+
if @codes.has_key? command_code
|
13
|
+
raise "Command code #{command_code} already used for #{@codes[command_code].name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
@codes[klass] = command_code
|
17
|
+
@classes[command_code] = klass
|
18
|
+
end
|
19
|
+
|
20
|
+
def class_for(command_code)
|
21
|
+
@classes[command_code]
|
22
|
+
end
|
23
|
+
|
24
|
+
def code_for(klass)
|
25
|
+
@codes[klass]
|
26
|
+
end
|
27
|
+
|
28
|
+
def codes_and_classes
|
29
|
+
ret_val = []
|
30
|
+
@codes.each { |klass, code| ret_val << [code, klass.name] }
|
31
|
+
ret_val.sort!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
@@registry = Registry.new
|
36
|
+
def self.registry
|
37
|
+
@@registry
|
38
|
+
end
|
39
|
+
|
40
|
+
# subclasses must call this to register and declare their command code
|
41
|
+
def self.command_code(code)
|
42
|
+
registry.register self, code
|
43
|
+
end
|
44
|
+
|
45
|
+
# subclasses should override this (and add to the result)
|
46
|
+
# to provide a debug string
|
47
|
+
def to_s
|
48
|
+
self.class.name
|
49
|
+
end
|
50
|
+
|
51
|
+
# subclasses should override this and call super
|
52
|
+
# before performing their own initialization
|
53
|
+
def initialize_from_io(io)
|
54
|
+
return self
|
55
|
+
end
|
56
|
+
|
57
|
+
# Encode this command to a IO / IOString.
|
58
|
+
def encode(io)
|
59
|
+
io.write RTunnel::Command.registry.code_for(self.class)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Produce a string with an encoding of this command.
|
63
|
+
def to_encoded_str
|
64
|
+
string_io = StringIO.new
|
65
|
+
self.encode string_io
|
66
|
+
string_io.string
|
67
|
+
end
|
68
|
+
|
69
|
+
# Decode a Command instance from a IO / IOString.
|
70
|
+
def self.decode(io)
|
71
|
+
return nil unless code = io.getc
|
72
|
+
klass = registry.class_for code.chr
|
73
|
+
return nil unless klass
|
74
|
+
|
75
|
+
command = klass.new
|
76
|
+
command.initialize_from_io io
|
77
|
+
end
|
78
|
+
|
79
|
+
# Printable string containing all the codes and their classes.
|
80
|
+
def self.printable_codes
|
81
|
+
printable = ''
|
82
|
+
registry.codes_and_classes.each do |code_and_class|
|
83
|
+
printable << "#{code_and_class.first}: #{code_and_class.last}\n"
|
84
|
+
end
|
85
|
+
return printable
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class RTunnel::ConnectionCommand < RTunnel::Command
|
90
|
+
attr_reader :connection_id
|
91
|
+
|
92
|
+
def initialize(connection_id = nil)
|
93
|
+
super()
|
94
|
+
@connection_id = connection_id
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_s
|
98
|
+
super + "/id=#{connection_id}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize_from_io(io)
|
102
|
+
super
|
103
|
+
@connection_id = io.read_varstring
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
def encode(io)
|
108
|
+
super
|
109
|
+
io.write_varstring @connection_id
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class RTunnel::CreateConnectionCommand < RTunnel::ConnectionCommand
|
114
|
+
command_code 'C'
|
115
|
+
end
|
116
|
+
|
117
|
+
class RTunnel::CloseConnectionCommand < RTunnel::ConnectionCommand
|
118
|
+
command_code 'X'
|
119
|
+
end
|
120
|
+
|
121
|
+
class RTunnel::SendDataCommand < RTunnel::ConnectionCommand
|
122
|
+
command_code 'D'
|
123
|
+
|
124
|
+
attr_reader :data
|
125
|
+
|
126
|
+
def initialize(connection_id = nil, data = nil)
|
127
|
+
super(connection_id)
|
128
|
+
@data = data
|
129
|
+
end
|
130
|
+
|
131
|
+
def initialize_from_io(io)
|
132
|
+
super
|
133
|
+
@data = io.read_varstring
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_s
|
138
|
+
super + "/data=#{data}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def encode(io)
|
142
|
+
super
|
143
|
+
io.write_varstring @data
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class RTunnel::RemoteListenCommand < RTunnel::Command
|
148
|
+
command_code 'L'
|
149
|
+
|
150
|
+
attr_reader :address
|
151
|
+
|
152
|
+
def initialize(address = nil)
|
153
|
+
super()
|
154
|
+
@address = address
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_s
|
158
|
+
super + "/address=#{address}"
|
159
|
+
end
|
160
|
+
|
161
|
+
def initialize_from_io(io)
|
162
|
+
super
|
163
|
+
@address = io.read_varstring
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
def encode(io)
|
168
|
+
super
|
169
|
+
io.write_varstring address
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class RTunnel::KeepAliveCommand < RTunnel::Command
|
174
|
+
command_code 'A'
|
175
|
+
|
176
|
+
def initialize_from_io(io)
|
177
|
+
super
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class RTunnel::GenerateSessionKeyCommand < RTunnel::Command
|
182
|
+
command_code 'S'
|
183
|
+
|
184
|
+
attr_reader :public_key_fp
|
185
|
+
|
186
|
+
def initialize(public_key_fp = nil)
|
187
|
+
super()
|
188
|
+
@public_key_fp = public_key_fp
|
189
|
+
end
|
190
|
+
|
191
|
+
def to_s
|
192
|
+
super + "/pubkey_fp=#{@public_key_fp.inspect}"
|
193
|
+
end
|
194
|
+
|
195
|
+
def initialize_from_io(io)
|
196
|
+
super
|
197
|
+
@public_key_fp = io.read_varstring
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
def encode(io)
|
202
|
+
super
|
203
|
+
io.write_varstring @public_key_fp
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class RTunnel::SetSessionKeyCommand < RTunnel::Command
|
208
|
+
command_code 'K'
|
209
|
+
|
210
|
+
attr_reader :encrypted_keys
|
211
|
+
|
212
|
+
def initialize(encrypted_keys = nil)
|
213
|
+
super()
|
214
|
+
@encrypted_keys = encrypted_keys
|
215
|
+
end
|
216
|
+
|
217
|
+
def to_s
|
218
|
+
super + "/enc_keys=#{@encrypted_key.inspect}"
|
219
|
+
end
|
220
|
+
|
221
|
+
def initialize_from_io(io)
|
222
|
+
super
|
223
|
+
@encrypted_keys = io.read_varstring
|
224
|
+
self
|
225
|
+
end
|
226
|
+
|
227
|
+
def encode(io)
|
228
|
+
super
|
229
|
+
io.write_varstring @encrypted_keys
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# TODO(not_me): this file (and its tests) cry for a DSL
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
# Unique ID generation functionality.
|
5
|
+
module RTunnel::ConnectionId
|
6
|
+
def self.new_cipher
|
7
|
+
cipher = OpenSSL::Cipher::Cipher.new 'aes-128-ecb'
|
8
|
+
cipher.encrypt
|
9
|
+
cipher.key, cipher.iv = cipher.random_key, cipher.random_iv
|
10
|
+
cipher
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.new_counter
|
14
|
+
'0' * 16
|
15
|
+
end
|
16
|
+
|
17
|
+
def new_connection_id
|
18
|
+
@session_id_cipher ||= RTunnel::ConnectionId.new_cipher
|
19
|
+
@session_id_counter ||= RTunnel::ConnectionId.new_counter
|
20
|
+
connection_id = @session_id_cipher.update @session_id_counter
|
21
|
+
@session_id_counter.succ!
|
22
|
+
Base64.encode64(connection_id).strip
|
23
|
+
end
|
24
|
+
end
|
data/lib/rtunnel/core.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module RTunnel
|
4
|
+
DEFAULT_CONTROL_PORT = 19050
|
5
|
+
TUNNEL_TIMEOUT = 10
|
6
|
+
KEEP_ALIVE_INTERVAL = 2
|
7
|
+
|
8
|
+
class AbortProgramException < Exception
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module RTunnel::Logging
|
14
|
+
def init_log(options = {})
|
15
|
+
# TODO(costan): parse logging options
|
16
|
+
if options[:to]
|
17
|
+
@log = options[:to].instance_variable_get(:@log).dup
|
18
|
+
else
|
19
|
+
@log = Logger.new(STDERR)
|
20
|
+
@log.level = Logger::ERROR
|
21
|
+
end
|
22
|
+
if options[:level]
|
23
|
+
@log.level = Logger::const_get(options[:level].upcase.to_sym)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def D(message)
|
28
|
+
@log.debug message
|
29
|
+
end
|
30
|
+
|
31
|
+
def W(message)
|
32
|
+
@log.warn message
|
33
|
+
end
|
34
|
+
|
35
|
+
def I(message)
|
36
|
+
@log.info message
|
37
|
+
end
|
38
|
+
|
39
|
+
def E(message)
|
40
|
+
@log.error message
|
41
|
+
end
|
42
|
+
|
43
|
+
def F(message)
|
44
|
+
@log.fatal message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module RTunnel
|
49
|
+
# Resolve the given address to an IP.
|
50
|
+
# The address can have the following formats: host; host:port; ip; ip:port;
|
51
|
+
def self.resolve_address(address, timeout_sec = 5)
|
52
|
+
host, rest = address.split(':', 2)
|
53
|
+
ip = timeout(timeout_sec) { Resolv.getaddress(host) }
|
54
|
+
rest ? "#{ip}:#{rest}" : ip
|
55
|
+
rescue Exception
|
56
|
+
raise AbortProgramException, "Error resolving #{host}"
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
require 'openssl'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'net/ssh'
|
7
|
+
|
8
|
+
module RTunnel::Crypto
|
9
|
+
# Reads all the keys from an openssh known_hosts or authorized_keys2 file.
|
10
|
+
def self.read_authorized_keys(file_name)
|
11
|
+
keys = []
|
12
|
+
File.read(file_name).each_line do |line|
|
13
|
+
pubkey_match = /ssh-\w*\s*(\S*)/.match line
|
14
|
+
next unless pubkey_match
|
15
|
+
pubkey_blob = pubkey_match[1].unpack('m*').first
|
16
|
+
keys << Net::SSH::Buffer.new(pubkey_blob).read_key
|
17
|
+
end
|
18
|
+
keys
|
19
|
+
end
|
20
|
+
|
21
|
+
# Loads a private key from an openssh key file.
|
22
|
+
def self.read_private_key(file_name)
|
23
|
+
Net::SSH::KeyFactory.load_private_key file_name
|
24
|
+
end
|
25
|
+
|
26
|
+
# Computes a string that represents the key. Different keys should
|
27
|
+
# map out to different fingerprints.
|
28
|
+
def self.key_fingerprint(key)
|
29
|
+
key.public_key.to_der
|
30
|
+
end
|
31
|
+
|
32
|
+
# Encrypts some data with a public key. The matching private key will be
|
33
|
+
# required to decrypt the data.
|
34
|
+
def self.encrypt_with_key(key, data)
|
35
|
+
if key.kind_of? OpenSSL::PKey::RSA
|
36
|
+
key.public_encrypt data, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
37
|
+
elsif key.kind_of? OpenSSL::PKey::DSA
|
38
|
+
key.public_encrypt encrypted_data
|
39
|
+
else
|
40
|
+
raise 'Unsupported key type'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Decrypts data that was previously encrypted with encrypt_with_key.
|
45
|
+
def self.decrypt_with_key(key, encrypted_data)
|
46
|
+
if key.kind_of? OpenSSL::PKey::RSA
|
47
|
+
key.private_decrypt encrypted_data, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
48
|
+
elsif key.kind_of? OpenSSL::PKey::DSA
|
49
|
+
key.private_decrypt encrypted_data
|
50
|
+
else
|
51
|
+
raise 'Unsupported key type'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Loads public keys to be used by a server.
|
56
|
+
def self.load_public_keys(file_name)
|
57
|
+
key_list = read_authorized_keys file_name
|
58
|
+
RTunnel::Crypto::KeySet.new key_list
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# A set of keys used by a server to authenticate clients.
|
63
|
+
class RTunnel::Crypto::KeySet
|
64
|
+
def initialize(key_list)
|
65
|
+
@keys_by_fp = {}
|
66
|
+
key_list.each { |k| @keys_by_fp[RTunnel::Crypto.key_fingerprint(k)] = k }
|
67
|
+
end
|
68
|
+
|
69
|
+
def [](key_fp)
|
70
|
+
@keys_by_fp[key_fp]
|
71
|
+
end
|
72
|
+
|
73
|
+
def length
|
74
|
+
@keys_by_fp.length
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# A cryptographically secure hasher. Instances will hash the data
|
79
|
+
class RTunnel::Crypto::Hasher
|
80
|
+
attr_reader :key
|
81
|
+
|
82
|
+
def initialize(key = nil)
|
83
|
+
@key = key || RTunnel::Crypto::Hasher.random_key
|
84
|
+
@cipher = OpenSSL::Cipher::Cipher.new 'aes-128-cbc'
|
85
|
+
@cipher.encrypt
|
86
|
+
iokey = StringIO.new @key
|
87
|
+
@cipher.key = iokey.read_varstring
|
88
|
+
@cipher.iv = iokey.read_varstring
|
89
|
+
end
|
90
|
+
|
91
|
+
# Creates a hash for the given data. Warning: this method is not idempotent.
|
92
|
+
# The intent is that the same hash can be produced by another hasher that is
|
93
|
+
# initialized with the same key and has been fed the same data.
|
94
|
+
def hash(data)
|
95
|
+
@cipher.update Digest::SHA2.digest(data)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Produces a random key for the hasher.
|
99
|
+
def self.random_key
|
100
|
+
cipher = OpenSSL::Cipher::Cipher.new 'aes-128-cbc'
|
101
|
+
iokey = StringIO.new
|
102
|
+
iokey.write_varstring cipher.random_key
|
103
|
+
iokey.write_varstring cipher.random_iv
|
104
|
+
iokey.string
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# eventmachine protocol
|
2
|
+
module RTunnel::FrameProtocol
|
3
|
+
def receive_data(data)
|
4
|
+
@frame_size_buffer ||= ''
|
5
|
+
|
6
|
+
i = 0
|
7
|
+
loop do
|
8
|
+
while @frame_buffer.nil? and i < data.size
|
9
|
+
@frame_size_buffer << data[i]
|
10
|
+
if (data[i] & 0x80) == 0
|
11
|
+
@remaining_frame_size = StringIO.new(@frame_size_buffer).read_varsize
|
12
|
+
@frame_buffer = ''
|
13
|
+
end
|
14
|
+
i += 1
|
15
|
+
end
|
16
|
+
|
17
|
+
return if @frame_buffer.nil?
|
18
|
+
break if @remaining_frame_size > data.size - i
|
19
|
+
|
20
|
+
receive_frame(@frame_buffer + data[i, @remaining_frame_size])
|
21
|
+
@frame_size_buffer, @frame_buffer = '', nil
|
22
|
+
i += @remaining_frame_size
|
23
|
+
end
|
24
|
+
|
25
|
+
@frame_buffer << data[i..-1]
|
26
|
+
@remaining_frame_size -= data.size-i
|
27
|
+
end
|
28
|
+
|
29
|
+
def send_frame(frame_data)
|
30
|
+
size_str = StringIO.new
|
31
|
+
size_str.write_varsize(frame_data.length)
|
32
|
+
send_data(size_str.string + frame_data)
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
class RTunnel::TruncatedDataError < Exception; end
|
4
|
+
|
5
|
+
module RTunnel::IOExtensions
|
6
|
+
# writes a size (non-negative Integer) to the stream using a varint encoding
|
7
|
+
def write_varsize(size)
|
8
|
+
chars = []
|
9
|
+
loop do
|
10
|
+
size, char = size.divmod(0x80)
|
11
|
+
chars << (char | ((size > 0) ? 0x80 : 0))
|
12
|
+
break if size == 0
|
13
|
+
end
|
14
|
+
write chars.pack('C*')
|
15
|
+
end
|
16
|
+
|
17
|
+
# reads a size (non-negative Integer) from the stream using a varint encoding
|
18
|
+
def read_varsize
|
19
|
+
size = 0
|
20
|
+
multiplier = 1
|
21
|
+
loop do
|
22
|
+
char = getc
|
23
|
+
# TODO(costan): better exception
|
24
|
+
unless char
|
25
|
+
raise RTunnel::TruncatedDataError.new("Encoded varsize truncated")
|
26
|
+
end
|
27
|
+
more, size_add = char.divmod(0x80)
|
28
|
+
size += size_add * multiplier
|
29
|
+
break if more == 0
|
30
|
+
multiplier *= 0x80
|
31
|
+
end
|
32
|
+
size
|
33
|
+
end
|
34
|
+
|
35
|
+
# writes a string and its length, so it can later be read with read_varstr
|
36
|
+
def write_varstring(str)
|
37
|
+
write_varsize str.length
|
38
|
+
write str
|
39
|
+
end
|
40
|
+
|
41
|
+
# reads a variable-length string that was previously written with write_varstr
|
42
|
+
def read_varstring
|
43
|
+
length = read_varsize
|
44
|
+
return '' if length == 0
|
45
|
+
str = read(length)
|
46
|
+
if ! str or str.length != length
|
47
|
+
raise RTunnel::TruncatedDataError, "Encoded varstring truncated"
|
48
|
+
end
|
49
|
+
str
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
IO.send :include, RTunnel::IOExtensions
|
54
|
+
StringIO.send :include, RTunnel::IOExtensions
|
data/lib/rtunnel/leak.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
class RTunnel::LeakTracker
|
4
|
+
# TODO(not_me): update this to eventmachine if it's still interesting
|
5
|
+
def self.start
|
6
|
+
logged_thread do
|
7
|
+
sleep 10
|
8
|
+
begin
|
9
|
+
objects = Hash.new 0
|
10
|
+
|
11
|
+
while true
|
12
|
+
last_objects = objects.dup
|
13
|
+
ObjectSpace.each_object do |o|
|
14
|
+
objects[o.class] += 1
|
15
|
+
end
|
16
|
+
|
17
|
+
objects.reject!{|k,v| ! last_objects.has_key? k } unless last_objects.empty?
|
18
|
+
|
19
|
+
new_objects = objects.dup
|
20
|
+
objects.each do |(klass, count)|
|
21
|
+
new_objects.delete klass if count < last_objects[klass] # has been GC'ed, "cant be leaking"
|
22
|
+
end
|
23
|
+
objects = new_objects
|
24
|
+
|
25
|
+
PP.pp objects.sort_by{|(k,cnt)| cnt }.reverse[0..10], STDERR
|
26
|
+
|
27
|
+
sleep 10
|
28
|
+
end
|
29
|
+
rescue Object
|
30
|
+
STDERR.puts $!.inspect
|
31
|
+
STDERR.puts $!.backtrace.join("\n")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'eventmachine'
|
5
|
+
|
6
|
+
|
7
|
+
module RTunnel
|
8
|
+
def self.run_client
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
(opts = OptionParser.new do |o|
|
12
|
+
o.on("-c", "--control-address ADDRESS") do |a|
|
13
|
+
options[:control_address] = a
|
14
|
+
end
|
15
|
+
o.on("-f", "--remote-listen-port ADDRESS") do |a|
|
16
|
+
options[:remote_listen_address] = a
|
17
|
+
end
|
18
|
+
o.on("-t", "--tunnel-to ADDRESS") do |a|
|
19
|
+
options[:tunnel_to_address] = a
|
20
|
+
end
|
21
|
+
o.on("-k", "--private-key KEYFILE") do |f|
|
22
|
+
options[:private_key] = f
|
23
|
+
end
|
24
|
+
o.on("-l", "--log-level LEVEL") do |l|
|
25
|
+
options[:log_level] = l
|
26
|
+
end
|
27
|
+
o.on("-o", "--timeout TIMEOUT_IN_SECONDS") do |t|
|
28
|
+
options[:tunnel_timeout] = t.to_f
|
29
|
+
end
|
30
|
+
end).parse! rescue (puts opts; return)
|
31
|
+
|
32
|
+
mandatory_keys = [:control_address, :remote_listen_address,
|
33
|
+
:tunnel_to_address]
|
34
|
+
|
35
|
+
(puts opts; return) unless mandatory_keys.all? { |key| options[key] }
|
36
|
+
|
37
|
+
EventMachine::run do
|
38
|
+
RTunnel::Client.new(options).start
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'eventmachine'
|
5
|
+
|
6
|
+
|
7
|
+
module RTunnel
|
8
|
+
def self.run_server
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
(opts = OptionParser.new do |o|
|
12
|
+
o.on("-c", "--control ADDRESS") { |a| options[:control_address] = a }
|
13
|
+
o.on("-a", "--authorized-keys KEYSFILE") do |f|
|
14
|
+
options[:authorized_keys] = f
|
15
|
+
end
|
16
|
+
o.on("-l", "--log-level LEVEL") { |l| options[:log_level] = l }
|
17
|
+
o.on("-k", "--keep-alive KEEP_ALIVE_INTERVAL") do |t|
|
18
|
+
options[:keep_alive_interval] = t.to_f
|
19
|
+
end
|
20
|
+
o.on("-p", "--lowest-listen-port PORT") do |p|
|
21
|
+
options[:lowest_listen_port] = p.to_i
|
22
|
+
end
|
23
|
+
o.on("-P", "--highest-listen-port PORT") do |p|
|
24
|
+
options[:highest_listen_port] = p.to_i
|
25
|
+
end
|
26
|
+
end).parse! rescue (puts opts; return)
|
27
|
+
|
28
|
+
EventMachine::run do
|
29
|
+
RTunnel::Server.new(options).start
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|