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)

TCP点对点转发的实现与原理(nodejs)的更多相关文章

  1. TCP点对点穿透探索--失败

    TCP点对点穿透探索 点对点穿透是穿透什么 点对点穿透,需要实现的是对NAT的穿透.想实现NAT的穿透,当然要先了解NAT到底是什么,以及NAT是用来干什么的.NAT全称Network Address ...

  2. Nginx配置TCP请求转发

    Nginx配置TCP请求转发 1.TCP请求转发基于stream在1.9版本前,需要单独编译安装该组建: # 依赖服务 [root@baolin conf]#yum -y install pcre-d ...

  3. TCP端口转发(centos7)

    =============================================== 2019/2/14_第1次修改                       ccb_warlock == ...

  4. 使用 ssh -R 建立反向/远程TCP端口转发代理

    转自:https://yq.aliyun.com/articles/8469 ssh是一个非常棒的工具, 不但能建立动态转发, 例如chrome的Switchy插件用到的就是这个技术.http://b ...

  5. Haproxy TCP数据转发

    在实际项目中需要用到haproxy做TCP转发,下面主要针对haproxy的安装及TCP数据转发配置进行说明 一.安装Haproxy (1)编译安装Haproxy mkdir -p /data01/h ...

  6. windows操作系统自带的TCP端口转发

    假定需要通过192.168.1.8的14941端口连接192.168.1.118的1494端口,则需要在192.168.1.8主机的命令行输入如下语句netsh  interface ipv6 ins ...

  7. TCP/IP协议族——IP工作原理及实例具体解释(上)

     IP协议具体解释 本文主要介绍了IP服务特点,头部结构,IP分片知识,并用tcpdump抓取数据包.来观察IP数据报传送过程中IP的格式,以及分片的过程. IP头部信息:IP头部信息出如今每一个 ...

  8. tcp/iP协议族——IP工作原理及实例具体解释(下)

     IP协议具体解释 上一篇文章文章主要介绍了IP服务的特点,IPv4头部结构IP分片.并用tcpdump抓取数据包,来观察IP数据报传送过程中IP的格式,以及分片的过程.本文主要介绍IP路由,IP ...

  9. TCP面试题之滑动窗口原理

    TCP 滑动窗口 作用: 1. 提供TCP可靠性:对发送的数据进行确认 2. 流量控制:窗口大小随链路变化 一.TCP窗口机制 TCP中窗口大小是指tcp协议一次传输多少个数据.因为TCP是一个面向连 ...

随机推荐

  1. javascript入门篇(三)

    字符串属性和方法 原始值字符串,如'liang', 没有属性和方法(因为他们不是对象). 原始值可以使用 JavaScript 的属性和方法,因为 JavaScript 在执行方法和属性时可以把原始值 ...

  2. Dotnet全平台下APM-Trace探索

    背景 随着支撑的内部业务系统越来越多,向着服务化架构进化,在整个迭代过程中,会逐渐暴露出以下问题. 传统依赖于应用服务器日志等手段的排除故障原因的复杂度越来越高,传统的监控服务已经无法满足需求. 终端 ...

  3. CSS 火焰?不在话下

    正文从下面开始. 今天的小技巧是使用纯 CSS 生成火焰,逼真一点的火焰. 嗯,长什么样子?在 CodePen 上输入关键字 CSS Fire,能找到这样的: 或者这样的: 我们希望,仅仅使用 CSS ...

  4. SQL Server死锁中的会话隔离级别为序列化(Serializable)实验测试

    最近在分析SQL Server的死锁时,发现一个比较有意思的现象,发现死锁当中一个会话的隔离级别为序列化(Serializable),这个是让人比较奇怪的地方,我们知道SQL Server数据库的默认 ...

  5. Django【部署】uwsgi+nginx

    uwsgi 遵循wsgi协议的web服务器 uwsgi的安装 pip install uwsgi uwsgi的配置 项目部署时,需要把settings.py文件夹下的: DEBUG = FALSE A ...

  6. Sitecore® 8.2 Professional Developer考试心得

    因工作原因入了Sitecore的坑.. 不了解Sitecore认证考试的同学请移步: http://www.cnblogs.com/edisonchou/archive/2018/08/17/9488 ...

  7. v-cloak的用法和注意事项

    v-cloak是前端框架vue.js中的一个方法,作用是为了防止在页面加载时先出现变量名闪烁的情况,造成不好的用户体验, 例如:{{ v.name}} (闪一下)=> 张三 用法:html中:& ...

  8. 03 JVM 从入门到实战 | 简述垃圾回收算法

    引言 之前我们学习了 JVM 基本介绍 以及 什么样的对象需要被 GC ,今天就来学习一下 JVM 在判断出一个对象需要被 GC 会采用何种方式进行 GC.在学习 JVM 如何进行垃圾回收方法时,发现 ...

  9. 卷积神经网络(Convolutional Neural Network,CNN)

    全连接神经网络(Fully connected neural network)处理图像最大的问题在于全连接层的参数太多.参数增多除了导致计算速度减慢,还很容易导致过拟合问题.所以需要一个更合理的神经网 ...

  10. Flink从入门到放弃(入门篇4) DataStreamAPI

    戳更多文章: 1-Flink入门 2-本地环境搭建&构建第一个Flink应用 3-DataSet API 4-DataSteam API 5-集群部署 6-分布式缓存 7-重启策略 8-Fli ...