Skip to content

Commit 97330bf

Browse files
author
刘河
committed
MUX optimization
1 parent f78e81b commit 97330bf

33 files changed

+744
-323
lines changed

README.md

+106-37
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务
2525
* [编译安装](#源码安装)
2626
* [release安装](#release安装)
2727
* [使用示例(以web主控模式为主)](#使用示例)
28-
* [统一准备工作](#统一准备工作)
28+
* [统一准备工作](#统一准备工作(必做))
2929
* [http|https域名解析](#域名解析)
3030
* [内网ssh连接即tcp隧道](#tcp隧道)
3131
* [内网dns解析即udp隧道](#udp隧道)
@@ -89,6 +89,10 @@ nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务
8989
* [URL路由](#URL路由)
9090
* [限制ip访问](#限制ip访问)
9191
* [客户端最大连接数限制](#客户端最大连接数)
92+
* [端口复用](#端口复用)
93+
* [环境变量渲染](#环境变量渲染)
94+
* [健康检查](#健康检查)
95+
9296
* [相关说明](#相关说明)
9397
* [流量统计](#流量统计)
9498
* [热更新支持](#热更新支持)
@@ -124,7 +128,7 @@ nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务
124128
## 使用示例
125129

126130
### 统一准备工作(必做)
127-
- 开启服务端,假设公网服务器ip为1.1.1.1,配置文件中`bridgePort`为8284,配置文件中`httpport`为8080
131+
- 开启服务端,假设公网服务器ip为1.1.1.1,配置文件中`bridgePort`为8284,配置文件中`web_port`为8080
128132
- 访问1.1.1.1:8080
129133
- 在客户端管理中创建一个客户端,记录下验证密钥
130134
- 内网客户端运行(windows使用cmd运行加.exe)
@@ -230,7 +234,7 @@ port=1000
230234
想通过访问机器1的2001端口---->访问到内网2机器的22端口
231235

232236
**使用步骤**
233-
-`nps.conf`中设置`serverIp``p2pPort`
237+
-`nps.conf`中设置`p2p_ip``p2p_port`
234238
- 在刚才刚才创建的客户端中添加一条p2p代理,并设置唯一密钥p2pssh
235239
- 在需要连接的机器上(即机器1)以配置文件模式启动客户端,内容如下
236240

@@ -291,23 +295,23 @@ port=2001
291295

292296
名称 | 含义
293297
---|---
294-
httpport | web管理端口
295-
password | web界面管理密码
296-
username | web界面管理账号
297-
bridgePort | 服务端客户端通信端口
298-
pemPath | ssl certFile绝对路径
299-
keyPath | ssl keyFile绝对路径
300-
httpsProxyPort | 域名代理https代理监听端口
301-
httpProxyPort | 域名代理http代理监听端口
302-
authKey|web api密钥
303-
bridgeType|客户端与服务端连接方式kcp或tcp
304-
publicVkey|客户端以配置文件模式启动时的密钥,设置为空表示关闭客户端配置文件连接模式
305-
ipLimit|是否限制ip访问,true或false或忽略
306-
flowStoreInterval|服务端流量数据持久化间隔,单位分钟,忽略表示不持久化
307-
logLevel|日志输出级别
308-
cryptKey | 获取服务端authKey时的aes加密密钥,16位
309-
serverIp| 服务端Ip,使用p2p模式必填
310-
p2pPort|p2p模式开启的udp端口
298+
web_port | web管理端口
299+
web_password | web界面管理密码
300+
web_username | web界面管理账号
301+
bridge_port | 服务端客户端通信端口
302+
pem_path | ssl certFile绝对路径
303+
key_path | ssl keyFile绝对路径
304+
https_proxy_port | 域名代理https代理监听端口
305+
http_proxy_port | 域名代理http代理监听端口
306+
auth_key|web api密钥
307+
bridge_type|客户端与服务端连接方式kcp或tcp
308+
public_vkey|客户端以配置文件模式启动时的密钥,设置为空表示关闭客户端配置文件连接模式
309+
ip_limit|是否限制ip访问,true或false或忽略
310+
flow_store_interval|服务端流量数据持久化间隔,单位分钟,忽略表示不持久化
311+
log_level|日志输出级别
312+
auth_crypt_key | 获取服务端authKey时的aes加密密钥,16位
313+
p2p_ip| 服务端Ip,使用p2p模式必填
314+
p2p_port|p2p模式开启的udp端口
311315

312316
### 使用https
313317

@@ -351,7 +355,7 @@ server {
351355
```
352356
### 关闭代理
353357

354-
如需关闭http代理可在配置文件中将httpProxyPort设置为空,如需关闭https代理可在配置文件中将httpsProxyPort设置为空
358+
如需关闭http代理可在配置文件中将http_proxy_port设置为空,如需关闭https代理可在配置文件中将https_proxy_port设置为空
355359

356360
### 将nps安装到系统
357361
如果需要长期并且方便的运行nps服务端,可将nps安装到操作系统中,可执行命令
@@ -371,17 +375,17 @@ nps.exe test|start|stop|restart|status
371375
```
372376

373377
### 流量数据持久化
374-
服务端支持将流量数据持久化,默认情况下是关闭的,如果有需求可以设置`nps.conf`中的`flowStoreInterval`参数,单位为分钟
378+
服务端支持将流量数据持久化,默认情况下是关闭的,如果有需求可以设置`nps.conf`中的`flow_store_interval`参数,单位为分钟
375379

376380
**注意:** nps不会持久化通过公钥连接的客户端
377381

378382
### 自定义客户端连接密钥
379383
web上可以自定义客户端连接的密钥,但是必须具有唯一性
380384
### 关闭公钥访问
381-
可以将`nps.conf`中的`publicVkey`设置为空或者删除
385+
可以将`nps.conf`中的`public_vkey`设置为空或者删除
382386

383387
### 关闭web管理
384-
可以将`nps.conf`中的`httpport`设置为空或者删除
388+
可以将`nps.conf`中的`web_port`设置为空或者删除
385389

386390
## 客户端
387391

@@ -616,17 +620,14 @@ LevelInformational->6 LevelDebug->7
616620
由于是内网穿透,内网客户端与服务端之间的隧道存在大量的数据交换,为节省流量,加快传输速度,由此本程序支持SNNAPY形式的压缩。
617621

618622

619-
- 所有模式均支持数据压缩,可以与加密同时使用
620-
- 开启此功能会增加cpu和内存消耗
623+
- 所有模式均支持数据压缩
621624
- 在web管理或客户端配置文件中设置
622625

623626

624627
### 加密传输
625628

626629
如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了ssh协议等,通过设置 配置文件,将服务端与客户端之间的通信内容加密传输,将会有效防止流量被拦截。
627-
628-
- 开启此功能会增加cpu和内存消耗
629-
- 在server端加上参数
630+
- nps使用tls加密,所以一定要保留conf目录下的密钥文件,同时也可以自行生成
630631
- 在web管理或客户端配置文件中设置
631632

632633

@@ -660,21 +661,21 @@ LevelInformational->6 LevelDebug->7
660661
支持客户端级带宽限制,带宽计算方式为入口和出口总和,权重均衡
661662

662663
### 负载均衡
663-
本代理支持域名解析模式的负载均衡,在web域名添加或者编辑中内网目标分行填写多个目标即可实现轮训级别的负载均衡
664+
本代理支持域名解析模式和tcp代理的负载均衡,在web域名添加或者编辑中内网目标分行填写多个目标即可实现轮训级别的负载均衡
664665

665666
### 端口白名单
666-
为了防止服务端上的端口被滥用,可在nps.conf中配置allowPorts限制可开启的端口,忽略或者不填表示端口不受限制,格式:
667+
为了防止服务端上的端口被滥用,可在nps.conf中配置allow_ports限制可开启的端口,忽略或者不填表示端口不受限制,格式:
667668

668669
```ini
669-
allowPorts=9001-9009,10001,11000-12000
670+
allow_ports=9001-9009,10001,11000-12000
670671
```
671672

672673
### 端口范围映射
673674
当客户端以配置文件的方式启动时,可以将本地的端口进行范围映射,仅支持tcp和udp模式,例如:
674675

675676
```ini
676677
[tcp]
677-
mode=tcpServer
678+
mode=tcp
678679
port=9001-9009,10001,11000-12000
679680
target=8001-8009,10002,13000-14000
680681
```
@@ -683,7 +684,7 @@ target=8001-8009,10002,13000-14000
683684
### 端口范围映射到其他机器
684685
```ini
685686
[tcp]
686-
mode=tcpServer
687+
mode=tcp
687688
port=9001-9009,10001,11000-12000
688689
target=8001-8009,10002,13000-14000
689690
targetAddr=10.1.50.2
@@ -699,8 +700,8 @@ targetAddr=10.1.50.2
699700
```
700701
### KCP协议支持
701702

702-
KCP 是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,在弱网环境下对性能能有一定的提升。可在nps.conf中修改bridgeType为kcp
703-
,设置后本代理将开启udp端口(bridgePort
703+
KCP 是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,在弱网环境下对性能能有一定的提升。可在nps.conf中修改`bridge_type`为kcp
704+
,设置后本代理将开启udp端口(`bridge_port`
704705

705706
注意:当服务端为kcp时,客户端连接时也需要使用相同配置,无配置文件模式加上参数type=kcp,配置文件模式在配置文件中设置tp=kcp
706707

@@ -725,7 +726,7 @@ location=/static
725726
### 限制ip访问
726727
如果将一些危险性高的端口例如ssh端口暴露在公网上,可能会带来一些风险,本代理支持限制ip访问。
727728

728-
**使用方法:** 在配置文件nps.conf中设置ipLimit=true,设置后仅通过注册的ip方可访问。
729+
**使用方法:** 在配置文件nps.conf中设置`ip_limit`=true,设置后仅通过注册的ip方可访问。
729730

730731
**ip注册**: 在需要访问的机器上,运行客户端
731732

@@ -739,6 +740,74 @@ time为有效小时数,例如time=2,在当前时间后的两小时内,本
739740
### 客户端最大连接数
740741
为防止恶意大量长连接,影响服务端程序的稳定性,可以在web或客户端配置文件中为每个客户端设置最大连接数。该功能针对`socks5``http正向代理``域名代理``tcp代理``私密代理`生效。
741742

743+
### 端口复用
744+
在一些严格的网络环境中,对端口的个数等限制较大,nps支持强大端口复用功能。将`bridge_port``http_proxy_port``https_proxy_port``web_port`都设置为同一端口,也能正常使用。
745+
746+
- 使用时将需要复用的端口设置为与`bridge_port`一致即可,将自动识别。
747+
- 如需将web管理的端口也复用,需要配置`web_host`也就是一个二级域名以便区分
748+
749+
### 环境变量渲染
750+
npc支持环境变量渲染以适应在某些特殊场景下的要求。
751+
752+
**在无配置文件启动模式下:**
753+
设置环境变量
754+
```
755+
export NPC_SERVER_ADDR=1.1.1.1:8284
756+
export NPC_SERVER_VKEY=xxxxx
757+
```
758+
直接执行./npc即可运行
759+
760+
**在配置文件启动模式下:**
761+
```ini
762+
[common]
763+
server={{.NPC_SERVER_ADDR}}
764+
tp=tcp
765+
vkey={{.NPC_SERVER_VKEY}}
766+
auto_reconnection=true
767+
[web]
768+
host={{.NPC_WEB_HOST}}
769+
target={{.NPC_WEB_TARGET}}
770+
```
771+
在配置文件中填入相应的环境变量名称,npc将自动进行渲染配置文件替换环境变量
772+
773+
### 健康检查
774+
775+
当客户端以配置文件模式启动时,支持多节点的健康检查。配置示例如下
776+
777+
```ini
778+
[health_check_test1]
779+
health_check_timeout=1
780+
health_check_max_failed=3
781+
health_check_interval=1
782+
health_http_url=/
783+
health_check_type=http
784+
health_check_target=127.0.0.1:8083,127.0.0.1:8082
785+
786+
[health_check_test2]
787+
health_check_timeout=1
788+
health_check_max_failed=3
789+
health_check_interval=1
790+
health_check_type=tcp
791+
health_check_target=127.0.0.1:8083,127.0.0.1:8082
792+
```
793+
**health关键词必须在开头存在**
794+
795+
第一种是http模式,也就是以get的方式请求目标+url,返回状态码为200表示成功
796+
797+
第一种是tcp模式,也就是以tcp的方式与目标建立连接,能成功建立连接表示成功
798+
799+
如果失败次数超过`health_check_max_failed`,nps则会移除该npc下的所有该目标,如果失败后目标重新上线,nps将自动将目标重新加入。
800+
项 | 含义
801+
---|---
802+
health_check_timeout | 健康检查超时时间
803+
health_check_max_failed | 健康检查允许失败次数
804+
health_check_interval | 健康检查间隔
805+
health_check_type | 健康检查类型
806+
health_check_target | 健康检查目标,多个以逗号(,)分隔
807+
health_check_type | 健康检查类型
808+
health_http_url | 健康检查url,仅http模式适用
809+
810+
742811
## 相关说明
743812

744813
### 获取用户真实ip

bridge/bridge.go

+64-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/cnlh/nps/vender/github.com/xtaci/kcp"
1818
"net"
1919
"strconv"
20+
"strings"
2021
"sync"
2122
"time"
2223
)
@@ -42,6 +43,7 @@ type Bridge struct {
4243
Client map[int]*Client
4344
tunnelType string //bridge type kcp or tcp
4445
OpenTask chan *file.Tunnel
46+
CloseTask chan *file.Tunnel
4547
CloseClient chan int
4648
SecretChan chan *conn.Secret
4749
clientLock sync.RWMutex
@@ -57,6 +59,7 @@ func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList map[int
5759
t.Client = make(map[int]*Client)
5860
t.tunnelType = tunnelType
5961
t.OpenTask = make(chan *file.Tunnel)
62+
t.CloseTask = make(chan *file.Tunnel)
6063
t.CloseClient = make(chan int)
6164
t.Register = make(map[string]time.Time)
6265
t.ipVerify = ipVerify
@@ -106,6 +109,62 @@ func (s *Bridge) StartTunnel() error {
106109
return nil
107110
}
108111

112+
//get health information form client
113+
func (s *Bridge) GetHealthFromClient(id int, c *conn.Conn) {
114+
for {
115+
if info, status, err := c.GetHealthInfo(); err != nil {
116+
logs.Error(err)
117+
break
118+
} else if !status { //the status is true , return target to the targetArr
119+
for _, v := range file.GetCsvDb().Tasks {
120+
if v.Client.Id == id && v.Mode == "tcp" && strings.Contains(v.Target, info) {
121+
v.Lock()
122+
if v.TargetArr == nil || (len(v.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) {
123+
v.TargetArr = common.TrimArr(strings.Split(v.Target, "\n"))
124+
}
125+
v.TargetArr = common.RemoveArrVal(v.TargetArr, info)
126+
if v.HealthRemoveArr == nil {
127+
v.HealthRemoveArr = make([]string, 0)
128+
}
129+
v.HealthRemoveArr = append(v.HealthRemoveArr, info)
130+
v.Unlock()
131+
}
132+
}
133+
for _, v := range file.GetCsvDb().Hosts {
134+
if v.Client.Id == id && strings.Contains(v.Target, info) {
135+
v.Lock()
136+
if v.TargetArr == nil || (len(v.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) {
137+
v.TargetArr = common.TrimArr(strings.Split(v.Target, "\n"))
138+
}
139+
v.TargetArr = common.RemoveArrVal(v.TargetArr, info)
140+
if v.HealthRemoveArr == nil {
141+
v.HealthRemoveArr = make([]string, 0)
142+
}
143+
v.HealthRemoveArr = append(v.HealthRemoveArr, info)
144+
v.Unlock()
145+
}
146+
}
147+
} else { //the status is false,remove target from the targetArr
148+
for _, v := range file.GetCsvDb().Tasks {
149+
if v.Client.Id == id && v.Mode == "tcp" && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.TargetArr, info) {
150+
v.Lock()
151+
v.TargetArr = append(v.TargetArr, info)
152+
v.HealthRemoveArr = common.RemoveArrVal(v.HealthRemoveArr, info)
153+
v.Unlock()
154+
}
155+
}
156+
for _, v := range file.GetCsvDb().Hosts {
157+
if v.Client.Id == id && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.TargetArr, info) {
158+
v.Lock()
159+
v.TargetArr = append(v.TargetArr, info)
160+
v.HealthRemoveArr = common.RemoveArrVal(v.HealthRemoveArr, info)
161+
v.Unlock()
162+
}
163+
}
164+
}
165+
}
166+
}
167+
109168
//验证失败,返回错误验证flag,并且关闭连接
110169
func (s *Bridge) verifyError(c *conn.Conn) {
111170
c.Write([]byte(common.VERIFY_EER))
@@ -187,6 +246,7 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
187246
s.Client[id] = NewClient(nil, nil, c)
188247
s.clientLock.Unlock()
189248
}
249+
go s.GetHealthFromClient(id, c)
190250
logs.Info("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr())
191251
case common.WORK_CHAN:
192252
s.clientLock.Lock()
@@ -264,7 +324,7 @@ func (s *Bridge) register(c *conn.Conn) {
264324
var hour int32
265325
if err := binary.Read(c, binary.LittleEndian, &hour); err == nil {
266326
s.registerLock.Lock()
267-
s.Register[common.GetIpByAddr(c.Conn.RemoteAddr().String())] = time.Now().Add(time.Hour * time.Duration(hour))
327+
s.Register[common.GetIpByAddr(c.Conn.RemoteAddr().String())] = time.Now().Add(time.Minute * time.Duration(hour))
268328
s.registerLock.Unlock()
269329
}
270330
}
@@ -282,11 +342,11 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string, t
282342
s.registerLock.Unlock()
283343
return nil, errors.New(fmt.Sprintf("The ip %s is not in the validation list", ip))
284344
} else {
345+
s.registerLock.Unlock()
285346
if !v.After(time.Now()) {
286347
return nil, errors.New(fmt.Sprintf("The validity of the ip %s has expired", ip))
287348
}
288349
}
289-
s.registerLock.Unlock()
290350
}
291351
var tunnel *mux.Mux
292352
if t != nil && t.Mode == "file" {
@@ -311,7 +371,6 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string, t
311371
logs.Info("new connect error ,the target %s refuse to connect", link.Host)
312372
return
313373
}
314-
315374
} else {
316375
s.clientLock.Unlock()
317376
err = errors.New(fmt.Sprintf("the client %d is not connect", clientId))
@@ -366,6 +425,7 @@ loop:
366425
if err != nil {
367426
break loop
368427
}
428+
file.GetCsvDb().Lock()
369429
for _, v := range file.GetCsvDb().Hosts {
370430
if v.Client.Id == id {
371431
str += v.Remark + common.CONN_DATA_SEQ
@@ -376,6 +436,7 @@ loop:
376436
str += v.Remark + common.CONN_DATA_SEQ
377437
}
378438
}
439+
file.GetCsvDb().Unlock()
379440
binary.Write(c, binary.LittleEndian, int32(len([]byte(str))))
380441
binary.Write(c, binary.LittleEndian, []byte(str))
381442
}

0 commit comments

Comments
 (0)