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