reverse-tunnel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/Gemfile +6 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +99 -0
- data/Rakefile +4 -0
- data/bin/reverse-tunnel +5 -0
- data/lib/reverse-tunnel.rb +35 -0
- data/lib/reverse-tunnel/cli.rb +170 -0
- data/lib/reverse-tunnel/client.rb +304 -0
- data/lib/reverse-tunnel/message.rb +140 -0
- data/lib/reverse-tunnel/server.rb +406 -0
- data/lib/reverse-tunnel/version.rb +3 -0
- data/reverse-tunnel.gemspec +33 -0
- data/spec/reverse-tunnel/cli_spec.rb +118 -0
- data/spec/reverse-tunnel/client/api_server_spec.rb +72 -0
- data/spec/reverse-tunnel/client_spec.rb +79 -0
- data/spec/reverse-tunnel/message_spec.rb +55 -0
- data/spec/reverse-tunnel/server/api_server_spec.rb +125 -0
- data/spec/reverse-tunnel/server/tunnels_spec.rb +66 -0
- data/spec/reverse-tunnel/server_spec.rb +57 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/log.rb +9 -0
- data/tasks/rspec.rake +2 -0
- metadata +280 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'reverse-tunnel/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "reverse-tunnel"
|
8
|
+
gem.version = ReverseTunnel::VERSION
|
9
|
+
gem.authors = ["Alban Peignier", "Florent Peyraud"]
|
10
|
+
gem.email = ["alban@tryphon.eu", "florent@tryphon.eu"]
|
11
|
+
gem.description = %q{Create easily a tunnel to forward connection (like a ssh) to the client host}
|
12
|
+
gem.summary = %q{Forward a tcp connection to client host}
|
13
|
+
gem.homepage = "http://projects.tryphon.eu/projects/reverse-tunnel"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_runtime_dependency "eventmachine"
|
21
|
+
gem.add_runtime_dependency "msgpack"
|
22
|
+
gem.add_runtime_dependency "eventmachine_httpserver"
|
23
|
+
gem.add_runtime_dependency "json"
|
24
|
+
gem.add_runtime_dependency "trollop"
|
25
|
+
gem.add_runtime_dependency "SyslogLogger"
|
26
|
+
|
27
|
+
gem.add_development_dependency "simplecov"
|
28
|
+
gem.add_development_dependency "rspec"
|
29
|
+
gem.add_development_dependency "guard"
|
30
|
+
gem.add_development_dependency "guard-rspec"
|
31
|
+
gem.add_development_dependency "rake"
|
32
|
+
gem.add_development_dependency "rest-client"
|
33
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ReverseTunnel::CLI do
|
4
|
+
|
5
|
+
describe "#debug=" do
|
6
|
+
|
7
|
+
context "when true" do
|
8
|
+
it "should change Logger level to debug" do
|
9
|
+
ReverseTunnel.logger.level = Logger::INFO
|
10
|
+
lambda {
|
11
|
+
subject.debug = true
|
12
|
+
}.should change(ReverseTunnel.logger, :level).to(Logger::DEBUG)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when false" do
|
17
|
+
it "should change Logger level to info" do
|
18
|
+
ReverseTunnel.logger.level = Logger::DEBUG
|
19
|
+
lambda {
|
20
|
+
subject.debug = false
|
21
|
+
}.should change(ReverseTunnel.logger, :level).to(Logger::INFO)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
after do
|
26
|
+
ReverseTunnel.reset_logger!
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#syslog=" do
|
32
|
+
|
33
|
+
context "when true" do
|
34
|
+
|
35
|
+
it "should change ReverseTunnel.logger to Syslog::Logger" do
|
36
|
+
subject.syslog = true
|
37
|
+
ReverseTunnel.logger.should be_instance_of(Syslog::Logger)
|
38
|
+
end
|
39
|
+
|
40
|
+
after do
|
41
|
+
ReverseTunnel.reset_logger!
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
describe ReverseTunnel::CLI::Configurator do
|
56
|
+
|
57
|
+
describe "#server=" do
|
58
|
+
|
59
|
+
it "should parse ip and port" do
|
60
|
+
subject.should_receive(:server_host=).with("host")
|
61
|
+
subject.should_receive(:server_port=).with(4893)
|
62
|
+
|
63
|
+
subject.server = "host:4893"
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe ReverseTunnel::CLI::Client do
|
71
|
+
|
72
|
+
describe "#api=" do
|
73
|
+
|
74
|
+
it "should parse ip and port" do
|
75
|
+
subject.should_receive(:api_host=).with("host")
|
76
|
+
subject.should_receive(:api_port=).with(4895)
|
77
|
+
|
78
|
+
subject.api = "host:4895"
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#token" do
|
84
|
+
|
85
|
+
it "should use first argument" do
|
86
|
+
subject.stub :arguments => [ "token"]
|
87
|
+
subject.token.should == "token"
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
describe ReverseTunnel::CLI::Server do
|
95
|
+
|
96
|
+
describe "#api=" do
|
97
|
+
|
98
|
+
it "should parse ip and port" do
|
99
|
+
subject.should_receive(:api_host=).with("host")
|
100
|
+
subject.should_receive(:api_port=).with(4894)
|
101
|
+
|
102
|
+
subject.api = "host:4894"
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#local=" do
|
108
|
+
|
109
|
+
it "should parse ip and port range" do
|
110
|
+
subject.should_receive(:local_host=).with("host")
|
111
|
+
subject.should_receive(:local_port_range=).with(123..456)
|
112
|
+
|
113
|
+
subject.local = "host:123-456"
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require "rest_client"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
describe ReverseTunnel::Client::ApiServer do
|
7
|
+
|
8
|
+
let(:tunnel) { mock :to_json => '{"token":"1008B1A077343ED8B7AAAC9BC580","local_port":22,"server_host":"localhost","server_port":4893,"local_connections":[],"connection":{"created_at":"2012-12-27 12:13:50 +0100"}}' }
|
9
|
+
|
10
|
+
let(:api_host) { "127.0.0.1" }
|
11
|
+
let(:api_port) { 38589 }
|
12
|
+
|
13
|
+
def url(path)
|
14
|
+
"http://#{api_host}:#{api_port}#{path}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(path)
|
18
|
+
JSON.parse RestClient.get url(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def wait_api
|
22
|
+
5.times do
|
23
|
+
begin
|
24
|
+
s = TCPSocket.new(api_host, api_port)
|
25
|
+
s.close
|
26
|
+
return true
|
27
|
+
rescue Errno::ECONNREFUSED
|
28
|
+
sleep 0.1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def wait_api_stopped
|
34
|
+
5.times do
|
35
|
+
begin
|
36
|
+
s = TCPSocket.new(api_host, api_port)
|
37
|
+
s.close
|
38
|
+
sleep 0.1
|
39
|
+
rescue Errno::ECONNREFUSED
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
before do
|
46
|
+
Thread.new do
|
47
|
+
EventMachine.run do
|
48
|
+
EventMachine.start_server api_host, api_port, ReverseTunnel::Client::ApiServer, tunnel
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
wait_api
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "GET '/status'" do
|
56
|
+
|
57
|
+
it "should return tunnel description" do
|
58
|
+
get("/status").should == JSON.parse(tunnel.to_json)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should accept requests on /tunnels.json" do
|
62
|
+
get("/status.json").should == get("/status")
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
after do
|
68
|
+
EventMachine.stop
|
69
|
+
wait_api_stopped
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ReverseTunnel::Client do
|
4
|
+
|
5
|
+
describe "#server_host" do
|
6
|
+
|
7
|
+
it "should be nil by default" do
|
8
|
+
subject.server_host.should be_nil
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#server_port" do
|
14
|
+
|
15
|
+
it "should be 4893 by default" do
|
16
|
+
subject.server_port.should == 4893
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#local_port" do
|
22
|
+
|
23
|
+
it "should be 22 by default" do
|
24
|
+
subject.local_port.should == 22
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#api_host" do
|
30
|
+
|
31
|
+
it "should be nil by default" do
|
32
|
+
subject.api_host.should be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#api_port" do
|
38
|
+
|
39
|
+
it "should be 4895 by default" do
|
40
|
+
subject.api_port.should == 4895
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#tunnel" do
|
46
|
+
it "should use server_host" do
|
47
|
+
subject.server_host = "localhost"
|
48
|
+
subject.tunnel.host.should == "localhost"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should use server_port" do
|
52
|
+
subject.server_port = 123
|
53
|
+
subject.tunnel.port.should == 123
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should use local_port" do
|
57
|
+
subject.local_port = 123
|
58
|
+
subject.tunnel.local_port.should == 123
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#start_api" do
|
63
|
+
it "should use api_host and api_port" do
|
64
|
+
subject.api_host = "localhost"
|
65
|
+
subject.api_port = 123
|
66
|
+
|
67
|
+
EventMachine.should_receive(:start_server).with("localhost", 123, anything, anything)
|
68
|
+
subject.start_api
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should not start api if api_host is not defined" do
|
72
|
+
subject.api_host = nil
|
73
|
+
|
74
|
+
EventMachine.should_not_receive(:start_server)
|
75
|
+
subject.start_api
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Message do
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
describe Message::Data do
|
8
|
+
subject { Message::Data.new(123, "dummy") }
|
9
|
+
|
10
|
+
its(:type) { should == :data }
|
11
|
+
|
12
|
+
it { should be_data }
|
13
|
+
|
14
|
+
it "should have the same session_id after pack/unpack" do
|
15
|
+
Message.unpack(subject.pack).session_id.should == subject.session_id
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have the same data after pack/unpack" do
|
19
|
+
Message.unpack(subject.pack).data.should == subject.data
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe Message::OpenSession do
|
24
|
+
its(:type) { should == :open_session }
|
25
|
+
|
26
|
+
it { should be_open_session }
|
27
|
+
|
28
|
+
it "should have the same session_id after pack/unpack" do
|
29
|
+
subject.session_id = 123
|
30
|
+
Message.unpack(subject.pack).session_id.should == subject.session_id
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe Message::OpenTunnel do
|
35
|
+
its(:type) { should == :open_tunnel }
|
36
|
+
|
37
|
+
it { should be_open_tunnel }
|
38
|
+
|
39
|
+
it "should have the same token after pack/unpack" do
|
40
|
+
subject.token = 123
|
41
|
+
Message.unpack(subject.pack).token.should == subject.token
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe Message::Ping do
|
46
|
+
its(:type) { should == :ping }
|
47
|
+
|
48
|
+
it { should be_ping }
|
49
|
+
|
50
|
+
it "should have the same sequence_number after pack/unpack" do
|
51
|
+
subject.sequence_number = 123
|
52
|
+
Message.unpack(subject.pack).sequence_number.should == subject.sequence_number
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require "rest_client"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
describe ReverseTunnel::Server::ApiServer do
|
7
|
+
|
8
|
+
let(:server) { ReverseTunnel::Server.new }
|
9
|
+
|
10
|
+
let(:api_host) { "127.0.0.1" }
|
11
|
+
let(:api_port) { 38589 }
|
12
|
+
|
13
|
+
def url(path)
|
14
|
+
"http://#{api_host}:#{api_port}#{path}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(path)
|
18
|
+
JSON.parse RestClient.get url(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def post(path, params)
|
22
|
+
JSON.parse RestClient.post url(path), params.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(path)
|
26
|
+
JSON.parse RestClient.delete url(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def wait_api
|
30
|
+
5.times do
|
31
|
+
begin
|
32
|
+
s = TCPSocket.new(api_host, api_port)
|
33
|
+
s.close
|
34
|
+
return true
|
35
|
+
rescue Errno::ECONNREFUSED
|
36
|
+
sleep 0.1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def wait_api_stopped
|
42
|
+
5.times do
|
43
|
+
begin
|
44
|
+
s = TCPSocket.new(api_host, api_port)
|
45
|
+
s.close
|
46
|
+
sleep 0.1
|
47
|
+
rescue Errno::ECONNREFUSED
|
48
|
+
return true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
before do
|
54
|
+
Thread.new do
|
55
|
+
EventMachine.run do
|
56
|
+
EventMachine.start_server api_host, api_port, ReverseTunnel::Server::ApiServer, server
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
wait_api
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "GET '/tunnels'" do
|
64
|
+
|
65
|
+
it "should return an empty array without tunnels" do
|
66
|
+
get("/tunnels").should be_empty
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return tunnels description" do
|
70
|
+
tunnel = server.tunnels.create
|
71
|
+
get("/tunnels").should include("token" => tunnel.token, "local_port" => tunnel.local_port)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should accept requests on /tunnels.json" do
|
75
|
+
get("/tunnels.json").should == get("/tunnels")
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "POST '/tunnels'" do
|
81
|
+
|
82
|
+
it "should create a new tunnel" do
|
83
|
+
post("/tunnels", :token => "dummy")
|
84
|
+
server.tunnels.find("dummy").should_not be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should return new tunnel description" do
|
88
|
+
post("/tunnels", :token => "dummy")["token"].should == "dummy"
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "GET '/tunnels/:id'" do
|
94
|
+
|
95
|
+
it "should return tunnel description" do
|
96
|
+
tunnel = server.tunnels.create
|
97
|
+
get("/tunnels/#{tunnel.token}")["local_port"].should == tunnel.local_port
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should return a 404 when tunnel doesn't exist" do
|
101
|
+
lambda { get("/tunnels/123") }.should raise_error(RestClient::ResourceNotFound)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "DELETE '/tunnels/:id'" do
|
107
|
+
|
108
|
+
it "should delete the specified tunnel" do
|
109
|
+
tunnel = server.tunnels.create
|
110
|
+
delete("/tunnels/#{tunnel.token}")
|
111
|
+
server.tunnels.find(tunnel.token).should be_nil
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should return a 404 when tunnel doesn't exist" do
|
115
|
+
lambda { delete("/tunnels/123") }.should raise_error(RestClient::ResourceNotFound)
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
after do
|
121
|
+
EventMachine.stop
|
122
|
+
wait_api_stopped
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|