diff --git a/lib/proxywrap.js b/lib/proxywrap.js index d0cec6b..5818765 100644 --- a/lib/proxywrap.js +++ b/lib/proxywrap.js @@ -37,6 +37,67 @@ exports.defaults = { overrideRemote: true, }; +function defineRemoteAddress(socket, header){ + Object.defineProperty(socket, 'remoteAddress', { + enumerable: false, + configurable: true, + get: function() { + return header[2]; + } + }); +} + +function defineRemotePort(socket, header){ + Object.defineProperty(socket, 'remotePort', { + enumerable: false, + configurable: true, + get: function() { + return parseInt(header[4], 10); + } + }); +} + +function defineClientAddress(socket, header){ + Object.defineProperty(socket, 'clientAddress', { + enumerable: false, + configurable: true, + get: function() { + return header[2]; + } + }); +} + +function defineClientPort(socket, header){ + Object.defineProperty(socket, 'clientPort', { + enumerable: false, + configurable: true, + get: function() { + return parseInt(header[4], 10); + } + }); +} + +function defineProxyAddress(socket, header){ + Object.defineProperty(socket, 'proxyAddress', { + enumerable: false, + configurable: true, + get: function() { + return header[3]; + } + }); +} + +function defineProxyPort(socket, header){ + Object.defineProperty(socket, 'proxyPort', { + enumerable: false, + configurable: true, + get: function() { + return parseInt(header[5], 10); + } + }); +} + + // Wraps the given module (ie, http, https, net, tls, etc) interface so that // `socket.remoteAddress` and `remotePort` work correctly when used with the // PROXY protocol (http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt) @@ -138,7 +199,21 @@ exports.proxy = function(iface, options) { function onReadable() { var chunk; - while (null != (chunk = socket.read())) { + chunk = socket.read(); + + if(null === chunk && header.length === 0){ + // unshifting will fire the readable event + socket.emit = realEmit; + //socket.unshift(buf.slice(protocolError ? 0 : crlf+2)); + + self.emit('proxiedConnection', socket); + + //restore(); + + return; + } + + while (null !== chunk) { buf = Buffer.concat([buf, chunk]); header += chunk.toString('ascii'); @@ -169,54 +244,16 @@ exports.proxy = function(iface, options) { if( options.overrideRemote ) { - Object.defineProperty(socket, 'remoteAddress', { - enumerable: false, - configurable: true, - get: function() { - return header[2]; - } - }); - Object.defineProperty(socket, 'remotePort', { - enumerable: false, - configurable: true, - get: function() { - return parseInt(header[4], 10); - } - }); + defineRemoteAddress(socket, header); + defineRemotePort(socket, header); } // Source properties of TCP connection - Object.defineProperty(socket, 'clientAddress', { - enumerable: false, - configurable: true, - get: function() { - return header[2]; - } - }); - Object.defineProperty(socket, 'clientPort', { - enumerable: false, - configurable: true, - get: function() { - return parseInt(header[4], 10); - } - }); - - // Destination properties of TCP connection - Object.defineProperty(socket, 'proxyAddress', { - enumerable: false, - configurable: true, - get: function() { - return header[3]; - } - }); - Object.defineProperty(socket, 'proxyPort', { - enumerable: false, - configurable: true, - get: function() { - return parseInt(header[5], 10); - } - }); + defineClientAddress(socket, header); + defineClientPort(socket, header); + defineProxyAddress(socket, header); + defineProxyPort(socket, header); } // unshifting will fire the readable event @@ -235,12 +272,15 @@ exports.proxy = function(iface, options) { } } - break; + return; } else if ( header.length > 107 ) { return destroy('PROXY header too long'); } + + chunk = socket.read(); } + } } diff --git a/test/issue-15.js b/test/issue-15.js new file mode 100644 index 0000000..a03f5f8 --- /dev/null +++ b/test/issue-15.js @@ -0,0 +1,87 @@ +'use strict'; +/* related to issue https://github.com/findhit/proxywrap/issues/15 */ +var http = require('http'), + assert = require('assert'), + net = require('net'), + exec = require('child_process').exec, + child, + proxyWrap = require('..'); + +function findCloseWaitConnections(port, callback) { + var child = exec('netstat -tonp | grep 8000 | grep CLOSE_WAIT', + function (err, stdout, stderr) { + if (err) { + return callback(err); + } + return callback(null, stdout); + }); +} + +function reproduce(proxyWrapConf, callback) { + var socket, server, port, proxiedHttp; + if (!callback) { + callback = proxyWrapConf; + proxyWrapConf = null; + } + + proxiedHttp = proxyWrap.proxy(http, proxyWrapConf); + + server = proxiedHttp.createServer(function handler(req, res) { + throw new Error('For this test socket should not call #write()'); + }).listen(function (err) { + if (err) { + return done(err); + } + port = this.address().port; + socket = net.connect({ + port: port + }, function () { + socket.end(); + }); + socket.on('end', function () { + return callback(null, server); + }); + }); +} +describe('Sockets closed before any write #15', function () { + describe('On strict mode', function () { + var port, server; + before(function (done) { + reproduce(function (err, _server) { + server = _server; + port = server.address().port; + return done(); + }); + }); + after(function () { + server.close(); + }); + it('should be restored', function (done) { + findCloseWaitConnections(port, function (err, stdout) { + assert.deepEqual(null, stdout); + return done(); + }); + }); + }); + describe('On non-strict mode', function () { + var port, server; + before(function (done) { + reproduce({ + strict: false + }, function (err, _server) { + server = _server; + port = server.address().port; + return done(); + }); + }); + after(function () { + server.close(); + }); + it('should be restored', function (done) { + findCloseWaitConnections(port, function (err, stdout) { + assert.deepEqual(null, stdout); + return done(); + }); + }); + }); +});