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
data/CHANGELOG
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
v0.4.0. Re-packaged as gem using echoe. Eventmachine. Client authentication.
|
2
|
+
|
3
|
+
v0.3.9. Cleanup and removed dependencies.
|
4
|
+
|
5
|
+
v0.3.6. New protocol. Available as gem for easier installation.
|
6
|
+
|
7
|
+
v0.2.1. Minor bugfix, cmdline options change.
|
8
|
+
|
9
|
+
v0.2.0. Much simpler
|
10
|
+
|
11
|
+
v0.1.2. Created rtunnel_server binary for linux so you don't need Ruby installed on the host you want to reverse tunnel from.
|
12
|
+
|
13
|
+
v0.1.1. Added default control port of 19050, no longer have to specify this on client or server unless you care to change it.
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2008 coderrr, Victor Costan
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
LICENSE
|
3
|
+
Manifest
|
4
|
+
README.markdown
|
5
|
+
Rakefile
|
6
|
+
bin/rtunnel_client
|
7
|
+
bin/rtunnel_server
|
8
|
+
lib/rtunnel.rb
|
9
|
+
lib/rtunnel/client.rb
|
10
|
+
lib/rtunnel/command_protocol.rb
|
11
|
+
lib/rtunnel/commands.rb
|
12
|
+
lib/rtunnel/core.rb
|
13
|
+
lib/rtunnel/crypto.rb
|
14
|
+
lib/rtunnel/frame_protocol.rb
|
15
|
+
lib/rtunnel/io_extensions.rb
|
16
|
+
lib/rtunnel/leak.rb
|
17
|
+
lib/rtunnel/rtunnel_client_cmd.rb
|
18
|
+
lib/rtunnel/rtunnel_server_cmd.rb
|
19
|
+
lib/rtunnel/server.rb
|
20
|
+
lib/rtunnel/socket_factory.rb
|
21
|
+
lib/rtunnel/connection_id.rb
|
22
|
+
lib/rtunnel/command_processor.rb
|
23
|
+
spec/client_spec.rb
|
24
|
+
spec/cmds_spec.rb
|
25
|
+
spec/integration_spec.rb
|
26
|
+
spec/server_spec.rb
|
27
|
+
spec/spec_helper.rb
|
28
|
+
test/command_stubs.rb
|
29
|
+
test/protocol_mocks.rb
|
30
|
+
test/scenario_connection.rb
|
31
|
+
test/test_client.rb
|
32
|
+
test/test_command_protocol.rb
|
33
|
+
test/test_commands.rb
|
34
|
+
test/test_crypto.rb
|
35
|
+
test/test_frame_protocol.rb
|
36
|
+
test/test_io_extensions.rb
|
37
|
+
test/test_server.rb
|
38
|
+
test/test_socket_factory.rb
|
39
|
+
test/test_tunnel.rb
|
40
|
+
test/test_connection_id.rb
|
41
|
+
test_data/known_hosts
|
42
|
+
test_data/ssh_host_rsa_key
|
43
|
+
test_data/random_rsa_key
|
44
|
+
test_data/ssh_host_dsa_key
|
45
|
+
test_data/authorized_keys2
|
46
|
+
tests/_ab_test.rb
|
47
|
+
tests/_stress_test.rb
|
48
|
+
tests/lo_http_server.rb
|
data/README.markdown
CHANGED
@@ -3,9 +3,13 @@ INSTALL
|
|
3
3
|
|
4
4
|
on server and local machine:
|
5
5
|
|
6
|
-
`gem
|
6
|
+
`gem sources -a http://gems.github.com`
|
7
7
|
|
8
|
-
|
8
|
+
`sudo gem install coderrr-rtunnel`
|
9
|
+
|
10
|
+
If you don't have root access, rubygems will install the gem into your `~/.gem` directory. If you install it like this, make sure you add the gems executable dir to your path.
|
11
|
+
|
12
|
+
`export PATH=$PATH:~/.gem/ruby/1.8/bin`
|
9
13
|
|
10
14
|
USAGE
|
11
15
|
-
|
@@ -20,30 +24,51 @@ on your local machine:
|
|
20
24
|
|
21
25
|
This would reverse tunnel myserver.com:4000 to localhost:3000 so that if you had a web server running at port 3000 on your local machine, anyone on the internet could access it by going to http://myserver.com:4000
|
22
26
|
|
23
|
-
**
|
27
|
+
**Logging (verbosity) level**
|
28
|
+
|
29
|
+
Both the server and the client support 4 levels of logging - 'debug', 'info', 'warn', 'error'. The -l parameter sets the logging level. The default level is 'error'. For example:
|
30
|
+
|
31
|
+
`rtunnel_server -l debug`
|
32
|
+
|
33
|
+
starts a server that will output debugging information.
|
34
|
+
|
35
|
+
**Secure connections**
|
36
|
+
|
37
|
+
RTunnel can be configured to use ssh keys to control access to the server. A ssh
|
38
|
+
key (generated by ssh-genkey) is required on the client, and the server must
|
39
|
+
have a list of authorized keys (using the format of known_hosts.) The keys are
|
40
|
+
used to authenticate clients and guarantee data integrity. For performance
|
41
|
+
reasons, encryption is not done.
|
42
|
+
|
43
|
+
Server setup:
|
44
|
+
|
45
|
+
`rtunnel_server -a ~/.ssh/known_hosts`
|
46
|
+
|
47
|
+
Client setup:
|
48
|
+
|
49
|
+
`rtunnel_client -c myserver.com -f 4000 -t 3000 -k /etc/ssh/ssh_host_rsa_key`
|
50
|
+
|
51
|
+
If you're concerned about security, you probably want to restrict the range of
|
52
|
+
ports that clients can open up on the rtunnel server.
|
53
|
+
|
54
|
+
`rtunnel_server -p 3000 -P 3999`
|
55
|
+
|
56
|
+
restricts clients to using ports 3000-3999 for reverse tunnels.
|
24
57
|
|
25
|
-
* 0.3.6 released, new protocol
|
26
|
-
* created gem for easier installation
|
27
|
-
* 0.2.1 released, minor bugfix, cmdline options change
|
28
|
-
* 0.2.0 released, much simpler
|
29
|
-
* 0.1.2 released
|
30
|
-
* Created rtunnel_server binary for linux so you don't need Ruby installed on the host you want to reverse tunnel from
|
31
|
-
* 0.1.1 released
|
32
|
-
* Added default control port of 19050, no longer have to specify this on client or server unless you care to change it
|
33
58
|
|
34
59
|
RTunnel?
|
35
60
|
-
|
36
61
|
|
37
|
-
This client/server allow you to reverse tunnel traffic. Reverse tunneling is useful if you want to run a server behind a NAT and you do not have the ability use port forwarding. The specific reason I created this program was to reduce the pain of Facebook App development on a crappy internet connection that drops often. ssh -R was not cutting it.
|
62
|
+
This client/server allow you to reverse tunnel traffic. Reverse tunneling is useful if you want to run a server behind a NAT and you do not have the ability to use port forwarding. The specific reason I created this program was to reduce the pain of Facebook App development on a crappy internet connection that drops often. ssh -R was not cutting it.
|
38
63
|
|
39
64
|
**How does reverse tunneling work?**
|
40
65
|
|
41
66
|
* tunnel\_client makes connection to tunnel\_server (through NAT)
|
42
67
|
* tunnel_server listens on port X
|
43
68
|
* internet_user connects to port X on tunnel server
|
44
|
-
* tunnel\_server uses existing connection to tunnel internet
|
69
|
+
* tunnel\_server uses existing connection to tunnel internet\_user's request back to tunnel\_client
|
45
70
|
* tunnel_client connects to local server on port Y
|
46
|
-
* tunnel_client tunnels internet
|
71
|
+
* tunnel_client tunnels internet\_user's connection through to local server
|
47
72
|
|
48
73
|
or:
|
49
74
|
|
@@ -56,4 +81,4 @@ With tunneling, usually your connections are made in the same direction you crea
|
|
56
81
|
|
57
82
|
**Why not just use ssh -R?**
|
58
83
|
|
59
|
-
The same thing can be achieved with ssh -R, why not just use it? A lot of ssh servers don't have the GatewayPorts sshd option set up to allow you to reverse tunnel. If you are not in control of the server and it is not setup correctly then you are SOL. RTunnel does not require you are in control of the server. ssh -R has other annoyances. When your connection drops and you try to re-initiate the reverse tunnel sometimes you get an address already in use error because the old tunnel process is still laying around. This
|
84
|
+
The same thing can be achieved with ssh -R, so why not just use it? A lot of ssh servers don't have the GatewayPorts sshd option set up to allow you to reverse tunnel. If you are not in control of the server and it is not setup correctly then you are SOL. RTunnel does not require you are in control of the server. ssh -R also has other annoyances. When your connection drops and you try to re-initiate the reverse tunnel sometimes you get an 'address already in use error' because the old tunnel process is still laying around. This may require you to kill the existing sshd process. RTunnel does not have this problem.
|
data/Rakefile
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'echoe'
|
1
3
|
require 'rake'
|
2
4
|
require 'spec/rake/spectask'
|
3
|
-
|
5
|
+
|
6
|
+
$: << File.join(File.dirname(__FILE__), 'lib')
|
7
|
+
require 'rtunnel'
|
4
8
|
|
5
9
|
desc "Run all examples"
|
6
10
|
Spec::Rake::SpecTask.new('spec') do |t|
|
@@ -9,10 +13,33 @@ end
|
|
9
13
|
|
10
14
|
desc "Create packages"
|
11
15
|
task :pkg4google do
|
12
|
-
system %Q{cd .. && tar cvzf rtunnel/pkg/rtunnel-#{RTunnel::VERSION}.tar.gz
|
13
|
-
--exclude=.svn --exclude=pkg --exclude=rtunnel.ipr
|
14
|
-
--exclude=rtunnel.iml
|
16
|
+
system %Q{cd .. && tar cvzf rtunnel/pkg/rtunnel-#{RTunnel::VERSION}.tar.gz \
|
17
|
+
rtunnel --exclude=.svn --exclude=pkg --exclude=rtunnel.ipr \
|
18
|
+
--exclude=rtunnel.iws --exclude=rtunnel.iml
|
15
19
|
}
|
16
20
|
system "rubyscript2exe rtunnel_server.rb --stop-immediately &&
|
17
21
|
mv rtunnel_server_linux pkg/rtunnel_server_linux-#{RTunnel::VERSION}"
|
18
22
|
end
|
23
|
+
|
24
|
+
desc "Print command codes"
|
25
|
+
task :codes do
|
26
|
+
print RTunnel::Command.printable_codes
|
27
|
+
end
|
28
|
+
|
29
|
+
Echoe.new('rtunnel') do |p|
|
30
|
+
p.rubyforge_name = 'coderrr'
|
31
|
+
p.author = 'coderrr'
|
32
|
+
p.email = 'coderrr.contact@gmail.com'
|
33
|
+
p.summary = 'Reverse tunnel server and client.'
|
34
|
+
p.description = ''
|
35
|
+
p.url = 'http://code.google.com/p/rtunnel/'
|
36
|
+
# p.remote_rdoc_dir = '' # Release to root
|
37
|
+
p.dependencies = ["eventmachine >=0.12.2",
|
38
|
+
"net-ssh >=2.0.4"]
|
39
|
+
p.development_dependencies = ["echoe >=3.0.1",
|
40
|
+
"rspec >=1.1.11",
|
41
|
+
"simple-daemon >=0.1.2",
|
42
|
+
"thin >=1.0.0"]
|
43
|
+
p.need_tar_gz = false
|
44
|
+
p.need_zip = false
|
45
|
+
end
|
data/bin/rtunnel_client
CHANGED
data/bin/rtunnel_server
CHANGED
data/lib/rtunnel.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module RTunnel
|
2
|
+
end
|
3
|
+
|
4
|
+
require 'rtunnel/core.rb'
|
5
|
+
require 'rtunnel/io_extensions.rb'
|
6
|
+
require 'rtunnel/socket_factory.rb'
|
7
|
+
require 'rtunnel/frame_protocol.rb'
|
8
|
+
|
9
|
+
require 'rtunnel/commands.rb'
|
10
|
+
require 'rtunnel/command_processor.rb'
|
11
|
+
require 'rtunnel/command_protocol.rb'
|
12
|
+
require 'rtunnel/connection_id.rb'
|
13
|
+
require 'rtunnel/crypto.rb'
|
14
|
+
|
15
|
+
require 'rtunnel/client.rb'
|
16
|
+
require 'rtunnel/leak.rb'
|
17
|
+
require 'rtunnel/server.rb'
|
18
|
+
|
19
|
+
require 'rtunnel/rtunnel_client_cmd.rb'
|
20
|
+
require 'rtunnel/rtunnel_server_cmd.rb'
|
@@ -0,0 +1,308 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'eventmachine'
|
6
|
+
|
7
|
+
class RTunnel::Client
|
8
|
+
include RTunnel
|
9
|
+
include RTunnel::Logging
|
10
|
+
|
11
|
+
attr_reader :control_address, :control_host, :control_port
|
12
|
+
attr_reader :remote_listen_address, :tunnel_to_address
|
13
|
+
attr_reader :tunnel_timeout, :private_key
|
14
|
+
attr_reader :logger
|
15
|
+
attr_reader :connections, :server_connection
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
process_options options
|
19
|
+
@connections = {}
|
20
|
+
@server_connection = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
return if @server_connection
|
25
|
+
@control_host = SocketFactory.host_from_address @control_address
|
26
|
+
@control_port = SocketFactory.port_from_address @control_address
|
27
|
+
connect_to_server
|
28
|
+
end
|
29
|
+
|
30
|
+
def connect_to_server
|
31
|
+
D "Connecting to #{@control_host} port #{@control_port}"
|
32
|
+
@server_connection = EventMachine.connect @control_host, @control_port,
|
33
|
+
Client::ServerConnection, self
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop
|
37
|
+
@connections.each { |connection| connection.close_connection_after_writing }
|
38
|
+
|
39
|
+
return unless @server_connection
|
40
|
+
@server_connection.close_connection_after_writing
|
41
|
+
@server_connection.disable_tunnel_timeouts
|
42
|
+
@server_connection = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
## option processing
|
46
|
+
|
47
|
+
def process_options(options)
|
48
|
+
[:control_address, :remote_listen_address, :tunnel_to_address,
|
49
|
+
:tunnel_timeout, :private_key].each do |opt|
|
50
|
+
instance_variable_set "@#{opt}".to_sym,
|
51
|
+
RTunnel::Client.send("extract_#{opt}".to_sym, options[opt])
|
52
|
+
end
|
53
|
+
|
54
|
+
init_log :level => options[:log_level]
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.extract_control_address(address)
|
58
|
+
unless SocketFactory.port_from_address address
|
59
|
+
address = "#{address}:#{RTunnel::DEFAULT_CONTROL_PORT}"
|
60
|
+
end
|
61
|
+
RTunnel.resolve_address address
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.extract_remote_listen_address(address)
|
65
|
+
unless SocketFactory.port_from_address address
|
66
|
+
address = "0.0.0.0:#{address}"
|
67
|
+
end
|
68
|
+
RTunnel.resolve_address address
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.extract_tunnel_to_address(address)
|
72
|
+
address = "localhost:#{address}" if address =~ /^\d+$/
|
73
|
+
RTunnel.resolve_address address
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.extract_tunnel_timeout(timeout)
|
77
|
+
timeout || RTunnel::TUNNEL_TIMEOUT
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.extract_private_key(key_file)
|
81
|
+
key_file and Crypto.read_private_key key_file
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Connection to the server's control port.
|
86
|
+
class RTunnel::Client::ServerConnection < EventMachine::Connection
|
87
|
+
# Note: I would've loved to make this a module, but event_machine's
|
88
|
+
# connection init order (initialize, connect block, post_init) does not
|
89
|
+
# work as advertised (the connect block seems to execute after post_init).
|
90
|
+
# So I'm taking the safe route and having my own initialize.
|
91
|
+
|
92
|
+
include RTunnel
|
93
|
+
include RTunnel::Logging
|
94
|
+
include RTunnel::CommandProcessor
|
95
|
+
include RTunnel::CommandProtocol
|
96
|
+
|
97
|
+
attr_reader :client
|
98
|
+
|
99
|
+
def initialize(client)
|
100
|
+
super()
|
101
|
+
|
102
|
+
@client = client
|
103
|
+
@tunnel_to_address = client.tunnel_to_address
|
104
|
+
@tunnel_to_host = SocketFactory.host_from_address @tunnel_to_address
|
105
|
+
@tunnel_to_port = SocketFactory.port_from_address @tunnel_to_address
|
106
|
+
@timeout_timer = nil
|
107
|
+
@hasher = nil
|
108
|
+
@connections = @client.connections
|
109
|
+
init_log :to => @client
|
110
|
+
end
|
111
|
+
|
112
|
+
def post_init
|
113
|
+
if @client.private_key
|
114
|
+
request_session_key
|
115
|
+
else
|
116
|
+
request_listen
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Asks the server to open a listen socket for this client's tunnel.
|
121
|
+
def request_listen
|
122
|
+
send_command RemoteListenCommand.new(@client.remote_listen_address)
|
123
|
+
enable_tunnel_timeouts
|
124
|
+
end
|
125
|
+
|
126
|
+
# Asks the server to establish a session key with this client.
|
127
|
+
def request_session_key
|
128
|
+
D 'Private key provided, asking server for session key'
|
129
|
+
key_fp = Crypto.key_fingerprint @client.private_key
|
130
|
+
send_command GenerateSessionKeyCommand.new(key_fp)
|
131
|
+
end
|
132
|
+
|
133
|
+
def unbind
|
134
|
+
# wait for a second, then try connecting again
|
135
|
+
W 'Lost server connection, will reconnect in 1s'
|
136
|
+
EventMachine.add_timer(1.0) { client.connect_to_server }
|
137
|
+
@connections.each { |conn_id, conn| conn.close_connection_after_writing }
|
138
|
+
@connections.clear
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
## Command processing
|
143
|
+
|
144
|
+
# CreateConnectionCommand handler
|
145
|
+
def process_create_connection(connection_id)
|
146
|
+
if @connections[connection_id]
|
147
|
+
E "asked to create already open connection #{connection_id}"
|
148
|
+
return
|
149
|
+
end
|
150
|
+
|
151
|
+
D "Tunnel #{connection_id} to #{@tunnel_to_host} port #{@tunnel_to_port}"
|
152
|
+
connection = EventMachine.connect(@tunnel_to_host, @tunnel_to_port,
|
153
|
+
Client::TunnelConnection, connection_id, @client)
|
154
|
+
@connections[connection_id] = connection
|
155
|
+
end
|
156
|
+
|
157
|
+
# CloseConnectionCommand handler
|
158
|
+
def process_close_connection(connection_id)
|
159
|
+
if connection = @connections[connection_id]
|
160
|
+
I "Closing connection #{connection_id}"
|
161
|
+
connection.close_connection_after_writing
|
162
|
+
@connections.delete connection_id
|
163
|
+
else
|
164
|
+
W "Asked to close inexistent connection #{connection_id}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
# Called when a tunnel connection is closed.
|
168
|
+
def data_connection_closed(connection_id)
|
169
|
+
return unless @connections.delete(connection_id)
|
170
|
+
D "Connection #{connection_id} closed by this end"
|
171
|
+
send_command CloseConnectionCommand.new(connection_id)
|
172
|
+
end
|
173
|
+
|
174
|
+
# SendData handler
|
175
|
+
def process_send_data(connection_id, data)
|
176
|
+
if connection = @connections[connection_id]
|
177
|
+
D "Data: #{data.length} bytes for #{connection_id}"
|
178
|
+
connection.tunnel_data data
|
179
|
+
else
|
180
|
+
W "Received data for non-existent connection #{connection_id}!"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# SetSessionKey handler
|
185
|
+
def process_set_session_key(encrypted_keys)
|
186
|
+
case encrypted_keys
|
187
|
+
when ''
|
188
|
+
W "Sent key to open tunnel server"
|
189
|
+
request_listen
|
190
|
+
when 'NO'
|
191
|
+
if @client.private_key
|
192
|
+
E "Server refused provided key"
|
193
|
+
else
|
194
|
+
E "Server requires authentication and no private key was provided"
|
195
|
+
end
|
196
|
+
close_connection_after_writing
|
197
|
+
else
|
198
|
+
D "Received server session keys, installing hashers"
|
199
|
+
iokeys = StringIO.new Crypto.decrypt_with_key(client.private_key,
|
200
|
+
encrypted_keys)
|
201
|
+
@out_hasher = Crypto::Hasher.new iokeys.read_varstring
|
202
|
+
@in_hasher = Crypto::Hasher.new iokeys.read_varstring
|
203
|
+
self.outgoing_command_hasher = @out_hasher
|
204
|
+
self.incoming_command_hasher = @in_hasher
|
205
|
+
|
206
|
+
D "Hashers installed, opening listen socket on server"
|
207
|
+
request_listen
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def receive_bad_frame(frame, exception)
|
212
|
+
case exception
|
213
|
+
when :bad_signature
|
214
|
+
D "Ignoring command with invalid signature"
|
215
|
+
when Exception
|
216
|
+
D "Ignoring malformed command."
|
217
|
+
D "Decoding exception: #{exception.class.name} - #{exception}\n" +
|
218
|
+
"#{exception.backtrace.join("\n")}\n"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
## Connection timeouts.
|
223
|
+
|
224
|
+
# Keep-alive received from the control connection.
|
225
|
+
def process_keep_alive
|
226
|
+
end
|
227
|
+
|
228
|
+
#:nodoc:
|
229
|
+
def receive_command(command)
|
230
|
+
@last_packet_time = Time.now
|
231
|
+
super
|
232
|
+
end
|
233
|
+
|
234
|
+
# After this is called, the control connection will be closed if no command is
|
235
|
+
# received within a certain amount of time.
|
236
|
+
def enable_tunnel_timeouts
|
237
|
+
@last_packet_time = Time.now
|
238
|
+
@timeout_timer = EventMachine::PeriodicTimer.new(1.0) do
|
239
|
+
check_tunnel_timeout
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Closes the connection if no command has been received for some time.
|
244
|
+
def check_tunnel_timeout
|
245
|
+
if tunnel_timeout?
|
246
|
+
W 'Tunnel timeout. Disconnecting from server.'
|
247
|
+
disable_tunnel_timeouts
|
248
|
+
close_connection_after_writing
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Disables timeout checking (so the tunnel will not be torn down if no command
|
253
|
+
# is received for some period of time).
|
254
|
+
def disable_tunnel_timeouts
|
255
|
+
return unless @timeout_timer
|
256
|
+
@timeout_timer.cancel
|
257
|
+
@timeout_timer = nil
|
258
|
+
end
|
259
|
+
|
260
|
+
# If true, a tunnel timeout has occured.
|
261
|
+
def tunnel_timeout?
|
262
|
+
Time.now - @last_packet_time > client.tunnel_timeout
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# A connection to the tunnelled port.
|
267
|
+
class RTunnel::Client::TunnelConnection < EventMachine::Connection
|
268
|
+
include RTunnel
|
269
|
+
include RTunnel::Logging
|
270
|
+
|
271
|
+
def initialize(connection_id, client)
|
272
|
+
super()
|
273
|
+
|
274
|
+
@connection_id = connection_id
|
275
|
+
@backlog = ''
|
276
|
+
@client = client
|
277
|
+
init_log :to => @client
|
278
|
+
end
|
279
|
+
|
280
|
+
def server_connection
|
281
|
+
@client.server_connection
|
282
|
+
end
|
283
|
+
|
284
|
+
def tunnel_data(data)
|
285
|
+
# if the connection hasn't been accepted, store the incoming data until
|
286
|
+
# sending can happen
|
287
|
+
if @backlog
|
288
|
+
@backlog << data
|
289
|
+
else
|
290
|
+
send_data data
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def post_init
|
295
|
+
D "Tunnel #{@connection_id} established"
|
296
|
+
send_data @backlog unless @backlog.empty?
|
297
|
+
@backlog = nil
|
298
|
+
end
|
299
|
+
|
300
|
+
def receive_data(data)
|
301
|
+
D "Data: #{data.length} bytes from #{@connection_id}"
|
302
|
+
server_connection.send_command SendDataCommand.new(@connection_id, data)
|
303
|
+
end
|
304
|
+
|
305
|
+
def unbind
|
306
|
+
server_connection.data_connection_closed @connection_id
|
307
|
+
end
|
308
|
+
end
|