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,119 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module RTunnel::SocketFactory
|
4
|
+
def self.split_address(address)
|
5
|
+
port_index = address.index /[^:]\:[^:]/
|
6
|
+
if port_index
|
7
|
+
[address[0, port_index + 1], address[port_index + 2, address.length]]
|
8
|
+
else
|
9
|
+
[address, nil]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.host_from_address(address)
|
14
|
+
address and split_address(address)[0]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.port_from_address(address)
|
18
|
+
address and (port_string = split_address(address)[1]) and port_string.to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.inbound?(options)
|
22
|
+
options[:inbound] or [:in_port, :in_host, :in_addr].any? { |k| options[k] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.bind_host(options)
|
26
|
+
options[:in_host] or host_from_address(options[:in_addr]) or '0.0.0.0'
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.bind_port(options)
|
30
|
+
options[:in_port] or port_from_address(options[:in_addr]) or 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.bind_socket_address(options)
|
34
|
+
Socket::pack_sockaddr_in bind_port(options), bind_host(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.connect_host(options)
|
38
|
+
options[:out_host] or host_from_address(options[:out_addr])
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.connect_port(options)
|
42
|
+
options[:out_port] or port_from_address(options[:out_addr])
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.connect_socket_address(options)
|
46
|
+
Socket::pack_sockaddr_in connect_port(options), connect_host(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.tcp?(options)
|
50
|
+
options[:tcp] or !options[:udp]
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.new_tcp_socket
|
54
|
+
Socket.new Socket::AF_INET, Socket::SOCK_STREAM, Socket::PF_UNSPEC
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.new_udp_socket
|
58
|
+
Socket.new Socket::AF_INET, Socket::SOCK_DGRAM, Socket::PF_UNSPEC
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.new_socket(options)
|
62
|
+
tcp?(options) ? new_tcp_socket : new_udp_socket
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.bind(socket, options)
|
66
|
+
socket.bind bind_socket_address(options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.connect(socket, options)
|
70
|
+
socket.connect connect_socket_address(options)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.set_options(socket, options)
|
74
|
+
if options[:no_delay]
|
75
|
+
socket.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true
|
76
|
+
socket.sync = true
|
77
|
+
end
|
78
|
+
|
79
|
+
if options[:reuse_addr]
|
80
|
+
socket.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
|
81
|
+
end
|
82
|
+
|
83
|
+
unless options[:reverse_lookup]
|
84
|
+
if socket.respond_to? :do_not_reverse_lookup
|
85
|
+
socket.do_not_reverse_lookup = true
|
86
|
+
else
|
87
|
+
# work around until the patch below actually gets committed:
|
88
|
+
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/2346
|
89
|
+
BasicSocket.do_not_reverse_lookup = true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# new sockets coming out of socket.accept will have the given options set
|
95
|
+
def self.set_options_on_accept_sockets(socket, options)
|
96
|
+
socket.instance_variable_set :@rtunnel_factory_options, options
|
97
|
+
def socket.accept(*args)
|
98
|
+
sock, addr = super
|
99
|
+
RTunnel::SocketFactory.set_options sock, @rtunnel_factory_options
|
100
|
+
return sock, addr
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.socket(options = {})
|
105
|
+
s = new_socket options
|
106
|
+
set_options s, options
|
107
|
+
if inbound? options
|
108
|
+
bind s, options
|
109
|
+
set_options_on_accept_sockets s, options
|
110
|
+
else
|
111
|
+
connect s, options
|
112
|
+
end
|
113
|
+
s
|
114
|
+
end
|
115
|
+
|
116
|
+
def socket(options = {})
|
117
|
+
RTunnel::SocketFactory.socket(options)
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Contains generate_ and verify_ names for all RTunnel commands.
|
2
|
+
module CommandStubs
|
3
|
+
@@test_id1 = "1029384756ALSKDJFH"
|
4
|
+
@@test_id2 = "ALSKDJFH1029384756"
|
5
|
+
@@test_address = "192.168.43.95"
|
6
|
+
@@data = (0..255).to_a.pack('C*')
|
7
|
+
@@ekey = (128...192).to_a.pack('C*') * 8
|
8
|
+
@@pubfp = (0...128).to_a.pack('C*') * 5
|
9
|
+
|
10
|
+
def generate_keep_alive
|
11
|
+
RTunnel::KeepAliveCommand.new
|
12
|
+
end
|
13
|
+
def verify_keep_alive(cmd)
|
14
|
+
assert_equal RTunnel::KeepAliveCommand, cmd.class
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_create
|
18
|
+
RTunnel::CreateConnectionCommand.new @@test_id1
|
19
|
+
end
|
20
|
+
def verify_create(cmd)
|
21
|
+
assert_equal RTunnel::CreateConnectionCommand, cmd.class
|
22
|
+
assert_equal @@test_id1, cmd.connection_id
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_close
|
26
|
+
RTunnel::CloseConnectionCommand.new @@test_id2
|
27
|
+
end
|
28
|
+
def verify_close(cmd)
|
29
|
+
assert_equal RTunnel::CloseConnectionCommand, cmd.class
|
30
|
+
assert_equal @@test_id2, cmd.connection_id
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_listen
|
34
|
+
RTunnel::RemoteListenCommand.new @@test_address
|
35
|
+
end
|
36
|
+
def verify_listen(cmd)
|
37
|
+
assert_equal RTunnel::RemoteListenCommand, cmd.class
|
38
|
+
assert_equal @@test_address, cmd.address
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate_send
|
42
|
+
RTunnel::SendDataCommand.new @@test_id1, @@data
|
43
|
+
end
|
44
|
+
def verify_send(cmd)
|
45
|
+
assert_equal RTunnel::SendDataCommand, cmd.class
|
46
|
+
assert_equal @@test_id1, cmd.connection_id
|
47
|
+
assert_equal @@data, cmd.data
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate_set_sk
|
51
|
+
RTunnel::SetSessionKeyCommand.new @@ekey
|
52
|
+
end
|
53
|
+
def verify_set_sk(cmd)
|
54
|
+
assert_equal RTunnel::SetSessionKeyCommand, cmd.class
|
55
|
+
assert_equal @@ekey, cmd.encrypted_keys
|
56
|
+
end
|
57
|
+
|
58
|
+
def generate_gen_sk
|
59
|
+
RTunnel::GenerateSessionKeyCommand.new @@pubfp
|
60
|
+
end
|
61
|
+
def verify_gen_sk(cmd)
|
62
|
+
assert_equal RTunnel::GenerateSessionKeyCommand, cmd.class
|
63
|
+
assert_equal @@pubfp, cmd.public_key_fp
|
64
|
+
end
|
65
|
+
|
66
|
+
# An array with the names of all commands.
|
67
|
+
# Use these names to obtain the names of the genrate_ and verify_ methods.
|
68
|
+
def self.command_names
|
69
|
+
[:keep_alive, :create, :close, :listen, :send, :gen_sk, :set_sk]
|
70
|
+
end
|
71
|
+
|
72
|
+
# A sequence of command names useful for testing "real" connections.
|
73
|
+
def self.command_test_sequence
|
74
|
+
[:create, :keep_alive, :gen_sk, :set_sk, :keep_alive, :listen, :keep_alive,
|
75
|
+
:send, :send, :keep_alive, :keep_alive, :send, :close]
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Mocks the sending end of an EventMachine connection.
|
2
|
+
# The sent data is concatenated in a string available by calling #string.
|
3
|
+
class EmSendMock
|
4
|
+
attr_reader :string
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@string = ''
|
8
|
+
end
|
9
|
+
|
10
|
+
def send_data(data)
|
11
|
+
@string << data
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Mocks the receiving end of an EventMachine connection.
|
16
|
+
# The data to be received is passed as an array of strings to the constructor.
|
17
|
+
# Calling #replay mocks receiving the data.
|
18
|
+
class EmReceiveMock
|
19
|
+
attr_accessor :strings
|
20
|
+
attr_accessor :objects
|
21
|
+
|
22
|
+
def initialize(strings = [''])
|
23
|
+
@strings = strings
|
24
|
+
@objects = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def replay
|
28
|
+
@strings.each { |str| receive_data str }
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def receive_object(object)
|
33
|
+
@objects << object
|
34
|
+
end
|
35
|
+
|
36
|
+
# Declares the name of the object to be received. For instance, a frame
|
37
|
+
# protocol would use :frame for name. This generates a receive_frame method,
|
38
|
+
# and a frames accessor.
|
39
|
+
def self.object_name(name)
|
40
|
+
alias_method "receive_#{name}".to_sym, :receive_object
|
41
|
+
alias_method "#{name}s".to_sym, :objects
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
# Event Machine connection that runs a fixed send-expect scenario.
|
5
|
+
class ScenarioConnection < EventMachine::Connection
|
6
|
+
def initialize(test_case, scenario = nil)
|
7
|
+
super()
|
8
|
+
|
9
|
+
@test_case = test_case
|
10
|
+
@scenario = scenario || yield
|
11
|
+
@ignore_unbind = false
|
12
|
+
next_step
|
13
|
+
end
|
14
|
+
|
15
|
+
def post_init
|
16
|
+
scenario_can_send
|
17
|
+
end
|
18
|
+
|
19
|
+
def receive_data(data)
|
20
|
+
scenario_received data
|
21
|
+
end
|
22
|
+
|
23
|
+
def unbind
|
24
|
+
return if @ignore_unbind
|
25
|
+
|
26
|
+
unless @step and @step.first == :unbind
|
27
|
+
scenario_fail "Received unexpected unbind\n"
|
28
|
+
end
|
29
|
+
next_step
|
30
|
+
while @step and @step.first == :proc
|
31
|
+
@step.last.call
|
32
|
+
next_step
|
33
|
+
end
|
34
|
+
if @step and @step.first == :stop
|
35
|
+
scenario_stop @step.last
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Called when data is received. Plays the connection scenario.
|
40
|
+
def scenario_received(data)
|
41
|
+
unless @step and @step.first == :recv
|
42
|
+
scenario_fail "Received unexpected data: #{data}\n"
|
43
|
+
end
|
44
|
+
|
45
|
+
@test_case.send :assert_equal, @step.last, data
|
46
|
+
next_step
|
47
|
+
scenario_can_send
|
48
|
+
end
|
49
|
+
|
50
|
+
# Called when data can be sent. Plays the connection scenario.
|
51
|
+
def scenario_can_send
|
52
|
+
while @step
|
53
|
+
case @step.first
|
54
|
+
when :proc
|
55
|
+
@step.last.call
|
56
|
+
when :send
|
57
|
+
send_data @step.last
|
58
|
+
else
|
59
|
+
break
|
60
|
+
end
|
61
|
+
next_step
|
62
|
+
end
|
63
|
+
|
64
|
+
unless @step
|
65
|
+
# EM might stifle this exception and reraise
|
66
|
+
msg = "Scenario completed prematurely"
|
67
|
+
$stderr.puts msg
|
68
|
+
fail msg
|
69
|
+
end
|
70
|
+
|
71
|
+
case @step.first
|
72
|
+
when :receive
|
73
|
+
# wait to receive
|
74
|
+
return
|
75
|
+
when :unbind
|
76
|
+
# wait for unbind
|
77
|
+
return
|
78
|
+
when :close
|
79
|
+
@ignore_unbind = true
|
80
|
+
close_connection_after_writing
|
81
|
+
next_step
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def scenario_fail(fail_message)
|
86
|
+
if @step
|
87
|
+
fail_message << "Expected to #{@step.inspect}"
|
88
|
+
else
|
89
|
+
fail_message << "Scenario was completed\n"
|
90
|
+
end
|
91
|
+
@test_case.send :fail, fail_message
|
92
|
+
end
|
93
|
+
|
94
|
+
def scenario_stop(stop_proc)
|
95
|
+
if stop_proc.kind_of? Proc
|
96
|
+
# call the proc, then give em time to stop all its servers
|
97
|
+
stop_proc.call
|
98
|
+
EventMachine.add_timer 0.1 do
|
99
|
+
EventMachine.stop_event_loop
|
100
|
+
end
|
101
|
+
else
|
102
|
+
EventMachine.stop_event_loop
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def next_step
|
107
|
+
@step = @scenario.shift
|
108
|
+
end
|
109
|
+
end
|
data/test/test_client.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rtunnel'
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'resolv'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
class ClientTest < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
@client = RTunnel::Client.new(:control_address => 'localhost',
|
10
|
+
:remote_listen_address => '9199',
|
11
|
+
:tunnel_to_address => '4444',
|
12
|
+
:private_key => 'test_data/ssh_host_rsa_key',
|
13
|
+
:tunnel_timeout => 5)
|
14
|
+
@localhost_addr = Resolv.getaddress 'localhost'
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_options
|
18
|
+
client = RTunnel::Client
|
19
|
+
assert_equal "18.241.3.100:#{RTunnel::DEFAULT_CONTROL_PORT}",
|
20
|
+
client.extract_control_address('18.241.3.100')
|
21
|
+
assert_equal "18.241.3.100:9199",
|
22
|
+
client.extract_control_address('18.241.3.100:9199')
|
23
|
+
assert_equal "#{@localhost_addr}:#{RTunnel::DEFAULT_CONTROL_PORT}",
|
24
|
+
client.extract_control_address('localhost')
|
25
|
+
assert_equal "#{@localhost_addr}:9199",
|
26
|
+
client.extract_control_address('localhost:9199')
|
27
|
+
|
28
|
+
assert_equal "0.0.0.0:9199",
|
29
|
+
client.extract_remote_listen_address('9199')
|
30
|
+
assert_equal "18.241.3.100:9199",
|
31
|
+
client.extract_remote_listen_address('18.241.3.100:9199')
|
32
|
+
assert_equal "#{@localhost_addr}:9199",
|
33
|
+
client.extract_remote_listen_address('localhost:9199')
|
34
|
+
|
35
|
+
assert_equal "18.241.3.100:9199",
|
36
|
+
client.extract_tunnel_to_address('18.241.3.100:9199')
|
37
|
+
assert_equal "#{@localhost_addr}:9199",
|
38
|
+
client.extract_tunnel_to_address('9199')
|
39
|
+
|
40
|
+
assert_equal RTunnel::TUNNEL_TIMEOUT, client.extract_tunnel_timeout(nil)
|
41
|
+
assert_equal 29, client.extract_tunnel_timeout(29)
|
42
|
+
|
43
|
+
assert_equal nil, client.extract_private_key(nil)
|
44
|
+
key = client.extract_private_key 'test_data/ssh_host_rsa_key'
|
45
|
+
assert_equal OpenSSL::PKey::RSA, key.class
|
46
|
+
assert_equal File.read('test_data/ssh_host_rsa_key'), key.to_pem
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rtunnel'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require 'test/command_stubs.rb'
|
6
|
+
require 'test/protocol_mocks.rb'
|
7
|
+
|
8
|
+
# Send mock for commands.
|
9
|
+
class EmSendCommandsMock < EmSendMock
|
10
|
+
include RTunnel::CommandProtocol
|
11
|
+
end
|
12
|
+
|
13
|
+
# Receive mock for commands.
|
14
|
+
class EmReceiveCommandsMock < EmReceiveMock
|
15
|
+
include RTunnel::CommandProtocol
|
16
|
+
object_name :command
|
17
|
+
end
|
18
|
+
|
19
|
+
class CommandProtocolTest < Test::Unit::TestCase
|
20
|
+
include CommandStubs
|
21
|
+
C = RTunnel::Crypto
|
22
|
+
|
23
|
+
def setup
|
24
|
+
super
|
25
|
+
@send_mock = EmSendCommandsMock.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def teardown
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def commandset_test(names)
|
33
|
+
@send_mock.outgoing_command_hasher = @hasher if @hasher
|
34
|
+
names.each do |name|
|
35
|
+
command = self.send "generate_#{name}".to_sym
|
36
|
+
@send_mock.send_command command
|
37
|
+
end
|
38
|
+
receive_mock = EmReceiveCommandsMock.new([@send_mock.string])
|
39
|
+
receive_mock.incoming_command_hasher = C::Hasher.new(@hasher.key) if @hasher
|
40
|
+
o_commands = receive_mock.replay.commands
|
41
|
+
self.assert_equal names.length, o_commands.length
|
42
|
+
names.each_index do |i|
|
43
|
+
self.send "verify_#{names[i]}", o_commands[i]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
CommandStubs.command_names.each do |name|
|
48
|
+
define_method "test_#{name}".to_sym do
|
49
|
+
commandset_test [name]
|
50
|
+
end
|
51
|
+
|
52
|
+
define_method "test_signed_#{name}".to_sym do
|
53
|
+
@hasher = C::Hasher.new
|
54
|
+
commandset_test [name]
|
55
|
+
end
|
56
|
+
|
57
|
+
define_method "test_signed_#{name}_has_signature".to_sym do
|
58
|
+
sig_send_mock = EmSendCommandsMock.new
|
59
|
+
sig_send_mock.outgoing_command_hasher = C::Hasher.new
|
60
|
+
outputs = [@send_mock, sig_send_mock].map do |mock|
|
61
|
+
mock.send_command self.send("generate_#{name}".to_sym)
|
62
|
+
mock.string
|
63
|
+
end
|
64
|
+
assert outputs.first.length < outputs.last.length,
|
65
|
+
"No signature generated"
|
66
|
+
end
|
67
|
+
|
68
|
+
define_method "test_signed_#{name}_enforces_signature".to_sym do
|
69
|
+
@send_mock.outgoing_command_hasher = hasher = C::Hasher.new
|
70
|
+
@send_mock.send_command self.send("generate_#{name}".to_sym)
|
71
|
+
signed_str = @send_mock.string
|
72
|
+
|
73
|
+
0.upto(signed_str.length - 1) do |i|
|
74
|
+
bad_str = signed_str.dup
|
75
|
+
bad_str[i] ^= 0x01
|
76
|
+
recv_mock = EmReceiveCommandsMock.new([bad_str])
|
77
|
+
recv_mock.incoming_command_hasher = C::Hasher.new hasher.key
|
78
|
+
assert_equal [], recv_mock.replay.commands
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|