catproxy
Version:
a node proxy or host change tools
340 lines (336 loc) • 9.42 kB
JavaScript
import url from 'url';
import Promise from 'promise';
import {Buffer} from 'buffer';
import log from './log';
import net from 'net';
import getServer from './serverManager';
import {STATUS, LIMIT_SIZE} from './config/defCfg';
import * as config from './config/config';
import http from 'http';
import https from 'https';
import changeHost from './changeHost';
import {getCert} from './cert/cert.js';
import responseService from './responseService';
let headerWsTest = /upgrade\s*:\s*websocket\s*\n/i;
import {pipeRequest} from './evt';
let rep = /^\/|\/$/g;
// 升级到 ws wss
let upgradeToWebSocket = function(req, cltSocket, head) {
var com = this;
// 不是upgrade websocket请求 直接放弃
if (req.headers.upgrade.toLowerCase() !== 'websocket') {
cltSocket.destroy();
return;
}
// 禁止超时
cltSocket.setTimeout(0);
// 禁止纳格(Nagle)算法。默认情况下TCP连接使用纳格算法,这些连接在发送数据之前对数据进行缓冲处理。 将noDelay设成true会在每次socket.write()被调用时立刻发送数据。noDelay默认为true。
cltSocket.setNoDelay(true);
// 启用长连接
cltSocket.setKeepAlive(true, 0);
let isSecure = req.connection.encrypted || req.connection.pai;
let url = req.url;
let hostname = req.headers.host.split(':');
let port = hostname[1] ? hostname[1] : isSecure ? 443 : 80;
hostname = hostname[0];
let options = {
port,
path: url,
method: req.method,
headers: req.headers
};
if(isSecure) {
let {privateKey: key, cert} = getCert(hostname);
options.key = key;
options.cert = cert;
options.rejectUnauthorized = false;
}
let {port: p, httpsPort: hp} = config.get();
let isServerPort = +port === +p;
if (isSecure) {
isServerPort = +hp === +port;
}
changeHost(hostname, isServerPort).then(ip => {
options.hostname = ip;
var proxyReq = (isSecure ? https : http)
.request(options, (proxyRes) => {
if (!proxyRes.upgrade) {
proxyRes.end && proxyRes.end();
}
});
proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
let result = {};
Object.defineProperties(result, {
host: {
value: req.headers.host,
enumerable: true
},
port : {
value: port,
enumerable: true
},
headers: req.headers,
protocol: {
value: isSecure ? "wss" : "ws",
enumerable: true
}
});
pipeRequest.call(com, result);
proxySocket.on('error', (err)=> log.error(err));
proxySocket.on('end', function () {
cltSocket.end();
});
proxySocket.setTimeout(0);
proxySocket.setNoDelay(true);
proxySocket.setKeepAlive(true, 0);
cltSocket.on('error', (err) => {
proxySocket.end();
log.error(err);
});
if (proxyHead && proxyHead.length) {
proxySocket.unshift(proxyHead);
}
let headers = Object.keys(proxyRes.headers)
.reduce(function (head, key) {
var value = proxyRes.headers[key];
if (!Array.isArray(value)) {
head.push(key + ': ' + value);
return head;
}
for (var i = 0; i < value.length; i++) {
head.push(key + ': ' + value[i]);
}
return head;
}, ['HTTP/1.1 101 Switching Protocols'])
.join('\r\n') + '\r\n\r\n';
// 写入头文件
cltSocket.write(headers);
proxySocket.pipe(cltSocket).pipe(proxySocket);
});
proxyReq.on('error', (err) => {
log.error(err);
cltSocket.end();
});
req.pipe(proxyReq);
}, (error) => {
log.error(error);
cltSocket.end();
});
};
// 请求到后的解析
export let requestHandler = function(req, res) {
var com = this;
let isSecure = req.connection.encrypted || req.connection.pai,
headers = req.headers,
method = req.method,
host = headers.host,
protocol = !!isSecure ? "https" : 'http',
fullUrl = /^http.*/.test(req.url) ? req.url : (protocol + '://' + host + req.url),
urlObject = url.parse(fullUrl),
port = urlObject.port || (protocol === "http" ? '80' : '443'),
pathStr = urlObject.path,
pathname = urlObject.pathname,
visitUrl = protocol + "://" + host + pathname;
log.verbose("request url: " + fullUrl);
// 请求信息
let reqInfo = {
headers,
host,
method,
protocol,
port,
path: pathStr
};
Object.defineProperties(reqInfo, {
req: {
writable: false,
value: req,
enumerable: true
},
originalFullUrl: {
writable: false,
value: fullUrl,
enumerable: true
},
originalUrl: {
writable: false,
value: visitUrl,
enumerable: true
},
startTime:{
writable: false,
value: new Date().getTime(),
enumerable: true
}
});
// 响应信息
let resInfo = {headers: {}};
Object.defineProperties(resInfo, {
res: {
writable: false,
value: res,
enumerable: true
},
originalFullUrl: {
writable: false,
value: fullUrl,
enumerable: true
},
originalUrl: {
writable: false,
value: visitUrl,
enumerable: true
}
});
// 调用相应模块
responseService.call(com, reqInfo, resInfo);
let reqBodyData = [];
let l = 0;
let end = () => {
req.emit('reqBodyDataReady', null, Buffer.concat(reqBodyData));
};
let data = (buffer)=> {
l = l + buffer.length;
reqBodyData.push(buffer);
// 超过长度了
if (l > LIMIT_SIZE) {
req.pause();
req.removeListener('data', data);
req.removeListener('end', end);
req.emit('reqBodyDataReady', {
message: '请求数据头过大,无法显示',
status: STATUS.LIMIT_ERROR
}, Buffer.concat(reqBodyData));
}
};
req
.on('data', data)
.on('end', end)
.on('error', err => {
log.error('error req', err);
});
};
/**
* connect转发请求处理
* @param req
* @param cltSocket
* @param head
*/
export let requestConnectHandler = function(req, cltSocket, head) {
var com = this;
return new Promise(function(resolve, reject) {
if (!head || head.length === 0) {
cltSocket.once('data', (chunk) => {
resolve(chunk);
});
} else {
resolve(head);
}
cltSocket.write('HTTP/' + req.httpVersion +' 200 Connection Established\r\n' +
'Proxy-agent: Node-CatProxy\r\n'
);
// if (req.headers['proxy-connection'] === 'keep-alive') {
// cltSocket.write('Proxy-Connection: keep-alive\r\n');
// cltSocket.write('Connection: keep-alive\r\n');
// }
cltSocket.write('\r\n');
})
.then(first => {
cltSocket.pause();
// log.debug("first data", first[0]);
let opt = config.get();
let reqUrl = `http://${req.url}`;
let srvUrl = url.parse(reqUrl);
let crackHttps;
if (typeof opt.breakHttps === 'boolean') {
crackHttps = opt.breakHttps;
} else if (typeof opt.breakHttps === 'object' && opt.breakHttps.length) {
crackHttps = opt.breakHttps.some((current) => new RegExp(current.replace(rep, "")).test(srvUrl.hostname));
}
// 如果当前状态是 破解状态 并且有排除列表
if (crackHttps && typeof opt.excludeHttps === 'object' && opt.excludeHttps) {
crackHttps = !opt.excludeHttps.some((current) => new RegExp(current.replace(rep, "")).test(srvUrl.hostname));
}
// * - an incoming connection using SSLv3/TLSv1 records should start with 0x16
// * - an incoming connection using SSLv2 records should start with the record size
// * and as the first record should not be very big we can expect 0x80 or 0x00 (the MSB is a flag)
// 如果需要捕获https的请求
// 访问地址直接是ip,跳过不代理
if (crackHttps && (first[0] == 0x16 || first[0] == 0x80 || first[0] == 0x00)) {
log.verbose(`crack https ${reqUrl}`);
getServer(opt.sni === 1 ? "" : srvUrl.hostname)
.then(({
port,
server
}) => {
// 与服务器绑定
server.catProxy = com.catProxy;
let srvSocket = net.connect(port, "localhost", () => {
srvSocket.pipe(cltSocket).pipe(srvSocket);
cltSocket.emit('data', first);
cltSocket.resume();
});
srvSocket.on('error', (err) => {
cltSocket.end();
log.error(`crack https-srv:${reqUrl}请求出现错误: ${err}${err.stack}`);
});
cltSocket.on('error', err => {
log.error(`crack https-clt:${reqUrl}请求出现错误: ${err}${err.stack}`);
srvSocket.end();
});
});
} else {
// 不认识的协议或者 不破解的https直接连接对应的服务器
log.verbose(`pipe request ${reqUrl}`);
let result = {};
Object.defineProperties(result, {
host: {
value: srvUrl.host,
enumerable: true
},
headers: req.headers,
port : {
value: srvUrl.port,
enumerable: true
},
protocol: {
value: headerWsTest.test(first.toString()) ? "ws" : "http",
enumerable: true
}
});
pipeRequest.call(com, result);
let srvSocket = net.connect(srvUrl.port, srvUrl.hostname, () => {
srvSocket.pipe(cltSocket).pipe(srvSocket);
cltSocket.emit('data', first);
cltSocket.resume();
});
cltSocket.on('error', err => {
log.error(`转发请求出现错误: ${err}`);
srvSocket.end();
});
srvSocket.on('error', (err) => {
cltSocket.end();
log.error(`转发请求出现错误: ${err}`);
});
}
})
.then(null, err => log.error(err));
};
/**
* upgrade ws转发请求处理
* @param req
* @param socket
*/
export let requestUpgradeHandler = function(req, cltSocket, head) {
// 不是get 取不到 upgrade就放弃
if (req.method === 'GET' && req.headers.upgrade) {
upgradeToWebSocket.call(this, req, cltSocket, head);
} else {
cltSocket.destroy();
}
};
export default {
requestHandler,
requestConnectHandler,
requestUpgradeHandler
};