├── .gitignore ├── .npmignore ├── Gruntfile.coffee ├── LICENSE ├── README.md ├── bin └── dispatch.js ├── docs └── README.md ├── package.json └── src ├── dispatcher.coffee ├── index.coffee └── proxy ├── http.coffee └── socks.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules/* 4 | lib/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | src/ 3 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) -> 2 | grunt.initConfig 3 | pkg: grunt.file.readJSON 'package.json' 4 | 5 | watch: 6 | src: 7 | files: 'src/**/*.coffee' 8 | tasks: ['default'] 9 | options: 10 | spawn: false 11 | 12 | coffee: 13 | compile: 14 | expand: true 15 | cwd: 'src/' 16 | src: '**/*.coffee' 17 | dest: 'lib/' 18 | ext: '.js' 19 | options: 20 | bare: true 21 | 22 | clean: ['lib/'] 23 | 24 | grunt.loadNpmTasks 'grunt-contrib-watch' 25 | grunt.loadNpmTasks 'grunt-contrib-coffee' 26 | grunt.loadNpmTasks 'grunt-contrib-clean' 27 | 28 | grunt.registerTask 'default', ['clean', 'coffee:compile'] 29 | grunt.registerTask 'demon', ['default', 'watch:src'] 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Alexandre Kirszenberg 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 | ### This project is archived and will not receive any further updates. A modern rewrite of this tool is available [here](https://github.com/alexkirsz/dispatch). 2 | 3 | # dispatch-proxy 4 | 5 | A SOCKS5/HTTP proxy that balances traffic between multiple internet connections. 6 | 7 | _Works on Mac OS X, Windows and Linux._ 8 | 9 | **Detailed installation instructions:** 10 | 11 | - Windows: [imgur album](http://imgur.com/a/0snis) 12 | - Mac OS X: [imgur album](http://imgur.com/a/TSD5F) 13 | 14 | ## Installation 15 | 16 | You'll need to have Node.JS >= 0.10.0 installed on your system. 17 | 18 | ``` 19 | $ npm install -g dispatch-proxy 20 | ``` 21 | 22 | To update: 23 | 24 | ``` 25 | $ npm update -g dispatch-proxy 26 | ``` 27 | 28 | ## Rationale 29 | 30 | You often find yourself with multiple unused internet connections, be it a 3G/4G mobile subscription or a free wifi hotspot, that your system won't let you use together with your main one. 31 | 32 | For example, my residence provides me with a cabled and wireless internet access. Both are capped at 1,200kB/s download/upload speed, but they can simultaneously run at full speed. My mobile internet access also provides me with 400kB/s download/upload speed. 33 | 34 | Combine all of these with `dispatch` and a threaded download manager and you get a 2,800kB/s download and upload speed limit, which is considerably better :) 35 | 36 | ## Use-cases 37 | 38 | The possibilities are endless: 39 | 40 | - combine as many Wi-Fi networks/Ethernet/3G/4G connections as you have access to in one big, load balanced connection, 41 | - use it in conjunction with a threaded download manager, effectively combining multiple connections' speed in single file downloads, 42 | - create two proxies, assign to each its own interface, and run two apps simultaneously that use a different interface (e.g. for balancing download/upload), 43 | - create a hotspot proxy at home that connects through Ethernet and your 4G card for all your mobile devices, 44 | - etc. 45 | 46 | ## Quick start 47 | 48 | The module provides a simple command-line utility called `dispatch`. 49 | 50 | ``` 51 | $ dispatch start 52 | ``` 53 | 54 | Start a SOCKS proxy server on `localhost:1080`. Simply add this address as a SOCKS proxy in your system settings and your traffic will be automatically balanced between all available internet connections. 55 | 56 | ## Usage 57 | 58 | ``` 59 | $ dispatch -h 60 | 61 | Usage: dispatch [options] [command] 62 | 63 | Commands: 64 | 65 | list list all available network interfaces 66 | start [options] start a proxy server 67 | 68 | Options: 69 | 70 | -h, --help output usage information 71 | -V, --version output the version number 72 | ``` 73 | 74 | ``` 75 | $ dispatch start -h 76 | 77 | Usage: start [options] [addresses] 78 | 79 | Options: 80 | 81 | -h, --help output usage information 82 | -H, --host which host to accept connections from (defaults to localhost) 83 | -p, --port

which port to listen to for connections (defaults to 8080 for HTTP proxy, 1080 for SOCKS proxy) 84 | --http start an http proxy server 85 | --debug log debug info in the console 86 | ``` 87 | 88 | ## Examples 89 | 90 | ``` 91 | $ dispatch start --http 92 | ``` 93 | 94 | Start an HTTP proxy server listening on `localhost:8080`, dispatching connections to every non-internal IPv4 local addresses. 95 | 96 | ``` 97 | $ dispatch start 10.0.0.0 10.0.0.1 98 | ``` 99 | 100 | Dispatch connections only to local addresses `10.0.0.0` and `10.0.0.1`. 101 | 102 | ``` 103 | $ dispatch start 10.0.0.0@7 10.0.0.1@3 104 | ``` 105 | 106 | Dispatch connections to `10.0.0.0` 7 times out of 10 and to '10.0.0.1' 3 times out of 10. 107 | -------------------------------------------------------------------------------- /bin/dispatch.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require(__dirname + '/../lib'); 3 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Dispatch-Proxy Docs 2 | 3 | Dispatch-proxy is a SOCKS5/HTTP proxy that balances traffic between multiple internet connections. 4 | 5 | ## Pre-requisites 6 | * Mac OS X 10.6+ 7 | * Windows 7+ 8 | * Node.JS >= 0.10.0 9 | 10 | ## Installation instructions 11 | 12 | * Windows: [imgur album](http://imgur.com/a/0snis) 13 | * Mac OS X: [imgur album](http://imgur.com/a/TSD5F) 14 | 15 | ## Help and Support 16 | Please check out the documentation and FAQ first. We do not offer support but if you find a bug, you are more than welcome to open an issue. 17 | 18 | ### FAQ: ### 19 | **Q: Can I run it on a router?** 20 | 21 | **A:** It would be difficult to run this proxy on a wireless router, including a router configured as repeater because most of the routers on the market can only work at AP mode or Repeater mode. Thus a router generally saying cannot route packets through Ethernet and wireless connection to another AP at the same time. 22 | 23 | We strongly encourage you to test running this project on a router which you have ssh access to. Please do let us know if any success! 24 | 25 | **Q: I'm just curious, but how does this work?** 26 | 27 | **A:** Dispatch-proxy balances traffic between connections. For instance, when you upload a youtube video, you establish a single HTTP connection between you and a youtube server. But if you were to upload two videos at the same time, you could potentially use both your interfaces. 28 | 29 | The current load balancing algorithm is very simple and looks at the number of live connections owned by each interface to determine which interface to dispatch the next connection to (respecting priorities set via the @ syntax). In the future it will look at the total traffic of those connections. 30 | 31 | At the moment the SOCKS proxy supports TCP CONNECT command, which means basically all TCP operations, but has no support for UDP BIND (UDP) yet. 32 | 33 | **Q: Can I proxy a VPN by connecting to the same VPN server using multiple connections?** 34 | 35 | **A:** Unfortunately it's not possible. 36 | 37 | **Q: Can I share the proxy with others?** 38 | 39 | **A:** If all devices are connected to the same network and upon correctly configured firewall rules, yes. However we do not provide any support at this. 40 | 41 | ## Contributing 42 | So you like this project and want to make the world of internet even faster? That's awesome! In below we have provided some basic guidelines to help you contribute easier. 43 | 44 | ### Bug Report 45 | Definition: A bug is a *demonstrable* problem that is caused by the code in the repository. 46 | 47 | 1. Use the GitHub issue search — check if the issue has already been reported. How to use search function? Check out this [guide](https://help.github.com/articles/using-search-to-filter-issues-and-pull-requests/). 48 | 49 | 2. Check if the issue has been fixed — try to reproduce it using the latest master or look for closed issues in the current milestone. 50 | 51 | 3. Isolate the problem — ideally create a reduced test case and a live example. 52 | 53 | 4. Include a screencast if relevant - Is your issue about a design or front end feature or bug? The most helpful thing in the world is if we can see what you're talking about. Use LICEcap to quickly and easily record a short screencast (24fps) and save it as an animated gif! Embed it directly into your GitHub issue. 54 | 55 | 5. Include as much info as possible! Use the Bug Report template below or click this link to start creating a bug report with the template automatically. 56 | 57 | A good bug report shouldn't leave others needing to chase you up for more information. Be sure to include the details of your environment. 58 | 59 | Here is a real example of a great bug report. 60 | 61 | 62 | ```sh 63 | Short and descriptive example bug report title 64 | 65 | ### Issue Summary 66 | 67 | A summary of the issue and the browser/OS environment in which it occurs. 68 | 69 | ### Steps to Reproduce 70 | 1. This is the first step 71 | 2. This is the second step, etc. 72 | 73 | Any other info e.g. Why do you consider this to be a bug? What did you expect to happen instead? 74 | 75 | ### Technical details: 76 | * Operation System: macOS 10.13.4 77 | * Node Version: 4.4.7 78 | * Dispatch-proxy Version: master (latest commit: a761de2079dca4df49567b1bddac492f25033985) 79 | * Internet connections and local IP adresses 80 | * Browser: Chrome 48.0.2564.109 on Mac OS X 10.10.4 81 | ``` 82 | 83 | ### Feature Request 84 | If you have ideas, feel free to open an issue and tell us all about it! 85 | 86 | Request template: 87 | ```sh 88 | Short and descriptive example feature request title 89 | 90 | ### Summary 91 | 92 | A summary of the your idea and how it would work. 93 | 94 | ### Use case model 95 | As... (a game streamer) 96 | I want... (make use of two internet connections, both at 100 Mbps upload) 97 | Because... (I need at least 120 Mbps to stream in 4k at 144 fps) 98 | ``` 99 | 100 | ### Change Request 101 | Change requests cover both architectural and functional changes to how Ghost works. If you have an idea for a new or different dependency, a refactor, or an improvement to a feature, etc - please be sure to: 102 | 103 | 1. Use the GitHub search and check someone else didn't get there first 104 | 105 | 2. Take a moment to think about the best way to make a case for, and explain what you're thinking as it's up to you to convince the project's leaders the change is worthwhile. Some questions to consider are: 106 | 107 | * Is it really one idea or is it many? 108 | * What problem are you solving? 109 | * Why is what you are suggesting better than what's already there? 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dispatch-proxy", 3 | "description": "A SOCKS5/HTTP proxy that balances traffic between multiple internet connections.", 4 | "author": "Alexandre Kirszenberg ", 5 | "version": "0.1.4", 6 | "license": "MIT", 7 | "keywords": [ 8 | "dispatch", 9 | "proxy", 10 | "socks", 11 | "socks5", 12 | "http", 13 | "load", 14 | "balancer" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "git@github.com:morhaus/dispatch-proxy.git" 19 | }, 20 | "bin": { 21 | "dispatch": "./bin/dispatch.js" 22 | }, 23 | "scripts": { 24 | "prepublish": "grunt" 25 | }, 26 | "engines": { 27 | "node": ">=0.10.0" 28 | }, 29 | "engineStrict": true, 30 | "dependencies": { 31 | "commander": "2.0.0", 32 | "socks-handler": "0.2.1", 33 | "tmpl-log": "0.0.3" 34 | }, 35 | "devDependencies": { 36 | "grunt": "*", 37 | "grunt-cli": "*", 38 | "grunt-contrib-watch": "*", 39 | "grunt-contrib-coffee": "*", 40 | "grunt-contrib-clean": "*" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/dispatcher.coffee: -------------------------------------------------------------------------------- 1 | { EventEmitter } = require 'events' 2 | 3 | module.exports = class Dispatcher extends EventEmitter 4 | constructor: (@addresses) -> 5 | @connectionsTotal = 0 6 | @connectionsByAddress = {} 7 | 8 | _prioritiesSum: -> 9 | (priority for { priority } in @addresses).reduce (a, b) -> a + b 10 | 11 | dispatch: -> 12 | availableAddress = null # Address whose priority is least respected. 13 | prevailingAddress = null # Address with the highest priority. 14 | maxRatioDiff = 0 15 | maxPriority = 0 16 | 17 | for address in @addresses 18 | currentRatio = (@connectionsByAddress[address.address] / @connectionsTotal) or 0 19 | priorityRatio = address.priority / @_prioritiesSum() 20 | ratioDiff = priorityRatio - currentRatio 21 | 22 | if ratioDiff > maxRatioDiff 23 | maxRatioDiff = ratioDiff 24 | availableAddress = address.address 25 | if address.priority > maxPriority 26 | maxPriority = address.priority 27 | prevailingAddress = address.address 28 | 29 | # If the maxDiff approaches zero, it means that all address priorities are currently respected. 30 | if maxRatioDiff < 0.000001 31 | @_increment prevailingAddress 32 | return prevailingAddress 33 | else 34 | @_increment availableAddress 35 | return availableAddress 36 | 37 | _increment: (address) -> 38 | @connectionsByAddress[address] or= 0 39 | @connectionsByAddress[address]++ 40 | @connectionsTotal++ 41 | 42 | free: (address) => 43 | @connectionsTotal-- 44 | delete @connectionsByAddress[address] if --@connectionsByAddress[address] is 0 45 | 46 | -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | os = require 'os' 2 | { inspect } = require 'util' 3 | crypto = require 'crypto' 4 | program = require 'commander' 5 | Logger = require 'tmpl-log' 6 | SocksProxy = require './proxy/socks' 7 | HttpProxy = require './proxy/http' 8 | pkg = require '../package' 9 | 10 | logger = new Logger(tab: 10, gutter: ' ') 11 | .registerTag('b', ['bold']) 12 | .registerTag('s', ['green']) # Success 13 | .registerTag('i', ['cyan']) # Info 14 | .registerTag('e', ['red']) # Error 15 | .registerTag('a', ['b', 'underline']) # Address 16 | 17 | .registerEvent('request', 'request') 18 | .registerEvent('dispatch', 'dispatch') 19 | .registerEvent('connect', 'connect') 20 | .registerEvent('response', 'response') 21 | .registerEvent('error', 'error') 22 | .registerEvent('end', 'end') 23 | 24 | .registerMode('default', ['error']) 25 | .registerMode('debug', true) 26 | 27 | program 28 | .version(pkg.version) 29 | 30 | program 31 | .command('list') 32 | .description('list all available network interfaces') 33 | .action -> 34 | interfaces = os.networkInterfaces() 35 | 36 | for name, addrs of interfaces 37 | logger.log "#{name}" 38 | 39 | for { address, family, internal } in addrs 40 | opts = [] 41 | opts.push family if family 42 | opts.push 'internal' if internal 43 | logger.log " #{address}" + if opts.length > 0 then " (#{opts.join ', '})" else '' 44 | 45 | logger.log '' 46 | 47 | program 48 | .command('start') 49 | .usage('[options] [addresses]') 50 | .description('start a proxy server') 51 | .option('-H, --host ', 'which host to accept connections from (defaults to localhost)', String) 52 | .option('-p, --port

', 'which port to listen to for connections (defaults to 8080 for HTTP proxy, 1080 for SOCKS proxy)', Number) 53 | .option('--http', 'start an http proxy server', Boolean) 54 | .option('--debug', 'log debug info in the console', Boolean) 55 | .action (args..., { port, host, http, https, debug }) -> 56 | logger.setMode 'debug' if debug 57 | 58 | addresses = [] 59 | if args.length is 0 60 | for name, addrs of os.networkInterfaces() 61 | for addr in addrs when addr.family is 'IPv4' and not addr.internal 62 | addresses.push address: addr.address, priority: 1 63 | else 64 | for arg in args 65 | [address, priority] = arg.split '@' 66 | priority = if priority then (parseInt priority) else 1 67 | addresses.push { address, priority } 68 | 69 | host or= 'localhost' 70 | 71 | if http 72 | port or= 8080 73 | type = 'HTTP' 74 | proxy = new HttpProxy addresses, port, host 75 | 76 | proxy 77 | .on 'request', ({ clientRequest, serverRequest, localAddress }) -> 78 | id = (crypto.randomBytes 3).toString 'hex' 79 | 80 | logger.emit 'request', "[#{id}] #{clientRequest.url}" 81 | logger.emit 'dispatch', "[#{id}] #{localAddress}" 82 | 83 | serverRequest 84 | .on 'response', (serverResponse) -> 85 | logger.emit 'response', "[#{id}] #{serverResponse.statusCode}" 86 | 87 | .on 'error', (err) -> 88 | logger.emit 'error', "[#{id}] clientRequest\n#{escape err.stack}" 89 | 90 | .on 'end', -> 91 | logger.emit 'end', "[#{id}] serverRequest" 92 | 93 | clientRequest 94 | .on 'error', (err) -> 95 | logger.emit 'error', "[#{id}] clientRequest\n#{escape err.stack}" 96 | 97 | .on 'end', -> 98 | logger.emit 'end', "[#{id}] clientRequest" 99 | 100 | .on 'error', (err) -> 101 | logger.emit 'error', "server\n#{escape err.stack}" 102 | 103 | else 104 | port or= 1080 105 | type = 'SOCKS' 106 | proxy = new SocksProxy addresses, port, host 107 | 108 | proxy 109 | .on 'request', ({ serverConnection, clientConnection, host, port, localAddress }) -> 110 | id = (crypto.randomBytes 3).toString 'hex' 111 | 112 | logger.emit 'request', "[#{id}] #{host}:#{port}" 113 | logger.emit 'dispatch', "[#{id}] #{localAddress}" 114 | 115 | serverConnection 116 | .on 'connect', -> 117 | logger.emit 'connect', "[#{id}] #{host}:#{port}" 118 | 119 | .on 'error', (err) -> 120 | logger.emit 'error', "[#{id}] serverConnection\n#{escape err.stack}" 121 | 122 | .on 'end', -> 123 | logger.emit 'end', "[#{id}] serverConnection" 124 | 125 | clientConnection 126 | .on 'error', (err) -> 127 | logger.emit 'error', "[#{id}] clientConnection\n#{escape err.stack}" 128 | 129 | .on 'end', -> 130 | logger.emit 'end', "[#{id}] clientConnection" 131 | 132 | .on 'error', (err) -> 133 | logger.emit 'error', "server\n#{escape err.stack}" 134 | 135 | .on 'socksError', (err) -> 136 | logger.emit 'error', "socks\n#{escape err.message}" 137 | 138 | logger.log """ 139 | #{type} server started on #{host}:#{port} 140 | Dispatching to addresses #{("#{address}@#{priority}" for { address, priority } in addresses).join ', '} 141 | """ 142 | 143 | program.parse process.argv 144 | -------------------------------------------------------------------------------- /src/proxy/http.coffee: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | url = require 'url' 3 | Dispatcher = require '../dispatcher' 4 | 5 | module.exports = class HttpProxy extends Dispatcher 6 | constructor: (addresses, listenPort, listenHost) -> 7 | super addresses 8 | 9 | agent = new http.Agent maxSockets: Infinity 10 | 11 | @server = http.createServer (clientRequest, clientResponse) => 12 | localAddress = @dispatch() 13 | 14 | options = url.parse clientRequest.url 15 | options.localAddress = localAddress 16 | options.method = clientRequest.method 17 | options.headers = clientRequest.headers 18 | options.agent = agent 19 | 20 | serverRequest = http.request options 21 | 22 | clientRequest.pipe serverRequest 23 | 24 | serverRequest 25 | .on 'response', (serverResponse) -> 26 | clientResponse.writeHead serverResponse.statusCode, serverResponse.headers 27 | serverResponse.pipe clientResponse 28 | .on 'error', (error) -> 29 | clientResponse.writeHead 502 # Bad gateway 30 | clientResponse.end() 31 | .on 'end', => 32 | @free localAddress 33 | 34 | @emit 'request', { clientRequest, serverRequest, localAddress } 35 | 36 | @server.listen listenPort, listenHost 37 | -------------------------------------------------------------------------------- /src/proxy/socks.coffee: -------------------------------------------------------------------------------- 1 | net = require 'net' 2 | socks = require 'socks-handler' 3 | Dispatcher = require '../dispatcher' 4 | 5 | module.exports = class SocksProxy extends Dispatcher 6 | constructor: (addresses, listenPort, listenHost) -> 7 | super addresses 8 | 9 | @server = net.createServer (clientConnection) => 10 | socks.handle clientConnection, (err, handler) => 11 | if err 12 | @emit 'socksError', err 13 | return 14 | 15 | handler.on 'error', (err) -> 16 | @emit 'socksError', err 17 | 18 | handler.on 'request', ({ version, command, host, port }, callback) => 19 | if command isnt socks[5].COMMAND.CONNECT 20 | @emit 'socksError', new Error "Unsupported command: #{command}" 21 | if version is 5 22 | callback socks[5].REQUEST_STATUS.COMMAND_NOT_SUPPORTED 23 | else 24 | callback socks[4].REQUEST_STATUS.REFUSED 25 | return 26 | 27 | localAddress = @dispatch() 28 | serverConnection = net.createConnection { port, host, localAddress } 29 | 30 | clientConnection.pipe(serverConnection).pipe(clientConnection) 31 | 32 | serverConnection 33 | .on 'error', onConnectError = (err) -> 34 | if version is 5 35 | status = 36 | switch err.code 37 | when 'EHOSTUNREACH' then socks[5].REQUEST_STATUS.HOST_UNREACHABLE 38 | when 'ECONNREFUSED' then socks[5].REQUEST_STATUS.CONNECTION_REFUSED 39 | when 'ENETUNREACH' then socks[5].REQUEST_STATUS.NETWORK_UNREACHABLE 40 | else socks[5].REQUEST_STATUS.SERVER_FAILURE 41 | else 42 | status = socks[4].REQUEST_STATUS.FAILED 43 | 44 | callback status 45 | 46 | .on 'connect', -> 47 | serverConnection.removeListener 'error', onConnectError 48 | status = if version is 5 then socks[5].REQUEST_STATUS.SUCCESS else socks[4].REQUEST_STATUS.GRANTED 49 | callback status 50 | 51 | .on 'end', => 52 | @free localAddress 53 | 54 | @emit 'request', { clientConnection, serverConnection, host, port, localAddress } 55 | 56 | @server.on 'error', (err) => @emit 'error', err 57 | 58 | @server.listen listenPort, listenHost 59 | 60 | --------------------------------------------------------------------------------