File mode|pubVkey optimization

pull/59/head V0.17.0
刘河 2019-03-02 17:43:21 +08:00
parent f526c56784
commit 1c1aa5ec5b
29 changed files with 477 additions and 195 deletions

128
README.md
View File

@ -49,6 +49,7 @@ go语言编写无第三方依赖各个平台都已经编译在release中
* [流量数据持久化](#流量数据持久化)
* [自定义客户端连接密钥](#自定义客户端连接密钥)
* [关闭公钥访问](#关闭公钥访问)
* [关闭web管理](#关闭web管理)
* [客户端](#客户端)
* [客户端启动](#客户端启动)
* [无配置文件模式](#无配置文件模式)
@ -60,7 +61,9 @@ go语言编写无第三方依赖各个平台都已经编译在release中
* [udp隧道](#udp隧道模式)
* [http正向代理](#http代理模式)
* [socks5代理](#socks5代理模式)
* [私密代理](#私密代理)
* [私密代理](#私密代理模式)
* [p2p服务](#p2p代理)
* [文件访问代理](#文件访问模式)
* [断线重连](#断线重连)
* [状态检查](#状态检查)
* [重载配置文件](#重载配置文件)
@ -161,17 +164,21 @@ go语言编写无第三方依赖各个平台都已经编译在release中
---|---
httpport | web管理端口
password | web界面管理密码
bridePort | 服务端客户端通信端口
username | web界面管理账号
bridgePort | 服务端客户端通信端口
pemPath | ssl certFile绝对路径
keyPath | ssl keyFile绝对路径
httpsProxyPort | 域名代理https代理监听端口
httpProxyPort | 域名代理http代理监听端口
authip|web api免验证IP地址
authKey|web api密钥
bridgeType|客户端与服务端连接方式kcp或tcp
publicVkey|客户端以配置文件模式启动时的密钥,设置为空表示关闭客户端配置文件连接模式
ipLimit|是否限制ip访问true或false或忽略
flowStoreInterval|服务端流量数据持久化间隔,单位分钟,忽略表示不持久化
logLevel|日志输出级别
cryptKey | 获取服务端authKey时的aes加密密钥16位
serverIp| 服务端Ip使用p2p模式必填
p2pPort|p2p模式开启的udp端口
### 详细说明
@ -213,7 +220,7 @@ logLevel|日志输出级别
./npc -server=1.1.1.1:8284 -vkey=客户端的密钥
```
- 在该客户端隧道管理中添加一条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@1.1.1.1`
#### udp隧道
@ -271,7 +278,7 @@ logLevel|日志输出级别
**适用范围:** 无需占用多余的端口、安全性要求较高可以防止其他人连接的TCP服务例如ssh。
**假设场景:**
无需新增多的端将映射内网服务器10.1.50.2的22端口
无需新增多的端将映射内网服务器10.1.50.2的22端口公网服务器ip为1.1.1.1,网桥端口为8284
**使用步骤**
- 在客户端管理中创建一个客户端,记录下验证密钥
@ -284,7 +291,7 @@ logLevel|日志输出级别
```ini
[common]
server=127.0.0.1:8284
server=1.1.1.1:8284
tp=tcp
vkey=123
[secret_ssh]
@ -295,6 +302,37 @@ port=1000
假设用户名为root现在执行`ssh -p 1000 root@127.0.0.1`即可访问ssh
#### p2p服务
**适用范围:** 大流量传输场景流量不经过公网服务器但是由于p2p穿透和nat类型关系较大成功率不高。
**假设场景:**
内网1机器ip为10.1.50.2 内网2机器ip为10.2.50.2 口公网服务器ip为1.1.1.1,网桥端口为8284
想通过访问机器1的2001端口---->访问到内网2机器的22端口
**使用步骤**
- 在客户端管理中创建一个客户端,记录下验证密钥
- 内网机器2客户端运行
```
./npc -server=1.1.1.1:8284 -vkey=客户端的密钥
```
- 添加一条p2p代理并设置唯一密钥p2pssh
- 在需要连接的机器上(即机器1)以配置文件模式启动客户端,内容如下
```ini
[common]
server=1.1.1.1:8284
tp=tcp
vkey=123
[p2p_ssh]
password=p2pssh
port=2001
```
**注意:** p2p前缀必须存在password为web管理上添加的唯一密钥
假设机器2用户名为root现在执行`ssh -p 2001 root@127.0.0.1`即可访问机器2的ssh
### 使用https
@ -368,6 +406,9 @@ web上可以自定义客户端连接的密钥但是必须具有唯一性
### 关闭公钥访问
可以将`nps.conf`中的`publicVkey`设置为空或者删除
### 关闭web管理
可以将`nps.conf`中的`httpport`设置为空或者删除
## 客户端
### 客户端启动
@ -432,7 +473,7 @@ header_xxx|请求header修改或添加header_proxy表示添加header proxy:np
```ini
[tcp]
mode=tcp
mode=tcpServer
target=127.0.0.1:8080
port=9001
```
@ -446,7 +487,7 @@ target|内网目标
```ini
[udp]
mode=udp
mode=udpServer
target=127.0.0.1:8080
port=9002
```
@ -459,7 +500,7 @@ target|内网目标
```ini
[http]
mode=httpProxy
mode=httpProxyServer
port=9003
```
项 | 含义
@ -470,7 +511,7 @@ port | 在服务端的代理端口
```ini
[socks5]
mode=socks5
mode=socks5Server
port=9004
```
项 | 含义
@ -487,10 +528,44 @@ target=10.1.50.2:22
```
项 | 含义
---|---
mode | secret
mode | secretServer
password | 唯一密钥
target|内网目标
##### p2p代理模式
```ini
[p2p_ssh]
mode=p2p
password=ssh2
target=10.1.50.2:22
```
项 | 含义
---|---
mode | p2p
password | 唯一密钥
target|内网目标
##### 文件访问模式
利用nps提供一个公网可访问的本地文件服务
```ini
[file]
mode=file
port=9100
local_path=/tmp/
strip_pre=/web/
````
项 | 含义
---|---
mode | file
port | 服务端开启的端口
local_path|本地文件目录
strip_pre|前缀
对于`strip_pre`,访问公网`ip:9100/web/`相当于访问`/tmp/`目录
#### 断线重连
```ini
[common]
@ -600,7 +675,7 @@ allowPorts=9001-9009,10001,11000-12000
```ini
[tcp]
mode=tcp
mode=tcpServer
port=9001-9009,10001,11000-12000
target=8001-8009,10002,13000-14000
```
@ -609,7 +684,7 @@ target=8001-8009,10002,13000-14000
### 端口范围映射到其他机器
```ini
[tcp]
mode=tcp
mode=tcpServer
port=9001-9009,10001,11000-12000
target=8001-8009,10002,13000-14000
targetAddr=10.1.50.2
@ -707,6 +782,33 @@ time为有效小时数例如time=2在当前时间后的两小时内
## webAPI
### webAPI验证说明
- 采用auth_key的验证方式
- 在提交的每个请求后面附带两个参数,`auth_key` 和`timestamp`
```
auth_key的生成方式为md5(配置文件中的auth_key+当前时间戳)
```
```
timestamp为当前时间戳
```
**注意:** 为保证安全时间戳的有效范围为20秒内所以每次提交请求必须重新生成。
### 获取服务端authKey
如果想获取authKey服务端提供获取authKey的接口
```
POST /auth/getauthkey
```
将返回加密后的authKey采用aes cbc加密请使用与服务端配置文件中cryptKey相同的密钥进行解密
### 详细文档
- 此文档近期可能更新较慢,建议自行抓包
为方便第三方扩展在web模式下可利用webAPI进行相关操作详情见
[webAPI文档](https://github.com/cnlh/nps/wiki/webAPI%E6%96%87%E6%A1%A3)

View File

@ -21,15 +21,18 @@ import (
)
type Client struct {
tunnel *mux.Mux
signal *conn.Conn
tunnel *mux.Mux
signal *conn.Conn
file *mux.Mux
retryTime int // it will be add 1 when ping not ok until to 3 will close the client
sync.RWMutex
}
func NewClient(t *mux.Mux, s *conn.Conn) *Client {
func NewClient(t, f *mux.Mux, s *conn.Conn) *Client {
return &Client{
signal: s,
tunnel: t,
file: f,
}
}
@ -64,6 +67,7 @@ func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList map[int
}
func (s *Bridge) StartTunnel() error {
go s.ping()
var err error
if s.tunnelType == "kcp" {
s.kcpListener, err = kcp.ListenWithOptions(":"+strconv.Itoa(s.TunnelPort), nil, 150, 3)
@ -117,15 +121,17 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
c.Close()
return
}
//write server version to client
c.Write([]byte(crypt.Md5(version.GetVersion())))
c.SetReadDeadline(5, s.tunnelType)
var buf []byte
var err error
//get vkey from client
if buf, err = c.GetShortContent(32); err != nil {
c.Close()
return
}
//验证
//verify
id, err := file.GetCsvDb().GetIdByVerifyKey(string(buf), c.Conn.RemoteAddr().String())
if err != nil {
logs.Info("Current client connection validation error, close this client:", c.Conn.RemoteAddr())
@ -150,7 +156,9 @@ func (s *Bridge) DelClient(id int, isOther bool) {
if c, err := file.GetCsvDb().GetClient(id); err == nil && c.NoStore {
s.CloseClient <- c.Id
}
v.signal.Close()
if v.signal != nil {
v.signal.Close()
}
delete(s.Client, id)
}
}
@ -170,13 +178,9 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
v.signal = c
v.Unlock()
} else {
s.Client[id] = NewClient(nil, c)
s.Client[id] = NewClient(nil, nil, c)
s.clientLock.Unlock()
}
go func(id int) {
binary.Read(c, binary.LittleEndian, true)
s.DelClient(id, false)
}(id)
logs.Info("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr())
case common.WORK_CHAN:
s.clientLock.Lock()
@ -186,17 +190,38 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
v.tunnel = mux.NewMux(c.Conn)
v.Unlock()
} else {
s.Client[id] = NewClient(mux.NewMux(c.Conn), nil)
s.Client[id] = NewClient(mux.NewMux(c.Conn), nil, nil)
s.clientLock.Unlock()
}
case common.WORK_CONFIG:
go s.getConfig(c)
var isPub bool
client, err := file.GetCsvDb().GetClient(id);
if err == nil {
if client.VerifyKey == beego.AppConfig.String("publicVkey") {
isPub = true
} else {
isPub = false
}
}
binary.Write(c, binary.LittleEndian, isPub)
go s.getConfig(c, isPub, client)
case common.WORK_REGISTER:
go s.register(c)
case common.WORK_SECRET:
if b, err := c.GetShortContent(32); err == nil {
s.SecretChan <- conn.NewSecret(string(b), c)
}
case common.WORK_FILE:
s.clientLock.Lock()
if v, ok := s.Client[id]; ok {
s.clientLock.Unlock()
v.Lock()
v.file = mux.NewMux(c.Conn)
v.Unlock()
} else {
s.Client[id] = NewClient(nil, mux.NewMux(c.Conn), nil)
s.clientLock.Unlock()
}
case common.WORK_P2P:
//read md5 secret
if b, err := c.GetShortContent(32); err != nil {
@ -238,10 +263,12 @@ func (s *Bridge) register(c *conn.Conn) {
}
}
func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string) (target net.Conn, err error) {
func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string, t *file.Tunnel) (target net.Conn, err error) {
s.clientLock.Lock()
if v, ok := s.Client[clientId]; ok {
s.clientLock.Unlock()
//If ip is restricted to do ip verification
if s.ipVerify {
s.registerLock.Lock()
ip := common.GetIpByAddr(linkAddr)
@ -255,18 +282,27 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string) (t
}
s.registerLock.Unlock()
}
if v.tunnel == nil {
var tunnel *mux.Mux
if t != nil && t.Mode == "file" {
tunnel = v.file
} else {
tunnel = v.tunnel
}
if tunnel == nil {
err = errors.New("the client connect error")
return
}
if target, err = v.tunnel.NewConn(); err != nil {
if target, err = tunnel.NewConn(); err != nil {
return
}
if t != nil && t.Mode == "file" {
return
}
if _, err = conn.NewConn(target).SendLinkInfo(link); err != nil {
logs.Warn("new connect error ,the target %s refuse to connect", link.Host)
logs.Info("new connect error ,the target %s refuse to connect", link.Host)
return
}
@ -277,9 +313,36 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string) (t
return
}
func (s *Bridge) ping() {
ticker := time.NewTicker(time.Second * 5)
for {
select {
case <-ticker.C:
s.clientLock.Lock()
arr := make([]int, 0)
for k, v := range s.Client {
if v.tunnel == nil {
v.retryTime += 1
if v.retryTime >= 3 {
arr = append(arr, k)
}
continue
}
if v.tunnel.IsClose {
arr = append(arr, k)
}
}
s.clientLock.Unlock()
for _, v := range arr {
logs.Info("the client %d closed", v)
s.DelClient(v, false)
}
}
}
}
//get config and add task from client config
func (s *Bridge) getConfig(c *conn.Conn) {
var client *file.Client
func (s *Bridge) getConfig(c *conn.Conn, isPub bool, client *file.Client) {
var fail bool
for {
@ -292,7 +355,6 @@ func (s *Bridge) getConfig(c *conn.Conn) {
if b, err := c.GetShortContent(32); err != nil {
break
} else {
logs.Warn(string(b))
var str string
id, err := file.GetCsvDb().GetClientIdByVkey(string(b))
if err != nil {
@ -327,17 +389,26 @@ func (s *Bridge) getConfig(c *conn.Conn) {
c.Write([]byte(client.VerifyKey))
}
case common.NEW_HOST:
if h, err := c.GetHostInfo(); err != nil {
fail = true
c.WriteAddFail()
break
} else if file.GetCsvDb().IsHostExist(h) {
h, err := c.GetHostInfo()
if err != nil {
fail = true
c.WriteAddFail()
break
}
h.Client = client
if h.Location == "" {
h.Location = "/"
}
if !client.HasHost(h) {
if file.GetCsvDb().IsHostExist(h) {
fail = true
c.WriteAddFail()
break
} else {
file.GetCsvDb().NewHost(h)
c.WriteAddOk()
}
} else {
h.Client = client
file.GetCsvDb().NewHost(h)
c.WriteAddOk()
}
case common.NEW_TASK:
@ -381,18 +452,22 @@ func (s *Bridge) getConfig(c *conn.Conn) {
tl.NoStore = true
tl.Client = client
tl.Password = t.Password
if err := file.GetCsvDb().NewTask(tl); err != nil {
logs.Notice("Add task error ", err.Error())
fail = true
c.WriteAddFail()
break
}
if b := tool.TestServerPort(tl.Port, tl.Mode); !b && t.Mode != "secret" {
fail = true
c.WriteAddFail()
break
} else {
s.OpenTask <- tl
tl.LocalPath = t.LocalPath
tl.StripPre = t.StripPre
if !client.HasTunnel(tl) {
if err := file.GetCsvDb().NewTask(tl); err != nil {
logs.Notice("Add task error ", err.Error())
fail = true
c.WriteAddFail()
break
}
if b := tool.TestServerPort(tl.Port, tl.Mode); !b && t.Mode != "secret" && t.Mode != "p2p" {
fail = true
c.WriteAddFail()
break
} else {
s.OpenTask <- tl
}
}
c.WriteAddOk()
}
@ -400,7 +475,7 @@ func (s *Bridge) getConfig(c *conn.Conn) {
}
}
if fail && client != nil {
s.CloseClient <- client.Id
s.DelClient(client.Id, false)
}
c.Close()
}

View File

@ -17,6 +17,8 @@ type TRPClient struct {
stop chan bool
proxyUrl string
vKey string
tunnel *mux.Mux
signal *conn.Conn
}
//new client
@ -39,16 +41,19 @@ retry:
time.Sleep(time.Second * 5)
goto retry
}
logs.Info("Successful connection with server %s", s.svrAddr)
go s.ping()
s.processor(c)
}
func (s *TRPClient) Close() {
s.stop <- true
s.signal.Close()
}
//处理
func (s *TRPClient) processor(c *conn.Conn) {
s.signal = c
go s.dealChan()
for {
flags, err := c.ReadFlag()
@ -176,9 +181,9 @@ func (s *TRPClient) dealChan() {
return
}
go func() {
l := mux.NewMux(tunnel.Conn)
s.tunnel = mux.NewMux(tunnel.Conn)
for {
src, err := l.Accept()
src, err := s.tunnel.Accept()
if err != nil {
logs.Warn(err)
break
@ -196,6 +201,7 @@ func (s *TRPClient) srcProcess(src net.Conn) {
logs.Error("get connection info from server error ", err)
return
}
//host for target processing
lk.Host = common.FormatAddress(lk.Host)
//connect to target
if targetConn, err := net.Dial(lk.ConnType, lk.Host); err != nil {
@ -206,3 +212,18 @@ func (s *TRPClient) srcProcess(src net.Conn) {
conn.CopyWaitGroup(src, targetConn, lk.Crypt, lk.Compress, nil, nil)
}
}
func (s *TRPClient) ping() {
ticker := time.NewTicker(time.Second * 5)
loop:
for {
select {
case <-ticker.C:
if s.tunnel.IsClose {
s.Close()
ticker.Stop()
break loop
}
}
}
}

View File

@ -1,6 +1,7 @@
package client
import (
"encoding/binary"
"errors"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/config"
@ -41,7 +42,8 @@ func GetTaskStatus(path string) {
} else if _, err := c.Write([]byte(crypt.Md5(string(f)))); err != nil {
log.Fatalln(err)
}
var isPub bool
binary.Read(c, binary.LittleEndian, &isPub)
if l, err := c.GetLen(); err != nil {
log.Fatalln(err)
} else if b, err := c.GetShortContent(l); err != nil {
@ -104,25 +106,30 @@ re:
logs.Error(err)
goto re
}
// send global configuration to server and get status of config setting
if _, err := c.SendConfigInfo(cnf.CommonConfig); err != nil {
logs.Error(err)
goto re
}
if !c.GetAddStatus() {
logs.Error(errAdd)
goto re
}
var isPub bool
binary.Read(c, binary.LittleEndian, &isPub)
// get tmp password
var b []byte
if b, err = c.GetShortContent(16); err != nil {
logs.Error(err)
goto re
} else {
ioutil.WriteFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt"), []byte(string(b)), 0600)
vkey := cnf.CommonConfig.VKey
if isPub {
// send global configuration to server and get status of config setting
if _, err := c.SendConfigInfo(cnf.CommonConfig); err != nil {
logs.Error(err)
goto re
}
if !c.GetAddStatus() {
logs.Error(errAdd)
goto re
}
if b, err = c.GetShortContent(16); err != nil {
logs.Error(err)
goto re
}
vkey = string(b)
}
ioutil.WriteFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt"), []byte(vkey), 0600)
//send hosts to server
for _, v := range cnf.Hosts {
@ -146,6 +153,10 @@ re:
logs.Error(errAdd, v.Ports)
goto re
}
if v.Mode == "file" {
//start local file server
go startLocalFileServer(cnf.CommonConfig, v, vkey)
}
}
//create local server secret or p2p
@ -154,7 +165,7 @@ re:
}
c.Close()
NewRPClient(cnf.CommonConfig.Server, string(b), cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl).Start()
NewRPClient(cnf.CommonConfig.Server, vkey, cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl).Start()
CloseLocalServer()
goto re
}

View File

@ -5,31 +5,52 @@ import (
"github.com/cnlh/nps/lib/config"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/crypt"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/lib/mux"
"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
"github.com/cnlh/nps/vender/github.com/xtaci/kcp"
"net"
"net/http"
"strings"
)
var LocalServer []*net.TCPListener
var udpConn net.Conn
var muxSession *mux.Mux
var fileServer []*http.Server
func CloseLocalServer() {
for _, v := range LocalServer {
v.Close()
}
for _, v := range fileServer {
v.Close()
}
}
func startLocalFileServer(config *config.CommonConfig, t *file.Tunnel, vkey string) {
remoteConn, err := NewConn(config.Tp, vkey, config.Server, common.WORK_FILE, config.ProxyUrl)
if err != nil {
logs.Error("Local connection server failed ", err.Error())
return
}
srv := &http.Server{
Handler: http.StripPrefix(t.StripPre, http.FileServer(http.Dir(t.LocalPath))),
}
logs.Info("start local file system, local path %s, strip prefix %s ,remote port %s ", t.LocalPath, t.StripPre, t.Ports)
fileServer = append(fileServer, srv)
listener := mux.NewMux(remoteConn.Conn)
logs.Warn(srv.Serve(listener))
}
func StartLocalServer(l *config.LocalServer, config *config.CommonConfig) error {
listener, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), l.Port, ""})
if err != nil {
logs.Error("Local listener startup failed port %d, error %s", l.Port, err.Error())
logs.Error("local listener startup failed port %d, error %s", l.Port, err.Error())
return err
}
LocalServer = append(LocalServer, listener)
logs.Info("Successful start-up of local monitoring, port", l.Port)
logs.Info("successful start-up of local monitoring, port", l.Port)
for {
c, err := listener.AcceptTCP()
if err != nil {
@ -52,9 +73,11 @@ func processSecret(localTcpConn net.Conn, config *config.CommonConfig, l *config
remoteConn, err := NewConn(config.Tp, config.VKey, config.Server, common.WORK_SECRET, config.ProxyUrl)
if err != nil {
logs.Error("Local connection server failed ", err.Error())
return
}
if _, err := remoteConn.Write([]byte(crypt.Md5(l.Password))); err != nil {
logs.Error("Local connection server failed ", err.Error())
return
}
conn.CopyWaitGroup(remoteConn, localTcpConn, false, false, nil, nil)
}
@ -62,6 +85,9 @@ func processSecret(localTcpConn net.Conn, config *config.CommonConfig, l *config
func processP2P(localTcpConn net.Conn, config *config.CommonConfig, l *config.LocalServer) {
if udpConn == nil {
newUdpConn(config, l)
if udpConn == nil {
return
}
muxSession = mux.NewMux(udpConn)
}
nowConn, err := muxSession.NewConn()
@ -110,6 +136,7 @@ func newUdpConn(config *config.CommonConfig, l *config.LocalServer) {
conn.SetUdpSession(localKcpConn)
if err != nil {
logs.Error(err)
return
}
//写入密钥、provider身份
if _, err := localKcpConn.Write([]byte(crypt.Md5(l.Password))); err != nil {

View File

@ -1,3 +1 @@
2,test1,,true,dsads,dsddsda,0,false,0,0,0
5,rilj9h70ux8yz3d2,,true,,,0,false,0,0,0
8,88,111,true,,,0,false,0,70,0
2,corjmrbhr33otit1,,true,,,0,false,0,0,0

1 2 test1 corjmrbhr33otit1 true dsads dsddsda 0 false 0 0 0
5 rilj9h70ux8yz3d2 true 0 false 0 0 0
8 88 111 true 0 false 0 70 0

View File

@ -1,3 +1 @@
b.o.com,127.0.0.1:8080,2,,,,,2,0,0
a.o.com,127.0.0.1:8082,8,,127.0.0.1:8080,,/,3,62428000,807503
c.o.com,127.0.0.1:8082,8,,,,,4,0,0
b.o.com,127.0.0.1:8080,2,,,111,/,3,0,0

1 b.o.com 127.0.0.1:8080 2 111 / 2 3 0 0
a.o.com 127.0.0.1:8082 8 127.0.0.1:8080 / 3 62428000 807503
c.o.com 127.0.0.1:8082 8 4 0 0

View File

@ -4,6 +4,9 @@ tp=tcp
vkey=123
auto_reconnection=true
[web]
host=a.o.com
target=127.0.0.1:8080
[tcp]
mode=tcp
target=8006-8010,8012
@ -18,6 +21,13 @@ port=9005
mode=httpProxy
port=9004
[file]
mode=file
port=9100
local_path=./
strip_pre=/web/
[s_ssh]
mode=secret
password=1234

View File

@ -1,7 +1,7 @@
appname = nps
#Web Management Port
httpport =
httpport = 8080
#Boot mode(dev|pro)
runmode = dev

View File

@ -1,4 +0,0 @@
0,p2p,,1,32,8,p2p ssh,0,0,p2pssh
9002,tcp,127.0.0.1:808022,1,1,8,dsa,0,0,
9001,tcp,5900,1,48,8,,0,0,
9999,socks5,,1,66,8,,0,0,
1 0 p2p 1 32 8 p2p ssh 0 0 p2pssh
0 p2p 1 32 8 p2p ssh 0 0 p2pssh
9002 tcp 127.0.0.1:808022 1 1 8 dsa 0 0
9001 tcp 5900 1 48 8 0 0
9999 socks5 1 66 8 0 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 KiB

After

Width:  |  Height:  |  Size: 270 KiB

View File

@ -9,6 +9,7 @@ const (
WORK_CONFIG = "conf"
WORK_REGISTER = "rgst"
WORK_SECRET = "sert"
WORK_FILE = "file"
WORK_P2P = "p2pm"
WORK_P2P_VISITOR = "p2pv"
WORK_P2P_PROVIDER = "p2pp"

View File

@ -183,6 +183,10 @@ func dealTunnel(s string) *file.Tunnel {
t.TargetAddr = item[1]
case "password":
t.Password = item[1]
case "local_path":
t.LocalPath = item[1]
case "strip_pre":
t.StripPre = item[1]
}
}
return t

View File

@ -302,7 +302,7 @@ func (s *Conn) SendTaskInfo(t *file.Tunnel) (int, error) {
*/
raw := bytes.NewBuffer([]byte{})
binary.Write(raw, binary.LittleEndian, []byte(common.NEW_TASK))
common.BinaryWrite(raw, t.Mode, t.Ports, t.Target, t.Remark, t.TargetAddr, t.Password)
common.BinaryWrite(raw, t.Mode, t.Ports, t.Target, t.Remark, t.TargetAddr, t.Password, t.LocalPath, t.StripPre)
s.Lock()
defer s.Unlock()
return s.Write(raw.Bytes())
@ -329,6 +329,8 @@ func (s *Conn) GetTaskInfo() (t *file.Tunnel, err error) {
t.Remark = arr[3]
t.TargetAddr = arr[4]
t.Password = arr[5]
t.LocalPath = arr[6]
t.StripPre = arr[7]
t.NoStore = true
}
return

View File

@ -148,7 +148,7 @@ func (s *Csv) GetIdByVerifyKey(vKey string, addr string) (int, error) {
func (s *Csv) NewTask(t *Tunnel) error {
for _, v := range s.Tasks {
if v.Mode == "secret" && v.Password == t.Password {
if (v.Mode == "secret" || v.Mode == "p2p") && v.Password == t.Password {
return errors.New(fmt.Sprintf("Secret mode keys %s must be unique", t.Password))
}
}
@ -159,10 +159,8 @@ func (s *Csv) NewTask(t *Tunnel) error {
}
func (s *Csv) UpdateTask(t *Tunnel) error {
for k, v := range s.Tasks {
for _, v := range s.Tasks {
if v.Id == t.Id {
s.Tasks = append(s.Tasks[:k], s.Tasks[k+1:]...)
s.Tasks = append(s.Tasks, t)
s.StoreTasksToCsv()
return nil
}
@ -332,6 +330,9 @@ func (s *Csv) NewHost(t *Host) error {
if s.IsHostExist(t) {
return errors.New("host has exist")
}
if t.Location == "" {
t.Location = "/"
}
t.Flow = new(Flow)
s.Hosts = append(s.Hosts, t)
s.StoreHostToCsv()
@ -339,10 +340,8 @@ func (s *Csv) NewHost(t *Host) error {
}
func (s *Csv) UpdateHost(t *Host) error {
for k, v := range s.Hosts {
for _, v := range s.Hosts {
if v.Host == t.Host {
s.Hosts = append(s.Hosts[:k], s.Hosts[k+1:]...)
s.Hosts = append(s.Hosts, t)
s.StoreHostToCsv()
return nil
}
@ -465,7 +464,7 @@ func (s *Csv) GetClient(id int) (v *Client, err error) {
}
func (s *Csv) GetClientIdByVkey(vkey string) (id int, err error) {
for _, v := range s.Clients {
if v.VerifyKey == vkey {
if crypt.Md5(v.VerifyKey) == vkey {
id = v.Id
return
}

View File

@ -77,6 +77,24 @@ func (s *Client) GetConn() bool {
return false
}
func (s *Client) HasTunnel(t *Tunnel) bool {
for _, v := range GetCsvDb().Tasks {
if v.Client.Id == s.Id && v.Port == t.Port {
return true
}
}
return false
}
func (s *Client) HasHost(h *Host) bool {
for _, v := range GetCsvDb().Hosts {
if v.Client.Id == s.Id && v.Host == h.Host && h.Location == v.Location {
return true
}
}
return false
}
type Tunnel struct {
Id int //Id
Port int //服务端监听端口
@ -91,6 +109,8 @@ type Tunnel struct {
Remark string //备注
TargetAddr string
NoStore bool
LocalPath string
StripPre string
}
type Config struct {

View File

@ -140,7 +140,7 @@ func (s *conn) Close() error {
close(s.connStatusOkCh)
close(s.connStatusFailCh)
close(s.readCh)
if !s.mux.isClose {
if !s.mux.IsClose {
s.sendMsgCh <- NewMsg(s.connId, nil)
}
return nil

View File

@ -21,6 +21,8 @@ const (
MUX_NEW_CONN
MUX_PING
MUX_CONN_CLOSE
MUX_PING_RETURN
RETRY_TIME = 2 //Heart beat allowed fault tolerance times
)
type Mux struct {
@ -32,7 +34,8 @@ type Mux struct {
newConnCh chan *conn
id int32
closeChan chan struct{}
isClose bool
IsClose bool
pingOk int
sync.Mutex
}
@ -45,7 +48,7 @@ func NewMux(c net.Conn) *Mux {
id: 0,
closeChan: make(chan struct{}),
newConnCh: make(chan *conn),
isClose: false,
IsClose: false,
}
//read session by flag
go m.readSession()
@ -57,7 +60,7 @@ func NewMux(c net.Conn) *Mux {
}
func (s *Mux) NewConn() (*conn, error) {
if s.isClose {
if s.IsClose {
return nil, errors.New("the mux has closed")
}
conn := NewConn(s.getId(), s, s.sendMsgCh, s.sendStatusCh)
@ -82,7 +85,7 @@ func (s *Mux) NewConn() (*conn, error) {
}
func (s *Mux) Accept() (net.Conn, error) {
if s.isClose {
if s.IsClose {
return nil, errors.New("accpet error,the conn has closed")
}
return <-s.newConnCh, nil
@ -107,10 +110,11 @@ func (s *Mux) ping() {
raw.Reset()
binary.Write(raw, binary.LittleEndian, MUX_PING_FLAG)
binary.Write(raw, binary.LittleEndian, MUX_PING)
if _, err := s.conn.Write(raw.Bytes()); err != nil {
if _, err := s.conn.Write(raw.Bytes()); err != nil || s.pingOk > RETRY_TIME {
s.Close()
break
}
s.pingOk += 1
}
}()
select {
@ -176,6 +180,13 @@ func (s *Mux) readSession() {
s.conn.Write(raw.Bytes())
continue
case MUX_PING_FLAG: //ping
raw.Reset()
binary.Write(raw, binary.LittleEndian, MUX_PING_RETURN)
binary.Write(raw, binary.LittleEndian, MUX_PING)
s.conn.Write(raw.Bytes())
continue
case MUX_PING_RETURN:
s.pingOk -= 1
continue
case MUX_NEW_MSG:
if n, err = ReadLenBytes(buf, s.conn); err != nil {
@ -212,10 +223,10 @@ func (s *Mux) readSession() {
}
func (s *Mux) Close() error {
if s.isClose {
if s.IsClose {
return errors.New("the mux has closed")
}
s.isClose = true
s.IsClose = true
s.connMap.Close()
s.closeChan <- struct{}{}
s.closeChan <- struct{}{}

View File

@ -6,6 +6,7 @@ import (
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
"net"
"net/http"
"sync"
@ -74,13 +75,17 @@ func (s *BaseServer) checkFlow() error {
func (s *BaseServer) DealClient(c *conn.Conn, addr string, rb []byte, tp string) error {
link := conn.NewLink(tp, addr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, c.Conn.RemoteAddr().String())
if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.Conn.RemoteAddr().String()); err != nil {
if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.Conn.RemoteAddr().String(), s.task); err != nil {
logs.Warn("task id %d get connection from client id %d error %s", s.task.Id, s.task.Client.Id, err.Error())
c.Close()
return err
} else {
if rb != nil {
target.Write(rb)
}
conn.CopyWaitGroup(target, c, link.Crypt, link.Compress, s.task.Client.Rate, s.task.Client.Flow)
}
s.task.Client.AddConn()
return nil
}

View File

@ -147,7 +147,7 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) {
break
}
lk := conn.NewLink(common.CONN_TCP, host.Target, host.Client.Cnf.Crypt, host.Client.Cnf.Compress, r.RemoteAddr)
if target, err = s.bridge.SendLinkInfo(host.Client.Id, lk, c.Conn.RemoteAddr().String()); err != nil {
if target, err = s.bridge.SendLinkInfo(host.Client.Id, lk, c.Conn.RemoteAddr().String(), nil); err != nil {
logs.Notice("connect to target %s error %s", lk.Host, err)
break
}

View File

@ -144,7 +144,7 @@ func (s *Sock5ModeServer) doConnect(c net.Conn, command uint8) {
//s.DealClient(conn.NewConn(c), addr, nil, ltype)
link := conn.NewLink(ltype, addr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, c.RemoteAddr().String())
if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.RemoteAddr().String()); err != nil {
if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.RemoteAddr().String(),s.task); err != nil {
c.Close()
return
} else {

View File

@ -50,7 +50,7 @@ func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
if err := s.checkFlow(); err != nil {
return
}
if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, addr.String()); err != nil {
if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, addr.String(), s.task); err != nil {
return
} else {
s.task.Flow.Add(int64(len(data)), 0)

View File

@ -79,7 +79,7 @@ func DealBridgeTask() {
func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string) {
Bridge = bridge.NewTunnel(bridgePort, bridgeType, common.GetBoolByStr(beego.AppConfig.String("ipLimit")), RunList)
if err := Bridge.StartTunnel(); err != nil {
logs.Error("服务端开启失败", err)
logs.Error("start server bridge error", err)
os.Exit(0)
} else {
logs.Info("Server startup, the bridge type is %s, the bridge port is %d", bridgeType, bridgePort)
@ -103,7 +103,7 @@ func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string) {
func NewMode(Bridge *bridge.Bridge, c *file.Tunnel) proxy.Service {
var service proxy.Service
switch c.Mode {
case "tcp":
case "tcp", "file":
service = proxy.NewTunnelModeServer(proxy.ProcessTunnel, Bridge, c)
case "socks5":
service = proxy.NewSock5ModeServer(Bridge, c)
@ -134,6 +134,7 @@ func StopServer(id int) error {
if err := svr.Close(); err != nil {
return err
}
logs.Info("stop server id %d", id)
}
if t, err := file.GetCsvDb().GetTask(id); err != nil {
return err
@ -144,7 +145,7 @@ func StopServer(id int) error {
delete(RunList, id)
return nil
}
return errors.New("未在运行中")
return errors.New("task is not running")
}
//add task

View File

@ -43,6 +43,11 @@ func (s *IndexController) Http() {
s.SetType("httpProxy")
s.display("index/list")
}
func (s *IndexController) File() {
s.SetInfo("file server")
s.SetType("file")
s.display("index/list")
}
func (s *IndexController) Secret() {
s.SetInfo("secret")
@ -85,14 +90,16 @@ func (s *IndexController) Add() {
s.display()
} else {
t := &file.Tunnel{
Port: s.GetIntNoErr("port"),
Mode: s.GetString("type"),
Target: s.GetString("target"),
Id: file.GetCsvDb().GetTaskId(),
Status: true,
Remark: s.GetString("remark"),
Password: s.GetString("password"),
Flow: &file.Flow{},
Port: s.GetIntNoErr("port"),
Mode: s.GetString("type"),
Target: s.GetString("target"),
Id: file.GetCsvDb().GetTaskId(),
Status: true,
Remark: s.GetString("remark"),
Password: s.GetString("password"),
LocalPath: s.GetString("local_path"),
StripPre: s.GetString("strip_pre"),
Flow: &file.Flow{},
}
if !tool.TestServerPort(t.Port, t.Mode) {
s.AjaxErr("The port cannot be opened because it may has been occupied or is no longer allowed.")
@ -101,7 +108,9 @@ func (s *IndexController) Add() {
if t.Client, err = file.GetCsvDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
s.AjaxErr(err.Error())
}
file.GetCsvDb().NewTask(t)
if err := file.GetCsvDb().NewTask(t); err != nil {
s.AjaxErr(err.Error())
}
if err := server.AddTask(t); err != nil {
s.AjaxErr(err.Error())
} else {
@ -140,11 +149,15 @@ func (s *IndexController) Edit() {
t.Target = s.GetString("target")
t.Password = s.GetString("password")
t.Id = id
t.LocalPath = s.GetString("local_path")
t.StripPre = s.GetString("strip_pre")
t.Remark = s.GetString("remark")
if t.Client, err = file.GetCsvDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
s.AjaxErr("modified error")
}
file.GetCsvDb().UpdateTask(t)
server.StopServer(t.Id)
server.StartTask(t.Id)
}
s.AjaxOk("modified success")
}

View File

@ -150,6 +150,13 @@
title: 'key',//标题
visible: true,//false表示不显示
sortable: true,//启用排序
formatter: function (value, row, index) {
if (!row.NoStore) {
return value
} else {
return "public vkey"
}
}
},
{
field: 'Addr',//域值

View File

@ -12,9 +12,10 @@
<option {{if eq "udp" .type}}selected{{end}} value="udp">udp</option>
<option {{if eq "socks5" .type}}selected{{end}} value="socks5">socks5
</option>
<option {{if eq "httpProxy" .type}}selected{{end}} value="httpProxy">http
<option {{if eq "secret" .type}}selected{{end}} value="secret">secret
<option {{if eq "p2p" .type}}selected{{end}} value="p2p">p2p
<option {{if eq "httpProxy" .type}}selected{{end}} value="httpProxy">http</option>
<option {{if eq "secret" .type}}selected{{end}} value="secret">secret</option>
<option {{if eq "p2p" .type}}selected{{end}} value="p2p">p2p</option>
{{/*<option {{if eq "file" .type}}selected{{end}} value="file">file*/}}
</select>
</div>
</div>
@ -33,6 +34,7 @@
<input class="form-control" type="text" name="port" placeholder="such as 8024">
</div>
</div>
<div class="form-group" id="target">
<label class="col-sm-2 control-label">target of Intranet(ip:port)</label>
<div class="col-sm-10">
@ -50,6 +52,22 @@
</div>
</div>
<div class="form-group" id="local_path">
<label class="col-sm-2 control-label">local path</label>
<div class="col-sm-10">
<input class="form-control" type="text" name="local_path"
placeholder="such as /tmp">
</div>
</div>
<div class="form-group" id="strip_pre">
<label class="col-sm-2 control-label">strip pre</label>
<div class="col-sm-10">
<input class="form-control" type="text" name="strip_pre"
placeholder="such as static">
</div>
</div>
<div class="form-group" id="password">
<label class="col-sm-2 control-label">unique identification key</label>
<div class="col-sm-10">
@ -75,13 +93,14 @@
</div>
<script>
var arr = []
arr["all"] = ["type", "port", "compress", "u", "p", "target", "password"]
arr["all"] = ["type", "port", "compress", "u", "p", "target", "password", "strip_pre", "local_path"]
arr["tcp"] = ["type", "port", "target", "compress", "u", "p", "tcp隧道模式提供一条tcp隧道适用于ssh、远程桌面等添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,访问公网服务器的设定端口,则相当于访问内网目标地址的目标端口"]
arr["udp"] = ["type", "port", "target", "compress", "udp隧道模式提供一条udp隧道适用于dns、内网dns访问等添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后访问公网服务器的设定端口则相当于访问内网目标地址的udp目标端口"]
arr["socks5"] = ["type", "port", "compress", "u", "p", "socks5代理模式内网socks5代理配合proxifer可如同使用vpn一样访问内网设备或资源添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后在外网环境下本机配置socks5代理即访问内网设备或者资源 "]
arr["httpProxy"] = ["type", "port", "compress", "u", "p", " http代理模式内网http代理可访问内网网站添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后在外网环境下本机配置http代理即访问内网站点"]
arr["secret"] = ["type", "target", "compress", "password", "u", "p", " http代理模式内网http代理可访问内网网站添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后在外网环境下本机配置http代理即访问内网站点"]
arr["p2p"] = ["type", "compress", "password", "u", "p", " http代理模式内网http代理可访问内网网站添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后在外网环境下本机配置http代理即访问内网站点"]
arr["file"] = ["type", "strip_pre", "local_path", "port", " http代理模式内网http代理可访问内网网站添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后在外网环境下本机配置http代理即访问内网站点"]
arrClientHide = ["compress", "u", "p", "crypt", "mux"]
function resetForm() {

View File

@ -13,9 +13,10 @@
<option {{if eq "udp" .t.Mode}}selected{{end}} value="udp">udp</option>
<option {{if eq "socks5" .t.Mode}}selected{{end}} value="socks5">socks5
</option>
<option {{if eq "httpProxy" .t.Mode}}selected{{end}} value="httpProxy">http
<option {{if eq "secret" .t.Mode}}selected{{end}} value="secret">secret
<option {{if eq "p2p" .t.Mode}}selected{{end}} value="p2p">p2p
<option {{if eq "httpProxy" .t.Mode}}selected{{end}} value="httpProxy">http</option>
<option {{if eq "secret" .t.Mode}}selected{{end}} value="secret">secret</option>
<option {{if eq "p2p" .t.Mode}}selected{{end}} value="p2p">p2p</option>
<option {{if eq "file" .t.Mode}}selected{{end}} value="file">file</option>
</select>
</div>
</div>
@ -31,7 +32,8 @@
<div class="form-group" id="port">
<label class="col-sm-2 control-label">port of server</label>
<div class="col-sm-10">
<input value="{{.t.Port}}" class="form-control" type="text" name="port" placeholder="such as 8024">
<input value="{{.t.Port}}" class="form-control" type="text" name="port"
placeholder="such as 8024">
</div>
</div>
<div class="form-group" id="target">
@ -46,11 +48,28 @@
<div class="form-group" id="client_id">
<label class="col-sm-2 control-label">id of client</label>
<div class="col-sm-10">
<input value="{{.t.Client.Id}}" value="{{.client_id}}" class="form-control" type="text" name="client_id"
<input value="{{.t.Client.Id}}" value="{{.client_id}}" class="form-control" type="text"
name="client_id"
placeholder="id of client">
</div>
</div>
<div class="form-group" id="local_path">
<label class="col-sm-2 control-label">local path</label>
<div class="col-sm-10">
<input value="{{.t.LocalPath}}" class="form-control" type="text" name="local_path"
placeholder="such as /tmp">
</div>
</div>
<div class="form-group" id="strip_pre">
<label class="col-sm-2 control-label">strip pre</label>
<div class="col-sm-10">
<input value="{{.t.StripPre}}" class="form-control" type="text" name="strip_pre"
placeholder="such as static">
</div>
</div>
<div class="form-group" id="password">
<label class="col-sm-2 control-label">unique identification key</label>
<div class="col-sm-10">
@ -63,8 +82,8 @@
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-success" href="#" id="add"><i
class="fa fa-fw fa-lg fa-eye"></i>save
</button>
class="fa fa-fw fa-lg fa-eye"></i>save
</button>
</div>
</div>
@ -73,17 +92,16 @@
</div>
</div>
</div>
</main>
<script>
var arr = []
arr["all"] = ["type", "port", "compress", "u", "p", "target","password"]
arr["all"] = ["type", "port", "compress", "u", "p", "target", "password", "local_path", "strip_pre"]
arr["tcp"] = ["type", "port", "target", "u", "p", "compress"]
arr["udp"] = ["type", "port", "target", "compress"]
arr["socks5"] = ["type", "port", "compress", "u", "p"]
arr["httpProxy"] = ["type", "port", "compress", "u", "p"]
arr["secret"] = ["type", "target", "compress", "u", "p","password"]
arr["p2p"] = ["type", "compress", "u", "p","password"]
arr["secret"] = ["type", "target", "compress", "u", "p", "password"]
arr["p2p"] = ["type", "password"]
arr["file"] = ["type", "port", "local_path", "strip_pre"]
arrClientHide = ["compress", "u", "p", "crypt", "mux"]
function resetForm() {

View File

@ -1,78 +1,22 @@
{{/*<div class="row">*/}}
{{/*<div class="col-md-3">*/}}
{{/*<div class="widget-small warning coloured-icon"><i class="icon fa fa-html5 fa-3x"></i>*/}}
{{/*<div class="info">*/}}
{{/*<h4>客户端连接端口</h4>*/}}
{{/*<p><b>{{.p}}</b></p>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*<div class="col-md-3">*/}}
{{/*<div class="widget-small danger coloured-icon"><i class="icon fa fa-home fa-3x"></i>*/}}
{{/*<div class="info">*/}}
{{/*<h4>当前TCP连接总数</h4>*/}}
{{/*<p><b>{{.data.tcpCount}}</b></p>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*<div class="col-md-3">*/}}
{{/*<div class="widget-small primary coloured-icon"><i class="icon fa fa-users fa-3x"></i>*/}}
{{/*<div class="info">*/}}
{{/*<h4>总客户端数</h4>*/}}
{{/*<p><b>{{.data.clientCount}}</b></p>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*<div class="col-md-3">*/}}
{{/*<div class="widget-small info coloured-icon"><i class="icon fa fa-user-secret fa-3x"></i>*/}}
{{/*<div class="info">*/}}
{{/*<h4>在线客户端数</h4>*/}}
{{/*<p><b>{{.data.clientOnlineCount}}</b></p>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*<div class="row">*/}}
{{/*<div class="col-md-6">*/}}
{{/*<div class="tile">*/}}
{{/*<h3 class="tile-title">流量</h3>*/}}
{{/*<div id="flow" style="width: 600px;height:400px;"></div>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*<div class="col-md-6">*/}}
{{/*<div class="tile">*/}}
{{/*<h3 class="tile-title">代理类型</h3>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
{{/*</div>*/}}
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
{{/*<span class="label label-success pull-right">月</span>*/}}
<h5>client connection port</h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{.p}}</h1>
{{/*<div class="stat-percent font-bold text-success">98% <i class="fa fa-bolt"></i></div>*/}}
{{/*<small>总收入</small>*/}}
</div>
</div>
</div>
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
{{/*<span class="label label-info pull-right">季</span>*/}}
<h5>number of clients</h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{.data.clientCount}}</h1>
{{/*<div class="stat-percent font-bold text-info">20% <i class="fa fa-level-up"></i></div>*/}}
{{/*<small>新订单</small>*/}}
</div>
</div>
</div>
@ -92,13 +36,10 @@
<div class="col-lg-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
{{/*<span class="label label-danger pull-right">低值</span>*/}}
<h5>number of tcp connections</h5>
</div>
<div class="ibox-content">
<h1 class="no-margins">{{.data.tcpCount}}</h1>
{{/*<div class="stat-percent font-bold text-danger">38% <i class="fa fa-level-down"></i></div>*/}}
{{/*<small>第一个月</small>*/}}
</div>
</div>
</div>

View File

@ -73,6 +73,9 @@
<li class="{{if eq "p2p" .menu}}active{{end}}">
<a href="/index/p2p"><i class="fa fa-dashcube"></i> <span class="nav-label">p2p</span></a>
</li>
<li class="{{if eq "file" .menu}}active{{end}}">
<a href="/index/file"><i class="fa fa-laptop"></i> <span class="nav-label">file</span></a>
</li>
</ul>
</div>