├── .gitignore ├── cert.pfx ├── index.js ├── jsconfig.json ├── .eslintrc.json ├── package.json ├── LICENSE ├── cert.cer ├── relay-server-cli.js ├── relay-client-cli.js ├── README.md ├── relay-client.js └── relay-server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode -------------------------------------------------------------------------------- /cert.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tewarid/node-tcp-relay/HEAD/cert.pfx -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var relayClient = require("./relay-client"); 2 | var relayServer = require("./relay-server"); 3 | exports.createRelayClient = relayClient.createRelayClient; 4 | exports.createRelayServer = relayServer.createRelayServer; 5 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "commonjs", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "bower_components", 12 | "jspm_packages", 13 | "tmp", 14 | "temp" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6 4 | }, 5 | "plugins": [ 6 | "security" 7 | ], 8 | "extends": [ 9 | "google", 10 | "plugin:security/recommended" 11 | ], 12 | "rules": { 13 | "quotes": "off", 14 | "require-jsdoc": "off", 15 | "no-var": "off", 16 | "guard-for-in": "off", 17 | "comma-dangle": "off" 18 | } 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-tcp-relay", 3 | "description": "A simple TCP relay mechanism for NAT traversal built using Node.js", 4 | "license": "MIT", 5 | "version": "0.0.18", 6 | "main": "index.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/tewarid/node-tcp-relay.git" 10 | }, 11 | "bin": { 12 | "tcprelayc": "./relay-client-cli.js", 13 | "tcprelays": "./relay-server-cli.js" 14 | }, 15 | "dependencies": { 16 | "commander": "^2.19.0" 17 | }, 18 | "devDependencies": { 19 | "eslint": "^8.9.0", 20 | "eslint-config-google": "^0.14.0", 21 | "eslint-plugin-security": "^1.4.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-2016 Devendra Tewari 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /cert.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICujCCAaKgAwIBAgIEWy6HKjANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARU 3 | ZXN0MB4XDTE4MDYyMzE3NDUxNFoXDTM4MDYyMzE3NDUxNFowDzENMAsGA1UEAwwE 4 | VGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMjCaahrNXupv5gk 5 | mY+unEqusINlsy/WX1QU3lH5Lctdh7/sPGh8zqqlUevZhdKMfNFLk+FTEvxh13k8 6 | sjkgX6+pzqW6Hi0fn5Nc0nizrHVLX6cHBVS9dwEi/M0hwk7DEUsYwPxFTvL2BCXf 7 | Xxotb32JUC6Y1UVGNXYZQ0R9XXOecoDjwDpVJNbK3fTrrE/wNB2+ppZk8sqd4Gyb 8 | 9+PicYI9oidcNXQWOFqVejXC7M635CxGzPopalDJB3NLw+8iBzq84NwosVBjqzmh 9 | 0JGrNSXfXrQa0EjCI22B5MnVlNsG5DiNLjALO/REoGmsKxMQXAWw+to0muXqZaUo 10 | tjftvg8CAwEAAaMeMBwwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqG 11 | SIb3DQEBCwUAA4IBAQDEMnfRxCuu/1dbCl2v9iLp5mqPx2tXD1mUsrmdehJmYGi8 12 | gGfyk3X7CPkHZUNY8etRwM8ghG2j5Kx5e0ecV6A6YW/dfVuyiX/1ZrCNExI5IIAh 13 | fVTPF2ZrI93kG/eGNheCqAOSrnqaCWYeM/v57pG+M4R36Vefj8+fz4uppurXeXkq 14 | i0H5oG/R7pi/kRSaquY9FMAwHmHUgeGY/n6I709PZGDvbXwWOqJnH2KmWkj6krs4 15 | neTelyBkFiKkgPzN4t5LLm/NaXXHQYYC05tpw1IVZALrZX0dFDZfTExw6Yya2j9i 16 | Pm+N/y0kK8PGEf9BGP6rTHkVSk3YqsBdrU0gG1cO 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /relay-server-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var argv = require("commander"); 3 | var packageConfig = require('./package.json'); 4 | 5 | argv 6 | .usage("[options]") 7 | .version(packageConfig.version) 8 | .option("-r, --relayPort ", "Relay port number", parseInt) 9 | .option("-s, --servicePort ", "Internet port number", parseInt) 10 | .option("-h, --hostname ", "Name or IP address of host") 11 | .option("-t, --tls [both]", "Use TLS 1.2 with relay server; " + 12 | "specify value both to also use TLS 1.2 with internet clients", false) 13 | .option("-c, --pfx [file]", "Private key file", 14 | require.resolve("./cert.pfx")) 15 | .option("-p, --passphrase [value]", 16 | "Passphrase to access private key file", "abcd") 17 | .option("-a, --auth", 18 | "Authenticate relay client by requesting its certificate; " + 19 | "implies tls option is specified") 20 | .option("-f, --caFile [value]", 21 | "CA certs file used for validating client certificate") 22 | .parse(process.argv); 23 | 24 | var options = { 25 | hostname: argv.hostname, 26 | tls: argv.tls || argv.auth, 27 | pfx: argv.pfx, 28 | passphrase: argv.passphrase, 29 | auth: argv.auth, 30 | caFile: argv.caFile 31 | }; 32 | 33 | var relayServer = require("./relay-server.js"); 34 | 35 | var newRelayServer = relayServer.createRelayServer(argv.relayPort, 36 | argv.servicePort, options); 37 | 38 | process.on("uncaughtException", function(err) { 39 | console.log(err); 40 | }); 41 | 42 | process.on("SIGINT", function() { 43 | newRelayServer.end(); 44 | }); 45 | -------------------------------------------------------------------------------- /relay-client-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var argv = require("commander"); 3 | var packageConfig = require('./package.json'); 4 | 5 | argv 6 | .usage("[options]") 7 | .version(packageConfig.version) 8 | .option("-n, --host ", "Name or IP address of service host") 9 | .option("-s, --port ", "Service port number", parseInt) 10 | .option("-h, --relayHost ", "Name or IP address of relay host") 11 | .option("-r, --relayPort ", "Relay port number", parseInt) 12 | .option("-c, --numConn [numConn]", 13 | "Number of connections to maintain with relay", 1) 14 | .option("-t, --tls [both]", "Use TLS 1.2 with relay server; " + 15 | "specify value both to also use TLS 1.2 with service", false) 16 | .option("-u, --rejectUnauthorized [value]", 17 | "Do not accept invalid certificate", false) 18 | .option("-f, --caFile [value]", 19 | "CA certs file to authenticate relay server") 20 | .option("-x, --pfx [file]", "Private key file", 21 | require.resolve("./cert.pfx")) 22 | .option("-p, --passphrase [value]", 23 | "Passphrase to access private key file", "abcd") 24 | .parse(process.argv); 25 | 26 | var options = { 27 | numConn: argv.numConn, 28 | tls: argv.tls, 29 | rejectUnauthorized: argv.rejectUnauthorized !== "false", 30 | caFile: argv.caFile, 31 | pfx: argv.pfx, 32 | passphrase: argv.passphrase 33 | }; 34 | 35 | var relayClient = require("./relay-client.js"); 36 | 37 | var newRelayClient = relayClient.createRelayClient(argv.host, argv.port, 38 | argv.relayHost, argv.relayPort, options); 39 | 40 | process.on("uncaughtException", function(err) { 41 | console.log(err); 42 | }); 43 | 44 | process.on("SIGINT", function() { 45 | newRelayClient.end(); 46 | }); 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-tcp-relay [![Codacy Badge](https://app.codacy.com/project/badge/Grade/fdbbca1d689d4b13b5a22c5765f41c8e)](https://www.codacy.com/gh/tewarid/node-tcp-relay/dashboard?utm_source=github.com&utm_medium=referral&utm_content=tewarid/node-tcp-relay&utm_campaign=Badge_Grade) [![Maintainability](https://api.codeclimate.com/v1/badges/4e63f2f80369103db673/maintainability)](https://codeclimate.com/github/tewarid/node-tcp-relay/maintainability) 2 | 3 | This TCP relay/reverse proxy can be used to expose any TCP/IP service running behind a NAT. This includes services that use HTTP and SSH. 4 | 5 | To install from npm 6 | 7 | ```bash 8 | sudo npm install -g node-tcp-relay 9 | ``` 10 | 11 | ## Command Line Interface 12 | 13 | The relay server is meant to be executed on a server visible on the internet, as follows 14 | 15 | ```bash 16 | tcprelays --relayPort 10080 --servicePort 10081 [--hostname [IP]] [--tls [both]] [--pfx file] [--passphrase passphrase] [--auth] [--caFile file] 17 | ``` 18 | 19 | `relayPort` is the port where the relay server will listen for incoming connections from the relay client. `servicePort` is the port where internet clients can connect to the service exposed through the relay. Optionally, `hostname` specifies the IP address to listen at. Node.js listens on unspecified IPv6 address `::` by default. 20 | 21 | `tls` option enables secure communication with relay client using TLS. If followed by `both`, TLS is also enabled on the service port. `pfx` option specifies a private key file used to establish TLS. `passphrase` specifies password used to protect private key. Relay server authenticates relay client by requesting its certificate when `auth` option is specified. Use `caFile` option to specify CA certificates used to validate client certificate. 22 | 23 | The relay client is meant to be executed on a machine behind a NAT, as follows 24 | 25 | ```bash 26 | tcprelayc --host host --port 10080 --relayHost host --relayPort port [--numConn count] [--tls [both]] [--rejectUnauthorized] [--caFile file] [--pfx file] [--passphrase value] 27 | ``` 28 | 29 | `host` is any server visible to the machine behind the NAT. `port` is the port of the service you want to expose through the relay. 30 | 31 | `relayServer` is the host name or IP address of the server visible on the internet executing the relay server. `relayPort` is the relay server port where the client will connect. 32 | 33 | `numConn` is the number of unused connections relay client maintains with the server. As soon as it detects data activity on a socket, it establishes another connection. Servicing internet clients that don't transfer any data may lead to denial of service. 34 | 35 | `tls` enables secure TLS communication with relay server. If followed by `both`, TLS is also used with server behind the NAT. `rejectUnauthorized` enables checking for valid server certificate. Custom CA file can be specified using the `caFile` option. Use `pfx` option to specify certificate used to authenticate relay client at relay server. 36 | 37 | If you're relaying HTTP(S), use a reverse proxy such as http-proxy, between the relay client and the local service e.g. 38 | 39 | ```javascript 40 | var httpProxy = require('http-proxy'); 41 | httpProxy.createProxyServer({target:'http://host:port'}).listen(port); 42 | ``` 43 | 44 | ## Programming Interface 45 | 46 | Create and start a relay server thus 47 | 48 | ```javascript 49 | var relayServer = require("node-tcp-relay"); 50 | var newRelayServer = relayServer.createRelayServer(10080, 10081); 51 | ``` 52 | 53 | End relay server 54 | 55 | ```javascript 56 | newRelayServer.end(); 57 | ``` 58 | 59 | Create and start a relay client thus 60 | 61 | ```javascript 62 | var relayClient = require("node-tcp-relay") 63 | var newRelayClient = relayClient.createRelayClient("hostname", 8080, "relayserver", 10080, 1); 64 | ``` 65 | 66 | End relay client 67 | 68 | ```javascript 69 | newRelayClient.end(); 70 | ``` 71 | 72 | ## Alternatives 73 | 74 | * [ssh -R](https://www.ssh.com/ssh/tunneling/example#remote-forwarding) 75 | * VPN 76 | -------------------------------------------------------------------------------- /relay-client.js: -------------------------------------------------------------------------------- 1 | var util = require("util"); 2 | var EventEmitter = require("events").EventEmitter; 3 | var net = require("net"); 4 | var tls = require('tls'); 5 | var fs = require('fs'); 6 | 7 | module.exports = { 8 | createRelayClient: createRelayClient 9 | }; 10 | 11 | function createRelayClient(host, port, relayHost, relayPort, numConn) { 12 | var options; 13 | if (typeof numConn === "number") { 14 | options = {}; 15 | options.numConn = numConn; 16 | } else { 17 | options = Object.assign(numConn); 18 | options.numConn = numConn.numConn; 19 | } 20 | options.host = host; 21 | options.port = port; 22 | options.relayHost = relayHost; 23 | options.relayPort = relayPort; 24 | options.relayTlsOptions = makeRelayTlsOptions(options); 25 | options.serviceTlsOptions = makeServiceTlsOptions(options); 26 | return new RelayClient(options); 27 | } 28 | 29 | function RelayClient(options) { 30 | this.options = options; 31 | this.clients = []; 32 | 33 | for (var i = 0; i < this.options.numConn; i++) { 34 | this.clients[this.clients.length] = 35 | this.createClient(options); 36 | } 37 | } 38 | 39 | RelayClient.prototype.createClient = function(options) { 40 | var relayClient = this; 41 | var client = new Client(options); 42 | client.on("pair", function() { 43 | relayClient.clients[relayClient.clients.length] = 44 | relayClient.createClient(options); 45 | }); 46 | client.on("close", function() { 47 | var i = relayClient.clients.indexOf(client); 48 | if (i != -1) { 49 | relayClient.clients.splice(i, 1); 50 | } 51 | setTimeout(function() { 52 | if (relayClient.endCalled) return; 53 | relayClient.clients[relayClient.clients.length] = 54 | relayClient.createClient(options); 55 | }, 5000); 56 | }); 57 | return client; 58 | }; 59 | 60 | RelayClient.prototype.end = function() { 61 | this.endCalled = true; 62 | for (var i = 0; i < this.clients.length; i++) { 63 | this.clients[i].removeAllListeners(); 64 | this.clients[i].relaySocket.destroy(); 65 | } 66 | }; 67 | 68 | util.inherits(Client, EventEmitter); 69 | 70 | function Client(options) { 71 | this.options = options; 72 | this.serviceSocket = undefined; 73 | this.bufferData = true; 74 | this.buffer = []; 75 | 76 | var client = this; 77 | client.connect(); 78 | client.relaySocket.on("data", function(data) { 79 | client.receiveData(data); 80 | }); 81 | client.relaySocket.on("close", function(hadError) { 82 | client.close(); 83 | }); 84 | client.relaySocket.on("error", function(error) { 85 | }); 86 | } 87 | 88 | function makeRelayTlsOptions(options) { 89 | var tlsOptions = { 90 | rejectUnauthorized: options.rejectUnauthorized, 91 | secureProtocol: "TLSv1_2_method", 92 | pfx: fs.readFileSync(options.pfx), 93 | passphrase: options.passphrase, 94 | }; 95 | if (options.caFile) { 96 | tlsOptions.ca = fs.readFileSync(options.caFile); 97 | } 98 | return tlsOptions; 99 | } 100 | 101 | function makeServiceTlsOptions(options) { 102 | var tlsOptions = { 103 | rejectUnauthorized: options.rejectUnauthorized, 104 | secureProtocol: "TLSv1_2_method" 105 | }; 106 | return tlsOptions; 107 | } 108 | 109 | Client.prototype.connect = function() { 110 | var client = this; 111 | if (this.options.tls) { 112 | this.relaySocket = tls.connect(this.options.relayPort, 113 | this.options.relayHost, this.options.relayTlsOptions, 114 | function() { 115 | client.authorize(); 116 | }); 117 | } else { 118 | this.relaySocket = new net.Socket(); 119 | this.relaySocket.connect(this.options.relayPort, 120 | this.options.relayHost, 121 | function() { 122 | client.authorize(); 123 | }); 124 | } 125 | }; 126 | 127 | Client.prototype.receiveData = function(data) { 128 | if (this.serviceSocket == undefined) { 129 | this.emit("pair"); 130 | this.createServiceSocket(this.options.host, this.options.port); 131 | } 132 | if (this.bufferData) { 133 | this.buffer[this.buffer.length] = data; 134 | } else { 135 | this.serviceSocket.write(data); 136 | } 137 | }; 138 | 139 | Client.prototype.close = function() { 140 | if (this.serviceSocket != undefined) { 141 | this.serviceSocket.destroy(); 142 | } else { 143 | this.emit("close"); 144 | } 145 | }; 146 | 147 | Client.prototype.createServiceSocket = function(host, port) { 148 | var client = this; 149 | if (this.options.tls === "both") { 150 | this.serviceSocket = tls.connect(port, host, 151 | this.options.serviceTlsOptions, 152 | function() { 153 | client.writeBuffer(); 154 | }); 155 | } else { 156 | this.serviceSocket = new net.Socket(); 157 | this.serviceSocket.connect(port, host, function() { 158 | client.writeBuffer(); 159 | }); 160 | } 161 | this.serviceSocket.on("data", function(data) { 162 | try { 163 | client.relaySocket.write(data); 164 | } catch (ex) { 165 | } 166 | }); 167 | this.serviceSocket.on("error", function(hadError) { 168 | client.relaySocket.end(); 169 | }); 170 | }; 171 | 172 | Client.prototype.writeBuffer = function() { 173 | this.bufferData = false; 174 | if (this.buffer.length > 0) { 175 | for (var i = 0; i < this.buffer.length; i++) { 176 | this.serviceSocket.write(this.buffer[i]); 177 | } 178 | this.buffer.length = 0; 179 | } 180 | }; 181 | -------------------------------------------------------------------------------- /relay-server.js: -------------------------------------------------------------------------------- 1 | var util = require("util"); 2 | var EventEmitter = require("events").EventEmitter; 3 | var net = require("net"); 4 | var tls = require('tls'); 5 | var fs = require('fs'); 6 | 7 | module.exports = { 8 | createRelayServer: function(relayPort, internetPort, options) { 9 | if (options === undefined) { 10 | options = { 11 | tls: false, 12 | pfx: "cert.pfx", 13 | passphrase: "abcd" 14 | }; 15 | } 16 | return new RelayServer(relayPort, internetPort, options); 17 | } 18 | }; 19 | 20 | function RelayServer(relayPort, internetPort, options) { 21 | this.options = options || {}; 22 | this.relayPort = relayPort; 23 | this.internetPort = internetPort; 24 | this.createListeners(); 25 | var server = this; 26 | this.relayListener.on("new", function(client) { 27 | server.internetListener.pair(server.relayListener, client); 28 | }); 29 | this.internetListener.on("new", function(client) { 30 | server.relayListener.pair(server.internetListener, client); 31 | }); 32 | } 33 | 34 | RelayServer.prototype.createListeners = function() { 35 | this.relayTlsOptions = makeRelayTlsOptions(this.options); 36 | this.relayListener = new Listener(this.relayPort, { 37 | hostname: this.options.hostname, 38 | tls: this.options.tls !== false ? true : false, 39 | pfx: this.options.pfx, 40 | passphrase: this.options.passphrase, 41 | tlsOptions: this.relayTlsOptions 42 | }); 43 | this.internetTlsOptions = makeInternetTlsOptions(this.options); 44 | this.internetListener = new Listener(this.internetPort, { 45 | hostname: this.options.hostname, 46 | bufferData: true, 47 | timeout: 20000, 48 | tls: this.options.tls === "both" ? true : false, 49 | pfx: this.options.pfx, 50 | passphrase: this.options.passphrase, 51 | tlsOptions: this.internetTlsOptions 52 | }); 53 | }; 54 | 55 | RelayServer.prototype.end = function() { 56 | this.relayListener.end(); 57 | this.internetListener.end(); 58 | }; 59 | 60 | util.inherits(Listener, EventEmitter); 61 | 62 | function Listener(port, options) { 63 | this.port = port; 64 | this.options = options || {}; 65 | this.pending = []; 66 | this.active = []; 67 | var listener = this; 68 | if (options.tls === true) { 69 | this.server = tls.createServer(options.tlsOptions, function(socket) { 70 | listener.createClient(socket); 71 | }); 72 | } else { 73 | this.server = net.createServer(function(socket) { 74 | listener.createClient(socket); 75 | }); 76 | } 77 | this.server.listen(port, options.hostname); 78 | } 79 | 80 | function makeRelayTlsOptions(options) { 81 | var tlsOptions = { 82 | pfx: fs.readFileSync(options.pfx), 83 | passphrase: options.passphrase, 84 | secureProtocol: "TLSv1_2_method" 85 | }; 86 | if (options.auth) { 87 | tlsOptions.requestCert = true; 88 | } 89 | if (options.caFile) { 90 | tlsOptions.ca = fs.readFileSync(options.caFile); 91 | } 92 | return tlsOptions; 93 | } 94 | 95 | function makeInternetTlsOptions(options) { 96 | var tlsOptions = { 97 | pfx: fs.readFileSync(options.pfx), 98 | passphrase: options.passphrase, 99 | secureProtocol: "TLSv1_2_method" 100 | }; 101 | return tlsOptions; 102 | } 103 | 104 | Listener.prototype.createClient = function(socket) { 105 | var client = new Client(socket, { 106 | bufferData: this.options.bufferData, 107 | timeout: this.options.timeout 108 | }); 109 | var listener = this; 110 | client.on("close", function() { 111 | listener.handleClose(client); 112 | }); 113 | listener.emit("new", client); 114 | }; 115 | 116 | Listener.prototype.handleClose = function(client) { 117 | var i = this.pending.indexOf(client); 118 | if (i != -1) { 119 | this.pending.splice(i, 1); 120 | } else { 121 | i = this.active.indexOf(client); 122 | if (i != -1) { 123 | this.active.splice(i, 1); 124 | } 125 | } 126 | }; 127 | 128 | Listener.prototype.end = function() { 129 | this.server.close(); 130 | destroyAllSockets(this.pending); 131 | destroyAllSockets(this.active); 132 | this.server.unref(); 133 | }; 134 | 135 | function destroyAllSockets(arr) { 136 | for (var i = 0; i < arr.length; i++) { 137 | arr[i].socket.destroy(); 138 | } 139 | } 140 | 141 | Listener.prototype.pair = function(other, client) { 142 | if (this.pending.length > 0) { 143 | var thisClient = this.pending[0]; 144 | this.pending.splice(0, 1); 145 | client.pairedSocket = thisClient.socket; 146 | thisClient.pairedSocket = client.socket; 147 | this.active[this.active.length] = thisClient; 148 | other.active[other.active.length] = client; 149 | client.writeBuffer(); 150 | thisClient.writeBuffer(); 151 | } else { 152 | other.pending.push(client); 153 | } 154 | }; 155 | 156 | util.inherits(Client, EventEmitter); 157 | 158 | function Client(socket, options) { 159 | this.socket = socket; 160 | this.options = options || {}; 161 | if (options.bufferData) { 162 | this.buffer = []; 163 | } 164 | this.pairedSocket = undefined; 165 | this.timeout(); 166 | 167 | var client = this; 168 | socket.on("data", function(data) { 169 | client.receiveData(data); 170 | }); 171 | socket.on("close", function(hadError) { 172 | client.handleClose(hadError); 173 | }); 174 | socket.on("error", function(err) { 175 | client.emit("close"); 176 | }); 177 | } 178 | 179 | Client.prototype.receiveData = function(data) { 180 | if (this.options.bufferData) { 181 | this.buffer[this.buffer.length] = data; 182 | return; 183 | } 184 | try { 185 | this.pairedSocket.write(data); 186 | } catch (ex) { 187 | } 188 | }; 189 | 190 | Client.prototype.handleClose = function(hadError) { 191 | if (this.pairedSocket != undefined) { 192 | this.pairedSocket.destroy(); 193 | } 194 | this.emit("close"); 195 | }; 196 | 197 | Client.prototype.timeout = function() { 198 | var client = this; 199 | if (!client.options.timeout) { 200 | return; 201 | } 202 | setTimeout(function() { 203 | if (client.options.bufferData) { 204 | client.socket.destroy(); 205 | client.emit("close"); 206 | } 207 | }, client.options.timeout); 208 | }; 209 | 210 | Client.prototype.writeBuffer = function() { 211 | if (this.options.bufferData && this.buffer.length > 0) { 212 | try { 213 | for (var i = 0; i < this.buffer.length; i++) { 214 | this.pairedSocket.write(this.buffer[i]); 215 | } 216 | } catch (ex) { 217 | } 218 | this.buffer.length = 0; 219 | } 220 | this.options.bufferData = false; 221 | }; 222 | --------------------------------------------------------------------------------