UNPKG

vpn.email.server.gfw

Version:
348 lines (275 loc) 10.4 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. */ import * as Net from 'net' import * as Http from 'http' import * as Dns from 'dns' import * as fs from 'fs' import * as Async from 'async' import * as Stream from 'stream' import * as ShortId from 'shortid' import * as cluster from 'cluster' import * as HttpProxy from './proxy/httpProxy' import * as IptablesAdd from './util/iptablesAdd' import * as Compress from './proxy/compress' import * as StreamFun from './proxy/streamFunction' const MaxAllowedTimeOut = 1000 * 60 * 5 const saveLog = ( _log: string, fileName: string ) => { const log = new Date ().toString () + `: ${ _log }\n` fs.appendFile ( fileName, log, { encoding: 'utf8' }, err => { console.log ( log ) }) } const otherRespon = ( body: string| Buffer, _status: number ) => { 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` //+ `Transfer-Encoding: chunked\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: string, 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 ( private headString: string ) { super ()} public _transform ( chunk: Buffer, encode, cb ) { console.log ( this.headString ) console.log ( chunk.toString ('hex')) console.log ( this.headString ) return cb ( null, chunk ) } } class ssModeV1 { private logFileName = logDir + this.clientIp private serverNet: Net.Server = null private saveLog ( log: string ) { saveLog ( log, this.logFileName ) } private nslookupRequest ( hostName: string, socket: Net.Socket ) { return Async.waterfall ([ next => dnsLookup ( hostName, next ), ( data1, next ) => Compress.encrypt ( Buffer.from( JSON.stringify ( data1 ), 'utf8' ), this.password, next ) ], ( err, result: Buffer ) => { 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 ) }) }) } constructor ( private clientIp: string, private port: number, private password: string ) { 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: Buffer ) => { 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: VE_IPptpStream = 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 ) }) }) } private testConnect ( id: string, data: VE_IPptpStream, socket: Net.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 { private socket: Net.Socket = null constructor ( private clientSocket: Net.Socket, private encrypt: Compress.encryptStream ) { super ()} public _write ( chunk: Buffer, 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 ) }