├── .gitignore ├── LICENSE ├── README.md ├── example ├── http-test.js ├── index.js └── tcp-test.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mathias Buus 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smart-proxy 2 | 3 | smart-proxy is a tcp proxy that allows you to route 4 | tcp connections based on hostnames for ANY protocol 5 | 6 | ``` 7 | npm install smart-proxy 8 | ``` 9 | 10 | ## Usage 11 | 12 | First start two services, a tcp service and a http service 13 | on two different ports (lets say `10001` and `10002`) 14 | 15 | ``` js 16 | var net = require('net') 17 | var http = require('http') 18 | 19 | var tcpServer = net.createServer(function(socket) { 20 | socket.end('hello from tcp server\n') 21 | }) 22 | 23 | tcpServer.listen(10001) 24 | 25 | var httpServer = http.createServer(function(req, res) { 26 | res.end('hello from http server\n') 27 | }) 28 | 29 | httpServer.listen(10002) 30 | ``` 31 | 32 | Then start the proxy and add the two services. 33 | 34 | ``` js 35 | var proxy = require('smart-proxy') 36 | var server = proxy() 37 | 38 | server.add('tcp-test', 10001, 'localhost') 39 | server.add('http-test', 10002, 'localhost') 40 | 41 | server.listen(10000) 42 | ``` 43 | 44 | You can now access each of the two services simply by using `{service-name}.local` as 45 | the dns host and `10000` as the port on the local machine. 46 | 47 | ``` js 48 | curl http-test.local 10000 # prints hello from http server 49 | nc tcp-test.local 10000 # prints hello from tcp server 50 | ``` 51 | 52 | ## How? 53 | 54 | smart-proxy using dns to resolve each service name to a unique ip in the `127.x.x.x` range. 55 | All of these addresses should be loopback addresses. When a connection is being proxied it 56 | simply checks which ip was used and proxies to the corresponding service. 57 | 58 | Currently [mdns](https://github.com/mafintosh/multicast-dns) is used to resolve the hostnames 59 | since that makes it work without having to setup a new name server on your machine. 60 | 61 | 62 | ## OSX Notice 63 | 64 | smart-proxy needs `127.x.x.x` to all be loopback addresses. This *just works* on ubuntu 65 | but on osx you need to run the following command to get this working. 66 | 67 | ``` sh 68 | #!/bin/bash 69 | for ((i=2;i<256;i++)) 70 | do 71 | sudo ifconfig lo0 alias 127.0.0.$i up 72 | done 73 | ``` 74 | 75 | ## License 76 | 77 | MIT 78 | -------------------------------------------------------------------------------- /example/http-test.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | 3 | var server = http.createServer(function(req, res) { 4 | res.end('hello from http server\n') 5 | }) 6 | 7 | server.listen(10002) -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | var router = require('../') 2 | 3 | var server = router() 4 | 5 | server.add('tcp-server', 10001) 6 | server.add('http-server', 10002) 7 | 8 | server.listen(10000) 9 | -------------------------------------------------------------------------------- /example/tcp-test.js: -------------------------------------------------------------------------------- 1 | var net = require('net') 2 | 3 | var server = net.createServer(function(socket) { 4 | socket.end('hello from tcp server\n') 5 | }) 6 | 7 | server.listen(10001) -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var net = require('net') 2 | var pump = require('pump') 3 | var network = require('network-address') 4 | var multicast = require('multicast-dns') 5 | var os = require('os') 6 | 7 | var osx = os.platform() === 'darwin' 8 | var limit = osx ? 256 : 256*256*256 9 | var tick = 0 10 | 11 | module.exports = function() { 12 | var services = {} 13 | var hosts = {} 14 | var mdns = multicast() 15 | var me = network() 16 | 17 | mdns.on('query', function(query, rinfo) { 18 | if (rinfo.address !== me) return 19 | 20 | query.questions.forEach(function(q) { 21 | var service = services[q.name] || services[q.name.replace(/\.local$/, '')] 22 | 23 | if (!service || !service.bind) return 24 | if (q.type !== 'A') return 25 | 26 | mdns.respond({ 27 | answers: [{ 28 | type: 'A', 29 | ttl: 5, 30 | name: q.name, 31 | data: service.bind 32 | }], 33 | additionals: [{ 34 | type: 'AAAA', // seems AAAA record makes caching work with mdns 35 | ttl: 5, 36 | name: q.name, 37 | data: 'fe80::1' // just resolve to localhost - won't be used 38 | }] 39 | }) 40 | }) 41 | }) 42 | 43 | var freeHost = function() { 44 | for (var i = 0; i < limit; i++) { 45 | tick++ 46 | if (tick === limit) tick = 1 47 | 48 | var a = (tick / 256 / 256) | 0 49 | var b = ((tick % (256*256)) / 256) | 0 50 | var c = tick % 256 51 | 52 | var host = '127.'+a+'.'+b+'.'+c 53 | if (!hosts[host]) return host 54 | } 55 | return null 56 | } 57 | 58 | var server = net.createServer(function(socket) { 59 | var addr = socket.address().address 60 | var service = hosts[addr] 61 | 62 | if (!service) return socket.destroy() 63 | 64 | server.emit('route', service) 65 | pump(socket, net.connect(service.port, server.host), socket) 66 | }) 67 | 68 | server.add = function(name, port, host) { 69 | var addr = freeHost() 70 | hosts[addr] = services[name] = {bind:addr, port:port, host:host || 'localhost'} 71 | } 72 | 73 | server.remove = function(name) { 74 | var addr = services[name] && services[name].bind 75 | delete services[name] 76 | delete hosts[addr] 77 | } 78 | 79 | return server 80 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-proxy", 3 | "version": "1.0.2", 4 | "description": "smart-proxy is a tcp proxy that allows you to route tcp connections based on hostnames for ANY protocol", 5 | "main": "index.js", 6 | "dependencies": { 7 | "multicast-dns": "^1.2.0", 8 | "network-address": "^1.0.0", 9 | "pump": "^1.0.0" 10 | }, 11 | "devDependencies": {}, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/mafintosh/smart-proxy.git" 15 | }, 16 | "author": "Mathias Buus (@mafintosh)", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/mafintosh/smart-proxy/issues" 20 | }, 21 | "homepage": "https://github.com/mafintosh/smart-proxy" 22 | } 23 | --------------------------------------------------------------------------------