TCP点对点转发的实现与原理(nodejs)
Nagent
Nagent是TCP点对点转发实现,名称来源于Nat与Agent的组合。类似frp项目,可以在局域网与互联网提供桥梁。
前提是你要有一台流量服务器并且有一个公网IP。如果没有,也可以找服务商。
暂不能向frp那样为HTTP服务,但可以实现简单的分发————你只需要在两台内网HTTP服务器上运行Nagent客户端即可。
项目位置:https://github.com/FettLuo/nagent
进度
可以使用
未向特殊协议优化,例如http/s的转发
虽然协议有涉及账号名与密码,但未实现
未来希望你或我,向项目添加账号管理支持,以webservice的形式支持
名词解释
客户端:运行在内网的Nagent客户端。
服务端:运行在公网服务器上的Nagent服务端。
用户:互联网上的实际用户。
过程
服务器监听在5670端口(默认)。
客户端配置好自己的服务端口,也可以指定内网其他计算机。假设本机80端口。
客户端登录到服务器,通知服务器我需要监听的外网端口,比如90。
一切正常的话(防火墙没问题,端口没被占用等),服务器上90端口的连接即会被导向到内网的80端口服务上。
原理
客户端与服务器保持着一定数量的连接,每个连接都需要登录成功。
用户连接公网服务器的端口后会从客户端的列表中弹出一个用于数据转发。
当客户端第一次收到数据时,建立与本地服务的连接,并发送/转发数据。
部署
需要NodeJS
运行为服务端
windows/linux:
node nagent.js -s
linux:
./nagent.js -s
运行为客户端
windows/linux:
node nagent.js -p 90 -P 80
linux:
./nagent.js -p 90 -P 80
配置
保存下面内容到nagent.js所在的目录,文件名为nagent.config,方括号内替换为你的参数。
local_port=[你的本地服务端口]
server_port=5670// 服务端端口号
server_host='[服务端的主机地址,IP或域名均可]'
remote_port=[你需要服务端为你开放的公网端口]
keep_conn_count=10// 同时保持的最大连接数量
客户端代码
// handling login var handling_login=(sock,data)=>{ if(data.toString()=="ok\r"){ log("login is done.", sock.remoteAddress+":"+sock.remotePort) sock.on("data", d=>{handling_data(sock,d)}) }else{ log("login is failed.", sock.remoteAddress+":"+sock.remotePort, data.toString()) sock.end() } } // handling data var handling_data=(sock,data)=>{ if(!sock.partner){ if(!sock.buffer)// save data for connect done sock.buffer=data else{ sock.buffer=Buffer.concat([sock.buffer,data]) return } partner = net.connect(local_port, local_host) conn_count-- partner.on("connect", e=>{ log("partner connect is done. port is", local_port) debug("s>>", show(sock.buffer)) partner.write(sock.buffer) sock.partner=partner }) partner.on("data", d=>{debug("s<<",show(d));sock.write(d)}) partner.on("error", e=>{open_conn()}) partner.on("end",e=>{log("partner is closed", local_port);sock.end()}) }else{ debug("s>>", show(data)) sock.partner.write(data) } } // open service var open_conn=port=>{ if(conn_count>=keep_conn_count)return var temp=net.connect(server_port, server_host) conn_count+=1 temp.on("connect", e=>{ log(temp.remoteAddress, temp.localPort, "connected! current connection count is", conn_count) temp.write("NAGENT1.0 guest nopwd "+remote_port+"\r")// protocal,username,password(can't include space char),open port temp.once("data", d=>{handling_login(temp,d)}) if(conn_count<keep_conn_count)open_conn() }) temp.on("error", e=>{conn_count--;log(e.errno);setTimeout(open_conn, 1000)}) temp.on("end", e=>{ conn_count-- log("connection is closed", temp.remotePort, "connnection count:", conn_count) if(temp.partner)temp.partner.end() setTimeout(open_conn, 1000) }) } open_conn()
服务端代码
var ports={}// port=>server(clients) var debug=e=>{}//console.warn // connection was closed var disconnect=(server,s)=>{ if(s.client){ server.conns.delete(s.client) s.client.destroy() }else if(s.partner){ s.partner.destroy() } if(server.conns.size+server.clients.length==0){ log("server was stop port is ", server.localPort) server.close() ports[server.localPort]=undefined } log(s.remotePort+" was closed") } // open port var open_port=(port,c)=>{ c.on("end", e=>{disconnect(server,c)}) c.on("error", e=>{log(e.errno, c.remotePort)}) if(ports[port]){ s=ports[port] s.clients.push(c) if(s.done)c.write("ok\r") return } log("open port...",port) var server=net.createServer() ports[port]=server server.done=false server.clients=[c] server.conns=new Set()// store used connection server.listen(port) server.on("connection", s=>{ console.log("new connection at", s.remoteAddress+":"+s.remotePort+">>:"+s.localPort) if(server.clients.length==0){ log(port+"'s client is null") s.end() return } s.client = server.clients.pop() s.client.partner = s server.conns.add(s.client) log("alloc",s.client.remoteAddress+":"+s.client.remotePort) s.client.on("data", d=>{ debug(">>u",d.toString("hex")) try{s.write(d)}catch(e){} }) s.on("data", d=>{debug(">>c",d.toString("hex"));s.client.write(d)}) s.on("end", e=>{disconnect(server,s)}) s.on("error", e=>{disconnect(server,s)}) }) server.on("error", e=>{ console.log(e.errno) for(var cli of server.clients){ cli.write("failed "+e.errno+"\r") cli.end() } ports[port]=undefined }) server.on("listening", e=>{ console.log("listen successful.",port) server.done=true for(var cli of server.clients){ log("notify ok!", cli.remoteAddress, cli.remotePort) cli.write("ok\r") } }) } var client_connect=c=>{ c.on("data", d=>{ try{ s=d.toString() segs=s.slice(0,-1).split(" ") if(segs.length!=4 || segs[0]!="NAGENT1.0"){ log("login failed",s) c.write("failed\r") c.end() return } }catch(e){ log("login exception:",e) c.write("except\r") c.end() return } log("login successful!",segs[1]) c.removeAllListeners() open_port(parseInt(segs[3]), c) }) log("new client at", c.remoteAddress, c.remotePort) } var server=net.createServer(client_connect) server.listen(server_port) log("service listen at", server_port)