forwardmachine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in forwardmachine.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Mariusz Pietrzyk
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # ForwardMachine
2
+
3
+ Port forwarding service configurable in runtime.
4
+
5
+ # How does it work?
6
+
7
+ ForwardMachine listens on TCP port for forward requests.
8
+ These requests are simple, they consist of host:port, e.g. host.example.com:3000
9
+ As response, host and port where forwarding has been set up is returned.
10
+
11
+ ## Installation
12
+
13
+ $ gem install forwardmachine
14
+
15
+ ## Usage
16
+
17
+ 1. Start forwarder for host proxy.example.com
18
+
19
+ $ forwardmachine --forwarder-host proxy.example.com --ports-range 8000..9000
20
+
21
+ 2. Control server by default will listen on localhost:8899.
22
+ Connect to it and create a new forwarder (here we use nc tool).
23
+ Below we have created two ports forwards.
24
+
25
+ $ nc localhost 8899
26
+ internal1.example.com:7777
27
+ proxy.example.com:8000
28
+
29
+ $ nc localhost 8899
30
+ internal2.example.com:9999
31
+ proxy.example.com:8001
32
+
33
+ ## Contributing
34
+
35
+ 1. Fork it
36
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
37
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
38
+ 4. Push to the branch (`git push origin my-new-feature`)
39
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'forwardmachine'
4
+ require 'optparse'
5
+
6
+ options = {
7
+ log: 'forwardmachine.log'
8
+ }
9
+
10
+ OptionParser.new do |opts|
11
+ opts.banner = "Usage: forwardmachine [options]"
12
+
13
+ opts.on("-f", "--forwarder-host [HOST]", String, "Forwarder host (default: localhost)") do |v|
14
+ options[:forwarder_host] = v
15
+ end
16
+
17
+ opts.on("-r", "--ports-range [RANGE]", String, "Forwarder ports range (default: 23200..23500)") do |v|
18
+ options[:ports_range] = Range.new(*v.split("..").map(&:to_i))
19
+ end
20
+
21
+ opts.on("-b", "--bind [HOST]", String, "Controller host (default: localhost) ") do |v|
22
+ options[:host] = v
23
+ end
24
+
25
+ opts.on("-p", "--port [PORT]", Integer, "Controller port (default: 8899)") do |v|
26
+ options[:port] = v
27
+ end
28
+
29
+ opts.on("-l", "--log [PATH]", String, "Log file (default: forwardmachine.log)") do |v|
30
+ options[:log] = v
31
+ end
32
+
33
+ opts.on_tail("-h", "--help", "Show this message") do
34
+ puts opts
35
+ exit
36
+ end
37
+ end.parse!
38
+
39
+ ForwardMachine.logger_path = options[:log]
40
+
41
+ controller = ForwardMachine::Controller.new(options)
42
+ controller.run
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/forwardmachine/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Mariusz Pietrzyk"]
6
+ gem.email = ["wijet@wijet.pl"]
7
+ gem.description = %q{
8
+ Simple forwarding service written in Ruby with EventMachine.
9
+ Allows to set up port forwarding to given destination in runtime.
10
+ }
11
+ gem.summary = %q{Port forwarding service configurable in runtime}
12
+ gem.homepage = "https://github.com/ragnarson/forwardmachine"
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.name = "forwardmachine"
18
+ gem.require_paths = ["lib"]
19
+ gem.version = ForwardMachine::VERSION
20
+
21
+ gem.add_runtime_dependency "eventmachine"
22
+ gem.add_runtime_dependency "em-logger"
23
+
24
+ gem.add_development_dependency "rspec"
25
+ end
@@ -0,0 +1,23 @@
1
+ require "eventmachine"
2
+ require "logger"
3
+ require "em-logger"
4
+ require "forwardmachine/version"
5
+ require "forwardmachine/ports_pool"
6
+ require "forwardmachine/controller"
7
+ require "forwardmachine/controller_connection"
8
+ require "forwardmachine/forwarder"
9
+ require "forwardmachine/forwarder_connection"
10
+ require "forwardmachine/forwarded_connection"
11
+
12
+ module ForwardMachine
13
+ class << self
14
+ attr_accessor :logger_path
15
+ end
16
+ end
17
+
18
+ def logger
19
+ @logger ||= begin
20
+ logger = Logger.new(ForwardMachine.logger_path)
21
+ EM::Logger.new(logger)
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module ForwardMachine
2
+ # Server which listens for forward requests.
3
+ # It should be run on internal address, like localhost
4
+ # because it doesn't have any authentication.
5
+ # Each connection is handled by ControllerConnection
6
+ class Controller
7
+ def initialize(options = {})
8
+ @host = options[:host] || "localhost"
9
+ @port = options[:port] || 8899
10
+ @forwarder_host = options[:forwarder_host] || @host
11
+ @ports = PortsPool.new(options[:ports_range] || (23200..23500))
12
+ end
13
+
14
+ def run
15
+ EM.run {
16
+ EM.start_server(@host, @port, ControllerConnection,
17
+ @forwarder_host, @ports)
18
+ logger.info("Started controller at #{@host}:#{@port}")
19
+ }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ module ForwardMachine
2
+ # Connection to controller server
3
+ # Sets up new forwarder to destination host given by client
4
+ class ControllerConnection < EM::Connection
5
+ attr_reader :host, :ports
6
+
7
+ # Internal: Initialize new ForwardConnection
8
+ # host - Host on which forwarders (servers) will be created
9
+ # ports - Ports pool from which ports for forwarders
10
+ # will be taken
11
+ def initialize(host, ports)
12
+ @host = host
13
+ @ports = ports
14
+ end
15
+
16
+ # Internal: Receives destination in format "host:port"
17
+ # from client, creates new forwarder, returns forwarder
18
+ # socket address in format "host:port" back to the client
19
+ # and closes the connection.
20
+ def receive_data(data)
21
+ forwarder = Forwarder.new(host, data.strip, ports)
22
+ send_data(forwarder.start)
23
+ close_connection_after_writing
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module ForwardMachine
2
+ # Connection between forwarder machine
3
+ # and a service on destination host
4
+ class ForwardedConnection < EM::Connection
5
+ def initialize(forwarder_connection)
6
+ @forwarder_connection = forwarder_connection
7
+ end
8
+
9
+ # Internal: Sets both ways proxy between forwarder server
10
+ # and client (on destination host)
11
+ def post_init
12
+ EM.enable_proxy(self, @forwarder_connection)
13
+ EM.enable_proxy(@forwarder_connection, self)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,63 @@
1
+ module ForwardMachine
2
+ # Server which accepts traffic on available port taken from
3
+ # ports pool. Each connection is handled by ForwarderConnection object
4
+ class Forwarder
5
+ # How long server will be open, waiting for the first connetion.
6
+ FIRST_USE_TIMEOUT = 15
7
+
8
+ attr_reader :host, :destination, :ports_pool, :port, :connections
9
+
10
+ # Public: Initialize new Forwarder server
11
+ #
12
+ # host - Host as String on which server will listen
13
+ # destination - Destination socket as String where traffic will
14
+ # be forwarded (in format host:port)
15
+ # ports - PortsPool object with ports numbers from which port
16
+ # for forwarder will be taken
17
+ def initialize(host, destination, ports_pool)
18
+ @host = host
19
+ @ports_pool = ports_pool
20
+ @port = ports_pool.reserve
21
+ @destination = destination
22
+ @connections = 0
23
+ end
24
+
25
+ # Public: Start forwarding server on given host and port taken from PortsPool.
26
+ # Returns: Socket address of the server in format "host:port" as String
27
+ def start
28
+ @server = EM.start_server(host, port, ForwarderConnection, destination, self) {
29
+ @connections += 1
30
+ @inactivity_timer.cancel
31
+ }
32
+ @inactivity_timer = EM::PeriodicTimer.new(FIRST_USE_TIMEOUT) { stop }
33
+ logger.info("Started forwarder #{socket_address} to #{destination}")
34
+ socket_address
35
+ end
36
+
37
+ # Internal: Callback which is called from connection to Forwarder when
38
+ # client disconnects. Stops Forwarder server if it's not used by any
39
+ # connection.
40
+ def forwarder_connection_closed
41
+ stop if (@connections -= 1).zero?
42
+ end
43
+
44
+ # Public: Fowarder socket address
45
+ # Returns: String with host and port on which forwarder listens
46
+ def socket_address
47
+ "#{host}:#{port}"
48
+ end
49
+
50
+ def to_s
51
+ socket_address
52
+ end
53
+
54
+ private
55
+
56
+ def stop
57
+ logger.info("Stopped forwarder #{socket_address} to #{destination}")
58
+ @inactivity_timer.cancel
59
+ EM.stop_server(@server)
60
+ ports_pool.release(port)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,50 @@
1
+ require 'socket'
2
+
3
+ module ForwardMachine
4
+ # Connection between client and forwarder server.
5
+ class ForwarderConnection < EM::Connection
6
+ def initialize(destination, forwarder)
7
+ @destination = destination
8
+ @destination_host, @destination_port = destination.split(":")
9
+ @forwarder = forwarder
10
+ # Number of seconds server will wait for TCP connection in pending state
11
+ self.pending_connect_timeout = 60
12
+ # Number of seconds server connection remain open waiting for data
13
+ self.comm_inactivity_timeout = 60 * 30
14
+ end
15
+
16
+ # Internal: After client is connected to forwarder, open connection
17
+ # to destination host and port
18
+ def post_init
19
+ logger.info("Client #{peer} connected to forwarder #{@forwarder} to #{@destination}")
20
+ EM.connect(@destination_host, @destination_port,
21
+ ForwardedConnection, self)
22
+ rescue RuntimeError => e
23
+ logger.error("Client #{peer} on #{@forwarder} couldn't be connected with #{@destination}")
24
+ close_connection
25
+ end
26
+
27
+ # Internal: After forwarder destination disconnected
28
+ # terminate forwarder connection
29
+ def proxy_target_unbound
30
+ logger.info("Destination #{@destination} disconnected from forwarder #{@forwarder}")
31
+ close_connection
32
+ end
33
+
34
+ # Internal: After client disconnects from forwarder
35
+ # notify forwarder server about it.
36
+ def unbind
37
+ logger.info("Client #{peer} disconnected from forwarder #{@forwarder} to #{@destination}")
38
+ @forwarder.forwarder_connection_closed
39
+ end
40
+
41
+ private
42
+
43
+ def peer
44
+ @peer ||= begin
45
+ port, ip = Socket.unpack_sockaddr_in(get_peername)
46
+ "#{ip}:#{port}"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ require 'set'
2
+
3
+ module ForwardMachine
4
+ # PortsPool from which ports will be taken for creating
5
+ # port forwards.
6
+ class PortsPool < SortedSet
7
+ # Public: Initialize pool with range of ports
8
+ def initialize(range)
9
+ super(range.to_a)
10
+ end
11
+
12
+ # Public: Reserve one port
13
+ # Returns: Port number as Integer
14
+ # nil if no port is available
15
+ def reserve
16
+ delete(elem = first)
17
+ elem
18
+ end
19
+
20
+ # Public: Release given port, puts it back in the pool
21
+ # makes it available for later reservation
22
+ def release(port)
23
+ self << port
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module ForwardMachine
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe ForwardMachine::Forwarder do
4
+ around do |example|
5
+ EM.run do
6
+ ports_pool = ForwardMachine::PortsPool.new(23000..23010)
7
+ @forwarder = ForwardMachine::Forwarder.new('localhost',
8
+ 'localhost:30000', ports_pool)
9
+ example.run
10
+ end
11
+ end
12
+
13
+ describe "#start" do
14
+ before do
15
+ EM.start_server('localhost', 30000, FakeServer)
16
+ EM.run do
17
+ @forward = @forwarder.start
18
+ @host, @port = @forward.split(':')
19
+ end
20
+ end
21
+
22
+ it "should create new forwarder server and return port" do
23
+ EM.connect(@host, @port, FakeClient, 'hey') do |socket|
24
+ socket.onmessage do |message|
25
+ socket.send_data('close')
26
+ socket.onmessage { |m| m.should == '> closed' }
27
+ socket.onclose { EM.stop }
28
+ end
29
+ end
30
+ end
31
+
32
+ it "should count connections" do
33
+ EM.connect(@host, @port, FakeClient, 'hey') do |socket|
34
+ socket.onmessage do |message|
35
+ @forwarder.connections.should == 1
36
+ socket.send_data('close')
37
+ socket.onmessage { EM.stop }
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ context "forwarder server not used for FIRST_USE_TIMEOUT seconds" do
44
+ it "should close forwarder" do
45
+ ForwardMachine::Forwarder.send :remove_const, :FIRST_USE_TIMEOUT
46
+ ForwardMachine::Forwarder::FIRST_USE_TIMEOUT = 0.05
47
+ EM.start_server('localhost', 30000, FakeServer)
48
+ host, port = nil, nil
49
+ EM.run do
50
+ host, port = @forwarder.start.split(':')
51
+ end
52
+ EM.add_timer(0.1) do
53
+ socket = EM.connect(host, port, FakeClient, 'hey')
54
+ socket.onerror { socket.should be_error; EM.stop }
55
+ socket.onmessage { raise }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe ForwardMachine::PortsPool do
4
+ let(:pool) { ForwardMachine::PortsPool.new(100..105) }
5
+
6
+ describe "#reserve" do
7
+ it "should reserve port from a pool" do
8
+ pool.reserve.should == 100
9
+ pool.reserve.should == 101
10
+ end
11
+ end
12
+
13
+ describe "#release" do
14
+ it "should return given port back to the pool" do
15
+ pool.reserve
16
+ pool.reserve
17
+ pool.release(100)
18
+ pool.reserve.should == 100
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe ForwardMachine do
4
+ around do |example|
5
+ EM.run { forwardmachine; example.run }
6
+ end
7
+
8
+ it "should listen on control port and return forwarder port number" do
9
+ socket = EM.connect('localhost', 27000, FakeClient, 'localhost:30000')
10
+ socket.onmessage { |m| m.should == 'localhost:23200' }
11
+ socket.onclose { EM.stop }
12
+ end
13
+
14
+ it "should forward connection to destination host and port" do
15
+ EM.start_server('localhost', 30000, FakeServer)
16
+ control_socket = EM.connect('localhost', 27000, FakeClient, 'localhost:30000')
17
+ control_socket.onmessage do |forward|
18
+ host, port = forward.split(':')
19
+ socket = EM.connect(host, port, FakeClient, 'hey')
20
+ socket.onmessage { |m| m.should == '> hey' }
21
+ EM.add_timer(0.1) do
22
+ socket.send_data('close')
23
+ socket.onmessage { |m| m.should == '> closed'; EM.stop }
24
+ end
25
+ end
26
+ end
27
+
28
+ it "should close forwarded when destination host disconnected" do
29
+ EM.start_server('localhost', 30000, FakeServer)
30
+ control_socket = EM.connect('localhost', 27000, FakeClient, 'localhost:30000')
31
+ control_socket.onmessage do |forward|
32
+ host, port = forward.split(':')
33
+ EM.connect(host, port, FakeClient, 'close') do |c|
34
+ c.onmessage { |m| m.should == '> closed' }
35
+ c.should_not be_error
36
+ c.onclose do
37
+ EM.connect('localhost', port, FakeClient) do |c|
38
+ c.should be_error
39
+ EM.stop
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ it "should not close forwarder when any connection is ongoing" do
47
+ EM.start_server('localhost', 30000, FakeServer)
48
+ control_socket = EM.connect('localhost', 27000, FakeClient, 'localhost:30000')
49
+ control_socket.onmessage do |forward|
50
+ host, port = forward.split(':')
51
+ EM.connect(host, port, FakeClient, 'first') do |first|
52
+ first.onmessage { |m| m.should == '> first' }
53
+ second = EM.connect(host, port, FakeClient, 'second') do |second|
54
+ second.onmessage { |m| m.should == '> second' }
55
+ end
56
+
57
+ EM.add_timer(0.1) { first.close_connection }
58
+ EM.add_timer(0.2) do
59
+ # second still operational, after first one is closed
60
+ second.send_data('close')
61
+ second.onmessage { |m| m.should == '> closed' }
62
+ second.onclose { EM.stop }
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,48 @@
1
+ require 'forwardmachine'
2
+
3
+ RSpec.configure do |config|
4
+ config.treat_symbols_as_metadata_keys_with_true_values = true
5
+ config.run_all_when_everything_filtered = true
6
+ config.filter_run :focus
7
+ config.order = 'random'
8
+ end
9
+
10
+ def forwardmachine
11
+ controller = ForwardMachine::Controller.new(
12
+ host: "localhost", port: 27000)
13
+ controller.run
14
+ end
15
+
16
+ class FakeServer < EM::Connection
17
+ def receive_data(data)
18
+ if data == 'close'
19
+ send_data('> closed')
20
+ close_connection_after_writing
21
+ else
22
+ send_data("> #{data}")
23
+ end
24
+ end
25
+ end
26
+
27
+ class FakeClient < EM::Connection
28
+ def onclose(&block); @onclose = block; end
29
+ def onmessage(&block); @onmessage = block; end
30
+ def onerror(&block); @onerror = block; end
31
+
32
+ def initialize(message = nil)
33
+ @message = message
34
+ end
35
+
36
+ def post_init
37
+ send_data(@message) if @message
38
+ end
39
+
40
+ def receive_data(data)
41
+ @onmessage.call(data) if @onmessage
42
+ end
43
+
44
+ def unbind
45
+ @onerror.call if error? and @onerror
46
+ @onclose.call if @onclose
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: forwardmachine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mariusz Pietrzyk
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: em-logger
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: ! "\n Simple forwarding service written in Ruby with EventMachine.\n
63
+ \ Allows to set up port forwarding to given destination in runtime.\n "
64
+ email:
65
+ - wijet@wijet.pl
66
+ executables:
67
+ - forwardmachine
68
+ extensions: []
69
+ extra_rdoc_files: []
70
+ files:
71
+ - .gitignore
72
+ - .rspec
73
+ - Gemfile
74
+ - LICENSE
75
+ - README.md
76
+ - Rakefile
77
+ - bin/forwardmachine
78
+ - forwardmachine.gemspec
79
+ - lib/forwardmachine.rb
80
+ - lib/forwardmachine/controller.rb
81
+ - lib/forwardmachine/controller_connection.rb
82
+ - lib/forwardmachine/forwarded_connection.rb
83
+ - lib/forwardmachine/forwarder.rb
84
+ - lib/forwardmachine/forwarder_connection.rb
85
+ - lib/forwardmachine/ports_pool.rb
86
+ - lib/forwardmachine/version.rb
87
+ - spec/forwardmachine/forwarder_spec.rb
88
+ - spec/forwardmachine/ports_pool_spec.rb
89
+ - spec/integration_spec.rb
90
+ - spec/spec_helper.rb
91
+ homepage: https://github.com/ragnarson/forwardmachine
92
+ licenses: []
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 1.8.24
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: Port forwarding service configurable in runtime
115
+ test_files:
116
+ - spec/forwardmachine/forwarder_spec.rb
117
+ - spec/forwardmachine/ports_pool_spec.rb
118
+ - spec/integration_spec.rb
119
+ - spec/spec_helper.rb