Ai
1 Star 0 Fork 1

AppNet.Link/node-forward-proxy

Create your Gitee Account
Explore and code with more than 13.5 million developers,Free private repositories !:)
Sign up
文件
Clone or Download
proxy.js 62.80 KB
Copy Edit Raw Blame History
tom zhou authored 2020-04-01 10:16 +08:00 . use appnet.link literal
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208
// 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;
};
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/AppNet5G/node-forward-proxy.git
git@gitee.com:AppNet5G/node-forward-proxy.git
AppNet5G
node-forward-proxy
node-forward-proxy
v3.3.x

Search