ffwd-tunnel 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5df1c67ae1513904503ff2db9e7071ec1fff649a
4
+ data.tar.gz: 07ec0e4930a3f2b8af7c850dd799d73775f83c08
5
+ SHA512:
6
+ metadata.gz: e463d7728abf1f437ef965038f41267f4a93161617d9ed77ea4ebdee3b7018e374c12d29a9fb9d0c68624099b81c09487df74aafe2cbe199b0a82be15956e6ba
7
+ data.tar.gz: 21bfd3d35a7bbef9b6e979fe7b0ab9cbc7a2cc64b5990fe18f3d9a04b40f928a7d329ab454eaaed73fd77b7d62d4b764d6b34b55664c388e0e2c3b6aa7386bc1
@@ -0,0 +1,516 @@
1
+ #!/usr/bin/env python
2
+ """A reference tunneling proxy for FFWD."""
3
+
4
+ import time
5
+ import json
6
+ import asyncore
7
+ import sys
8
+ import socket
9
+ import struct
10
+ import logging
11
+ import errno
12
+ import argparse
13
+
14
+ log = logging.getLogger(__name__)
15
+
16
+ TEXT = object()
17
+ BINARY = object()
18
+ UDP = 'udp'
19
+ TCP = 'tcp'
20
+ RECV_MAX = 8192
21
+ DEFAULT_PROTOCOL = "text"
22
+ PROTOCOL_MAX_DATALEN = 2 ** 16
23
+ DEFAULT_PORT = 9000
24
+
25
+
26
+ class _BindUDP(asyncore.dispatcher):
27
+ def __init__(self, family, tunnel, bindaddress, bindport):
28
+ asyncore.dispatcher.__init__(self)
29
+ self.create_socket(family, socket.SOCK_DGRAM)
30
+ self.set_reuse_addr()
31
+ self.bind((bindaddress, bindport))
32
+
33
+ self._family = family
34
+ self._tunnel = tunnel
35
+ self._port = bindport
36
+
37
+ def writable(self):
38
+ return False
39
+
40
+ def handle_read(self):
41
+ """implement asyncore.dispatcher#handle_read"""
42
+ data, addr = self.recvfrom(RECV_MAX)
43
+
44
+ self._tunnel.client_data(
45
+ socket.SOCK_DGRAM, self._family, self._port, addr, data)
46
+
47
+ def receive_data(self, addr, data):
48
+ family, addr, port = addr
49
+ self.sendto(data, (addr, port))
50
+
51
+
52
+ class _BindTCP(asyncore.dispatcher):
53
+ class Connection(asyncore.dispatcher_with_send):
54
+ def __init__(self, bind, sock, addr):
55
+ asyncore.dispatcher_with_send.__init__(self, sock)
56
+ self.bind = bind
57
+ self.addr = addr
58
+
59
+ def handle_close(self):
60
+ """implement asyncore.dispatcher_with_send#handle_close."""
61
+ self.bind.conn_handle_close(self.addr)
62
+
63
+ def handle_error(self):
64
+ """implement asyncore.dispatcher_with_send#handle_error."""
65
+
66
+ def handle_read(self):
67
+ """implement asyncore.dispatcher#handle_read."""
68
+ self.bind.conn_handle_data(self.addr, self.recv(RECV_MAX))
69
+
70
+ def __init__(self, family, tunnel, bindaddress, bindport):
71
+ asyncore.dispatcher.__init__(self)
72
+ self.create_socket(family, socket.SOCK_STREAM)
73
+ self.set_reuse_addr()
74
+ self.bind((bindaddress, bindport))
75
+ self.listen(5)
76
+
77
+ self._family = family
78
+ self._tunnel = tunnel
79
+ self._port = bindport
80
+ self._connections = dict()
81
+
82
+ def handle_close(self):
83
+ """implement asyncore.dispatcher#handle_close."""
84
+ self.close()
85
+
86
+ def handle_accept(self):
87
+ """implement asyncore.dispatcher#handle_accept."""
88
+ pair = self.accept()
89
+
90
+ if pair is not None:
91
+ sock, addr = pair
92
+ log.debug("open tcp/%d: %s:%d", self._port, addr[0], addr[1])
93
+ self._connections[addr] = self.Connection(self, sock, addr)
94
+ self.client_state(addr, Protocol.OPEN)
95
+
96
+ def conn_handle_data(self, addr, data):
97
+ """Receive data from TCP connections."""
98
+ self.client_data(addr, data)
99
+
100
+ def conn_handle_close(self, addr):
101
+ """Remove the client connection associated with addr."""
102
+ log.debug("closed tcp/%d: %s:%d", self._port, addr[0], addr[1])
103
+
104
+ client = self._connections[addr]
105
+ client.close()
106
+
107
+ del self._connections[addr]
108
+ self.client_state(addr, Protocol.CLOSE)
109
+
110
+ def client_data(self, addr, data):
111
+ self._tunnel.client_data(
112
+ socket.SOCK_STREAM, self._family, self._port, addr, data)
113
+
114
+ def client_state(self, addr, state):
115
+ self._tunnel.client_state(
116
+ socket.SOCK_STREAM, self._family, self._port, addr, state)
117
+
118
+ def close(self):
119
+ for client in self._connections.values():
120
+ client.close()
121
+
122
+ self._connections = {}
123
+ asyncore.dispatcher.close(self)
124
+
125
+ def receive_data(self, addr, data):
126
+ try:
127
+ client = self._connections[addr]
128
+ except KeyError:
129
+ log.error("no such client: %s", addr)
130
+ self.close()
131
+ return
132
+
133
+ client.send(data)
134
+
135
+
136
+ class _LineProtocol(object):
137
+ delimiter = '\n'
138
+
139
+ buffer_limit = 1048576
140
+
141
+ def __init__(self):
142
+ self._lp_buffer = ""
143
+ self._lp_size = 0
144
+ self._lp_limit = self.buffer_limit
145
+
146
+ def set_mode(self, size):
147
+ self._lp_size = size
148
+
149
+ def handle_read(self):
150
+ """implement asyncore.dispatcher#handle_read."""
151
+
152
+ data = self.recv(RECV_MAX)
153
+
154
+ if len(self._lp_buffer) + len(data) > self._lp_limit:
155
+ log.error("buffer limit reached, closing connection")
156
+ self.close()
157
+ return
158
+
159
+ if self._lp_size == 0:
160
+ self._handle_line(data)
161
+ else:
162
+ self._handle_text(data)
163
+
164
+ def _handle_line(self, data):
165
+ while True:
166
+ try:
167
+ i = data.index(self.delimiter)
168
+ except ValueError:
169
+ break
170
+
171
+ try:
172
+ self.receive_line(self._lp_buffer + data[:i])
173
+ except:
174
+ log.error("receive_line failed", exc_info=sys.exc_info())
175
+ self.close()
176
+ return
177
+
178
+ self._lp_buffer = ""
179
+ data = data[i + 2:]
180
+
181
+ if len(data) > 0:
182
+ self._lp_buffer += data
183
+
184
+ def _handle_text(self, data):
185
+ self._lp_buffer += data
186
+
187
+ while len(self._lp_buffer) >= self._lp_size:
188
+ size = self._lp_size
189
+ self._lp_size = 0
190
+
191
+ try:
192
+ self.receive_text(self._lp_buffer[:size])
193
+ except:
194
+ log.error("failed to receive text", exc_info=sys.exc_info())
195
+ self.close()
196
+ return
197
+
198
+ self._lp_buffer = self._lp_buffer[size:]
199
+
200
+ def send_line(self, line):
201
+ """Send a line of data using the specified delimiter."""
202
+ self.send(line + self.delimiter)
203
+
204
+
205
+ BIND_PROTOCOLS = {
206
+ socket.SOCK_STREAM: _BindTCP,
207
+ socket.SOCK_DGRAM: _BindUDP,
208
+ }
209
+
210
+
211
+ class Protocol(object):
212
+ ST_HEADER = struct.Struct("!HHHBB")
213
+
214
+ ST_STATE = struct.Struct("!H")
215
+ ST_PEER_ADDR_AF_INET = struct.Struct("!4sH")
216
+ ST_PEER_ADDR_AF_INET6 = struct.Struct("!16sH")
217
+
218
+ STATE = 0x0000
219
+ DATA = 0x0001
220
+
221
+ OPEN = 0x0000
222
+ CLOSE = 0x0001
223
+
224
+ PACKET_TYPES = {
225
+ STATE: ST_STATE,
226
+ DATA: None,
227
+ }
228
+
229
+ def __init__(self, conn):
230
+ self._header = None
231
+ self._c = conn
232
+
233
+ def parse_st_addr(self, family):
234
+ if family == socket.AF_INET:
235
+ return self.ST_PEER_ADDR_AF_INET
236
+
237
+ if family == socket.AF_INET6:
238
+ return self.ST_PEER_ADDR_AF_INET6
239
+
240
+ raise Exception("Unsupported family: %d" % (family))
241
+
242
+ def peer_addr_pack(self, family, addr):
243
+ st_addr = self.parse_st_addr(family)
244
+ ip, port = addr
245
+ ip = socket.inet_pton(family, ip)
246
+ return st_addr.pack(ip, port), st_addr.size
247
+
248
+ def peer_addr_unpack(self, family, data):
249
+ st_addr = self.parse_st_addr(family)
250
+ ip, port = st_addr.unpack(data[:st_addr.size])
251
+ ip = socket.inet_ntop(family, ip)
252
+ return (ip, port), st_addr.size
253
+
254
+ def client_data(self, protocol, family, port, addr, data):
255
+ """Send client DATA."""
256
+ addr, addr_size = self.peer_addr_pack(family, addr)
257
+
258
+ length = self.ST_HEADER.size + addr_size + len(data)
259
+
260
+ if length > PROTOCOL_MAX_DATALEN:
261
+ raise Exception("Maximum possible frame length exceeded")
262
+
263
+ header = self.ST_HEADER.pack(length, self.DATA, port, family, protocol)
264
+
265
+ frame = header + addr + data
266
+ self._c.send(frame)
267
+
268
+ def client_state(self, protocol, family, port, addr, state):
269
+ """Send a client STATE update."""
270
+ addr_data, addr_size = self.peer_addr_pack(family, addr)
271
+
272
+ length = self.ST_HEADER.size + addr_size + self.ST_STATE.size
273
+
274
+ if length > PROTOCOL_MAX_DATALEN:
275
+ raise Exception("Maximum possible frame length exceeded")
276
+
277
+ header_data = self.ST_HEADER.pack(
278
+ length, self.STATE, port, family, protocol)
279
+ state_data = self.ST_STATE.pack(state)
280
+
281
+ frame = header_data + addr_data + state_data
282
+ self._c.send(frame)
283
+
284
+ def setup(self):
285
+ self._c.set_mode(self.ST_HEADER.size)
286
+
287
+ def receive_line(self, line):
288
+ raise Exception("did not expect line")
289
+
290
+ def receive_text(self, data):
291
+ if self._header is None:
292
+ self._header = self.ST_HEADER.unpack(data)
293
+ self._c.set_mode(self._header[0] - self.ST_HEADER.size)
294
+ return
295
+
296
+ length, frame_type, port, family, protocol = self._header
297
+ addr, addr_size = self.peer_addr_unpack(family, data)
298
+ rest = data[addr_size:]
299
+
300
+ if frame_type == self.DATA:
301
+ tunnel_id = (family, protocol, port)
302
+ self._c.receive_data(tunnel_id, addr, rest)
303
+ else:
304
+ raise Exception("Unexpected frame type: %d" % (frame_type))
305
+
306
+ self._c.set_mode(self.ST_HEADER.size)
307
+ self._header = None
308
+
309
+
310
+ class TunnelClient(_LineProtocol, asyncore.dispatcher_with_send):
311
+ def __init__(self, metadata, addr):
312
+ asyncore.dispatcher_with_send.__init__(self)
313
+ _LineProtocol.__init__(self)
314
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
315
+ self.connect(addr)
316
+
317
+ self._metadata = metadata
318
+ self._tunnels = dict()
319
+ self._protocol = None
320
+ self._addr = addr
321
+
322
+ def handle_error(self):
323
+ """implement asyncore.dispatcher#handle_error."""
324
+ exc_info = sys.exc_info()
325
+ e = exc_info[1]
326
+
327
+ if isinstance(e, socket.error):
328
+ if e.errno == errno.ECONNREFUSED:
329
+ log.warn("connection refused: %s", self._addr)
330
+ self.close()
331
+ return
332
+
333
+ log.error("error: %s", str(exc_info[1]), exc_info=exc_info)
334
+ self.close()
335
+
336
+ def handle_close(self):
337
+ """implement asyncore.dispatcher#handle_close."""
338
+ log.info("closed")
339
+ self.close()
340
+
341
+ def close(self):
342
+ for tunnel in self._tunnels.values():
343
+ tunnel.close()
344
+
345
+ self._tunnels = dict()
346
+ self._protocol = None
347
+
348
+ asyncore.dispatcher_with_send.close(self)
349
+
350
+ def client_data(self, *args):
351
+ if self._protocol is None:
352
+ raise Exception("protocol is not configured")
353
+
354
+ self._protocol.client_data(*args)
355
+
356
+ def client_state(self, *args):
357
+ if self._protocol is None:
358
+ raise Exception("protocol is not configured")
359
+
360
+ self._protocol.client_state(*args)
361
+
362
+ def _receive_binary(self, protocol, bindport, addr, data):
363
+ family = addr[0]
364
+ ip = socket.inet_pton(addr[0], addr[1])
365
+ port = addr[2]
366
+ datasize = len(data)
367
+
368
+ if datasize > PROTOCOL_MAX_DATALEN:
369
+ raise Exception("Maximum data length exceeded")
370
+
371
+ header = self.ST_HEADER.pack(
372
+ protocol, bindport, family, ip, port, datasize)
373
+
374
+ frame = header + data
375
+ self.send(frame)
376
+
377
+ def handle_connect(self):
378
+ """implement asyncore.dispatcher#handle_connect."""
379
+ log.info("connected")
380
+ self.send_line(json.dumps(self._metadata))
381
+
382
+ def receive_line(self, line):
383
+ """implement _LineProtocol#receive_line."""
384
+
385
+ if self._protocol is not None:
386
+ raise Exception("protocol already configured")
387
+
388
+ try:
389
+ self._protocol = self.configure(line)
390
+ except:
391
+ log.error("failed to receive line", exc_info=sys.exc_info())
392
+ self.close()
393
+ return
394
+
395
+ try:
396
+ self._protocol.setup()
397
+ except:
398
+ log.error("failed to setup protocol", exc_info=sys.exc_info())
399
+ self.close()
400
+ return
401
+
402
+ def configure(self, line):
403
+ config = json.loads(line)
404
+ log.info("CONFIG: %s", repr(config))
405
+
406
+ if not self._bind_all(config):
407
+ self.close()
408
+ return
409
+
410
+ return Protocol(self)
411
+
412
+ def receive_text(self, data):
413
+ if self._protocol is None:
414
+ raise Exception("protocol is not configured")
415
+
416
+ try:
417
+ self._protocol.receive_text(data)
418
+ except:
419
+ log.error("failed to receive text", exc_info=sys.exc_info())
420
+ self.close()
421
+
422
+ def receive_data(self, tunnel_id, addr, data):
423
+ try:
424
+ tunnel = self._tunnels[tunnel_id]
425
+ except KeyError:
426
+ log.error("no such tunnel: %s", tunnel_id)
427
+ return
428
+
429
+ tunnel.receive_data(addr, data)
430
+
431
+ def _bind_all(self, config):
432
+ """Bind all protocol/port combinations from configuration."""
433
+ bind = config.get('bind', [])
434
+
435
+ for b in bind:
436
+ tunnel_id = (b['family'], b['protocol'], b['port'])
437
+
438
+ if tunnel_id in self._tunnels:
439
+ log.error("Already bound: %s", repr(tunnel_id))
440
+ continue
441
+
442
+ try:
443
+ family, protocol, port = tunnel_id
444
+ protocol = BIND_PROTOCOLS[protocol]
445
+ self._tunnels[tunnel_id] = protocol(
446
+ family, self, '127.0.0.1', port)
447
+ except:
448
+ log.error("failed to bind: %s", repr(b),
449
+ exc_info=sys.exc_info())
450
+ continue
451
+
452
+ if len(self._tunnels) != len(bind):
453
+ log.error("unable to bind everything: %s", repr(bind))
454
+ return False
455
+
456
+ log.info("ports bound")
457
+ return True
458
+
459
+
460
+ def hostip(string):
461
+ if ':' not in string:
462
+ return (string, DEFAULT_PORT)
463
+
464
+ ip, port = string.split(':', 2)
465
+ return (ip, int(port))
466
+
467
+ parser = argparse.ArgumentParser(sys.argv[0])
468
+
469
+ parser.add_argument(
470
+ "-j", "--json-metadata", dest="json_metadata",
471
+ help="Load metadata from JSON file.", metavar="<file>")
472
+
473
+ parser.add_argument(
474
+ "-d", "--debug", dest="debug", action="store_const", const=True,
475
+ default=False, help="Enable debugging.")
476
+
477
+ parser.add_argument(
478
+ "-c", "--connect", dest="connect", default=('127.0.0.1', DEFAULT_PORT),
479
+ type=hostip, metavar="<host>[:port]", help="Connect to the specified ")
480
+
481
+
482
+ def parse_args(args):
483
+ ns = parser.parse_args(args)
484
+
485
+ log_level = logging.INFO
486
+
487
+ if ns.debug:
488
+ log_level = logging.DEBUG
489
+
490
+ logging.basicConfig(level=log_level)
491
+
492
+ if ns.json_metadata:
493
+ with open(ns.json_metadata) as f:
494
+ ns.metadata = json.load(f)
495
+ else:
496
+ ns.metadata = dict()
497
+
498
+ log.info("Metadata: %s", repr(ns.metadata))
499
+
500
+ return ns
501
+
502
+
503
+ def _main(args):
504
+ ns = parse_args(args)
505
+
506
+ reconnect_timeout = 1.0
507
+
508
+ while True:
509
+ TunnelClient(ns.metadata, ns.connect)
510
+ asyncore.loop()
511
+
512
+ log.info("reconnecting in %ds", reconnect_timeout)
513
+ time.sleep(reconnect_timeout)
514
+
515
+ if __name__ == "__main__":
516
+ sys.exit(_main(sys.argv[1:]))
@@ -0,0 +1,384 @@
1
+ # $LICENSE
2
+ # Copyright 2013-2014 Spotify AB. All rights reserved.
3
+ #
4
+ # The contents of this file are licensed under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ require 'ffwd/core/emitter'
17
+ require 'ffwd/core/interface'
18
+ require 'ffwd/core/reporter'
19
+ require 'ffwd/lifecycle'
20
+ require 'ffwd/logging'
21
+ require 'ffwd/plugin_channel'
22
+ require 'ffwd/tunnel/plugin'
23
+ require 'ffwd/utils'
24
+
25
+ module FFWD::Plugin::Tunnel
26
+ class BinaryProtocol < FFWD::Tunnel::Plugin
27
+ class BindUDP
28
+ class Handle < FFWD::Tunnel::Plugin::Handle
29
+ attr_reader :addr
30
+
31
+ def initialize bind, addr
32
+ @bind = bind
33
+ @addr = addr
34
+ end
35
+
36
+ def send_data data
37
+ @bind.send_data @addr, data
38
+ end
39
+ end
40
+
41
+ def initialize port, family, tunnel, block
42
+ @port = port
43
+ @family = family
44
+ @tunnel = tunnel
45
+ @block = block
46
+ end
47
+
48
+ def send_data addr, data
49
+ @tunnel.send_data Socket::SOCK_DGRAM, @family, @port, addr, data
50
+ end
51
+
52
+ def data! addr, data
53
+ handle = Handle.new self, addr
54
+ @block.call handle, data
55
+ end
56
+ end
57
+
58
+ class BindTCP
59
+ class Handle < FFWD::Tunnel::Plugin::Handle
60
+ attr_reader :addr
61
+
62
+ def initialize bind, addr
63
+ @bind = bind
64
+ @addr = addr
65
+ @close = nil
66
+ @data = nil
67
+ end
68
+
69
+ def send_data data
70
+ @bind.send_data @addr, data
71
+ end
72
+
73
+ def close &block
74
+ @close = block
75
+ end
76
+
77
+ def data &block
78
+ @data = block
79
+ end
80
+
81
+ def recv_close
82
+ return if @close.nil?
83
+ @close.call
84
+ @close = nil
85
+ @data = nil
86
+ end
87
+
88
+ def recv_data data
89
+ return if @data.nil?
90
+ @data.call data
91
+ end
92
+ end
93
+
94
+ def initialize port, family, tunnel, block
95
+ @port = port
96
+ @family = family
97
+ @tunnel = tunnel
98
+ @block = block
99
+ @handles = {}
100
+ end
101
+
102
+ def open addr
103
+ raise "Already open: #{addr}" if @handles[addr]
104
+ handle = @handles[addr] = Handle.new self, addr
105
+ @block.call handle
106
+ end
107
+
108
+ def close addr
109
+ raise "Not open: #{addr}" unless handle = @handles[addr]
110
+ handle.recv_close
111
+ @handles.delete addr
112
+ end
113
+
114
+ def data addr, data
115
+ unless handle = @handles[addr]
116
+ raise "Not available: #{addr}"
117
+ end
118
+
119
+ handle.recv_data data
120
+ end
121
+
122
+ def send_data addr, data
123
+ @tunnel.send_data Socket::SOCK_STREAM, @family, @port, addr, data
124
+ end
125
+ end
126
+
127
+ include FFWD::Logging
128
+ include FFWD::Lifecycle
129
+
130
+ Header = Struct.new(:length, :type, :port, :family, :protocol)
131
+ HeaderFormat = 'nnnCC'
132
+ HeaderSize = 8
133
+
134
+ PeerAddrAfInet = Struct.new(:ip, :port)
135
+ PeerAddrAfInetFormat = "a4n"
136
+ PeerAddrAfInetSize = 6
137
+
138
+ PeerAddrAfInet6 = Struct.new(:ip, :port)
139
+ PeerAddrAfInet6Format = "a16n"
140
+ PeerAddrAfInet6Size = 18
141
+
142
+ State = Struct.new(:state)
143
+ StateFormat = 'n'
144
+ StateSize = 2
145
+
146
+ STATE = 0x0000
147
+ DATA = 0x0001
148
+
149
+ OPEN = 0x0000
150
+ CLOSE = 0x0001
151
+
152
+ def initialize core, c
153
+ @c = c
154
+ @tcp_bind = {}
155
+ @udp_bind = {}
156
+ @input = FFWD::PluginChannel.build 'tunnel_input'
157
+
158
+ @metadata = nil
159
+ @processor = nil
160
+ @channel_id = nil
161
+ @statistics_id = nil
162
+ @header = nil
163
+
164
+ starting do
165
+ raise "no metadata" if @metadata.nil?
166
+
167
+ if host = @metadata[:host]
168
+ @statistics_id = "tunnel/#{host}"
169
+ @channel_id = "tunnel.input/#{host}"
170
+ else
171
+ @statistics_id = "tunnel/#{@c.get_peer}"
172
+ @channel_id = "tunnel.input/#{@c.get_peer}"
173
+ end
174
+
175
+ # setup a small core
176
+ emitter = FFWD::Core::Emitter.build @core.output, @metadata
177
+ @processor = FFWD::Core::Processor.build @input, emitter, @core.processors
178
+
179
+ @reporter = FFWD::Core::Reporter.new [@input, @processor]
180
+
181
+ if @core.debug
182
+ @core.debug.monitor @channel_id, @input, FFWD::Debug::Input
183
+ end
184
+
185
+ if @core.statistics
186
+ @core.statistics.register @statistics_id, @reporter
187
+ end
188
+ end
189
+
190
+ stopping do
191
+ if @core.statistics and @statistics_id
192
+ @core.statistics.unregister @statistics_id
193
+ @statistics_id = nil
194
+ end
195
+
196
+ @metadata = nil
197
+ @processor = nil
198
+ @channel_id = nil
199
+ @tcp_bind = {}
200
+ @udp_bind = {}
201
+ end
202
+
203
+ @core = core.reconnect @input
204
+
205
+ @core.tunnel_plugins.each do |t|
206
+ instance = t.setup @core, self
207
+ instance.depend_on self
208
+ end
209
+
210
+ @input.depend_on self
211
+ @core.depend_on self
212
+ end
213
+
214
+ def tcp port, &block
215
+ @tcp_bind[[port, Socket::AF_INET]] = BindTCP.new(
216
+ port, Socket::AF_INET, self, block)
217
+ end
218
+
219
+ def udp port, &block
220
+ @udp_bind[[port, Socket::AF_INET]] = BindUDP.new(
221
+ port, Socket::AF_INET, self, block)
222
+ end
223
+
224
+ def read_metadata data
225
+ d = {}
226
+
227
+ d[:tags] = FFWD.merge_sets @core.tags, data["tags"]
228
+ d[:attributes] = FFWD.merge_sets @core.attributes, data["attributes"]
229
+
230
+ if host = data["host"]
231
+ d[:host] = host
232
+ end
233
+
234
+ if ttl = data["ttl"]
235
+ d[:ttl] = ttl
236
+ end
237
+
238
+ d
239
+ end
240
+
241
+ def send_config
242
+ response = {}
243
+
244
+ tcp = @tcp_bind.keys.map{|port, family|
245
+ {:protocol => Socket::SOCK_STREAM,
246
+ :family => family,
247
+ :port => port}}
248
+ udp = @udp_bind.keys.map{|port, family|
249
+ {:protocol => Socket::SOCK_DGRAM,
250
+ :family => family,
251
+ :port => port}}
252
+
253
+ response[:bind] = tcp + udp
254
+
255
+ response = JSON.dump(response)
256
+ @c.send_data "#{response}\n"
257
+ end
258
+
259
+ def receive_metadata data
260
+ @metadata = read_metadata data
261
+ start
262
+ send_config
263
+ end
264
+
265
+ def receive_line line
266
+ raise "already have metadata" if @metadata
267
+ receive_metadata JSON.load(line)
268
+ @c.set_text_mode HeaderSize
269
+ end
270
+
271
+ def parse_addr_format family
272
+ if family == Socket::AF_INET
273
+ return PeerAddrAfInetFormat, PeerAddrAfInetSize
274
+ end
275
+
276
+ if family == Socket::AF_INET6
277
+ return PeerAddrAfInet6Format, PeerAddrAfInet6Size
278
+ end
279
+
280
+ raise "Unsupported address family: #{family}"
281
+ end
282
+
283
+ def peer_addr_pack family, addr
284
+ format, size = parse_addr_format family
285
+ return addr.pack(format), size
286
+ end
287
+
288
+ def peer_addr_unpack family, data
289
+ format, size = parse_addr_format family
290
+ return data[0,size].unpack(format), size
291
+ end
292
+
293
+ def receive_frame header, addr, data
294
+ if header.type == DATA
295
+ tunnel_data header, addr, data
296
+ return
297
+ end
298
+
299
+ if header.type == STATE
300
+ state = data.unpack(StateFormat)[0]
301
+ tunnel_state header, addr, state
302
+ return
303
+ end
304
+ end
305
+
306
+ def receive_binary_data data
307
+ if @header
308
+ addr, addr_size = peer_addr_unpack @header.family, data
309
+ data = data[addr_size,data.size - addr_size]
310
+ receive_frame @header, addr, data
311
+ @header = nil
312
+ @c.set_text_mode HeaderSize
313
+ return
314
+ end
315
+
316
+ fields = data.unpack HeaderFormat
317
+ @header = Header.new(*fields)
318
+ rest = @header.length - HeaderSize
319
+ @c.set_text_mode rest
320
+ end
321
+
322
+ def send_data protocol, family, port, addr, data
323
+ addr_data, addr_size = peer_addr_pack family, addr
324
+ length = HeaderSize + addr_size + data.size
325
+ # Struct.new(:length, :type, :port, :family, :protocol)
326
+ header_data = [
327
+ length, DATA, port, family, protocol].pack HeaderFormat
328
+ frame = header_data + addr_data + data
329
+ @c.send_data frame
330
+ end
331
+
332
+ def tunnel_data header, addr, data
333
+ if header.protocol == Socket::SOCK_DGRAM
334
+ if udp = @udp_bind[[header.port, header.family]]
335
+ udp.data! addr, data
336
+ end
337
+
338
+ return
339
+ end
340
+
341
+ if header.protocol == Socket::SOCK_STREAM
342
+ unless bind = @tcp_bind[[header.port, header.family]]
343
+ log.error "Nothing listening on tcp/#{header.port}"
344
+ return
345
+ end
346
+
347
+ bind.data addr, data
348
+ return
349
+ end
350
+
351
+ log.error "DATA: Unsupported protocol: #{header.protocol}"
352
+ end
353
+
354
+ def tunnel_state header, addr, state
355
+ if header.protocol == Socket::SOCK_DGRAM
356
+ # ignored
357
+ log.error "UDP does not handle: #{state}"
358
+ return
359
+ end
360
+
361
+ if header.protocol == Socket::SOCK_STREAM
362
+ unless bind = @tcp_bind[[header.port, header.family]]
363
+ log.error "Nothing listening on tcp/#{header.port}"
364
+ return
365
+ end
366
+
367
+ if state == OPEN
368
+ bind.open addr
369
+ return
370
+ end
371
+
372
+ if state == CLOSE
373
+ bind.close addr
374
+ return
375
+ end
376
+
377
+ log.error "Unknown state: #{state}"
378
+ return
379
+ end
380
+
381
+ log.error "STATE: Unsupported protocol: #{header.protocol}"
382
+ end
383
+ end
384
+ end
@@ -0,0 +1,62 @@
1
+ # $LICENSE
2
+ # Copyright 2013-2014 Spotify AB. All rights reserved.
3
+ #
4
+ # The contents of this file are licensed under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ require 'ffwd/connection'
17
+
18
+ module FFWD::Plugin::Tunnel
19
+ class ConnectionTCP < FFWD::Connection
20
+ include FFWD::Logging
21
+ include EM::Protocols::LineText2
22
+
23
+ def self.plugin_type
24
+ "tunnel_in_tcp"
25
+ end
26
+
27
+ def initialize bind, core, tunnel_protocol
28
+ @bind = bind
29
+ @core = core
30
+ @tunnel_protocol = tunnel_protocol
31
+ @protocol_instance = nil
32
+ end
33
+
34
+ def receive_line line
35
+ @protocol_instance.receive_line line
36
+ end
37
+
38
+ def receive_binary_data data
39
+ @protocol_instance.receive_binary_data data
40
+ end
41
+
42
+ def get_peer
43
+ peer = get_peername
44
+ port, ip = Socket.unpack_sockaddr_in(peer)
45
+ "#{ip}:#{port}"
46
+ end
47
+
48
+ def post_init
49
+ @protocol_instance = @tunnel_protocol.new @core, self
50
+ end
51
+
52
+ def unbind
53
+ log.info "Shutting down tunnel connection"
54
+ @protocol_instance.stop
55
+ @protocol_instance = nil
56
+ end
57
+
58
+ def metadata?
59
+ not @metadata.nil?
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ # $LICENSE
2
+ # Copyright 2013-2014 Spotify AB. All rights reserved.
3
+ #
4
+ # The contents of this file are licensed under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ module FFWD
17
+ module Plugin
18
+ module Tunnel
19
+ VERSION = "0.1.0"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,57 @@
1
+ # $LICENSE
2
+ # Copyright 2013-2014 Spotify AB. All rights reserved.
3
+ #
4
+ # The contents of this file are licensed under the Apache License, Version 2.0
5
+ # (the "License"); you may not use this file except in compliance with the
6
+ # License. You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ require 'eventmachine'
17
+ require 'base64'
18
+
19
+ require_relative 'tunnel/connection_tcp'
20
+ require_relative 'tunnel/binary_protocol'
21
+
22
+ require 'ffwd/logging'
23
+ require 'ffwd/plugin'
24
+ require 'ffwd/protocol'
25
+
26
+ module FFWD::Plugin::Tunnel
27
+ include FFWD::Plugin
28
+ include FFWD::Logging
29
+
30
+ register_plugin "tunnel"
31
+
32
+ DEFAULT_HOST = 'localhost'
33
+ DEFAULT_PORT = 9000
34
+ DEFAULT_PROTOCOL = 'tcp'
35
+ DEFAULT_PROTOCOL_TYPE = 'text'
36
+
37
+ CONNECTIONS = {
38
+ :tcp => ConnectionTCP
39
+ }
40
+
41
+ def self.setup_input opts, core
42
+ opts[:host] ||= DEFAULT_HOST
43
+ opts[:port] ||= DEFAULT_PORT
44
+ protocol = FFWD.parse_protocol(opts[:protocol] || DEFAULT_PROTOCOL)
45
+ protocol_type = opts[:protocol_type] || DEFAULT_PROTOCOL_TYPE
46
+
47
+ unless connection = CONNECTIONS[protocol.family]
48
+ raise "No connection for protocol family: #{protocol.family}"
49
+ end
50
+
51
+ if core.tunnel_plugins.empty?
52
+ raise "Nothing requires tunneling"
53
+ end
54
+
55
+ protocol.bind opts, core, log, connection, BinaryProtocol
56
+ end
57
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffwd-tunnel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - John-John Tedro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffwd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-mocks
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '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'
55
+ description:
56
+ email:
57
+ - udoprog@spotify.com
58
+ executables:
59
+ - ffwd-tunnel-agent
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - bin/ffwd-tunnel-agent
64
+ - lib/ffwd/plugin/tunnel.rb
65
+ - lib/ffwd/plugin/tunnel/binary_protocol.rb
66
+ - lib/ffwd/plugin/tunnel/version.rb
67
+ - lib/ffwd/plugin/tunnel/connection_tcp.rb
68
+ homepage: https://github.com/spotify/ffwd
69
+ licenses:
70
+ - Apache 2.0
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.0.3
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Simple tunneling support for FFWD.
92
+ test_files: []