├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin.js ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '5' 4 | - '4' 5 | - '0.12' 6 | - '0.10' 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Thomas Watson Steen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fork-proxy 2 | 3 | Proxy a single incomming TCP connection to multiple remote TCP servers. 4 | Only the response from one target will be proxied back to the client. 5 | 6 | Can be used both from the command line and programmatically. 7 | 8 | [![Build status](https://travis-ci.org/watson/fork-proxy.svg?branch=master)](https://travis-ci.org/watson/fork-proxy) 9 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) 10 | 11 | ## Command Line Usage 12 | 13 | ``` 14 | fork-proxy port [forward_host:]forward_port... 15 | ``` 16 | 17 | The `fork-proxy` command takes the following arguments: 18 | 19 | - `port` - The port that it should listen on 20 | - `forwards` - a list of `host:port` combinations to forward TCP traffic 21 | to. If `host:` is omitted, `localhost` is assumed 22 | 23 | The responses from the first forward target will be piped back to the 24 | client. Responses from the remaining forward targets will be ignored. 25 | 26 | Example: 27 | 28 | ``` 29 | $ fork-proxy 3000 example.com:80 example.org:80 30 | ``` 31 | 32 | ## Programmatic Usage 33 | 34 | ```js 35 | var multi = require('fork-proxy') 36 | 37 | // proxy TCP traffic to both example.com and example.org 38 | var proxy = multi([ 39 | { host: 'example.com', port: 80 }, 40 | { host: 'example.org', port: 80 } 41 | ]) 42 | 43 | // listen for incoming TCP traffic on port 3000 44 | proxy.listen(3000) 45 | ``` 46 | 47 | ## API 48 | 49 | ### `var proxy = multi(targets)` 50 | 51 | The module exposes a single constructor function `multi`, which takes an 52 | array of target TCP servers as the first argument. The array must have 53 | at least one element. 54 | 55 | Each element in the array must be an object. The object is passed into 56 | [`net.connect()`](https://nodejs.org/api/net.html#net_net_connect_options_connectlistener) 57 | and as such is expected to follow the same API. 58 | 59 | The constructor function returns the proxy server which is an instance 60 | of [`net.Server`](https://nodejs.org/api/net.html#net_class_net_server). 61 | 62 | Only the response from the first target will be proxied back to the 63 | client. Responses from the remaining targets will be ignored. 64 | 65 | Each connection object emitted on the `connection` event will have a 66 | property named `targets`. It's an array containing the sockets created 67 | to connect to the different target servers: 68 | 69 | ```js 70 | var proxy = multi([{ port: 3001 }, { port: 3002 }]) 71 | 72 | proxy.on('connection', function (c) { 73 | console.log('connecting client to %d servers', c.targets.length) 74 | }) 75 | 76 | proxy.listen(3000) 77 | ``` 78 | 79 | ## License 80 | 81 | MIT 82 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | var util = require('util') 5 | var log = require('single-line-log').stdout 6 | var chalk = require('chalk') 7 | var multi = require('./') 8 | 9 | var port = parseInt(process.argv[2], 10) 10 | var targets = process.argv.slice(3).map(function (target) { 11 | target = ~target.indexOf(':') ? target.split(':') : [undefined, target] 12 | return { host: target[0], port: parseInt(target[1], 10) } 13 | }) 14 | 15 | if (Number.isNaN(port) || !targets.length) usage() 16 | 17 | var connections = [] 18 | 19 | var proxy = multi(targets).listen(port, function () { 20 | console.log('Proxy server listening on port %s', chalk.yellow(proxy.address().port)) 21 | }) 22 | 23 | proxy.on('connection', function (c) { 24 | connections.push(c) 25 | draw() 26 | c.on('data', draw) 27 | c.on('end', draw) 28 | }) 29 | 30 | function draw () { 31 | setTimeout(function () { 32 | log(connections.map(function (c, i) { 33 | var lines = c.targets.map(function (t) { 34 | var target = util.format('%s:%s', 35 | chalk.green(t._target.host || 'localhost'), 36 | chalk.yellow(t._target.port)) 37 | 38 | if (t.destroyed) return util.format(' -> %s: %s', target, chalk.red('destroyed')) 39 | 40 | return util.format(' -> %s - read: %s, written: %s', 41 | target, 42 | chalk.magenta(t.bytesRead), 43 | chalk.magenta(t.bytesWritten)) 44 | }) 45 | 46 | lines.unshift(util.format('\nConnection %d', i + 1)) 47 | 48 | return lines.join('\n') 49 | }).join('\n')) 50 | }, 10) 51 | } 52 | 53 | function usage () { 54 | console.log('Usage:') 55 | console.log('') 56 | console.log(' fork-proxy port [forward_host:]forward_port') 57 | console.log('') 58 | process.exit(1) 59 | } 60 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var net = require('net') 4 | var multi = require('multi-write-stream') 5 | 6 | module.exports = function (targets) { 7 | return net.createServer(function (c) { 8 | c.targets = targets.map(function (target) { 9 | var socket = net.connect(target) 10 | socket._target = target 11 | return socket 12 | }) 13 | 14 | // proxy data from source to all targets 15 | c.pipe(multi(c.targets)) 16 | 17 | // proxy data from target 1 back to source 18 | c.targets[0].pipe(c) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fork-proxy", 3 | "version": "1.0.0", 4 | "description": "Proxy a single incomming TCP connection to multiple remote TCP servers", 5 | "main": "index.js", 6 | "bin": "bin.js", 7 | "dependencies": { 8 | "multi-write-stream": "^2.0.1", 9 | "chalk": "^1.1.3", 10 | "single-line-log": "^1.1.1" 11 | }, 12 | "devDependencies": { 13 | "after-all": "^2.0.2", 14 | "standard": "^6.0.8", 15 | "tape": "^4.5.1" 16 | }, 17 | "scripts": { 18 | "test": "standard && tape test.js" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/watson/fork-proxy.git" 23 | }, 24 | "keywords": [ 25 | "proxy", 26 | "tcp", 27 | "multiple", 28 | "multi", 29 | "fork", 30 | "split", 31 | "duplicate" 32 | ], 33 | "author": "Thomas Watson Steen (https://twitter.com/wa7son)", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/watson/fork-proxy/issues" 37 | }, 38 | "homepage": "https://github.com/watson/fork-proxy#readme", 39 | "coordinates": [ 40 | 55.6469226, 41 | 12.5508618 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use stirct' 2 | 3 | var test = require('tape') 4 | var net = require('net') 5 | var afterAll = require('after-all') 6 | var multi = require('./') 7 | 8 | test(function (t) { 9 | t.plan(3) 10 | 11 | var next = afterAll(function () { 12 | var proxy = multi([ 13 | { port: t1.address().port }, 14 | { port: t2.address().port } 15 | ]) 16 | 17 | proxy.listen() 18 | 19 | var source = net.connect({ port: proxy.address().port }) 20 | 21 | source.on('data', function (chunk) { 22 | t.equal(chunk.toString(), '1hello') 23 | }) 24 | 25 | source.write('hello') 26 | }) 27 | 28 | var t1 = net.createServer(function (c) { 29 | c.on('data', function (chunk) { 30 | t.equal(chunk.toString(), 'hello') 31 | c.write('1') 32 | c.write(chunk) 33 | }) 34 | }) 35 | 36 | var t2 = net.createServer(function (c) { 37 | c.on('data', function (chunk) { 38 | t.equal(chunk.toString(), 'hello') 39 | c.write('2') 40 | c.write(chunk) 41 | }) 42 | }) 43 | 44 | t1.listen(next()) 45 | t2.listen(next()) 46 | }) 47 | 48 | test('end', function (t) { 49 | t.end() 50 | process.exit() 51 | }) 52 | --------------------------------------------------------------------------------