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
data/spec/cmds_spec.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
require 'cmds'
|
4
|
+
|
5
|
+
include RTunnel
|
6
|
+
|
7
|
+
describe RTunnel::CreateConnectionCommand do
|
8
|
+
it "should be able to serialize and parse itself" do
|
9
|
+
serialized = CreateConnectionCommand.new("id").to_s
|
10
|
+
|
11
|
+
cmd = Command.parse(serialized)
|
12
|
+
cmd.class.should == CreateConnectionCommand
|
13
|
+
cmd.conn_id.should == "id"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be able to match itself and remove itself from the stream" do
|
17
|
+
serialized = CreateConnectionCommand.new("abcbdefg").to_s
|
18
|
+
|
19
|
+
Command.match(serialized).should == true
|
20
|
+
Command.parse(serialized)
|
21
|
+
serialized.should be_empty
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe RTunnel::SendDataCommand do
|
26
|
+
it "should be able to serialzied and parse itself" do
|
27
|
+
serialized = SendDataCommand.new("id", "here is some data").to_s
|
28
|
+
|
29
|
+
cmd = Command.parse(serialized)
|
30
|
+
cmd.class.should == SendDataCommand
|
31
|
+
cmd.conn_id.should == "id"
|
32
|
+
cmd.data.should == "here is some data"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be able to match itself and remove itself from the stream" do
|
36
|
+
serialized = SendDataCommand.new("abcbdefg", "and here is some data").to_s
|
37
|
+
|
38
|
+
serialized << "WAY MORE CRAP DATA!!!"
|
39
|
+
|
40
|
+
Command.match(serialized).should == true
|
41
|
+
Command.parse(serialized)
|
42
|
+
serialized.should == "WAY MORE CRAP DATA!!!"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe RTunnel::CloseConnectionCommand do
|
47
|
+
it "should be able to serialize and parse itself" do
|
48
|
+
serialized = CloseConnectionCommand.new("id").to_s
|
49
|
+
|
50
|
+
cmd = Command.parse(serialized)
|
51
|
+
cmd.class.should == CloseConnectionCommand
|
52
|
+
cmd.conn_id.should == "id"
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should be able to match itself and remove itself from the stream" do
|
56
|
+
serialized = CloseConnectionCommand.new("abcbdefg").to_s
|
57
|
+
|
58
|
+
Command.match(serialized).should == true
|
59
|
+
Command.parse(serialized)
|
60
|
+
serialized.should be_empty
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe RTunnel::PingCommand do
|
65
|
+
it "should be able to serialize and parse itself" do
|
66
|
+
serialized = PingCommand.new.to_s
|
67
|
+
|
68
|
+
cmd = Command.parse(serialized)
|
69
|
+
cmd.class.should == PingCommand
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should be able to match itself and remove itself from the stream" do
|
73
|
+
serialized = PingCommand.new.to_s
|
74
|
+
|
75
|
+
Command.match(serialized).should == true
|
76
|
+
Command.parse(serialized)
|
77
|
+
serialized.should be_empty
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
describe RTunnel::RemoteListenCommand do
|
83
|
+
it "should be able to serialize and parse itself" do
|
84
|
+
serialized = RemoteListenCommand.new("0.0.0.0:1234").to_s
|
85
|
+
|
86
|
+
cmd = Command.parse(serialized)
|
87
|
+
cmd.class.should == RemoteListenCommand
|
88
|
+
cmd.address.should == "0.0.0.0:1234"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should be able to match itself and remove itself from the stream" do
|
92
|
+
serialized = RemoteListenCommand.new("0.0.0.0:1234").to_s
|
93
|
+
|
94
|
+
Command.match(serialized).should == true
|
95
|
+
Command.parse(serialized)
|
96
|
+
serialized.should be_empty
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
describe RTunnel::Command do
|
102
|
+
it "should be able to match a command in a stream" do
|
103
|
+
Command.match("C1234abc|").should == true
|
104
|
+
Command.match("C1234abc").should == false
|
105
|
+
Command.match("C1234abc|C|").should == true
|
106
|
+
|
107
|
+
Command.match("Did|15|DATA").should == false
|
108
|
+
Command.match("Did|1|DATA").should == true
|
109
|
+
Command.match("Did|4|\0\0\0\0").should == true
|
110
|
+
|
111
|
+
data = "C1234abc|D1234abc|15|here is my dataX1234abc|L0.0.0.0:1234|CASDF"
|
112
|
+
cmd1 = Command.parse(data)
|
113
|
+
cmd2 = Command.parse(data)
|
114
|
+
cmd3 = Command.parse(data)
|
115
|
+
cmd4 = Command.parse(data)
|
116
|
+
cmd1.class.should == CreateConnectionCommand
|
117
|
+
cmd1.conn_id.should == "1234abc"
|
118
|
+
cmd2.class.should == SendDataCommand
|
119
|
+
cmd2.conn_id.should == "1234abc"
|
120
|
+
cmd3.class.should == CloseConnectionCommand
|
121
|
+
cmd3.conn_id.should == "1234abc"
|
122
|
+
cmd4.class.should == RemoteListenCommand
|
123
|
+
cmd4.address.should == "0.0.0.0:1234"
|
124
|
+
|
125
|
+
Command.match(data).should == false
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
require 'client'
|
4
|
+
require 'server'
|
5
|
+
|
6
|
+
describe "RTunnel" do
|
7
|
+
class TextServer < GServer
|
8
|
+
def initialize(port, text)
|
9
|
+
@text = text
|
10
|
+
super(port)
|
11
|
+
end
|
12
|
+
|
13
|
+
def serve(io)
|
14
|
+
io.write(@text)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def read_from_address(addr)
|
19
|
+
timeout(2) { TCPSocket.open(*addr.split(/:/)) {|s| s.read } }
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should work!" do
|
23
|
+
begin
|
24
|
+
server = RTunnel::Server.new(:control_address => 'localhost:9999')
|
25
|
+
client = RTunnel::Client.new(:control_address => 'localhost:9999', :remote_listen_address => '30002', :tunnel_to_address => '30003')
|
26
|
+
|
27
|
+
server.start
|
28
|
+
client.start
|
29
|
+
|
30
|
+
s = TextServer.new(30003, echo_text = "tunnel this txt plz!")
|
31
|
+
s.start
|
32
|
+
|
33
|
+
sleep 0.5
|
34
|
+
|
35
|
+
# direct connect (sanity check)
|
36
|
+
read_from_address('localhost:30003').should == echo_text
|
37
|
+
# tunneled connect
|
38
|
+
read_from_address('localhost:30002').should == echo_text
|
39
|
+
ensure
|
40
|
+
begin
|
41
|
+
client.stop
|
42
|
+
server.stop
|
43
|
+
s.stop
|
44
|
+
rescue
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should ping all clients periodically" do
|
50
|
+
begin
|
51
|
+
server = RTunnel::Server.new(:control_address => 'localhost:9999', :ping_interval => 0.2)
|
52
|
+
clients = []
|
53
|
+
clients << RTunnel::Client.new(:control_address => 'localhost:9999', :remote_listen_address => '30002', :tunnel_to_address => '30003')
|
54
|
+
clients << RTunnel::Client.new(:control_address => 'localhost:9999', :remote_listen_address => '30012', :tunnel_to_address => '30013')
|
55
|
+
server.start
|
56
|
+
clients.each{|c|c.start}
|
57
|
+
|
58
|
+
pings = Hash.new {|h,k| h[k] = [] }
|
59
|
+
t = Thread.new do
|
60
|
+
loop do
|
61
|
+
clients.each do |client|
|
62
|
+
pings[client] << client.instance_eval { @last_ping }
|
63
|
+
end
|
64
|
+
sleep 0.05
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
sleep 2
|
69
|
+
|
70
|
+
clients.each do |client|
|
71
|
+
# 2 seconds, 0.2 pings a sec = ~10 pings
|
72
|
+
(9..11).should include(pings[client].uniq.size)
|
73
|
+
end
|
74
|
+
ensure
|
75
|
+
t.kill
|
76
|
+
begin
|
77
|
+
clients.each{|c|c.stop}
|
78
|
+
server.stop
|
79
|
+
rescue
|
80
|
+
p $!,$@
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "the client shouldnt fail even if there is no server running at the tunnel_to address" do
|
86
|
+
begin
|
87
|
+
server = RTunnel::Server.new(:control_address => 'localhost:9999')
|
88
|
+
client = RTunnel::Client.new(:control_address => 'localhost:9999', :remote_listen_address => '30002', :tunnel_to_address => '30003')
|
89
|
+
|
90
|
+
server.start
|
91
|
+
client.start
|
92
|
+
|
93
|
+
sleep 0.5
|
94
|
+
|
95
|
+
# tunneled connect
|
96
|
+
read_from_address('localhost:30002').should be_empty
|
97
|
+
ensure
|
98
|
+
begin
|
99
|
+
client.stop
|
100
|
+
server.stop
|
101
|
+
rescue
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
require 'server'
|
4
|
+
|
5
|
+
include RTunnel
|
6
|
+
describe RTunnel::Server, "addresses" do
|
7
|
+
it "control address should listen on all interfaces and use default control port if not specified" do
|
8
|
+
Server.new(:control_address => nil).
|
9
|
+
instance_eval { @control_address }.should == "0.0.0.0:#{DEFAULT_CONTROL_PORT}"
|
10
|
+
|
11
|
+
Server.new(:control_address => '5555').
|
12
|
+
instance_eval { @control_address }.should == "0.0.0.0:5555"
|
13
|
+
|
14
|
+
Server.new(:control_address => 'interface2').
|
15
|
+
instance_eval { @control_address }.should == "interface2:#{DEFAULT_CONTROL_PORT}"
|
16
|
+
|
17
|
+
Server.new(:control_address => 'interface2:2222').
|
18
|
+
instance_eval { @control_address }.should == "interface2:2222"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rtunnel'
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
require 'test/command_stubs.rb'
|
7
|
+
|
8
|
+
class CommandsTest < Test::Unit::TestCase
|
9
|
+
include CommandStubs
|
10
|
+
|
11
|
+
def setup
|
12
|
+
super
|
13
|
+
@str = StringIO.new
|
14
|
+
end
|
15
|
+
|
16
|
+
CommandStubs.command_names.each do |cmd|
|
17
|
+
define_method "test_#{cmd}_encode" do
|
18
|
+
command = self.send "generate_#{cmd}"
|
19
|
+
command.encode @str
|
20
|
+
@str.rewind
|
21
|
+
decoded_command = RTunnel::Command.decode @str
|
22
|
+
self.send "verify_#{cmd}", decoded_command
|
23
|
+
assert_equal "", @str.read, "Command #{cmd} did not consume its entire outpt"
|
24
|
+
end
|
25
|
+
|
26
|
+
define_method "test_#{cmd}_to_encoded_str" do
|
27
|
+
command = self.send "generate_#{cmd}"
|
28
|
+
command.encode @str
|
29
|
+
@str.rewind
|
30
|
+
assert_equal @str.read, command.to_encoded_str
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_all_encodes
|
35
|
+
sequence = CommandStubs.command_test_sequence
|
36
|
+
sequence.each { |cmd| self.send("generate_#{cmd}").encode @str }
|
37
|
+
@str.rewind
|
38
|
+
sequence.each do |cmd|
|
39
|
+
command = RTunnel::Command.decode(@str)
|
40
|
+
self.send "verify_#{cmd}", command
|
41
|
+
end
|
42
|
+
assert_equal "", @str.read
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_codes
|
46
|
+
# TODO(not_me): it'd be nice to have more than a smoke test here
|
47
|
+
assert_equal String, RTunnel::Command.printable_codes.class
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rtunnel'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class ConnectionIdTest < Test::Unit::TestCase
|
6
|
+
class CidWrapper
|
7
|
+
include RTunnel::ConnectionId
|
8
|
+
end
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@server = CidWrapper.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_ids_are_unique
|
15
|
+
n_ids = 1024
|
16
|
+
ids = (0...n_ids).map { @server.new_connection_id }
|
17
|
+
assert_equal n_ids, ids.uniq.length
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_id_sequences_are_not_trivial
|
21
|
+
n_servers = 256
|
22
|
+
n_ids = 16
|
23
|
+
sequences = (0...n_servers).map { CidWrapper.new }.map do |server|
|
24
|
+
(0...n_ids).map { server.new_connection_id }
|
25
|
+
end
|
26
|
+
0.upto(n_ids - 1) do |i|
|
27
|
+
assert_equal n_servers, sequences.map { |seq| seq[i] }.uniq.length
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|