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,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
|
@@ -0,0 +1,351 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'eventmachine'
|
5
|
+
|
6
|
+
|
7
|
+
# The RTunnel server class, managing control and connection servers.
|
8
|
+
class RTunnel::Server
|
9
|
+
include RTunnel
|
10
|
+
include RTunnel::CommandProcessor
|
11
|
+
include RTunnel::Logging
|
12
|
+
include RTunnel::ConnectionId
|
13
|
+
|
14
|
+
attr_reader :control_address, :control_host, :control_port
|
15
|
+
attr_reader :keep_alive_interval, :authorized_keys
|
16
|
+
attr_reader :lowest_listen_port, :highest_listen_port
|
17
|
+
attr_reader :tunnel_connections
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
process_options options
|
21
|
+
@tunnel_controls = {}
|
22
|
+
@tunnel_connections = {}
|
23
|
+
@tunnel_connections_by_control = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def start
|
27
|
+
return if @control_listener
|
28
|
+
@control_host = SocketFactory.host_from_address @control_address
|
29
|
+
@control_port = SocketFactory.port_from_address @control_address
|
30
|
+
start_server
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
return unless @control_listener
|
35
|
+
EventMachine.stop_server @control_listener
|
36
|
+
@control_listener = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def start_server
|
40
|
+
D "Control server on #{@control_host} port #{@control_port}"
|
41
|
+
@control_listener = EventMachine.start_server @control_host, @control_port,
|
42
|
+
Server::ControlConnection,
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates a listener on a certain port. The given block should create and
|
47
|
+
# return the listener. If a listener is already active on the given port,
|
48
|
+
# the current listener is closed, and the new listener is created after the
|
49
|
+
# old listener is closed.
|
50
|
+
def create_tunnel_listener(listen_port, control_connection, &creation_block)
|
51
|
+
if old_control = @tunnel_controls[listen_port]
|
52
|
+
D "Closing old listener on port #{listen_port}"
|
53
|
+
EventMachine.stop_server old_control.listener
|
54
|
+
end
|
55
|
+
|
56
|
+
EventMachine.next_tick do
|
57
|
+
next unless yield
|
58
|
+
|
59
|
+
@tunnel_controls[listen_port] = control_connection
|
60
|
+
redirect_tunnel_connections old_control, control_connection if old_control
|
61
|
+
on_remote_listen
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Registers a tunnel connection, so it can receive data.
|
66
|
+
def register_tunnel_connection(connection)
|
67
|
+
@tunnel_connections[connection.connection_id] = connection
|
68
|
+
control_connection = connection.control_connection
|
69
|
+
@tunnel_connections_by_control[control_connection] ||= Set.new
|
70
|
+
@tunnel_connections_by_control[control_connection] << connection
|
71
|
+
end
|
72
|
+
|
73
|
+
# De-registers a tunnel connection.
|
74
|
+
def deregister_tunnel_connection(connection)
|
75
|
+
@tunnel_connections.delete connection.connection_id
|
76
|
+
control_connection = connection.control_connection
|
77
|
+
@tunnel_connections_by_control[control_connection].delete connection
|
78
|
+
end
|
79
|
+
|
80
|
+
def redirect_tunnel_connections(old_control, new_control)
|
81
|
+
return unless old_connections = @tunnel_connections_by_control[old_control]
|
82
|
+
old_connections.each do |tunnel_connection|
|
83
|
+
tunnel_connection.control_connection = new_control
|
84
|
+
end
|
85
|
+
@tunnel_connections_by_control[new_control] ||= Set.new
|
86
|
+
@tunnel_connections_by_control[new_control] += old_connections
|
87
|
+
end
|
88
|
+
|
89
|
+
def on_remote_listen(&block)
|
90
|
+
if block
|
91
|
+
@on_remote_listen = block
|
92
|
+
elsif @on_remote_listen
|
93
|
+
@on_remote_listen.call
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
## option processing
|
98
|
+
|
99
|
+
def process_options(options)
|
100
|
+
[:control_address, :keep_alive_interval, :authorized_keys,
|
101
|
+
:lowest_listen_port, :highest_listen_port].each do |opt|
|
102
|
+
instance_variable_set "@#{opt}".to_sym,
|
103
|
+
RTunnel::Server.send("extract_#{opt}".to_sym, options[opt])
|
104
|
+
end
|
105
|
+
|
106
|
+
init_log :level => options[:log_level]
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.extract_control_address(address)
|
110
|
+
return "0.0.0.0:#{RTunnel::DEFAULT_CONTROL_PORT}" unless address
|
111
|
+
if address =~ /^\d+$/
|
112
|
+
host = nil
|
113
|
+
port = address.to_i
|
114
|
+
else
|
115
|
+
host = SocketFactory.host_from_address address
|
116
|
+
port = SocketFactory.port_from_address address
|
117
|
+
end
|
118
|
+
host = RTunnel.resolve_address(host || "0.0.0.0")
|
119
|
+
port ||= RTunnel::DEFAULT_CONTROL_PORT.to_s
|
120
|
+
"#{host}:#{port}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.extract_keep_alive_interval(interval)
|
124
|
+
interval || RTunnel::KEEP_ALIVE_INTERVAL
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.extract_authorized_keys(keys_file)
|
128
|
+
keys_file and Crypto.load_public_keys keys_file
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.extract_lowest_listen_port(port)
|
132
|
+
port || 0
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.extract_highest_listen_port(port)
|
136
|
+
port || 65535
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# A client connection to the server's control port.
|
142
|
+
class RTunnel::Server::ControlConnection < EventMachine::Connection
|
143
|
+
include RTunnel
|
144
|
+
include RTunnel::CommandProcessor
|
145
|
+
include RTunnel::CommandProtocol
|
146
|
+
include RTunnel::Logging
|
147
|
+
|
148
|
+
attr_reader :server, :listener
|
149
|
+
|
150
|
+
def initialize(server)
|
151
|
+
super()
|
152
|
+
|
153
|
+
@server = server
|
154
|
+
@tunnel_connections = server.tunnel_connections
|
155
|
+
@listener = nil
|
156
|
+
@keep_alive_timer = nil
|
157
|
+
@keep_alive_interval = server.keep_alive_interval
|
158
|
+
@in_hasher = @out_hasher = nil
|
159
|
+
|
160
|
+
init_log :to => @server
|
161
|
+
end
|
162
|
+
|
163
|
+
def post_init
|
164
|
+
@client_port, @client_host = *Socket.unpack_sockaddr_in(get_peername)
|
165
|
+
D "Established connection with #{@client_host} port #{@client_port}"
|
166
|
+
enable_keep_alives
|
167
|
+
end
|
168
|
+
|
169
|
+
def unbind
|
170
|
+
D "Lost connection from #{@client_host} port #{@client_port}"
|
171
|
+
disable_keep_alives
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
## Command processing
|
176
|
+
|
177
|
+
def process_remote_listen(address)
|
178
|
+
listen_host = SocketFactory.host_from_address address
|
179
|
+
listen_port = SocketFactory.port_from_address address
|
180
|
+
|
181
|
+
unless validate_remote_listen listen_host, listen_port
|
182
|
+
send_command SetSessionKeyCommand.new('NO')
|
183
|
+
return
|
184
|
+
end
|
185
|
+
|
186
|
+
@server.create_tunnel_listener listen_port, self do
|
187
|
+
D "Creating listener for #{listen_host} port #{listen_port}"
|
188
|
+
begin
|
189
|
+
@listener = EventMachine.start_server listen_host, listen_port,
|
190
|
+
Server::TunnelConnection, self,
|
191
|
+
listen_host, listen_port
|
192
|
+
rescue RuntimeError => e
|
193
|
+
# EventMachine raises 'no acceptor' if the listen address is invalid
|
194
|
+
E "Invalid listen address #{listen_host}"
|
195
|
+
@listener = nil
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
D "Listening on #{listen_host} port #{listen_port}"
|
200
|
+
end
|
201
|
+
|
202
|
+
# Verifies if a RemoteListenCommand should be honored.
|
203
|
+
def validate_remote_listen(host, port)
|
204
|
+
if @server.authorized_keys and @out_hasher.nil?
|
205
|
+
D "Asked to open listen socket by unauthorized client"
|
206
|
+
return false
|
207
|
+
end
|
208
|
+
|
209
|
+
if port < @server.lowest_listen_port or port > @server.highest_listen_port
|
210
|
+
D "Asked to listen to forbidden port"
|
211
|
+
return false
|
212
|
+
end
|
213
|
+
|
214
|
+
true
|
215
|
+
end
|
216
|
+
|
217
|
+
def process_send_data(tunnel_connection_id, data)
|
218
|
+
tunnel_connection = @tunnel_connections[tunnel_connection_id]
|
219
|
+
if tunnel_connection
|
220
|
+
D "Data: #{data.length} bytes coming from #{tunnel_connection_id}"
|
221
|
+
tunnel_connection.send_data data
|
222
|
+
else
|
223
|
+
W "Asked to send to unknown connection #{tunnel_connection_id}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def process_close_connection(tunnel_connection_id)
|
228
|
+
tunnel_connection = @tunnel_connections[tunnel_connection_id]
|
229
|
+
if tunnel_connection
|
230
|
+
D "Closed from tunneled end: #{tunnel_connection_id}"
|
231
|
+
tunnel_connection.close_from_tunnel
|
232
|
+
else
|
233
|
+
W "Asked to close unknown connection #{tunnel_connection_id}"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def process_generate_session_key(public_key_fp)
|
238
|
+
if @server.authorized_keys
|
239
|
+
if public_key = @server.authorized_keys[public_key_fp]
|
240
|
+
D "Authorized client key received, generating session key"
|
241
|
+
@out_hasher, @in_hasher = Crypto::Hasher.new, Crypto::Hasher.new
|
242
|
+
|
243
|
+
iokeys = StringIO.new
|
244
|
+
iokeys.write_varstring @in_hasher.key
|
245
|
+
iokeys.write_varstring @out_hasher.key
|
246
|
+
encrypted_keys = Crypto.encrypt_with_key public_key, iokeys.string
|
247
|
+
else
|
248
|
+
D("Rejecting unauthorized client key (%s authorized keys)" %
|
249
|
+
@server.authorized_keys.length)
|
250
|
+
encrypted_keys = 'NO'
|
251
|
+
end
|
252
|
+
else
|
253
|
+
D "Asked to generate session key, but no authorized keys set"
|
254
|
+
encrypted_keys = ''
|
255
|
+
end
|
256
|
+
send_command SetSessionKeyCommand.new(encrypted_keys)
|
257
|
+
self.incoming_command_hasher = @in_hasher if @in_hasher
|
258
|
+
self.outgoing_command_hasher = @out_hasher if @out_hasher
|
259
|
+
end
|
260
|
+
|
261
|
+
def receive_bad_frame(frame, exception)
|
262
|
+
case exception
|
263
|
+
when :bad_signature
|
264
|
+
D "Ignoring command with invalid signature"
|
265
|
+
when Exception
|
266
|
+
D "Ignoring malformed command."
|
267
|
+
D "Decoding exception: #{exception.class.name} - #{exception}\n" +
|
268
|
+
"#{exception.backtrace.join("\n")}\n"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
## Keep-Alives (preventing timeouts)
|
274
|
+
|
275
|
+
#:nodoc:
|
276
|
+
def send_command(command)
|
277
|
+
@last_command_time = Time.now
|
278
|
+
super
|
279
|
+
end
|
280
|
+
|
281
|
+
# Enables sending KeepAliveCommands every few seconds.
|
282
|
+
def enable_keep_alives
|
283
|
+
@last_command_time = Time.now
|
284
|
+
@keep_alive_timer =
|
285
|
+
EventMachine::PeriodicTimer.new(@keep_alive_interval / 2) do
|
286
|
+
keep_alive_if_needed
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Sends a KeepAlive command if no command was sent recently.
|
291
|
+
def keep_alive_if_needed
|
292
|
+
if Time.now - @last_command_time >= @keep_alive_interval
|
293
|
+
send_command KeepAliveCommand.new
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Disables sending KeepAlives.
|
298
|
+
def disable_keep_alives
|
299
|
+
return unless @keep_alive_timer
|
300
|
+
@keep_alive_timer.cancel
|
301
|
+
@keep_alive_timer = nil
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
# A connection to a tunnelled port.
|
307
|
+
class RTunnel::Server::TunnelConnection < EventMachine::Connection
|
308
|
+
include RTunnel
|
309
|
+
include RTunnel::Logging
|
310
|
+
|
311
|
+
attr_reader :connection_id
|
312
|
+
attr_accessor :control_connection
|
313
|
+
|
314
|
+
def initialize(control_connection, listen_host, listen_port)
|
315
|
+
# listen_host and listen_port are passed for logging purposes only
|
316
|
+
@listen_host = listen_host
|
317
|
+
@listen_port = listen_port
|
318
|
+
@control_connection = control_connection
|
319
|
+
@server = @control_connection.server
|
320
|
+
@hasher = nil
|
321
|
+
|
322
|
+
init_log :to => @server
|
323
|
+
end
|
324
|
+
|
325
|
+
def post_init
|
326
|
+
@connection_id = @server.new_connection_id
|
327
|
+
peer = Socket.unpack_sockaddr_in(get_peername).reverse.join ':'
|
328
|
+
D "Tunnel connection from #{peer} on #{@connection_id}"
|
329
|
+
@server.register_tunnel_connection self
|
330
|
+
@control_connection.send_command CreateConnectionCommand.new(@connection_id)
|
331
|
+
end
|
332
|
+
|
333
|
+
def unbind
|
334
|
+
unless @tunnel_closed
|
335
|
+
D "Closed from client end: #{@connection_id}"
|
336
|
+
close_command = CloseConnectionCommand.new(@connection_id)
|
337
|
+
@control_connection.send_command close_command
|
338
|
+
end
|
339
|
+
@server.deregister_tunnel_connection self
|
340
|
+
end
|
341
|
+
|
342
|
+
def close_from_tunnel
|
343
|
+
@tunnel_closed = true
|
344
|
+
close_connection_after_writing
|
345
|
+
end
|
346
|
+
|
347
|
+
def receive_data(data)
|
348
|
+
D "Data: #{data.length} bytes for #{@connection_id}"
|
349
|
+
@control_connection.send_command SendDataCommand.new(@connection_id, data)
|
350
|
+
end
|
351
|
+
end
|