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.
Files changed (55) hide show
  1. data/CHANGELOG +13 -0
  2. data/LICENSE +21 -0
  3. data/Manifest +48 -0
  4. data/README.markdown +40 -15
  5. data/Rakefile +31 -4
  6. data/bin/rtunnel_client +2 -1
  7. data/bin/rtunnel_server +2 -1
  8. data/lib/rtunnel.rb +20 -0
  9. data/lib/rtunnel/client.rb +308 -0
  10. data/lib/rtunnel/command_processor.rb +62 -0
  11. data/lib/rtunnel/command_protocol.rb +50 -0
  12. data/lib/rtunnel/commands.rb +233 -0
  13. data/lib/rtunnel/connection_id.rb +24 -0
  14. data/lib/rtunnel/core.rb +58 -0
  15. data/lib/rtunnel/crypto.rb +106 -0
  16. data/lib/rtunnel/frame_protocol.rb +34 -0
  17. data/lib/rtunnel/io_extensions.rb +54 -0
  18. data/lib/rtunnel/leak.rb +35 -0
  19. data/lib/rtunnel/rtunnel_client_cmd.rb +41 -0
  20. data/lib/rtunnel/rtunnel_server_cmd.rb +32 -0
  21. data/lib/rtunnel/server.rb +351 -0
  22. data/lib/rtunnel/socket_factory.rb +119 -0
  23. data/test/command_stubs.rb +77 -0
  24. data/test/protocol_mocks.rb +43 -0
  25. data/test/scenario_connection.rb +109 -0
  26. data/test/test_client.rb +48 -0
  27. data/test/test_command_protocol.rb +82 -0
  28. data/test/test_commands.rb +49 -0
  29. data/test/test_connection_id.rb +30 -0
  30. data/test/test_crypto.rb +127 -0
  31. data/test/test_frame_protocol.rb +109 -0
  32. data/test/test_io_extensions.rb +70 -0
  33. data/test/test_server.rb +70 -0
  34. data/test/test_socket_factory.rb +42 -0
  35. data/test/test_tunnel.rb +186 -0
  36. data/test_data/authorized_keys2 +4 -0
  37. data/test_data/known_hosts +4 -0
  38. data/test_data/random_rsa_key +27 -0
  39. data/test_data/ssh_host_dsa_key +12 -0
  40. data/test_data/ssh_host_rsa_key +27 -0
  41. data/tests/_ab_test.rb +16 -0
  42. data/tests/_stress_test.rb +96 -0
  43. data/tests/lo_http_server.rb +55 -0
  44. metadata +67 -27
  45. data/ab_test.rb +0 -23
  46. data/lib/client.rb +0 -150
  47. data/lib/cmds.rb +0 -166
  48. data/lib/core.rb +0 -58
  49. data/lib/rtunnel_client_cmd.rb +0 -23
  50. data/lib/rtunnel_server_cmd.rb +0 -18
  51. data/lib/server.rb +0 -197
  52. data/rtunnel.gemspec +0 -18
  53. data/rtunnel_client.rb +0 -3
  54. data/rtunnel_server.rb +0 -3
  55. 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
@@ -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