New functions

This commit is contained in:
刘河 2019-02-24 13:17:43 +08:00
parent 750ecb824a
commit db43405237
20 changed files with 287 additions and 74 deletions

8
.idea/nps.iml generated Executable file
View File

@ -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>

178
README.md
View File

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

View File

@ -71,6 +71,7 @@ func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList map[int
} }
func (s *Bridge) StartTunnel() error { func (s *Bridge) StartTunnel() error {
go s.linkCleanSession()
var err error var err error
if s.tunnelType == "kcp" { if s.tunnelType == "kcp" {
s.kcpListener, err = kcp.ListenWithOptions(":"+strconv.Itoa(s.TunnelPort), nil, 150, 3) 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) { func (s *Bridge) cliProcess(c *conn.Conn) {
c.Write([]byte(crypt.Md5(version.GetVersion()))) if b, err := c.ReadLen(32); err != nil || string(b) != crypt.Md5(version.GetVersion()) {
if b, err := c.ReadFlag(); err != nil || string(b) != version.VERSION_OK {
logs.Info("The client %s version does not match", c.Conn.RemoteAddr()) logs.Info("The client %s version does not match", c.Conn.RemoteAddr())
c.Close() c.Close()
return return
} }
c.Write([]byte(crypt.Md5(version.GetVersion())))
c.SetReadDeadline(5, s.tunnelType) c.SetReadDeadline(5, s.tunnelType)
var buf []byte var buf []byte
var err error var err error
@ -372,14 +373,17 @@ func (s *Bridge) GetConfig(c *conn.Conn) {
case common.NEW_CONF: case common.NEW_CONF:
var err error var err error
if client, err = c.GetConfigInfo(); err != nil { if client, err = c.GetConfigInfo(); err != nil {
c.Write([]byte(client.VerifyKey))
fail = true fail = true
c.WriteAddFail() c.WriteAddFail()
break break
} else { } else {
c.Write([]byte(client.VerifyKey)) if err = file.GetCsvDb().NewClient(client); err != nil {
file.GetCsvDb().NewClient(client) fail = true
c.WriteAddFail()
break
}
c.WriteAddOk() c.WriteAddOk()
c.Write([]byte(client.VerifyKey))
} }
case common.NEW_HOST: case common.NEW_HOST:
if h, err := c.GetHostInfo(); err != nil { 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()
}
}
}

View File

@ -38,6 +38,7 @@ func NewRPClient(svraddr string, vKey string, bridgeConnType string, proxyUrl st
//start //start
func (s *TRPClient) Start() { func (s *TRPClient) Start() {
go s.linkCleanSession()
retry: retry:
c, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_MAIN, s.proxyUrl) c, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_MAIN, s.proxyUrl)
if err != nil { if err != nil {
@ -129,7 +130,6 @@ func (s *TRPClient) linkProcess(link *conn.Link, c *conn.Conn) {
} }
pool.PutBufPoolCopy(buf) pool.PutBufPoolCopy(buf)
s.Lock() s.Lock()
//TODO 删除map
s.Unlock() s.Unlock()
} }
@ -188,3 +188,19 @@ func (s *TRPClient) dealChan() {
}() }()
<-s.stop <-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()
}
}
}

View File

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

View File

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

View File

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

View File

@ -1 +1,4 @@
7,7hv3avgeg2ldzvx7,,true,,,1,,0,999,10 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

1 7 7hv3avgeg2ldzvx7 true 1 0 999 10
2 10 213123dsd true 0 0 0 0
3 11 213123dsd2 true 0 0 0 0
4 15 wx1wdhjmpmntowc8 true 0 0 0 0

View File

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

View File

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

View File

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

View File

@ -35,6 +35,7 @@ type Link struct {
MsgCh chan []byte MsgCh chan []byte
MsgConn *Conn MsgConn *Conn
StatusCh chan bool 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 { 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 { select {
case content := <-s.MsgCh: case content := <-s.MsgCh:
if len(content) == len(common.IO_EOF) && string(content) == common.IO_EOF { if len(content) == len(common.IO_EOF) && string(content) == common.IO_EOF {
s.FinishUse = true
if s.Conn != nil { if s.Conn != nil {
s.Conn.Close() s.Conn.Close()
} }
@ -81,8 +83,8 @@ func (s *Link) Run(flow bool) {
return return
} }
s.MsgConn.WriteWriteSuccess(s.Id) s.MsgConn.WriteWriteSuccess(s.Id)
pool.PutBufPoolCopy(content)
} }
pool.PutBufPoolCopy(content)
} }
} }
}() }()

View File

@ -372,7 +372,19 @@ func (s *Csv) DelClient(id int) error {
return errors.New("不存在") 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 { if c.Id == 0 {
c.Id = s.GetClientId() c.Id = s.GetClientId()
} }
@ -383,6 +395,16 @@ func (s *Csv) NewClient(c *Client) {
defer s.Unlock() defer s.Unlock()
s.Clients = append(s.Clients, c) s.Clients = append(s.Clients, c)
s.StoreClientsToCsv() 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 { func (s *Csv) GetClientId() int {

View File

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

View File

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

View File

@ -154,10 +154,10 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) {
isConn = false isConn = false
} else { } else {
r, err = http.ReadRequest(bufio.NewReader(c)) 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 { if err != nil {
break 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 { 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) logs.Notice("the url %s %s can't be parsed!", r.Host, r.RequestURI)
break break

View File

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

View File

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

View File

@ -28,6 +28,10 @@
<label class="control-label">验证密码(仅socks5,web穿透支持)</label> <label class="control-label">验证密码(仅socks5,web穿透支持)</label>
<input class="form-control" type="text" name="p" placeholder="不填则无需验证"> <input class="form-control" type="text" name="p" placeholder="不填则无需验证">
</div> </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"> <div class="form-group" id="compress">
<label class="control-label">数据压缩方式</label> <label class="control-label">数据压缩方式</label>
<select class="form-control" name="compress"> <select class="form-control" name="compress">

View File

@ -34,6 +34,11 @@
<input class="form-control" value="{{.c.Cnf.P}}" type="text" name="p" <input class="form-control" value="{{.c.Cnf.P}}" type="text" name="p"
placeholder="不填则无需验证"> placeholder="不填则无需验证">
</div> </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"> <div class="form-group" id="compress">
<label class="control-label">数据压缩方式(所有模式均支持)</label> <label class="control-label">数据压缩方式(所有模式均支持)</label>
<select class="form-control" name="compress"> <select class="form-control" name="compress">