├── config-client.json ├── config-server.json ├── package.json ├── mix-server.js ├── mix-client.js ├── README.md └── common.js /config-client.json: -------------------------------------------------------------------------------- 1 | { 2 | "tunnelhost":"172.16.41.1", 3 | "tunnelport":1088, 4 | "type":"fixed", 5 | "userinfo":{ 6 | "username":"user1", 7 | "password":"123456" 8 | }, 9 | "localport": [5080,5081,5022], 10 | "remoteproxy":[ 11 | ] 12 | } -------------------------------------------------------------------------------- /config-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "tunnelhost":"172.16.41.1", 3 | "tunnelport":1088, 4 | "type":"fixed", 5 | "users":[ 6 | { 7 | "username":"user1", 8 | "password":"123456", 9 | "status":"up" 10 | }, 11 | { 12 | "username":"user2", 13 | "password":"123456", 14 | "status":"down" 15 | } 16 | ], 17 | "proxy":[ 18 | { 19 | "host":"127.0.0.1", 20 | "port":82 21 | }, 22 | { 23 | "host":"127.0.0.1", 24 | "port":9000 25 | }, 26 | { 27 | "host":"172.16.203.254", 28 | "port":80 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tcp-tunnel", 3 | "version": "0.0.1", 4 | "description": "node.js tcp tunnel", 5 | "keywords": [ 6 | "realtime", 7 | "framework", 8 | "ws", 9 | "tcp", 10 | "socket" 11 | ], 12 | "main": "./mix-server.js", 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/huge818/tcp-tunnel.git" 17 | }, 18 | "scripts": { 19 | }, 20 | "dependencies": { 21 | "ws": "*", 22 | }, 23 | "devDependencies": { 24 | }, 25 | "contributors": [ 26 | { 27 | "name": "huzhongchuan", 28 | "email": "huzc818@qq.com" 29 | }, 30 | ], 31 | "bugs": { 32 | "url": "https://github.com/huge818/tcp-tunnel/issues" 33 | }, 34 | "homepage": "https://github.com/huge818/tcp-tunnel#readme", 35 | "_id": "tcp-tunnel@0.0.1", 36 | "_npmUser": { 37 | "name": "huzc818@qq.com", 38 | "email": "huzc818@qq.com" 39 | }, 40 | "dist": { 41 | 42 | }, 43 | "maintainers": [ 44 | { 45 | "name": "huzc818", 46 | "email": "huzc818@qq.com" 47 | } 48 | ], 49 | "_npmOperationalInternal": { 50 | 51 | }, 52 | "directories": {}," 53 | } 54 | -------------------------------------------------------------------------------- /mix-server.js: -------------------------------------------------------------------------------- 1 | var net=require('net'); 2 | var toolkit=require('./common'); 3 | var ws = require('ws'); 4 | 5 | var config=toolkit.readConfig("./config-server.json"); 6 | if(!config){ 7 | process.exit(); 8 | } 9 | 10 | toolkit.socketList={}; 11 | 12 | var wss = new ws.Server({port:config.tunnelport}); 13 | wss.on('connection', function(ws) { 14 | ws.on('message', function(data) { 15 | var mixObj = toolkit.readMixBuffer(data); 16 | toolkit.checkreadBuffer(data,"主服务器接受到mix数据解析"); 17 | if(!mixObj.mix){ 18 | console.log("服务器收到非mix数据"); 19 | return; 20 | } 21 | var id=mixObj.id; 22 | var index=mixObj.index; 23 | var buf=mixObj.data; 24 | if(id==="00000000"&&index===65535){ 25 | var str=mixObj.data.toString(); 26 | var proxy=JSON.parse(str); 27 | config.proxy=proxy; 28 | return; 29 | } 30 | var socketGroup=toolkit.socketList[index+""]; 31 | if(socketGroup&&socketGroup[id]){ 32 | socketGroup[id].write(buf); 33 | } 34 | else{ 35 | var fn=function(singleClient){ 36 | singleClient.write(buf); 37 | }; 38 | var address=config.proxy[index]; 39 | toolkit.clientConnect(id,index,address,ws,fn); 40 | } 41 | }); 42 | }); 43 | 44 | console.log("server id running"); 45 | -------------------------------------------------------------------------------- /mix-client.js: -------------------------------------------------------------------------------- 1 | var net=require('net'); 2 | var toolkit=require('./common'); 3 | var websocket = require('ws'); 4 | var config=toolkit.readConfig("./config-client.json"); 5 | if(!config){ 6 | process.exit(); 7 | } 8 | 9 | var ws = new websocket("ws://"+config.tunnelhost+":"+config.tunnelport); 10 | 11 | ws.on('open', function(){ 12 | console.log("connect ok"); 13 | }); 14 | 15 | ws.on('message', function(buf) { 16 | var mixObj=toolkit.readMixBuffer(buf); 17 | toolkit.checkreadBuffer(buf,"mixClient收到数据解析readMixBuffer"); 18 | if(!mixObj.mix){ 19 | console.log("mixClient回来非mix"); 20 | return; 21 | } 22 | var index=mixObj.index; 23 | var id=mixObj.id; 24 | var data=mixObj.data; 25 | var socketHandle=toolkit.socketList[index+""][id]; 26 | if(socketHandle){ 27 | socketHandle.write(data); 28 | } else{ 29 | console.log("找不到句柄id="+id); 30 | } 31 | }); 32 | 33 | if(config.type=="custom"){ 34 | var str=JSON.stringify(config.remoteproxy); 35 | var data= new Buffer(str); 36 | var buf=toolkit.writeMixBuffer(65535,"00000000",data); 37 | ws.send(buf); 38 | } 39 | 40 | toolkit.socketList={}; 41 | 42 | //fixed,custom,all 43 | var L=config.localport.length; 44 | for(var i=0;i<=L-1;i++){ 45 | toolkit.socketList[i+""]={}; 46 | toolkit.clientServer(i, config, ws); 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tcp-tunnel 2 | Mixing multiple port data into one port channel 3 | 4 | 混合多个端口的数据,走一个端口通道。 5 | 6 | 例如:当服务器(IP:172.16.41.1)的防火墙封闭了几乎所有的端口,只开放了1088端口, 7 | 8 | 我们如何访问服务器的其他端口(80,82,9000)的服务呢? 9 | 10 | 只需要 11 | 12 | 在服务器端执行 13 | ```javascript 14 | node mix-server 15 | ``` 16 | 17 | 在客户端执行 18 | 19 | ```javascript 20 | node mix-client 21 | ``` 22 | 23 | 客户端通过访问 24 | 25 | 127.0.0.1:5080 26 | 27 | 127.0.0.1:5081 28 | 29 | 127.0.0.1:5022 30 | 31 | 这样通过本地IP访问远程服务器的一些端口的服务。 32 | 端口1088可以走所有协议数据,称之为隧道端口。 33 | 34 | 配置文件说明 35 | 36 | config-client.json 37 | 38 | ```javascript 39 | { 40 | "tunnelhost":"172.16.41.1", //远程服务器IP 41 | "tunnelport":1088, //远程服务器隧道端口,可以不改 42 | "type":"fixed", //连接模式,和服务器提供的保持一致,有fixed和custom两个值, 43 | //fixed模式表示服务端代理host+port服务,custom模式表示客户端来指定服务代理哪些host+port,custom模式比较危险,慎用 44 | "userinfo":{ //用户认证账号,以后支持 45 | "username":"user1", 46 | "password":"123456" 47 | }, 48 | "localport": [5080,5081,5022], //本地提供服务的三个端口 49 | "remoteproxy":[] //type为custom的时候可配置此属性格式为多个对象 example:{"host":"xx.xx.xx.xx","port":80} 50 | } 51 | ``` 52 | 53 | config-server.json 54 | 55 | ```javascript 56 | { 57 | "tunnelhost":"172.16.41.1", //本机IP地址,可以不写 58 | "tunnelport":1088, //本机监听端口,隧道端口 59 | "type":"fixed", //连接模式,和服务器提供的保持一致,有fixed和custom两个值, 60 | //fixed模式表示服务端代理host+port服务,custom模式表示客户端来指定服务代理哪些host+port,custom模式比较危险,慎用 61 | "users":[ //用户认证账号列表,以后支持 62 | { 63 | "username":"user1", 64 | "password":"123456", 65 | "status":"up" 66 | }, 67 | { 68 | "username":"user2", 69 | "password":"123456", 70 | "status":"down" 71 | } 72 | ], 73 | "proxy":[ //指定代理哪些服务,ip可是本地的127.0.0.1,这个用得比较多,也就可以配置其他局域网IP和外网IP 74 | { 75 | "host":"127.0.0.1", 76 | "port":82 77 | }, 78 | { 79 | "host":"127.0.0.1", 80 | "port":9000 81 | }, 82 | { 83 | "host":"172.16.203.254", 84 | "port":80 85 | } 86 | ] 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | var net=require('net'); 2 | var fs=require('fs'); 3 | var toolkit={}; 4 | /** 5 | * [readMixBuffer 解析混合数据流] 6 | * @param {[buffer]} buf [混合数据流] 7 | * @return {[object]} [解析之后的数据格式object] 8 | */ 9 | toolkit.readMixBuffer=function(buf){ 10 | var mix=buf.slice(0,3); 11 | var mixbuf = new Buffer("mix"); 12 | if(!mix.equals(mixbuf)){ 13 | return {mix:false}; 14 | } 15 | var L=buf.length; 16 | var index=buf.slice(3,5).readUInt16BE(0); 17 | var id=buf.slice(5,13).toString(); 18 | var data=buf.slice(13); 19 | var portstr=index+""; 20 | if(typeof(index)!=="number"){ 21 | return {mix:false}; 22 | } 23 | if(index>65535){ 24 | return {mix:false}; 25 | } 26 | if(!id.match(/\d{8}/g)){ 27 | return {mix:false}; 28 | } 29 | return { 30 | mix:true, 31 | index:index, 32 | id:id, 33 | data:data 34 | }; 35 | } 36 | 37 | /** 38 | * [removeBuffer 移除子buffer] 39 | * @param {[buffer]} buf [main buffer] 40 | * @param {[buffer]} subuff [sub buffer] 41 | * @return {[buffer]} [result buffer] 42 | */ 43 | toolkit.removeBuffer=function(buf,subuff){ 44 | var pos=buf.indexOf(subuff); 45 | var L=subuff.length; 46 | var A=buf.slice(0,pos); 47 | var B=buf.slice(pos+L); 48 | var data= Buffer.concat([A,B]); 49 | return data; 50 | } 51 | 52 | /** 53 | * [writeMixBuffer 组装混合流的数据包] 54 | * @param {[number]} index [混合流之前数据的端口] 55 | * @param {[string]} id [唯一标识符] 56 | * @param {[buffer]} buf [混合之前的buffer数据] 57 | * @return {[buffer]} [混合之后的buffer] 58 | */ 59 | toolkit.writeMixBuffer=function(index, id, buf){ 60 | var mix= new Buffer("mix"); 61 | var bufindex= new Buffer(2); 62 | bufindex.writeUInt16BE(index,0); 63 | var bufid = new Buffer(id); 64 | var data= Buffer.concat([mix,bufindex,bufid,buf]); 65 | return data; 66 | } 67 | 68 | /** 69 | * [checkreadBuffer 检查混合包是否有误] 70 | * @param {[buffer]} data [description] 71 | * @param {[string]} log [description] 72 | * @return {[buffer]} [description] 73 | */ 74 | toolkit.checkreadBuffer=function(data,log){ 75 | var mixObj = toolkit.readMixBuffer(data); 76 | console.log("==========="+log+"=============="); 77 | if(!mixObj.mix){ 78 | return false; 79 | } 80 | if(typeof(mixObj.index)!=="number"){ 81 | console.log("数据异常!index"); 82 | return false; 83 | } 84 | if(mixObj.id.length!==8){ 85 | console.log("数据异常!id"); 86 | return false; 87 | } 88 | if(mixObj.data.length!==data.length-13){ 89 | console.log("数据异常!data"); 90 | console.log(mixObj.data.length,data.length); 91 | console.log("id="+mixObj.id+"\n"); 92 | console.log("index="+mixObj.index+"\n"); 93 | console.log("data=",data); 94 | return false; 95 | } 96 | return true; 97 | } 98 | 99 | /** 100 | * [uuid 唯一id,8位字符串,待优化] 101 | * @return {[string]} [字符串id] 102 | */ 103 | toolkit.uuid=function(){ 104 | var str=parseInt(Math.random()*100000000)+""; 105 | var L=str.length; 106 | if(L==8){ 107 | return str; 108 | } 109 | var M=8-L; 110 | for(var i=1;i<=8-L;i++){ 111 | str=str+"0"; 112 | } 113 | if(str.length!==8){ 114 | console.log("error str.length!==8"); 115 | } 116 | return str; 117 | } 118 | 119 | /** 120 | * [clientServer 客户端即时创建客户端tcpserver,提供给客户端自己使用127.0.0.1来调用] 121 | * @param {[number]} port [客户端本地端口] 122 | * @param {[socket]} mixClient [混合数据流句柄] 123 | * @param {Function} callback [回调] 124 | */ 125 | toolkit.clientServer=function(index, config, ws){ 126 | var self=this; 127 | var port=config.localport[index]; 128 | var tcpServer = net.createServer(function(socket){ 129 | var id=toolkit.uuid(); 130 | self.socketList[index+""][id]=socket; 131 | //收到浏览器或其他客户端发来的数据 132 | socket.on("data",function(data){ 133 | var buf=toolkit.writeMixBuffer(index,id,data); 134 | toolkit.checkreadBuffer(buf,"client server "+port); 135 | var mixObj = toolkit.readMixBuffer(buf); 136 | if(!mixObj.mix){ 137 | console.log("toolkit.clientServer 数据异常!mix"); 138 | return false; 139 | } 140 | ws.send(buf); 141 | }); 142 | 143 | socket.on('timeout', function(){ 144 | console.log("socket timeout"); 145 | }); 146 | 147 | var fn=function(error){ 148 | console.log(error); 149 | setTimeout(function(){ 150 | delete self.socketList[index+""][id]; 151 | },1000); 152 | } 153 | socket.on("end",fn); 154 | socket.on("error",fn); 155 | socket.on("close",fn); 156 | }); 157 | 158 | tcpServer.on('error', function(err){ 159 | throw err; 160 | }); 161 | 162 | tcpServer.listen(port); 163 | } 164 | 165 | /** 166 | * [clientConnect 服务器端client连接到各个提供服务器的host:port 167 | * 暂时只提供服务器本地host 168 | * mixserver发送给singclient的是解析后的直接流 169 | * singleclient发送给mixserver的是加头之后的混合流 170 | * ] 171 | * @param {[string]} id [socket唯一标识符] 172 | * @param {[number]} index [序号] 173 | * @param {[socket]} mixSocket [服务器端主服务器socket句柄] 174 | * @param {Function} fn [连接成功回调] 175 | * @return {[object]} [client连接句柄] 176 | */ 177 | toolkit.clientConnect=function(id,index,address,ws,fn){ 178 | var singleClient = net.createConnection(address, function(){ 179 | console.log("connect ok"); 180 | console.log(); 181 | if(!toolkit.socketList[index+""]){ 182 | toolkit.socketList[index+""]={}; 183 | } 184 | toolkit.socketList[index+""][id]=singleClient; 185 | fn&&fn(singleClient); 186 | }); 187 | 188 | singleClient.on('data', function(buf){ 189 | var data=toolkit.writeMixBuffer(index,id,buf); 190 | toolkit.checkreadBuffer(data,"singleClient发送给mixServer"); 191 | ws.send(data); 192 | }); 193 | 194 | singleClient.on('end', function(){ 195 | console.log("end"); 196 | setTimeout(function(){ 197 | delete toolkit.socketList[index+""][id]; 198 | },1000); 199 | }); 200 | 201 | singleClient.on('timeout', function(){ 202 | console.log(" singleClient timeout"); 203 | }); 204 | 205 | return singleClient; 206 | } 207 | 208 | /** 209 | * [readConfig 读取配置] 210 | * @param {[type]} path [配置文件路径] 211 | * @param {Function} fn [回调] 212 | */ 213 | toolkit.readConfig=function(path,fn){ 214 | var flag=fs.existsSync(path); 215 | if(!flag){ 216 | fn&&fn("file not exist"); 217 | return; 218 | } 219 | var data=fs.readFileSync(path,"utf-8"); 220 | try{ 221 | var config=JSON.parse(data); 222 | } 223 | catch(e){ 224 | fn&&fn("JSON.parse error"); 225 | console.log("JSON.parse error"); 226 | return; 227 | } 228 | fn&&fn(null,config); 229 | return config; 230 | } 231 | 232 | toolkit.socketList={}; 233 | 234 | module.exports = toolkit; 235 | 236 | --------------------------------------------------------------------------------