reverse-tunnel 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,140 @@
1
+ module ReverseTunnel
2
+ class Message
3
+
4
+ def self.type
5
+ name.split("::").last.gsub(/^([A-Z])/) {$1.downcase}.gsub(/([A-Z])/) { "_#{$1.downcase}" }.to_sym
6
+ end
7
+ def type
8
+ self.class.type
9
+ end
10
+
11
+ @@types = [:open_tunnel, :open_session, :data, :ping]
12
+ def self.types
13
+ @@types
14
+ end
15
+
16
+ types.each do |type|
17
+ define_method "#{type}?" do
18
+ self.type == type
19
+ end
20
+ end
21
+
22
+ def self.type_id
23
+ types.index(type)
24
+ end
25
+ def type_id
26
+ self.class.type_id
27
+ end
28
+
29
+ def pack
30
+ [type_id, *payload].to_msgpack
31
+ end
32
+
33
+ def self.create(type)
34
+ type = types.at(type) if Fixnum === type
35
+ const_get(type.to_s.gsub(/(^|_)(.)/) { $2.capitalize }).new
36
+ end
37
+
38
+ class Unpacker
39
+
40
+ attr_reader :unpacker
41
+
42
+ def initialize
43
+ @unpacker = MessagePack::Unpacker.new
44
+ end
45
+
46
+ def feed(data)
47
+ unpacker.feed data
48
+ end
49
+
50
+ include Enumerable
51
+ def each(&block)
52
+ unpacker.each do |data|
53
+ type_id = data.shift
54
+ payload = data
55
+
56
+ Message.create(type_id).tap do |message|
57
+ message.load(payload)
58
+
59
+ yield message
60
+ end
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ def self.unpack(data)
67
+ Unpacker.new.tap do |packer|
68
+ packer.feed data
69
+ end.first
70
+ end
71
+
72
+ class Data < Message
73
+ attr_accessor :session_id, :data
74
+
75
+ def initialize(session_id = nil, data = nil)
76
+ self.session_id = session_id
77
+ self.data = data
78
+ end
79
+
80
+ def payload
81
+ [session_id, data]
82
+ end
83
+
84
+ def load(payload)
85
+ self.session_id, self.data = payload
86
+ end
87
+ end
88
+
89
+ class OpenSession < Message
90
+ attr_accessor :session_id
91
+
92
+ def initialize(session_id = nil)
93
+ self.session_id = session_id
94
+ end
95
+
96
+ def payload
97
+ [session_id]
98
+ end
99
+
100
+ def load(payload)
101
+ self.session_id = payload.first
102
+ end
103
+
104
+ end
105
+
106
+ class OpenTunnel < Message
107
+ attr_accessor :token
108
+
109
+ def initialize(token = nil)
110
+ self.token = token
111
+ end
112
+
113
+ def payload
114
+ [token]
115
+ end
116
+
117
+ def load(payload)
118
+ self.token = payload.first
119
+ end
120
+ end
121
+
122
+ class Ping < Message
123
+ attr_accessor :sequence_number
124
+
125
+ def initialize(sequence_number = nil)
126
+ self.sequence_number = sequence_number
127
+ end
128
+
129
+ def payload
130
+ [sequence_number]
131
+ end
132
+
133
+ def load(payload)
134
+ self.sequence_number = payload.first
135
+ end
136
+ end
137
+
138
+ end
139
+ end
140
+
@@ -0,0 +1,406 @@
1
+ require 'evma_httpserver'
2
+ require 'json'
3
+
4
+ module ReverseTunnel
5
+ class Server
6
+
7
+ class ApiServer < EM::Connection
8
+ include EM::HttpServer
9
+
10
+ attr_accessor :server
11
+
12
+ def initialize(server)
13
+ @server = server
14
+ end
15
+
16
+ def post_init
17
+ super
18
+ no_environment_strings
19
+ end
20
+
21
+ def process_http_request
22
+ # the http request details are available via the following instance variables:
23
+ # @http_protocol
24
+ # @http_request_method
25
+ # @http_cookie
26
+ # @http_if_none_match
27
+ # @http_content_type
28
+ # @http_path_info
29
+ # @http_request_uri
30
+ # @http_query_string
31
+ # @http_post_content
32
+ # @http_headers
33
+
34
+ ReverseTunnel.logger.debug "Process http request #{@http_request_uri}"
35
+
36
+ response = EM::DelegatedHttpResponse.new(self)
37
+ response.status = 200
38
+ response.content_type 'application/json'
39
+
40
+ begin
41
+
42
+ case @http_request_uri
43
+ when %r{^/tunnels(.json)?$}
44
+ case @http_request_method
45
+ when "GET"
46
+ response.content = server.tunnels.to_json
47
+ when "POST"
48
+ params = @http_post_content ? JSON.parse(@http_post_content) : {}
49
+ tunnel = server.tunnels.create params
50
+ response.content = tunnel.to_json
51
+ end
52
+ when %r{^/tunnels/([0-9A-F]+)(.json)?$}
53
+ tunnel_id = $1
54
+ tunnel = server.tunnels.find(tunnel_id)
55
+
56
+ if tunnel
57
+ case @http_request_method
58
+ when "GET"
59
+ response.content = tunnel.to_json
60
+ when "DELETE"
61
+ tunnel = server.tunnels.destroy(tunnel_id)
62
+ response.content = tunnel.to_json
63
+ end
64
+ end
65
+ else
66
+ end
67
+ rescue => e
68
+ ReverseTunnel.logger.error "Error in http request processing: #{e}"
69
+ response.status = 500
70
+ end
71
+
72
+ if response.content.nil?
73
+ response.status = 404
74
+ end
75
+
76
+ response.send_response
77
+ end
78
+
79
+ end
80
+
81
+ class Tunnel
82
+
83
+ attr_accessor :token, :local_port, :local_host
84
+
85
+ def initialize(attributes)
86
+ attributes.each { |k,v| send "#{k}=", v }
87
+ end
88
+
89
+ def local_host
90
+ @local_host ||= "127.0.0.1"
91
+ end
92
+
93
+ attr_accessor :connection
94
+
95
+ def connection=(connection)
96
+ if @connection and @connection != connection
97
+ @connection.close_connection
98
+ local_connections.each(&:close_connection)
99
+ end
100
+
101
+ @connection = connection
102
+
103
+ if @connection
104
+ open
105
+ end
106
+ end
107
+
108
+ def connection_closed(connection)
109
+ self.connection = nil if self.connection == connection
110
+ end
111
+
112
+ attr_accessor :local_server
113
+
114
+ def close
115
+ if local_server
116
+ ReverseTunnel.logger.info "Close local connections on #{local_port}"
117
+ EventMachine.stop_server local_server
118
+ self.local_server = nil
119
+ end
120
+
121
+ if connection
122
+ ReverseTunnel.logger.info "Close tunnel connection #{token}"
123
+ self.connection.tap do |connection|
124
+ @connection = nil
125
+ connection.close_connection
126
+ end
127
+ end
128
+ end
129
+
130
+ def open
131
+ unless local_server
132
+ ReverseTunnel.logger.info "Listen on #{local_host}:#{local_port} for #{token}"
133
+ self.local_server = EventMachine.start_server local_host, local_port, LocalConnection, self
134
+ end
135
+ rescue => e
136
+ ReverseTunnel.logger.error "Can't listen on #{local_host}:#{local_port} for #{token} : #{e}"
137
+ end
138
+
139
+ def open_session(session_id)
140
+ if connection
141
+ ReverseTunnel.logger.debug "Send open session #{session_id}"
142
+ connection.send_data Message::OpenSession.new(session_id).pack
143
+ end
144
+ end
145
+
146
+ def ping_received(ping)
147
+ ReverseTunnel.logger.debug "Receive ping #{token}/#{ping.sequence_number}"
148
+ connection.send_data Message::Ping.new(ping.sequence_number).pack
149
+ end
150
+
151
+ def send_data(session_id, data)
152
+ if connection
153
+ ReverseTunnel.logger.debug "Send data to local connection #{session_id}"
154
+ connection.send_data Message::Data.new(session_id,data).pack
155
+ end
156
+ end
157
+
158
+ def local_connections
159
+ @local_connections ||= []
160
+ end
161
+
162
+ def receive_data(session_id, data)
163
+ local_connection = local_connections.find { |c| c.session_id == session_id }
164
+ if local_connection
165
+ ReverseTunnel.logger.debug "Send data for local connection #{session_id}"
166
+ local_connection.send_data data
167
+ end
168
+ end
169
+
170
+ def next_session_id
171
+ @next_session_id ||= 0
172
+ @next_session_id += 1
173
+ end
174
+
175
+ def to_json(*args)
176
+ { :token => token, :local_port => local_port }.tap do |attributes|
177
+ attributes[:connection] = connection.to_json if connection
178
+ end.to_json(*args)
179
+ end
180
+
181
+ end
182
+
183
+ class TunnelConnection < EventMachine::Connection
184
+ attr_accessor :server, :created_at
185
+
186
+ def initialize(server)
187
+ @server = server
188
+ end
189
+
190
+ def post_init
191
+ ReverseTunnel.logger.info "New tunnel connection from #{peer}"
192
+ self.created_at = Time.now
193
+
194
+ EventMachine.add_timer(10) do
195
+ unless open? or closed?
196
+ ReverseTunnel.logger.info "Force close of unopened tunnel connection from #{peer}"
197
+ close_connection
198
+ end
199
+ end
200
+ end
201
+
202
+ def message_unpacker
203
+ @message_unpacker ||= Message::Unpacker.new
204
+ end
205
+
206
+ def receive_data(data)
207
+ message_unpacker.feed data
208
+
209
+ message_unpacker.each do |message|
210
+ if message.data?
211
+ tunnel.receive_data message.session_id, message.data
212
+ elsif message.open_tunnel?
213
+ open_tunnel message.token
214
+ elsif message.ping?
215
+ tunnel.ping_received message
216
+ end
217
+ end
218
+ end
219
+
220
+ attr_accessor :tunnel
221
+
222
+ def open?
223
+ !!tunnel
224
+ end
225
+
226
+ def closed?
227
+ @closed ||= false
228
+ end
229
+
230
+ def close_connection(after_writing = false)
231
+ super
232
+ @closed = true
233
+ end
234
+
235
+ def open_tunnel(token)
236
+ self.tunnel = server.tunnels.find token
237
+ if tunnel
238
+ ReverseTunnel.logger.info "Open tunnel #{token}"
239
+ tunnel.connection = self
240
+ else
241
+ ReverseTunnel.logger.warn "Refuse tunnel connection #{token}"
242
+ close_connection
243
+ end
244
+ end
245
+
246
+ def unbind
247
+ tunnel.connection_closed self if tunnel
248
+ end
249
+
250
+ def peer
251
+ @peer ||=
252
+ begin
253
+ port, ip = Socket.unpack_sockaddr_in(get_peername)
254
+ "#{ip}:#{port}"
255
+ end
256
+ end
257
+
258
+ def to_json(*args)
259
+ { :peer => peer, :created_at => created_at }.json
260
+ end
261
+
262
+ end
263
+
264
+ class LocalConnection < EventMachine::Connection
265
+ attr_accessor :tunnel
266
+
267
+ def initialize(tunnel)
268
+ @tunnel = tunnel
269
+ end
270
+
271
+ def post_init
272
+ ReverseTunnel.logger.debug "New local connection"
273
+ tunnel.local_connections << self
274
+ tunnel.open_session(session_id)
275
+ end
276
+
277
+ def receive_data(data)
278
+ ReverseTunnel.logger.debug "Received data in local #{session_id}"
279
+ tunnel.send_data session_id, data
280
+ end
281
+
282
+ def session_id
283
+ @session_id ||= tunnel.next_session_id
284
+ end
285
+
286
+ def unbind
287
+ ReverseTunnel.logger.debug "Close local connection"
288
+ tunnel.local_connections.delete self
289
+ end
290
+
291
+ end
292
+
293
+ def tunnels
294
+ @tunnels ||= Tunnels.new
295
+ end
296
+
297
+ def local_host=(local_host)
298
+ tunnels.local_host = local_host
299
+ end
300
+
301
+ def local_port_range=(local_port_range)
302
+ tunnels.local_port_range = local_port_range
303
+ end
304
+
305
+ class Tunnels
306
+
307
+ def tunnels
308
+ @tunnels ||= []
309
+ end
310
+
311
+ attr_accessor :local_host, :local_port_range
312
+
313
+ def local_port_range
314
+ @local_port_range ||= 10000..10200
315
+ end
316
+
317
+ def find(token)
318
+ tunnels.find { |t| t.token == token }
319
+ end
320
+
321
+ def create(attributes = {})
322
+ attributes = default_attributes.merge(attributes).merge(:local_host => local_host)
323
+ Tunnel.new(attributes).tap do |tunnel|
324
+ ReverseTunnel.logger.info "Create tunnel #{tunnel.inspect}"
325
+ tunnels << tunnel
326
+ end
327
+ end
328
+
329
+ def destroy(token)
330
+ tunnel = find(token)
331
+ if tunnel
332
+ tunnel.close
333
+ tunnels.delete tunnel
334
+ tunnel
335
+ end
336
+ end
337
+
338
+ def default_attributes
339
+ { "token" => create_token, "local_port" => available_local_port }
340
+ end
341
+
342
+ def create_token
343
+ rand(10e32).to_s(16).ljust(28, '0').upcase
344
+ end
345
+
346
+ def used_local_ports
347
+ tunnels.map(&:local_port)
348
+ end
349
+
350
+ def available_local_ports
351
+ local_port_range.to_a - used_local_ports
352
+ end
353
+
354
+ def available_local_port
355
+ available_local_ports.tap do |ports|
356
+ ports.shuffle if respond_to?(:shuffle)
357
+ end.first
358
+ end
359
+
360
+ def to_json(*args)
361
+ tunnels.to_json(*args)
362
+ end
363
+
364
+ end
365
+
366
+ def start
367
+ tunnels.create "token" => "6B833D3F561369156820B4240C7C2657", "local_port" => 10000
368
+
369
+ EventMachine.run do
370
+ start_server
371
+ start_api
372
+ end
373
+ end
374
+
375
+ attr_accessor :server_host, :server_port
376
+
377
+ def server_host
378
+ @server_host ||= "0.0.0.0"
379
+ end
380
+
381
+ def server_port
382
+ @server_port ||= 4893
383
+ end
384
+
385
+ def start_server
386
+ ReverseTunnel.logger.info "Wait tunnel connections on #{server_host}:#{server_port}"
387
+ EventMachine.start_server server_host, server_port, TunnelConnection, self
388
+ end
389
+
390
+ attr_accessor :api_host, :api_port
391
+
392
+ def api_host
393
+ @api_host ||= "127.0.0.1"
394
+ end
395
+
396
+ def api_port
397
+ @api_port ||= 4894
398
+ end
399
+
400
+ def start_api
401
+ ReverseTunnel.logger.info "Wait api requests #{api_host}:#{api_port}"
402
+ EventMachine.start_server api_host, api_port, ApiServer, self
403
+ end
404
+
405
+ end
406
+ end