瀏覽代碼

New functions

刘河 6 年之前
父節點
當前提交
db43405237

+ 8 - 0
.idea/nps.iml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 139 - 39
README.md

@@ -41,10 +41,14 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
        * [udp隧道](#udp隧道)
        * [socks5代理](#socks5代理)
        * [http正向代理](#http正向代理)
+       * [私密代理](#私密代理)
     * [使用https](#使用https)
     * [与nginx配合](#与nginx配合)
     * [关闭http|https代理](#关闭代理)
     * [将nps安装到系统](#将nps安装到系统)
+    * [web流量数据持久化](#流量数据持久化)
+    * [自定义客户端连接密钥](#自定义客户端连接密钥)
+    * [关闭公钥访问](#关闭公钥访问)
 * [客户端](#客户端)
     * [客户端启动](#客户端启动)
         * [无配置文件模式](#无配置文件模式)
@@ -54,12 +58,14 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
         * [域名代理](#域名代理)
         * [tcp隧道模式](#tcp隧道模式)
         * [udp隧道模式](#udp隧道模式)
-        * [http代理模式](#http代理模式)
-        * [socks5模式](#socks5模式)
+        * [http正向代理模式](#http代理模式)
+        * [socks5模式](#socks5代理模式)
+        * [私密代理](#私密代理)
     * [断线重连](#断线重连)
     * [状态检查](#状态检查)
     * [重载配置文件](#重载配置文件)
     * [通过代理连接nps](#通过代理连接nps)
+    * [日志输出级别](#日志输出级别)
 
 * [相关功能](#相关功能)
    * [数据压缩支持](#数据压缩支持)
@@ -73,16 +79,19 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
    * [负载均衡](#负载均衡)
    * [端口白名单](#端口白名单)
    * [端口范围映射](#端口范围映射)
+   * [端口范围映射到其他机器](#端口范围映射到其他机器)
    * [守护进程](#守护进程)
    * [KCP协议支持](#KCP协议支持)
    * [域名泛解析](#域名泛解析)
    * [URL路由](#URL路由)
    * [限制ip访问](#限制ip访问)
+   * [客户端最大连接数限制](#客户端最大连接数)
 * [相关说明](#相关说明)
    * [流量统计](#流量统计)
    * [热更新支持](#热更新支持)
    * [获取用户真实ip](#获取用户真实ip)
    * [客户端地址显示](#客户端地址显示)
+   * [客户端与服务端版本对比](#客户端与服务端版本对比)
 * [简单的性能测试](#简单的性能测试)
    * [qps](#qps)
    * [速度测试](#速度测试)
@@ -103,7 +112,7 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
 
 ### 源码安装
 - 安装源码
-> go get github.com/cnlh/nps
+> go get -u github.com/cnlh/nps...
 - 编译
 > go build cmd/nps/nps.go
 
@@ -123,12 +132,12 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
 
 
 #### 服务端测试
-```
+```shell
  ./nps test
 ```
 如有错误请及时修改配置文件,无错误可继续进行下去
 #### 服务端启动
-```
+```shell
  ./nps start
 ```
 如果无需daemon运行,去掉start即可
@@ -141,12 +150,12 @@ go语言编写,无第三方依赖,各个平台都已经编译在release中
 
 #### 服务端停止或重启
 如果是daemon启动
-```
+```shell
  ./nps stop|restart
 ```
 
 ### 服务端配置文件
-- /conf/app.conf
+- /conf/nps.conf
 
 名称 | 含义
 ---|---
@@ -161,6 +170,8 @@ authip|web api免验证IP地址
 bridgeType|客户端与服务端连接方式kcp或tcp
 publicVkey|客户端以配置文件模式启动时的密钥,设置为空表示关闭客户端配置文件连接模式
 ipLimit|是否限制ip访问,true或false或忽略
+flowStoreInterval|服务端流量数据持久化间隔,单位分钟,忽略表示不持久化
+logLevel|日志输出级别
 
 ### 详细说明
 
@@ -172,18 +183,18 @@ ipLimit|是否限制ip访问,true或false或忽略
 - 有一个域名proxy.com,有一台公网机器ip为1.1.1.1
 - 两个内网开发站点127.0.0.1:81,127.0.0.1:82
 - 想通过(http|https://)a.proxy.com访问127.0.0.1:81,通过(http|https://)b.proxy.com访问127.0.0.1:82
-- 例如配置文件中tcpport为8284
+- 例如配置文件中bridgePort为8284
 
 **使用步骤**
 - 将*.proxy.com解析到公网服务器1.1.1.1
 - 在客户端管理中创建一个客户端,记录下验证密钥
-- 点击该客户端的域名管理,添加两条规则规则:1、域名:a.proxy.com,内网目标:127.0.0.1:81,2、域名:b.proxy.com,内网目标:127.0.0.1:82
+- 点击该客户端的域名管理,添加两条规则规则:1、域名:`a.proxy.com`,内网目标:`127.0.0.1:81`,2、域名:`b.proxy.com`,内网目标:`127.0.0.1:82`
 - 内网客户端运行
 
-```
+```shell
 ./npc -server=1.1.1.1:8284 -vkey=客户端的密钥
 ```
-现在访问(http|https://)a.proxy.com,b.proxy.com即可成功
+现在访问(http|https://)`a.proxy.com``b.proxy.com`即可成功
 
 **https:** 如需使用https请在配置文件中将https端口设置为443,和将对应的证书文件路径添加到配置文件中,上面添加的这条记录将会把http、https都转发到内网目标
 
@@ -193,16 +204,16 @@ ipLimit|是否限制ip访问,true或false或忽略
 **适用范围:**  ssh、远程桌面等tcp连接场景
 
 **假设场景:**
- 想通过访问公网服务器1.1.1.1的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接,例如配置文件中tcpport为8284
+ 想通过访问公网服务器1.1.1.1的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接,例如配置文件中bridgePort为8284
 
 **使用步骤**
 - 在客户端管理中创建一个客户端,记录下验证密钥
 - -内网客户端运行
-```
+```shell
 ./npc -server=1.1.1.1:8284 -vkey=客户端的密钥
 ```
 - 在该客户端隧道管理中添加一条tcp隧道,填写监听的端口(8001)、内网目标ip和目标端口(10.1.50.101:22),选择压缩方式,保存。
-- 访问公网服务器ip(127.0.0.1),填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:ssh -p 8001 root@127.0.0.1
+- 访问公网服务器ip(127.0.0.1),填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:`ssh -p 8001 root@127.0.0.1`
 
 #### udp隧道
 
@@ -211,12 +222,12 @@ ipLimit|是否限制ip访问,true或false或忽略
 **适用范围:**  内网dns解析等udp连接场景
 
 **假设场景:**
-内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为1.1.1.1,例如配置文件中tcpport为8284
+内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为1.1.1.1,例如配置文件中bridgePort为8284
 
 **使用步骤**
 - 在客户端管理中创建一个客户端,记录下验证密钥
 - -内网客户端运行
-```
+```shell
 ./npc -server=1.1.1.1:8284 -vkey=客户端的密钥
 ```
 - 在该客户端的隧道管理中添加一条udp隧道,填写监听的端口(53)、内网目标ip和目标端口(10.1.50.102:53),选择压缩方式,保存。
@@ -228,12 +239,12 @@ ipLimit|是否限制ip访问,true或false或忽略
 **适用范围:**  在外网环境下如同使用vpn一样访问内网设备或者资源
 
 **假设场景:**
-想将公网服务器1.1.1.1的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果,例如配置文件中tcpport为8284
+想将公网服务器1.1.1.1的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果,例如配置文件中bridgePort为8284
 
 **使用步骤**
 - 在客户端管理中创建一个客户端,记录下验证密钥
 - -内网客户端运行
-```
+```shell
 ./npc -server=1.1.1.1:8284 -vkey=客户端的密钥
 ```
 - 在该客户端隧道管理中添加一条socks5代理,填写监听的端口(8003),验证用户名和密码自行选择(建议先不填,部分客户端不支持,proxifer支持),选择压缩方式,保存。
@@ -244,25 +255,55 @@ ipLimit|是否限制ip访问,true或false或忽略
 **适用范围:**  在外网环境下使用http代理访问内网站点
 
 **假设场景:**
-想将公网服务器1.1.1.1的8004端口作为http代理,访问内网网站,例如配置文件中tcpport为8284
+想将公网服务器1.1.1.1的8004端口作为http代理,访问内网网站,例如配置文件中bridgePort为8284
 
 **使用步骤**
 - 在客户端管理中创建一个客户端,记录下验证密钥
 - -内网客户端运行
-```
+```shell
 ./npc -server=1.1.1.1:8284 -vkey=客户端的密钥
 ```
 - 在该客户端隧道管理中添加一条http代理,填写监听的端口(8004),选择压缩方式,保存。
 - 在外网环境的本机配置http代理,ip为公网服务器ip(127.0.0.1),端口为填写的监听端口(8004),即可访问了
 
+#### 私密代理
+
+**适用范围:**  无需占用多余的端口、安全性要求较高可以防止其他人连接的TCP服务,例如ssh。
+
+**假设场景:**
+无需新增多的端将映射内网服务器10.1.50.2的22端口
+
+**使用步骤**
+- 在客户端管理中创建一个客户端,记录下验证密钥
+- 内网客户端运行
+```
+./npc -server=1.1.1.1:8284 -vkey=客户端的密钥
+```
+- 添加一条私密代理,并设置唯一密钥和内网目标10.1.50.2:22
+- 在需要连接ssh的机器上以配置文件模式启动客户端,内容如下
+
+```ini
+[common]
+server=127.0.0.1:8284
+tp=tcp
+vkey=123
+[secret_ssh]
+password=1111
+port=1000
+```
+**注意:** secret前缀必须存在,password为web管理上添加的唯一密钥
+
+假设用户名为root,现在执行`ssh -p 1000 root@127.0.0.1`即可访问ssh
+
+
 
 ### 使用https
 
 在配置文件中将httpsProxyPort设置为443或者其他你想配置的端口,和将对应的证书文件路径添加到配置文件中,然后就和http代理一样了,例如
 
-- 需要访问https://a.proxy.com 对应内网127.0.0.1:80
+- 需要访问`https://a.proxy.com` 对应内网`127.0.0.1:80`
 
-- 在域名代理中添加a.proxy.com 内网目标127.0.0.1:80 即可将所有到达本代理的http(s)请求都转发到127.0.0.1:80
+- 在域名代理中添加`a.proxy.com` 内网目标`127.0.0.1:80` 即可将所有到达本代理的http(s)请求都转发到127.0.0.1:80
 
 ### 与nginx配合
 
@@ -317,6 +358,16 @@ nps test|start|stop|restart|status
 nps.exe test|start|stop|restart|status
 ```
 
+### 流量数据持久化
+服务端支持将流量数据持久化,默认情况下是关闭的,如果有需求可以设置`nps.conf`中的`flowStoreInterval`参数,单位为分钟
+
+**注意:** nps不会持久化通过公钥连接的客户端
+
+### 自定义客户端连接密钥
+web上可以自定义客户端连接的密钥,但是必须具有唯一性
+### 关闭公钥访问
+可以将`nps.conf`中的`publicVkey`设置为空或者删除 
+
 ## 客户端
 
 ### 客户端启动
@@ -333,7 +384,7 @@ nps.exe test|start|stop|restart|status
 #### 配置文件说明
 [示例配置文件](https://github.com/cnlh/nps/tree/master/conf/npc.conf)
 ##### 全局配置
-```
+```ini
 [common]
 server=127.0.0.1:8284
 tp=tcp
@@ -342,6 +393,10 @@ username=111
 password=222
 compress=snappy
 crypt=true
+rate_limit=10000
+flow_limit=100
+remark=test
+max_conn=10
 ```
 项 | 含义
 ---|---
@@ -352,9 +407,13 @@ username|socks5或http(s)密码保护用户名(可忽略)
 username|socks5或http(s)密码保护密码(可忽略)
 compress|是否压缩传输(snappy或空或忽略)
 crypt|是否加密传输(true或false或忽略)
+rate_limit|速度限制,可忽略
+flow_limit|流量限制,可忽略
+remark|客户端备注,可忽略
+max_conn|最大连接数,可忽略
 ##### 域名代理
 
-```
+```ini
 [web1]
 host=a.o.com
 target=127.0.0.1:8080,127.0.0.1:8082
@@ -371,7 +430,7 @@ header_xxx|请求header修改或添加,header_proxy表示添加header proxy:np
 
 ##### tcp隧道模式
 
-```
+```ini
 [tcp]
 mode=tcpServer
 target=127.0.0.1:8080
@@ -385,7 +444,7 @@ target|内网目标
 
 ##### udp隧道模式
 
-```
+```ini
 [udp]
 mode=udpServer
 target=127.0.0.1:8080
@@ -398,7 +457,7 @@ port | 在服务端的代理端口
 target|内网目标
 ##### http代理模式
 
-```
+```ini
 [http]
 mode=httpProxyServer
 port=9003
@@ -409,7 +468,7 @@ mode | httpProxyServer
 port | 在服务端的代理端口
 ##### socks5代理模式
 
-```
+```ini
 [socks5]
 mode=socks5Server
 port=9004
@@ -418,9 +477,22 @@ port=9004
 ---|---
 mode | socks5Server
 port | 在服务端的代理端口
+##### 私密代理模式
 
-#### 断线重连
+```ini
+[secret_ssh]
+mode=secretServer
+password=ssh2
+target=10.1.50.2:22
 ```
+项 | 含义
+---|---
+mode | secretServer
+password | 唯一密钥
+target|内网目标
+
+#### 断线重连
+```ini
 [common]
 auto_reconnection=true
 ```
@@ -429,7 +501,7 @@ auto_reconnection=true
 ```
  ./npc status -config=npc配置文件路径
 ```
-#### 配置文件重载
+#### 重载配置文件
 ```
  ./npc restart -config=npc配置文件路径
 ```
@@ -438,7 +510,7 @@ auto_reconnection=true
 有时候运行npc的内网机器无法直接访问外网,此时可以可以通过socks5代理连接nps
 
 对于配置文件方式启动,设置
-```
+```ini
 [common]
 proxy_socks5_url=socks5://111:222@127.0.0.1:8024
 ```
@@ -449,6 +521,20 @@ proxy_socks5_url=socks5://111:222@127.0.0.1:8024
 ```
 即socks5://username:password@ip:port
 
+#### 日志输出级别
+```
+-log_level=0~7
+```
+```
+LevelEmergency->0  LevelAlert->1
+
+LevelCritical->2 LevelError->3
+
+LevelWarning->4 LevelNotice->5
+
+LevelInformational->6 LevelDebug->7
+```
+默认为全输出,级别为0到7
 ## 相关功能
 
 ### 数据压缩支持
@@ -503,16 +589,16 @@ proxy_socks5_url=socks5://111:222@127.0.0.1:8024
 本代理支持域名解析模式的负载均衡,在web域名添加或者编辑中内网目标分行填写多个目标即可实现轮训级别的负载均衡
 
 ### 端口白名单
-为了防止服务端上的端口被滥用,可在app.conf中配置allowPorts限制可开启的端口,忽略或者不填表示端口不受限制,格式:
+为了防止服务端上的端口被滥用,可在nps.conf中配置allowPorts限制可开启的端口,忽略或者不填表示端口不受限制,格式:
 
-```
+```ini
 allowPorts=9001-9009,10001,11000-12000
 ```
 
 ### 端口范围映射
 当客户端以配置文件的方式启动时,可以将本地的端口进行范围映射,仅支持tcp和udp模式,例如:
 
-```
+```ini
 [tcp]
 mode=tcpServer
 port=9001-9009,10001,11000-12000
@@ -520,6 +606,15 @@ target=8001-8009,10002,13000-14000
 ```
 
 逗号分隔,可单个或者范围,注意上下端口的对应关系,无法一一对应将不能成功
+### 端口范围映射到其他机器
+```ini
+[tcp]
+mode=tcpServer
+port=9001-9009,10001,11000-12000
+target=8001-8009,10002,13000-14000
+targetAddr=10.1.50.2
+```
+填写targetAddr后则表示映射的该地址机器的端口,忽略则便是映射本地127.0.0.1,仅范围映射时有效
 ### 守护进程
 本代理支持守护进程,使用示例如下,服务端客户端所有模式通用,支持linux,darwin,windows。
 ```
@@ -530,7 +625,7 @@ target=8001-8009,10002,13000-14000
 ```
 ### KCP协议支持
 
-KCP 是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,在弱网环境下对性能能有一定的提升。可在app.conf中修改bridgeType为kcp
+KCP 是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,在弱网环境下对性能能有一定的提升。可在nps.conf中修改bridgeType为kcp
 ,设置后本代理将开启udp端口(bridgePort)
 
 注意:当服务端为kcp时,客户端连接时也需要使用相同配置,无配置文件模式加上参数type=kcp,配置文件模式在配置文件中设置tp=kcp
@@ -541,7 +636,7 @@ KCP 是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价
 ### URL路由
 本代理支持根据URL将同一域名转发到不同的内网服务器,可在web中或客户端配置文件中设置,此参数也可忽略,例如在客户端配置文件中
 
-```
+```ini
 [web1]
 host=a.proxy.com
 target=127.0.0.1:7001
@@ -551,12 +646,12 @@ host=a.proxy.com
 target=127.0.0.1:7002
 location=/static
 ```
-对于a.proxy.com/test将转发到web1,对于a.proxy.com/static将转发到web2
+对于`a.proxy.com/test`将转发到`web1`,对于`a.proxy.com/static`将转发到`web2`
 
 ### 限制ip访问
 如果将一些危险性高的端口例如ssh端口暴露在公网上,可能会带来一些风险,本代理支持限制ip访问。
 
-**使用方法:** 在配置文件app.conf中设置ipLimit=true,设置后仅通过注册的ip方可访问。
+**使用方法:** 在配置文件nps.conf中设置ipLimit=true,设置后仅通过注册的ip方可访问。
 
 **ip注册**: 在需要访问的机器上,运行客户端
 
@@ -567,6 +662,8 @@ location=/static
 time为有效小时数,例如time=2,在当前时间后的两小时内,本机公网ip都可以访问nps代理.
 
 **注意:** 本机公网ip并不是一成不变的,请自行注意有效期的设置,同时同一网络下,多人也可能是在公用同一个公网ip。
+### 客户端最大连接数
+为防止恶意大量长连接,影响服务端程序的稳定性,可以在web或客户端配置文件中为每个客户端设置最大连接数。该功能针对`socks5`、`http正向代理`、`域名代理`、`tcp代理`、`私密代理`生效。
 
 ## 相关说明
 
@@ -585,7 +682,10 @@ time为有效小时数,例如time=2,在当前时间后的两小时内,本
 ### 流量统计
 可统计显示每个代理使用的流量,由于压缩和加密等原因,会和实际环境中的略有差异
 
-## 简单性能测试
+### 客户端与服务端版本对比
+为了程序正常运行,客户端与服务端的版本必须一致,否则将导致客户端无法成功连接致服务端。
+
+## 简单的性能测试
 
 ### qps
 ![image](https://github.com/cnlh/nps/blob/master/image/qps.png?raw=true)

+ 29 - 5
bridge/bridge.go

@@ -71,6 +71,7 @@ func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList map[int
 }
 
 func (s *Bridge) StartTunnel() error {
+	go s.linkCleanSession()
 	var err error
 	if s.tunnelType == "kcp" {
 		s.kcpListener, err = kcp.ListenWithOptions(":"+strconv.Itoa(s.TunnelPort), nil, 150, 3)
@@ -118,12 +119,12 @@ func (s *Bridge) verifySuccess(c *conn.Conn) {
 }
 
 func (s *Bridge) cliProcess(c *conn.Conn) {
-	c.Write([]byte(crypt.Md5(version.GetVersion())))
-	if b, err := c.ReadFlag(); err != nil || string(b) != version.VERSION_OK {
+	if b, err := c.ReadLen(32); err != nil || string(b) != crypt.Md5(version.GetVersion()) {
 		logs.Info("The client %s version does not match", c.Conn.RemoteAddr())
 		c.Close()
 		return
 	}
+	c.Write([]byte(crypt.Md5(version.GetVersion())))
 	c.SetReadDeadline(5, s.tunnelType)
 	var buf []byte
 	var err error
@@ -372,14 +373,17 @@ func (s *Bridge) GetConfig(c *conn.Conn) {
 		case common.NEW_CONF:
 			var err error
 			if client, err = c.GetConfigInfo(); err != nil {
-				c.Write([]byte(client.VerifyKey))
 				fail = true
 				c.WriteAddFail()
 				break
 			} else {
-				c.Write([]byte(client.VerifyKey))
-				file.GetCsvDb().NewClient(client)
+				if err = file.GetCsvDb().NewClient(client); err != nil {
+					fail = true
+					c.WriteAddFail()
+					break
+				}
 				c.WriteAddOk()
+				c.Write([]byte(client.VerifyKey))
 			}
 		case common.NEW_HOST:
 			if h, err := c.GetHostInfo(); err != nil {
@@ -506,3 +510,23 @@ func (s *Bridge) clientCopy(clientId int) {
 		}
 	}
 }
+
+func (s *Bridge) linkCleanSession() {
+	ticker := time.NewTicker(time.Minute * 5)
+	for {
+		select {
+		case <-ticker.C:
+			s.clientLock.Lock()
+			for _, v := range s.Client {
+				v.Lock()
+				for _, vv := range v.linkMap {
+					if vv.FinishUse {
+						delete(v.linkMap, vv.Id)
+					}
+				}
+				v.Unlock()
+			}
+			s.clientLock.RUnlock()
+		}
+	}
+}

+ 17 - 1
client/client.go

@@ -38,6 +38,7 @@ func NewRPClient(svraddr string, vKey string, bridgeConnType string, proxyUrl st
 
 //start
 func (s *TRPClient) Start() {
+	go s.linkCleanSession()
 retry:
 	c, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_MAIN, s.proxyUrl)
 	if err != nil {
@@ -129,7 +130,6 @@ func (s *TRPClient) linkProcess(link *conn.Link, c *conn.Conn) {
 	}
 	pool.PutBufPoolCopy(buf)
 	s.Lock()
-	//TODO 删除map
 	s.Unlock()
 }
 
@@ -188,3 +188,19 @@ func (s *TRPClient) dealChan() {
 	}()
 	<-s.stop
 }
+
+func (s *TRPClient) linkCleanSession() {
+	ticker := time.NewTicker(time.Minute * 5)
+	for {
+		select {
+		case <-ticker.C:
+			s.Lock()
+			for _, v := range s.linkMap {
+				if v.FinishUse {
+					delete(s.linkMap, v.Id)
+				}
+			}
+			s.Unlock()
+		}
+	}
+}

+ 10 - 11
client/control.go

@@ -1,7 +1,6 @@
 package client
 
 import (
-	"encoding/binary"
 	"errors"
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/config"
@@ -54,6 +53,9 @@ func GetTaskStatus(path string) {
 		}
 		for _, v := range cnf.Tasks {
 			ports := common.GetPorts(v.Ports)
+			if v.Mode == "secretServer" {
+				ports = append(ports, 0)
+			}
 			for _, vv := range ports {
 				var remark string
 				if len(ports) > 1 {
@@ -102,6 +104,10 @@ re:
 		logs.Error(err)
 		goto re
 	}
+	if !c.GetAddStatus() {
+		logs.Error(errAdd)
+		goto re
+	}
 	var b []byte
 	if b, err = c.ReadLen(16); err != nil {
 		logs.Error(err)
@@ -109,11 +115,6 @@ re:
 	} else {
 		ioutil.WriteFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt"), []byte(string(b)), 0600)
 	}
-	if !c.GetAddStatus() {
-		logs.Error(errAdd)
-		goto re
-	}
-
 	for _, v := range cnf.Hosts {
 		if _, err := c.SendHostInfo(v); err != nil {
 			logs.Error(err)
@@ -171,15 +172,13 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
 		return nil, err
 	}
 	c := conn.NewConn(connection)
-	if b, err := c.ReadLen(32); err != nil {
+	if _, err := c.Write([]byte(crypt.Md5(version.GetVersion()))); err != nil {
 		logs.Error(err)
 		os.Exit(0)
-	} else if crypt.Md5(version.GetVersion()) != string(b) {
+	}
+	if b, err := c.ReadLen(32); err != nil || crypt.Md5(version.GetVersion()) != string(b) {
 		logs.Error("The client does not match the server version. The current version of the client is", version.GetVersion())
 		os.Exit(0)
-	} else if binary.Write(c, binary.LittleEndian, []byte(version.VERSION_OK)); err != nil {
-		logs.Error(err)
-		os.Exit(0)
 	}
 	if _, err := c.Write([]byte(common.Getverifyval(vkey))); err != nil {
 		logs.Error(err)

+ 2 - 1
cmd/npc/npc.go

@@ -21,7 +21,6 @@ var (
 	logLevel     = flag.String("log_level", "7", "log level 0~7")
 	registerTime = flag.Int("time", 2, "register time long /h")
 )
-
 func main() {
 	flag.Parse()
 	if len(os.Args) > 2 {
@@ -35,6 +34,8 @@ func main() {
 		}
 	}
 	daemon.InitDaemon("npc", common.GetRunPath(), common.GetTmpPath())
+	logs.EnableFuncCallDepth(true)
+	logs.SetLogFuncCallDepth(3)
 	if *logType == "stdout" {
 		logs.SetLogger(logs.AdapterConsole, `{"level":`+*logLevel+`,"color":true}`)
 	} else {

+ 4 - 1
cmd/nps/nps.go

@@ -13,6 +13,7 @@ import (
 	_ "github.com/cnlh/nps/web/routers"
 	"log"
 	"os"
+
 	"path/filepath"
 )
 
@@ -36,10 +37,13 @@ func main() {
 			return
 		}
 	}
+	beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf"))
 	if level = beego.AppConfig.String("logLevel"); level == "" {
 		level = "7"
 	}
 	logs.Reset()
+	logs.EnableFuncCallDepth(true)
+	logs.SetLogFuncCallDepth(3)
 	if *logType == "stdout" {
 		logs.SetLogger(logs.AdapterConsole, `{"level":`+level+`,"color":true}`)
 	} else {
@@ -53,6 +57,5 @@ func main() {
 		logs.Error("Getting bridgePort error", err)
 		os.Exit(0)
 	}
-	beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "app.conf"))
 	server.StartNewServer(bridgePort, task, beego.AppConfig.String("bridgeType"))
 }

+ 3 - 0
conf/clients.csv

@@ -1 +1,4 @@
 7,7hv3avgeg2ldzvx7,,true,,,1,,0,999,10
+10,213123dsd,,true,,,0,,0,0,0
+11,213123dsd2,,true,,,0,,0,0,0
+15,wx1wdhjmpmntowc8,,true,,,0,,0,0,0

+ 16 - 4
conf/npc.conf

@@ -3,7 +3,6 @@ server=127.0.0.1:8284
 tp=tcp
 vkey=123
 auto_reconnection=true
-crypt=true
 
 [web1]
 host=a.o.com
@@ -22,6 +21,19 @@ mode=secretServer
 password=1111
 target=123.206.77.88:22
 
-[secret_ssh]
-password=1111
-port=1000
+[tcp]
+mode=tcpServer
+target=127.0.0.1:8080
+port=9006
+
+[socks5]
+mode=socks5Server
+port=9005
+
+[http]
+mode=httpProxyServer
+port=9004
+
+[udp]
+mode=httpProxyServer
+port=9003

+ 6 - 2
conf/app.conf → conf/nps.conf

@@ -38,9 +38,13 @@ publicVkey=123
 
 #Traffic data persistence interval(minute)
 #Ignorance means no persistence
-flowStoreInterval=1
+#flowStoreInterval=1
 
 #log level
 #LevelEmergency->0  LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4
 #LevelNotice->5 LevelInformational->6 LevelDebug->7
-logLevel=7
+#logLevel=7
+
+#Whether to restrict IP access, true or false or ignore
+#ipLimit=true
+

+ 1 - 2
lib/conn/conn.go

@@ -7,7 +7,6 @@ import (
 	"errors"
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/config"
-	"github.com/cnlh/nps/lib/crypt"
 	"github.com/cnlh/nps/lib/file"
 	"github.com/cnlh/nps/lib/pool"
 	"github.com/cnlh/nps/lib/rate"
@@ -347,7 +346,7 @@ func (s *Conn) GetConfigInfo() (c *file.Client, err error) {
 		return
 	} else {
 		arr := strings.Split(string(b), common.CONN_DATA_SEQ)
-		c = file.NewClient(crypt.GetRandomString(16), true, false)
+		c = file.NewClient("", true, false)
 		c.Cnf.U = arr[0]
 		c.Cnf.P = arr[1]
 		c.Cnf.Crypt = common.GetBoolByStr(arr[2])

+ 3 - 1
lib/conn/link.go

@@ -35,6 +35,7 @@ type Link struct {
 	MsgCh         chan []byte
 	MsgConn       *Conn
 	StatusCh      chan bool
+	FinishUse     bool
 }
 
 func NewLink(id int, connType string, host string, en, de int, crypt bool, c *Conn, flow *file.Flow, udpListener *net.UDPConn, rate *rate.Rate, UdpRemoteAddr *net.UDPAddr) *Link {
@@ -61,6 +62,7 @@ func (s *Link) Run(flow bool) {
 			select {
 			case content := <-s.MsgCh:
 				if len(content) == len(common.IO_EOF) && string(content) == common.IO_EOF {
+					s.FinishUse = true
 					if s.Conn != nil {
 						s.Conn.Close()
 					}
@@ -81,8 +83,8 @@ func (s *Link) Run(flow bool) {
 						return
 					}
 					s.MsgConn.WriteWriteSuccess(s.Id)
-					pool.PutBufPoolCopy(content)
 				}
+				pool.PutBufPoolCopy(content)
 			}
 		}
 	}()

+ 23 - 1
lib/file/file.go

@@ -372,7 +372,19 @@ func (s *Csv) DelClient(id int) error {
 	return errors.New("不存在")
 }
 
-func (s *Csv) NewClient(c *Client) {
+func (s *Csv) NewClient(c *Client) error {
+	var isNotSet bool
+reset:
+	if c.VerifyKey == "" || isNotSet {
+		isNotSet = true
+		c.VerifyKey = crypt.GetRandomString(16)
+	}
+	if !s.VerifyVkey(c.VerifyKey, c.id) {
+		if isNotSet {
+			goto reset
+		}
+		return errors.New("Vkey duplicate, please reset")
+	}
 	if c.Id == 0 {
 		c.Id = s.GetClientId()
 	}
@@ -383,6 +395,16 @@ func (s *Csv) NewClient(c *Client) {
 	defer s.Unlock()
 	s.Clients = append(s.Clients, c)
 	s.StoreClientsToCsv()
+	return nil
+}
+
+func (s *Csv) VerifyVkey(vkey string, id int) bool {
+	for _, v := range s.Clients {
+		if v.VerifyKey == vkey && v.Id != id {
+			return false
+		}
+	}
+	return true
 }
 
 func (s *Csv) GetClientId() int {

+ 4 - 0
lib/file/obj.go

@@ -2,6 +2,7 @@ package file
 
 import (
 	"github.com/cnlh/nps/lib/rate"
+	"math"
 	"strings"
 	"sync"
 )
@@ -60,6 +61,9 @@ func NewClient(vKey string, noStore bool, noDisplay bool) *Client {
 func (s *Client) GetId() int {
 	s.Lock()
 	defer s.Unlock()
+	if s.id == math.MaxInt32 {
+		s.id = 0
+	}
 	s.id++
 	return s.id
 }

+ 0 - 1
lib/version/version.go

@@ -1,7 +1,6 @@
 package version
 
 const VERSION = "0.0.16"
-const VERSION_OK = "vrok"
 
 func GetVersion() string {
 	return VERSION

+ 1 - 1
server/proxy/http.go

@@ -154,10 +154,10 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) {
 			isConn = false
 		} else {
 			r, err = http.ReadRequest(bufio.NewReader(c))
-			logs.Trace("New http(s) connection,clientId %d,host %s,url %s,remote address %s", host.Client.Id, r.Host, r.URL, r.RemoteAddr)
 			if err != nil {
 				break
 			}
+			logs.Trace("New http(s) connection,clientId %d,host %s,url %s,remote address %s", host.Client.Id, r.Host, r.URL, r.RemoteAddr)
 			if host, err = file.GetCsvDb().GetInfoByHost(r.Host, r); err != nil {
 				logs.Notice("the url %s %s can't be parsed!", r.Host, r.RequestURI)
 				break

+ 4 - 1
server/server.go

@@ -52,8 +52,11 @@ func DealBridgeTask() {
 				if !t.Client.GetConn() {
 					logs.Info("Connections exceed the current client %d limit", t.Client.Id)
 					s.Conn.Close()
-				} else {
+				} else if t.Status {
 					go proxy.NewBaseServer(Bridge, t).DealClient(s.Conn, t.Target, nil)
+				} else {
+					s.Conn.Close()
+					logs.Trace("This key %s cannot be processed,status is close", s.Password)
 				}
 			} else {
 				logs.Trace("This key %s cannot be processed", s.Password)

+ 8 - 3
web/controllers/client.go

@@ -1,7 +1,6 @@
 package controllers
 
 import (
-	"github.com/cnlh/nps/lib/crypt"
 	"github.com/cnlh/nps/lib/file"
 	"github.com/cnlh/nps/lib/rate"
 	"github.com/cnlh/nps/server"
@@ -31,7 +30,7 @@ func (s *ClientController) Add() {
 		s.display()
 	} else {
 		t := &file.Client{
-			VerifyKey: crypt.GetRandomString(16),
+			VerifyKey: s.GetString("vkey"),
 			Id:        file.GetCsvDb().GetClientId(),
 			Status:    true,
 			Remark:    s.GetString("remark"),
@@ -53,7 +52,9 @@ func (s *ClientController) Add() {
 			t.Rate = rate.NewRate(int64(t.RateLimit * 1024))
 			t.Rate.Start()
 		}
-		file.GetCsvDb().NewClient(t)
+		if err := file.GetCsvDb().NewClient(t); err != nil {
+			s.AjaxErr(err.Error())
+		}
 		s.AjaxOk("添加成功")
 	}
 }
@@ -88,6 +89,10 @@ func (s *ClientController) Edit() {
 		if c, err := file.GetCsvDb().GetClient(id); err != nil {
 			s.error()
 		} else {
+			if !file.GetCsvDb().VerifyVkey(s.GetString("vkey"), c.Id) {
+				s.AjaxErr("Vkey duplicate, please reset")
+			}
+			c.VerifyKey = s.GetString("vkey")
 			c.Remark = s.GetString("remark")
 			c.Cnf.U = s.GetString("u")
 			c.Cnf.P = s.GetString("p")

+ 4 - 0
web/views/client/add.html

@@ -28,6 +28,10 @@
                         <label class="control-label">验证密码(仅socks5,web穿透支持)</label>
                         <input class="form-control" type="text" name="p" placeholder="不填则无需验证">
                     </div>
+                    <div class="form-group" id="vkey">
+                        <label class="control-label">客户端连接密钥(唯一、不填将会自动生成)</label>
+                        <input class="form-control" type="text" name="vkey" placeholder="客户端连接密钥">
+                    </div>
                     <div class="form-group" id="compress">
                         <label class="control-label">数据压缩方式</label>
                         <select class="form-control" name="compress">

+ 5 - 0
web/views/client/edit.html

@@ -34,6 +34,11 @@
                         <input class="form-control" value="{{.c.Cnf.P}}" type="text" name="p"
                                placeholder="不填则无需验证">
                     </div>
+                    <div class="form-group" id="vkey">
+                        <label class="control-label">客户端连接密钥(唯一、不填将会自动生成)</label>
+                        <input class="form-control" value="{{.c.VerifyKey}}" type="text" name="vkey"
+                               placeholder="客户端连接密钥">
+                    </div>
                     <div class="form-group" id="compress">
                         <label class="control-label">数据压缩方式(所有模式均支持)</label>
                         <select class="form-control" name="compress">