├── .gitignore ├── Cakefile ├── README.md ├── bin └── socket-pipe ├── build ├── hclient.js ├── hserver.js ├── stream │ ├── decode.js │ ├── encode.js │ └── proxy.js ├── tclient.js ├── tcp.js ├── tserver.js └── udp.js ├── index.js ├── package.json └── src ├── hclient.coffee ├── hserver.coffee ├── stream ├── decode.coffee ├── encode.coffee └── proxy.coffee ├── tclient.coffee ├── tcp.coffee ├── tserver.coffee └── udp.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | {print} = require 'util' 2 | {spawn} = require 'child_process' 3 | 4 | build = () -> 5 | os = require 'os' 6 | if os.platform() == 'win32' 7 | coffeeCmd = 'coffee.cmd' 8 | else 9 | coffeeCmd = 'coffee' 10 | coffee = spawn coffeeCmd, ['-c', '-o', 'build', 'src'] 11 | coffee.stderr.on 'data', (data) -> 12 | process.stderr.write data.toString() 13 | coffee.stdout.on 'data', (data) -> 14 | print data.toString() 15 | coffee.on 'exit', (code) -> 16 | if code != 0 17 | process.exit code 18 | 19 | task 'build', 'Build ./ from src/', -> 20 | build() 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Socket Pipe 2 | 3 | Socket Pipe can forward your socket from one address to anoter without any configs. It supports both tcp and udp, you can simplely make a software net-bridge. 4 | 5 | ## Install 6 | 7 | ``` 8 | npm install -g socket-pipe 9 | ``` 10 | 11 | ## Usage 12 | 13 | ### Tcp socket forwarding 14 | 15 | The following example shows how to map a remote address (ip=192.168.1.100 port=80) to a local address (ip=127.0.0.1 port=80) via tcp tunnel. 16 | 17 | ``` 18 | socket-pipe -l 127.0.0.1@80 -r 192.168.1.100@80 -t tcp 19 | ``` 20 | 21 | ### Udp socket forwarding 22 | 23 | The following example shows how to map a remote address (ip=8.8.8.8 port=53) to a local address (ip=127.0.0.1 port=53) via udp tunnel. 24 | 25 | ``` 26 | socket-pipe -l 127.0.0.1@53 -r 8.8.8.8@53 -t udp 27 | ``` 28 | 29 | ### Tcp reverse tunnel 30 | 31 | The following example shows how to map a server from LAN (ip=192.168.1.100 port=80) to internet (ip=123.123.123.123 port=80). 32 | 33 | #### Client side (LAN) 34 | 35 | ``` 36 | socket-pipe -l 192.168.1.100@80 -r 123.123.123.123@10080 -t tclient 37 | ``` 38 | 39 | #### Server side (internet) 40 | 41 | ``` 42 | socket-pipe -l 123.123.123.123@10080 -r 123.123.123.123@80 -t tserver 43 | ``` 44 | 45 | ### Http reverse tunnel 46 | 47 | The following example shows how to map multi http servers from LAN (ip=[192.168.1.100 - 192.168.1.102] port=80) to internet (ip=123.123.123.123 port=80). 48 | 49 | 50 | #### Client side (LAN) 51 | 52 | http1 53 | ``` 54 | socket-pipe -l 192.168.1.100@80 -r 123.123.123.123@10080 -t hclient -x git.dev.com -s git 55 | ``` 56 | 57 | http2 58 | ``` 59 | socket-pipe -l 192.168.1.101@80 -r 123.123.123.123@10080 -t hclient -x file.dev.com 60 | ``` 61 | 62 | http3 63 | ``` 64 | socket-pipe -l 192.168.1.102@80 -r 123.123.123.123@10080 -t hclient -s wiki 65 | ``` 66 | 67 | 68 | #### Server side (internet) 69 | 70 | ``` 71 | socket-pipe -l 123.123.123.123@10080 -r 123.123.123.123@80 -t hserver 72 | ``` 73 | 74 | There are two special params. 75 | 76 | 1. `-x` means socket-pipe will transform: 77 | 1. The `Host` value in http request header. 78 | 2. The host part of 'Location' value in http response header. 79 | 2. `-s` means specify a domain prefix. The server side will create a random prefix without specifying. 80 | 81 | Now you can visit different backend http server in a LAN from a portal on internet. 82 | 83 | For example if domain `*.test.com` is pointing to `123.123.123.123`, the visits to `http://git.test.com/` will be forwarded to `http://192.168.1.100/` with host `git.dev.com` because of the domain prefix `git`. 84 | 85 | -------------------------------------------------------------------------------- /bin/socket-pipe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../index'); 4 | -------------------------------------------------------------------------------- /build/hclient.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.2 2 | (function() { 3 | var Net, TClient, 4 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 5 | hasProp = {}.hasOwnProperty; 6 | 7 | Net = require('net'); 8 | 9 | TClient = require('./tclient'); 10 | 11 | module.exports = (function(superClass) { 12 | extend(_Class, superClass); 13 | 14 | function _Class(localAddress, remoteAddress, argv) { 15 | this.localAddress = localAddress; 16 | this.remoteAddress = remoteAddress; 17 | this.transfer = argv.x; 18 | this.specify = argv.s; 19 | this.createDaemonSocket(); 20 | this.hash = null; 21 | this.token = null; 22 | } 23 | 24 | _Class.prototype.createDaemonSocket = function() { 25 | var first, parts, ping, tmp; 26 | ping = Buffer.from([0]); 27 | if (this.transfer == null) { 28 | this.transfer = ''; 29 | } 30 | if (this.specify == null) { 31 | this.specify = ''; 32 | } 33 | parts = this.transfer + '|' + this.specify + (this.token != null ? '|' + this.token : ''); 34 | tmp = new Buffer(parts); 35 | first = new Buffer(1 + tmp.length); 36 | first.writeInt8(1, 0); 37 | tmp.copy(first, 1); 38 | this.daemonSocket = this.connectRemote((function(_this) { 39 | return function() { 40 | var connected; 41 | connected = false; 42 | _this.daemonSocket.ref(); 43 | _this.daemonSocket.on('data', function(data) { 44 | var hash, ref, url, uuid; 45 | if (!connected) { 46 | connected = true; 47 | url = data.toString('utf8'); 48 | console.info("url " + url); 49 | ref = url.split('|'), hash = ref[0], _this.token = ref[1]; 50 | return _this.hash = Buffer.from(hash); 51 | } else if (data.length === 4) { 52 | uuid = data.readInt32LE(0); 53 | console.info("request pipe " + uuid); 54 | return _this.createTunnel(uuid); 55 | } 56 | }); 57 | _this.daemonSocket.write(first); 58 | return setInterval(function() { 59 | return _this.daemonSocket.write(ping); 60 | }, 10000); 61 | }; 62 | })(this)); 63 | this.daemonSocket.on('error', console.error); 64 | return this.daemonSocket.on('close', (function(_this) { 65 | return function() { 66 | return setTimeout(function() { 67 | return _this.createDaemonSocket(); 68 | }, 1000); 69 | }; 70 | })(this)); 71 | }; 72 | 73 | _Class.prototype.createTunnel = function(uuid) { 74 | var ping, socket; 75 | ping = new Buffer(5 + this.hash.length); 76 | ping.writeInt8(2, 0); 77 | ping.writeInt32LE(uuid, 1); 78 | this.hash.copy(ping, 5); 79 | return socket = this.connectRemote((function(_this) { 80 | return function() { 81 | var local; 82 | console.info("connect remote " + uuid); 83 | return local = _this.connectLocal(function() { 84 | console.info("connect local " + uuid); 85 | socket.write(ping); 86 | socket.pipe(local).pipe(socket); 87 | return console.info("piped " + uuid); 88 | }); 89 | }; 90 | })(this)); 91 | }; 92 | 93 | return _Class; 94 | 95 | })(TClient); 96 | 97 | }).call(this); 98 | -------------------------------------------------------------------------------- /build/hserver.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.2 2 | (function() { 3 | var Event, Http, Net, ProxyStream, UUID, endSocket, pregQuote; 4 | 5 | Net = require('net'); 6 | 7 | Event = require('events'); 8 | 9 | Http = require('http'); 10 | 11 | UUID = require('node-uuid'); 12 | 13 | ProxyStream = require('./stream/proxy'); 14 | 15 | pregQuote = function(str) { 16 | return str.replace(/[-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 17 | }; 18 | 19 | endSocket = function(socket) { 20 | socket.resume(); 21 | return socket.end("HTTP/1.1 404 Not Found\r\nContent-Type: text/html;charset=UTF-8\r\n\r\nNotFound"); 22 | }; 23 | 24 | module.exports = (function() { 25 | function _Class(localAddress, remoteAddress) { 26 | this.localAddress = localAddress; 27 | this.remoteAddress = remoteAddress; 28 | this.id = 0; 29 | this.dataEvent = new Event; 30 | this.daemonSockets = {}; 31 | this.sockets = {}; 32 | this.pipes = {}; 33 | this.waits = {}; 34 | setInterval((function(_this) { 35 | return function() { 36 | var buff, hash, item, now, ref, results, time, uuid; 37 | now = Date.now(); 38 | ref = _this.waits; 39 | results = []; 40 | for (uuid in ref) { 41 | item = ref[uuid]; 42 | hash = item[0], buff = item[1], time = item[2]; 43 | if (now - time >= 1000) { 44 | item[2] = now; 45 | if ((_this.pipes[uuid] == null) && (_this.sockets[uuid] != null) && (_this.daemonSockets[hash] != null)) { 46 | _this.daemonSockets[hash][0].write(buff); 47 | results.push(console.info("retry pipe " + uuid)); 48 | } else { 49 | results.push(void 0); 50 | } 51 | } else { 52 | results.push(void 0); 53 | } 54 | } 55 | return results; 56 | }; 57 | })(this), 200); 58 | this.dataEvent.on('accept', (function(_this) { 59 | return function(uuid) { 60 | var input; 61 | if (_this.sockets[uuid] == null) { 62 | return; 63 | } 64 | input = new ProxyStream; 65 | _this.sockets[uuid].push(input); 66 | input.setCallback(function(reqHost, head) { 67 | var buff, hash, host, output, regex; 68 | console.info("request " + reqHost); 69 | hash = reqHost.split('.')[0]; 70 | if (_this.daemonSockets[hash] == null) { 71 | return endSocket(_this.sockets[uuid][0]); 72 | } 73 | host = _this.daemonSockets[hash][1] != null ? _this.daemonSockets[hash][1] : reqHost; 74 | buff = new Buffer(4); 75 | buff.writeInt32LE(uuid); 76 | console.info("request pipe " + uuid); 77 | _this.daemonSockets[hash][0].write(buff); 78 | _this.waits[uuid] = [hash, buff, Date.now()]; 79 | regex = new RegExp(pregQuote(reqHost), 'ig'); 80 | output = new ProxyStream; 81 | output.setFrom(host); 82 | output.setTo(reqHost); 83 | _this.sockets[uuid].push(output); 84 | _this.sockets[uuid][0].pause(); 85 | return head.replace(regex, host); 86 | }); 87 | _this.sockets[uuid][0].pipe(input); 88 | return _this.sockets[uuid][0].resume(); 89 | }; 90 | })(this)); 91 | this.dataEvent.on('pipe', (function(_this) { 92 | return function(uuid, hash) { 93 | delete _this.waits[uuid]; 94 | if (!_this.sockets[uuid]) { 95 | return; 96 | } 97 | if (!_this.daemonSockets[hash]) { 98 | return endSocket(_this.sockets[uuid][0]); 99 | } 100 | if (!_this.pipes[uuid]) { 101 | return endSocket(_this.sockets[uuid][0]); 102 | } 103 | _this.sockets[uuid][1].pipe(_this.pipes[uuid]).pipe(_this.sockets[uuid][2]).pipe(_this.sockets[uuid][0]); 104 | _this.sockets[uuid][1].release(); 105 | return _this.sockets[uuid][0].resume(); 106 | }; 107 | })(this)); 108 | this.createLocalServer(); 109 | this.createRemoteServer(); 110 | } 111 | 112 | _Class.prototype.accept = function(socket) { 113 | var uuid; 114 | console.info("accept " + socket.remoteAddress + ":" + socket.remotePort); 115 | uuid = this.id; 116 | this.id += 1; 117 | socket.pause(); 118 | this.sockets[uuid] = [socket]; 119 | socket.on('close', (function(_this) { 120 | return function() { 121 | console.info("close socket " + uuid); 122 | if (_this.sockets[uuid] != null) { 123 | delete _this.sockets[uuid]; 124 | } 125 | if (_this.waits[uuid] != null) { 126 | return delete _this.waits[uuid]; 127 | } 128 | }; 129 | })(this)); 130 | socket.on('error', console.error); 131 | return this.dataEvent.emit('accept', uuid); 132 | }; 133 | 134 | _Class.prototype.createRemoteServer = function() { 135 | this.remoteServer = Net.createServer((function(_this) { 136 | return function(socket) { 137 | return _this.accept(socket); 138 | }; 139 | })(this)); 140 | this.remoteServer.on('error', console.error); 141 | return this.remoteServer.listen(this.remoteAddress.port, this.remoteAddress.ip); 142 | }; 143 | 144 | _Class.prototype.createLocalServer = function() { 145 | this.localServer = Net.createServer((function(_this) { 146 | return function(socket) { 147 | var connected; 148 | connected = false; 149 | socket.on('error', console.error); 150 | return socket.on('data', function(data) { 151 | var hash, items, op, parts, token, transfer, uuid; 152 | if (!connected) { 153 | connected = true; 154 | op = data.readInt8(0); 155 | if (op === 1) { 156 | parts = (data.slice(1)).toString(); 157 | items = parts.split('|'); 158 | transfer = items[0], hash = items[1]; 159 | token = items[2] != null ? items[2] : null; 160 | if (hash.length === 0 || ((_this.daemonSockets[hash] != null) && token !== _this.daemonSockets[hash][2])) { 161 | hash = UUID.v1(); 162 | } 163 | if (transfer.length === 0) { 164 | transfer = null; 165 | } 166 | console.info("connected " + socket.remoteAddress + ":" + socket.remotePort + " = " + hash + " " + transfer); 167 | token = UUID.v1(); 168 | _this.daemonSockets[hash] = [socket, transfer, token]; 169 | socket.on('close', function() { 170 | if ((hash != null) && (_this.daemonSockets[hash] != null)) { 171 | return delete _this.daemonSockets[hash]; 172 | } 173 | }); 174 | return socket.write(new Buffer(hash + '|' + token)); 175 | } else if (op === 2) { 176 | uuid = data.readInt32LE(1); 177 | hash = (data.slice(5)).toString(); 178 | if (_this.pipes[uuid] != null) { 179 | return socket.end(); 180 | } 181 | _this.pipes[uuid] = socket; 182 | socket.on('close', function() { 183 | console.info("close pipe " + uuid); 184 | if (_this.pipes[uuid] != null) { 185 | return delete _this.pipes[uuid]; 186 | } 187 | }); 188 | console.info("created pipe " + uuid); 189 | return _this.dataEvent.emit('pipe', uuid, hash); 190 | } 191 | } 192 | }); 193 | }; 194 | })(this)); 195 | this.localServer.on('error', console.error); 196 | return this.localServer.listen(this.localAddress.port, this.localAddress.ip); 197 | }; 198 | 199 | return _Class; 200 | 201 | })(); 202 | 203 | }).call(this); 204 | -------------------------------------------------------------------------------- /build/stream/decode.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.2 2 | (function() { 3 | var Crypto, Transform, 4 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 5 | hasProp = {}.hasOwnProperty; 6 | 7 | Transform = (require('stream')).Transform; 8 | 9 | Crypto = require('crypto'); 10 | 11 | module.exports = (function(superClass) { 12 | extend(_Class, superClass); 13 | 14 | function _Class() { 15 | return _Class.__super__.constructor.apply(this, arguments); 16 | } 17 | 18 | _Class.prototype.initDecipher = function(cipher, password) { 19 | this.decipher = Crypto.createDecipher(cipher, password); 20 | this.received = false; 21 | this.packet = null; 22 | return this.last = null; 23 | }; 24 | 25 | _Class.prototype._transform = function(buff, enc, callback) { 26 | var e, end, length, newBuff; 27 | try { 28 | if (this.last != null) { 29 | newBuff = new Buffer(this.last.length + buff.length); 30 | this.last.copy(newBuff, 0); 31 | buff.copy(newBuff, this.last.length); 32 | buff = newBuff; 33 | this.last = null; 34 | } 35 | if (!this.received) { 36 | length = buff.readInt32LE(0); 37 | console.info("new packet " + length); 38 | this.packet = new Buffer(length); 39 | this.offset = 0; 40 | } 41 | end = Math.min(buff.length - 1, 3 + this.packet.length - this.offset); 42 | console.info("end with " + end); 43 | buff.copy(this.packet, this.offset, 4, end); 44 | this.received = !(end <= buff.length - 1); 45 | this.offset += end - 3; 46 | if (end < buff.length - 1) { 47 | this.last = buff.slice(end + 1); 48 | } 49 | if (!this.received) { 50 | return callback(null, this.decipher.update(this.packet)); 51 | } 52 | } catch (error) { 53 | e = error; 54 | return callback(e, null); 55 | } 56 | }; 57 | 58 | return _Class; 59 | 60 | })(Transform); 61 | 62 | }).call(this); 63 | -------------------------------------------------------------------------------- /build/stream/encode.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.2 2 | (function() { 3 | var Crypto, Transform, 4 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 5 | hasProp = {}.hasOwnProperty; 6 | 7 | Transform = (require('stream')).Transform; 8 | 9 | Crypto = require('crypto'); 10 | 11 | module.exports = (function(superClass) { 12 | extend(_Class, superClass); 13 | 14 | function _Class() { 15 | return _Class.__super__.constructor.apply(this, arguments); 16 | } 17 | 18 | _Class.prototype.initCipher = function(cipher, password) { 19 | return this.cipher = Crypto.createCipher(cipher, password); 20 | }; 21 | 22 | _Class.prototype.createPacket = function(buff) { 23 | var packet; 24 | packet = new Buffer(buff.length + 4); 25 | buff.copy(packet, 4, 0, buff.length - 1); 26 | packet.writeInt32LE(buff.length, 0); 27 | console.info("enconde " + buff.length); 28 | return packet; 29 | }; 30 | 31 | _Class.prototype._transform = function(buff, enc, callback) { 32 | var e, packet; 33 | try { 34 | packet = this.createPacket(this.cipher.update(buff)); 35 | return callback(null, packet); 36 | } catch (error) { 37 | e = error; 38 | return callback(e, null); 39 | } 40 | }; 41 | 42 | return _Class; 43 | 44 | })(Transform); 45 | 46 | }).call(this); 47 | -------------------------------------------------------------------------------- /build/stream/proxy.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.2 2 | (function() { 3 | var Transform, pregQuote, 4 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 5 | hasProp = {}.hasOwnProperty; 6 | 7 | Transform = (require('stream')).Transform; 8 | 9 | pregQuote = function(str) { 10 | return str.replace(/[-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 11 | }; 12 | 13 | module.exports = (function(superClass) { 14 | extend(_Class, superClass); 15 | 16 | function _Class() { 17 | this.filtered = false; 18 | this.piped = false; 19 | this.buffers = []; 20 | _Class.__super__.constructor.apply(this, arguments); 21 | } 22 | 23 | _Class.prototype.setCallback = function(cb) { 24 | this.cb = cb; 25 | }; 26 | 27 | _Class.prototype.setFrom = function(from) { 28 | return this.from = new RegExp(pregQuote(from), 'ig'); 29 | }; 30 | 31 | _Class.prototype.setTo = function(to) { 32 | this.to = to; 33 | }; 34 | 35 | _Class.prototype.pipe = function(stream) { 36 | this.stream = stream; 37 | this.piped = true; 38 | return _Class.__super__.pipe.apply(this, arguments); 39 | }; 40 | 41 | _Class.prototype.release = function() { 42 | var buffer, results; 43 | if (this.stream != null) { 44 | results = []; 45 | while (buffer = this.buffers.shift()) { 46 | results.push(this.stream.write(buffer)); 47 | } 48 | return results; 49 | } 50 | }; 51 | 52 | _Class.prototype.callback = function(err, buff) { 53 | if (this.piped) { 54 | return _Class.__super__.callback.apply(this, arguments); 55 | } else { 56 | return this.buffers.push(buff); 57 | } 58 | }; 59 | 60 | _Class.prototype._transform = function(buff, enc, callback) { 61 | var body, head, matches, pos, str; 62 | if (!this.filtered) { 63 | str = enc === 'buffer' ? buff.toString('binary') : buff; 64 | pos = str.indexOf("\r\n\r\n"); 65 | if (pos >= 0) { 66 | this.filtered = true; 67 | head = str.substring(0, pos); 68 | body = str.substring(pos); 69 | } else { 70 | head = str; 71 | body = ''; 72 | } 73 | if ((this.from != null) && (this.to != null)) { 74 | head = head.replace(this.from, this.to); 75 | } 76 | if (matches = head.match(/host:\s*([^\r]+)/i)) { 77 | if (this.cb != null) { 78 | head = this.cb(matches[1], head); 79 | } 80 | } 81 | callback(null, Buffer.from(head + body, 'binary')); 82 | } else { 83 | callback(null, buff); 84 | } 85 | if ((buff.indexOf(-1)) >= 0) { 86 | return this.clear(); 87 | } 88 | }; 89 | 90 | _Class.prototype.clear = function() { 91 | this.filtered = false; 92 | return this.buffers = []; 93 | }; 94 | 95 | return _Class; 96 | 97 | })(Transform); 98 | 99 | }).call(this); 100 | -------------------------------------------------------------------------------- /build/tclient.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.2 2 | (function() { 3 | var DecodeStream, EncodeStream, Net; 4 | 5 | Net = require('net'); 6 | 7 | EncodeStream = require('./stream/encode'); 8 | 9 | DecodeStream = require('./stream/decode'); 10 | 11 | module.exports = (function() { 12 | function _Class(localAddress, remoteAddress, argv) { 13 | this.localAddress = localAddress; 14 | this.remoteAddress = remoteAddress; 15 | this.argv = argv; 16 | this.createDaemonSocket(); 17 | } 18 | 19 | _Class.prototype.createDaemonSocket = function() { 20 | var ping; 21 | ping = Buffer.from([0]); 22 | this.daemonSocket = this.connectRemote((function(_this) { 23 | return function() { 24 | _this.daemonSocket.ref(); 25 | _this.daemonSocket.on('data', function(data) { 26 | var uuid; 27 | if (data.length === 4) { 28 | uuid = data.readInt32LE(0); 29 | console.info("request pipe " + uuid); 30 | return _this.createTunnel(uuid); 31 | } 32 | }); 33 | _this.daemonSocket.write(ping); 34 | return setInterval(function() { 35 | return _this.daemonSocket.write(ping); 36 | }, 10000); 37 | }; 38 | })(this)); 39 | return this.daemonSocket.on('close', (function(_this) { 40 | return function() { 41 | return setTimeout(function() { 42 | return _this.createDaemonSocket(); 43 | }, 1000); 44 | }; 45 | })(this)); 46 | }; 47 | 48 | _Class.prototype.connectRemote = function(cb) { 49 | var socket; 50 | socket = Net.connect(this.remoteAddress.port, this.remoteAddress.ip, cb); 51 | socket.on('error', console.error); 52 | return socket; 53 | }; 54 | 55 | _Class.prototype.connectLocal = function(cb) { 56 | var socket; 57 | socket = Net.connect(this.localAddress.port, this.localAddress.ip, cb); 58 | socket.on('error', console.error); 59 | return socket; 60 | }; 61 | 62 | _Class.prototype.createTunnel = function(uuid) { 63 | var ping, socket; 64 | ping = new Buffer(4); 65 | ping.writeInt32LE(uuid, 0); 66 | return socket = this.connectRemote((function(_this) { 67 | return function() { 68 | var local; 69 | console.info("connect remote " + uuid); 70 | return local = _this.connectLocal(function() { 71 | var decoder, encoder; 72 | console.info("connect local " + uuid); 73 | socket.write(ping); 74 | if (_this.argv.c != null) { 75 | encoder = new EncodeStream; 76 | decoder = new DecodeStream; 77 | encoder.initCipher(_this.argv.c, _this.argv.p); 78 | decoder.initDecipher(_this.argv.c, _this.argv.p); 79 | socket.pipe(decoder).pipe(local).pipe(encoder).pipe(socket); 80 | } else { 81 | socket.pipe(local).pipe(socket); 82 | } 83 | return console.info("piped " + uuid); 84 | }); 85 | }; 86 | })(this)); 87 | }; 88 | 89 | return _Class; 90 | 91 | })(); 92 | 93 | }).call(this); 94 | -------------------------------------------------------------------------------- /build/tcp.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.2 2 | (function() { 3 | var Net; 4 | 5 | Net = require('net'); 6 | 7 | module.exports = (function() { 8 | function _Class(localAddress, remoteAddress) { 9 | var server; 10 | server = Net.createServer(function(client) { 11 | var socket; 12 | socket = Net.connect(remoteAddress.port, remoteAddress.ip); 13 | client.pipe(socket).pipe(client); 14 | console.info("request " + client.remoteAddress + ":" + client.remotePort); 15 | return client.on('error', console.error); 16 | }); 17 | server.listen(localAddress.port, localAddress.ip); 18 | server.on('error', console.error); 19 | } 20 | 21 | return _Class; 22 | 23 | })(); 24 | 25 | }).call(this); 26 | -------------------------------------------------------------------------------- /build/tserver.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.2 2 | (function() { 3 | var DecodeStream, EncodeStream, Event, Net; 4 | 5 | Net = require('net'); 6 | 7 | Event = require('events'); 8 | 9 | EncodeStream = require('./stream/encode'); 10 | 11 | DecodeStream = require('./stream/decode'); 12 | 13 | module.exports = (function() { 14 | function _Class(localAddress, remoteAddress, argv) { 15 | this.localAddress = localAddress; 16 | this.remoteAddress = remoteAddress; 17 | this.argv = argv; 18 | this.id = 0; 19 | this.dataEvent = new Event; 20 | this.daemonSocket = null; 21 | this.sockets = {}; 22 | this.pipes = {}; 23 | this.dataEvent.on('pipe', (function(_this) { 24 | return function(uuid) { 25 | var buff, decoder, encoder; 26 | if (_this.sockets[uuid] == null) { 27 | return; 28 | } 29 | if (_this.pipes[uuid] == null) { 30 | buff = new Buffer(4); 31 | buff.writeInt32LE(uuid); 32 | if (_this.daemonSocket == null) { 33 | return; 34 | } 35 | console.info("request pipe " + uuid); 36 | return _this.daemonSocket.write(buff); 37 | } 38 | if (_this.argv.c != null) { 39 | encoder = new EncodeStream; 40 | decoder = new DecodeStream; 41 | encoder.initCipher(_this.argv.c, _this.argv.p); 42 | decoder.initDecipher(_this.argv.c, _this.argv.p); 43 | _this.sockets[uuid].pipe(encoder).pipe(_this.pipes[uuid]).pipe(decoder).pipe(_this.sockets[uuid]); 44 | } else { 45 | _this.sockets[uuid].pipe(_this.pipes[uuid]).pipe(_this.sockets[uuid]); 46 | } 47 | return _this.sockets[uuid].resume(); 48 | }; 49 | })(this)); 50 | this.createLocalServer(); 51 | this.createRemoteServer(); 52 | } 53 | 54 | _Class.prototype.accept = function(socket) { 55 | var uuid; 56 | console.info("accept " + socket.remoteAddress + ":" + socket.remotePort); 57 | socket.pause(); 58 | uuid = this.id; 59 | this.id += 1; 60 | this.sockets[uuid] = socket; 61 | socket.on('close', (function(_this) { 62 | return function() { 63 | console.info("close socket " + uuid); 64 | if (_this.sockets[uuid] != null) { 65 | return delete _this.sockets[uuid]; 66 | } 67 | }; 68 | })(this)); 69 | socket.on('error', console.error); 70 | return this.dataEvent.emit('pipe', uuid); 71 | }; 72 | 73 | _Class.prototype.createRemoteServer = function() { 74 | this.remoteServer = Net.createServer((function(_this) { 75 | return function(socket) { 76 | return _this.accept(socket); 77 | }; 78 | })(this)); 79 | this.remoteServer.on('error', console.error); 80 | return this.remoteServer.listen(this.remoteAddress.port, this.remoteAddress.ip); 81 | }; 82 | 83 | _Class.prototype.createLocalServer = function() { 84 | this.localServer = Net.createServer((function(_this) { 85 | return function(socket) { 86 | var connected; 87 | connected = false; 88 | socket.on('error', console.error); 89 | return socket.on('data', function(data) { 90 | var uuid; 91 | if (!connected) { 92 | connected = true; 93 | if (data.length === 1) { 94 | console.info("connected " + socket.remoteAddress + ":" + socket.remotePort); 95 | return _this.daemonSocket = socket; 96 | } else if (data.length === 4) { 97 | uuid = data.readInt32LE(0); 98 | _this.pipes[uuid] = socket; 99 | socket.on('close', function() { 100 | console.info("close pipe " + uuid); 101 | if (_this.pipes[uuid] != null) { 102 | return delete _this.pipes[uuid]; 103 | } 104 | }); 105 | console.info("created pipe " + uuid); 106 | return _this.dataEvent.emit('pipe', uuid); 107 | } 108 | } 109 | }); 110 | }; 111 | })(this)); 112 | this.localServer.on('error', console.error); 113 | return this.localServer.listen(this.localAddress.port, this.localAddress.ip); 114 | }; 115 | 116 | return _Class; 117 | 118 | })(); 119 | 120 | }).call(this); 121 | -------------------------------------------------------------------------------- /build/udp.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.12.2 2 | (function() { 3 | var Udp; 4 | 5 | Udp = require('dgram'); 6 | 7 | module.exports = (function() { 8 | function _Class(localAddress, remoteAddress) { 9 | this.localAddress = localAddress; 10 | this.remoteAddress = remoteAddress; 11 | this.connectionPool = {}; 12 | setInterval((function(_this) { 13 | return function() { 14 | var connect, index, key, now, ref, results; 15 | now = Date.now(); 16 | index = 0; 17 | ref = _this.connectionPool; 18 | results = []; 19 | for (key in ref) { 20 | connect = ref[key]; 21 | if (now - connect.time < 5000) { 22 | connect.socket.close(); 23 | delete _this.connectionPool[key]; 24 | results.push(console.log("close " + key)); 25 | } else { 26 | results.push(void 0); 27 | } 28 | } 29 | return results; 30 | }; 31 | })(this), 10000); 32 | this.createServer(); 33 | } 34 | 35 | _Class.prototype.createServer = function() { 36 | var receiver; 37 | receiver = Udp.createSocket('udp' + this.localAddress.type); 38 | receiver.bind(this.localAddress.port, this.localAddress.ip); 39 | receiver.ref(); 40 | receiver.on('error', console.error); 41 | return receiver.on('message', (function(_this) { 42 | return function(data, info) { 43 | var client, key, sender; 44 | key = _this.requestUdpSocket(info); 45 | sender = _this.connectionPool[key].socket; 46 | client = info; 47 | sender.on('message', function(data, info) { 48 | console.log("response " + info.address + ":" + info.port); 49 | if (typeof _this.connectionPool[key] !== 'undefined') { 50 | _this.connectionPool[key].time = Date.now(); 51 | } 52 | return receiver.send(data, 0, data.length, client.port, client.address); 53 | }); 54 | console.log("request " + client.address + ":" + client.port); 55 | return sender.send(data, 0, data.length, _this.requestUdpPort(_this.remoteAddress.port), _this.remoteAddress.ip); 56 | }; 57 | })(this)); 58 | }; 59 | 60 | _Class.prototype.requestUdpSocket = function(info) { 61 | var key, now, socket; 62 | now = Date.now(); 63 | key = info.address + ':' + info.port; 64 | if (typeof this.connectionPool[key] !== 'undefined') { 65 | this.connectionPool[key].time = now; 66 | return key; 67 | } 68 | socket = Udp.createSocket('udp' + this.remoteAddress.type); 69 | socket.bind(); 70 | socket.on('error', function(err) { 71 | console.error(err); 72 | socket.close(); 73 | return delete this.connectionPool[key]; 74 | }); 75 | this.connectionPool[key] = { 76 | time: now, 77 | socket: socket 78 | }; 79 | return key; 80 | }; 81 | 82 | _Class.prototype.requestUdpPort = function(port) { 83 | if (port instanceof Array) { 84 | return port[0] + Math.floor(Math.random() * (port[1] - port[0] + 1)); 85 | } else { 86 | return port; 87 | } 88 | }; 89 | 90 | return _Class; 91 | 92 | })(); 93 | 94 | }).call(this); 95 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | var Opt = require('optimist'), 3 | Net = require('net'), 4 | Udp = require('dgram'), 5 | Event = require('events'), 6 | Crypto = require('crypto'); 7 | 8 | var argv = Opt 9 | .usage('Usage: $0 [ -l 80 ] [ -r 10.0.10.11@80 ] [ -t udp ]') 10 | .demand(['l', 'r']) 11 | .boolean('h') 12 | .alias('t', 'type') 13 | .alias('h', 'help') 14 | .alias('l', 'local') 15 | .alias('r', 'remote') 16 | .alias('c', 'crypto') 17 | .alias('p', 'password') 18 | .alias('x', 'transfer') 19 | .alias('s', 'specify') 20 | .default('t', 'tcp') 21 | .default('p', '123456') 22 | .describe('l', 'Local address.') 23 | .describe('r', 'Remote address.') 24 | .describe('t', 'Socket type.') 25 | .describe('x', 'Transfer option.') 26 | .describe('c', 'Crypto cipher.') 27 | .describe('p', 'Crypto password.') 28 | .describe('s', 'Specify option.') 29 | .argv; 30 | 31 | if (argv.h) { 32 | Opt.showHelp(); 33 | process.exit(0); 34 | } 35 | 36 | var localAddress = parseAddress(argv.l), 37 | remoteAddress = parseAddress(argv.r); 38 | 39 | // parse and validate port number 40 | function parsePort(port) { 41 | if ((port + '').match(/^[0-9]+$/i)) { 42 | return parseInt(port); 43 | } 44 | 45 | if ((port + '').match(/^[0-9]+\-[0-9]+$/i)) { 46 | parsed = (port + '').split('-'); 47 | return [parseInt(parsed[0]), parseInt(parsed[1])]; 48 | } 49 | 50 | console.log(port + ' is not a valid port number.'); 51 | process.exit(1); 52 | } 53 | 54 | // parse and validate ip address 55 | function parseAddress(address) { 56 | var parsed = (address + '').split('@'), 57 | result = {}; 58 | 59 | if (parsed.length > 1) { 60 | isIP = Net.isIP(parsed[0]); 61 | 62 | if (!isIP) { 63 | console.log(parsed[0] + ' is not a valid ip address.'); 64 | process.exit(1); 65 | } 66 | 67 | result = {ip : parsed[0], port : parsePort(parsed[1]), type : isIP}; 68 | } else { 69 | result = {ip : '0.0.0.0', port : parsePort(parsed[0]), type : 4} 70 | } 71 | 72 | return result; 73 | } 74 | 75 | Adapter = require('./build/' + argv.t); 76 | new Adapter(localAddress, remoteAddress, argv); 77 | 78 | console.log("Piping " + localAddress.ip + "@" + localAddress.port 79 | + " to " + remoteAddress.ip + "@" + remoteAddress.port + " via " + argv.t); 80 | 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket-pipe", 3 | "version": "2.4.5", 4 | "description": "Socket Pipe", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/joyqi/socket-pipe.git" 12 | }, 13 | "bin": { 14 | "socket-pipe": "./bin/socket-pipe" 15 | }, 16 | "preferGlobal": true, 17 | "keywords": [ 18 | "socket", 19 | "pipe" 20 | ], 21 | "author": "joyqi", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/joyqi/socket-pipe/issues" 25 | }, 26 | "homepage": "https://github.com/joyqi/socket-pipe#readme", 27 | "dependencies": { 28 | "node-uuid": "^1.4.7", 29 | "optimist": "^0.6.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/hclient.coffee: -------------------------------------------------------------------------------- 1 | 2 | Net = require 'net' 3 | TClient = require './tclient' 4 | 5 | module.exports = class extends TClient 6 | 7 | constructor: (@localAddress, @remoteAddress, argv) -> 8 | @transfer = argv.x 9 | @specify = argv.s 10 | @createDaemonSocket() 11 | @hash = null 12 | @token = null 13 | 14 | # 创建一个常驻的隧道 15 | createDaemonSocket: -> 16 | ping = Buffer.from [0] 17 | 18 | @transfer = '' if not @transfer? 19 | @specify = '' if not @specify? 20 | parts = @transfer + '|' + @specify + (if @token? then '|' + @token else '') 21 | 22 | tmp = new Buffer parts 23 | first = new Buffer 1 + tmp.length 24 | first.writeInt8 1, 0 25 | tmp.copy first, 1 26 | 27 | @daemonSocket = @connectRemote => 28 | connected = no 29 | 30 | @daemonSocket.ref() 31 | 32 | # 创建一个隧道 33 | @daemonSocket.on 'data', (data) => 34 | if not connected 35 | connected = yes 36 | url = data.toString 'utf8' 37 | console.info "url #{url}" 38 | 39 | [hash, @token] = url.split '|' 40 | @hash = Buffer.from hash 41 | else if data.length == 4 42 | uuid = data.readInt32LE 0 43 | 44 | console.info "request pipe #{uuid}" 45 | @createTunnel uuid 46 | 47 | # 发送一个ping 48 | @daemonSocket.write first 49 | 50 | setInterval => 51 | @daemonSocket.write ping 52 | , 10000 53 | 54 | @daemonSocket.on 'error', console.error 55 | 56 | # 尝试重连 57 | @daemonSocket.on 'close', => 58 | setTimeout => 59 | @createDaemonSocket() 60 | , 1000 61 | 62 | 63 | # 创建隧道 64 | createTunnel: (uuid) -> 65 | ping = new Buffer 5 + @hash.length 66 | ping.writeInt8 2, 0 67 | ping.writeInt32LE uuid, 1 68 | @hash.copy ping, 5 69 | 70 | socket = @connectRemote => 71 | console.info "connect remote #{uuid}" 72 | 73 | local = @connectLocal -> 74 | console.info "connect local #{uuid}" 75 | 76 | socket.write ping 77 | socket.pipe local 78 | .pipe socket 79 | 80 | console.info "piped #{uuid}" 81 | 82 | -------------------------------------------------------------------------------- /src/hserver.coffee: -------------------------------------------------------------------------------- 1 | 2 | Net = require 'net' 3 | Event = require 'events' 4 | Http = require 'http' 5 | UUID = require 'node-uuid' 6 | ProxyStream = require './stream/proxy' 7 | 8 | pregQuote = (str) -> str.replace /[-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&" 9 | 10 | endSocket = (socket) -> 11 | socket.resume() 12 | socket.end "HTTP/1.1 404 Not Found\r\nContent-Type: text/html;charset=UTF-8\r\n\r\nNotFound" 13 | 14 | module.exports = class 15 | 16 | constructor: (@localAddress, @remoteAddress) -> 17 | @id = 0 18 | @dataEvent = new Event 19 | @daemonSockets = {} 20 | @sockets = {} 21 | @pipes = {} 22 | @waits = {} 23 | 24 | setInterval => 25 | now = Date.now() 26 | 27 | for uuid, item of @waits 28 | [hash, buff, time] = item 29 | 30 | if now - time >= 1000 31 | item[2] = now 32 | 33 | if not @pipes[uuid]? and @sockets[uuid]? and @daemonSockets[hash]? 34 | @daemonSockets[hash][0].write buff 35 | console.info "retry pipe #{uuid}" 36 | , 200 37 | 38 | @dataEvent.on 'accept', (uuid) => 39 | return if not @sockets[uuid]? 40 | 41 | input = new ProxyStream 42 | @sockets[uuid].push input 43 | 44 | input.setCallback (reqHost, head) => 45 | console.info "request #{reqHost}" 46 | 47 | [hash] = reqHost.split '.' 48 | if not @daemonSockets[hash]? 49 | return endSocket @sockets[uuid][0] 50 | 51 | host = if @daemonSockets[hash][1]? then @daemonSockets[hash][1] else reqHost 52 | buff = new Buffer 4 53 | buff.writeInt32LE uuid 54 | 55 | console.info "request pipe #{uuid}" 56 | @daemonSockets[hash][0].write buff 57 | 58 | @waits[uuid] = [hash, buff, Date.now()] 59 | 60 | regex = new RegExp (pregQuote reqHost), 'ig' 61 | 62 | output = new ProxyStream 63 | output.setFrom host 64 | output.setTo reqHost 65 | 66 | @sockets[uuid].push output 67 | @sockets[uuid][0].pause() 68 | 69 | head.replace regex, host 70 | 71 | @sockets[uuid][0].pipe input 72 | @sockets[uuid][0].resume() 73 | 74 | @dataEvent.on 'pipe', (uuid, hash) => 75 | delete @waits[uuid] 76 | 77 | return if not @sockets[uuid] 78 | return endSocket @sockets[uuid][0] if not @daemonSockets[hash] 79 | return endSocket @sockets[uuid][0] if not @pipes[uuid] 80 | 81 | @sockets[uuid][1].pipe @pipes[uuid] 82 | .pipe @sockets[uuid][2] 83 | .pipe @sockets[uuid][0] 84 | 85 | @sockets[uuid][1].release() 86 | @sockets[uuid][0].resume() 87 | 88 | @createLocalServer() 89 | @createRemoteServer() 90 | 91 | 92 | accept: (socket) -> 93 | console.info "accept #{socket.remoteAddress}:#{socket.remotePort}" 94 | 95 | uuid = @id 96 | @id += 1 97 | 98 | socket.pause() 99 | @sockets[uuid] = [socket] 100 | 101 | socket.on 'close', => 102 | console.info "close socket #{uuid}" 103 | if @sockets[uuid]? 104 | delete @sockets[uuid] 105 | 106 | if @waits[uuid]? 107 | delete @waits[uuid] 108 | 109 | socket.on 'error', console.error 110 | 111 | @dataEvent.emit 'accept', uuid 112 | 113 | 114 | createRemoteServer: -> 115 | @remoteServer = Net.createServer (socket) => 116 | @accept socket 117 | 118 | @remoteServer.on 'error', console.error 119 | @remoteServer.listen @remoteAddress.port, @remoteAddress.ip 120 | 121 | 122 | createLocalServer: -> 123 | @localServer = Net.createServer (socket) => 124 | connected = no 125 | 126 | socket.on 'error', console.error 127 | 128 | socket.on 'data', (data) => 129 | if not connected 130 | connected = yes 131 | op = data.readInt8 0 132 | 133 | if op == 1 134 | parts = (data.slice 1).toString() 135 | items = parts.split '|' 136 | [transfer, hash] = items 137 | token = if items[2]? then items[2] else null 138 | 139 | if hash.length == 0 or (@daemonSockets[hash]? and token != @daemonSockets[hash][2]) 140 | hash = UUID.v1() 141 | 142 | transfer = null if transfer.length == 0 143 | console.info "connected #{socket.remoteAddress}:#{socket.remotePort} = #{hash} #{transfer}" 144 | 145 | # add token 146 | token = UUID.v1() 147 | @daemonSockets[hash] = [socket, transfer, token] 148 | 149 | socket.on 'close', => 150 | delete @daemonSockets[hash] if hash? and @daemonSockets[hash]? 151 | 152 | socket.write new Buffer hash + '|' + token 153 | else if op == 2 154 | uuid = data.readInt32LE 1 155 | hash = (data.slice 5).toString() 156 | 157 | return socket.end() if @pipes[uuid]? 158 | 159 | @pipes[uuid] = socket 160 | 161 | socket.on 'close', => 162 | console.info "close pipe #{uuid}" 163 | 164 | if @pipes[uuid]? 165 | delete @pipes[uuid] 166 | 167 | console.info "created pipe #{uuid}" 168 | @dataEvent.emit 'pipe', uuid, hash 169 | 170 | @localServer.on 'error', console.error 171 | @localServer.listen @localAddress.port, @localAddress.ip 172 | 173 | 174 | -------------------------------------------------------------------------------- /src/stream/decode.coffee: -------------------------------------------------------------------------------- 1 | 2 | Transform = (require 'stream').Transform 3 | Crypto = require 'crypto' 4 | 5 | 6 | # 加密流 7 | module.exports = class extends Transform 8 | 9 | initDecipher: (cipher, password) -> 10 | @decipher = Crypto.createDecipher cipher, password 11 | @received = no 12 | @packet = null 13 | @last = null 14 | 15 | 16 | _transform: (buff, enc, callback) -> 17 | try 18 | if @last? 19 | newBuff = new Buffer @last.length + buff.length 20 | @last.copy newBuff, 0 21 | buff.copy newBuff, @last.length 22 | buff = newBuff 23 | @last = null 24 | 25 | if not @received 26 | length = buff.readInt32LE 0 27 | 28 | console.info "new packet #{length}" 29 | 30 | @packet = new Buffer length 31 | @offset = 0 32 | 33 | end = Math.min buff.length - 1, 3 + @packet.length - @offset 34 | console.info "end with #{end}" 35 | 36 | buff.copy @packet, @offset, 4, end 37 | @received = not (end <= buff.length - 1) 38 | @offset += end - 3 39 | 40 | @last = buff.slice end + 1 if end < buff.length - 1 41 | callback null, @decipher.update @packet if not @received 42 | catch e 43 | callback e, null 44 | -------------------------------------------------------------------------------- /src/stream/encode.coffee: -------------------------------------------------------------------------------- 1 | 2 | Transform = (require 'stream').Transform 3 | Crypto = require 'crypto' 4 | 5 | 6 | # 加密流 7 | module.exports = class extends Transform 8 | 9 | initCipher: (cipher, password) -> 10 | @cipher = Crypto.createCipher cipher, password 11 | 12 | 13 | createPacket: (buff) -> 14 | packet = new Buffer buff.length + 4 15 | buff.copy packet, 4, 0, buff.length - 1 16 | packet.writeInt32LE buff.length, 0 17 | 18 | console.info "enconde #{buff.length}" 19 | 20 | packet 21 | 22 | 23 | _transform: (buff, enc, callback) -> 24 | try 25 | packet = @createPacket @cipher.update buff 26 | callback null, packet 27 | catch e 28 | callback e, null 29 | -------------------------------------------------------------------------------- /src/stream/proxy.coffee: -------------------------------------------------------------------------------- 1 | 2 | Transform = (require 'stream').Transform 3 | 4 | pregQuote = (str) -> str.replace /[-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&" 5 | 6 | 7 | module.exports = class extends Transform 8 | 9 | constructor: -> 10 | @filtered = no 11 | @piped = no 12 | @buffers = [] 13 | super 14 | 15 | 16 | setCallback: (@cb) -> 17 | 18 | 19 | setFrom: (from) -> 20 | @from = new RegExp (pregQuote from), 'ig' 21 | 22 | 23 | setTo: (@to) -> 24 | 25 | 26 | pipe: (@stream) -> 27 | @piped = yes 28 | return super 29 | 30 | 31 | release: -> 32 | if @stream? 33 | while buffer = @buffers.shift() 34 | @stream.write buffer 35 | 36 | 37 | callback: (err, buff) -> 38 | if @piped 39 | super 40 | else 41 | @buffers.push buff 42 | 43 | 44 | _transform: (buff, enc, callback) -> 45 | if not @filtered 46 | str = if enc is 'buffer' then (buff.toString 'binary') else buff 47 | pos = str.indexOf "\r\n\r\n" 48 | 49 | if pos >= 0 50 | @filtered = yes 51 | 52 | head = str.substring 0, pos 53 | body = str.substring pos 54 | else 55 | head = str 56 | body = '' 57 | 58 | if @from? and @to? 59 | head = head.replace @from, @to 60 | 61 | if matches = head.match /host:\s*([^\r]+)/i 62 | head = @cb matches[1], head if @cb? 63 | 64 | callback null, Buffer.from head + body, 'binary' 65 | 66 | else 67 | callback null, buff 68 | 69 | @clear() if (buff.indexOf -1) >= 0 70 | 71 | 72 | clear: -> 73 | @filtered = no 74 | @buffers = [] 75 | -------------------------------------------------------------------------------- /src/tclient.coffee: -------------------------------------------------------------------------------- 1 | 2 | Net = require 'net' 3 | EncodeStream = require './stream/encode' 4 | DecodeStream = require './stream/decode' 5 | 6 | module.exports = class 7 | 8 | constructor: (@localAddress, @remoteAddress, @argv) -> 9 | @createDaemonSocket() 10 | 11 | 12 | # 创建一个常驻的隧道 13 | createDaemonSocket: -> 14 | ping = Buffer.from [0] 15 | 16 | @daemonSocket = @connectRemote => 17 | @daemonSocket.ref() 18 | 19 | # 创建一个隧道 20 | @daemonSocket.on 'data', (data) => 21 | if data.length == 4 22 | uuid = data.readInt32LE 0 23 | 24 | console.info "request pipe #{uuid}" 25 | @createTunnel uuid 26 | 27 | # 发送一个ping 28 | @daemonSocket.write ping 29 | 30 | setInterval => 31 | @daemonSocket.write ping 32 | , 10000 33 | 34 | # 尝试重连 35 | @daemonSocket.on 'close', => 36 | setTimeout => 37 | @createDaemonSocket() 38 | , 1000 39 | 40 | 41 | # 连接远程 42 | connectRemote: (cb) -> 43 | socket = Net.connect @remoteAddress.port, @remoteAddress.ip, cb 44 | 45 | socket.on 'error', console.error 46 | 47 | socket 48 | 49 | 50 | # 连接本地 51 | connectLocal: (cb) -> 52 | socket = Net.connect @localAddress.port, @localAddress.ip, cb 53 | 54 | socket.on 'error', console.error 55 | 56 | socket 57 | 58 | 59 | # 创建隧道 60 | createTunnel: (uuid) -> 61 | ping = new Buffer 4 62 | ping.writeInt32LE uuid, 0 63 | 64 | socket = @connectRemote => 65 | console.info "connect remote #{uuid}" 66 | 67 | local = @connectLocal => 68 | console.info "connect local #{uuid}" 69 | 70 | socket.write ping 71 | 72 | if @argv.c? 73 | encoder = new EncodeStream 74 | decoder = new DecodeStream 75 | 76 | encoder.initCipher @argv.c, @argv.p 77 | decoder.initDecipher @argv.c, @argv.p 78 | 79 | socket.pipe decoder 80 | .pipe local 81 | .pipe encoder 82 | .pipe socket 83 | else 84 | socket.pipe local 85 | .pipe socket 86 | 87 | console.info "piped #{uuid}" 88 | 89 | -------------------------------------------------------------------------------- /src/tcp.coffee: -------------------------------------------------------------------------------- 1 | 2 | Net = require 'net' 3 | 4 | module.exports = class 5 | 6 | constructor: (localAddress, remoteAddress) -> 7 | server = Net.createServer (client) -> 8 | socket = Net.connect remoteAddress.port, remoteAddress.ip 9 | client.pipe socket 10 | .pipe client 11 | 12 | console.info "request #{client.remoteAddress}:#{client.remotePort}" 13 | 14 | client.on 'error', console.error 15 | 16 | server.listen localAddress.port, localAddress.ip 17 | server.on 'error', console.error 18 | 19 | -------------------------------------------------------------------------------- /src/tserver.coffee: -------------------------------------------------------------------------------- 1 | 2 | Net = require 'net' 3 | Event = require 'events' 4 | EncodeStream = require './stream/encode' 5 | DecodeStream = require './stream/decode' 6 | 7 | module.exports = class 8 | 9 | constructor: (@localAddress, @remoteAddress, @argv) -> 10 | @id = 0 11 | @dataEvent = new Event 12 | @daemonSocket = null 13 | @sockets = {} 14 | @pipes = {} 15 | 16 | @dataEvent.on 'pipe', (uuid) => 17 | return if not @sockets[uuid]? 18 | 19 | if not @pipes[uuid]? 20 | buff = new Buffer 4 21 | buff.writeInt32LE uuid 22 | 23 | return if not @daemonSocket? 24 | 25 | console.info "request pipe #{uuid}" 26 | return @daemonSocket.write buff 27 | 28 | if @argv.c? 29 | encoder = new EncodeStream 30 | decoder = new DecodeStream 31 | 32 | encoder.initCipher @argv.c, @argv.p 33 | decoder.initDecipher @argv.c, @argv.p 34 | @sockets[uuid].pipe encoder 35 | .pipe @pipes[uuid] 36 | .pipe decoder 37 | .pipe @sockets[uuid] 38 | else 39 | @sockets[uuid].pipe @pipes[uuid] 40 | .pipe @sockets[uuid] 41 | 42 | @sockets[uuid].resume() 43 | 44 | @createLocalServer() 45 | @createRemoteServer() 46 | 47 | 48 | accept: (socket) -> 49 | console.info "accept #{socket.remoteAddress}:#{socket.remotePort}" 50 | socket.pause() 51 | 52 | uuid = @id 53 | @id += 1 54 | @sockets[uuid] = socket 55 | 56 | socket.on 'close', => 57 | console.info "close socket #{uuid}" 58 | if @sockets[uuid]? 59 | delete @sockets[uuid] 60 | 61 | socket.on 'error', console.error 62 | 63 | @dataEvent.emit 'pipe', uuid 64 | 65 | 66 | createRemoteServer: -> 67 | @remoteServer = Net.createServer (socket) => 68 | @accept socket 69 | 70 | @remoteServer.on 'error', console.error 71 | @remoteServer.listen @remoteAddress.port, @remoteAddress.ip 72 | 73 | 74 | createLocalServer: -> 75 | @localServer = Net.createServer (socket) => 76 | connected = no 77 | 78 | socket.on 'error', console.error 79 | 80 | socket.on 'data', (data) => 81 | if not connected 82 | connected = yes 83 | 84 | if data.length == 1 85 | console.info "connected #{socket.remoteAddress}:#{socket.remotePort}" 86 | @daemonSocket = socket 87 | else if data.length == 4 88 | uuid = data.readInt32LE 0 89 | @pipes[uuid] = socket 90 | 91 | socket.on 'close', => 92 | console.info "close pipe #{uuid}" 93 | 94 | if @pipes[uuid]? 95 | delete @pipes[uuid] 96 | 97 | console.info "created pipe #{uuid}" 98 | @dataEvent.emit 'pipe', uuid 99 | 100 | @localServer.on 'error', console.error 101 | @localServer.listen @localAddress.port, @localAddress.ip 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/udp.coffee: -------------------------------------------------------------------------------- 1 | 2 | Udp = require 'dgram' 3 | 4 | module.exports = class 5 | 6 | constructor: (@localAddress, @remoteAddress) -> 7 | @connectionPool = {} 8 | 9 | setInterval => 10 | now = Date.now() 11 | index = 0 12 | 13 | for key, connect of @connectionPool 14 | if now - connect.time < 5000 15 | connect.socket.close() 16 | delete @connectionPool[key] 17 | console.log "close #{key}" 18 | , 10000 19 | 20 | @createServer() 21 | 22 | 23 | createServer: -> 24 | receiver = Udp.createSocket 'udp' + @localAddress.type 25 | 26 | receiver.bind @localAddress.port, @localAddress.ip 27 | receiver.ref() 28 | 29 | receiver.on 'error', console.error 30 | 31 | receiver.on 'message', (data, info) => 32 | key = @requestUdpSocket info 33 | sender = @connectionPool[key].socket 34 | client = info 35 | 36 | sender.on 'message', (data, info) => 37 | console.log "response #{info.address}:#{info.port}" 38 | 39 | @connectionPool[key].time = Date.now() if typeof @connectionPool[key] != 'undefined' 40 | receiver.send data, 0, data.length, client.port, client.address 41 | 42 | console.log "request #{client.address}:#{client.port}" 43 | sender.send data, 0, data.length, (@requestUdpPort @remoteAddress.port), @remoteAddress.ip 44 | 45 | 46 | requestUdpSocket: (info) -> 47 | now = Date.now() 48 | key = info.address + ':' + info.port 49 | 50 | if typeof @connectionPool[key] != 'undefined' 51 | @connectionPool[key].time = now 52 | return key 53 | 54 | socket = Udp.createSocket 'udp' + @remoteAddress.type 55 | socket.bind() 56 | 57 | socket.on 'error', (err) -> 58 | console.error err 59 | socket.close() 60 | delete @connectionPool[key] 61 | 62 | @connectionPool[key] = {time : now, socket : socket} 63 | key 64 | 65 | 66 | requestUdpPort: (port) -> 67 | if port instanceof Array 68 | port[0] + Math.floor Math.random() * (port[1] - port[0] + 1) 69 | else 70 | port 71 | 72 | --------------------------------------------------------------------------------