reverse-tunnel 0.0.1
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/.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
|