luminati-proxy-stripped
Version:
A configurable local proxy for luminati.io
382 lines (374 loc) • 14.1 kB
JavaScript
// LICENSE_CODE ZON
; /*jslint node:true*/
const net = require('net');
const cluster = require('cluster');
const child_process = require('child_process');
const EventEmitter = require('events');
const _http_common = require('_http_common');
const tls = require('tls');
const socket_pair = require('socket-pair');
const etask = require('../util/etask.js');
const zerr = require('../util/zerr.js');
const conv = require('../util/conv.js');
const zutil = require('../util/util.js');
const array = require('../util/array.js');
const hash = require('./hash.js');
const os = require('os');
const proc = {
cluster_workers_n: ()=>{ return os.cpus().length; },
};
const E = exports, assign = Object.assign;
zutil.inherit_init(E, EventEmitter);
// cannot monkey patch 'freeParser' since it assigned to variable in modules
let old_free = _http_common.parsers.free.bind(_http_common.parsers);
_http_common.parsers.free = function(parser){
if (parser.old_execute)
{
parser.execute = parser.old_execute;
delete parser.old_execute;
}
delete parser.onIncoming;
return old_free(parser);
};
let worker_pending = {};
let master_init = ()=>{
cluster.on('fork', worker=>{
for (let {msg, handle} of worker_pending[worker.id]||[])
worker.send(msg, handle);
delete worker_pending[worker.id];
worker.on('message', (msg, handle)=>{
if (msg.cmd!='cluster_route:w2w_channel')
return;
let w = cluster.workers[msg.to_worker_id];
if (w)
w.send(msg, handle);
else
{
let queue = worker_pending[msg.to_worker_id];
if (!queue)
queue = worker_pending[msg.to_worker_id] = [];
queue.push({msg, handle});
}
});
});
};
let ipc_socket_error = e=>zerr('cluster_route: socket error: '+e);
E._create_ipc_channel = fd=>{
// save process so we can restore it after _forkChild() changed it
let original_process = process;
try {
let channel = global.process = new EventEmitter();
child_process._forkChild(fd);
// fixing node bug:
// recv handle loss when msg is splitted into multiple chunks
let pipe = channel._channel, orig_onread = pipe.onread, last_handle;
pipe.onread = (nread, pool, handle)=>{
last_handle = handle||last_handle;
return orig_onread.call(pipe, nread, pool, last_handle);
};
channel.on('internalMessage', (msg, handle)=>{
if (handle===last_handle)
last_handle = null;
});
return channel;
} finally { global.process = original_process; }
};
E.create_socket_pair = ()=>etask(function*create_socket_pair(){
let socket = socket_pair.create((err, other)=>{
if (err)
throw err;
other.pause();
this.continue(other);
});
let other = yield this.wait();
let fds = [socket._handle.fd, other._handle.fd];
return {socket, other, fds};
});
let tx_w2w_channel = {};
let worker_init = channel_needed=>etask(function*worker_init(){
for (let i=1; i<=proc.cluster_workers_n(); i++)
{
if (i==cluster.worker.id)
continue;
if (channel_needed && !channel_needed(cluster.worker.id, i))
continue;
let {socket, other} = yield E.create_socket_pair();
if (!socket._handle||!other._handle)
throw new Error(`cluster_route: bad ipc socket`);
tx_w2w_channel[i] = E._create_ipc_channel(socket._handle.fd);
zerr('cluster_route: w2w_channel, to:%s, from:%s', i,
cluster.worker.id);
process.send({cmd: 'cluster_route:w2w_channel', to_worker_id: i,
from_worker_id: cluster.worker.id}, other._handle);
}
process.on('message', (msg, recv_handle)=>{
if (msg.cmd!='cluster_route:w2w_channel')
return;
if (!recv_handle)
zerr('cluster_route no handle: %O', msg);
let rx_w2w_channel = E._create_ipc_channel(recv_handle.fd);
rx_w2w_channel.on('message', (w2w_msg, w2w_recv_handle)=>{
console.log('cluster_route: channel message', w2w_msg, w2w_recv_handle.fd);
if (w2w_msg && w2w_msg.cmd=='cluster_route:connection')
{
if (!w2w_recv_handle)
{
return zerr('cluster_route socket lost on ipc: %O %O', msg,
w2w_msg);
}
w2w_recv_handle.on('error', ipc_socket_error);
}
E.emit('message', w2w_msg, w2w_recv_handle, msg.from_worker_id);
});
});
E.on('message', (msg, recv_handle, from_worker_id)=>{
if (msg && typeof msg.cmd=='string')
E.emit(msg.cmd, msg, recv_handle, from_worker_id);
});
});
let socket_err = e=>{
zerr('cluster_route pipe '+zerr.e2s(e));
};
let pipe_wrap_socket = src_socket=>etask(function*pipe_wrap_socket(){
let spair = yield E.create_socket_pair();
let local_socket = spair.socket;
let ret_socket = spair.other;
let remove_listeners = sock=>{
if (!sock)
return false;
sock.removeListener('close', handle_close);
sock.removeListener('error', handle_error);
return true;
};
let handle_close = ()=>{
if (remove_listeners(src_socket))
src_socket = undefined;
if (remove_listeners(local_socket))
local_socket = undefined;
if (remove_listeners(ret_socket))
ret_socket = undefined;
};
let handle_error = e=>{
if (src_socket)
{
src_socket.unpipe(local_socket);
src_socket.destroy();
}
if (local_socket)
{
local_socket.unpipe(src_socket);
local_socket.destroy();
}
if (ret_socket)
ret_socket.destroy();
handle_close();
};
src_socket.pipe(local_socket).pipe(src_socket);
for (let sock of [local_socket, ret_socket, src_socket])
{
sock.on('close', handle_close)
.on('error', socket_err)
.on('error', handle_error);
}
return ret_socket;
});
E.send = (msg, send_handle, to_worker_id)=>etask(function*send(){
console.log('cluster_route: send', msg, send_handle._handle.fd, to_worker_id, to_worker_id==cluster.worker.id);
if (to_worker_id==cluster.worker.id)
return void E.emit('message', msg, send_handle, cluster.worker.id);
let w2w_channel;
console.log('send, has_channel', !!tx_w2w_channel[to_worker_id]);
if (!(w2w_channel = tx_w2w_channel[to_worker_id]))
throw new Error(`No channel to worker '${to_worker_id}'`);
if (send_handle)
{
msg.send_handle_src = {ip: send_handle.remoteAddress,
port: send_handle.remotePort, ssl: send_handle.encrypted};
}
console.log('send send_handle_src', msg.send_handle_src);
if (send_handle && send_handle.encrypted)
{
send_handle._handle.readStart();
console.log('send wrap socket');
send_handle = yield pipe_wrap_socket(send_handle);
}
if (msg.cmd=='cluster_route:connection')
{
if (send_handle)
msg.handle_ip = send_handle.remoteAddress;
else
msg.no_handle = true;
}
console.log('send w2w channel send', msg, send_handle._handle.fd);
w2w_channel.send(msg, send_handle);
});
let set_hook = (ee, event_name, hook_fn)=>{
let orig_listeners = array.to_array(ee._events[event_name]);
let call_orig = function(){
for (let listener of orig_listeners)
listener.apply(ee, arguments);
};
ee._events[event_name] = hook_fn.bind(ee, call_orig);
return call_orig;
};
let ip_hash = ip=>hash.hash_int(conv.inet_addr(ip));
let select_worker_by_ip = socket=>
ip_hash(socket.remoteAddress)%proc.cluster_workers_n()+1;
// XXX vladislav: when x-forwarded-for is not defined, we use only a single
// worker
let select_worker_by_ip_smart = (socket, req)=>ip_hash(
req.headers['x-forwarded-for']||socket.remoteAddress)
%proc.cluster_workers_n()+1;
let select_worker_by_server_id = (socket, req)=>{
let server_id = socket.route.opt.server_id||"none";
return hash.hash_string(server_id)%proc.cluster_workers_n()+1;
};
let check_fallback = to_worker_id=>{
console.log('check_fallback start', to_worker_id);
if (!to_worker_id)
to_worker_id = cluster.worker.id;
if (to_worker_id==cluster.worker.id)
to_worker_id = cluster.worker.id;
console.log('check_falback end', to_worker_id, cluster.worker.id);
return to_worker_id;
};
let conn_hook = (pass_conn, socket)=>void etask(function*_conn_hook(){
let route = socket.route;
socket.removeListener('error', ipc_socket_error);
if (route.routed)
{
route.routed = false;
return pass_conn(socket);
}
let opt = route.opt;
socket.on('error', e=>{
socket.destroy();
zerr(`cluster_route(${opt.server_id}): ${zerr.e2s(e)}`);
});
E.send({cmd: 'cluster_route:connection', server_id: opt.server_id},
socket, check_fallback(yield opt.select(socket)));
});
let parser_on_incoming = function(req, should_keep_alive){
let parser = this, socket = parser.socket, route = socket.route;
if (route.routed)
{
route.routed = false;
return route.orig_on_incoming(req, should_keep_alive);
}
req.should_keep_alive = should_keep_alive;
route.paused_on_headers = true;
socket._handle.readStop();
parser.pause();
};
let parser_execute = function(buf){
let parser = this, socket = parser.socket, route = socket&&socket.route;
let res = parser.constructor.prototype.execute.call(parser, buf);
if (!(route && route.paused_on_headers))
return res;
return etask(function*_parser_execute(){
let opt = route.opt, req = parser.incoming;
let to_worker_id = check_fallback(yield opt.select(socket, req));
if (to_worker_id==cluster.worker.id)
{
zerr.info(`cluster_route(${opt.server_id}): don't route`);
route.orig_on_incoming(req, req.should_keep_alive);
route.paused_on_headers = false;
socket._handle.readStart();
parser.resume();
buf = buf.slice(res.bytesParsed);
let next_res = parser.execute(buf);
if (next_res instanceof Error && parser[parser.constructor.kOnExecute])
{
next_res.bytesParsed = (next_res.bytesParsed||0)+res.bytesParsed;
parser[parser.constructor.kOnExecute](next_res, buf);
}
return;
}
delete socket._events.data;
for (let l of socket.listeners('end'))
{
if (l.name=='bound socketOnEnd')
socket.removeListener('end', l);
}
_http_common.freeParser(parser, req, socket);
while (socket._httpMessage)
{
zerr.info(`cluster_route(${opt.server_id}): wait for response`);
let wait = this.wait();
socket._httpMessage.on('finish', ()=>wait.continue());
yield wait;
}
let head = `${req.method} ${req.url} `+
`HTTP/${req.httpVersionMajor}.${req.httpVersionMinor}\r\n`;
for (let i=0; i<req.rawHeaders.length; i+=2)
head += `${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}\r\n`;
head += '\r\n';
let consumed_data = Buffer.concat(
[new Buffer(head), buf.slice(res.bytesParsed+1)]).toString('base64');
zerr.info(`cluster_route(${opt.server_id}): pass to C${to_worker_id}`);
E.send({cmd: 'cluster_route:connection', server_id: opt.server_id,
consumed_data}, req.socket, to_worker_id);
}); };
let http_server_on_connection = socket=>{
if (!socket._handle)
return void zerr('cluster_route broken socket '+socket.remoteAddress);
// make parser to unconsume socket and let 'data' events occur
socket.on('data', ()=>0);
let parser = socket.parser;
socket.route.orig_on_incoming = parser.onIncoming.bind(parser);
parser.onIncoming = parser_on_incoming;
parser.old_execute = parser.execute;
parser.execute = parser_execute;
};
E.setup = (server, opt)=>{
if (!cluster.isWorker)
throw new Error('cluster_route.setup called from master');
opt = assign({select: 'ip'}, opt);
if (opt.server_id==undefined)
{
let upd_server_id = ()=>{
let addr;
if (addr = server.address())
opt.server_id = addr.family+':'+addr.address+':'+addr.port;
};
server.on('listening', upd_server_id);
upd_server_id();
}
if (opt.select=='ip_smart')
assign(opt, {select: select_worker_by_ip_smart, need_headers: true});
else if (opt.select=='ip')
assign(opt, {select: select_worker_by_ip});
else if (opt.select=='server_id')
assign(opt, {select: select_worker_by_server_id});
let conn_ev = server instanceof tls.Server && opt.need_headers
? 'secureConnection' : 'connection';
if (opt.need_headers)
server.on(conn_ev, http_server_on_connection);
else
set_hook(server, conn_ev, conn_hook);
E.on('cluster_route:connection', (msg, socket)=>{
console.log('on connection1', msg, opt.server_id, socket._handle.fd);
if (msg.server_id!=opt.server_id)
return;
socket.route = {src: msg.send_handle_src, routed: true};
let src = socket.route.src;
if (src && src.ip)
socket._peername = {address: src.ip, port: src.port, family: 4};
server.emit(conn_ev, socket);
console.log('on connection2', msg.consumed_data);
if (msg.consumed_data)
socket.unshift(new Buffer(msg.consumed_data, 'base64'));
socket.resume();
});
set_hook(server, conn_ev, (pass_conn, socket)=>{
socket.route = assign({opt}, socket.route);
socket.server = server;
pass_conn(socket);
});
};
E.init = opt=>{
opt = opt||{};
if (cluster.isMaster)
return void master_init();
worker_init(opt.channel_needed);
};