UNPKG

catproxy

Version:

a node proxy or host change tools

340 lines (336 loc) 9.42 kB
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 };