forwardmachine 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 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