surro-gate 0.2.2 → 1.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a27ce2403acd4f16e3d9d0e67209773563b24e9d
4
- data.tar.gz: d8eb8edee4f24b9b66c03cea8e0df9cb2f6542da
2
+ SHA256:
3
+ metadata.gz: 542c462ea407735d332f61fd2a665ec27c151069c9b670da220592b30c4895bc
4
+ data.tar.gz: 17cf01b8d553093d77afb8409b995d06ff0351893431d35cf92afa079ae87247
5
5
  SHA512:
6
- metadata.gz: '0801c6d8fa84ea67afab352c434fcbdcb56b0518ffe5886d372e371dc22ae35cc8de4571b23313ea18612d28c0d2f3e4889b4cee447573c1b8e64635a6ab8f0c'
7
- data.tar.gz: 9b0324b3ca7638b9869285ea0993994fbabbf297f293eb0bd10b416fb37b4e2c77bc01ca75c7e9b92b7db8f042a906540ca5a94ecdbf237bcd0083ae1d55cd24
6
+ metadata.gz: 06d2cbf0cfab641f3006ab85c7baf48977b2b2bb05514b92cce2e990b35e31b34054b0b985cb9cc746f235bc764946e1c2d958c713a41e68a5c967508ece795b
7
+ data.tar.gz: a613ef079f5d878d5785139f2b215561252160f5f2bc1097ce13dce0eca47f063ac06a52d1e335df62bb1e08ec1eb117f56ef74ec93b858d5cb4ae6e6a4143ba
data/.gitignore CHANGED
@@ -9,6 +9,7 @@
9
9
  /test/tmp/
10
10
  /test/version_tmp/
11
11
  /tmp/
12
+ /lib/**/*.so
12
13
 
13
14
  ## Documentation cache and generated files:
14
15
  /.yardoc/
data/.travis.yml CHANGED
@@ -1,11 +1,18 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  cache: bundler
4
+ os:
5
+ - linux
6
+ - osx
7
+ compiler:
8
+ - gcc
9
+ - clang
4
10
  rvm:
5
- - 2.0.0
6
- - 2.1.9
7
- - 2.2.6
8
- - 2.3.3
11
+ - 2.2
12
+ - 2.3
13
+ - 2.4
14
+ - 2.5
15
+ - 2.6
9
16
  before_install:
10
17
  - "echo 'gem: --no-ri --no-rdoc --no-document' > ~/.gemrc"
11
18
  - "gem install bundler -v 1.13.0"
