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. redis增删查改数据Util

    目录 (1)需要导入的包 (2)redis配置文件 (3)RedisUtil类 (1)需要导入的包 <dependency> <groupId>org.springframew ...

  2. GIS之家demo源代码咨询

    GIS之家demo源代码咨询收费服务(希望对 webgis 新人有所帮助) GIS之家QQ群(采取QQ群入群收费模式): GIS之家001:296438295 需要入群的giser们,入群之前联系GI ...

  3. Docker 创建 Crucible4.6.1 以及与 Crowd3.3.2 实现 SSO 单点登录

    目录 目录 1.介绍 1.1.什么是 Crucible? 2.Crucible 的官网在哪里? 3.如何下载安装? 4.对 Crucible 进行配置 4.1.破解 Crucible 第一步 4.2. ...

  4. mssql sqlserver 不固定行转列数据(动态列)

    转自:http://www.maomao365.com/?p=5471 摘要: 下文主要讲述动态行列转换语句,列名会根据行数据的不同, 动态的发生变化 ------------------------ ...

  5. 数据库常用操作SQL语句

    禁用触发器: alter table tb disable trigger tir_name 启用触发器: alter table tb enable trigger tir_name

  6. netdom join 错误:指定的域不存在,或无法联系。

    环境: 域控制器:feiquan.com  IP:192.168.1.132 客户端:\\win-quan IP:192.168.1.129(动态) 域控制器可以和客户端ping通,但客户端加入域时就 ...

  7. Vue2.x源码学习笔记-Vue源码调试

    如果我们不用单文件组件开发,一般直接<script src="dist/vue.js">引入开发版vue.js这种情况下debug也是很方便的,只不过vue.js文件代 ...

  8. 后端MVC和前端MVVC关系详解

    MVC 是后端的分层开发概念: MVVM是前端视图层的概念,主要关注于 视图层分离,也就是说:MVVM把前端的视图层,分为了 三部分 Model, View , VM ViewModel

  9. PS抠图简单粗暴2种方法

    1.矩形框住要抠的→ Ctrl+j →选中图层 →矩形框住 →图像裁剪另存为 2.钢笔工具框住要抠的→ Ctrl+Enter→ Ctrl+j→.钢笔框住→图像裁剪另存为

  10. AI - TensorFlow - 示例01:基本分类

    基本分类 基本分类(Basic classification):https://www.tensorflow.org/tutorials/keras/basic_classification Fash ...