Fetch the repository succeeded.
// Copyright (c) 2013 Tom Zhou<appnet.link@gmail.com>
var eventEmitter = require('events').EventEmitter,
util = require('util'),
WEBPP = require('appnet.io'),
SEP = WEBPP.SEP,
vURL = WEBPP.vURL,
URL = require('url'),
NET = require('net'),
UDT = require('udt'),
httpps = require('httpps'),
filter = require('./filter');
OS = require('os'); // for network interface check;
// helpers
function isLocalhost(host){
return ((host === 'localhost') || (host === '127.0.0.1') ||
(host === '0:0:0:0:0:0:0:1') || (host === '::1'));
}
function isLocalintf(host){
var intfs = OS.networkInterfaces();
var yes = false;
for (var k in intfs)
for (var kk in intfs[k])
if ((intfs[k])[kk].address && ((intfs[k])[kk].address === host)) yes = true;
return yes;
}
// Debug level
var Debug = 0;
// Proxy class
// a proxy will contain one appnet.io name-client
// - options: user custom parameters, like {secmode: ..., usrkey: ..., domain: ..., endpoints: ..., turn: ...}
// - options.secmode: ssl, enable ssl/https; acl, enable ssl/https,host-based ACL
// - options.export: Forward-proxy's Export service vURL
// - options.access_local: Enable local access on Export host, 1: enable, 0: disable, default disable it
// - fn: callback to pass proxy informations
var Proxy = module.exports = function(options, fn){
var self = this;
if (!(this instanceof Proxy)) return new Proxy(options, fn);
// super constructor
eventEmitter.call(self);
if (typeof options == 'function') {
fn = options;
options = {};
}
// check arguments
self.access_local = options.access_local || 0;
// 0.
// export proxy cache
self.exportCache = {};
// 0.1
// fill dedicated export service vURL
if (options && options.export) {
self.exportCache[options.export] = {vurl: options.export};
}
// 1.
// create name client
var nmcln = self.nmcln = new WEBPP({
usrinfo: {
domain: (options && options.domain) || '51dese.com',
usrkey: (options && options.usrkey) || ('forward-proxy@'+Date.now())
},
srvinfo: {
timeout: 20,
endpoints: (options && options.endpoints) || [
{ip: '51dese.com', port: 51686},
{ip: '51dese.com', port: 51868}
],
turn: (options && options.turn) || [
{ip: '51dese.com', agent: 51866, proxy: 51688}
]
},
// vURL mode: vpath-based
vmode: vURL.URL_MODE_PATH,
// secure mode
secmode: (options && options.secmode === 'ssl') ?
SEP.SEP_SEC_SSL : SEP.SEP_SEC_SSL_ACL_HOST,
// ssl mode
sslmode: (options && options.sslmode === 'both') ?
SEP.SEP_SSL_AUTH_SRV_CLNT : SEP.SEP_SSL_AUTH_SRV_ONLY
});
// 2.
// check ready
nmcln.once('ready', function(){
// 3.
// export http proxy
// TBD... admin portal page
function exportHttpProxy(req, res){
res.writeHead(400);
res.end('TBD... admin portal page');
console.error('TBD... admin portal page');
}
// 3.1
// export http tunnel
function exportHttpTunnel(req, socket, head){
// 1.
// find next hop in case middle relay using turn-forward-to headers
var middle = req.headers && req.headers['turn-forward-to'];
if (middle) {
var relays = middle.split(',');
var nxstep = relays[0];
// 1.1
// break loops
var loop = false;
var mine = nmcln.vurl.match(vURL.regex_vboth);
for (var idx = 0; idx < relays.length; idx ++)
if (mine === (relays[idx]).match(vURL.regex_vboth)) {
loop = true;
break;
}
if (loop) {
// stop on loop
socket.end('stop on loop');
console.error('stop on loop:'+nmcln.vurl);
return;
}
// 1.2
// check on vURL
var vstrs, vurle;
if (vstrs = nxstep.match(vURL.regex_vboth)) {
vurle = vstrs[0];
// 2.
// get peer info by vURL
nmcln.getvURLInfo(vurle, function(err, routing){
// 2.1
// check error and authentication
if (err || !routing) {
// invalid vURL
socket.end('invalid URL');
console.error('invalid URL:'+nxstep);
} else {
// 3.
// check STUN alability
nmcln.checkStunable(vurle, function(err, yes){
if (err) {
// invalid vURL
socket.end('invalid URL');
console.error('invalid URL:'+nxstep);
} else {
// over STUN
if (yes) {
// 5.
// traverse STUN session to peer
nmcln.trvsSTUN(vurle, function(err, stun){
if (err || !stun) {
// STUN not availabe
socket.end('STUN not available, please use TURN');
console.error('STUN not available:'+nxstep);
} else {
// get peer endpoint
var dstip = stun.peerIP, dstport = stun.peerPort;
// setup tunnel to target by make CONNECT request
var roptions = {
port: dstport,
hostname: dstip,
method: 'CONNECT',
path: req.url,
agent: false,
// set user-specific feature,like maxim bandwidth,etc
localAddress: {
addr: nmcln.ipaddr,
port: nmcln.port,
opt: {
mbw: options.mbw || null
}
}
};
// set SSL related options
if (nmcln.secmode && nmcln.secerts) {
Object.keys(nmcln.secerts).forEach(function(k){
roptions[k] = nmcln.secerts[k];
});
}
// set turn-forward-to header for middle relays
if (relays.length > 1) {
var nmiddle = [];
for (var idx = 1; idx < relays.length; idx ++)
nmiddle.push(relays[idx]);
var going = nmiddle.join(',');
roptions.headers = {};
roptions.headers['turn-forward-to'] = going;
}
var rreq = httpps.request(roptions);
rreq.end();
if (Debug) console.log('tunnel proxy relay, connect to %s:%d', dstip, dstport);
rreq.on('connect', function(rres, rsocket, rhead) {
if (Debug) console.log('tunnel proxy relay, got connected');
socket.write('HTTP/1.1 200 Connection Established\r\n' +
'Proxy-agent: Node-Proxy\r\n' +
'\r\n');
rsocket.pipe(socket);
socket.pipe(rsocket);
rsocket.on('error', function(e) {
console.log("tunnel proxy relay, socket error: " + e);
socket.end();
});
});
rreq.on('error', function(e) {
console.log("tunnel proxy relay, CONNECT request error: " + e);
socket.end();
});
}
});
} else {
// over TURN, not support for middle relays
socket.end('not support turn for middle relays '+nxstep);
console.error('not support turn for middle relays '+nxstep);
}
}
});
}
});
} else {
// not reachable
socket.end('not reachable');
console.error('not reachable:'+nxstep);
}
} else {
// 2.
// reach export
var urls = URL.parse('http://'+req.url, true, true);
var srvip = urls.hostname;
var srvport = urls.port || 443;
// check if access to export local host
if ((self.access_local === 0) && (isLocalhost(srvip) || isLocalintf(srvip))) {
console.log("http tunnel proxy to " + req.url + ", deny local access on export host");
socket.end();
return;
}
if (Debug) console.log('http tunnel proxy, connect to %s:%d', srvip, srvport);
var srvSocket = NET.connect(srvport, srvip, function() {
if (Debug) console.log('http tunnel proxy, got connected!');
///srvSocket.write(head);
socket.write('HTTP/1.1 200 Connection Established\r\n' +
'Proxy-agent: Node-Proxy\r\n' +
'\r\n');
srvSocket.pipe(socket);
socket.pipe(srvSocket);
});
srvSocket.setNoDelay(true);
srvSocket.on('error', function(e) {
console.log("http tunnel proxy to " + req.url + ", socket error: " + e);
socket.end();
});
}
};
// 5.
// import http proxy
function importHttpProxy(req, res){
var vurle, vstrs, urle = req.url;
if (Debug) console.log('proxy to '+urle+',headers:'+JSON.stringify(req.headers));
function resErr(err){
try {
res.writeHead(500);
res.end(err);
} catch (e) {
console.log('res.end exception '+e);
}
}
// 0.
// find next hop
// 1.
// match vURL pattern:
// - vhost like http(s)://xxx.vurl.51dese.com
// - vpath like http(s)://51dese.com"/vurl/xxx"
if (vstrs = req.headers.host.match(vURL.regex_vhost)) {
vurle = vstrs[0];
if (Debug) console.log('proxy for client with vhost:'+vurle);
} else if (vstrs = urle.match(vURL.regex_vpath)) {
vurle = vstrs[0];
// prune vpath in req.url
req.url = req.url.replace(vurle, '');
if (Debug) console.log('proxy for client with vpath:'+vurle);
} else if (vurle = self.findExport(req.headers.host, urle)) {
if (Debug) console.log('use export proxy '+vurle);
} else {
// not reachable
resErr('not reachable');
console.error('not reachable:'+urle);
return;
}
if (Debug) console.log('tunnel proxy for client request.headers:'+JSON.stringify(req.headers)+
',url:'+urle+',vurl:'+vurle);
// 1.1
// !!! rewrite req.url to remove vToken parts
// TBD ... vToken check
req.url = req.url.replace(vURL.regex_vtoken, '');
// 2.
// get peer info by vURL
nmcln.getvURLInfo(vurle, function(err, routing){
// 2.1
// check error and authentication
if (err || !routing) {
// invalid vURL
resErr('invalid URL');
console.error('invalid URL:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// 3.
// check STUN alability
nmcln.checkStunable(vurle, function(err, yes){
if (err) {
// invalid vURL
resErr('invalid URL');
console.error('invalid URL:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// over STUN
if (yes) {
// 5.
// traverse STUN session to peer
nmcln.trvsSTUN(vurle, function(err, stun){
if (err || !stun) {
// STUN not availabe
resErr('STUN not available, please use TURN');
console.error('STUN not available:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// get peer endpoint
var dstip = stun.peerIP, dstport = stun.peerPort;
// 6.
// setup tunnel to target by make CONNECT request
var roptions = {
port: dstport,
hostname: dstip,
method: 'CONNECT',
path: (/(:\d+)$/gi).test(req.headers.host) ? req.headers.host : req.headers.host+':80',
agent: false,
// set user-specific feature,like maxim bandwidth,etc
localAddress: {
addr: nmcln.ipaddr,
port: nmcln.port,
opt: {
mbw: options.mbw || null
}
}
};
// set SSL related options
if (nmcln.secmode && nmcln.secerts) {
Object.keys(nmcln.secerts).forEach(function(k){
roptions[k] = nmcln.secerts[k];
});
}
var rreq = httpps.request(roptions);
rreq.end();
rreq.on('error', function(e) {
console.log("tunnel proxy, CONNECT request error: " + e);
resErr("tunnel proxy, CONNECT request error: " + e);
});
if (Debug) console.log('tunnel proxy, connect to %s:%d', dstip, dstport);
rreq.on('connect', function(rres, rsocket, rhead) {
if (Debug) console.log('tunnel proxy, got connected');
rsocket.on('error', function(e) {
console.log("tunnel proxy, socket error: " + e);
resErr("tunnel proxy, socket error: " + e);
});
if (Debug) console.log('req.headers: '+JSON.stringify(req.headers));
// request on tunnel connection
var toptions = {
method: req.method,
path: req.url.match(/^(http:)/gi)? URL.parse(req.url).path : req.url,
agent: false,
// set headers
headers: req.headers,
// pass rsocket which's request on
createConnection: function(port, host, options){
return rsocket
}
};
var treq = httpps.request(toptions, function(tres){
if (Debug) console.log('tunnel proxy, got response, headers:'+JSON.stringify(tres.headers));
try {
// set headers
Object.keys(tres.headers).forEach(function (key) {
res.setHeader(key, tres.headers[key]);
});
res.writeHead(tres.statusCode);
tres.pipe(res);
tres.on('error', function(e) {
console.log("tunnel proxy, tunnel response error: " + e);
resErr("tunnel proxy, tunnel response error: " + e);
});
} catch (e) {
console.log("tunnel proxy, tunnel response exception: " + e);
}
});
treq.on('error', function(e) {
console.log("tunnel proxy, tunnel request error: " + e);
resErr("tunnel proxy, tunnel request error: " + e);
});
req.pipe(treq);
req.on('error', resErr);
req.on('aborted', function () {
treq.abort();
});
if (req.trailers) {
treq.end();
}
req.on('close', function () {
treq.abort();
});
});
}
});
} else {
// over TURN
// 5.
// traverse TURN session to peer
// notes: TURN session will use vToken for authentication
nmcln.trvsTURN(vurle, function(err, turn){
if (err || !turn) {
// TURN not availabe
resErr('TURN not available, please check TURN service setup');
console.error('TURN not available:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// 6.
// setup tunnel to target by make CONNECT request
var roptions = {
port: routing.turn.proxyport,
hostname: routing.turn.ipaddr,
method: 'CONNECT',
path: (/(:\d+)$/gi).test(req.headers.host) ? req.headers.host : req.headers.host+':80',
agent: false
};
// set turn-forward-to header: destination name-client's full vURL string
roptions.headers = {};
roptions.headers['turn-forward-to'] = vurle;
// set SSL related options
// TBD...
/*if (nmcln.secmode && nmcln.secerts) {
Object.keys(nmcln.secerts).forEach(function(k){
roptions[k] = nmcln.secerts[k];
});
}*/
var rreq = httpps.request(roptions);
rreq.end();
rreq.on('error', function(e) {
console.log("tunnel proxy over TURN, CONNECT request error: " + e);
resErr("tunnel proxy over TURN, CONNECT request error: " + e);
});
if (Debug) console.log('tunnel proxy over TURN, connect to %s:%d', routing.turn.ipaddr, routing.turn.proxyport);
rreq.on('connect', function(rres, rsocket, rhead) {
if (Debug) console.log('tunnel proxy over TURN, got connected');
rsocket.on('error', function(e) {
console.log("tunnel proxy over TURN, socket error: " + e);
resErr("tunnel proxy over TURN, socket error: " + e);
});
// request on tunnel connection
var toptions = {
method: req.method,
path: req.url.match(/^(http:)/gi)? URL.parse(req.url).path : req.url,
agent: false,
// set headers
headers: req.headers,
// pass rsocket which's request on
createConnection: function(port, host, options){
return rsocket
}
};
var treq = httpps.request(toptions, function(tres){
if (Debug) console.log('tunnel proxy over TURN, got response, headers:'+JSON.stringify(tres.headers));
// set headers
Object.keys(tres.headers).forEach(function (key) {
res.setHeader(key, tres.headers[key]);
});
try {
res.writeHead(tres.statusCode);
tres.pipe(res);
tres.on('error', function(e) {
console.log("tunnel proxy over TURN, tunnel response error: " + e);
resErr("tunnel proxy, tunnel response error: " + e);
});
} catch (e) {
console.log("tunnel proxy over TURN, tunnel response exception: " + e);
}
});
treq.on('error', function(e) {
console.log("tunnel proxy over TURN, tunnel request error: " + e);
resErr("tunnel proxy over TURN, tunnel request error: " + e);
});
req.pipe(treq);
req.on('error', resErr);
req.on('aborted', function () {
treq.abort();
});
if (req.trailers) {
treq.end();
}
req.on('close', function () {
treq.abort();
});
});
}
});
}
}
});
}
});
}
// 5.1
// import http tunnel proxy based on CONNECT method
function importHttpTunnel(req, socket, head) {
var vurle, vstrs, urle = req.url;
if (Debug) console.log('tunnel to '+urle);
// 0.
// find next hop
// 1.
// match vURL pattern:
// - vhost like http(s)://xxx.vurl.51dese.com
// - vpath like http(s)://51dese.com/vurl/xxx"
if (vstrs = urle.match(vURL.regex_vhost)) {
vurle = vstrs[0];
if (Debug) console.log('tunnel for client with vhost:'+vurle);
} else if (vstrs = urle.match(vURL.regex_vpath)) {
vurle = vstrs[0];
// prune vpath in req.url
req.url = req.url.replace(vurle, '');
if (Debug) console.log('proxy for client with vpath:'+vurle);
} else if (vurle = self.findExport(urle, urle)) {
if (Debug) console.log('use export proxy '+vurle);
} else {
// not reachable
socket.end('not reachable');
console.error('not reachable:'+urle);
return;
}
if (Debug) console.log('tunnel proxy for client request.headers:'+JSON.stringify(req.headers)+
',url:'+urle+',vurl:'+vurle);
// 1.1
// !!! rewrite req.url to remove vToken parts
// TBD ... vToken check
req.url = req.url.replace(vURL.regex_vtoken, '');
// 2.
// get peer info by vURL
nmcln.getvURLInfo(vurle, function(err, routing){
// 2.1
// check error and authentication
if (err || !routing) {
// invalid vURL
socket.end('invalid URL');
console.error('invalid URL:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// 3.
// check STUN alability
nmcln.checkStunable(vurle, function(err, yes){
if (err) {
// invalid vURL
resErr('invalid URL');
console.error('invalid URL:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// over STUN
if (yes) {
// 5.
// traverse STUN session to peer
nmcln.trvsSTUN(vurle, function(err, stun){
if (err || !stun) {
// STUN not availabe
socket.end('STUN not available, please use TURN');
console.error('STUN not available:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// get peer endpoint
var dstip = stun.peerIP, dstport = stun.peerPort;
// 6.
// if req.url is valid vURL, connect it directly,
// otherwise do CONNECT tunnel over export vURL
// notes: disable it to avoid middle-man attack
if (urle.match(vurle)) {
// 6.1
// connect it directly
if (Debug) console.log('https proxy, httpp connect to %s:%d', dstip, dstport);
// connection options
var coptions = {
port: dstport,
host: dstip,
// set user-specific feature,like maxim bandwidth,etc
localAddress: {
addr: nmcln.ipaddr,
port: nmcln.port,
opt: {
mbw: options.mbw || null
}
}
};
var srvSocket = UDT.connect(coptions, function() {
if (Debug) console.log('https proxy, httpp connect, got connected!');
socket.write('HTTP/1.1 200 Connection Established\r\n' +
'Proxy-agent: Node-Proxy\r\n' +
'\r\n');
srvSocket.pipe(socket);
socket.pipe(srvSocket);
});
srvSocket.on('error', function(e) {
console.log("https proxy, httpp connect to " + req.url + ", socket error: " + e);
socket.end();
});
} else {
// 6.2
// setup tunnel to target by make CONNECT request
var roptions = {
port: dstport,
hostname: dstip,
method: 'CONNECT',
path: req.url,
agent: false,
// set user-specific feature,like maxim bandwidth,etc
localAddress: {
addr: nmcln.ipaddr,
port: nmcln.port,
opt: {
mbw: options.mbw || null
}
}
};
// set SSL related options
if (nmcln.secmode && nmcln.secerts) {
Object.keys(nmcln.secerts).forEach(function(k){
roptions[k] = nmcln.secerts[k];
});
}
var rreq = httpps.request(roptions);
rreq.end();
if (Debug) console.log('tunnel proxy, connect to %s:%d', dstip, dstport);
rreq.on('connect', function(rres, rsocket, rhead) {
if (Debug) console.log('tunnel proxy, got connected');
socket.write('HTTP/1.1 200 Connection Established\r\n' +
'Proxy-agent: Node-Proxy\r\n' +
'\r\n');
rsocket.pipe(socket);
socket.pipe(rsocket);
rsocket.on('error', function(e) {
console.log("tunnel proxy, socket error: " + e);
socket.end();
});
});
rreq.on('error', function(e) {
console.log("tunnel proxy, CONNECT request error: " + e);
socket.end();
});
}
}
});
} else {
// over TURN
// 5.
// traverse TURN session to peer
nmcln.trvsTURN(vurle, function(err, turn){
if (err || !turn) {
// TURN not availabe
resErr('TURN not available, please check TURN service setup');
console.error('TURN not available:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// 6.
// setup tunnel to target by make CONNECT request
var roptions = {
port: routing.turn.proxyport,
hostname: routing.turn.ipaddr,
method: 'CONNECT',
path: req.url,
agent: false
};
// set turn-forward-to header: destination name-client's full vURL string
roptions.headers = {};
roptions.headers['turn-forward-to'] = vurle;
// set SSL related options
/*if (nmcln.secmode && nmcln.secerts) {
Object.keys(nmcln.secerts).forEach(function(k){
roptions[k] = nmcln.secerts[k];
});
}*/
var rreq = httpps.request(roptions);
rreq.end();
if (Debug) console.log('tunnel proxy over TURN, connect to %s:%d',
routing.turn.ipaddr, routing.turn.proxyport);
rreq.on('connect', function(rres, rsocket, rhead) {
if (Debug) console.log('tunnel proxy over TURN, got connected');
socket.write('HTTP/1.1 200 Connection Established\r\n' +
'Proxy-agent: Node-Proxy\r\n' +
'\r\n');
rsocket.pipe(socket);
socket.pipe(rsocket);
rsocket.on('error', function(e) {
console.log("tunnel proxy over TURN, socket error: " + e);
socket.end();
});
});
rreq.on('error', function(e) {
console.log("tunnel proxy over TURN, CONNECT request error: " + e);
socket.end();
});
}
});
}
}
});
}
});
}
// 5.2
// import socks proxy
function importSocksProxy(socket, port, address, proxy_ready) {
var vurle, vstrs, urle = address+':'+port;
if (Debug) console.log('socks proxy to '+urle);
// 1.
// find next hop
// TBD...
if (vstrs = urle.match(vURL.regex_vhost)) {
vurle = vstrs[0];
if (Debug) console.log('tunnel for client with vhost:'+vurle);
} else if (vurle = self.findExport(urle, urle)) {
if (Debug) console.log('use export proxy '+vurle);
} else {
// not reachable
socket.end('not reachable');
console.error('not reachable:'+urle);
return;
}
if (Debug) console.log('socks proxy for client'+
',url:'+urle+',vurl:'+vurle);
// 2.
// get peer info by vURL
nmcln.getvURLInfo(vurle, function(err, routing){
// 2.1
// check error and authentication
if (err || !routing) {
// invalid vURL
socket.end('invalid URL');
console.error('invalid URL:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// 3.
// check STUN alability
nmcln.checkStunable(vurle, function(err, yes){
if (err) {
// invalid vURL
resErr('invalid URL');
console.error('invalid URL:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// over STUN
if (yes) {
// 5.
// traverse STUN session to peer
nmcln.trvsSTUN(vurle, function(err, stun){
if (err || !stun) {
// STUN not availabe
socket.end('STUN not available, please use TURN');
console.error('STUN not available:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// get peer endpoint
var dstip = stun.peerIP, dstport = stun.peerPort;
// 6.
// if address:port is valid vURL, connect it directly,
// otherwise do CONNECT tunnel over export vURL
// notes: disable it to avoid middle-man attack
if (urle.match(vurle)) {
// 6.1
// connect it directly
if (Debug) console.log('socks proxy, httpp connect to %s:%d', dstip, dstport);
// connection options
var coptions = {
port: dstport,
host: dstip,
// set user-specific feature,like maxim bandwidth,etc
localAddress: {
addr: nmcln.ipaddr,
port: nmcln.port,
opt: {
mbw: options.mbw || null
}
}
};
var srvSocket = UDT.connect(coptions, function() {
if (Debug) console.log('socks proxy, httpp got connected');
// send socks response
proxy_ready();
srvSocket.pipe(socket);
socket.pipe(srvSocket);
});
srvSocket.on('error', function(e) {
console.log("socks proxy, httpp socket error: " + e);
socket.end();
});
} else {
// 6.2
// setup tunnel to target by make CONNECT request
var roptions = {
port: dstport,
hostname: dstip,
method: 'CONNECT',
path: urle,
agent: false,
// set user-specific feature,like maxim bandwidth,etc
localAddress: {
addr: nmcln.ipaddr,
port: nmcln.port,
opt: {
mbw: options.mbw || null
}
}
};
// set SSL related options
if (nmcln.secmode && nmcln.secerts) {
Object.keys(nmcln.secerts).forEach(function(k){
roptions[k] = nmcln.secerts[k];
});
}
var rreq = httpps.request(roptions);
rreq.end();
if (Debug) console.log('socks proxy, connect to %s:%d', dstip, dstport);
rreq.on('connect', function(rres, rsocket, rhead) {
if (Debug) console.log('socks proxy, got connected');
// send socks response
proxy_ready();
rsocket.pipe(socket);
socket.pipe(rsocket);
rsocket.on('error', function(e) {
console.log("socks proxy, socket error: " + e);
socket.end();
});
});
rreq.on('error', function(e) {
console.log("socks proxy, CONNECT request error: " + e);
socket.end();
});
}
}
});
} else {
// over TURN
// 5.
// traverse TURN session to peer
nmcln.trvsTURN(vurle, function(err, turn){
if (err || !turn) {
// TURN not availabe
resErr('TURN not available, please check TURN service setup');
console.error('TURN not available:'+urle);
// clear export cache
if (self.exportCache[vurle]) {
self.exportCache[vurle] = null;
}
} else {
// 6.
// setup tunnel to target by make CONNECT request
var roptions = {
port: routing.turn.proxyport,
hostname: routing.turn.ipaddr,
method: 'CONNECT',
path: urle,
agent: false
};
// set turn-forward-to header: destination name-client's full vURL string
roptions.headers = {};
roptions.headers['turn-forward-to'] = vurle;
// set SSL related options
/*if (nmcln.secmode && nmcln.secerts) {
Object.keys(nmcln.secerts).forEach(function(k){
roptions[k] = nmcln.secerts[k];
});
}*/
var rreq = httpps.request(roptions);
rreq.end();
if (Debug) console.log('socks proxy over TURN, connect to %s:%d', routing.turn.ipaddr, routing.turn.proxyport);
rreq.on('connect', function(rres, rsocket, rhead) {
if (Debug) console.log('socks proxy over TURN, got connected');
// send socks response
proxy_ready();
rsocket.pipe(socket);
socket.pipe(rsocket);
rsocket.on('error', function(e) {
console.log("socks proxy over TURN, socket error: " + e);
socket.end();
});
});
rreq.on('error', function(e) {
console.log("socks proxy over TURN, CONNECT request error: " + e);
socket.end();
});
}
});
}
}
});
}
});
}
// 8.
// pass forward proxy App
var papps = {importApp: {httpApp: {tunnel: importHttpTunnel, proxy: importHttpProxy}, socksApp: importSocksProxy},
exportApp: {httpApp: {tunnel: exportHttpTunnel, proxy: exportHttpProxy}}};
if (fn) fn(null, papps);
self.emit('ready', papps);
});
// 1.2
// check error
nmcln.on('error', function(err){
// 1.2.1
// clear export service query timer
if (self.qsInterval) {
clearInterval(self.qsInterval);
self.qsInterval = null;
}
console.log('name-client create failed:'+JSON.stringify(err));
if (fn) fn(err);
self.emit('error', err);
});
};
util.inherits(Proxy, eventEmitter);
// Choose an export service vURL
// TBD... geoip based algorithm for host/url
Proxy.prototype.findExport = function(host, url){
var self = this;
var rndm = Math.ceil(Math.random() * 1000000);
var vkey = [];
var isCN = filter.isCN(host && (host.split(':'))[0], url);
// screen valid export
Object.keys(self.exportCache).forEach(function(k){
if (self.exportCache[k])
// filter CN site
if (isCN) {
if (self.exportCache[k].geoip &&
self.exportCache[k].geoip.country === 'CN')
vkey.push(k);
} else {
// TBD... filter on rest
if (self.exportCache[k].geoip &&
self.exportCache[k].geoip.country != 'CN')
vkey.push(k);
}
});
if (Debug) console.log('vkey: '+JSON.stringify(vkey));
// choose one
if (vkey && vkey.length) {
///console.log(rndm % vkey.length);
return self.exportCache[vkey[rndm % vkey.length]].vurl;
} else {
return null;
}
};
// Query Live export service and cache it
Proxy.prototype.queryExport = function(fn){
var self = this;
// export service cache
self.exportCache = self.exportCache || {};
// 1.
// query live forward-proxy-export service
self.nmcln.queryService({cate: 'forward-proxy-export', live: true}, function(err, srv){
if (err || !srv) {
console.log('No available export services '+err);
if (fn) fn('No available export services');
} else {
if (Debug) console.log('available export services: '+JSON.stringify(srv));
// 2.
// cache it
Object.keys(srv).forEach(function(k){
if (srv[k] && !(k in self.exportCache)) {
self.exportCache[k] = srv[k];
}
});
if (fn) fn(null, srv);
}
});
return self;
};
// Turn on/off export service query timer
// - on: true or false
// - timeout: optional, default is 20s
Proxy.prototype.turnQuerytimer = function(on, timeout){
var self = this;
timeout = timeout || 20000;
if (on && !self.qsInterval) {
if (Debug) console.log('turn on export service query timemout '+timeout);
// query for the first time
self.queryExport();
// delayed query for the second time after 6s
setTimeout(function() {
self.queryExport();
self.qsInterval = setInterval(function(){
self.queryExport();
}, timeout);
}, 6000); // 6s delay
} else {
if (self.qsInterval) {
if (Debug) console.log('turn off export service query timer');
clearInterval(self.qsInterval);
self.qsInterval = null;
}
}
return self;
};
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。