data/README.md CHANGED
@@ -5,9 +5,8 @@
5
5
  [![Dependency Status](https://gemnasium.com/skateman/surro-gate.svg)](https://gemnasium.com/skateman/surro-gate)
6
6
  [![Inline docs](http://inch-ci.org/github/skateman/surro-gate.svg?branch=master)](http://inch-ci.org/github/skateman/surro-gate)
7
7
  [![Code Climate](https://codeclimate.com/github/skateman/surro-gate/badges/gpa.svg)](https://codeclimate.com/github/skateman/surro-gate)
8
- [![codecov](https://codecov.io/gh/skateman/surro-gate/branch/master/graph/badge.svg)](https://codecov.io/gh/skateman/surro-gate)
9
8
 
10
- SurroGate is a general purpose TCP-to-TCP proxy implemented in Ruby.
9
+ SurroGate is a generic purrpose TCP-to-TCP proxy for Ruby implemented using epoll.
11
10
 
12
11
  ## Installation
13
12
 
@@ -32,15 +31,27 @@ require 'surro-gate'
32
31
 
33
32
  proxy = SurroGate.new
34
33
 
35
- # Create two TCP socket connections
36
- left = TCPSocket.new('localhost', 3333)
37
- right = TCPSocket.new('localhost', 2222)
38
-
39
- # Push the sockets to the proxy
40
- proxy.push(left, right)
41
-
42
- # Wait for the communication to finish
43
- proxy.wait
34
+ # Create a pair of TCP socket connections
35
+ sock_1 = TCPSocket.new('localhost', 1111)
36
+ sock_2 = TCPSocket.new('localhost', 2222)
37
+
38
+ # Push the pair of sockets to the proxy
39
+ proxy.push(sock_1, sock_2)
40
+
41
+ loop do
42
+ # Select with a 1 second timeout
43
+ proxy.select(1000)
44
+
45
+ # Do some hard work
46
+ proxy.each_ready do |left, right|
47
+ begin
48
+ right.write_nonblock(left.read_nonblock(4096))
49
+ rescue => ex
50
+ # ...
51
+ proxy.pop(left, right) # Remove the failed connection pair from the proxy
52
+ end
53
+ end
54
+ end
44
55
 
45
56
  ```
46
57
 
data/Rakefile CHANGED
@@ -1,6 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
5
+ require 'rake/extensiontask'
6
+ require 'surro-gate/version'
3
7
 
4
8
  RSpec::Core::RakeTask.new(:spec)
5
9
 
6
- task :default => :spec
10
+ Rake::ExtensionTask.new('surro-gate') do |ext|
11
+ ext.name = 'surro-gate/selector_ext'
12
+ end
13
+
14
+ task :default => SurroGate::HAVE_EXT ? %i[compile spec] : %i[clean spec]
15
+
16
+ CLEAN.include '**/*.o', '**/*.so', 'pkg', 'tmp'
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+
3
+ have_header 'sys/epoll.h'
4
+ create_makefile 'surro-gate/selector_ext'
@@ -0,0 +1,203 @@
1
+ #include "selector_ext.h"
2
+
3
+ static VALUE mSurroGate = Qnil;
4
+ static VALUE cSurroGate_Selector = Qnil;
5
+ static VALUE cSurroGate_Pair = Qnil;
6
+
7
+ void epoll_register(int *epoll, int socket, int ltr, int rtl) {
8
+ struct epoll_event ev;
9
+ ev.data.u64 = ((uint64_t)ltr) << 32 | rtl;
10
+ ev.events = EPOLLONESHOT | EPOLLIN | EPOLLOUT;
11
+ epoll_ctl(*epoll, EPOLL_CTL_ADD, socket, &ev);
12
+ }
13
+
14
+ void epoll_deregister(int *epoll, int socket) {
15
+ epoll_ctl(*epoll, EPOLL_CTL_DEL, socket, NULL);
16
+ }
17
+
18
+ void epoll_rearm(int *epoll, int socket, int ltr, int rtl, int events) {
19
+ struct epoll_event ev;
20
+ ev.data.u64 = ((uint64_t)ltr) << 32 | rtl;
21
+ ev.events = EPOLLONESHOT | events;
22
+ epoll_ctl(*epoll, EPOLL_CTL_MOD, socket, &ev);
23
+ }
24
+
25
+ static VALUE pairing_compare(VALUE pair, VALUE sockets) {
26
+ int i;
27
+ VALUE left = rb_iv_get(pair, "@left");
28
+ VALUE right = rb_iv_get(pair, "@right");
29
+
30
+ for (i=0; i<RARRAY_LEN(sockets); i++) { // sockets.each
31
+ VALUE item = rb_ary_entry(sockets, i);
32
+ if (left == item || right == item) {
33
+ return Qtrue;
34
+ }
35
+ }
36
+
37
+ return Qnil;
38
+ };
39
+
40
+ static VALUE pairing_iterate(VALUE pair, VALUE self, int argc, VALUE *argv) {
41
+ int *selector;
42
+ VALUE inverse, inv_idx;
43
+
44
+ // Yield only for the pairs that are ready
45
+ if (rb_funcall(pair, rb_intern("ready?"), 0) == Qtrue) {
46
+ rb_yield_values(2, rb_iv_get(pair, "@left"), rb_iv_get(pair, "@right")); // yield(pair.left, pair.right)
47
+
48
+ inv_idx = rb_iv_get(pair, "@inverse");
49
+ inverse = rb_funcall(rb_iv_get(self, "@pairing"), rb_intern("[]"), 1, inv_idx); // @pairing[inv_idx]
50
+
51
+ rb_iv_set(pair, "@rd_rdy", Qfalse);
52
+ rb_iv_set(pair, "@wr_rdy", Qfalse);
53
+
54
+ Data_Get_Struct(self, int, selector);
55
+ // Rearm left socket for reading and also writing if not ready for writing
56
+ epoll_rearm(selector, SOCK_PTR(rb_iv_get(pair, "@left")), NUM2INT(argv[1]), NUM2INT(inv_idx), EPOLLIN | (IVAR_TRUE(inverse, "@wr_rdy") ? 0 : EPOLLOUT));
57
+ // Rearm right socket for writing and also reading if not ready for reading
58
+ epoll_rearm(selector, SOCK_PTR(rb_iv_get(pair, "@right")), NUM2INT(inv_idx), NUM2INT(argv[1]), EPOLLOUT | (IVAR_TRUE(inverse, "@rd_rdy") ? 0 : EPOLLIN));
59
+ }
60
+ return Qnil;
61
+ }
62
+
63
+ static VALUE SurroGate_Selector_allocate(VALUE self) {
64
+ int *selector = malloc(sizeof(int));
65
+
66
+ if (selector != NULL) {
67
+ *selector = epoll_create1(0);
68
+ if (*selector > 0) {
69
+ return Data_Wrap_Struct(self, NULL, SurroGate_Selector_free, selector);
70
+ } else {
71
+ xfree(selector);
72
+ }
73
+ }
74
+
75
+ rb_raise(rb_eRuntimeError, "Allocation failed!");
76
+ return Qnil;
77
+ }
78
+
79
+ static void SurroGate_Selector_free(int *selector) {
80
+ close(*selector);
81
+ xfree(selector);
82
+ }
83
+
84
+ static VALUE SurroGate_Selector_initialize(VALUE self, VALUE logger) {
85
+ VALUE mConcurrent = rb_const_get(rb_cObject, rb_intern("Concurrent"));
86
+ VALUE cArray = rb_const_get(mConcurrent, rb_intern("Array"));
87
+
88
+ rb_iv_set(self, "@pairing", rb_class_new_instance(0, NULL, cArray)); // @pairing = Concurrent::Array.new
89
+ rb_iv_set(self, "@logger", logger);
90
+
91
+ return Qnil;
92
+ }
93
+
94
+ static VALUE SurroGate_Selector_push(VALUE self, VALUE left, VALUE right) {
95
+ int index_ltr, index_rtl, *selector;
96
+
97
+ VALUE pair_LTR[2] = {left, right};
98
+ VALUE pair_RTL[2] = {right, left};
99
+ VALUE left_to_right = rb_class_new_instance(2, pair_RTL, cSurroGate_Pair); // SurroGate::Pair.new(left, right)
100
+ VALUE right_to_left = rb_class_new_instance(2, pair_LTR, cSurroGate_Pair); // SurroGate::Pair.new(right, left)
101
+
102
+ VALUE pairing = rb_iv_get(self, "@pairing");
103
+
104
+ Check_Type(left, T_FILE);
105
+ Check_Type(right, T_FILE);
106
+
107
+ // raise ArgumentError if @pairing.detect(&pairing_compare)
108
+ if (rb_block_call(pairing, rb_intern("detect"), 0, NULL, pairing_compare, rb_ary_new_from_values(2, pair_LTR)) != Qnil) {
109
+ rb_raise(rb_eArgError, "Socket already registered!");
110
+ }
111
+
112
+ Data_Get_Struct(self, int, selector);
113
+
114
+ rb_funcall(pairing, rb_intern("push"), 2, left_to_right, right_to_left); // @pairing.push(left_to_right, right_to_left)
115
+ index_ltr = NUM2INT(rb_funcall(pairing, rb_intern("index"), 1, left_to_right)); // @pairing.index(left_to_right)
116
+ index_rtl = NUM2INT(rb_funcall(pairing, rb_intern("index"), 1, right_to_left)); // @pairing.index(right_to_left)
117
+
118
+ rb_iv_set(left_to_right, "@inverse", INT2NUM(index_rtl));
119
+ rb_iv_set(right_to_left, "@inverse", INT2NUM(index_ltr));
120
+
121
+ epoll_register(selector, SOCK_PTR(left), index_ltr, index_rtl);
122
+ epoll_register(selector, SOCK_PTR(right), index_rtl, index_ltr);
123
+
124
+ return Qtrue;
125
+ }
126
+
127
+ static VALUE SurroGate_Selector_pop(VALUE self, VALUE sockets) {
128
+ int i, *selector;
129
+
130
+ VALUE pairing = rb_iv_get(self, "@pairing");
131
+ rb_block_call(pairing, rb_intern("delete_if"), 0, NULL, pairing_compare, sockets); // @pairing.delete_if(&pairing_compare)
132
+
133
+ Data_Get_Struct(self, int, selector);
134
+ for (i=0; i<RARRAY_LEN(sockets); i++) {
135
+ epoll_deregister(selector, SOCK_PTR(rb_ary_entry(sockets, i)));
136
+ }
137
+
138
+ return Qnil;
139
+ }
140
+
141
+ static VALUE SurroGate_Selector_select(VALUE self, VALUE timeout) {
142
+ int i, count, *selector, source, target;
143
+ struct epoll_event events[256];
144
+ VALUE read, write, socket;
145
+
146
+ VALUE pairing = rb_iv_get(self, "@pairing");
147
+ Data_Get_Struct(self, int, selector);
148
+
149
+ count = epoll_wait(*selector, events, 256, NUM2INT(timeout));
150
+
151
+ for (i=0; i<count; i++) {
152
+ source = (int)((events[i].data.u64 & 0xFFFFFFFF00000000LL) >> 32);
153
+ target = (int)(events[i].data.u64 & 0xFFFFFFFFLL);
154
+
155
+ read = rb_funcall(pairing, rb_intern("[]"), 1, INT2NUM(target)); // @pairing[source]
156
+ write = rb_funcall(pairing, rb_intern("[]"), 1, INT2NUM(source)); // @pairing[target]
157
+
158
+ if (events[i].events & EPOLLIN && events[i].events & EPOLLOUT) {
159
+ // Socket is both available for read and write
160
+ rb_iv_set(read, "@rd_rdy", Qtrue); // read.rd_rdy = true
161
+ rb_iv_set(write, "@wr_rdy", Qtrue); // write.wr_rdy = true
162
+ } else if (events[i].events & EPOLLIN) {
163
+ // Socket is available for read, reregister it for write if not writable
164
+ rb_iv_set(read, "@rd_rdy", Qtrue); // read.rd_rdy = true
165
+ if (rb_iv_get(write, "@wr_rdy") == Qfalse) { // if !write.wr_rdy
166
+ socket = rb_iv_get(read, "@left"); // read.left
167
+ epoll_rearm(selector, SOCK_PTR(socket), target, source, EPOLLOUT);
168
+ }
169
+ } else if (events[i].events & EPOLLOUT) {
170
+ // Socket is available for write, reregister it for read if not readable
171
+ rb_iv_set(write, "@wr_rdy", Qtrue); // write.wr_rdy = true
172
+ if (rb_iv_get(write, "@rd_rdy") == Qfalse) { // if !source.rd_rdy
173
+ socket = rb_iv_get(write, "@right"); // write.right
174
+ epoll_rearm(selector, SOCK_PTR(socket), source, target, EPOLLIN);
175
+ }
176
+ }
177
+ }
178
+
179
+ return INT2NUM(count);
180
+ }
181
+
182
+ static VALUE SurroGate_Selector_each_ready(VALUE self) {
183
+ VALUE pairing = rb_iv_get(self, "@pairing");
184
+ rb_need_block();
185
+ return rb_block_call(pairing, rb_intern("each_with_index"), 0, NULL, pairing_iterate, self);
186
+ }
187
+
188
+ void Init_selector_ext() {
189
+ rb_require("concurrent");
190
+ rb_require("surro-gate/pair");
191
+
192
+ mSurroGate = rb_define_module("SurroGate");
193
+ cSurroGate_Selector = rb_define_class_under(mSurroGate, "Selector", rb_cObject);
194
+ cSurroGate_Pair = rb_const_get(mSurroGate, rb_intern("Pair"));
195
+
196
+ rb_define_alloc_func(cSurroGate_Selector, SurroGate_Selector_allocate);
197
+
198
+ rb_define_method(cSurroGate_Selector, "initialize", SurroGate_Selector_initialize, 1);
199
+ rb_define_method(cSurroGate_Selector, "push", SurroGate_Selector_push, 2);
200
+ rb_define_method(cSurroGate_Selector, "pop", SurroGate_Selector_pop, -2);
201
+ rb_define_method(cSurroGate_Selector, "select", SurroGate_Selector_select, 1);
202
+ rb_define_method(cSurroGate_Selector, "each_ready", SurroGate_Selector_each_ready, 0);
203
+ }
@@ -0,0 +1,22 @@
1
+ #ifndef SELECTOR_EXT_H
2
+ #define SELECTOR_EXT_H
3
+
4
+ #include "ruby.h"
5
+ #include "ruby/io.h"
6
+ #include "stdlib.h"
7
+ #include "sys/epoll.h"
8
+
9
+ #define SOCK_PTR(X) RFILE(X)->fptr->fd
10
+ #define IVAR_TRUE(X, Y) rb_iv_get(X, Y) == Qtrue
11
+
12
+ static VALUE SurroGate_Selector_allocate(VALUE self);
13
+ static VALUE SurroGate_Selector_initialize(VALUE self, VALUE logger);
14
+ static VALUE SurroGate_Selector_push(VALUE self, VALUE left, VALUE right);
15
+ static VALUE SurroGate_Selector_select(VALUE self, VALUE timeout);
16
+ static VALUE SurroGate_Selector_each_ready(VALUE self);
17
+
18
+ static void SurroGate_Selector_free(int *epoll);
19
+ static VALUE pairing_compare(VALUE pair, VALUE sockets);
20
+ static VALUE pairing_iterate(VALUE pair, VALUE self, int argc, VALUE *argv);
21
+
22
+ #endif
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SurroGate
4
+ class Pair
5
+ attr_reader :left, :right
6
+
7
+ def initialize(left, right)
8
+ @left = left
9
+ @right = right
10
+ @rd_rdy = @wr_rdy = false
11
+ end
12
+
13
+ def ready?
14
+ @rd_rdy && @wr_rdy || @left.closed? || @right.closed?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'surro-gate/pair'
4
+ require 'concurrent'
5
+
6
+ module SurroGate
7
+ class Selector
8
+ def initialize(logger)
9
+ @logger = logger
10
+
11
+ @pairing = Concurrent::Array.new
12
+ @sockets = Concurrent::Array.new
13
+ @reads = Concurrent::Array.new
14
+ @writes = Concurrent::Array.new
15
+
16
+ @mutex = Mutex.new
17
+ end
18
+
19
+ def push(left, right)
20
+ raise TypeError unless left.is_a?(IO) && right.is_a?(IO)
21
+ raise ArgumentError if @pairing.detect { |pair| [left, right].include?(pair.left) || [left, right].include?(pair.right) }
22
+
23
+ left_to_right = SurroGate::Pair.new(left, right)
24
+ right_to_left = SurroGate::Pair.new(right, left)
25
+
26
+ # The method can be called from a different thread
27
+ @mutex.synchronize do
28
+ @pairing.push(left_to_right, right_to_left)
29
+
30
+ @sockets.push(left, right)
31
+ @reads.push(left, right)
32
+ @writes.push(left, right)
33
+ end
34
+
35
+ true
36
+ end
37
+
38
+ def pop(*sockets)
39
+ [@sockets, @reads, @writes].each do |arr|
40
+ arr.delete_if { |sock| sockets.include?(sock) }
41
+ end
42
+
43
+ @pairing.delete_if { |pair| pairing_compare(pair, sockets) }
44
+
45
+ nil
46
+ end
47
+
48
+ def select(timeout)
49
+ begin
50
+ read, write, error = @mutex.synchronize { IO.select(@reads, @writes, @sockets, timeout * 0.001) }
51
+ rescue IOError
52
+ # One of the sockets is closed, Pair#ready? will catch it
53
+ end
54
+
55
+ error.to_a.each do
56
+ ltr = find_pairing(sock, :left)
57
+ rtl = find_pairing(sock, :right)
58
+
59
+ [ltr, rtl].each do |pair|
60
+ %i[@rd_rdy @wr_rdy].each do |ivar|
61
+ pair.instance_variable_set(ivar, true)
62
+ end
63
+ end
64
+ end
65
+
66
+ read.to_a.each do |sock|
67
+ @reads.delete(sock)
68
+ find_pairing(sock, :left).instance_variable_set(:@rd_rdy, true)
69
+ end
70
+
71
+ write.to_a.each do |sock|
72
+ @writes.delete(sock)
73
+ find_pairing(sock, :right).instance_variable_set(:@wr_rdy, true)
74
+ end
75
+
76
+ read.to_a.length + write.to_a.length
77
+ end
78
+
79
+ def each_ready
80
+ @pairing.each do |pair|
81
+ next unless pair.ready?
82
+
83
+ yield(pair.left, pair.right)
84
+
85
+ pair.instance_variable_set(:@rd_rdy, false)
86
+ pair.instance_variable_set(:@wr_rdy, false)
87
+
88
+ @reads.push(pair.left)
89
+ @writes.push(pair.right)
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def find_pairing(sock, direction)
96
+ @pairing.find { |pair| pair.send(direction) == sock }
97
+ end
98
+
99
+ def pairing_compare(pair, sockets)
100
+ sockets.each do |sock|
101
+ return true if [pair.left, pair.right].include?(sock)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SurroGate
2
- VERSION = '0.2.2'.freeze
4
+ VERSION = '1.0.0'.freeze
5
+ HAVE_EXT = RUBY_PLATFORM =~ /linux/ && !defined?(JRUBY_VERSION) && !ENV['SURRO_GATE_NOEXT']
3
6
  end
data/lib/surro-gate.rb CHANGED
@@ -1,14 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'surro-gate/version'
2
- require 'surro-gate/proxy_error'
3
- require 'surro-gate/proxy'
4
4
 
5
- # A generic purpose TCP-to-TCP proxy
5
+ begin
6
+ require 'surro-gate/selector_ext'
7
+ rescue LoadError, NameError # Fall back to IO#select if epoll isn't available
8
+ require 'surro-gate/selector'
9
+ end
10
+
11
+ # A generic purrpose TCP-to-TCP proxy selector
6
12
  module SurroGate
7
13
  class << self
8
- # Initializes a new Proxy instance
9
- # @return [Proxy]
14
+ # Initializes a new Selector instance
15
+ # @return [SurroGate::Selector]
10
16
  def new(logger = nil)
11
- Proxy.new(logger)
17
+ SurroGate::Selector.new(logger)
12
18
  end
13
19
  end
14
20
  end
data/surro-gate.gemspec CHANGED
@@ -1,5 +1,6 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'surro-gate/version'
5
6
 
@@ -9,24 +10,23 @@ Gem::Specification.new do |spec|
9
10
  spec.authors = ['Dávid Halász']
10
11
  spec.email = ['skateman@skateman.eu']
11
12
 
12
- spec.summary = 'A general purrpose TCP-to-TCP proxy written in Ruby'
13
- spec.description = 'A general purrpose TCP-to-TCP proxy written in Ruby'
13
+ spec.summary = 'A generic purrpose TCP-to-TCP proxy in Ruby'
14
+ spec.description = 'A generic purrpose TCP-to-TCP proxy for Ruby implemented using epoll'
14
15
  spec.homepage = 'https://github.com/skateman/surro-gate'
15
16
  spec.license = 'MIT'
16
17
 
17
18
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
19
  f.match(%r{^(test|spec|features)/})
19
20
  end
21
+ spec.extensions = ['ext/surro-gate/extconf.rb'] if SurroGate::HAVE_EXT
20
22
  spec.bindir = 'exe'
21
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
24
  spec.require_paths = ['lib']
23
25
 
24
- spec.add_dependency 'nio4r', '~> 1.2.0'
26
+ spec.add_dependency 'concurrent-ruby'
25
27
 
26
- spec.add_development_dependency 'bundler', '~> 1.13'
27
- spec.add_development_dependency 'codecov', '~> 0.1.0'
28
- spec.add_development_dependency 'nyan-cat-formatter', '~> 0.11'
29
- spec.add_development_dependency 'rake', '~> 10.0'
30
- spec.add_development_dependency 'rspec', '~> 3.0'
31
- spec.add_development_dependency 'simplecov', '~> 0.12'
28
+ spec.add_development_dependency 'bundler'
29
+ spec.add_development_dependency 'rake'
30
+ spec.add_development_dependency 'rake-compiler'
31
+ spec.add_development_dependency 'rspec'
32
32
  end
metadata CHANGED
@@ -1,118 +1,91 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: surro-gate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dávid Halász
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-02-12 00:00:00.000000000 Z
11
+ date: 2018-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: nio4r
14
+ name: concurrent-ruby
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.2.0
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.2.0
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.13'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '1.13'
41
- - !ruby/object:Gem::Dependency
42
- name: codecov
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 0.1.0
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 0.1.0
55
- - !ruby/object:Gem::Dependency
56
- name: nyan-cat-formatter
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '0.11'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '0.11'
40
+ version: '0'
69
41
  - !ruby/object:Gem::Dependency
70
42
  name: rake
71
43
  requirement: !ruby/object:Gem::Requirement
72
44
  requirements:
73
- - - "~>"
45
+ - - ">="
74
46
  - !ruby/object:Gem::Version
75
- version: '10.0'
47
+ version: '0'
76
48
  type: :development
77
49
  prerelease: false
78
50
  version_requirements: !ruby/object:Gem::Requirement
79
51
  requirements:
80
- - - "~>"
52
+ - - ">="
81
53
  - !ruby/object:Gem::Version
82
- version: '10.0'
54
+ version: '0'
83
55
  - !ruby/object:Gem::Dependency
84
- name: rspec
56
+ name: rake-compiler
85
57
  requirement: !ruby/object:Gem::Requirement
86
58
  requirements:
87
- - - "~>"
59
+ - - ">="
88
60
  - !ruby/object:Gem::Version
89
- version: '3.0'
61
+ version: '0'
90
62
  type: :development
91
63
  prerelease: false
92
64
  version_requirements: !ruby/object:Gem::Requirement
93
65
  requirements:
94
- - - "~>"
66
+ - - ">="
95
67
  - !ruby/object:Gem::Version
96
- version: '3.0'
68
+ version: '0'
97
69
  - !ruby/object:Gem::Dependency
98
- name: simplecov
70
+ name: rspec
99
71
  requirement: !ruby/object:Gem::Requirement
100
72
  requirements:
101
- - - "~>"
73
+ - - ">="
102
74
  - !ruby/object:Gem::Version
103
- version: '0.12'
75
+ version: '0'
104
76
  type: :development
105
77
  prerelease: false
106
78
  version_requirements: !ruby/object:Gem::Requirement
107
79
  requirements:
108
- - - "~>"
80
+ - - ">="
109
81
  - !ruby/object:Gem::Version
110
- version: '0.12'
111
- description: A general purrpose TCP-to-TCP proxy written in Ruby
82
+ version: '0'
83
+ description: A generic purrpose TCP-to-TCP proxy for Ruby implemented using epoll
112
84
  email:
113
85
  - skateman@skateman.eu
114
86
  executables: []
115
- extensions: []
87
+ extensions:
88
+ - ext/surro-gate/extconf.rb
116
89
  extra_rdoc_files: []
117
90
  files:
118
91
  - ".codeclimate.yml"
@@ -128,9 +101,12 @@ files:
128
101
  - bin/console
129
102
  - bin/setup
130
103
  - codecov.yml
104
+ - ext/surro-gate/extconf.rb
105
+ - ext/surro-gate/selector_ext.c
106
+ - ext/surro-gate/selector_ext.h
131
107
  - lib/surro-gate.rb
132
- - lib/surro-gate/proxy.rb
133
- - lib/surro-gate/proxy_error.rb
108
+ - lib/surro-gate/pair.rb
109
+ - lib/surro-gate/selector.rb
134
110
  - lib/surro-gate/version.rb
135
111
  - surro-gate.gemspec
136
112
  homepage: https://github.com/skateman/surro-gate
@@ -153,8 +129,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
129
  version: '0'
154
130
  requirements: []
155
131
  rubyforge_project:
156
- rubygems_version: 2.5.2
132
+ rubygems_version: 2.7.7
157
133
  signing_key:
158
134
  specification_version: 4
159
- summary: A general purrpose TCP-to-TCP proxy written in Ruby
135
+ summary: A generic purrpose TCP-to-TCP proxy in Ruby
160
136
  test_files: []
@@ -1,129 +0,0 @@
1
- require 'nio'
2
- require 'logger'
3
-
4
- module SurroGate
5
- # This class is responsible for connecting TCP socket pairs and proxying between them.
6
- #
7
- # It uses a lazily-forked thread to handle the non blocking read and write operations.
8
- # If one of the sockets get closed, the proxy automatically cleans up by deregistering
9
- # and closing its pair. When the last socket pair gets cleaned up, the internal thread
10
- # is killed automatically.
11
- #
12
- # The proxy was designed to be highly reusable and it can handle multiple socket pairs.
13
- class Proxy
14
- def initialize(logger)
15
- @mutex = Mutex.new
16
- @reader = NIO::Selector.new
17
- @writer = NIO::Selector.new
18
- @selectors = [@reader, @writer]
19
- @log = logger || Logger.new(STDOUT)
20
- end
21
-
22
- # Registers a pair of socket for proxying.
23
- #
24
- # It also forks the internal thread if it is not running yet.
25
- #
26
- # @raise [ProxyError] when at least one of the pushed sockets is already registered
27
- # @param left [TCPSocket]
28
- # @param right [TCPSocket]
29
- # @yield The block responsible for additional cleanup
30
- # @return the registered socket pair as an array
31
- def push(left, right, &block)
32
- raise ProxyError, 'Socket already handled by the proxy' if includes?(left, right)
33
-
34
- @log.info("Connecting #{left} <-> #{right}")
35
-
36
- @mutex.synchronize do
37
- proxy(left, right, block)
38
- end
39
-
40
- [left, right]
41
- end
42
-
43
- # Blocking wait until the internal thread is doing something useful.
44
- def wait
45
- @thread.join if alive?
46
- end
47
-
48
- # Determine if the internal thread is currently running or not.
49
- def alive?
50
- !@thread.nil? && @thread.alive?
51
- end
52
-
53
- private
54
-
55
- def proxy(left, right, block = nil)
56
- # Register the proxying in both directions
57
- [[left, right], [right, left]].each do |rd, wr|
58
- # Set up monitors for read/write separately
59
- src = @reader.register(rd, :r)
60
- dst = @writer.register(wr, :w)
61
-
62
- # Set up handlers for the reader monitor
63
- src.value = proc do
64
- # Clean up the connection if one of the endpoints gets closed
65
- cleanup(src.io, dst.io, &block) if src.io.closed? || dst.io.closed?
66
- # Do the transmission and return with the bytes transferred
67
- transmit(src.io, dst.io, block) if src.readable? && dst.writable?
68
- end
69
- end
70
-
71
- thread_start unless @reader.empty? || @writer.empty?
72
- end
73
-
74
- def transmit(src, dst, block)
75
- dst.write_nonblock(src.read_nonblock(4096))
76
- rescue # Clean up both sockets if something bad happens
77
- @log.warn("Transmission failure between #{src} <-> #{dst}")
78
- cleanup(src, dst, &block)
79
- end
80
-
81
- def cleanup(*sockets)
82
- # Deregister and close the sockets
83
- sockets.each do |socket|
84
- @selectors.each { |selector| selector.deregister(socket) if selector.registered?(socket) }
85
- socket.close unless socket.closed?
86
- end
87
-
88
- @log.info("Disconnecting #{sockets.join(' <-> ')}")
89
-
90
- yield if block_given?
91
-
92
- # Make sure that the internal thread is stopped if no sockets remain
93
- thread_stop if @reader.empty? && @writer.empty?
94
- end
95
-
96
- def thread_start
97
- @log.debug('Starting the internal thread')
98
- @thread ||= Thread.new do
99
- loop do
100
- reactor
101
- end
102
- end
103
- end
104
-
105
- def thread_stop
106
- return if @thread.nil?
107
- @log.debug('Stopping the internal thread')
108
- thread = @thread
109
- @thread = nil
110
- thread.kill
111
- end
112
-
113
- def reactor
114
- # Atomically get an array of readable monitors while also polling for writables
115
- monitors = @mutex.synchronize do
116
- @writer.select(0.1)
117
- @reader.select(0.1) || []
118
- end
119
- # Call each transmission proc and collect the results
120
- monitors.map { |m| m.value.call }
121
- end
122
-
123
- def includes?(*sockets)
124
- sockets.any? do |socket|
125
- @selectors.any? { |selector| selector.registered?(socket) }
126
- end
127
- end
128
- end
129
- end
@@ -1,5 +0,0 @@
1
- module SurroGate
2
- # This error is raised when at least one of the pushed sockets is already registered
3
- class ProxyError < StandardError
4
- end
5
- end