ffwd-tunnel 0.1.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 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: []