UNPKG

vpn.email.server.gfw

Version:
287 lines (286 loc) 12.2 kB
/*! * Copyright 2017 Vpn.Email network security technology Canada Inc. All Rights Reserved. * * Vpn.Email network technolog Canada Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ "use strict"; const Net = require("net"); const Dns = require("dns"); const fs = require("fs"); const Async = require("async"); const Stream = require("stream"); const cluster = require("cluster"); const HttpProxy = require("./proxy/httpProxy"); const IptablesAdd = require("./util/iptablesAdd"); const Compress = require("./proxy/compress"); const StreamFun = require("./proxy/streamFunction"); const MaxAllowedTimeOut = 1000 * 60 * 5; const saveLog = (_log, fileName) => { const log = new Date().toString() + `: ${_log}\n`; fs.appendFile(fileName, log, { encoding: 'utf8' }, err => { console.log(log); }); }; const otherRespon = (body, _status) => { const Ranges = (_status === 200) ? 'Accept-Ranges: bytes\r\n' : ''; const Content = (_status === 200) ? `Content-Type: text/plain; charset=utf-8\r\n` : 'Content-Type: text/html\r\n'; const headers = `Server: nginx/1.6.2\r\n` + `Date: ${new Date().toUTCString()}\r\n` + Content + `Content-Length: ${body.length}\r\n` + `Connection: keep-alive\r\n` + `Vary: Accept-Encoding\r\n` + '\r\n'; const status = _status === 200 ? 'HTTP/1.1 200 OK\r\n' : 'HTTP/1.1 404 Not Found\r\n'; return status + headers + body; }; const return404 = () => { const kkk = '<html>\r\n<head><title>404 Not Found</title></head>\r\n<body bgcolor="white">\r\n<center><h1>404 Not Found</h1></center>\r\n<hr><center>nginx/1.6.2</center>\r\n</body>\r\n</html>\r\n'; return otherRespon(Buffer.from(kkk, 'utf8'), 404); }; const dnsLookup = (hostName, CallBack) => { return Dns.lookup(hostName, { all: true }, (err, data) => { if (err) return CallBack(err); const _buf = Buffer.from(JSON.stringify(data), 'utf8'); return CallBack(null, _buf); }); }; class listen extends Stream.Transform { constructor(headString) { super(); this.headString = headString; } _transform(chunk, encode, cb) { console.log(this.headString); console.log(chunk.toString('hex')); console.log(this.headString); return cb(null, chunk); } } class ssModeV1 { constructor(clientIp, port, password) { this.clientIp = clientIp; this.port = port; this.password = password; this.logFileName = logDir + this.clientIp; this.serverNet = null; IptablesAdd.appPort(port, (err, ret) => { if (err) { saveLog(`IptablesAdd.appPort ERROR: ${err.message}`, this.logFileName); return process.exit(1); } this.serverNet = Net.createServer(socket => { const _remoteAddress = socket.remoteAddress; const remoteAddress = _remoteAddress.split(':').length > 2 ? _remoteAddress.split(':')[3] : _remoteAddress; const id = `[${remoteAddress}]:[${socket.remotePort}]`; const isAllowIP = remoteAddress === this.clientIp; const streamFunBlock = new StreamFun.blockRequestData(isAllowIP, MaxAllowedTimeOut); const streamDecrypt = new Compress.decryptStream(this.password); const streamEncrypt = new Compress.encryptStream(this.password, 500, null, () => { const firstConnect = new FirstConnect(socket, streamEncrypt); firstConnect.on('error', err => { console.log(`firstConnect.on ERROR:`, err.message); return socket.end(return404()); }); socket.pipe(streamFunBlock).pipe(streamDecrypt).pipe(firstConnect); }); streamFunBlock.on('error', err => { console.log(`streamFunBlock.on ERROR:`, err.message); if (/404/.test(err.message)) return socket.end(return404()); return socket.end(); }); socket.on('end', () => { return this.serverNet.getConnections((err, count) => { console.log(id, 'socket.on END! connected = ', count); }); }); socket.on('unpipe', src => { return socket.end(); }); socket.on('error', err => { return saveLog('HTTP on ERROR:' + err.message, this.logFileName); }); }); this.serverNet.on('error', err => { return saveLog('SS mode Net on error:' + err.message, this.logFileName); }); this.serverNet.listen(port, null, 512, () => { const log = 'SS mode start up listening ' + `[${clientIp}:${port}]`; console.log(log); return saveLog(log, this.logFileName); }); }); IptablesAdd.appPort(port + 1, (err, ret) => { if (err) { saveLog(`IptablesAdd.appPort ERROR: ${err.message}`, this.logFileName); return process.exit(1); } const http1 = Net.createServer(socket => { const _remoteAddress = socket.remoteAddress; const remoteAddress = _remoteAddress.split(':').length > 2 ? _remoteAddress.split(':')[3] : _remoteAddress; const id = `[${remoteAddress}]:[${socket.remotePort}]`; socket.once('data', _buf => { const keepRead = (data) => { const header = new HttpProxy.httpProxy(data); if (header._parts.length < 2) { return socket.once('data', _Buf => { const _data = Buffer.concat([data, _Buf]); return keepRead(_data); }); } if (!header.isHttpRequest) { return socket.end(); } if (remoteAddress !== clientIp || !header.isGet) { return socket.end(return404()); } const url = header.Url.path.substr(1); if (!url || !url.length) { saveLog('HTTP.on data GET url null', this.logFileName); return socket.end(return404()); } const Data = Buffer.from(url, 'base64'); try { const request = JSON.parse(Data.toString('utf8')); return this.testConnect(id, request, socket); } catch (e) { return console.log('test data JSON.parse catch ERROR:', e.message); } }; return keepRead(_buf); }); socket.on('error', err => { return saveLog('SS test mode Net on error:' + err.message, this.logFileName); }); }); http1.on('error', err => { return saveLog('SS test mode Net on error:' + err.message, this.logFileName); }); http1.listen(port + 1, null, 512, () => { const log = 'SS test mode start up listening ' + `[${clientIp}:${port + 1}]`; console.log(log); return saveLog(log, this.logFileName); }); }); } saveLog(log) { saveLog(log, this.logFileName); } nslookupRequest(hostName, socket) { return Async.waterfall([ next => dnsLookup(hostName, next), (data1, next) => Compress.encrypt(Buffer.from(JSON.stringify(data1), 'utf8'), this.password, next) ], (err, result) => { if (err) { saveLog('nslookup error!' + err.message, this.logFileName); return socket.end(return404()); } const lll = result.toString('base64'); const time = new Date().getTime(); const _buf = otherRespon(lll, 200); return socket.end(_buf, () => { const _time = (new Date().getTime() - time) / 1000; return saveLog(`nslookup to client [${lll.length}] byte speed:[${lll.length / _time}] byte/sec`, this.logFileName); }); }); } testConnect(id, data, socket) { const conn = Net.createConnection(data.port, data.host, () => { conn.pipe(socket).pipe(conn); conn.write(Buffer.from(data.buffer, 'base64')); }); conn.on('end', () => { return console.log('test getWayRequest conn.on END'); }); return conn.on('error', err => { return console.log('test getWayRequest conn.on error', err.message); }); } } class FirstConnect extends Stream.Writable { constructor(clientSocket, encrypt) { super(); this.clientSocket = clientSocket; this.encrypt = encrypt; this.socket = null; } _write(chunk, encode, cb) { if (!this.socket) { const _data = chunk.toString('utf8'); try { const data = JSON.parse(_data); if (data.hostName && data.hostName.length) { return dnsLookup(data.hostName, (err, data) => { if (err) { return cb(err); } this.encrypt.pipe(this.clientSocket); this.encrypt.end(data); }); } if (data.uuid) { return this.socket = Net.connect({ port: data.port, host: data.host }, () => { this.socket.on('error', err => { console.log('FirstConnect socket on error!', err.message); this.end(); }); this.socket.pipe(this.encrypt).pipe(this.clientSocket); this.socket.write(Buffer.from(data.buffer, 'base64')); return cb(); }); } return cb(new Error('unknow connect!')); } catch (e) { return cb(e); } } if (this.socket.writable) { this.socket.write(chunk); return cb(); } return cb(new Error('FirstConnect socket.writable=false')); } } const clientIp = process.argv[2]; const clientPort = process.argv[3]; const password = process.argv[4]; const logDir = '/var/log/vpn.email/'; const logSystem = logDir + 'syslog'; if (!clientIp || !clientPort || !password) { console.log(`Usage: node server clientIP clientPort password!`); process.exit(1); } fs.access(logDir, fs.constants.R_OK | fs.constants.W_OK, err => { if (err) { fs.mkdir(logDir, err1 => { if (err1) { console.log(`${new Date().toString()} vpn.email.server.gfw can't mkdir log path!`); process.exit(1); } }); } }); if (cluster.isMaster) { let worker = cluster.fork(); worker.on('exit', () => { worker = cluster.fork(); }); } else { new ssModeV1(clientIp, parseInt(clientPort), password); }