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)
TCP点对点转发的实现与原理(nodejs)的更多相关文章
- TCP点对点穿透探索--失败
TCP点对点穿透探索 点对点穿透是穿透什么 点对点穿透,需要实现的是对NAT的穿透.想实现NAT的穿透,当然要先了解NAT到底是什么,以及NAT是用来干什么的.NAT全称Network Address ...
- Nginx配置TCP请求转发
Nginx配置TCP请求转发 1.TCP请求转发基于stream在1.9版本前,需要单独编译安装该组建: # 依赖服务 [root@baolin conf]#yum -y install pcre-d ...
- TCP端口转发(centos7)
=============================================== 2019/2/14_第1次修改 ccb_warlock == ...
- 使用 ssh -R 建立反向/远程TCP端口转发代理
转自:https://yq.aliyun.com/articles/8469 ssh是一个非常棒的工具, 不但能建立动态转发, 例如chrome的Switchy插件用到的就是这个技术.http://b ...
- Haproxy TCP数据转发
在实际项目中需要用到haproxy做TCP转发,下面主要针对haproxy的安装及TCP数据转发配置进行说明 一.安装Haproxy (1)编译安装Haproxy mkdir -p /data01/h ...
- windows操作系统自带的TCP端口转发
假定需要通过192.168.1.8的14941端口连接192.168.1.118的1494端口,则需要在192.168.1.8主机的命令行输入如下语句netsh interface ipv6 ins ...
- TCP/IP协议族——IP工作原理及实例具体解释(上)
IP协议具体解释 本文主要介绍了IP服务特点,头部结构,IP分片知识,并用tcpdump抓取数据包.来观察IP数据报传送过程中IP的格式,以及分片的过程. IP头部信息:IP头部信息出如今每一个 ...
- tcp/iP协议族——IP工作原理及实例具体解释(下)
IP协议具体解释 上一篇文章文章主要介绍了IP服务的特点,IPv4头部结构IP分片.并用tcpdump抓取数据包,来观察IP数据报传送过程中IP的格式,以及分片的过程.本文主要介绍IP路由,IP ...
- TCP面试题之滑动窗口原理
TCP 滑动窗口 作用: 1. 提供TCP可靠性:对发送的数据进行确认 2. 流量控制:窗口大小随链路变化 一.TCP窗口机制 TCP中窗口大小是指tcp协议一次传输多少个数据.因为TCP是一个面向连 ...
随机推荐
- 安装Mysql时端口号3306被占用,解决方法
当我们在卸载mysql数据库重新安装的时候,会出现端口号3306被占用的情况 有两种解决方案: 一:可以不使用3306端口,也可以换成别的端口,如3307,3308等等 二:可以打开命令窗口 1.wi ...
- .Net 反射学习
Why?为什么使用反射 MVC ORM EF 都是用的反射.反射可以让程序的扩展性,灵活性得到加强.一起即可动态创建 what 反射原理 动态加载类库 ,先添加引用类库,或者复制debug里 ...
- MVC设计模式思想及简单实现
一.什么是MVC MVC即Model-View-Controller(模型-视图-控制器)是一种软件设计模式,最早出现在Smalltalk语言中,后被Sun公司推荐为Java EE平台的设计模式. M ...
- Python函数的定义、参数传入与函数的调用
作为计算机代码的一种抽象方式,函数在Python中扮演了极为重要的角色.今天给大家介绍Python函数的定义.参数的传入以及调用方式.其中函数参数的传入方式为本节重点内容.Python函数的参数形式包 ...
- css中的position(定位)
一.position语法与结构 position语法: position : static absolute relative position参数:static : 无特殊定位,对象遵循HTML定位 ...
- 解决ruby安装后无法添加淘宝gem源------------学习记录
使用sass ,需要安装ruby,会建议移除gem源,添加淘宝的gem源,但是淘宝的镜像源已经停止维护啦!!用https://gems.ruby-china.com 代替即可. 操作如下: 1)删除原 ...
- gitbook 入门教程之使用 gitbook-cli 开发电子书
gitbook 生成电子书主要有三种方式: gitbook-cli 命令行操作,简洁高效,适合从事软件开发的相关人员. gitbook-editor 编辑器操作,可视化编辑,适合无编程经验的文学创作者 ...
- Exp4恶意代码分析 20164312 马孝涛
1.实践目标 1.1是监控你自己系统的运行状态,看有没有可疑的程序在运行. 1.2是分析一个恶意软件,就分析Exp2或Exp3中生成后门软件:分析工具尽量使用原生指令或sysinternals,sy ...
- 【Teradata TTU】Windows TTU安装工具列表
Version Display Name-------------------------------------------------------------------------------- ...
- Go基础(3)
demo1: package main import "fmt" func print() { for i := 1; i < 10; i++ { for j := 1; j ...