UNPKG

proxyproto

Version:

Pre-process PROXY protocol headers from node tcp sockets

131 lines (119 loc) 4.43 kB
// pre-process PROXY protocol headers from tcp sockets // https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt const proxyProtoSignature = Buffer.from('0d0a0d0a000d0a515549540a', 'hex') const parser = require('proxy-protocol-v2'); const createServer = (server, options) => { if (!server) { throw new Error('Missing server argument - http.createServer(), https, net, tls, etc'); } options = options || {}; if (!options.hasOwnProperty('handleCommonErrors')) { options.handleCommonErrors = true; } function onError(err, source) { // handle common socket errors if (options.handleCommonErrors) { const errCodes = new Set(['ECONNRESET', 'EPIPE', 'HPE_INVALID_EOF_STATE', 'HPE_HEADER_OVERFLOW']); if (err && err.code && errCodes.has(err.code)) { return; } const errRegex = /SSL\sroutines|TLS\shandshake/; if (errRegex.test(String(err))) { return; } } if (options.onError) { return options.onError(err, source); } throw err; } // create proxy protocol processing server const proxied = require('net').createServer(socket => { const buf = []; let bytesRead = 0; let proxyProtoLength; let isProxyProto; socket.setKeepAlive(true); // prevent idle timeout ECONNRESET if (options.setNoDelay) { socket.setNoDelay(true); // disable nagle algorithm } socket.addListener('error', err => onError(err, 'proxyproto socket')); socket.addListener('data', onData); function onData(buffer) { socket.pause(); bytesRead += buffer.length; buf.push(buffer); if (bytesRead > 16 && Buffer.concat(buf).slice(0,12).equals(proxyProtoSignature)) { isProxyProto = true; } if (isProxyProto && !proxyProtoLength) { proxyProtoLength = 16 + buffer.readUInt16BE(14); } // consume data for proxy proto if (isProxyProto && bytesRead < proxyProtoLength) { socket.resume(); return; } let data = Buffer.concat(buf); if (isProxyProto) { const details = parser.decode(data.slice(0,proxyProtoLength)); ['remoteAddress','remotePort'].forEach(property => { Object.defineProperty(socket, property, { get: () => details[property], configurable: true }); }); data = data.slice(proxyProtoLength); } socket.removeListener('data', onData); // pass socket to server, mimic onconnection // https://github.com/nodejs/node/blob/5de804e636ce577b46027a24941163a421ada472/lib/net.js#L1472 socket.server = server; socket._server = server; server._connections++; server.emit('connection', socket); // socket.emit('data' does not start handshake for tls or https server // call private method for onStreamRead to start handshake // https://github.com/nodejs/node/blob/5de804e636ce577b46027a24941163a421ada472/lib/net.js#L224 socket._handle.onread(data.length, data); socket.resume(); } }); proxied.on('clientError', err => onError(err, 'proxyproto client')); proxied.on('error', err => onError(err, 'proxyproto')); server.on('clientError', err => onError(err, 'server client')); server.on('error', err => onError(err, 'server')); // if server is tls, prepare child socket if (server._sharedCreds) { server.on('secureConnection', socket => { ['remoteAddress','remotePort'].forEach(property => { Object.defineProperty(socket, property, { get: () => socket._parent[property], configurable: true }); }); socket.addListener('error', err => onError(err, 'secure socket')); socket.setKeepAlive(true); // prevent idle timeout ECONNRESET if (options.setNoDelay) { socket.setNoDelay(true); // disable nagle algorithm } }); } else { server.on('connection', socket => { socket.addListener('error', err => onError(err, 'socket')); }); } // listen to listening event function listen() { const port = server.address().port; server.close(); proxied.listen(port, () => console.log(`PROXY protocol parser listening to port ${port}`)); } server.on('listening', listen); // if server is already listening, use that port if (server.listening) { listen(); } return proxied; }; module.exports = { createServer, parser };