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)