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 +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +39 -0
- data/Rakefile +2 -0
- data/bin/forwardmachine +42 -0
- data/forwardmachine.gemspec +25 -0
- data/lib/forwardmachine.rb +23 -0
- data/lib/forwardmachine/controller.rb +22 -0
- data/lib/forwardmachine/controller_connection.rb +26 -0
- data/lib/forwardmachine/forwarded_connection.rb +16 -0
- data/lib/forwardmachine/forwarder.rb +63 -0
- data/lib/forwardmachine/forwarder_connection.rb +50 -0
- data/lib/forwardmachine/ports_pool.rb +26 -0
- data/lib/forwardmachine/version.rb +3 -0
- data/spec/forwardmachine/forwarder_spec.rb +59 -0
- data/spec/forwardmachine/ports_pool_spec.rb +21 -0
- data/spec/integration_spec.rb +67 -0
- data/spec/spec_helper.rb +48 -0
- metadata +119 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
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
data/bin/forwardmachine
ADDED
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|