pacproxy-js
Version:
A https proxy server which supports websocket and CDN. 支持websocket和CDN中转的加密代理服务器
700 lines (571 loc) • 23.1 kB
JavaScript
'use strict'
//sample configs, also the default configs, change it to your settings
const configsInCode = {
// set to false to save storage and avoid problems
logging : true,
// run as https server, or as http server only for testing purpose
// set to true if a https tunnel/reverse-proxy runs in front of this http service
https : false,
// set to true if a tunnel/reverse-proxy runs in front of this service
behindTunnel : false,
// proxy domain like 'your.proxy.domain'
domain : 'localhost',
// proxy listening port, if Port Forwarding, it's Internal Port.
port : 3128,
// proxy access port, only used to show in the pac url links
// if Port Forwarding, it's External Port, normally set to 443 if https is true
proxyport : 3128,
// proxy public ip, use when a vps has multiple public ips but only use one ip
proxyip : '0.0.0.0',
// you will share your pacurl as: https://your.proxy.domain/paclink , please change it to a long random '/xxxxxxxx'
// if behindtunnel is true paclink will not work as pacurl
paclink : '/0000000000000000',
// how long this IP can access proxy since last visit(relaunch browser to reauthorize access)
// if set to 0 above paclink will not work as pacurl
iphours : 2,
// a special paclink with username/password, format is:['paclink', 'username', 'password'], browser will prompt to input proxy username/password
// iphours do not apply to pacpass, need to input correct user/pass in 2 minutes, or you need to relaunch browser
pacpass : ['/1111111111111111', 'proxyuser', 'proxypass'],
// content of https://www.proxy.domain, style is: https://blog.ddns.com/homepage.htm. no local site for safety reason
website : '',
// avoid hacker DDOS attack cause proxy IP blocked by major CDN, format is:['account_name', 'username', 'password'], browser will prompt to input username/password, and save to browser ’account_name' account
website_auth : '', //['Protected','webuser','webpass'],
// web request handler for not proxy traffic, enable if website value is empty, by default return 403 error
onrequest : (req, res) => {response(res,403);},
// websocket handler for not proxy traffic, enable if websocket enabled
onconnection : (ws, req) => { ws.close(1011, "authentication failed");},
// ssl cert file, if empty it will be: {certdir}/{domain}/fullchain.pem
cert : '',
// ssl key file, if empty it will be: {certdir}/{domain}/privkey.pem
key : '',
// default ssl cert dir, if empty it will be env argument or current path
certdir : '',
// if false proxy will start the proxy server later
skipStart : false,
// need to "npm install ws", it will create inner proxy servers to handle websocket traffic
websocket : false,
// websocket wss service inner listning port
wssport : 0,
// websocket wss+tls service inner listning port
tlsport : 0,
// websocket wss+pac service inner listning port
pacport : 0
};
/**
* Dependencies
*/
const http = require('http');
const https = require('https');
const net = require('net');
const fs = require('fs');
const path = require('path');
/**
* Shared Variables
*/
const normalBrowser = { 'firefox' : ' Firefox', 'chrome' : ' Chrome', 'edge' : ' Edg', 'opera' : ' OPR' } ;
const pacDirect = 'function FindProxyForURL(url, host) { return "DIRECT";}';
const pacHeaders = {'Content-Type': 'text/plain', 'Cache-Control': 'no-cache, no-store'};
this.configs = false;
this.server = false;
this.httpAgents = new Map();
this.websiteAgent = newAgent();
this.websiteParsed = false;
this.proxyClients = new Map();
this.proxyUsers = new Map();
this.proxyAgents = new Map();
this.ipMilliSeconds = 0;
this.innerServer = false;
this.tlsServer = false;
this.proxyAuth = false;
this.WebSocket = false;
const pacProxy = this;
global.Buffer = global.Buffer || require('buffer').Buffer;
if (typeof btoa === 'undefined') {
global.btoa = function (str) {
return Buffer.from(str, 'binary').toString('base64');
};
}
if (typeof atob === 'undefined') {
global.atob = function (b64Encoded) {
return Buffer.from(b64Encoded, 'base64').toString('binary');
};
}
/**
* Export Module functions
*/
exports.proxy = proxy;
exports.merge = merge;
exports.run = run;
exports.startServer = startServer;
/**
* Init and start proxy
*/
function proxy(configs) {
if(!configs) configs = configsInCode;
else {
configsInCode.paclink = '';
configsInCode.pacpass = [];
merge(configs, configsInCode);
}
pacProxy.configs = configs;
pacProxy.ipMilliSeconds = pacProxy.configs.iphours * 3600 * 1000;
if(pacProxy.configs.website) pacProxy.websiteParsed = new URL(pacProxy.configs.website);
if(pacProxy.websiteParsed.host && isLocalHost(pacProxy.websiteParsed.host)) pacProxy.configs.website = false;
else if(pacProxy.websiteParsed.protocol && (pacProxy.websiteParsed.protocol=='https:')) pacProxy.websiteAgent = newAgent(true);
if(pacProxy.configs.paclink && !pacProxy.configs.paclink.startsWith('/')) pacProxy.configs.paclink = '/' + pacProxy.configs.paclink;
if(pacProxy.configs.pacpass && pacProxy.configs.pacpass[0] && !pacProxy.configs.pacpass[0].startsWith('/')) pacProxy.configs.pacpass[0] = '/' + pacProxy.configs.pacpass[0];
if(pacProxy.configs.pacpass && pacProxy.configs.pacpass.length==3) pacProxy.proxyAuth = generateBasicAuth(pacProxy.configs.pacpass[1],pacProxy.configs.pacpass[2]);
if(pacProxy.configs.website_auth && pacProxy.configs.website_auth.length==3) pacProxy.websiteAuth = generateBasicAuth(pacProxy.configs.website_auth[1],pacProxy.configs.website_auth[2]);
if(configs.skipStart) return;
return startServer();
}
function startServer() {
let server = createServer();
bindServer(server);
pacProxy.server = server;
if(pacProxy.configs.websocket && pacProxy.configs.paclink) initInnerServer();
return server;
}
function bindServer(server) {
if(pacProxy.configs.behindTunnel){
server.on('connect', handleConnectBehindTunnel);
server.on('request', handleRequestBehindTunnel);
} else {
server.on('connect', handleConnect);
server.on('request', handleRequest);
}
server.listen(pacProxy.configs.port, pacProxy.configs.proxyip, () => {
console.log('\r\npac proxy server listening on port %d,',server.address().port);
if(!pacProxy.configs.behindTunnel && pacProxy.configs.paclink) console.log('\r\nshare your pac url: \r\n%s', getShareLink('http'));
if(pacProxy.configs.pacpass.length!==3) return;
console.log(
'\r\nshare your pac url with username/password: \r\n%s %s / %s\r\n',
getShareLink('http', pacProxy.configs.pacpass[0]), pacProxy.configs.pacpass[1], pacProxy.configs.pacpass[2]
);
});
server.on("error", err=>console.log(err));
}
function initInnerServer() {
pacProxy.innerServer = http.createServer();
pacProxy.innerServer.on('connect', _handleConnect);
pacProxy.innerServer.on('request', _handleRequest);
pacProxy.innerServer.listen(pacProxy.configs.wssport, '127.0.0.1', () => {
pacProxy.configs.wssport = pacProxy.innerServer.address().port;
console.log('\r\npac proxy server listening on port %d,\r\nshare your wss url: \r\n%s\r\n',
pacProxy.configs.wssport, getShareLink('ws'));
pacProxy.WebSocket = require("ws");
var ws = new pacProxy.WebSocket.Server({ server: pacProxy.server });
ws.on("connection", handleWebsocket);
});
pacProxy.innerServer.on('error', gErrorHandler);
if(!pacProxy.configs.https) return;
pacProxy.tlsServer = createServer();
pacProxy.tlsServer.on('connect', _handleConnect);
pacProxy.tlsServer.on('request', _handleRequest);
pacProxy.tlsServer.listen(pacProxy.configs.tlsport, '127.0.0.1', ()=>{
pacProxy.configs.tlsport = pacProxy.tlsServer.address().port;
console.log('\r\npac proxy server listening on port %d,\r\nshare your wss+tls url: \r\n%s\r\n',
pacProxy.configs.tlsport, getShareLink('ws')+'/tls');
});
pacProxy.tlsServer.on('error', gErrorHandler);
pacProxy.pacServer = createServer();
pacProxy.pacServer.on('connect', handleConnectBehindTunnel);
pacProxy.pacServer.on('request', handleRequestBehindTunnel);
pacProxy.pacServer.listen(pacProxy.configs.pacport, '127.0.0.1', ()=>{
pacProxy.configs.pacport = pacProxy.pacServer.address().port;
console.log('\r\npac proxy server listening on port %d,\r\nshare your wss+pac url: \r\n%s\r\n',
pacProxy.configs.pacport, getShareLink('ws')+'/pac');
});
pacProxy.pacServer.on('error', gErrorHandler);
}
function run() {
var configs = getConfigs();
proxy(configs);
}
function getConfigs(){
if(!process.argv[2]){
if(process.env.PORT) configsInCode.port = process.env.PORT;
return false;
}
if(!isNaN(process.argv[2])){
configsInCode.port = process.argv[2];
return false;
}
let configPath = path.resolve(process.cwd(), process.argv[2]);
let configs = require(configPath);
if(!process.argv[3]) return configs;
configs.port = process.argv[3];
return configs;
}
function createServer() {
if(!pacProxy.configs.https) return http.createServer();
if(pacProxy.configs.behindTunnel && (!pacProxy.configs.cert) && (!pacProxy.configs.key) && (!pacProxy.configs.certdir) ) return http.createServer();
if(pacProxy.configs.cert && pacProxy.configs.key){
let cert1 = fs.readFileSync(pacProxy.configs.cert);
let key1 = fs.readFileSync(pacProxy.configs.key);
return https.createServer({key: key1, cert: cert1});
}
var certDir = pacProxy.configs.certdir || process.env.CERTDIR || process.cwd()
let domain = pacProxy.configs.domain;
var options = {
key: fs.readFileSync(`${certDir}/${domain}/privkey.pem`),
cert: fs.readFileSync(`${certDir}/${domain}/fullchain.pem`)
};
return https.createServer(options);
}
/**
* shared functions
*/
function gErrorHandler(e) {
log('General Error %s ', e.message);
}
function generateBasicAuth(user, password) {
var token = user + ":" + password;
var hash = btoa(token);
return "Basic " + hash;
}
function newAgent(ssl = false) {
if(ssl) return new https.Agent({keepAlive: true, timeout: 300000, maxCachedSessions: 200 });
else return new http.Agent({ keepAlive: true, timeout: 300000});
}
function merge(vmain, vdefault){
Object.entries(vdefault).forEach((value, key) => {
if(!(value[0] in vmain)) vmain[value[0]] = value[1];
} ) ;
}
function getShareLink(protocal, vlink) {
let linkDomain = protocal + (pacProxy.configs.https? 's://' : '://') + pacProxy.configs.domain;
let linkHost = ':' + pacProxy.configs.proxyport;
if(pacProxy.configs.https && (pacProxy.configs.proxyport == 443)) linkHost = '';
if(!pacProxy.configs.https && (pacProxy.configs.proxyport == 80)) linkHost = '';
if(!vlink) vlink = pacProxy.configs.paclink;
return linkDomain + linkHost + vlink;
}
function filterHeader(reqHeaders){
let resHeaders = reqHeaders;
if(!reqHeaders) return resHeaders;
if ('connection' in resHeaders) delete resHeaders['connection'];
if ('keep-alive' in resHeaders) delete resHeaders['keep-alive'];
if ('upgrade' in resHeaders) delete resHeaders['upgrade'];
return resHeaders;
}
function log(...args) {
if (pacProxy.configs && pacProxy.configs.logging) console.log(...args);
}
function pacContent(req, vbrowser) {
let userAgent = req.headers['user-agent'];
log('%s PAC %s ', vbrowser, userAgent);
if(!req.headers["host"]){ return pacDirect;}
if(vbrowser){
let mbrowser = vbrowser.trim().toLowerCase();
if(mbrowser in normalBrowser){
if(!userAgent) return pacDirect;
else if(!userAgent.includes(normalBrowser[mbrowser])) return pacDirect;
else if(mbrowser=='chrome'){
if(userAgent.includes(normalBrowser['edge'])) return pacDirect;
if(userAgent.includes(normalBrowser['opera'])) return pacDirect;
}
} else if(!userAgent.includes(vbrowser)){
return pacDirect;
}
}
let [vdomain, vport] = req.headers["host"].split(':');
if(!vport){ vport = pacProxy.configs.https? 443 : 80}
let proxyType = pacProxy.configs.https ? 'HTTPS' : 'PROXY' ;
let pacjs = `function FindProxyForURL(url, host) { return "${proxyType} ${vdomain}:${vport}";}`;
return pacjs;
}
function isLocalHost(host) {
if(!host) return true;
const domain = (host.split(':')[0]).trim().toLowerCase();
if(domain.includes('localhost') || domain.includes('.local')) return true;
return isLocalIP(domain);
}
function isLocalIP(address) {
if(!address) return true;
address = address.toLowerCase();
if(address.startsWith('::ffff:')) address = address.slice(7);
if(address.startsWith('::') || address.startsWith('0')) return true;
if(address.startsWith('fc') || address.startsWith('fd') || address.startsWith('fe')) return true;
if(address.startsWith('192.168.') || address.startsWith('10.') || address.startsWith('127.') || address.startsWith('169.254.') || address.startsWith('172.16')) return true;
return false;
}
function authenticateIP(req) {
if(basicAuthentication(req)) return true;
const checkIP = req.socket.remoteAddress;
if(pacProxy.proxyUsers.has(checkIP)){
const [pacPassTime, userAgent] = pacProxy.proxyUsers.get(checkIP);
if(Date.now()>pacPassTime) pacProxy.proxyUsers.delete(checkIP);
else if(req.headers['user-agent']==userAgent) return 407
}
if(pacProxy.configs.iphours==0) return false;
if(!pacProxy.proxyClients.has(checkIP)) return false;
if (pacProxy.proxyClients.get(checkIP) >= Date.now()){
pacProxy.proxyClients.set(checkIP, Date.now() + pacProxy.ipMilliSeconds );
return true;
} else {
pacProxy.proxyClients.delete(checkIP);
return false;
}
}
function authenticateNoIP(req) {
if(basicAuthentication(req)) return true;
const userAgent = req.headers['user-agent'];
if(pacProxy.proxyAgents.has(userAgent)){
const pacPassTime = pacProxy.proxyAgents.get(userAgent);
if(Date.now()>pacPassTime) pacProxy.proxyAgents.delete(userAgent);
else return 407;
}
return false;
}
function basicAuthentication(request) {
if(pacProxy.configs.pacpass.length!==3) return false;
if(!pacProxy.proxyAuth) return false;
const Authorization = request.headers['proxy-authorization'];
if(!Authorization) return false;
if (pacProxy.proxyAuth==Authorization) return true;
return false;
}
function websiteAuthentication(request) {
if(!pacProxy.configs.website_auth) return true;
if(pacProxy.configs.website_auth.length!==3) return false;
if(!pacProxy.websiteAuth) return false;
const Authorization = request.headers['authorization'];
if(!Authorization) return false;
if (pacProxy.websiteAuth==Authorization) return true;
return false;
}
function response(res, httpCode, headers, content) {
res.on('error', gErrorHandler);
if(headers) res.writeHead(httpCode, headers);
else res.writeHead(httpCode);
if(content) res.write(content);
res.end();
}
function socketResponse(socket, content, cb) {
if(socket.destroyed) return;
if(!cb) cb = () => socket.end();
socket.write(content+ '\r\n', 'UTF-8', cb);
}
function requestRemote(parsed, req, res) {
const visitorIP = req.socket.remoteAddress;
log('%s Fetch %s ', visitorIP, parsed.toString());
let agent = http;
if(parsed.protocol == 'https:') agent = https;
if(pacProxy.configs.proxyip != '0.0.0.0'){
parsed.localAddress = pacProxy.configs.proxyip;
}
var gotResponse = false;
var proxyReq = agent.request(parsed, function(proxyRes) {
if(isLocalIP(proxyRes.socket.remoteAddress)) return response(res,403);
gotResponse = true;
let headers = filterHeader(proxyRes.headers);
let statusCode = proxyRes.statusCode ? proxyRes.statusCode : 200;
res.writeHead(statusCode, headers);
proxyRes.pipe(res);
});
proxyReq.on('error', (err) => {
log('%s REQUEST %s ', visitorIP, err);
if (gotResponse) {}
else if ('ENOTFOUND' == err.code) response(res,400);
else response(res,500);
gotResponse = true;
req.socket.end();
});
const endRequest = ()=>{
proxyReq.end();
res.removeListener('finish', endRequest);
req.removeListener('end', endRequest);
}
res.on('finish', ()=>endRequest());
req.on('end', ()=>endRequest());
if(!req.writableEnded) req.pipe(proxyReq);
else endRequest();
}
/**
* handle website requests
*/
function handleWebsite(req, res, tunnelRequest=false) {
const visitorIP = req.socket.remoteAddress;
log('%s %s %s ', visitorIP, req.headers.host, req.url);
if ((!tunnelRequest) && (pacProxy.configs.iphours>0) && pacProxy.configs.paclink && req.url.startsWith(pacProxy.configs.paclink)) {
const vpac = pacContent(req, req.url.slice(pacProxy.configs.paclink.length+1));
if(vpac==pacDirect) return response(res,200,pacHeaders,vpac);
pacProxy.proxyClients.set(visitorIP,Date.now()+pacProxy.ipMilliSeconds)
return response(res,200,pacHeaders,vpac);
}
if ((pacProxy.configs.pacpass.length==3) && req.url.startsWith(pacProxy.configs.pacpass[0])) {
const vpac = pacContent(req, req.url.slice(pacProxy.configs.pacpass[0].length+1));
if(vpac==pacDirect) return response(res,200,pacHeaders,vpac);
if(!tunnelRequest) pacProxy.proxyUsers.set(visitorIP,[Date.now()+120000, req.headers['user-agent']]);
else if(req.headers['user-agent']) pacProxy.proxyAgents.set(req.headers['user-agent'], Date.now()+120000);
return response(res,200,pacHeaders,vpac);
}
if(!websiteAuthentication(req)) return response(res,401,{'WWW-Authenticate': 'Basic realm="' + pacProxy.configs.website_auth[0] + '"'});
if(!pacProxy.configs.website) return pacProxy.configs.onrequest(req, res);
try{
var parsed = new URL('https://'+pacProxy.configs.domain + req.url);
} catch (e) {
return response(res, 403);
}
const headers = filterHeader(req.headers);
if (parsed.pathname == '/') parsed.pathname = pacProxy.websiteParsed.pathname;
parsed.protocol = pacProxy.websiteParsed.protocol;
parsed.host = pacProxy.websiteParsed.host;
headers.host = pacProxy.websiteParsed.host;
parsed.port = pacProxy.websiteParsed.port;
parsed.method = req.method;
parsed.headers = headers;
parsed.agent = pacProxy.websiteAgent;
try {
requestRemote(parsed, req, res);
} catch (e) {
log('%s Error %s ', visitorIP, e.message);
}
}
/**
* handle proxy http requests
*/
function handleRequest(req, res) {
if(req.url.startsWith('/')) return handleWebsite(req, res);
const auth = authenticateIP(req);
if(!auth) return response(res, 403);
if(auth==407) return response(res,407,{'Proxy-Authenticate': 'Basic realm="proxy"'});
_handleRequest(req, res);
}
function handleRequestBehindTunnel(req, res) {
if(req.url.startsWith('/')) return handleWebsite(req, res, true);
const auth = authenticateNoIP(req);
if(!auth) return response(res, 403);
if(auth==407) return response(res,407,{'Proxy-Authenticate': 'Basic realm="proxy"'});
_handleRequest(req, res);
}
function _handleRequest(req, res) {
const visitorIP = req.socket.remoteAddress;
log('%s %s %s ', visitorIP, req.method, req.url);
if((visitorIP=='127.0.0.1') && req.headers.host.startsWith('localhost') && req.url.startsWith('/pac')) {
const vpac = pacContent(req, req.url.slice(5));
if(vpac==pacDirect) return response(res,200,pacHeaders,vpac);
const pacjs = `function FindProxyForURL(url, host) { return "PROXY ${req.headers.host}";}`;
return response(res,200,pacHeaders,pacjs);
}
try {
var parsed = new URL(req.url);
} catch (e) {
return response(res, 403);
}
if(isLocalHost(parsed.host)) return response(res, 403);
const headers = filterHeader(req.headers);
parsed.method = req.method;
parsed.headers = headers;
// use keep-alive http agents
const host = parsed.host;
let agent = pacProxy.httpAgents.get(host);
if (!agent) {
agent = newAgent();
pacProxy.httpAgents.set(host,agent);
}
parsed.agent = agent;
if (! parsed.port) {
parsed.port = 80;
}
try{
requestRemote(parsed, req, res);
} catch (e) {
log('%s Error %s ', visitorIP, e.message);
}
};
/**
* handle proxy CONNECT requests.
*/
function handleConnect(req, socket) {
socket.on('error', gErrorHandler);
const auth = authenticateIP(req);
if(!auth) return socketResponse(socket, 'HTTP/1.1 403 Forbidden\r\n');
if(auth == 407 ) return socketResponse(socket, 'HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm="proxy"\r\n');
_handleConnect(req, socket);
}
function handleConnectBehindTunnel(req, socket) {
socket.on('error', gErrorHandler);
const auth = authenticateNoIP(req);
if(!auth) return socketResponse(socket, 'HTTP/1.1 403 Forbidden\r\n');
if(auth == 407 ) return socketResponse(socket, 'HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm="proxy"\r\n');
_handleConnect(req, socket);
}
function _handleConnect(req, socket) {
if(isLocalHost(req.url)) return socketResponse(socket, 'HTTP/1.1 403 Forbidden\r\n');
const visitorIP = req.socket.remoteAddress;
log('%s %s %s ', visitorIP, req.method, req.url);
var gotResponse = false;
const ontunnelerror = (err) => {
if (gotResponse) return socket.end();
if ('ENOTFOUND' == err.code) return socketResponse(socket, 'HTTP/1.1 404 Not Found\r\n');
else return socketResponse(socket, 'HTTP/1.1 500 Internal Server Error\r\n');
}
const vhost = req.url.split(':')[0];
const vport = req.url.split(':')[1] || 443;
const ropts = {
host: vhost,
port: vport,
keepAlive: true
};
if(pacProxy.configs.proxyip != '0.0.0.0'){
ropts.localAddress = pacProxy.configs.proxyip;
}
const transfer = (error) => {
try{
gotResponse = true;
if (error) {
tunnel.destroy();
socket.end();
return;
}
tunnel.pipe(socket);
socket.pipe(tunnel);
} catch (e) {
log('%s Error %s ', visitorIP, e.message);
}
};
var tunnel = net.createConnection(ropts,
socketResponse(socket, 'HTTP/1.1 200 Connection established\r\n', transfer)
);
tunnel.on('lookup',(err, addresss) => {
if(isLocalIP(addresss)){
log('%s Error %s ', visitorIP, 'visit localIP');
gotResponse = true;
tunnel.destroy();
socket.end();
}
});
tunnel.on('error', ontunnelerror);
tunnel.on('end', () => socket.end());
socket.on('end', () => tunnel.destroy())
tunnel.setNoDelay(true);
}
/**
* handle proxy websocket requests.
*/
function handleWebsocket(ws, req) {
if(!pacProxy.configs.paclink) return pacProxy.configs.onconnection(ws,req);
if(!req.url.startsWith( pacProxy.configs.paclink)) return pacProxy.configs.onconnection(ws,req);
const visitorIP = req.socket.remoteAddress;
const suburl = req.url.slice(pacProxy.configs.paclink.length);
log('%s %s %s ', visitorIP, 'WSS', suburl);
if(!suburl) var tolocal = { host: '127.0.0.1', port: pacProxy.configs.wssport, keepAlive: true};
else if(suburl.toLowerCase() == '/tls') var tolocal = { host: '127.0.0.1', port: pacProxy.configs.tlsport, keepAlive: true};
else if(suburl.toLowerCase() == '/pac') var tolocal = { host: '127.0.0.1', port: pacProxy.configs.pacport, keepAlive: true};
else return ws.close(1011, "authentication failed");
const duplex = pacProxy.WebSocket.createWebSocketStream(ws);
try{
const tunnel = net.createConnection(tolocal)
duplex.on('close', () => tunnel.destroy());
duplex.on('error', () => tunnel.destroy());
tunnel.on('end', () => {duplex.destroy(); ws.close(1000)});
tunnel.on('error', () => {duplex.destroy(); ws.close(1000)});
duplex.pipe(tunnel);
tunnel.pipe(duplex);
} catch (e) {
log('%s Error %s ', visitorIP, e.message);
}
}
process.on('uncaughtException', gErrorHandler)