merge master

This commit is contained in:
cnlh 2020-01-13 21:46:26 +08:00
commit 39b9489c92
109 changed files with 4836 additions and 1716 deletions

58
.travis.yml Normal file
View File

@ -0,0 +1,58 @@
language: go
go:
- "1.13"
- master
services:
- docker
script:
- go test -v ./cmd/nps/
os:
- linux
before_deploy:
- chmod +x ./build.sh && chmod +x ./build.android.sh && ./build.sh
deploy:
provider: releases
api_key:
secure: ${TOKEN}
skip_cleanup: true
file:
- freebsd_386_client.tar.gz
- freebsd_386_server.tar.gz
- freebsd_amd64_client.tar.gz
- freebsd_amd64_server.tar.gz
- freebsd_arm_client.tar.gz
- freebsd_arm_server.tar.gz
- linux_386_client.tar.gz
- linux_386_server.tar.gz
- linux_amd64_client.tar.gz
- linux_amd64_server.tar.gz
- linux_arm64_client.tar.gz
- linux_arm64_server.tar.gz
- linux_arm_v5_client.tar.gz
- linux_arm_v6_client.tar.gz
- linux_arm_v7_client.tar.gz
- linux_arm_v5_server.tar.gz
- linux_arm_v6_server.tar.gz
- linux_arm_v7_server.tar.gz
- linux_mips64le_client.tar.gz
- linux_mips64le_server.tar.gz
- linux_mips64_client.tar.gz
- linux_mips64_server.tar.gz
- linux_mipsle_client.tar.gz
- linux_mipsle_server.tar.gz
- linux_mips_client.tar.gz
- linux_mips_server.tar.gz
- darwin_amd64_client.tar.gz
- darwin_amd64_server.tar.gz
- windows_386_client.tar.gz
- windows_386_server.tar.gz
- windows_amd64_client.tar.gz
- windows_amd64_server.tar.gz
- npc_syno.spk
- npc_sdk.tar.gz
- android_client.apk
on:
tags: true
all_branches: true

View File

@ -1,10 +1,10 @@
FROM golang as builder
WORKDIR /go/src/github.com/cnlh/nps
WORKDIR /go/src/ehang.io/nps
COPY . .
RUN go get -d -v ./...
RUN CGO_ENABLED=0 go build -ldflags="-w -s -extldflags -static" ./cmd/npc/npc.go
FROM scratch
COPY --from=builder /go/src/github.com/cnlh/nps/npc /
COPY --from=builder /go/src/ehang.io/nps/npc /
VOLUME /conf
ENTRYPOINT ["/npc"]

View File

@ -1,11 +1,11 @@
FROM golang as builder
WORKDIR /go/src/github.com/cnlh/nps
WORKDIR /go/src/ehang.io/nps
COPY . .
RUN go get -d -v ./...
RUN CGO_ENABLED=0 go build -ldflags="-w -s -extldflags -static" ./cmd/nps/nps.go
FROM scratch
COPY --from=builder /go/src/github.com/cnlh/nps/nps /
COPY --from=builder /go/src/github.com/cnlh/nps/web /web
COPY --from=builder /go/src/ehang.io/nps/nps /
COPY --from=builder /go/src/ehang.io/nps/web /web
VOLUME /conf
CMD ["/nps"]

1042
README.md

File diff suppressed because it is too large Load Diff

80
README_zh.md Normal file
View File

@ -0,0 +1,80 @@
# nps
![](https://img.shields.io/github/stars/ehang-io/nps.svg) ![](https://img.shields.io/github/forks/ehang-io/nps.svg)
[![Gitter](https://badges.gitter.im/cnlh-nps/community.svg)](https://gitter.im/cnlh-nps/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Build Status](https://travis-ci.org/ehang-io/nps.svg?branch=master)](https://travis-ci.org/ehang-io/nps)
![GitHub All Releases](https://img.shields.io/github/downloads/ehang-io/nps/total)
[README](https://github.com/ehang-io/nps/blob/master/README.md)|[中文文档](https://github.com/ehang-io/nps/blob/master/README_zh.md)
nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何**tcp、udp**上层协议访问内网网站、本地支付接口调试、ssh访问、远程桌面内网dns解析等等……此外还**支持内网http代理、内网socks5代理**、**p2p等**并带有功能强大的web管理端。
## 背景
![image](https://github.com/ehang-io/nps/blob/master/image/web.png?raw=true)
1. 做微信公众号开发、小程序开发等----> 域名代理模式
2. 想在外网通过ssh连接内网的机器做云服务器到内网服务器端口的映射----> tcp代理模式
3. 在非内网环境下使用内网dns或者需要通过udp访问内网机器等----> udp代理模式
4. 在外网使用HTTP代理访问内网站点----> http代理模式
5. 搭建一个内网穿透ss在外网如同使用内网vpn一样访问内网资源或者设备----> socks5代理模式
## 特点
- 协议支持全面兼容几乎所有常用协议例如tcp、udp、http(s)、socks5、p2p、http代理...
- 全平台兼容(linux、windows、macos、群辉等),支持一键安装为系统服务
- 控制全面,同时支持服务端和客户端控制
- https集成支持将后端代理和web服务转成https同时支持多证书
- 操作简单只需简单的配置即可在web ui上完成其余操作
- 展示信息全面,流量、系统信息、即时带宽、客户端版本等
- 扩展功能强大,该有的都有了(缓存、压缩、加密、流量限制、带宽限制、端口复用等等)
- 域名解析具备自定义header、404页面配置、host修改、站点保护、URL路由、泛解析等功能
- 服务端支持多用户和用户注册功能
**没找到你想要的功能?不要紧,点击[进入文档](https://ehang-io.github.io/nps)查找吧**
## 快速开始
### 安装
> [releases](https://github.com/ehang-io/nps/releases)
下载对应的系统版本即可,服务端和客户端是单独的
### 服务端启动
下载完服务器压缩包后,解压,然后进入解压后的文件夹
- 执行安装命令
对于linux|darwin ```sudo ./nps install```
对于windows管理员身份运行cmd进入安装目录 ```nps.exe install```
- 启动
对于linux|darwin ```sudo nps start```
对于windows管理员身份运行cmd进入程序目录 ```nps.exe start```
```安装后windows配置文件位于 C:\Program Files\npslinux和darwin位于/etc/nps```
**如果发现没有启动成功,可以查看日志(Windows日志文件位于当前运行目录下linux和darwin位于/var/log/nps.log)**
- 访问服务端ip:web服务端口默认为8080
- 使用用户名和密码登陆默认admin/123正式使用一定要更改
- 创建客户端
### 客户端连接
- 点击web管理中客户端前的+号,复制启动命令
- 执行启动命令linux直接执行即可windows将./npc换成npc.exe用cmd执行
如果需要注册到系统服务可查看[注册到系统服务](https://ehang-io.github.io/nps/#/use?id=注册到系统服务)
### 配置
- 客户端连接后在web中配置对应穿透服务即可
- 更多高级用法见[完整文档](https://ehang-io.github.io/nps/)
## 贡献
- 如果遇到bug可以直接提交至dev分支
- 使用遇到问题可以通过issues反馈
- 项目处于开发阶段,还有很多待完善的地方,如果可以贡献代码,请提交 PR 至 dev 分支
- 如果有新的功能特性反馈可以通过issues或者qq群反馈

View File

@ -11,30 +11,32 @@ import (
"sync"
"time"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/mux"
"ehang.io/nps/lib/version"
"ehang.io/nps/server/connection"
"ehang.io/nps/server/tool"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/common"
"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/lib/version"
"github.com/cnlh/nps/server/connection"
"github.com/cnlh/nps/server/tool"
)
type Client struct {
tunnel *mux.Mux
signal *conn.Conn
file *mux.Mux
Version string
retryTime int // it will be add 1 when ping not ok until to 3 will close the client
}
func NewClient(t, f *mux.Mux, s *conn.Conn) *Client {
func NewClient(t, f *mux.Mux, s *conn.Conn, vs string) *Client {
return &Client{
signal: s,
tunnel: t,
file: f,
signal: s,
tunnel: t,
file: f,
Version: vs,
}
}
@ -166,16 +168,23 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
return
}
//version check
if b, err := c.GetShortContent(32); err != nil || string(b) != crypt.Md5(version.GetVersion()) {
if b, err := c.GetShortLenContent(); err != nil || string(b) != version.GetVersion() {
logs.Info("The client %s version does not match", c.Conn.RemoteAddr())
c.Close()
return
}
//version get
var vs []byte
var err error
if vs, err = c.GetShortLenContent(); err != nil {
logs.Info("get client %s version error", err.Error())
c.Close()
return
}
//write server version to client
c.Write([]byte(crypt.Md5(version.GetVersion())))
c.SetReadDeadlineBySecond(5)
var buf []byte
var err error
//get vKey from client
if buf, err = c.GetShortContent(32); err != nil {
c.Close()
@ -191,7 +200,7 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
s.verifySuccess(c)
}
if flag, err := c.ReadFlag(); err == nil {
s.typeDeal(flag, c, id)
s.typeDeal(flag, c, id, string(vs))
} else {
logs.Warn(err, flag)
}
@ -214,7 +223,7 @@ func (s *Bridge) DelClient(id int) {
}
//use different
func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int, vs string) {
isPub := file.GetDb().IsPubClient(id)
switch typeVal {
case common.WORK_MAIN:
@ -223,17 +232,18 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
return
}
//the vKey connect by another ,close the client of before
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, nil, c)); ok {
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, nil, c, vs)); ok {
if v.(*Client).signal != nil {
v.(*Client).signal.WriteClose()
}
v.(*Client).signal = c
v.(*Client).Version = vs
}
go s.GetHealthFromClient(id, c)
logs.Info("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr())
case common.WORK_CHAN:
muxConn := mux.NewMux(c.Conn, s.tunnelType)
if v, ok := s.Client.LoadOrStore(id, NewClient(muxConn, nil, nil)); ok {
if v, ok := s.Client.LoadOrStore(id, NewClient(muxConn, nil, nil, vs)); ok {
v.(*Client).tunnel = muxConn
}
case common.WORK_CONFIG:
@ -254,7 +264,7 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
}
case common.WORK_FILE:
muxConn := mux.NewMux(c.Conn, s.tunnelType)
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, muxConn, nil)); ok {
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, muxConn, nil, vs)); ok {
v.(*Client).file = muxConn
}
case common.WORK_P2P:
@ -358,6 +368,7 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (ta
func (s *Bridge) ping() {
ticker := time.NewTicker(time.Second * 5)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@ -434,7 +445,7 @@ loop:
}
c.WriteAddOk()
c.Write([]byte(client.VerifyKey))
s.Client.Store(client.Id, NewClient(nil, nil, nil))
s.Client.Store(client.Id, NewClient(nil, nil, nil, ""))
}
case common.NEW_HOST:
h, err := c.GetHostInfo()

39
build.android.sh Normal file
View File

@ -0,0 +1,39 @@
#/bin/bash
#sudo apt-get install libgl1-mesa-dev xorg-dev
#go get github.com/ffdfgdfg/fyne-cross
#fyne-cross --targets=linux/amd64,windows/amd64,darwin/amd64 gui/npc/npc.go
cd /go
apt-get install libegl1-mesa-dev libgles2-mesa-dev libx11-dev -y
#go get -u fyne.io/fyne/cmd/fyne fyne.io/fyne
mkdir -p /go/src/fyne.io
cd src/fyne.io
git clone https://github.com/fyne-io/fyne.git
cd fyne
git checkout v1.2.0
go install -v ./cmd/fyne
#fyne package -os android fyne.io/fyne/cmd/hello
echo "fyne install success"
mkdir -p /go/src/ehang.io/nps
cp -R /app/* /go/src/ehang.io/nps
cd /go/src/ehang.io/nps
#go get -u fyne.io/fyne fyne.io/fyne/cmd/fyne
rm cmd/npc/sdk.go
#go get -u ./...
#go mod tidy
#rm -rf /go/src/golang.org/x/mobile
echo "tidy success"
cd /go/src/ehang.io/nps
go mod vendor
cd vendor
cp -R * /go/src
cd ..
rm -rf vendor
#rm -rf ~/.cache/*
echo "vendor success"
cd gui/npc
#rm -rf /go/src/golang.org/x/mobile
#go get -u fyne.io/fyne/cmd/fyne@v1.2.0
#export ANDROID_NDK_HOME=/usr/local/android_sdk/ndk-bundle
fyne package -appID org.nps.client -os android -icon ../../docs/logo.png
mv npc.apk /app/android_client.apk

179
build.sh Executable file
View File

@ -0,0 +1,179 @@
#/bash/sh
export VERSION=0.26.0
sudo apt-get install gcc-mingw-w64-i686
env GOOS=windows GOARCH=386 CGO_ENABLED=1 CC=i686-w64-mingw32-gcc go build -ldflags "-s -w -extldflags -static -extldflags -static" -buildmode=c-shared -o npc_sdk.dll cmd/npc/sdk.go
tar -czvf npc_sdk.tar.gz npc_sdk.dll npc_sdk.h
wget https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz
tar -xvf upx-3.95-amd64_linux.tar.xz
cp upx-3.95-amd64_linux/upx ./
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_386_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf freebsd_386_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf freebsd_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf freebsd_arm_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_arm_v7_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_arm_v6_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_arm_v5_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_arm64_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_mips64_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_mips64le_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_mipsle_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_mips_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf windows_386_client.tar.gz npc.exe conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf windows_amd64_client.tar.gz npc.exe conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf darwin_amd64_client.tar.gz npc conf/npc.conf conf/multi_account.conf
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf linux_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf linux_386_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf linux_arm_v5_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf linux_arm_v6_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf linux_arm_v7_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf linux_arm64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf freebsd_arm_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf freebsd_386_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf freebsd_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf linux_mips_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf linux_mips64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf linux_mips64le_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf linux_mipsle_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf darwin_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf windows_amd64_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps.exe
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf windows_386_server.tar.gz conf/nps.conf conf/tasks.json conf/clients.json conf/hosts.json conf/server.key conf/server.pem web/views web/static nps.exe
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
docker --version
docker run --rm -i -w /app -v $(pwd):/app -e ANDROID_HOME=/usr/local/android_sdk ffdfgdfg/fyne-cross:android /app/build.android.sh
git clone https://github.com/cnlh/spksrc.git ~/spksrc
mkdir ~/spksrc/nps && cp -rf ./* ~/spksrc/nps/
docker run -itd --name spksrc --env VERSION=$VERSION -v ~/spksrc:/spksrc synocommunity/spksrc /bin/bash
docker exec -it spksrc /bin/bash -c 'cd /spksrc && make setup && cd /spksrc/spk/npc && make'
cp ~/spksrc/packages/npc_noarch-all_$VERSION-1.spk ./npc_syno.spk
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
export DOCKER_CLI_EXPERIMENTAL=enabled
docker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d
docker buildx create --use --name mybuilder
docker buildx build --tag ffdfgdfg/nps:$VERSION --tag ffdfgdfg/nps:latest --output type=image,push=true --file Dockerfile.nps --platform=linux/amd64,linux/arm64,linux/386,linux/arm .
docker buildx build --tag ffdfgdfg/npc:$VERSION --tag ffdfgdfg/npc:latest --output type=image,push=true --file Dockerfile.npc --platform=linux/amd64,linux/arm64,linux/386,linux/arm .

View File

@ -2,18 +2,20 @@ package client
import (
"bufio"
"bytes"
"net"
"net/http"
"strconv"
"time"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/config"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/crypt"
"github.com/cnlh/nps/lib/mux"
"github.com/xtaci/kcp-go"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/config"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/mux"
)
type TRPClient struct {
@ -40,12 +42,20 @@ func NewRPClient(svraddr string, vKey string, bridgeConnType string, proxyUrl st
}
}
var NowStatus int
var CloseClient bool
//start
func (s *TRPClient) Start() {
CloseClient = false
retry:
if CloseClient {
return
}
NowStatus = 0
c, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_MAIN, s.proxyUrl)
if err != nil {
logs.Error("The connection server failed and will be reconnected in five seconds")
logs.Error("The connection server failed and will be reconnected in five seconds, error", err.Error())
time.Sleep(time.Second * 5)
goto retry
}
@ -64,6 +74,7 @@ retry:
if s.cnf != nil && len(s.cnf.Healths) > 0 {
go heathCheck(s.cnf.Healths, s.signal)
}
NowStatus = 1
//msg connection, eg udp
s.handleMain()
}
@ -156,7 +167,7 @@ func (s *TRPClient) newChan() {
func (s *TRPClient) handleChan(src net.Conn) {
lk, err := conn.NewConn(src).GetLinkInfo()
if err != nil {
if err != nil || lk == nil {
src.Close()
logs.Error("get connection info from server error ", err)
return
@ -165,7 +176,7 @@ func (s *TRPClient) handleChan(src net.Conn) {
lk.Host = common.FormatAddress(lk.Host)
//if Conn type is http, read the request and log
if lk.ConnType == "http" {
if targetConn, err := net.Dial(common.CONN_TCP, lk.Host); err != nil {
if targetConn, err := net.DialTimeout(common.CONN_TCP, lk.Host, lk.Option.Timeout); err != nil {
logs.Warn("connect to %s error %s", lk.Host, err.Error())
src.Close()
} else {
@ -188,8 +199,12 @@ func (s *TRPClient) handleChan(src net.Conn) {
}
return
}
if lk.ConnType == "udp5" {
logs.Trace("new %s connection with the goal of %s, remote address:%s", lk.ConnType, lk.Host, lk.RemoteAddr)
s.handleUdp(src)
}
//connect to target if conn type is tcp or udp
if targetConn, err := net.Dial(lk.ConnType, lk.Host); err != nil {
if targetConn, err := net.DialTimeout(lk.ConnType, lk.Host, lk.Option.Timeout); err != nil {
logs.Warn("connect to %s error %s", lk.Host, err.Error())
src.Close()
} else {
@ -198,6 +213,65 @@ func (s *TRPClient) handleChan(src net.Conn) {
}
}
func (s *TRPClient) handleUdp(serverConn net.Conn) {
// bind a local udp port
local, err := net.ListenUDP("udp", nil)
defer local.Close()
defer serverConn.Close()
if err != nil {
logs.Error("bind local udp port error ", err.Error())
return
}
go func() {
defer serverConn.Close()
b := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(b)
for {
n, raddr, err := local.ReadFrom(b)
if err != nil {
logs.Error("read data from remote server error", err.Error())
}
buf := bytes.Buffer{}
dgram := common.NewUDPDatagram(common.NewUDPHeader(0, 0, common.ToSocksAddr(raddr)), b[:n])
dgram.Write(&buf)
b, err := conn.GetLenBytes(buf.Bytes())
if err != nil {
logs.Warn("get len bytes error", err.Error())
continue
}
if _, err := serverConn.Write(b); err != nil {
logs.Error("write data to remote error", err.Error())
return
}
}
}()
b := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(b)
for {
n, err := serverConn.Read(b)
if err != nil {
logs.Error("read udp data from server error ", err.Error())
return
}
udpData, err := common.ReadUDPDatagram(bytes.NewReader(b[:n]))
if err != nil {
logs.Error("unpack data error", err.Error())
return
}
raddr, err := net.ResolveUDPAddr("udp", udpData.Header.Addr.String())
if err != nil {
logs.Error("build remote addr err", err.Error())
continue // drop silently
}
_, err = local.WriteTo(udpData.Data, raddr)
if err != nil {
logs.Error("write data to remote ", raddr.String(), "error", err.Error())
return
}
}
}
// Whether the monitor channel is closed
func (s *TRPClient) ping() {
s.ticker = time.NewTicker(time.Second * 5)
@ -214,6 +288,8 @@ loop:
}
func (s *TRPClient) Close() {
CloseClient = true
NowStatus = 0
if s.tunnel != nil {
s.tunnel.Close()
}

View File

@ -1,75 +0,0 @@
package client
import (
"net"
"sync"
"testing"
"github.com/cnlh/nps/lib/common"
conn2 "github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/file"
)
func TestConfig(t *testing.T) {
conn, err := net.Dial("tcp", "127.0.0.1:8284")
if err != nil {
t.Fail()
}
c := conn2.NewConn(conn)
c.SetAlive("tcp")
if _, err := c.Write([]byte(common.Getverifyval("123"))); err != nil {
t.Fail()
}
c.WriteConfig()
config := &file.Config{
U: "1",
P: "2",
Compress: "snappy",
Crypt: true,
CompressEncode: 0,
CompressDecode: 0,
}
host := &file.Host{
Host: "a.o.com",
Target: "127.0.0.1:8080",
HeaderChange: "",
HostChange: "",
Flow: nil,
Client: nil,
Remark: "111",
NowIndex: 0,
TargetArr: nil,
NoStore: false,
RWMutex: sync.RWMutex{},
}
tunnel := &file.Tunnel{
Port: 9001,
Mode: "tcp",
Target: "127.0.0.1:8082",
Remark: "333",
}
var b []byte
if b, err = c.ReadLen(16); err != nil {
t.Fail()
}
if _, err := c.SendConfigInfo(config); err != nil {
t.Fail()
}
if !c.GetAddStatus() {
t.Fail()
}
if _, err := c.SendHostInfo(host); err != nil {
t.Fail()
}
if !c.GetAddStatus() {
t.Fail()
}
if _, err := c.SendTaskInfo(tunnel); err != nil {
t.Fail()
}
if !c.GetAddStatus() {
t.Fail()
}
c.Close()
NewRPClient("127.0.0.1:8284", string(b), "tcp").Start()
}

View File

@ -19,12 +19,12 @@ import (
"strings"
"time"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/config"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/version"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/config"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/crypt"
"github.com/cnlh/nps/lib/version"
"github.com/xtaci/kcp-go"
"golang.org/x/net/proxy"
)
@ -198,7 +198,7 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
return nil, er
}
connection, err = n.Dial("tcp", server)
case "http":
default:
connection, err = NewHttpProxyConn(u, server)
}
} else {
@ -220,7 +220,10 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
if _, err := c.Write([]byte(common.CONN_TEST)); err != nil {
return nil, err
}
if _, err := c.Write([]byte(crypt.Md5(version.GetVersion()))); err != nil {
if err := c.WriteLenContent([]byte(version.GetVersion())); err != nil {
return nil, err
}
if err := c.WriteLenContent([]byte(version.VERSION)); err != nil {
return nil, err
}
b, err := c.GetShortContent(32)
@ -238,8 +241,7 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
if s, err := c.ReadFlag(); err != nil {
return nil, err
} else if s == common.VERIFY_EER {
logs.Error("Validation key %s incorrect", vkey)
os.Exit(0)
return nil, errors.New(fmt.Sprintf("Validation key %s incorrect", vkey))
}
if _, err := c.Write([]byte(connType)); err != nil {
return nil, err
@ -259,7 +261,7 @@ func NewHttpProxyConn(url *url.URL, remoteAddr string) (net.Conn, error) {
Proto: "HTTP/1.1",
}
password, _ := url.User.Password()
req.Header.Set("Proxy-Authorization", "Basic "+basicAuth(url.User.Username(), password))
req.Header.Set("Authorization", "Basic "+basicAuth(strings.Trim(url.User.Username(), " "), password))
b, err := httputil.DumpRequest(req, false)
if err != nil {
return nil, err
@ -369,6 +371,7 @@ func sendP2PTestMsg(localConn *net.UDPConn, remoteAddr1, remoteAddr2, remoteAddr
}
logs.Trace("try send test packet to target %s", addr)
ticker := time.NewTicker(time.Millisecond * 500)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@ -394,6 +397,7 @@ func sendP2PTestMsg(localConn *net.UDPConn, remoteAddr1, remoteAddr2, remoteAddr
return
}
ticker := time.NewTicker(time.Second * 2)
defer ticker.Stop()
for {
select {
case <-ticker.C:

View File

@ -7,10 +7,10 @@ import (
"strings"
"time"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/sheap"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/lib/sheap"
"github.com/pkg/errors"
)

View File

@ -1,19 +1,21 @@
package client
import (
"errors"
"net"
"net/http"
"runtime"
"sync"
"time"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/config"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/mux"
"ehang.io/nps/server/proxy"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/common"
"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/server/proxy"
"github.com/xtaci/kcp-go"
)
@ -31,6 +33,14 @@ type p2pBridge struct {
}
func (p2pBridge *p2pBridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) {
for i := 0; muxSession == nil; i++ {
if i >= 20 {
err = errors.New("p2pBridge:too many times to get muxSession")
logs.Error(err)
return
}
runtime.Gosched() // waiting for another goroutine establish the mux connection
}
nowConn, err := muxSession.NewConn()
if err != nil {
udpConn = nil
@ -117,6 +127,7 @@ func StartLocalServer(l *config.LocalServer, config *config.CommonConfig) error
func handleUdpMonitor(config *config.CommonConfig, l *config.LocalServer) {
ticker := time.NewTicker(time.Second * 1)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@ -157,6 +168,7 @@ func handleP2PVisitor(localTcpConn net.Conn, config *config.CommonConfig, l *con
if udpConn == nil {
logs.Notice("new conn, P2P can not penetrate successfully, traffic will be transferred through the server")
handleSecret(localTcpConn, config, l)
return
}
logs.Trace("start trying to connect with the server")
//TODO just support compress now because there is not tls file in client packages

View File

@ -5,7 +5,7 @@ import (
"log"
"os"
"github.com/cnlh/nps/lib/common"
"ehang.io/nps/lib/common"
)
func RegisterLocalIp(server string, vKey string, tp string, proxyUrl string, hour int) {

View File

@ -1,20 +1,21 @@
package main
import (
"ehang.io/nps/client"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/config"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/install"
"ehang.io/nps/lib/version"
"flag"
"fmt"
"os"
"strings"
"time"
"github.com/astaxie/beego/logs"
"github.com/ccding/go-stun/stun"
"github.com/cnlh/nps/client"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/config"
"github.com/cnlh/nps/lib/daemon"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/lib/version"
"github.com/kardianos/service"
"os"
"runtime"
"strings"
"time"
)
var (
@ -30,11 +31,60 @@ var (
password = flag.String("password", "", "p2p password flag")
target = flag.String("target", "", "p2p target")
localType = flag.String("local_type", "p2p", "p2p target")
logPath = flag.String("log_path", "npc.log", "npc log path")
logPath = flag.String("log_path", "", "npc log path")
debug = flag.Bool("debug", true, "npc debug")
)
func main() {
flag.Parse()
logs.Reset()
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(3)
if *logPath == "" {
*logPath = common.GetNpcLogPath()
}
if common.IsWindows() {
*logPath = strings.Replace(*logPath, "\\", "\\\\", -1)
}
if *debug {
logs.SetLogger(logs.AdapterConsole, `{"level":`+*logLevel+`,"color":true}`)
} else {
logs.SetLogger(logs.AdapterFile, `{"level":`+*logLevel+`,"filename":"`+*logPath+`","daily":false,"maxlines":100000,"color":true}`)
}
// init service
options := make(service.KeyValue)
options["Restart"] = "on-success"
options["SuccessExitStatus"] = "1 2 8 SIGKILL"
svcConfig := &service.Config{
Name: "Npc",
DisplayName: "nps内网穿透客户端",
Description: "一款轻量级、功能强大的内网穿透代理服务器。支持tcp、udp流量转发支持内网http代理、内网socks5代理同时支持snappy压缩、站点保护、加密传输、多路复用、header修改等。支持web图形化管理集成多用户模式。",
Option: options,
}
if !common.IsWindows() {
svcConfig.Dependencies = []string{
"Requires=network.target",
"After=network-online.target syslog.target"}
}
for _, v := range os.Args[1:] {
switch v {
case "install", "start", "stop", "uninstall", "restart":
continue
}
if !strings.Contains(v, "-service=") && !strings.Contains(v, "-debug=") {
svcConfig.Arguments = append(svcConfig.Arguments, v)
}
}
svcConfig.Arguments = append(svcConfig.Arguments, "-debug=false")
prg := &npc{
exit: make(chan struct{}),
}
s, err := service.New(prg, svcConfig)
if err != nil {
logs.Error(err)
return
}
if len(os.Args) >= 2 {
switch os.Args[1] {
case "status":
@ -45,6 +95,9 @@ func main() {
case "register":
flag.CommandLine.Parse(os.Args[2:])
client.RegisterLocalIp(*serverAddr, *verifyKey, *connType, *proxyUrl, *registerTime)
case "update":
install.UpdateNpc()
return
case "nat":
nat, host, err := stun.NewClient().Discover()
if err != nil || host == nil {
@ -53,16 +106,47 @@ func main() {
}
fmt.Printf("nat type: %s \npublic address: %s\n", nat.String(), host.String())
os.Exit(0)
case "install", "start", "stop", "uninstall", "restart":
if os.Args[1] == "install" {
service.Control(s, "stop")
service.Control(s, "uninstall")
install.InstallNpc()
}
err := service.Control(s, os.Args[1])
if err != nil {
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
}
return
}
}
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 {
logs.SetLogger(logs.AdapterFile, `{"level":`+*logLevel+`,"filename":"`+*logPath+`","daily":false,"maxlines":100000,"color":true}`)
s.Run()
}
type npc struct {
exit chan struct{}
}
func (p *npc) Start(s service.Service) error {
go p.run()
return nil
}
func (p *npc) Stop(s service.Service) error {
close(p.exit)
if service.Interactive() {
os.Exit(0)
}
return nil
}
func (p *npc) run() error {
defer func() {
if err := recover(); err != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
logs.Warning("npc: panic serving %v: %v\n%s", err, string(buf))
}
}()
//p2p or secret command
if *password != "" {
commonConfig := new(config.CommonConfig)
@ -76,8 +160,8 @@ func main() {
localServer.Port = *localPort
commonConfig.Client = new(file.Client)
commonConfig.Client.Cnf = new(file.Config)
client.StartLocalServer(localServer, commonConfig)
return
go client.StartLocalServer(localServer, commonConfig)
return nil
}
env := common.GetEnvMap()
if *serverAddr == "" {
@ -88,15 +172,22 @@ func main() {
}
logs.Info("the version of client is %s, the core version of client is %s", version.VERSION, version.GetVersion())
if *verifyKey != "" && *serverAddr != "" && *configPath == "" {
for {
client.NewRPClient(*serverAddr, *verifyKey, *connType, *proxyUrl, nil).Start()
logs.Info("It will be reconnected in five seconds")
time.Sleep(time.Second * 5)
}
go func() {
for {
client.NewRPClient(*serverAddr, *verifyKey, *connType, *proxyUrl, nil).Start()
logs.Info("It will be reconnected in five seconds")
time.Sleep(time.Second * 5)
}
}()
} else {
if *configPath == "" {
*configPath = "npc.conf"
*configPath = "conf/npc.conf"
}
client.StartFromFile(*configPath)
go client.StartFromFile(*configPath)
}
select {
case <-p.exit:
logs.Warning("stop...")
}
return nil
}

51
cmd/npc/sdk.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"C"
"ehang.io/nps/client"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/version"
"github.com/astaxie/beego/logs"
)
var cl *client.TRPClient
//export StartClientByVerifyKey
func StartClientByVerifyKey(serverAddr, verifyKey, connType, proxyUrl *C.char) int {
logs.SetLogger("store")
if cl != nil {
cl.Close()
}
cl = client.NewRPClient(C.GoString(serverAddr), C.GoString(verifyKey), C.GoString(connType), C.GoString(proxyUrl), nil)
go func() {
cl.Start()
return
}()
return 1
}
//export GetClientStatus
func GetClientStatus() int {
return client.NowStatus
}
//export CloseClient
func CloseClient() {
if cl != nil {
cl.Close()
}
}
//export Version
func Version() *C.char {
return C.CString(version.VERSION)
}
//export Logs
func Logs() *C.char {
return C.CString(common.GetLogMsg())
}
func main() {
// Need a main function to make CGO compile package as C shared library
}

View File

@ -1,46 +1,38 @@
package main
import (
"ehang.io/nps/lib/install"
"flag"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/daemon"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/version"
"ehang.io/nps/server"
"ehang.io/nps/server/connection"
"ehang.io/nps/server/tool"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/crypt"
"github.com/cnlh/nps/lib/daemon"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/lib/install"
"github.com/cnlh/nps/lib/version"
"github.com/cnlh/nps/server"
"github.com/cnlh/nps/server/connection"
"github.com/cnlh/nps/server/test"
"github.com/cnlh/nps/server/tool"
_ "github.com/cnlh/nps/web/routers"
"ehang.io/nps/web/routers"
"github.com/kardianos/service"
)
var (
level string
logType = flag.String("log", "stdout", "Log output modestdout|file")
level string
)
func main() {
flag.Parse()
beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf"))
if len(os.Args) > 1 {
switch os.Args[1] {
case "test":
test.TestServerConfig()
log.Println("test ok, no error")
return
case "start", "restart", "stop", "status", "reload":
daemon.InitDaemon("nps", common.GetRunPath(), common.GetTmpPath())
case "install":
install.InstallNps()
return
}
// init log
if err := beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf")); err != nil {
log.Fatalln("load config file error", err.Error())
}
if level = beego.AppConfig.String("log_level"); level == "" {
level = "7"
@ -48,11 +40,106 @@ func main() {
logs.Reset()
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(3)
if *logType == "stdout" {
logs.SetLogger(logs.AdapterConsole, `{"level":`+level+`,"color":true}`)
} else {
logs.SetLogger(logs.AdapterFile, `{"level":`+level+`,"filename":"`+beego.AppConfig.String("log_path")+`","daily":false,"maxlines":100000,"color":true}`)
logPath := beego.AppConfig.String("log_path")
if logPath == "" {
logPath = common.GetLogPath()
}
if common.IsWindows() {
logPath = strings.Replace(logPath, "\\", "\\\\", -1)
}
// init service
options := make(service.KeyValue)
options["Restart"] = "on-success"
options["SuccessExitStatus"] = "1 2 8 SIGKILL"
svcConfig := &service.Config{
Name: "Nps",
DisplayName: "nps内网穿透代理服务器",
Description: "一款轻量级、功能强大的内网穿透代理服务器。支持tcp、udp流量转发支持内网http代理、内网socks5代理同时支持snappy压缩、站点保护、加密传输、多路复用、header修改等。支持web图形化管理集成多用户模式。",
Option: options,
}
svcConfig.Arguments = append(svcConfig.Arguments, "service")
if len(os.Args) > 1 && os.Args[1] == "service" {
logs.SetLogger(logs.AdapterFile, `{"level":`+level+`,"filename":"`+logPath+`","daily":false,"maxlines":100000,"color":true}`)
} else {
logs.SetLogger(logs.AdapterConsole, `{"level":`+level+`,"color":true}`)
}
if !common.IsWindows() {
svcConfig.Dependencies = []string{
"Requires=network.target",
"After=network-online.target syslog.target"}
}
prg := &nps{}
prg.exit = make(chan struct{})
s, err := service.New(prg, svcConfig)
if err != nil {
logs.Error(err)
return
}
if len(os.Args) > 1 && os.Args[1] != "service" {
switch os.Args[1] {
case "reload":
daemon.InitDaemon("nps", common.GetRunPath(), common.GetTmpPath())
return
case "install":
// uninstall before
service.Control(s, "stop")
service.Control(s, "uninstall")
binPath := install.InstallNps()
svcConfig.Executable = binPath
s, err := service.New(prg, svcConfig)
if err != nil {
logs.Error(err)
return
}
err = service.Control(s, os.Args[1])
if err != nil {
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
}
return
case "start", "restart", "stop", "uninstall":
err := service.Control(s, os.Args[1])
if err != nil {
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
}
return
case "update":
install.UpdateNps()
return
default:
logs.Error("command is not support")
return
}
}
s.Run()
}
type nps struct {
exit chan struct{}
}
func (p *nps) Start(s service.Service) error {
p.run()
return nil
}
func (p *nps) Stop(s service.Service) error {
close(p.exit)
if service.Interactive() {
os.Exit(0)
}
return nil
}
func (p *nps) run() error {
defer func() {
if err := recover(); err != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
logs.Warning("nps: panic serving %v: %v\n%s", err, string(buf))
}
}()
routers.Init()
task := &file.Tunnel{
Mode: "webServer",
}
@ -66,5 +153,10 @@ func main() {
crypt.InitTls(filepath.Join(common.GetRunPath(), "conf", "server.pem"), filepath.Join(common.GetRunPath(), "conf", "server.key"))
tool.InitAllowPort()
tool.StartSystemInfo()
server.StartNewServer(bridgePort, task, beego.AppConfig.String("bridge_type"))
go server.StartNewServer(bridgePort, task, beego.AppConfig.String("bridge_type"))
select {
case <-p.exit:
logs.Warning("stop...")
}
return nil
}

View File

@ -26,7 +26,7 @@ public_vkey=123
# log level LevelEmergency->0 LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4 LevelNotice->5 LevelInformational->6 LevelDebug->7
log_level=7
log_path=nps.log
#log_path=nps.log
#Whether to restrict IP access, true or false or ignore
#ip_limit=true
@ -41,6 +41,12 @@ web_username=admin
web_password=123
web_port = 8080
web_ip=0.0.0.0
web_base_url=
web_open_ssl=false
web_cert_file=conf/server.pem
web_key_file=conf/server.key
# if web under proxy use sub path. like http://host/nps need this.
#web_base_url=/nps
#Web API unauthenticated IP address(the len of auth_crypt_key must be 16)
auth_key=test

0
docs/.nojekyll Normal file
View File

21
docs/README.md Normal file
View File

@ -0,0 +1,21 @@
# nps
![](https://img.shields.io/github/stars/cnlh/nps.svg) ![](https://img.shields.io/github/forks/cnlh/nps.svg)
[![Gitter](https://badges.gitter.im/cnlh-nps/community.svg)](https://gitter.im/cnlh-nps/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Build Status](https://travis-ci.org/cnlh/nps.svg?branch=master)](https://travis-ci.org/cnlh/nps)
nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何**tcp、udp**上层协议访问内网网站、本地支付接口调试、ssh访问、远程桌面内网dns解析等等……此外还**支持内网http代理、内网socks5代理**、**p2p等**并带有功能强大的web管理端。
## 背景
![image](https://github.com/ehang-io/nps/blob/master/image/web.png?raw=true)
1. 做微信公众号开发、小程序开发等----> 域名代理模式
2. 想在外网通过ssh连接内网的机器做云服务器到内网服务器端口的映射----> tcp代理模式
3. 在非内网环境下使用内网dns或者需要通过udp访问内网机器等----> udp代理模式
4. 在外网使用HTTP代理访问内网站点----> http代理模式
5. 搭建一个内网穿透ss在外网如同使用内网vpn一样访问内网资源或者设备----> socks5代理模式

16
docs/_coverpage.md Normal file
View File

@ -0,0 +1,16 @@
![logo](logo.svg)
# NPS <small>0.26.0</small>
> 一款轻量级、高性能、功能强大的内网穿透代理服务器
- 几乎支持所有协议
- 支持内网http代理、内网socks5代理、p2p等
- 简洁但功能强大的WEB管理界面
- 支持服务端、客户端同时控制
- 扩展功能强大
- 全平台兼容,一键注册为服务
[GitHub](https://github.com/ehang-io/nps/)
[开始使用](#nps)

28
docs/_sidebar.md Normal file
View File

@ -0,0 +1,28 @@
* 入门
* [安装](install.md)
* [启动](run.md)
* [使用示例](example.md)
* 服务端
* [介绍](introduction.md)
* [使用](nps_use.md)
* [配置文件](server_config.md)
* [增强功能](nps_extend.md)
* 客户端
* [基本使用](use.md)
* [增强功能](npc_extend.md)
* 扩展
* [功能](feature.md)
* [说明](description.md)
* [web api](api.md)
* [sdk](npc_sdk.md)
* 其他
* [贡献](contribute.md)
* [捐助](donate.md)
* [致谢](thanks.md)
* [交流](discuss.md)

46
docs/api.md Normal file
View File

@ -0,0 +1,46 @@
# web api
## webAPI验证说明
- 采用auth_key的验证方式
- 在提交的每个请求后面附带两个参数,`auth_key``timestamp`
```
auth_key的生成方式为md5(配置文件中的auth_key+当前时间戳)
```
```
timestamp为当前时间戳
```
```
curl --request POST \
--url http://127.0.0.1:8080/client/list \
--data 'auth_key=2a0000d9229e7dbcf79dd0f5e04bb084&timestamp=1553045344&start=0&limit=10'
```
**注意:** 为保证安全时间戳的有效范围为20秒内所以每次提交请求必须重新生成。
## 获取服务端时间
由于服务端与api请求的客户端时间差异不能太大所以提供了一个可以获取服务端时间的接口
```
POST /auth/gettime
```
## 获取服务端authKey
如果想获取authKey服务端提供获取authKey的接口
```
POST /auth/getauthkey
```
将返回加密后的authKey采用aes cbc加密请使用与服务端配置文件中cryptKey相同的密钥进行解密
**注意:** nps配置文件中`auth_crypt_key`需为16位
- 解密密钥长度128
- 偏移量与密钥相同
- 补码方式pkcs5padding
- 解密串编码方式 十六进制
## 详细文档
- **此文档近期可能更新较慢,建议自行抓包**
为方便第三方扩展在web模式下可利用webAPI进行相关操作详情见
[webAPI文档](https://github.com/ehang-io/nps/wiki/webAPI%E6%96%87%E6%A1%A3)

6
docs/contribute.md Normal file
View File

@ -0,0 +1,6 @@
# 贡献
- 如果遇到bug可以直接提交至dev分支
- 使用遇到问题可以通过issues反馈
- 项目处于开发阶段,还有很多待完善的地方,如果可以贡献代码,请提交 PR 至 dev 分支
- 如果有新的功能特性反馈可以通过issues或者qq群反馈

32
docs/description.md Normal file
View File

@ -0,0 +1,32 @@
# 说明
## 获取用户真实ip
在域名代理模式中可以通过request请求 header 中的 X-Forwarded-For 和 X-Real-IP 来获取用户真实 IP。
**本代理前会在每一个http(s)请求中添加了这两个 header。**
## 热更新支持
对于绝大多数配置在web管理中的修改将实时使用无需重启客户端或者服务端
## web端保护
在一分钟内如果密码错误次数超过10次该ip在一分钟内将不能再次登陆。
## 客户端地址显示
在web管理中将显示客户端的连接地址
## 流量统计
可统计显示每个代理使用的流量,由于压缩和加密等原因,会和实际环境中的略有差异
## 当前客户端带宽
可统计每个客户端当前的带宽,可能和实际有一定差异,仅供参考。
## 客户端与服务端版本对比
为了程序正常运行,客户端与服务端的核心版本必须一致,否则将导致客户端无法成功连接致服务端。
## Linux系统限制
默认情况下linux对连接数量有限制对于性能好的机器完全可以调整内核参数以处理更多的连接。
`tcp_max_syn_backlog` `somaxconn`
酌情调整参数,增强网络性能
## web管理保护
当一个ip连续登陆失败次数超过10次将在一分钟内禁止该ip再次尝试。

3
docs/discuss.md Normal file
View File

@ -0,0 +1,3 @@
# 交流群
![二维码.jpeg](https://i.loli.net/2019/02/15/5c66c32a42074.jpeg)

7
docs/donate.md Normal file
View File

@ -0,0 +1,7 @@
# 捐助
如果您觉得nps对你有帮助欢迎给予我们一定捐助也是帮助nps更好的发展。
## 支付宝
![image](https://github.com/ehang-io/nps/blob/master/image/donation_zfb.png?raw=true)
## 微信
![image](https://github.com/ehang-io/nps/blob/master/image/donation_wx.png?raw=true)

121
docs/example.md Normal file
View File

@ -0,0 +1,121 @@
# 使用示例
## 统一准备工作(必做)
- 开启服务端假设公网服务器ip为1.1.1.1,配置文件中`bridge_port`为8284配置文件中`web_port`为8080
- 访问1.1.1.1:8080
- 在客户端管理中创建一个客户端,记录下验证密钥
- 内网客户端运行windows使用cmd运行加.exe
```shell
./npc -server=1.1.1.1:8284 -vkey=客户端的密钥
```
**注意:运行服务端后,请确保能从客户端设备上正常访问配置文件中所配置的`bridge_port`端口telnetnetcat这类的来检查**
## 域名解析
**适用范围:** 小程序开发、微信公众号开发、产品演示
**假设场景:**
- 有一个域名proxy.com有一台公网机器ip为1.1.1.1
- 两个内网开发站点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
**使用步骤**
- 将*.proxy.com解析到公网服务器1.1.1.1
- 点击刚才创建的客户端的域名管理添加两条规则规则1、域名`a.proxy.com`,内网目标:`127.0.0.1:81`2、域名`b.proxy.com`,内网目标:`127.0.0.1:82`
现在访问http|https://`a.proxy.com``b.proxy.com`即可成功
**https:** 如需使用https请进行相关配置详见 [使用https](/nps_extend?id=使用https)
## tcp隧道
**适用范围:** ssh、远程桌面等tcp连接场景
**假设场景:**
想通过访问公网服务器1.1.1.1的8001端口连接内网机器10.1.50.101的22端口实现ssh连接
**使用步骤**
- 在刚才创建的客户端隧道管理中添加一条tcp隧道填写监听的端口8001、内网目标ip和目标端口10.1.50.101:22保存。
- 访问公网服务器ip1.1.1.1,填写的监听端口(8001)相当于访问内网ip(10.1.50.101):目标端口(22),例如:`ssh -p 8001 root@1.1.1.1`
## udp隧道
**适用范围:** 内网dns解析等udp连接场景
**假设场景:**
内网有一台dns10.1.50.102:53在非内网环境下想使用该dns公网服务器为1.1.1.1
**使用步骤**
- 在刚才创建的客户端的隧道管理中添加一条udp隧道填写监听的端口53、内网目标ip和目标端口10.1.50.102:53保存。
- 修改需要使用的dns地址为1.1.1.1则相当于使用10.1.50.102作为dns服务器
## socks5代理
**适用范围:** 在外网环境下如同使用vpn一样访问内网设备或者资源
**假设场景:**
想将公网服务器1.1.1.1的8003端口作为socks5代理达到访问内网任意设备或者资源的效果
**使用步骤**
- 在刚才创建的客户端隧道管理中添加一条socks5代理填写监听的端口8003保存。
- 在外网环境的本机配置socks5代理(例如使用proxifier进行全局代理)ip为公网服务器ip1.1.1.1),端口为填写的监听端口(8003),即可畅享内网了
**注意**
经过socks5代理当收到socks5数据包时socket已经是accept状态。表现是扫描端口全open建立连接后短时间关闭。若想同内网表现一致建议远程连接一台设备。
## http正向代理
**适用范围:** 在外网环境下使用http正向代理访问内网站点
**假设场景:**
想将公网服务器1.1.1.1的8004端口作为http代理访问内网网站
**使用步骤**
- 在刚才创建的客户端隧道管理中添加一条http代理填写监听的端口8004保存。
- 在外网环境的本机配置http代理ip为公网服务器ip1.1.1.1),端口为填写的监听端口(8004),即可访问了
## 私密代理
**适用范围:** 无需占用多余的端口、安全性要求较高可以防止其他人连接的tcp服务例如ssh。
**假设场景:**
无需新增多的端口实现访问内网服务器10.1.50.2的22端口
**使用步骤**
- 在刚才创建的客户端中添加一条私密代理并设置唯一密钥secrettest和内网目标10.1.50.2:22
- 在需要连接ssh的机器上以执行命令
```
./npc -server=1.1.1.1:8284 -vkey=vkey -type=tcp -password=secrettest -local_type=secret
```
如需指定本地端口可加参数`-local_port=xx`默认为2000
**注意:** password为web管理上添加的唯一密钥具体命令可查看web管理上的命令提示
假设10.1.50.2用户名为root现在执行`ssh -p 2000 root@1.1.1.1`即可访问ssh
## p2p服务
**适用范围:** 大流量传输场景流量不经过公网服务器但是由于p2p穿透和nat类型关系较大不保证100%成功支持大部分nat类型。[nat类型检测](/npc_extend?id=nat类型检测)
**假设场景:**
想通过访问使用端机器访问端也就是本机的2000端口---->访问到内网机器 10.2.50.2的22端口
**使用步骤**
- 在`nps.conf`中设置`p2p_ip`nps服务器ip`p2p_port`nps服务器udp端口
- 在刚才刚才创建的客户端中添加一条p2p代理并设置唯一密钥p2pssh
- 在使用端机器(本机)执行命令
```
./npc -server=1.1.1.1:8284 -vkey=123 -password=p2pssh -target=10.2.50.2:22
```
如需指定本地端口可加参数`-local_port=xx`默认为2000
**注意:** password为web管理上添加的唯一密钥具体命令可查看web管理上的命令提示
假设内网机器为10.2.50.2的ssh用户名为root现在在本机上执行`ssh -p 2000 root@127.0.0.1`即可访问机器2的ssh如果是网站在浏览器访问127.0.0.1:2000端口即可。

247
docs/feature.md Normal file
View File

@ -0,0 +1,247 @@
# 扩展功能
## 缓存支持
对于web站点来说一些静态文件往往消耗更大的流量且在内网穿透中静态文件还需到客户端获取一次这将导致更大的流量消耗。nps在域名解析代理中支持对静态文件进行缓存。
即假设一个站点有a.cssnps将只需从npc客户端读取一次该文件然后把该文件的内容放在内存中下一次将不再对npc客户端进行请求而直接返回内存中的对应内容。该功能默认是关闭的如需开启请在`nps.conf`中设置`http_cache=true`,并设置`http_cache_length`缓存文件的个数消耗内存不宜过大0表示不限制个数
## 数据压缩支持
由于是内网穿透内网客户端与服务端之间的隧道存在大量的数据交换为节省流量加快传输速度由此本程序支持SNNAPY形式的压缩。
- 所有模式均支持数据压缩
- 在web管理或客户端配置文件中设置
## 加密传输
如果公司内网防火墙对外网访问进行了流量识别与屏蔽例如禁止了ssh协议等通过设置 配置文件,将服务端与客户端之间的通信内容加密传输,将会有效防止流量被拦截。
- nps使用tls加密所以一定要保留conf目录下的密钥文件同时也可以自行生成
- 在web管理或客户端配置文件中设置
## 站点保护
域名代理模式所有客户端共用一个http服务端口在知道域名后任何人都可访问一些开发或者测试环境需要保密所以可以设置用户名和密码nps将通过 Http Basic Auth 来保护,访问时需要输入正确的用户名和密码。
- 在web管理或客户端配置文件中设置
## host修改
由于内网站点需要的host可能与公网域名不一致域名代理支持host修改功能即修改request的header中的host字段。
**使用方法在web管理中设置**
## 自定义header
支持对header进行新增或者修改以配合服务的需要
## 404页面配置
支持域名解析模式的自定义404页面修改/web/static/page/error.html中内容即可暂不支持静态文件等内容
## 流量限制
支持客户端级流量限制,当该客户端入口流量与出口流量达到设定的总量后会拒绝服务
域名代理会返回404页面其他代理会拒绝连接,使用该功能需要在`nps.conf`中设置`allow_flow_limit`,默认是关闭的。
## 带宽限制
支持客户端级带宽限制,带宽计算方式为入口和出口总和,权重均衡,使用该功能需要在`nps.conf`中设置`allow_rate_limit`,默认是关闭的。
## 负载均衡
本代理支持域名解析模式和tcp代理的负载均衡在web域名添加或者编辑中内网目标分行填写多个目标即可实现轮训级别的负载均衡
## 端口白名单
为了防止服务端上的端口被滥用可在nps.conf中配置allow_ports限制可开启的端口忽略或者不填表示端口不受限制格式
```ini
allow_ports=9001-9009,10001,11000-12000
```
## 端口范围映射
当客户端以配置文件的方式启动时可以将本地的端口进行范围映射仅支持tcp和udp模式例如
```ini
[tcp]
mode=tcp
server_port=9001-9009,10001,11000-12000
target_port=8001-8009,10002,13000-14000
```
逗号分隔,可单个或者范围,注意上下端口的对应关系,无法一一对应将不能成功
## 端口范围映射到其他机器
```ini
[tcp]
mode=tcp
server_port=9001-9009,10001,11000-12000
target_port=8001-8009,10002,13000-14000
target_ip=10.1.50.2
```
填写target_ip后则表示映射的该地址机器的端口忽略则便是映射本地127.0.0.1,仅范围映射时有效
## 守护进程
本代理支持守护进程,使用示例如下,服务端客户端所有模式通用,支持linuxdarwinwindows。
```
./(nps|npc) start|stop|restart|status 若有其他参数可加其他参数
```
```
(nps|npc).exe start|stop|restart|status 若有其他参数可加其他参数
```
## KCP协议支持
KCP 是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%在弱网环境下对性能能有一定的提升。可在nps.conf中修改`bridge_type`为kcp
设置后本代理将开启udp端口`bridge_port`
注意当服务端为kcp时客户端连接时也需要使用相同配置无配置文件模式加上参数type=kcp,配置文件模式在配置文件中设置tp=kcp
## 域名泛解析
支持域名泛解析例如将host设置为*.proxy.coma.proxy.com、b.proxy.com等都将解析到同一目标在web管理中或客户端配置文件中将host设置为此格式即可。
## URL路由
本代理支持根据URL将同一域名转发到不同的内网服务器可在web中或客户端配置文件中设置此参数也可忽略例如在客户端配置文件中
```ini
[web1]
host=a.proxy.com
target_addr=127.0.0.1:7001
location=/test
[web2]
host=a.proxy.com
target_addr=127.0.0.1:7002
location=/static
```
对于`a.proxy.com/test`将转发到`web1`,对于`a.proxy.com/static`将转发到`web2`
## 限制ip访问
如果将一些危险性高的端口例如ssh端口暴露在公网上可能会带来一些风险本代理支持限制ip访问。
**使用方法:** 在配置文件nps.conf中设置`ip_limit`=true设置后仅通过注册的ip方可访问。
**ip注册**
**方式一:**
在需要访问的机器上,运行客户端
```
./npc register -server=ip:port -vkey=公钥或客户端密钥 time=2
```
time为有效小时数例如time=2在当前时间后的两小时内本机公网ip都可以访问nps代理.
**方式二:**
此外nps的web登陆也可提供验证的功能成功登陆nps web admin后将自动为登陆的ip注册两小时的允许访问权限。
**注意:** 本机公网ip并不是一成不变的请自行注意有效期的设置同时同一网络下多人也可能是在公用同一个公网ip。
## 客户端最大连接数
为防止恶意大量长连接影响服务端程序的稳定性可以在web或客户端配置文件中为每个客户端设置最大连接数。该功能针对`socks5``http正向代理``域名代理``tcp代理``udp代理``私密代理`生效,使用该功能需要在`nps.conf`中设置`allow_connection_num_limit=true`,默认是关闭的。
## 客户端最大隧道数限制
nps支持对客户端的隧道数量进行限制该功能默认是关闭的如需开启请在`nps.conf`中设置`allow_tunnel_num_limit=true`
## 端口复用
在一些严格的网络环境中对端口的个数等限制较大nps支持强大端口复用功能。将`bridge_port``http_proxy_port``https_proxy_port``web_port`都设置为同一端口,也能正常使用。
- 使用时将需要复用的端口设置为与`bridge_port`一致即可,将自动识别。
- 如需将web管理的端口也复用需要配置`web_host`也就是一个二级域名以便区分
## 多路复用
nps主要通信默认基于多路复用无需开启。
多路复用基于TCP滑动窗口原理设计动态计算延迟以及带宽来算出应该往网络管道中打入的流量。
由于主要通信大多采用TCP协议并无法探测其实时丢包情况对于产生丢包重传的情况采用较大的宽容度
5分钟的等待时间超时将会关闭当前隧道连接并重新建立这将会抛弃当前所有的连接。
在Linux上可以通过调节内核参数来适应不同应用场景。
对于需求大带宽又有一定的丢包的场景,可以保持默认参数不变,尽可能少抛弃连接
高并发下可根据[Linux系统限制](## Linux系统限制) 调整
对于延迟敏感而又有一定丢包的场景可以适当调整TCP重传次数
`tcp_syn_retries`, `tcp_retries1`, `tcp_retries2`
高并发同上
nps会在系统主动关闭连接的时候拿到报错进而重新建立隧道连接
## 环境变量渲染
npc支持环境变量渲染以适应在某些特殊场景下的要求。
**在无配置文件启动模式下:**
设置环境变量
```
export NPC_SERVER_ADDR=1.1.1.1:8284
export NPC_SERVER_VKEY=xxxxx
```
直接执行./npc即可运行
**在配置文件启动模式下:**
```ini
[common]
server_addr={{.NPC_SERVER_ADDR}}
conn_type=tcp
vkey={{.NPC_SERVER_VKEY}}
auto_reconnection=true
[web]
host={{.NPC_WEB_HOST}}
target_addr={{.NPC_WEB_TARGET}}
```
在配置文件中填入相应的环境变量名称npc将自动进行渲染配置文件替换环境变量
## 健康检查
当客户端以配置文件模式启动时,支持多节点的健康检查。配置示例如下
```ini
[health_check_test1]
health_check_timeout=1
health_check_max_failed=3
health_check_interval=1
health_http_url=/
health_check_type=http
health_check_target=127.0.0.1:8083,127.0.0.1:8082
[health_check_test2]
health_check_timeout=1
health_check_max_failed=3
health_check_interval=1
health_check_type=tcp
health_check_target=127.0.0.1:8083,127.0.0.1:8082
```
**health关键词必须在开头存在**
第一种是http模式也就是以get的方式请求目标+url返回状态码为200表示成功
第一种是tcp模式也就是以tcp的方式与目标建立连接能成功建立连接表示成功
如果失败次数超过`health_check_max_failed`nps则会移除该npc下的所有该目标如果失败后目标重新上线nps将自动将目标重新加入。
项 | 含义
---|---
health_check_timeout | 健康检查超时时间
health_check_max_failed | 健康检查允许失败次数
health_check_interval | 健康检查间隔
health_check_type | 健康检查类型
health_check_target | 健康检查目标,多个以逗号(,)分隔
health_check_type | 健康检查类型
health_http_url | 健康检查url仅http模式适用
## 日志输出
日志输出级别
**对于npc**
```
-log_level=0~7 -log_path=npc.log
```
```
LevelEmergency->0 LevelAlert->1
LevelCritical->2 LevelError->3
LevelWarning->4 LevelNotice->5
LevelInformational->6 LevelDebug->7
```
默认为全输出,级别为0到7
**对于nps**
`nps.conf`中设置相关配置即可

42
docs/index.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="description" content="Description">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script src="//unpkg.com/docsify-edit-on-github/index.js"></script>
<script>
window.$docsify = {
name: '',
repo: '',
loadSidebar: true,
coverpage: true,
subMaxLevel: 2,
maxLevel: 4,
search: {
noData: "没有结果",
paths: 'auto',
placeholder: "搜索",
hideOtherSidebarContent: true, // whether or not to hide other sidebar content
},
plugins: [
EditOnGithubPlugin.create("https://github.com/ehang-io/nps/tree/master/docs/", "", "在github上编辑"),
]
}
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
<script src="//unpkg.com/docsify-copy-code"></script>
</body>
</html>

18
docs/install.md Normal file
View File

@ -0,0 +1,18 @@
# 安装
## 安装包安装
[releases](https://github.com/ehang-io/nps/releases)
下载对应的系统版本即可,服务端和客户端是单独的
## 源码安装
- 安装源码
```go get -u ehang.io/nps...```
- 编译
服务端```go build cmd/nps/nps.go```
客户端```go build cmd/npc/npc.go```
## docker安装
> [server](https://hub.docker.com/r/ffdfgdfg/nps)
> [client](https://hub.docker.com/r/ffdfgdfg/npc)

4
docs/introduction.md Normal file
View File

@ -0,0 +1,4 @@
![image](https://github.com/ehang-io/nps/blob/master/image/web2.png?raw=true)
# 介绍
可在网页上配置和管理各个tcp、udp隧道、内网站点代理http、https解析等功能强大操作方便。

BIN
docs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

1
docs/logo.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576165662444" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="50156" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M414.764 498.138c-4.094-8.188-13-12.844-22.094-11.562L181.58 516.73a21.496 21.496 0 0 0-12.062 6.032L48.926 643.4c-8.344 8.344-8.344 21.842 0 30.154l90.436 90.466c8.344 8.312 21.844 8.312 30.156 0l241.246-241.26a21.344 21.344 0 0 0 4-24.622zM507.262 842.428l30.156-211.09c1.312-9.094-3.376-17.968-11.562-22.094a21.34 21.34 0 0 0-24.624 4L259.984 854.49c-8.312 8.312-8.312 21.812 0 30.156l90.468 90.464c8.344 8.312 21.842 8.312 30.156 0l120.624-120.622a21.214 21.214 0 0 0 6.03-12.06z" fill="#DA4453" p-id="50157"></path><path d="M732.444 186.238a21.254 21.254 0 0 0-14.75-15.156l-93.778-26.876a21.288 21.288 0 0 0-20.938 5.438l-87.188 87.154a21.266 21.266 0 0 0-5.656 19.968 21.088 21.088 0 0 0 5.656 10.188 21.33 21.33 0 0 0 8.344 5.156l90.468 30.156a21.27 21.27 0 0 0 21.812-5.156l90.464-90.468a21.252 21.252 0 0 0 5.566-20.404zM879.786 400.078l-26.842-93.75a21.348 21.348 0 0 0-15.156-14.78 21.388 21.388 0 0 0-20.438 5.562l-90.468 90.466a21.348 21.348 0 0 0-5.156 21.812l30.156 90.466a21.33 21.33 0 0 0 5.156 8.344 21.4 21.4 0 0 0 10.188 5.688 21.356 21.356 0 0 0 19.968-5.688l87.186-87.156a21.39 21.39 0 0 0 5.406-20.964z" fill="#434A54" p-id="50158"></path><path d="M997.284 138.206c30.312-61.56 34.876-103.592 13.5-124.966-21.376-21.374-63.438-16.842-124.998 13.5-47.06 23.188-97.592 58.28-128.748 89.436L6.27 866.99c-4 4-6.25 9.406-6.25 15.062s2.25 11.094 6.25 15.094l120.624 120.59c8.312 8.344 21.812 8.344 30.156 0l750.768-750.782c31.154-31.156 66.278-81.686 89.466-128.748z" fill="#E6E9ED" p-id="50159"></path><path d="M907.816 266.954c31.154-31.156 66.28-81.686 89.466-128.748 30.312-61.56 34.876-103.592 13.5-124.966-21.376-21.374-63.438-16.842-124.998 13.5-47.06 23.188-97.592 58.28-128.748 89.436l150.78 150.778z" fill="#ED5564" p-id="50160"></path><path d="M157.048 1017.736l15.062-15.062-150.778-150.778-15.062 15.094c-4 4-6.25 9.406-6.25 15.062s2.25 11.094 6.25 15.094l120.624 120.59c8.312 8.344 21.81 8.344 30.154 0z" fill="#FFCE54" p-id="50161"></path><path d="M81.662 972.546l-30.156-30.156 30.156-30.154L111.814 942.39z" fill="#F6BB42" p-id="50162"></path><path d="M199.672 824.334c-8.312-8.344-8.312-21.844 0-30.156l241.246-241.246c8.344-8.312 21.844-8.312 30.156 0 8.344 8.344 8.344 21.844 0 30.156L229.828 824.334c-8.312 8.312-21.812 8.312-30.156 0z" fill="#ED5564" p-id="50163"></path><path d="M787.194 236.798c-8.312-8.312-21.812-8.312-30.156 0l-120.622 120.624c-8.312 8.344-8.312 21.844 0 30.156 8.342 8.344 21.842 8.344 30.154 0l120.624-120.624c8.344-8.312 8.344-21.812 0-30.156z" fill="#656D78" p-id="50164"></path><path d="M997.284 138.206c30.312-61.56 34.876-103.592 13.5-124.966a40.728 40.728 0 0 0-8.562-6.562c13.376 23.156 6.688 62.25-20.032 116.466-23.188 47.062-58.312 97.592-89.436 128.748L141.954 1002.674c-8.312 8.312-21.812 8.312-30.156 0l-60.312-60.31-45.216-45.248v0.032l120.624 120.59c8.312 8.344 21.812 8.344 30.156 0l750.768-750.782c31.154-31.158 66.278-81.688 89.466-128.75z" fill="#FFFFFF" opacity=".2" p-id="50165"></path></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

36
docs/npc_extend.md Normal file
View File

@ -0,0 +1,36 @@
# 增强功能
## nat类型检测
```
./npc nat
```
如果p2p双方都是Symmetric Nat肯定不能成功其他组合都有较大成功率。
## 状态检查
```
./npc status -config=npc配置文件路径
```
## 重载配置文件
```
./npc restart -config=npc配置文件路径
```
## 通过代理连接nps
有时候运行npc的内网机器无法直接访问外网此时可以可以通过socks5代理连接nps
对于配置文件方式启动,设置
```ini
[common]
proxy_url=socks5://111:222@127.0.0.1:8024
```
对于无配置文件模式,加上参数
```
-proxy=socks5://111:222@127.0.0.1:8024
```
支持socks5和http两种模式
即socks5://username:password@ip:port
或http://username:password@ip:port
## 群晖支持
可在releases中下载spk群晖套件例如`npc_x64-6.1_0.19.0-1.spk`

23
docs/npc_sdk.md Normal file
View File

@ -0,0 +1,23 @@
# npc sdk文档
```
命令行模式启动客户端
p0->连接地址
p1->vkey
p2->连接类型tcp or udp
p3->连接代理
extern GoInt StartClientByVerifyKey(char* p0, char* p1, char* p2, char* p3);
查看当前启动的客户端状态在线为1离线为0
extern GoInt GetClientStatus();
关闭客户端
extern void CloseClient();
获取当前客户端版本
extern char* Version();
获取日志,实时更新
extern char* Logs();
```

107
docs/nps_extend.md Normal file
View File

@ -0,0 +1,107 @@
# 增强功能
## 使用https
**方式一:** 类似于nginx实现https的处理
在配置文件中将https_proxy_port设置为443或者其他你想配置的端口和在web中对应域名编辑中设置对应的证书路径`https_just_proxy`设置为false然后就和http代理一样了
**此外:** 可以在`nps.conf`中设置一个默认的https配置当遇到未在web中设置https证书的域名解析时将自动使用默认证书另还有一种情况就是对于某些请求的clienthello不携带sni扩展信息nps也将自动使用默认证书
**方式二:** 在内网对应服务器上设置https
`nps.conf`中将`https_just_proxy`设置为true并且打开`https_proxy_port`端口然后nps将直接转发https请求到内网服务器上由内网服务器进行https处理
## 与nginx配合
有时候我们还需要在云服务器上运行nginx来保证静态文件缓存等本代理可和nginx配合使用在配置文件中将httpProxyPort设置为非80端口并在nginx中配置代理例如httpProxyPort为8024时
```
server {
listen 80;
server_name *.proxy.com;
location / {
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:8024;
}
}
```
如需使用https也可在nginx监听443端口并配置ssl并将本代理的httpsProxyPort设置为空关闭https即可例如httpProxyPort为8024时
```
server {
listen 443;
server_name *.proxy.com;
ssl on;
ssl_certificate certificate.crt;
ssl_certificate_key private.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:8024;
}
}
```
## web管理使用https
如果web管理需要使用https可以在配置文件`nps.conf`中设置`web_open_ssl=true`,并配置`web_cert_file``web_key_file`
## web使用Caddy代理
如果将web配置到Caddy代理,实现子路径访问nps,可以这样配置.
假设我们想通过 `http://caddy_ip:caddy_port/nps` 来访问后台, Caddyfile 这样配置:
```Caddyfile
caddy_ip:caddy_port/nps {
##server_ip 为 nps 服务器IP
##web_port 为 nps 后台端口
proxy / http://server_ip:web_port/nps {
transparent
}
}
```
nps.conf 修改 `web_base_url``/nps` 即可
```
web_base_url=/nps
```
## 关闭代理
如需关闭http代理可在配置文件中将http_proxy_port设置为空如需关闭https代理可在配置文件中将https_proxy_port设置为空。
## 流量数据持久化
服务端支持将流量数据持久化,默认情况下是关闭的,如果有需求可以设置`nps.conf`中的`flow_store_interval`参数,单位为分钟
**注意:** nps不会持久化通过公钥连接的客户端
## 系统信息显示
nps服务端支持在web上显示和统计服务器的相关信息但默认一些统计图表是关闭的如需开启请在`nps.conf`中设置`system_info_display=true`
## 自定义客户端连接密钥
web上可以自定义客户端连接的密钥但是必须具有唯一性
## 关闭公钥访问
可以将`nps.conf`中的`public_vkey`设置为空或者删除
## 关闭web管理
可以将`nps.conf`中的`web_port`设置为空或者删除
## 服务端多用户登陆
如果将`nps.conf`中的`allow_user_login`设置为true,服务端web将支持多用户登陆登陆用户名为user默认密码为每个客户端的验证密钥登陆后可以进入客户端编辑修改web登陆的用户名和密码默认该功能是关闭的。
## 用户注册功能
nps服务端支持用户注册功能可将`nps.conf`中的`allow_user_register`设置为true开启后登陆页将会有有注册功能
## 监听指定ip
nps支持每个隧道监听不同的服务端端口,在`nps.conf`中设置`allow_multi_ip=true`可在web中控制或者npc配置文件中(可忽略默认为0.0.0.0)
```ini
server_ip=xxx
```
## 代理到服务端本地
在使用nps监听80或者443端口时默认是将所有的请求都会转发到内网上但有时候我们的nps服务器的上一些服务也需要使用这两个端口nps提供类似于`nginx` `proxy_pass` 的功能支持将代理到服务器本地该功能支持域名解析tcp、udp隧道默认关闭。
**即:** 假设在nps的vps服务器上有一个服务使用5000端口这时候nps占用了80端口和443我们想能使用一个域名通过http(s)访问到5000的服务。
**使用方式:** 在`nps.conf`中设置`allow_local_proxy=true`然后在web上设置想转发的隧道或者域名然后选择转发到本地选项即可成功。

45
docs/nps_use.md Normal file
View File

@ -0,0 +1,45 @@
# 使用
**提示使用web模式时服务端执行文件必须在项目根目录否则无法正确加载配置文件**
## web管理
进入web界面公网ip:web界面端口默认8080密码默认为123
进入web管理界面有详细的说明
## 服务端配置文件重载
对于linux、darwin
```shell
sudo nps reload
```
对于windows
```shell
nps.exe reload
```
**说明:** 仅支持部分配置重载,例如`allow_user_login` `auth_crypt_key` `auth_key` `web_username` `web_password` 等,未来将支持更多
## 服务端停止或重启
对于linux、darwin
```shell
sudo nps stop|restart
```
对于windows
```shell
nps.exe stop|restart
```
## 服务端更新
请首先执行`sudo nps stop`或者`nps.exe stop`停止运行,然后
对于linux
```shell
sudo nps-update update
```
对于windows
```shell
nps-update.exe update
```
更新完成后,执行执行`sudo nps start`或者`nps.exe start`重新运行即可完成升级
如果无法更新成功可以直接自行下载releases压缩包然后覆盖原有的nps二进制文件和web目录

35
docs/run.md Normal file
View File

@ -0,0 +1,35 @@
# 启动
## 服务端
下载完服务器压缩包后,解压,然后进入解压后的文件夹
- 执行安装命令
对于linux|darwin ```sudo ./nps install```
对于windows管理员身份运行cmd进入安装目录 ```nps.exe install```
- 启动
对于linux|darwin ```sudo nps start```
对于windows管理员身份运行cmd进入程序目录 ```nps.exe start```
```安装后windows配置文件位于 C:\Program Files\npslinux和darwin位于/etc/nps```
停止和重启可用stop和restart
**如果发现没有启动成功,可以使用`nps(.exe) stop`,然后运行`nps.(exe)`运行调试,或查看日志**(Windows日志文件位于当前运行目录下linux和darwin位于/var/log/nps.log)
- 访问服务端ip:web服务端口默认为8080
- 使用用户名和密码登陆默认admin/123正式使用一定要更改
- 创建客户端
## 客户端
- 下载客户端安装包并解压,进入到解压目录
- 点击web管理中客户端前的+号,复制启动命令
- 执行启动命令linux直接执行即可windows将./npc换成npc.exe用cmd执行
如果需要注册到系统服务可查看[注册到系统服务](/use?id=注册到系统服务)
## 配置
- 客户端连接后在web中配置对应穿透服务即可
- 可以查看[使用示例](/example)

21
docs/server_config.md Normal file
View File

@ -0,0 +1,21 @@
# 服务端配置文件
- /conf/nps.conf
名称 | 含义
---|---
web_port | web管理端口
web_password | web界面管理密码
web_username | web界面管理账号
web_base_url | web管理主路径,用于将web管理置于代理子路径后面
bridge_port | 服务端客户端通信端口
https_proxy_port | 域名代理https代理监听端口
http_proxy_port | 域名代理http代理监听端口
auth_key|web api密钥
bridge_type|客户端与服务端连接方式kcp或tcp
public_vkey|客户端以配置文件模式启动时的密钥,设置为空表示关闭客户端配置文件连接模式
ip_limit|是否限制ip访问true或false或忽略
flow_store_interval|服务端流量数据持久化间隔,单位分钟,忽略表示不持久化
log_level|日志输出级别
auth_crypt_key | 获取服务端authKey时的aes加密密钥16位
p2p_ip| 服务端Ip使用p2p模式必填
p2p_port|p2p模式开启的udp端口

5
docs/thanks.md Normal file
View File

@ -0,0 +1,5 @@
Thanks [jetbrains](https://www.jetbrains.com/?from=nps) for providing development tools for nps
<html>
<img src="https://ftp.bmp.ovh/imgs/2019/12/6435398b0c7402b1.png" width="300" align=center />
</html>

221
docs/use.md Normal file
View File

@ -0,0 +1,221 @@
# 基本使用
## 无配置文件模式
此模式的各种配置在服务端web管理中完成,客户端除运行一条命令外无需任何其他设置
```
./npc -server=ip:port -vkey=web界面中显示的密钥
```
## 注册到系统服务(开机启动、守护进程)
对于linux、darwin
- 注册:`sudo ./npc install 其他参数(例如-server=xx -vkey=xx或者-config=xxx`
- 启动:`sudo npc start`
- 停止:`sudo npc stop`
- 如果需要更换命令内容需要先卸载`./npc uninstall`,再重新注册
对于windows使用管理员身份运行cmd
- 注册:`npc.exe install 其他参数(例如-server=xx -vkey=xx或者-config=xxx`
- 启动:`npc.exe start`
- 停止:`npc.exe stop`
- 如果需要更换命令内容需要先卸载`npc.exe uninstall`,再重新注册
注册到服务后日志文件windows位于当前目录下linux和darwin位于/var/log/npc.log
## 客户端更新
首先进入到对于的客户端二进制文件目录
请首先执行`sudo npc stop`或者`npc.exe stop`停止运行,然后
对于linux
```shell
sudo npc-update update
```
对于windows
```shell
npc-update.exe update
```
更新完成后,执行执行`sudo npc start`或者`npc.exe start`重新运行即可完成升级
如果无法更新成功可以直接自行下载releases压缩包然后覆盖原有的npc二进制文件
## 配置文件模式
此模式使用nps的公钥或者客户端私钥验证各种配置在客户端完成同时服务端web也可以进行管理
```
./npc -config=npc配置文件路径
```
## 配置文件说明
[示例配置文件](https://github.com/ehang-io/nps/tree/master/conf/npc.conf)
#### 全局配置
```ini
[common]
server_addr=1.1.1.1:8284
conn_type=tcp
vkey=123
username=111
password=222
compress=true
crypt=true
rate_limit=10000
flow_limit=100
remark=test
max_conn=10
```
项 | 含义
---|---
server_addr | 服务端ip:port
conn_type | 与服务端通信模式(tcp或kcp)
vkey|服务端配置文件中的密钥(非web)
username|socks5或http(s)密码保护用户名(可忽略)
password|socks5或http(s)密码保护密码(可忽略)
compress|是否压缩传输(true或false或忽略)
crypt|是否加密传输(true或false或忽略)
rate_limit|速度限制,可忽略
flow_limit|流量限制,可忽略
remark|客户端备注,可忽略
max_conn|最大连接数,可忽略
#### 域名代理
```ini
[common]
server_addr=1.1.1.1:8284
vkey=123
[web1]
host=a.proxy.com
target_addr=127.0.0.1:8080,127.0.0.1:8082
host_change=www.proxy.com
header_set_proxy=nps
```
项 | 含义
---|---
web1 | 备注
host | 域名(http|https都可解析)
target_addr|内网目标,负载均衡时多个目标,逗号隔开
host_change|请求host修改
header_xxx|请求header修改或添加header_proxy表示添加header proxy:nps
#### tcp隧道模式
```ini
[common]
server_addr=1.1.1.1:8284
vkey=123
[tcp]
mode=tcp
target_addr=127.0.0.1:8080
server_port=9001
```
项 | 含义
---|---
mode | tcp
server_port | 在服务端的代理端口
tartget_addr|内网目标
#### udp隧道模式
```ini
[common]
server_addr=1.1.1.1:8284
vkey=123
[udp]
mode=udp
target_addr=127.0.0.1:8080
server_port=9002
```
项 | 含义
---|---
mode | udp
server_port | 在服务端的代理端口
target_addr|内网目标
#### http代理模式
```ini
[common]
server_addr=1.1.1.1:8284
vkey=123
[http]
mode=httpProxy
server_port=9003
```
项 | 含义
---|---
mode | httpProxy
server_port | 在服务端的代理端口
#### socks5代理模式
```ini
[common]
server_addr=1.1.1.1:8284
vkey=123
[socks5]
mode=socks5
server_port=9004
multi_account=multi_account.conf
```
项 | 含义
---|---
mode | socks5
server_port | 在服务端的代理端口
multi_account | socks5多账号配置文件可选),配置后使用basic_username和basic_password无法通过认证
#### 私密代理模式
```ini
[common]
server_addr=1.1.1.1:8284
vkey=123
[secret_ssh]
mode=secret
password=ssh2
target_addr=10.1.50.2:22
```
项 | 含义
---|---
mode | secret
password | 唯一密钥
target_addr|内网目标
#### p2p代理模式
```ini
[common]
server_addr=1.1.1.1:8284
vkey=123
[p2p_ssh]
mode=p2p
password=ssh2
target_addr=10.1.50.2:22
```
项 | 含义
---|---
mode | p2p
password | 唯一密钥
target_addr|内网目标
#### 文件访问模式
利用nps提供一个公网可访问的本地文件服务此模式仅客户端使用配置文件模式方可启动
```ini
[common]
server_addr=1.1.1.1:8284
vkey=123
[file]
mode=file
server_port=9100
local_path=/tmp/
strip_pre=/web/
````
项 | 含义
---|---
mode | file
server_port | 服务端开启的端口
local_path|本地文件目录
strip_pre|前缀
对于`strip_pre`,访问公网`ip:9100/web/`相当于访问`/tmp/`目录
#### 断线重连
```ini
[common]
auto_reconnection=true
```

233
docs/webapi.md Normal file
View File

@ -0,0 +1,233 @@
获取客户端列表
```
POST /client/list/
```
| 参数 | 含义 |
| --- | --- |
| search | 搜索 |
| order | 排序asc 正序 desc倒序 |
| offset | 分页(第几页) |
| limit | 条数(分页显示的条数) |
***
获取单个客户端
```
POST /client/getclient/
```
| 参数 | 含义 |
| --- | --- |
| id | 客户端id |
***
添加客户端
```
POST /client/add/
```
| 参数 | 含义 |
| --- | --- |
| remark | 备注 |
| u | basic权限认证用户名 |
| p | basic权限认证密码 |
| limit | 条数(分页显示的条数) |
| vkey | 客户端验证密钥 |
| config\_conn\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 |
| compress | 压缩1允许 0不允许 |
| crypt | 是否加密1或者01允许 0不允许 |
| rate\_limit | 带宽限制 单位KB/S 空则为不限制 |
| flow\_limit | 流量限制 单位M 空则为不限制 |
| max\_conn | 客户端最大连接数量 空则为不限制 |
| max\_tunnel | 客户端最大隧道数量 空则为不限制 |
***
修改客户端(25.4版本有问题暂时不能用)
```
POST /client/edit/
```
| 参数 | 含义 |
| --- | --- |
| remark | 备注 |
| u | basic权限认证用户名 |
| p | basic权限认证密码 |
| limit | 条数(分页显示的条数) |
| vkey | 客户端验证密钥 |
| config\_conn\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 |
| compress | 压缩1允许 0不允许 |
| crypt | 是否加密1或者01允许 0不允许 |
| rate\_limit | 带宽限制 单位KB/S 空则为不限制 |
| flow\_limit | 流量限制 单位M 空则为不限制 |
| max\_conn | 客户端最大连接数量 空则为不限制 |
| max\_tunnel | 客户端最大隧道数量 空则为不限制 |
| id | 要修改的客户端id |
***
删除客户端
```
POST /client/del/
```
| 参数 | 含义 |
| --- | --- |
| id | 要删除的客户端id |
***
获取域名解析列表
```
POST /index/hostlist/
```
| 参数 | 含义 |
| --- | --- |
| search | 搜索(可以搜域名/备注什么的) |
| offset | 分页(第几页) |
| limit | 条数(分页显示的条数) |
***
添加域名解析
```
POST /index/addhost/
```
| 参数 | 含义 |
| --- | --- |
| remark | 备注 |
| host | 域名 |
| scheme | 协议类型(三种 all http https) |
| location | url路由 空则为不限制 |
| client\_id | 客户端id |
| target | 内网目标(ip:端口) |
| header | request header 请求头 |
| hostchange | request host 请求主机 |
***
修改域名解析
```
POST /index/edithost/
```
| 参数 | 含义 |
| --- | --- |
| remark | 备注 |
| host | 域名 |
| scheme | 协议类型(三种 all http https) |
| location | url路由 空则为不限制 |
| client\_id | 客户端id |
| target | 内网目标(ip:端口) |
| header | request header 请求头 |
| hostchange | request host 请求主机 |
| id | 需要修改的域名解析id |
***
删除域名解析
```
POST /index/delhost/
```
| 参数 | 含义 |
| --- | --- |
| id | 需要删除的域名解析id |
***
获取单条隧道信息
```
POST /index/getonetunnel/
```
| 参数 | 含义 |
| --- | --- |
| id | 隧道的id |
***
获取隧道列表
```
POST /index/gettunnel/
```
| 参数 | 含义 |
| --- | --- |
| client\_id | 穿透隧道的客户端id |
| type | 类型tcp udp httpProx socks5 secret p2p |
| search | 搜索 |
| offset | 分页(第几页) |
| limit | 条数(分页显示的条数) |
***
添加隧道
```
POST /index/add/
```
| 参数 | 含义 |
| --- | --- |
| type | 类型tcp udp httpProx socks5 secret p2p |
| remark | 备注 |
| port | 服务端端口 |
| target | 目标(ip:端口) |
| client\_id | 客户端id |
***
修改隧道
```
POST /index/edit/
```
| 参数 | 含义 |
| --- | --- |
| type | 类型tcp udp httpProx socks5 secret p2p |
| remark | 备注 |
| port | 服务端端口 |
| target | 目标(ip:端口) |
| client\_id | 客户端id |
| id | 隧道id |
***
删除隧道
```
POST /index/del/
```
| 参数 | 含义 |
| --- | --- |
| id | 隧道id |
***
隧道停止工作
```
POST /index/stop/
```
| 参数 | 含义 |
| --- | --- |
| id | 隧道id |
***
隧道开始工作
```
POST /index/start/
```
| 参数 | 含义 |
| --- | --- |
| id | 隧道id |

20
go.mod
View File

@ -1,28 +1,32 @@
module github.com/cnlh/nps
module ehang.io/nps
go 1.12
go 1.13
require (
fyne.io/fyne v1.2.0
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/astaxie/beego v1.12.0
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff // indirect
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c // indirect
github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d
github.com/dsnet/compress v0.0.1 // indirect
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db
github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 // indirect
github.com/kardianos/service v1.0.0
github.com/klauspost/cpuid v1.2.1 // indirect
github.com/klauspost/pgzip v1.2.1 // indirect
github.com/klauspost/reedsolomon v1.9.2 // indirect
github.com/onsi/gomega v1.5.0 // indirect
github.com/panjf2000/ants/v2 v2.2.2
github.com/pkg/errors v0.8.0
github.com/pkg/errors v0.8.1
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
github.com/shirou/gopsutil v2.18.12+incompatible
github.com/stretchr/testify v1.3.0 // indirect
github.com/shirou/gopsutil v2.19.11+incompatible
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
github.com/tjfoc/gmsm v1.0.1 // indirect
github.com/xtaci/kcp-go v5.4.4+incompatible
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
golang.org/x/net v0.0.0-20181220203305-927f97764cc3
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa // indirect
)

78
go.sum
View File

@ -1,13 +1,18 @@
fyne.io/fyne v1.2.0 h1:mdp7Cs7QmSJTeazYxEDa9wWeJNig7paBcjm0dooFtLE=
fyne.io/fyne v1.2.0/go.mod h1:Ab+3DIB/FVteW0y4DXfmZv4N3JdnCBh2lHkINI02BOU=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/astaxie/beego v1.12.0 h1:MRhVoeeye5N+Flul5PoVfD9CslfdoH+xqC/xvSQ5u2Y=
github.com/astaxie/beego v1.12.0/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c h1:FUUopH4brHNO2kJoNN3pV+OBEYmgraLT/KHZrMM69r0=
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c h1:aprLqMn7gSPT+vdDSl+/E6NLEuArwD/J7IWd8bJt5lQ=
github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c/go.mod h1:Ie6SubJv/NTO9Q0UBH0QCl3Ve50lu9hjbi5YJUw03TE=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZT9z33pyLEprMd6CUSfhbmMY57Io=
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE=
@ -18,50 +23,75 @@ github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFl
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/exfly/beego v1.12.0-export-init h1:VQNYKdXhAwZGUaFmQv8Aj921O3rQJZRIF8xeGrhsjrI=
github.com/exfly/beego v1.12.0-export-init/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o=
github.com/exfly/beego v1.12.0 h1:OXwIwngaAx35Mga+jLiZmArusBxj8/H0jYXzGDAdwOg=
github.com/exfly/beego v1.12.0/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM=
github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 h1:WgfvpuKg42WVLkxNwzfFraXkTXPK36bMqXvMFN67clI=
github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214/go.mod h1:kj6hFWqfwSjFjLnYW5PK1DoxZ4O0uapwHRmd9jhln4E=
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
github.com/josephspurrier/goversioninfo v0.0.0-20190124120936-8611f5a5ff3f/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
github.com/kardianos/service v1.0.0 h1:HgQS3mFfOlyntWX8Oke98JcJLqt1DBcHR4kxShpYef0=
github.com/kardianos/service v1.0.0/go.mod h1:8CzDhVuCuugtsHyZoTvsOBuvonN/UDBvl0kH+BUxvbo=
github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM=
github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/reedsolomon v1.9.2 h1:E9CMS2Pqbv+C7tsrYad4YC9MfhnMVWhMRsTi7U0UB18=
github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/panjf2000/ants/v2 v2.2.2 h1:TWzusBjq/IflXhy+/S6u5wmMLCBdJnB9tPIx9Zmhvok=
github.com/panjf2000/ants/v2 v2.2.2/go.mod h1:1GFm8bV8nyCQvU5K4WvBCTG1/YBFOD2VzjffD8fV55A=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v2.19.11+incompatible h1:lJHR0foqAjI4exXqWsU3DbH7bX1xvdhGdnXTIARA9W4=
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/srwiley/oksvg v0.0.0-20190829233741-58e08c8fe40e h1:LJUrNHytcMXWKxnULIHPe5SCb1jDpO9o672VB1x2EuQ=
github.com/srwiley/oksvg v0.0.0-20190829233741-58e08c8fe40e/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
github.com/srwiley/rasterx v0.0.0-20181219215540-696f7edb7a7e h1:FFotfUvew9Eg02LYRl8YybAnm0HCwjjfY5JlOI1oB00=
github.com/srwiley/rasterx v0.0.0-20181219215540-696f7edb7a7e/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 h1:Ko2LQMrRU+Oy/+EDBwX7eZ2jp3C47eDBB8EIhKTun+I=
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
@ -69,6 +99,8 @@ github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkB
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU=
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/xtaci/kcp-go v5.4.4+incompatible h1:QIJ0a0Q0N1G20yLHL2+fpdzyy2v/Cb3PI+xiwx/KK9c=
github.com/xtaci/kcp-go v5.4.4+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
@ -76,17 +108,25 @@ github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+A
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa h1:KIDDMLT1O0Nr7TSxp8xM5tJcdn8tgyAONntO829og1M=
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

37
gui/npc/AndroidManifest.xml Executable file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="0.26.0"
package="org.nps.client"
platformBuildVersionCode="15"
platformBuildVersionName="4.0.4-1406430">
<uses-permission
android:name="android.permission.INTERNET"/>
<application
android:label="Npc"
android:debuggable="true">
<activity
android:label="Npc"
android:name="org.golang.app.GoNativeActivity"
android:configChanges="0xa0">
<meta-data
android:name="android.app.lib_name"
android:value="npc"/>
<intent-filter>
<action
android:name="android.intent.action.MAIN"/>
<category
android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

173
gui/npc/npc.go Normal file
View File

@ -0,0 +1,173 @@
package main
import (
"ehang.io/nps/client"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/daemon"
"ehang.io/nps/lib/version"
"fmt"
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/layout"
"fyne.io/fyne/widget"
"github.com/astaxie/beego/logs"
"io/ioutil"
"os"
"path"
"runtime"
"strings"
"time"
)
func main() {
daemon.InitDaemon("npc", common.GetRunPath(), common.GetTmpPath())
logs.SetLogger("store")
application := app.New()
window := application.NewWindow("Npc " + version.VERSION)
window.SetContent(WidgetScreen())
window.Resize(fyne.NewSize(910, 350))
window.ShowAndRun()
}
var (
start bool
status = "Start!"
connType = "tcp"
cl = new(client.TRPClient)
)
func WidgetScreen() fyne.CanvasObject {
return fyne.NewContainerWithLayout(layout.NewBorderLayout(nil, nil, nil, nil),
makeMainTab(),
)
}
func makeMainTab() fyne.Widget {
serverPort := widget.NewEntry()
serverPort.SetPlaceHolder("Server:Port")
vKey := widget.NewEntry()
vKey.SetPlaceHolder("Vkey")
radio := widget.NewRadio([]string{"tcp", "kcp"}, func(s string) { connType = s })
radio.Horizontal = true
refreshCh := make(chan struct{})
button := widget.NewButton(status, func() {
start = !start
if start {
status = "Stop!"
// init the npc
fmt.Println("submit", serverPort.Text, vKey.Text, connType)
sp, vk, ct := loadConfig()
if sp != serverPort.Text || vk != vKey.Text || ct != connType {
saveConfig(serverPort.Text, vKey.Text, connType)
}
cl = client.NewRPClient(serverPort.Text, vKey.Text, connType, "", nil)
go cl.Start()
} else {
// close the npc
status = "Start!"
if cl != nil {
go cl.Close()
cl = nil
}
}
refreshCh <- struct{}{}
})
go func() {
for {
<-refreshCh
button.SetText(status)
}
}()
lo := widget.NewMultiLineEntry()
lo.SetReadOnly(true)
lo.Resize(fyne.NewSize(910, 250))
slo := widget.NewScrollContainer(lo)
slo.Resize(fyne.NewSize(910, 250))
go func() {
for {
time.Sleep(time.Second)
lo.SetText(common.GetLogMsg())
slo.Resize(fyne.NewSize(910, 250))
}
}()
sp, vk, ct := loadConfig()
if sp != "" && vk != "" && ct != "" {
serverPort.SetText(sp)
vKey.SetText(vk)
connType = ct
radio.SetSelected(ct)
}
return widget.NewVBox(
widget.NewLabel("Npc "+version.VERSION),
serverPort,
vKey,
radio,
button,
slo,
)
}
func getDir() (dir string, err error) {
if runtime.GOOS != "android" {
dir, err = os.UserConfigDir()
if err != nil {
return
}
} else {
dir = "/data/data/org.nps.client/files"
}
return
}
func saveConfig(host, vkey, connType string) {
data := strings.Join([]string{host, vkey, connType}, "\n")
ph, err := getDir()
if err != nil {
logs.Warn("not found config dir")
return
}
_ = os.Remove(path.Join(ph, "npc.conf"))
f, err := os.OpenFile(path.Join(ph, "npc.conf"), os.O_CREATE|os.O_WRONLY, 0644)
defer f.Close()
if err != nil {
logs.Error(err)
return
}
if _, err := f.Write([]byte(data)); err != nil {
f.Close() // ignore error; Write error takes precedence
logs.Error(err)
return
}
}
func loadConfig() (host, vkey, connType string) {
ph, err := getDir()
if err != nil {
logs.Warn("not found config dir")
return
}
f, err := os.OpenFile(path.Join(ph, "npc.conf"), os.O_RDONLY, 0644)
defer f.Close()
if err != nil {
logs.Error(err)
return
}
data, err := ioutil.ReadAll(f)
if err != nil {
logs.Error(err)
return
}
li := strings.Split(string(data), "\n")
host = li[0]
vkey = li[1]
connType = li[2]
return
}

821
image/work_flow.svg Normal file
View File

@ -0,0 +1,821 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- 由 Microsoft Visio, SVG Export 生成 工作图.svg Page-1 -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="11.6929in" height="8.26772in"
viewBox="0 0 841.89 595.276" xml:space="preserve" color-interpolation-filters="sRGB" class="st17">
<v:documentProperties v:langID="2052" v:metric="true" v:viewMarkup="false">
<v:userDefs>
<v:ud v:nameU="msvNoAutoConnect" v:prompt="" v:val="VT0(0):26"/>
</v:userDefs>
</v:documentProperties>
<style type="text/css">
<![CDATA[
.st1 {fill:#5b9bd5;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
.st2 {stroke:#ffffff;stroke-linecap:butt;stroke-width:0.5}
.st3 {fill:#1e4a73;font-family:Calibri;font-size:1.5em}
.st4 {fill:#aad288;stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
.st5 {fill:#ffffff}
.st6 {font-size:1em}
.st7 {fill:#5b9bd5}
.st8 {stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
.st9 {fill:#a5a5a5}
.st10 {marker-end:url(#mrkr4-135);marker-start:url(#mrkr4-133);stroke:#5592c9;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
.st11 {fill:#5592c9;fill-opacity:1;stroke:#5592c9;stroke-opacity:1;stroke-width:0.37313432835821}
.st12 {fill:#ffffff;stroke:none;stroke-linecap:butt;stroke-width:7.2}
.st13 {fill:#41729d;font-family:Calibri;font-size:1.5em}
.st14 {fill:none;stroke:#42829e;stroke-dasharray:45,27;stroke-linecap:round;stroke-linejoin:round;stroke-width:3}
.st15 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5}
.st16 {fill:#5b9bd5;font-family:Calibri;font-size:1.99999em}
.st17 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
]]>
</style>
<defs id="Markers">
<g id="lend4">
<path d="M 2 1 L 0 0 L 2 -1 L 2 1 " style="stroke:none"/>
</g>
<marker id="mrkr4-133" class="st11" v:arrowType="4" v:arrowSize="2" v:setback="5.12" refX="5.12" orient="auto"
markerUnits="strokeWidth" overflow="visible">
<use xlink:href="#lend4" transform="scale(2.68) "/>
</marker>
<marker id="mrkr4-135" class="st11" v:arrowType="4" v:arrowSize="2" v:setback="5.36" refX="-5.36" orient="auto"
markerUnits="strokeWidth" overflow="visible">
<use xlink:href="#lend4" transform="scale(-2.68,-2.68) "/>
</marker>
</defs>
<g v:mID="0" v:index="1" v:groupContext="foregroundPage">
<v:userDefs>
<v:ud v:nameU="msvThemeOrder" v:val="VT0(0):26"/>
</v:userDefs>
<title>页-1</title>
<v:pageProperties v:drawingScale="0.0393701" v:pageScale="0.0393701" v:drawingUnits="24" v:shadowOffsetX="8.50394"
v:shadowOffsetY="-8.50394"/>
<v:layer v:name="连接线" v:index="0"/>
<g id="group1-1" transform="translate(418.11,-311.811)" v:mID="1" v:groupContext="group">
<v:custProps>
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
<v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(防火墙)"/>
</v:custProps>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
</v:userDefs>
<title>防火墙</title>
<desc>NAT</desc>
<g id="shape2-2" v:mID="2" v:groupContext="shape" transform="translate(0.545123,-7.63784)">
<title>工作表.2</title>
<rect x="0" y="539.685" width="69.7759" height="55.5905" class="st1"/>
</g>
<g id="shape3-4" v:mID="3" v:groupContext="shape" transform="translate(0.795123,-7.88784)">
<title>工作表.3</title>
<v:userDefs>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
</v:userDefs>
<path d="M0 587.41 L69.28 587.41" class="st2"/>
<path d="M0 579.54 L69.28 579.54" class="st2"/>
<path d="M0 571.67 L69.28 571.67" class="st2"/>
<path d="M0 563.8 L69.28 563.8" class="st2"/>
<path d="M0 555.93 L69.28 555.93" class="st2"/>
<path d="M0 548.06 L69.28 548.06" class="st2"/>
<path d="M17.32 595.28 L17.32 587.41" class="st2"/>
<path d="M34.64 595.28 L34.64 587.41" class="st2"/>
<path d="M51.96 595.28 L51.96 587.41" class="st2"/>
<path d="M8.66 587.41 L8.66 579.54" class="st2"/>
<path d="M25.98 587.41 L25.98 579.54" class="st2"/>
<path d="M43.3 587.41 L43.3 579.54" class="st2"/>
<path d="M60.62 587.41 L60.62 579.54" class="st2"/>
<path d="M17.32 579.54 L17.32 571.67" class="st2"/>
<path d="M34.64 579.54 L34.64 571.67" class="st2"/>
<path d="M51.96 579.54 L51.96 571.67" class="st2"/>
<path d="M8.66 571.67 L8.66 563.8" class="st2"/>
<path d="M25.98 571.67 L25.98 563.8" class="st2"/>
<path d="M43.3 571.67 L43.3 563.8" class="st2"/>
<path d="M60.62 571.67 L60.62 563.8" class="st2"/>
<path d="M17.32 563.8 L17.32 555.93" class="st2"/>
<path d="M34.64 563.8 L34.64 555.93" class="st2"/>
<path d="M51.96 563.8 L51.96 555.93" class="st2"/>
<path d="M8.66 555.93 L8.66 548.06" class="st2"/>
<path d="M25.98 555.93 L25.98 548.06" class="st2"/>
<path d="M43.3 555.93 L43.3 548.06" class="st2"/>
<path d="M60.62 555.93 L60.62 548.06" class="st2"/>
<path d="M17.32 548.06 L17.32 540.19" class="st2"/>
<path d="M34.64 548.06 L34.64 540.19" class="st2"/>
<path d="M51.96 548.06 L51.96 540.19" class="st2"/>
</g>
<g id="shape1-36" v:mID="1" v:groupContext="groupContent">
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="35.4331" cy="610.077" width="42.88" height="29.6036"/>
<text x="20.03" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>NAT</text> </g>
</g>
<g id="group4-38" transform="translate(701.575,-311.811)" v:mID="4" v:groupContext="group">
<v:custProps>
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Memory" v:lbl="内存" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="OperatingSystem" v:lbl="操作系统" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="HardDriveSize" v:lbl="硬盘容量" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(服务器)"/>
<v:cp v:nameU="BelongsTo" v:lbl="属于" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true" v:ask="false"
v:langID="2052" v:cal="0"/>
</v:custProps>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
</v:userDefs>
<title>服务器</title>
<desc>Application2 10.0.0.4:PORT</desc>
<g id="shape5-39" v:mID="5" v:groupContext="shape" transform="translate(12.8133,0)">
<title>工作表.5</title>
<rect x="0" y="524.409" width="45.2395" height="70.8661" class="st1"/>
</g>
<g id="shape6-41" v:mID="6" v:groupContext="shape" transform="translate(46.625,-30.2513)">
<title>工作表.6</title>
<ellipse cx="2.73472" cy="592.541" rx="2.73472" ry="2.73472" class="st4"/>
</g>
<g id="shape7-43" v:mID="7" v:groupContext="shape" transform="translate(30.0295,-11.6164)">
<title>工作表.7</title>
<v:userDefs>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
</v:userDefs>
<path d="M-0 595.28 L22.06 595.28 L22.06 593.5 L-0 593.5 L-0 595.28 ZM-0 589.9 L22.06 589.9 L22.06 588.13 L-0 588.13
L-0 589.9 ZM-0 584.53 L22.06 584.53 L22.06 582.76 L-0 582.76 L-0 584.53 Z" class="st5"/>
</g>
<g id="shape4-46" v:mID="4" v:groupContext="groupContent">
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="35.4331" cy="620.877" width="115.9" height="51.2036"/>
<text x="-10.6" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>Application2<v:newlineChar/><tspan
x="-16.48" dy="1.2em" class="st6">10.0.0.4:PORT</tspan></text> </g>
</g>
<g id="group8-49" transform="translate(701.575,-496.063)" v:mID="8" v:groupContext="group">
<v:custProps>
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Memory" v:lbl="内存" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="OperatingSystem" v:lbl="操作系统" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="HardDriveSize" v:lbl="硬盘容量" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(服务器)"/>
<v:cp v:nameU="BelongsTo" v:lbl="属于" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true" v:ask="false"
v:langID="2052" v:cal="0"/>
</v:custProps>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
</v:userDefs>
<title>服务器.8</title>
<desc>Application1 10.0.0.3:PORT</desc>
<g id="shape9-50" v:mID="9" v:groupContext="shape" transform="translate(12.8133,0)">
<title>工作表.9</title>
<rect x="0" y="524.409" width="45.2395" height="70.8661" class="st1"/>
</g>
<g id="shape10-52" v:mID="10" v:groupContext="shape" transform="translate(46.625,-30.2513)">
<title>工作表.10</title>
<ellipse cx="2.73472" cy="592.541" rx="2.73472" ry="2.73472" class="st4"/>
</g>
<g id="shape11-54" v:mID="11" v:groupContext="shape" transform="translate(30.0295,-11.6164)">
<title>工作表.11</title>
<v:userDefs>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
</v:userDefs>
<path d="M-0 595.28 L22.06 595.28 L22.06 593.5 L-0 593.5 L-0 595.28 ZM-0 589.9 L22.06 589.9 L22.06 588.13 L-0 588.13
L-0 589.9 ZM-0 584.53 L22.06 584.53 L22.06 582.76 L-0 582.76 L-0 584.53 Z" class="st5"/>
</g>
<g id="shape8-57" v:mID="8" v:groupContext="groupContent">
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="35.4331" cy="620.877" width="115.9" height="51.2036"/>
<text x="-10.6" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>Application1<v:newlineChar/><tspan
x="-16.48" dy="1.2em" class="st6">10.0.0.3:PORT</tspan></text> </g>
</g>
<g id="group12-60" transform="translate(701.575,-127.559)" v:mID="12" v:groupContext="group">
<v:custProps>
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="CPU" v:lbl="CPU" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Memory" v:lbl="内存" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="OperatingSystem" v:lbl="操作系统" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="HardDriveSize" v:lbl="硬盘容量" v:prompt="" v:type="0" v:format="" v:sortKey="Workstation"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(服务器)"/>
<v:cp v:nameU="BelongsTo" v:lbl="属于" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true" v:ask="false"
v:langID="2052" v:cal="0"/>
</v:custProps>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
</v:userDefs>
<title>服务器.12</title>
<desc>Application3 10.0.0.5:PORT</desc>
<g id="shape13-61" v:mID="13" v:groupContext="shape" transform="translate(12.8133,0)">
<title>工作表.13</title>
<rect x="0" y="524.409" width="45.2395" height="70.8661" class="st1"/>
</g>
<g id="shape14-63" v:mID="14" v:groupContext="shape" transform="translate(46.625,-30.2513)">
<title>工作表.14</title>
<ellipse cx="2.73472" cy="592.541" rx="2.73472" ry="2.73472" class="st4"/>
</g>
<g id="shape15-65" v:mID="15" v:groupContext="shape" transform="translate(30.0295,-11.6164)">
<title>工作表.15</title>
<v:userDefs>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
</v:userDefs>
<path d="M-0 595.28 L22.06 595.28 L22.06 593.5 L-0 593.5 L-0 595.28 ZM-0 589.9 L22.06 589.9 L22.06 588.13 L-0 588.13
L-0 589.9 ZM-0 584.53 L22.06 584.53 L22.06 582.76 L-0 582.76 L-0 584.53 Z" class="st5"/>
</g>
<g id="shape12-68" v:mID="12" v:groupContext="groupContent">
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="35.4331" cy="620.877" width="115.9" height="51.2036"/>
<text x="-10.6" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>Application3<v:newlineChar/><tspan
x="-16.48" dy="1.2em" class="st6">10.0.0.5:PORT</tspan></text> </g>
</g>
<g id="group16-71" transform="translate(538.583,-311.811)" v:mID="16" v:groupContext="group">
<v:custProps>
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Building" v:lbl="建筑物" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(计算机)"/>
<v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(大型机)"/>
</v:custProps>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
</v:userDefs>
<title>主机</title>
<desc>NPC Client 10.0.0.2 Dial To: -&#62;10.0.0.3:PORT -&#62;10.0.0.4:PORT ...</desc>
<g id="shape17-72" v:mID="17" v:groupContext="shape" transform="translate(5.68158,0)">
<title>工作表.17</title>
<path d="M0 595.28 L59.5 595.28 L59.5 524.41 L0 524.41 L0 595.28 Z" class="st7"/>
<path d="M0 595.28 L59.5 595.28 L59.5 524.41 L0 524.41 L0 595.28" class="st8"/>
<path d="M29.75 595.28 L29.75 524.41" class="st8"/>
</g>
<g id="shape18-76" v:mID="18" v:groupContext="shape" transform="translate(11.5726,-5.14536)">
<title>工作表.18</title>
<v:userDefs>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
</v:userDefs>
<path d="M36.09 551.42 L49.23 551.42 L49.23 536.38 L36.09 536.38 L36.09 551.42 ZM14.89 542.47 L16.68 542.47 L16.68
536.06 L14.89 536.06 L14.89 542.47 ZM14.89 551.71 L16.68 551.71 L16.68 545.3 L14.89 545.3 L14.89 551.71
ZM10.63 542.47 L12.43 542.47 L12.43 536.06 L10.63 536.06 L10.63 542.47 ZM10.63 551.71 L12.43 551.71
L12.43 545.3 L10.63 545.3 L10.63 551.71 ZM4.25 542.47 L6.05 542.47 L6.05 536.06 L4.25 536.06 L4.25 542.47
ZM4.25 551.71 L6.05 551.71 L6.05 545.3 L4.25 545.3 L4.25 551.71 ZM0 542.47 L1.79 542.47 L1.79 536.06
L0 536.06 L0 542.47 ZM0 551.71 L1.79 551.71 L1.79 545.3 L0 545.3 L0 551.71 ZM33.82 586.45 L33.82 587.7
L49.39 587.69 L49.39 586.44 L33.82 586.45 ZM33.82 590.24 L33.82 591.49 L49.39 591.48 L49.39 590.24 L33.82
590.24 ZM33.82 594.03 L33.82 595.28 L49.39 595.27 L49.39 594.03 L33.82 594.03 ZM2.96 587.7 L18.52 587.7
L18.52 586.45 L2.95 586.45 L2.96 587.7 ZM2.96 591.49 L18.52 591.49 L18.52 590.24 L2.95 590.24 L2.96
591.49 ZM2.96 595.28 L18.52 595.28 L18.52 594.03 L2.95 594.03 L2.96 595.28 Z" class="st5"/>
</g>
<g id="shape19-79" v:mID="19" v:groupContext="shape" transform="translate(49.0815,-50.4059)">
<title>工作表.19</title>
<path d="M8.15 583.79 A1.11073 1.11073 -180 1 0 10.24 584.56 A1.11073 1.11073 -180 1 0 8.15 583.79 ZM8.17 587.08
A1.11073 1.11073 -180 1 0 10.22 587.93 A1.11073 1.11073 -180 1 0 8.17 587.08 ZM8.18 590.39 A1.11073
1.11073 -180 1 0 10.21 591.28 A1.11073 1.11073 -180 1 0 8.18 590.39 ZM8.18 593.71 A1.11073 1.11073 -180
1 0 10.21 594.6 A1.11073 1.11073 -180 1 0 8.18 593.71 ZM4.1 583.84 A1.11073 1.11073 -180 1 0 6.21 584.51
A1.11073 1.11073 -180 1 0 4.1 583.84 ZM4.11 587.14 A1.11073 1.11073 -180 1 0 6.2 587.87 A1.11073 1.11073
-180 1 0 4.11 587.14 ZM4.11 590.44 A1.11073 1.11073 -180 1 0 6.19 591.22 A1.11073 1.11073 -180 1 0 4.11
590.44 ZM4.11 593.77 A1.11073 1.11073 -180 1 0 6.19 594.55 A1.11073 1.11073 -180 1 0 4.11 593.77 ZM0.04
583.9 A1.11144 1.11144 -180 1 0 2.19 584.46 A1.11144 1.11144 -180 1 0 0.04 583.9 ZM0.05 587.19 A1.11144
1.11144 -180 1 0 2.18 587.82 A1.11144 1.11144 -180 1 0 0.05 587.19 ZM0.05 590.5 A1.11144 1.11144 -180
1 0 2.17 591.16 A1.11144 1.11144 -180 1 0 0.05 590.5 ZM0.05 593.83 A1.11144 1.11144 -180 1 0 2.17 594.49
A1.11144 1.11144 -180 1 0 0.05 593.83 Z" class="st9"/>
</g>
<g id="shape16-82" v:mID="16" v:groupContext="groupContent">
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="35.4331" cy="674.877" width="130.38" height="159.204"/>
<text x="20.18" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>NPC<v:newlineChar/><tspan
x="14.28" dy="1.2em" class="st6">Client<v:newlineChar/></tspan><tspan x="5.81" dy="1.2em" class="st6">10.0.0.2<v:newlineChar/></tspan><tspan
x="7.88" dy="1.2em" class="st6">Dial To:<v:newlineChar/></tspan><tspan x="-23.72" dy="1.2em"
class="st6">-</tspan>&#62;10.0.0.3:PORT<v:newlineChar/><tspan x="-23.72" dy="1.2em" class="st6">-</tspan>&#62;10.0.0.4:PORT<v:newlineChar/><tspan
x="-23.72" dy="1.2em" class="st6">-</tspan>&#62;10.0.0.5:PORT</text> </g>
</g>
<g id="group20-90" transform="translate(212.598,-311.811)" v:mID="20" v:groupContext="group">
<v:custProps>
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Building" v:lbl="建筑物" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="Asset" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:prompt="" v:type="0" v:format="" v:sortKey="Equipment"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:prompt="" v:type="0" v:format="" v:sortKey="Network"
v:invis="false" v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:prompt="" v:type="0" v:format="" v:sortKey="Network" v:invis="false"
v:ask="false" v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(设备)"/>
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(计算机)"/>
<v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(大型机)"/>
</v:custProps>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
</v:userDefs>
<title>主机.20</title>
<desc>NPS Server 1.1.1.1 Listen On: 8003-&#62;10.0.0.3:PORT 8004-&#62;10.0....</desc>
<g id="shape21-91" v:mID="21" v:groupContext="shape" transform="translate(5.68158,0)">
<title>工作表.21</title>
<path d="M0 595.28 L59.5 595.28 L59.5 524.41 L0 524.41 L0 595.28 Z" class="st7"/>
<path d="M0 595.28 L59.5 595.28 L59.5 524.41 L0 524.41 L0 595.28" class="st8"/>
<path d="M29.75 595.28 L29.75 524.41" class="st8"/>
</g>
<g id="shape22-95" v:mID="22" v:groupContext="shape" transform="translate(11.5726,-5.14536)">
<title>工作表.22</title>
<v:userDefs>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(1)"/>
<v:ud v:nameU="SurroundingRegionColor" v:prompt="" v:val="VT5(#5b9bd5)"/>
</v:userDefs>
<path d="M36.09 551.42 L49.23 551.42 L49.23 536.38 L36.09 536.38 L36.09 551.42 ZM14.89 542.47 L16.68 542.47 L16.68
536.06 L14.89 536.06 L14.89 542.47 ZM14.89 551.71 L16.68 551.71 L16.68 545.3 L14.89 545.3 L14.89 551.71
ZM10.63 542.47 L12.43 542.47 L12.43 536.06 L10.63 536.06 L10.63 542.47 ZM10.63 551.71 L12.43 551.71
L12.43 545.3 L10.63 545.3 L10.63 551.71 ZM4.25 542.47 L6.05 542.47 L6.05 536.06 L4.25 536.06 L4.25 542.47
ZM4.25 551.71 L6.05 551.71 L6.05 545.3 L4.25 545.3 L4.25 551.71 ZM0 542.47 L1.79 542.47 L1.79 536.06
L0 536.06 L0 542.47 ZM0 551.71 L1.79 551.71 L1.79 545.3 L0 545.3 L0 551.71 ZM33.82 586.45 L33.82 587.7
L49.39 587.69 L49.39 586.44 L33.82 586.45 ZM33.82 590.24 L33.82 591.49 L49.39 591.48 L49.39 590.24 L33.82
590.24 ZM33.82 594.03 L33.82 595.28 L49.39 595.27 L49.39 594.03 L33.82 594.03 ZM2.96 587.7 L18.52 587.7
L18.52 586.45 L2.95 586.45 L2.96 587.7 ZM2.96 591.49 L18.52 591.49 L18.52 590.24 L2.95 590.24 L2.96
591.49 ZM2.96 595.28 L18.52 595.28 L18.52 594.03 L2.95 594.03 L2.96 595.28 Z" class="st5"/>
</g>
<g id="shape23-98" v:mID="23" v:groupContext="shape" transform="translate(49.0815,-50.4059)">
<title>工作表.23</title>
<path d="M8.15 583.79 A1.11073 1.11073 -180 1 0 10.24 584.56 A1.11073 1.11073 -180 1 0 8.15 583.79 ZM8.17 587.08
A1.11073 1.11073 -180 1 0 10.22 587.93 A1.11073 1.11073 -180 1 0 8.17 587.08 ZM8.18 590.39 A1.11073
1.11073 -180 1 0 10.21 591.28 A1.11073 1.11073 -180 1 0 8.18 590.39 ZM8.18 593.71 A1.11073 1.11073 -180
1 0 10.21 594.6 A1.11073 1.11073 -180 1 0 8.18 593.71 ZM4.1 583.84 A1.11073 1.11073 -180 1 0 6.21 584.51
A1.11073 1.11073 -180 1 0 4.1 583.84 ZM4.11 587.14 A1.11073 1.11073 -180 1 0 6.2 587.87 A1.11073 1.11073
-180 1 0 4.11 587.14 ZM4.11 590.44 A1.11073 1.11073 -180 1 0 6.19 591.22 A1.11073 1.11073 -180 1 0 4.11
590.44 ZM4.11 593.77 A1.11073 1.11073 -180 1 0 6.19 594.55 A1.11073 1.11073 -180 1 0 4.11 593.77 ZM0.04
583.9 A1.11144 1.11144 -180 1 0 2.19 584.46 A1.11144 1.11144 -180 1 0 0.04 583.9 ZM0.05 587.19 A1.11144
1.11144 -180 1 0 2.18 587.82 A1.11144 1.11144 -180 1 0 0.05 587.19 ZM0.05 590.5 A1.11144 1.11144 -180
1 0 2.17 591.16 A1.11144 1.11144 -180 1 0 0.05 590.5 ZM0.05 593.83 A1.11144 1.11144 -180 1 0 2.17 594.49
A1.11144 1.11144 -180 1 0 0.05 593.83 Z" class="st9"/>
</g>
<g id="shape20-101" v:mID="20" v:groupContext="groupContent">
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="35.4331" cy="674.877" width="166.87" height="159.204"/>
<text x="20.84" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>NPS<v:newlineChar/><tspan
x="12" dy="1.2em" class="st6">Server<v:newlineChar/></tspan><tspan x="10.37" dy="1.2em" class="st6">1.1.1.1<v:newlineChar/></tspan><tspan
x="-1.29" dy="1.2em" class="st6">Listen On:<v:newlineChar/></tspan><tspan x="-41.96" dy="1.2em"
class="st6">8003</tspan>-&#62;10.0.0.3:PORT<v:newlineChar/><tspan x="-41.96" dy="1.2em" class="st6">8004</tspan>-&#62;10.0.0.4:PORT<v:newlineChar/><tspan
x="-41.96" dy="1.2em" class="st6">8005</tspan>-&#62;10.0.0.5:PORT</text> </g>
</g>
<g id="group24-109" transform="translate(49.6063,-496.063)" v:mID="24" v:groupContext="group">
<v:custProps>
<v:cp v:nameU="Name" v:lbl="名称" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(连接性)"/>
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(用户)"/>
</v:custProps>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
</v:userDefs>
<title>用户</title>
<desc>User1 Wants:APP1</desc>
<g id="shape25-110" v:mID="25" v:groupContext="shape" transform="translate(18.0575,0)">
<title>工作表.25</title>
<path d="M26.29 533.22 A8.81 8.81 -180 1 0 8.67 533.22 A8.81 8.81 -180 1 0 26.29 533.22 ZM27.58 544.41 L7.17 544.41
C3.22 544.41 0 547.62 0 551.58 L0 576.58 L5.59 576.58 L5.59 562.03 L7.45 562.03 L7.45 595.28 L16.55
595.28 L16.55 580.42 C16.55 579.9 16.97 579.48 17.48 579.48 C18 579.48 18.42 579.9 18.42 580.42 L18.42
595.28 L28.04 595.28 L28.04 561.98 L29.91 561.98 L29.91 576.58 L34.75 576.58 L34.75 551.58 C34.75 547.62
31.54 544.41 27.58 544.41 Z" class="st1"/>
</g>
<g id="shape24-112" v:mID="24" v:groupContext="groupContent">
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="35.4331" cy="620.877" width="102.19" height="51.2036"/>
<text x="13.96" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>User1<v:newlineChar/><tspan
x="-9.62" dy="1.2em" class="st6">Wants:APP1</tspan></text> </g>
</g>
<g id="group26-115" transform="translate(49.6063,-311.811)" v:mID="26" v:groupContext="group">
<v:custProps>
<v:cp v:nameU="Name" v:lbl="名称" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(连接性)"/>
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(用户)"/>
</v:custProps>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
</v:userDefs>
<title>用户.26</title>
<desc>User2 Wants:APP2</desc>
<g id="shape27-116" v:mID="27" v:groupContext="shape" transform="translate(18.0575,0)">
<title>工作表.27</title>
<path d="M26.29 533.22 A8.81 8.81 -180 1 0 8.67 533.22 A8.81 8.81 -180 1 0 26.29 533.22 ZM27.58 544.41 L7.17 544.41
C3.22 544.41 0 547.62 0 551.58 L0 576.58 L5.59 576.58 L5.59 562.03 L7.45 562.03 L7.45 595.28 L16.55
595.28 L16.55 580.42 C16.55 579.9 16.97 579.48 17.48 579.48 C18 579.48 18.42 579.9 18.42 580.42 L18.42
595.28 L28.04 595.28 L28.04 561.98 L29.91 561.98 L29.91 576.58 L34.75 576.58 L34.75 551.58 C34.75 547.62
31.54 544.41 27.58 544.41 Z" class="st1"/>
</g>
<g id="shape26-118" v:mID="26" v:groupContext="groupContent">
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="35.4331" cy="620.877" width="102.19" height="51.2036"/>
<text x="13.96" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>User2<v:newlineChar/><tspan
x="-9.62" dy="1.2em" class="st6">Wants:APP2</tspan></text> </g>
</g>
<g id="group28-121" transform="translate(49.6063,-127.559)" v:mID="28" v:groupContext="group">
<v:custProps>
<v:cp v:nameU="Name" v:lbl="名称" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Location" v:lbl="位置" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Building" v:lbl="构建" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Room" v:lbl="空间" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="Department" v:lbl="部门" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="false" v:ask="false"
v:langID="2052" v:cal="0"/>
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(连接性)"/>
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:prompt="" v:type="0" v:format="" v:sortKey="" v:invis="true"
v:ask="false" v:langID="2052" v:cal="0" v:val="VT4(用户)"/>
</v:custProps>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
<v:ud v:nameU="ShapeClass" v:prompt="" v:val="VT0(5):26"/>
<v:ud v:nameU="SolSH" v:prompt="" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
<v:ud v:nameU="visLegendShape" v:prompt="" v:val="VT0(2):26"/>
</v:userDefs>
<title>用户.28</title>
<desc>User3 Wants:APP3</desc>
<g id="shape29-122" v:mID="29" v:groupContext="shape" transform="translate(18.0575,0)">
<title>工作表.29</title>
<path d="M26.29 533.22 A8.81 8.81 -180 1 0 8.67 533.22 A8.81 8.81 -180 1 0 26.29 533.22 ZM27.58 544.41 L7.17 544.41
C3.22 544.41 0 547.62 0 551.58 L0 576.58 L5.59 576.58 L5.59 562.03 L7.45 562.03 L7.45 595.28 L16.55
595.28 L16.55 580.42 C16.55 579.9 16.97 579.48 17.48 579.48 C18 579.48 18.42 579.9 18.42 580.42 L18.42
595.28 L28.04 595.28 L28.04 561.98 L29.91 561.98 L29.91 576.58 L34.75 576.58 L34.75 551.58 C34.75 547.62
31.54 544.41 27.58 544.41 Z" class="st1"/>
</g>
<g id="shape28-124" v:mID="28" v:groupContext="groupContent">
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="35.4331" cy="620.877" width="102.19" height="51.2036"/>
<text x="13.96" y="615.48" class="st3" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>User3<v:newlineChar/><tspan
x="-9.62" dy="1.2em" class="st6">Wants:APP3</tspan></text> </g>
</g>
<g id="shape1003-127" v:mID="1003" v:groupContext="shape" v:layerMember="0" transform="translate(99.8466,-514.757)">
<title>动态连接线.1003</title>
<desc>-&#62;8003 Multi Conn</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="56.059" cy="684.836" width="93.29" height="51.2036"/>
<path d="M5.09 601.03 L5.33 601.3 L113.11 723.13" class="st10"/>
<rect v:rectContext="textBkgnd" x="15.4535" y="663.236" width="81.2109" height="43.1999" class="st12"/>
<text x="30.58" y="679.44" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>-&#62;8003<v:newlineChar/><tspan
x="15.45" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
<g id="shape1004-139" v:mID="1004" v:groupContext="shape" v:layerMember="0" transform="translate(102.415,-340.157)">
<title>动态连接线.1004</title>
<desc>-&#62;8004 Multi Conn</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="57.9325" cy="588.189" width="93.29" height="51.2036"/>
<path d="M7.68 588.19 L8.04 588.19 L107.83 588.19" class="st10"/>
<rect v:rectContext="textBkgnd" x="17.327" y="566.589" width="81.2109" height="43.1999" class="st12"/>
<text x="32.45" y="582.79" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>-&#62;8004<v:newlineChar/><tspan
x="17.33" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
<g id="shape1005-149" v:mID="1005" v:groupContext="shape" v:layerMember="0" transform="translate(98.1493,-177.812)">
<title>动态连接线.1005</title>
<desc>-&#62;8005 Multi Conn</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="60.0654" cy="527.376" width="93.29" height="51.2036"/>
<path d="M5.09 589.52 L5.33 589.25 L114.8 465.5" class="st10"/>
<rect v:rectContext="textBkgnd" x="19.4599" y="505.776" width="81.2109" height="43.1999" class="st12"/>
<text x="34.58" y="521.98" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>-&#62;8005<v:newlineChar/><tspan
x="19.46" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
<g id="shape1006-159" v:mID="1006" v:groupContext="shape" v:layerMember="0" transform="translate(277.783,-354.331)">
<title>动态连接线.1006</title>
<desc>NPS &#38; NPC Multiplexing Connection TCP or KCP Only One Conn Pe...</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="70.4362" cy="602.362" width="103.62" height="159.204"/>
<path d="M7.68 602.36 L8.04 602.36 L132.83 602.36" class="st10"/>
<rect v:rectContext="textBkgnd" x="24.6671" y="526.762" width="91.5381" height="151.2" class="st12"/>
<text x="30.38" y="542.96" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>NPS &#38; NPC<v:newlineChar/><tspan
x="24.67" dy="1.2em" class="st6">Multiplexing<v:newlineChar/></tspan><tspan x="28.6" dy="1.2em" class="st6">Connection<v:newlineChar/></tspan><tspan
x="30.53" dy="1.2em" class="st6">TCP or KCP<v:newlineChar/></tspan><tspan x="36.41" dy="1.2em" class="st6">Only One<v:newlineChar/></tspan><tspan
x="32.66" dy="1.2em" class="st6">Conn Peer<v:newlineChar/></tspan><tspan x="55.18" dy="1.2em" class="st6">NPC</tspan></text> </g>
<g id="shape1007-174" v:mID="1007" v:groupContext="shape" v:layerMember="0" transform="translate(603.767,-380.876)">
<title>动态连接线.1007</title>
<desc>-&#62;PORT Multi Conn</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="55.0044" cy="550.955" width="93.29" height="51.2036"/>
<path d="M5.09 589.52 L5.33 589.25 L105.29 476.25" class="st10"/>
<rect v:rectContext="textBkgnd" x="14.3989" y="529.355" width="81.2109" height="43.1999" class="st12"/>
<text x="27.89" y="545.56" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>-&#62;PORT<v:newlineChar/><tspan
x="14.4" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
<g id="shape1008-184" v:mID="1008" v:groupContext="shape" v:layerMember="0" transform="translate(603.767,-340.157)">
<title>动态连接线.1008</title>
<desc>-&#62;PORT Multi Conn</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="55.3104" cy="588.189" width="93.29" height="51.2036"/>
<path d="M7.68 588.19 L8.04 588.19 L102.58 588.19" class="st10"/>
<rect v:rectContext="textBkgnd" x="14.7049" y="566.589" width="81.2109" height="43.1999" class="st12"/>
<text x="28.19" y="582.79" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>-&#62;PORT<v:newlineChar/><tspan
x="14.7" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
<g id="shape1009-194" v:mID="1009" v:groupContext="shape" v:layerMember="0" transform="translate(603.767,-313.612)">
<title>动态连接线.1009</title>
<desc>-&#62;PORT Multi Conn</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="68.0438" cy="667.943" width="93.29" height="51.2036"/>
<path d="M5.09 601.03 L5.33 601.3 L105.29 714.3" class="st10"/>
<rect v:rectContext="textBkgnd" x="27.4383" y="646.343" width="81.2109" height="43.1999" class="st12"/>
<text x="40.93" y="662.54" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>-&#62;PORT<v:newlineChar/><tspan
x="27.44" dy="1.2em" class="st6">Multi Conn</tspan></text> </g>
<g id="shape1010-204" v:mID="1010" v:groupContext="shape" v:layerMember="0" transform="translate(488.431,-340.157)">
<title>动态连接线.1010</title>
<desc>NPS &#38; NPC</desc>
<v:userDefs>
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
</v:userDefs>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="27.9165" cy="588.189" width="90" height="72.8036"/>
<path d="M7.68 588.19 L8.04 588.19 L47.79 588.19" class="st10"/>
<rect v:rectContext="textBkgnd" x="12.6587" y="555.789" width="30.5156" height="64.7998" class="st12"/>
<text x="13.32" y="571.99" class="st13" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>NPS<v:newlineChar/><tspan
x="21.78" dy="1.2em" class="st6">&#38;<v:newlineChar/></tspan><tspan x="12.66" dy="1.2em" class="st6">NPC</tspan></text> </g>
<g id="shape1011-215" v:mID="1011" v:groupContext="shape" transform="translate(34.0157,-62.8844)">
<title>工作表.1011</title>
<path d="M0 595.28 L398.27 595.28 L398.27 85.04 L0 85.04 L0 595.28 Z" class="st14"/>
</g>
<g id="shape1012-217" v:mID="1012" v:groupContext="shape" transform="translate(473.386,-62.8844)">
<title>工作表.1012</title>
<path d="M0 595.28 L320.31 595.28 L320.31 85.04 L0 85.04 L0 595.28 Z" class="st14"/>
</g>
<g id="shape1013-219" v:mID="1013" v:groupContext="shape" transform="translate(255.118,-496.063)">
<title>工作表.1013</title>
<desc>Internet</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="63.7795" cy="559.843" width="127.56" height="70.8661"/>
<rect x="0" y="524.409" width="127.559" height="70.8661" class="st15"/>
<text x="23.98" y="567.04" class="st16" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>Internet</text> </g>
<g id="shape1014-222" v:mID="1014" v:groupContext="shape" transform="translate(517.323,-515.866)">
<title>工作表.1014</title>
<desc>Intranet</desc>
<v:textBlock v:margins="rect(4,4,4,4)" v:tabSpace="42.5197"/>
<v:textRect cx="76" cy="579.646" width="152.01" height="31.2598"/>
<rect x="0" y="564.016" width="152" height="31.2598" class="st15"/>
<text x="36.43" y="586.85" class="st16" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>Intranet</text> </g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -49,6 +49,6 @@ const (
MUX_PING_RETURN
MUX_PING int32 = -1
MAXIMUM_SEGMENT_SIZE = PoolSizeWindow
MAXIMUM_WINDOW_SIZE = 1 << 25 // 1<<31-1 TCP slide window size is very large,
// we use 32M, reduce memory usage
MAXIMUM_WINDOW_SIZE = 1 << 27 // 1<<31-1 TCP slide window size is very large,
// we use 128M, reduce memory usage
)

48
lib/common/logs.go Normal file
View File

@ -0,0 +1,48 @@
package common
import (
"github.com/astaxie/beego/logs"
"time"
)
const MaxMsgLen = 5000
var logMsgs string
func init() {
logs.Register("store", func() logs.Logger {
return new(StoreMsg)
})
}
func GetLogMsg() string {
return logMsgs
}
type StoreMsg struct {
}
func (lg *StoreMsg) Init(config string) error {
return nil
}
func (lg *StoreMsg) WriteMsg(when time.Time, msg string, level int) error {
m := when.Format("2006-01-02 15:04:05") + " " + msg + "\r\n"
if len(logMsgs) > MaxMsgLen {
start := MaxMsgLen - len(m)
if start <= 0 {
start = MaxMsgLen
}
logMsgs = logMsgs[start:]
}
logMsgs += m
return nil
}
func (lg *StoreMsg) Destroy() {
return
}
func (lg *StoreMsg) Flush() {
return
}

View File

@ -6,6 +6,9 @@ import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"net"
"strconv"
"strings"
)
@ -38,6 +41,9 @@ func (Self *BasePackager) NewPac(contents ...interface{}) (err error) {
}
}
Self.setLength()
if Self.Length > MAXIMUM_SEGMENT_SIZE {
err = errors.New("mux:packer: newpack content segment too large")
}
return
}
@ -74,6 +80,11 @@ func (Self *BasePackager) UnPack(reader io.Reader) (n uint16, err error) {
}
if int(Self.Length) > cap(Self.Content) {
err = errors.New("unpack err, content length too large")
return
}
if Self.Length > MAXIMUM_SEGMENT_SIZE {
err = errors.New("mux:packer: unpack content segment too large")
return
}
Self.Content = Self.Content[:int(Self.Length)]
//n, err := io.ReadFull(reader, Self.Content)
@ -119,7 +130,8 @@ func (Self *BasePackager) Split() (strList []string) {
return
}
type ConnPackager struct { // Todo
type ConnPackager struct {
// Todo
ConnType uint8
BasePackager
}
@ -150,10 +162,9 @@ func (Self *ConnPackager) UnPack(reader io.Reader) (n uint16, err error) {
}
type MuxPackager struct {
Flag uint8
Id int32
Window uint32
ReadLength uint32
Flag uint8
Id int32
Window uint64
BasePackager
}
@ -166,19 +177,8 @@ func (Self *MuxPackager) NewPac(flag uint8, id int32, content ...interface{}) (e
err = Self.BasePackager.NewPac(content...)
//logs.Warn(Self.Length, string(Self.Content))
case MUX_MSG_SEND_OK:
// MUX_MSG_SEND_OK contains two data
switch content[0].(type) {
case int:
Self.Window = uint32(content[0].(int))
case uint32:
Self.Window = content[0].(uint32)
}
switch content[1].(type) {
case int:
Self.ReadLength = uint32(content[1].(int))
case uint32:
Self.ReadLength = content[1].(uint32)
}
// MUX_MSG_SEND_OK contains one data
Self.Window = content[0].(uint64)
}
return
}
@ -198,10 +198,6 @@ func (Self *MuxPackager) Pack(writer io.Writer) (err error) {
WindowBuff.Put(Self.Content)
case MUX_MSG_SEND_OK:
err = binary.Write(writer, binary.LittleEndian, Self.Window)
if err != nil {
return
}
err = binary.Write(writer, binary.LittleEndian, Self.ReadLength)
}
return
}
@ -223,13 +219,219 @@ func (Self *MuxPackager) UnPack(reader io.Reader) (n uint16, err error) {
//logs.Warn("unpack", Self.Length, string(Self.Content))
case MUX_MSG_SEND_OK:
err = binary.Read(reader, binary.LittleEndian, &Self.Window)
if err != nil {
return
}
n += 4 // uint32
err = binary.Read(reader, binary.LittleEndian, &Self.ReadLength)
n += 4 // uint32
n += 8 // uint64
}
n += 5 //uint8 int32
return
}
func (Self *MuxPackager) reset() {
Self.Id = 0
Self.Flag = 0
Self.Length = 0
Self.Content = nil
Self.Window = 0
}
const (
ipV4 = 1
domainName = 3
ipV6 = 4
)
type UDPHeader struct {
Rsv uint16
Frag uint8
Addr *Addr
}
func NewUDPHeader(rsv uint16, frag uint8, addr *Addr) *UDPHeader {
return &UDPHeader{
Rsv: rsv,
Frag: frag,
Addr: addr,
}
}
type Addr struct {
Type uint8
Host string
Port uint16
}
func (addr *Addr) String() string {
return net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port)))
}
func (addr *Addr) Decode(b []byte) error {
addr.Type = b[0]
pos := 1
switch addr.Type {
case ipV4:
addr.Host = net.IP(b[pos : pos+net.IPv4len]).String()
pos += net.IPv4len
case ipV6:
addr.Host = net.IP(b[pos : pos+net.IPv6len]).String()
pos += net.IPv6len
case domainName:
addrlen := int(b[pos])
pos++
addr.Host = string(b[pos : pos+addrlen])
pos += addrlen
default:
return errors.New("decode error")
}
addr.Port = binary.BigEndian.Uint16(b[pos:])
return nil
}
func (addr *Addr) Encode(b []byte) (int, error) {
b[0] = addr.Type
pos := 1
switch addr.Type {
case ipV4:
ip4 := net.ParseIP(addr.Host).To4()
if ip4 == nil {
ip4 = net.IPv4zero.To4()
}
pos += copy(b[pos:], ip4)
case domainName:
b[pos] = byte(len(addr.Host))
pos++
pos += copy(b[pos:], []byte(addr.Host))
case ipV6:
ip16 := net.ParseIP(addr.Host).To16()
if ip16 == nil {
ip16 = net.IPv6zero.To16()
}
pos += copy(b[pos:], ip16)
default:
b[0] = ipV4
copy(b[pos:pos+4], net.IPv4zero.To4())
pos += 4
}
binary.BigEndian.PutUint16(b[pos:], addr.Port)
pos += 2
return pos, nil
}
func (h *UDPHeader) Write(w io.Writer) error {
b := BufPoolUdp.Get().([]byte)
defer BufPoolUdp.Put(b)
binary.BigEndian.PutUint16(b[:2], h.Rsv)
b[2] = h.Frag
addr := h.Addr
if addr == nil {
addr = &Addr{}
}
length, _ := addr.Encode(b[3:])
_, err := w.Write(b[:3+length])
return err
}
type UDPDatagram struct {
Header *UDPHeader
Data []byte
}
func ReadUDPDatagram(r io.Reader) (*UDPDatagram, error) {
b := BufPoolUdp.Get().([]byte)
defer BufPoolUdp.Put(b)
// when r is a streaming (such as TCP connection), we may read more than the required data,
// but we don't know how to handle it. So we use io.ReadFull to instead of io.ReadAtLeast
// to make sure that no redundant data will be discarded.
n, err := io.ReadFull(r, b[:5])
if err != nil {
return nil, err
}
header := &UDPHeader{
Rsv: binary.BigEndian.Uint16(b[:2]),
Frag: b[2],
}
atype := b[3]
hlen := 0
switch atype {
case ipV4:
hlen = 10
case ipV6:
hlen = 22
case domainName:
hlen = 7 + int(b[4])
default:
return nil, errors.New("addr not support")
}
dlen := int(header.Rsv)
if dlen == 0 { // standard SOCKS5 UDP datagram
extra, err := ioutil.ReadAll(r) // we assume no redundant data
if err != nil {
return nil, err
}
copy(b[n:], extra)
n += len(extra) // total length
dlen = n - hlen // data length
} else { // extended feature, for UDP over TCP, using reserved field as data length
if _, err := io.ReadFull(r, b[n:hlen+dlen]); err != nil {
return nil, err
}
n = hlen + dlen
}
header.Addr = new(Addr)
if err := header.Addr.Decode(b[3:hlen]); err != nil {
return nil, err
}
data := make([]byte, dlen)
copy(data, b[hlen:n])
d := &UDPDatagram{
Header: header,
Data: data,
}
return d, nil
}
func NewUDPDatagram(header *UDPHeader, data []byte) *UDPDatagram {
return &UDPDatagram{
Header: header,
Data: data,
}
}
func (d *UDPDatagram) Write(w io.Writer) error {
h := d.Header
if h == nil {
h = &UDPHeader{}
}
buf := bytes.Buffer{}
if err := h.Write(&buf); err != nil {
return err
}
if _, err := buf.Write(d.Data); err != nil {
return err
}
_, err := buf.WriteTo(w)
return err
}
func ToSocksAddr(addr net.Addr) *Addr {
host := "0.0.0.0"
port := 0
if addr != nil {
h, p, _ := net.SplitHostPort(addr.String())
host = h
port, _ = strconv.Atoi(p)
}
return &Addr{
Type: ipV4,
Host: host,
Port: uint16(port),
}
}

View File

@ -7,7 +7,7 @@ import (
const PoolSize = 64 * 1024
const PoolSizeSmall = 100
const PoolSizeUdp = 1472
const PoolSizeUdp = 1472 + 200
const PoolSizeCopy = 32 << 10
const PoolSizeBuffer = 4096
const PoolSizeWindow = PoolSizeBuffer - 2 - 4 - 4 - 1
@ -93,18 +93,20 @@ type windowBufferPool struct {
func (Self *windowBufferPool) New() {
Self.pool = sync.Pool{
New: func() interface{} {
return make([]byte, PoolSizeWindow, PoolSizeWindow)
return make([]byte, PoolSizeWindow)
},
}
}
func (Self *windowBufferPool) Get() (buf []byte) {
buf = Self.pool.Get().([]byte)
return buf[:PoolSizeWindow]
buf = buf[:PoolSizeWindow]
return buf
}
func (Self *windowBufferPool) Put(x []byte) {
Self.pool.Put(x[:PoolSizeWindow]) // make buf to full
x = x[:0] // clean buf
Self.pool.Put(x)
}
type bufferPool struct {
@ -146,6 +148,7 @@ func (Self *muxPackagerPool) Get() *MuxPackager {
}
func (Self *muxPackagerPool) Put(pack *MuxPackager) {
pack.reset()
Self.pool.Put(pack)
}

View File

@ -48,9 +48,20 @@ func IsWindows() bool {
func GetLogPath() string {
var path string
if IsWindows() {
path = GetAppPath()
path = filepath.Join(GetAppPath(), "nps.log")
} else {
path = "/tmp"
path = "/var/log/nps.log"
}
return path
}
//interface npc log file path
func GetNpcLogPath() string {
var path string
if IsWindows() {
path = filepath.Join(GetAppPath(), "npc.log")
} else {
path = "/var/log/npc.log"
}
return path
}

View File

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/base64"
"encoding/binary"
"errors"
"html/template"
"io"
"io/ioutil"
@ -15,7 +16,7 @@ import (
"strings"
"sync"
"github.com/cnlh/nps/lib/crypt"
"ehang.io/nps/lib/crypt"
)
//Get the corresponding IP address through domain name
@ -50,7 +51,10 @@ func DomainCheck(domain string) bool {
func CheckAuth(r *http.Request, user, passwd string) bool {
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 {
return false
s = strings.SplitN(r.Header.Get("Proxy-Authorization"), " ", 2)
if len(s) != 2 {
return false
}
}
b, err := base64.StdEncoding.DecodeString(s[1])
@ -109,8 +113,8 @@ func ChangeHostAndHeader(r *http.Request, host string, header string, addr strin
}
addr = strings.Split(addr, ":")[0]
if prior, ok := r.Header["X-Forwarded-For"]; ok {
addr = strings.Join(prior, ", ") + ", " + addr
}
addr = strings.Join(prior, ", ") + ", " + addr
}
r.Header.Set("X-Forwarded-For", addr)
r.Header.Set("X-Real-IP", addr)
}
@ -121,6 +125,7 @@ func ReadAllFromFile(filePath string) ([]byte, error) {
if err != nil {
return nil, err
}
defer f.Close()
return ioutil.ReadAll(f)
}
@ -395,3 +400,62 @@ func GetExtFromPath(path string) string {
}
return string(re.Find([]byte(s[0])))
}
var externalIp string
func GetExternalIp() string {
if externalIp != "" {
return externalIp
}
resp, err := http.Get("http://myexternalip.com/raw")
if err != nil {
return ""
}
defer resp.Body.Close()
content, _ := ioutil.ReadAll(resp.Body)
externalIp = string(content)
return externalIp
}
func GetIntranetIp() (error, string) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, ""
}
for _, address := range addrs {
// 检查ip地址判断是否回环地址
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return nil, ipnet.IP.To4().String()
}
}
}
return errors.New("get intranet ip error"), ""
}
func IsPublicIP(IP net.IP) bool {
if IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() {
return false
}
if ip4 := IP.To4(); ip4 != nil {
switch true {
case ip4[0] == 10:
return false
case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:
return false
case ip4[0] == 192 && ip4[1] == 168:
return false
default:
return true
}
}
return false
}
func GetServerIpByClientIp(clientIp net.IP) string {
if IsPublicIP(clientIp) {
return GetExternalIp()
}
_, ip := GetIntranetIp()
return ip
}

View File

@ -6,8 +6,8 @@ import (
"regexp"
"strings"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/file"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
)
type CommonConfig struct {
@ -241,13 +241,15 @@ func dealTunnel(s string) *file.Tunnel {
t.StripPre = item[1]
case "multi_account":
t.MultiAccount = &file.MultiAccount{}
if b, err := common.ReadAllFromFile(item[1]); err != nil {
panic(err)
} else {
if content, err := common.ParseStr(string(b)); err != nil {
if common.FileExists(item[1]) {
if b, err := common.ReadAllFromFile(item[1]); err != nil {
panic(err)
} else {
t.MultiAccount.AccountMap = dealMultiUser(content)
if content, err := common.ParseStr(string(b)); err != nil {
panic(err)
} else {
t.MultiAccount.AccountMap = dealMultiUser(content)
}
}
}
}

View File

@ -3,11 +3,11 @@ package conn
import (
"bufio"
"bytes"
"ehang.io/nps/lib/goroutine"
"encoding/binary"
"encoding/json"
"errors"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/goroutine"
"io"
"net"
"net/http"
@ -16,11 +16,11 @@ import (
"strings"
"time"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/crypt"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/lib/mux"
"github.com/cnlh/nps/lib/rate"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/mux"
"ehang.io/nps/lib/rate"
"github.com/xtaci/kcp-go"
)
@ -87,7 +87,7 @@ func (s *Conn) GetShortContent(l int) (b []byte, err error) {
//读取指定长度内容
func (s *Conn) ReadLen(cLen int, buf []byte) (int, error) {
if cLen > len(buf) {
if cLen > len(buf) || cLen <= 0 {
return 0, errors.New("长度错误" + strconv.Itoa(cLen))
}
if n, err := io.ReadFull(s, buf[:cLen]); err != nil || n != cLen {
@ -124,8 +124,8 @@ func (s *Conn) SetAlive(tp string) {
case *net.TCPConn:
conn := s.Conn.(*net.TCPConn)
conn.SetReadDeadline(time.Time{})
conn.SetKeepAlive(true)
conn.SetKeepAlivePeriod(time.Duration(2 * time.Second))
//conn.SetKeepAlive(false)
//conn.SetKeepAlivePeriod(time.Duration(2 * time.Second))
case *mux.PortConn:
s.Conn.(*mux.PortConn).SetReadDeadline(time.Time{})
}

View File

@ -1,5 +1,7 @@
package conn
import "time"
type Secret struct {
Password string
Conn *Conn
@ -19,9 +21,20 @@ type Link struct {
Compress bool
LocalProxy bool
RemoteAddr string
Option Options
}
func NewLink(connType string, host string, crypt bool, compress bool, remoteAddr string, localProxy bool) *Link {
type Option func(*Options)
type Options struct {
Timeout time.Duration
}
var defaultTimeOut = time.Second * 5
func NewLink(connType string, host string, crypt bool, compress bool, remoteAddr string, localProxy bool, opts ...Option) *Link {
options := newOptions(opts...)
return &Link{
RemoteAddr: remoteAddr,
ConnType: connType,
@ -29,5 +42,22 @@ func NewLink(connType string, host string, crypt bool, compress bool, remoteAddr
Crypt: crypt,
Compress: compress,
LocalProxy: localProxy,
Option: options,
}
}
func newOptions(opts ...Option) Options {
opt := Options{
Timeout: defaultTimeOut,
}
for _, o := range opts {
o(&opt)
}
return opt
}
func LinkTimeout(t time.Duration) Option {
return func(opt *Options) {
opt.Timeout = t
}
}

View File

@ -3,7 +3,6 @@ package conn
import (
"io"
"github.com/cnlh/nps/lib/common"
"github.com/golang/snappy"
)
@ -32,13 +31,7 @@ func (s *SnappyConn) Write(b []byte) (n int, err error) {
//snappy压缩读
func (s *SnappyConn) Read(b []byte) (n int, err error) {
buf := common.BufPool.Get().([]byte)
defer common.BufPool.Put(buf)
if n, err = s.r.Read(buf); err != nil {
return
}
copy(b, buf[:n])
return
return s.r.Read(b)
}
func (s *SnappyConn) Close() error {

View File

@ -9,7 +9,7 @@ import (
"strconv"
"strings"
"github.com/cnlh/nps/lib/common"
"ehang.io/nps/lib/common"
)
func InitDaemon(f string, runPath string, pidPath string) {

View File

@ -8,8 +8,8 @@ import (
"path/filepath"
"syscall"
"ehang.io/nps/lib/common"
"github.com/astaxie/beego"
"github.com/cnlh/nps/lib/common"
)
func init() {

View File

@ -9,9 +9,9 @@ import (
"strings"
"sync"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/crypt"
"github.com/cnlh/nps/lib/rate"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/rate"
)
type DbUtils struct {

View File

@ -9,8 +9,8 @@ import (
"sync"
"sync/atomic"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/rate"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/rate"
)
func NewJsonDb(runPath string) *JsonDb {

View File

@ -6,7 +6,7 @@ import (
"sync/atomic"
"time"
"github.com/cnlh/nps/lib/rate"
"ehang.io/nps/lib/rate"
"github.com/pkg/errors"
)
@ -50,6 +50,7 @@ type Client struct {
WebPassword string //the password of web login
ConfigConnAllow bool //is allow connected by config file
MaxTunnelNum int
Version string
sync.RWMutex
}

View File

@ -1,8 +1,8 @@
package goroutine
import (
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/file"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"github.com/panjf2000/ants/v2"
"io"
"net"

View File

@ -1,88 +1,151 @@
package install
import (
"ehang.io/nps/lib/common"
"encoding/json"
"errors"
"fmt"
"github.com/c4milo/unpackit"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/cnlh/nps/lib/common"
)
func InstallNps() {
unit := `[Unit]
Description=nps - convenient proxy server
Documentation=https://github.com/cnlh/nps/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target`
service := `[Service]
Type=simple
KillMode=process
Restart=always
RestartSec=15s
StandardOutput=append:/var/log/nps/nps.log
ExecStartPre=/bin/echo 'Starting nps'
ExecStopPost=/bin/echo 'Stopping nps'
ExecStart=`
install := `[Install]
WantedBy=multi-user.target`
path := common.GetInstallPath()
if common.FileExists(path) {
log.Fatalf("the path %s has exist, does not support install", path)
}
MkidrDirAll(path, "conf", "web/static", "web/views")
func UpdateNps() {
destPath := downloadLatest("server")
//复制文件到对应目录
if err := CopyDir(filepath.Join(common.GetAppPath(), "web", "views"), filepath.Join(path, "web", "views")); err != nil {
log.Fatalln(err)
}
if err := CopyDir(filepath.Join(common.GetAppPath(), "web", "static"), filepath.Join(path, "web", "static")); err != nil {
log.Fatalln(err)
}
if err := CopyDir(filepath.Join(common.GetAppPath(), "conf"), filepath.Join(path, "conf")); err != nil {
log.Fatalln(err)
}
copyStaticFile(destPath, "nps")
fmt.Println("Update completed, please restart")
}
func UpdateNpc() {
destPath := downloadLatest("client")
//复制文件到对应目录
copyStaticFile(destPath, "npc")
fmt.Println("Update completed, please restart")
}
type release struct {
TagName string `json:"tag_name"`
}
func downloadLatest(bin string) string {
// get version
data, err := http.Get("https://api.github.com/repos/cnlh/nps/releases/latest")
if err != nil {
log.Fatal(err.Error())
}
b, err := ioutil.ReadAll(data.Body)
if err != nil {
log.Fatal(err)
}
rl := new(release)
json.Unmarshal(b, &rl)
version := rl.TagName
fmt.Println("the latest version is", version)
filename := runtime.GOOS + "_" + runtime.GOARCH + "_" + bin + ".tar.gz"
// download latest package
downloadUrl := fmt.Sprintf("https://ehang.io/nps/releases/download/%s/%s", version, filename)
fmt.Println("download package from ", downloadUrl)
resp, err := http.Get(downloadUrl)
if err != nil {
log.Fatal(err.Error())
}
destPath, err := unpackit.Unpack(resp.Body, "")
if err != nil {
log.Fatal(err)
}
if bin == "server" {
destPath = strings.Replace(destPath, "/web", "", -1)
destPath = strings.Replace(destPath, `\web`, "", -1)
destPath = strings.Replace(destPath, "/views", "", -1)
destPath = strings.Replace(destPath, `\views`, "", -1)
} else {
destPath = strings.Replace(destPath, `\conf`, "", -1)
destPath = strings.Replace(destPath, "/conf", "", -1)
}
return destPath
}
func copyStaticFile(srcPath, bin string) string {
path := common.GetInstallPath()
if bin == "nps" {
//复制文件到对应目录
if err := CopyDir(filepath.Join(srcPath, "web", "views"), filepath.Join(path, "web", "views")); err != nil {
log.Fatalln(err)
}
chMod(filepath.Join(path, "web", "views"), 0766)
if err := CopyDir(filepath.Join(srcPath, "web", "static"), filepath.Join(path, "web", "static")); err != nil {
log.Fatalln(err)
}
chMod(filepath.Join(path, "web", "static"), 0766)
}
binPath, _ := filepath.Abs(os.Args[0])
if !common.IsWindows() {
if _, err := copyFile(filepath.Join(common.GetAppPath(), "nps"), "/usr/bin/nps"); err != nil {
if _, err := copyFile(filepath.Join(common.GetAppPath(), "nps"), "/usr/local/bin/nps"); err != nil {
if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin); err != nil {
if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin); err != nil {
log.Fatalln(err)
} else {
os.Chmod("/usr/local/bin/nps", 0755)
service += "/usr/local/bin/nps"
log.Println("Executable files have been copied to", "/usr/local/bin/nps")
copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin+"-update")
chMod("/usr/local/bin/"+bin+"-update", 0755)
binPath = "/usr/local/bin/" + bin
}
} else {
os.Chmod("/usr/bin/nps", 0755)
service += "/usr/bin/nps"
log.Println("Executable files have been copied to", "/usr/bin/nps")
copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin+"-update")
chMod("/usr/bin/"+bin+"-update", 0755)
binPath = "/usr/bin/" + bin
}
systemd := unit + "\n\n" + service + "\n\n" + install
_ = os.Remove("/usr/lib/systemd/system/nps.service")
err := ioutil.WriteFile("/usr/lib/systemd/system/nps.service", []byte(systemd), 0644)
if err != nil {
log.Println("Write systemd service err ", err)
}
_ = os.Mkdir("/var/log/nps", 644)
} else {
copyFile(filepath.Join(srcPath, bin+".exe"), filepath.Join(common.GetAppPath(), bin+"-update.exe"))
copyFile(filepath.Join(srcPath, bin+".exe"), filepath.Join(common.GetAppPath(), bin+".exe"))
}
chMod(binPath, 0755)
return binPath
}
func InstallNpc() {
path := common.GetInstallPath()
if !common.FileExists(path) {
err := os.Mkdir(path, 0755)
if err != nil {
log.Fatal(err)
}
}
copyStaticFile(common.GetAppPath(), "npc")
}
func InstallNps() string {
path := common.GetInstallPath()
if common.FileExists(path) {
MkidrDirAll(path, "web/static", "web/views")
} else {
MkidrDirAll(path, "conf", "web/static", "web/views")
// not copy config if the config file is exist
if err := CopyDir(filepath.Join(common.GetAppPath(), "conf"), filepath.Join(path, "conf")); err != nil {
log.Fatalln(err)
}
chMod(filepath.Join(path, "conf"), 0766)
}
binPath := copyStaticFile(common.GetAppPath(), "nps")
log.Println("install ok!")
log.Println("Static files and configuration files in the current directory will be useless")
log.Println("The new configuration file is located in", path, "you can edit them")
if !common.IsWindows() {
log.Println(`You can start with:
sudo systemctl enable|disable|start|stop|restart|status nps
or:
nps test|start|stop|restart|status
nps start|stop|restart|uninstall|update or nps-update update
anywhere!`)
} else {
log.Println(`You can copy executable files to any directory and start working with:
nps.exe test|start|stop|restart|status
nps.exe start|stop|restart|uninstall|update or nps-update.exe update
now!`)
}
chMod(common.GetLogPath(), 0777)
return binPath
}
func MkidrDirAll(path string, v ...string) {
for _, item := range v {
@ -119,6 +182,9 @@ func CopyDir(srcPath string, destPath string) error {
destNewPath := strings.Replace(path, srcPath, destPath, -1)
log.Println("copy file ::" + path + " to " + destNewPath)
copyFile(path, destNewPath)
if !common.IsWindows() {
chMod(destNewPath, 0766)
}
}
return nil
})
@ -171,3 +237,9 @@ func pathExists(path string) (bool, error) {
}
return false, err
}
func chMod(name string, mode os.FileMode) {
if !common.IsWindows() {
os.Chmod(name, mode)
}
}

View File

@ -1,7 +1,9 @@
package mux
import (
"ehang.io/nps/lib/common"
"errors"
"github.com/astaxie/beego/logs"
"io"
"math"
"net"
@ -9,8 +11,6 @@ import (
"sync"
"sync/atomic"
"time"
"github.com/cnlh/nps/lib/common"
)
type conn struct {
@ -145,25 +145,44 @@ func (s *conn) SetWriteDeadline(t time.Time) error {
}
type window struct {
remainingWait uint64 // 64bit alignment
off uint32
maxSize uint32
closeOp bool
closeOpCh chan struct{}
mux *Mux
maxSizeDone uint64
// 64bit alignment
// maxSizeDone contains 4 parts
// 1 31 1 31
// wait maxSize useless done
// wait zero means false, one means true
off uint32
closeOp bool
closeOpCh chan struct{}
mux *Mux
}
func (Self *window) unpack(ptrs uint64) (remaining, wait uint32) {
const mask = 1<<dequeueBits - 1
remaining = uint32((ptrs >> dequeueBits) & mask)
wait = uint32(ptrs & mask)
const windowBits = 31
const waitBits = dequeueBits + windowBits
const mask1 = 1
const mask31 = 1<<windowBits - 1
func (Self *window) unpack(ptrs uint64) (maxSize, done uint32, wait bool) {
maxSize = uint32((ptrs >> dequeueBits) & mask31)
done = uint32(ptrs & mask31)
//logs.Warn("unpack", maxSize, done)
if ((ptrs >> waitBits) & mask1) == 1 {
wait = true
return
}
return
}
func (Self *window) pack(remaining, wait uint32) uint64 {
const mask = 1<<dequeueBits - 1
return (uint64(remaining) << dequeueBits) |
uint64(wait&mask)
func (Self *window) pack(maxSize, done uint32, wait bool) uint64 {
//logs.Warn("pack", maxSize, done, wait)
if wait {
return (uint64(1)<<waitBits |
uint64(maxSize&mask31)<<dequeueBits) |
uint64(done&mask31)
}
return (uint64(0)<<waitBits |
uint64(maxSize&mask31)<<dequeueBits) |
uint64(done&mask31)
}
func (Self *window) New() {
@ -180,24 +199,28 @@ func (Self *window) CloseWindow() {
type ReceiveWindow struct {
window
bufQueue ReceiveWindowQueue
bufQueue *ReceiveWindowQueue
element *common.ListElement
count int8
bw *writeBandwidth
once sync.Once
// receive window send the current max size and read size to send window
// means done size actually store the size receive window has read
}
func (Self *ReceiveWindow) New(mux *Mux) {
// initial a window for receive
Self.bufQueue.New()
Self.bufQueue = NewReceiveWindowQueue()
Self.element = common.ListElementPool.Get()
Self.maxSize = common.MAXIMUM_SEGMENT_SIZE * 10
Self.maxSizeDone = Self.pack(common.MAXIMUM_SEGMENT_SIZE*30, 0, false)
Self.mux = mux
Self.window.New()
Self.bw = NewWriteBandwidth()
}
func (Self *ReceiveWindow) remainingSize(delta uint16) (n uint32) {
func (Self *ReceiveWindow) remainingSize(maxSize uint32, delta uint16) (n uint32) {
// receive window remaining
l := int64(atomic.LoadUint32(&Self.maxSize)) - int64(Self.bufQueue.Len())
l := int64(maxSize) - int64(Self.bufQueue.Len())
l -= int64(delta)
if l > 0 {
n = uint32(l)
@ -209,29 +232,46 @@ func (Self *ReceiveWindow) calcSize() {
// calculating maximum receive window size
if Self.count == 0 {
//logs.Warn("ping, bw", Self.mux.latency, Self.bw.Get())
conns := Self.mux.connMap.Size()
n := uint32(math.Float64frombits(atomic.LoadUint64(&Self.mux.latency)) *
Self.mux.bw.Get() / float64(conns))
if n < common.MAXIMUM_SEGMENT_SIZE*10 {
n = common.MAXIMUM_SEGMENT_SIZE * 10
//conns := Self.mux.connMap.Size()
muxBw := Self.mux.bw.Get()
connBw := Self.bw.Get()
//logs.Warn("muxbw connbw", muxBw, connBw)
var n uint32
if connBw > 0 && muxBw > 0 {
n = uint32(math.Float64frombits(atomic.LoadUint64(&Self.mux.latency)) *
(muxBw + connBw))
}
bufLen := Self.bufQueue.Len()
if n < bufLen {
n = bufLen
//logs.Warn(n)
if n < common.MAXIMUM_SEGMENT_SIZE*30 {
//logs.Warn("window small", n, Self.mux.bw.Get(), Self.bw.Get())
n = common.MAXIMUM_SEGMENT_SIZE * 30
}
if n < Self.maxSize/2 {
n = Self.maxSize / 2
for {
ptrs := atomic.LoadUint64(&Self.maxSizeDone)
size, read, wait := Self.unpack(ptrs)
if n < size/2 {
n = size / 2
// half reduce
}
// set the minimal size
if n > 2*size {
n = 2 * size
// twice grow
}
if connBw > 0 && muxBw > 0 {
limit := uint32(common.MAXIMUM_WINDOW_SIZE * (connBw / (muxBw + connBw)))
if n > limit {
logs.Warn("window too large, calculated:", n, "limit:", limit, connBw, muxBw)
n = limit
}
}
// set the maximum size
//logs.Warn("n", n)
if atomic.CompareAndSwapUint64(&Self.maxSizeDone, ptrs, Self.pack(n, read, wait)) {
// only change the maxSize
break
}
}
// set the minimal size
if n > 2*Self.maxSize {
n = 2 * Self.maxSize
}
if n > (common.MAXIMUM_WINDOW_SIZE / uint32(conns)) {
n = common.MAXIMUM_WINDOW_SIZE / uint32(conns)
}
// set the maximum size
//logs.Warn("n", n)
atomic.StoreUint32(&Self.maxSize, n)
Self.count = -10
}
Self.count += 1
@ -243,30 +283,40 @@ func (Self *ReceiveWindow) Write(buf []byte, l uint16, part bool, id int32) (err
return errors.New("conn.receiveWindow: write on closed window")
}
element, err := NewListElement(buf, l, part)
//logs.Warn("push the buf", len(buf), l, (&element).l)
//logs.Warn("push the buf", len(buf), l, element.L)
if err != nil {
return
}
Self.calcSize() // calculate the max window size
var wait uint32
var wait bool
var maxSize, read uint32
start:
ptrs := atomic.LoadUint64(&Self.remainingWait)
_, wait = Self.unpack(ptrs)
newRemaining := Self.remainingSize(l)
ptrs := atomic.LoadUint64(&Self.maxSizeDone)
maxSize, read, wait = Self.unpack(ptrs)
remain := Self.remainingSize(maxSize, l)
// calculate the remaining window size now, plus the element we will push
if newRemaining == 0 {
if remain == 0 && !wait {
//logs.Warn("window full true", remaining)
wait = 1
}
if !atomic.CompareAndSwapUint64(&Self.remainingWait, ptrs, Self.pack(0, wait)) {
goto start
// another goroutine change the status, make sure shall we need wait
}
wait = true
if !atomic.CompareAndSwapUint64(&Self.maxSizeDone, ptrs, Self.pack(maxSize, read, wait)) {
// only change the wait status, not send the read size
goto start
// another goroutine change the status, make sure shall we need wait
}
//logs.Warn("receive window full")
} else if !wait {
if !atomic.CompareAndSwapUint64(&Self.maxSizeDone, ptrs, Self.pack(maxSize, 0, wait)) {
// reset read size here, and send the read size directly
goto start
// another goroutine change the status, make sure shall we need wait
}
} // maybe there are still some data received even if window is full, just keep the wait status
// and push into queue. when receive window read enough, send window will be acknowledged.
Self.bufQueue.Push(element)
// status check finish, now we can push the element into the queue
if wait == 0 {
Self.mux.sendInfo(common.MUX_MSG_SEND_OK, id, Self.maxSize, newRemaining)
// send the remaining window size, not including zero size
if !wait {
Self.mux.sendInfo(common.MUX_MSG_SEND_OK, id, Self.pack(maxSize, read, false))
// send the current status to send window
}
return nil
}
@ -275,9 +325,16 @@ func (Self *ReceiveWindow) Read(p []byte, id int32) (n int, err error) {
if Self.closeOp {
return 0, io.EOF // receive close signal, returns eof
}
Self.bw.StartRead()
n, err = Self.readFromQueue(p, id)
Self.bw.SetCopySize(uint16(n))
return
}
func (Self *ReceiveWindow) readFromQueue(p []byte, id int32) (n int, err error) {
pOff := 0
l := 0
//logs.Warn("receive window read off, element.l", Self.off, Self.element.l)
//logs.Warn("receive window read off, element.l", Self.off, Self.element.L)
copyData:
if Self.off == uint32(Self.element.L) {
// on the first Read method invoked, Self.off and Self.element.l
@ -289,13 +346,13 @@ copyData:
Self.element, err = Self.bufQueue.Pop()
// if the queue is empty, Pop method will wait until one element push
// into the queue successful, or timeout.
// timer start on timeout parameter is set up ,
// reset to 60s if timeout and data still available
// timer start on timeout parameter is set up
Self.off = 0
if err != nil {
return // queue receive stop or time out, break the loop and return
Self.CloseWindow() // also close the window, to avoid read twice
return // queue receive stop or time out, break the loop and return
}
//logs.Warn("pop element", Self.element.l, Self.element.part)
//logs.Warn("pop element", Self.element.L, Self.element.Part)
}
l = copy(p[pOff:], Self.element.Buf[Self.off:Self.element.L])
pOff += l
@ -317,22 +374,41 @@ copyData:
}
func (Self *ReceiveWindow) sendStatus(id int32, l uint16) {
var remaining, wait uint32
var maxSize, read uint32
var wait bool
for {
ptrs := atomic.LoadUint64(&Self.remainingWait)
remaining, wait = Self.unpack(ptrs)
remaining += uint32(l)
if atomic.CompareAndSwapUint64(&Self.remainingWait, ptrs, Self.pack(remaining, 0)) {
break
ptrs := atomic.LoadUint64(&Self.maxSizeDone)
maxSize, read, wait = Self.unpack(ptrs)
if read <= (read+uint32(l))&mask31 {
read += uint32(l)
remain := Self.remainingSize(maxSize, 0)
if wait && remain > 0 || read >= maxSize/2 || remain == maxSize {
if atomic.CompareAndSwapUint64(&Self.maxSizeDone, ptrs, Self.pack(maxSize, 0, false)) {
// now we get the current window status success
// receive window free up some space we need acknowledge send window, also reset the read size
// still having a condition that receive window is empty and not send the status to send window
// so send the status here
//logs.Warn("receive window free up some space", remain)
Self.mux.sendInfo(common.MUX_MSG_SEND_OK, id, Self.pack(maxSize, read, false))
break
}
} else {
if atomic.CompareAndSwapUint64(&Self.maxSizeDone, ptrs, Self.pack(maxSize, read, wait)) {
// receive window not into the wait status, or still not having any space now,
// just change the read size
break
}
}
} else {
//overflow
if atomic.CompareAndSwapUint64(&Self.maxSizeDone, ptrs, Self.pack(maxSize, uint32(l), wait)) {
// reset to l
Self.mux.sendInfo(common.MUX_MSG_SEND_OK, id, Self.pack(maxSize, read, false))
break
}
}
runtime.Gosched()
// another goroutine change remaining or wait status, make sure
// we need acknowledge other side
}
// now we get the current window status success
if wait == 1 {
//logs.Warn("send the wait status", remaining)
Self.mux.sendInfo(common.MUX_MSG_SEND_OK, id, atomic.LoadUint32(&Self.maxSize), remaining)
}
return
}
@ -361,14 +437,14 @@ func (Self *ReceiveWindow) release() {
// common.ListElementPool.Put(Self.element)
//}
for {
Self.element = Self.bufQueue.TryPop()
if Self.element == nil {
ele := Self.bufQueue.TryPop()
if ele == nil {
return
}
if Self.element.Buf != nil {
common.WindowBuff.Put(Self.element.Buf)
if ele.Buf != nil {
common.WindowBuff.Put(ele.Buf)
}
common.ListElementPool.Put(Self.element)
common.ListElementPool.Put(ele)
} // release resource
}
@ -377,12 +453,14 @@ type SendWindow struct {
buf []byte
setSizeCh chan struct{}
timeout time.Time
// send window receive the receive window max size and read size
// done size store the size send window has send, send and read will be totally equal
// so send minus read, send window can get the current window size remaining
}
func (Self *SendWindow) New(mux *Mux) {
Self.setSizeCh = make(chan struct{})
Self.maxSize = common.MAXIMUM_SEGMENT_SIZE * 10
atomic.AddUint64(&Self.remainingWait, uint64(common.MAXIMUM_SEGMENT_SIZE*10)<<dequeueBits)
Self.maxSizeDone = Self.pack(common.MAXIMUM_SEGMENT_SIZE*30, 0, false)
Self.mux = mux
Self.window.New()
}
@ -393,7 +471,15 @@ func (Self *SendWindow) SetSendBuf(buf []byte) {
Self.off = 0
}
func (Self *SendWindow) SetSize(windowSize, newRemaining uint32) (closed bool) {
func (Self *SendWindow) remainingSize(maxSize, send uint32) uint32 {
l := int64(maxSize&mask31) - int64(send&mask31)
if l > 0 {
return uint32(l)
}
return 0
}
func (Self *SendWindow) SetSize(currentMaxSizeDone uint64) (closed bool) {
// set the window size from receive window
defer func() {
if recover() != nil {
@ -405,26 +491,34 @@ func (Self *SendWindow) SetSize(windowSize, newRemaining uint32) (closed bool) {
return true
}
//logs.Warn("set send window size to ", windowSize, newRemaining)
var remaining, wait, newWait uint32
var maxsize, send uint32
var wait, newWait bool
currentMaxSize, read, _ := Self.unpack(currentMaxSizeDone)
for {
ptrs := atomic.LoadUint64(&Self.remainingWait)
remaining, wait = Self.unpack(ptrs)
if remaining == newRemaining {
//logs.Warn("waiting for another window size")
return false // waiting for receive another usable window size
ptrs := atomic.LoadUint64(&Self.maxSizeDone)
maxsize, send, wait = Self.unpack(ptrs)
if read > send {
logs.Error("window read > send: max size:", currentMaxSize, "read:", read, "send", send)
return
}
if newRemaining == 0 && wait == 1 {
newWait = 1 // keep the wait status,
// also if newRemaining is not zero, change wait to 0
if read == 0 && currentMaxSize == maxsize {
return
}
if atomic.CompareAndSwapUint64(&Self.remainingWait, ptrs, Self.pack(newRemaining, newWait)) {
send -= read
remain := Self.remainingSize(currentMaxSize, send)
if remain == 0 && wait {
// just keep the wait status
newWait = true
}
// remain > 0, change wait to false. or remain == 0, wait is false, just keep it
if atomic.CompareAndSwapUint64(&Self.maxSizeDone, ptrs, Self.pack(currentMaxSize, send, newWait)) {
break
}
// anther goroutine change wait status or window size
}
if wait == 1 {
if wait && !newWait {
// send window into the wait status, need notice the channel
//logs.Warn("send window remaining size is 0")
//logs.Warn("send window allow")
Self.allow()
}
// send window not into the wait status, so just do slide
@ -443,7 +537,22 @@ func (Self *SendWindow) allow() {
}
func (Self *SendWindow) sent(sentSize uint32) {
atomic.AddUint64(&Self.remainingWait, ^(uint64(sentSize)<<dequeueBits - 1))
var maxSie, send uint32
var wait bool
for {
ptrs := atomic.LoadUint64(&Self.maxSizeDone)
maxSie, send, wait = Self.unpack(ptrs)
if (send+sentSize)&mask31 < send {
// overflow
runtime.Gosched()
continue
}
if atomic.CompareAndSwapUint64(&Self.maxSizeDone, ptrs, Self.pack(maxSie, send+sentSize, wait)) {
// set the send size
//logs.Warn("sent", maxSie, send+sentSize, wait)
break
}
}
}
func (Self *SendWindow) WriteTo() (p []byte, sendSize uint32, part bool, err error) {
@ -456,12 +565,14 @@ func (Self *SendWindow) WriteTo() (p []byte, sendSize uint32, part bool, err err
return nil, 0, false, io.EOF
// send window buff is drain, return eof and get another one
}
var remaining uint32
var maxSize, send uint32
start:
ptrs := atomic.LoadUint64(&Self.remainingWait)
remaining, _ = Self.unpack(ptrs)
if remaining == 0 {
if !atomic.CompareAndSwapUint64(&Self.remainingWait, ptrs, Self.pack(0, 1)) {
ptrs := atomic.LoadUint64(&Self.maxSizeDone)
maxSize, send, _ = Self.unpack(ptrs)
remain := Self.remainingSize(maxSize, send)
if remain == 0 {
if !atomic.CompareAndSwapUint64(&Self.maxSizeDone, ptrs, Self.pack(maxSize, send, true)) {
// just change the status wait status
goto start // another goroutine change the window, try again
}
// into the wait status
@ -474,17 +585,17 @@ start:
goto start
}
// there are still remaining window
//logs.Warn("rem", remaining)
//logs.Warn("rem", remain, maxSize, send)
if len(Self.buf[Self.off:]) > common.MAXIMUM_SEGMENT_SIZE {
sendSize = common.MAXIMUM_SEGMENT_SIZE
//logs.Warn("cut buf by mss")
} else {
sendSize = uint32(len(Self.buf[Self.off:]))
}
if remaining < sendSize {
if remain < sendSize {
// usable window size is small than
// window MAXIMUM_SEGMENT_SIZE or send buf left
sendSize = remaining
sendSize = remain
//logs.Warn("cut buf by remainingsize", sendSize, len(Self.buf[Self.off:]))
}
//logs.Warn("send size", sendSize)
@ -499,8 +610,16 @@ start:
func (Self *SendWindow) waitReceiveWindow() (err error) {
t := Self.timeout.Sub(time.Now())
if t < 0 {
t = time.Minute * 5
if t < 0 { // not set the timeout, wait for it as long as connection close
select {
case _, ok := <-Self.setSizeCh:
if !ok {
return errors.New("conn.writeWindow: window closed")
}
return nil
case <-Self.closeOpCh:
return errors.New("conn.writeWindow: window closed")
}
}
timer := time.NewTimer(t)
defer timer.Stop()
@ -555,81 +674,49 @@ func (Self *SendWindow) SetTimeOut(t time.Time) {
Self.timeout = t
}
//type bandwidth struct {
// readStart time.Time
// lastReadStart time.Time
// readEnd time.Time
// lastReadEnd time.Time
// bufLength int
// lastBufLength int
// count int8
// readBW float64
// writeBW float64
// readBandwidth float64
//}
//
//func (Self *bandwidth) StartRead() {
// Self.lastReadStart, Self.readStart = Self.readStart, time.Now()
// if !Self.lastReadStart.IsZero() {
// if Self.count == -5 {
// Self.calcBandWidth()
// }
// }
//}
//
//func (Self *bandwidth) EndRead() {
// Self.lastReadEnd, Self.readEnd = Self.readEnd, time.Now()
// if Self.count == -5 {
// Self.calcWriteBandwidth()
// }
// if Self.count == 0 {
// Self.calcReadBandwidth()
// Self.count = -6
// }
// Self.count += 1
//}
//
//func (Self *bandwidth) SetCopySize(n int) {
// // must be invoke between StartRead and EndRead
// Self.lastBufLength, Self.bufLength = Self.bufLength, n
//}
//// calculating
//// start end start end
//// read read
//// write
//
//func (Self *bandwidth) calcBandWidth() {
// t := Self.readStart.Sub(Self.lastReadStart)
// if Self.lastBufLength >= 32768 {
// Self.readBandwidth = float64(Self.lastBufLength) / t.Seconds()
// }
//}
//
//func (Self *bandwidth) calcReadBandwidth() {
// // Bandwidth between nps and npc
// readTime := Self.readEnd.Sub(Self.readStart)
// Self.readBW = float64(Self.bufLength) / readTime.Seconds()
// //logs.Warn("calc read bw", Self.readBW, Self.bufLength, readTime.Seconds())
//}
//
//func (Self *bandwidth) calcWriteBandwidth() {
// // Bandwidth between nps and user, npc and application
// writeTime := Self.readStart.Sub(Self.lastReadEnd)
// Self.writeBW = float64(Self.lastBufLength) / writeTime.Seconds()
// //logs.Warn("calc write bw", Self.writeBW, Self.bufLength, writeTime.Seconds())
//}
//
//func (Self *bandwidth) Get() (bw float64) {
// // The zero value, 0 for numeric types
// if Self.writeBW == 0 && Self.readBW == 0 {
// //logs.Warn("bw both 0")
// return 100
// }
// if Self.writeBW == 0 && Self.readBW != 0 {
// return Self.readBW
// }
// if Self.readBW == 0 && Self.writeBW != 0 {
// return Self.writeBW
// }
// return Self.readBandwidth
//}
type writeBandwidth struct {
writeBW uint64 // store in bits, but it's float64
readEnd time.Time
duration float64
bufLength uint32
}
const writeCalcThreshold uint32 = 5 * 1024 * 1024
func NewWriteBandwidth() *writeBandwidth {
return &writeBandwidth{}
}
func (Self *writeBandwidth) StartRead() {
if Self.readEnd.IsZero() {
Self.readEnd = time.Now()
}
Self.duration += time.Now().Sub(Self.readEnd).Seconds()
if Self.bufLength >= writeCalcThreshold {
Self.calcBandWidth()
}
}
func (Self *writeBandwidth) SetCopySize(n uint16) {
Self.bufLength += uint32(n)
Self.endRead()
}
func (Self *writeBandwidth) endRead() {
Self.readEnd = time.Now()
}
func (Self *writeBandwidth) calcBandWidth() {
atomic.StoreUint64(&Self.writeBW, math.Float64bits(float64(Self.bufLength)/Self.duration))
Self.bufLength = 0
Self.duration = 0
}
func (Self *writeBandwidth) Get() (bw float64) {
// The zero value, 0 for numeric types
bw = math.Float64frombits(atomic.LoadUint64(&Self.writeBW))
if bw <= 0 {
bw = 0
}
return
}

View File

@ -5,11 +5,12 @@ import (
"io"
"math"
"net"
"os"
"sync/atomic"
"time"
"ehang.io/nps/lib/common"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/common"
)
type Mux struct {
@ -34,13 +35,18 @@ type Mux struct {
func NewMux(c net.Conn, connType string) *Mux {
//c.(*net.TCPConn).SetReadBuffer(0)
//c.(*net.TCPConn).SetWriteBuffer(0)
_ = c.SetDeadline(time.Time{})
fd, err := getConnFd(c)
if err != nil {
logs.Warn(err)
}
m := &Mux{
conn: c,
connMap: NewConnMap(),
id: 0,
closeChan: make(chan struct{}, 1),
newConnCh: make(chan *conn),
bw: new(bandwidth),
bw: NewBandwidth(fd),
IsClose: false,
connType: connType,
pingCh: make(chan []byte),
@ -173,9 +179,9 @@ func (s *Mux) ping() {
s.sendInfo(common.MUX_PING_FLAG, common.MUX_PING, now)
// send the ping flag and get the latency first
ticker := time.NewTicker(time.Second * 5)
defer ticker.Stop()
for {
if s.IsClose {
ticker.Stop()
break
}
select {
@ -198,6 +204,7 @@ func (s *Mux) ping() {
}
atomic.AddUint32(&s.pingOk, 1)
}
return
}()
}
@ -213,7 +220,7 @@ func (s *Mux) pingReturn() {
case data = <-s.pingCh:
atomic.StoreUint32(&s.pingCheckTime, 0)
case <-s.closeChan:
break
return
}
_ = now.UnmarshalText(data)
latency := time.Now().UTC().Sub(now).Seconds() / 2
@ -296,7 +303,7 @@ func (s *Mux) readSession() {
if connection.isClose {
continue
}
connection.sendWindow.SetSize(pack.Window, pack.ReadLength)
connection.sendWindow.SetSize(pack.Window)
continue
case common.MUX_CONN_CLOSE: //close the connection
connection.closeFlag = true
@ -390,13 +397,19 @@ type bandwidth struct {
readStart time.Time
lastReadStart time.Time
bufLength uint32
fd *os.File
calcThreshold uint32
}
func NewBandwidth(fd *os.File) *bandwidth {
return &bandwidth{fd: fd}
}
func (Self *bandwidth) StartRead() {
if Self.readStart.IsZero() {
Self.readStart = time.Now()
}
if Self.bufLength >= common.MAXIMUM_SEGMENT_SIZE*300 {
if Self.bufLength >= Self.calcThreshold {
Self.lastReadStart, Self.readStart = Self.readStart, time.Now()
Self.calcBandWidth()
}
@ -408,7 +421,21 @@ func (Self *bandwidth) SetCopySize(n uint16) {
func (Self *bandwidth) calcBandWidth() {
t := Self.readStart.Sub(Self.lastReadStart)
atomic.StoreUint64(&Self.readBandwidth, math.Float64bits(float64(Self.bufLength)/t.Seconds()))
bufferSize, err := sysGetSock(Self.fd)
//logs.Warn(bufferSize)
if err != nil {
logs.Warn(err)
Self.bufLength = 0
return
}
if Self.bufLength >= uint32(bufferSize) {
atomic.StoreUint64(&Self.readBandwidth, math.Float64bits(float64(Self.bufLength)/t.Seconds()))
// calculate the whole socket buffer, the time meaning to fill the buffer
//logs.Warn(Self.Get())
} else {
Self.calcThreshold = uint32(bufferSize)
}
// socket buffer size is bigger than bufLength, so we don't calculate it
Self.bufLength = 0
}
@ -416,7 +443,7 @@ func (Self *bandwidth) Get() (bw float64) {
// The zero value, 0 for numeric types
bw = math.Float64frombits(atomic.LoadUint64(&Self.readBandwidth))
if bw <= 0 {
bw = 100
bw = 0
}
//logs.Warn(bw)
return

View File

@ -2,9 +2,9 @@ package mux
import (
"bufio"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/goroutine"
"fmt"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/goroutine"
"io"
"log"
"net"
@ -34,6 +34,7 @@ func TestNewMux(t *testing.T) {
time.Sleep(time.Second * 3)
go func() {
m2 := NewMux(conn2, "tcp")
//m2 := NewMux(conn2, "kcp")
for {
//logs.Warn("npc starting accept")
c, err := m2.Accept()
@ -83,6 +84,7 @@ func TestNewMux(t *testing.T) {
go func() {
m1 := NewMux(conn1, "tcp")
//m1 := NewMux(conn1, "kcp")
l, err := net.Listen("tcp", "127.0.0.1:7777")
if err != nil {
logs.Warn(err)
@ -145,11 +147,13 @@ func TestNewMux(t *testing.T) {
func server() {
var err error
l, err := net.Listen("tcp", "127.0.0.1:9999")
//l, err := kcp.Listen("127.0.0.1:9999")
if err != nil {
logs.Warn(err)
}
go func() {
conn1, err = l.Accept()
//logs.Info("accept", conn1)
if err != nil {
logs.Warn(err)
}
@ -160,6 +164,8 @@ func server() {
func client() {
var err error
conn2, err = net.Dial("tcp", "127.0.0.1:9999")
//logs.Warn("dial")
//conn2, err = kcp.Dial("127.0.0.1:9999")
if err != nil {
logs.Warn(err)
}

View File

@ -6,15 +6,17 @@ import (
)
type PortConn struct {
Conn net.Conn
rs []byte
start int
Conn net.Conn
rs []byte
readMore bool
start int
}
func newPortConn(conn net.Conn, rs []byte) *PortConn {
func newPortConn(conn net.Conn, rs []byte, readMore bool) *PortConn {
return &PortConn{
Conn: conn,
rs: rs,
Conn: conn,
rs: rs,
readMore: readMore,
}
}
@ -29,9 +31,15 @@ func (pConn *PortConn) Read(b []byte) (n int, err error) {
defer func() {
pConn.start = len(pConn.rs)
}()
return copy(b, pConn.rs[pConn.start:]), nil
n = copy(b, pConn.rs[pConn.start:])
if !pConn.readMore {
return
}
}
return pConn.Conn.Read(b)
var n2 = 0
n2, err = pConn.Conn.Read(b[n:])
n = n + n2
return
}
func (pConn *PortConn) Write(b []byte) (n int, err error) {

View File

@ -12,8 +12,8 @@ import (
"strings"
"time"
"ehang.io/nps/lib/common"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/common"
"github.com/pkg/errors"
)
@ -89,6 +89,7 @@ func (pMux *PortMux) process(conn net.Conn) {
var ch chan *PortConn
var rs []byte
var buffer bytes.Buffer
var readMore = false
switch common.BytesToNum(buf) {
case HTTP_CONNECT, HTTP_DELETE, HTTP_GET, HTTP_HEAD, HTTP_OPTIONS, HTTP_POST, HTTP_PUT, HTTP_TRACE: //http and manager
buffer.Reset()
@ -123,6 +124,7 @@ func (pMux *PortMux) process(conn net.Conn) {
case CLIENT: // client connection
ch = pMux.clientConn
default: // https
readMore = true
ch = pMux.httpsConn
}
if len(rs) == 0 {
@ -131,7 +133,7 @@ func (pMux *PortMux) process(conn net.Conn) {
timer := time.NewTimer(ACCEPT_TIME_OUT)
select {
case <-timer.C:
case ch <- newPortConn(conn, rs):
case ch <- newPortConn(conn, rs, readMore):
}
}

View File

@ -1,8 +1,8 @@
package mux
import (
"ehang.io/nps/lib/common"
"errors"
"github.com/cnlh/nps/lib/common"
"io"
"math"
"runtime"
@ -209,23 +209,26 @@ func NewListElement(buf []byte, l uint16, part bool) (element *common.ListElemen
}
type ReceiveWindowQueue struct {
lengthWait uint64
chain *bufChain
stopOp chan struct{}
readOp chan struct{}
lengthWait uint64 // really strange ???? need put here
// https://golang.org/pkg/sync/atomic/#pkg-note-BUG
// On non-Linux ARM, the 64-bit functions use instructions unavailable before the ARMv6k core.
// On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility
// to arrange for 64-bit alignment of 64-bit words accessed atomically.
// The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned.
timeout time.Time
timeout time.Time
}
func (Self *ReceiveWindowQueue) New() {
Self.readOp = make(chan struct{})
Self.chain = new(bufChain)
Self.chain.new(64)
Self.stopOp = make(chan struct{}, 2)
func NewReceiveWindowQueue() *ReceiveWindowQueue {
queue := ReceiveWindowQueue{
chain: new(bufChain),
stopOp: make(chan struct{}, 2),
readOp: make(chan struct{}),
}
queue.chain.new(64)
return &queue
}
func (Self *ReceiveWindowQueue) Push(element *common.ListElement) {
@ -300,8 +303,14 @@ func (Self *ReceiveWindowQueue) waitPush() (err error) {
//logs.Warn("wait push")
//defer logs.Warn("wait push finish")
t := Self.timeout.Sub(time.Now())
if t <= 0 {
t = time.Minute * 5
if t <= 0 { // not set the timeout, so wait for it without timeout, just like a tcp connection
select {
case <-Self.readOp:
return nil
case <-Self.stopOp:
err = io.EOF
return
}
}
timer := time.NewTimer(t)
defer timer.Stop()

View File

@ -0,0 +1,46 @@
// +build !windows
package mux
import (
"errors"
"github.com/xtaci/kcp-go"
"net"
"os"
"syscall"
)
func sysGetSock(fd *os.File) (bufferSize int, err error) {
if fd != nil {
return syscall.GetsockoptInt(int(fd.Fd()), syscall.SOL_SOCKET, syscall.SO_RCVBUF)
} else {
return 5 * 1024 * 1024, nil
}
}
func getConnFd(c net.Conn) (fd *os.File, err error) {
switch c.(type) {
case *net.TCPConn:
fd, err = c.(*net.TCPConn).File()
if err != nil {
return
}
return
case *net.UDPConn:
fd, err = c.(*net.UDPConn).File()
if err != nil {
return
}
return
case *kcp.UDPSession:
//fd, err = (*net.UDPConn)(unsafe.Pointer(c.(*kcp.UDPSession))).File()
//if err != nil {
// return
//}
// Todo
return
default:
err = errors.New("mux:unknown conn type, only tcp or kcp")
return
}
}

View File

@ -0,0 +1,46 @@
// +build windows
package mux
import (
"errors"
"github.com/xtaci/kcp-go"
"net"
"os"
)
func sysGetSock(fd *os.File) (bufferSize int, err error) {
// https://github.com/golang/sys/blob/master/windows/syscall_windows.go#L1184
// not support, WTF???
// Todo
// return syscall.GetsockoptInt((syscall.Handle)(unsafe.Pointer(fd.Fd())), syscall.SOL_SOCKET, syscall.SO_RCVBUF)
bufferSize = 5 * 1024 * 1024
return
}
func getConnFd(c net.Conn) (fd *os.File, err error) {
switch c.(type) {
case *net.TCPConn:
//fd, err = c.(*net.TCPConn).File()
//if err != nil {
// return
//}
return
case *net.UDPConn:
//fd, err = c.(*net.UDPConn).File()
//if err != nil {
// return
//}
return
case *kcp.UDPSession:
//fd, err = (*net.UDPConn)(unsafe.Pointer(c.(*kcp.UDPSession))).File()
//if err != nil {
// return
//}
// Todo
return
default:
err = errors.New("mux:unknown conn type, only tcp or kcp")
return
}
}

View File

@ -1,8 +1,8 @@
package version
const VERSION = "0.24.0"
const VERSION = "0.26.0"
// Compulsory minimum version, Minimum downward compatibility to this version
func GetVersion() string {
return "0.24.0"
return "0.26.0"
}

View File

@ -5,9 +5,9 @@ import (
"os"
"strconv"
"ehang.io/nps/lib/mux"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/mux"
)
var pMux *mux.PortMux

View File

@ -6,11 +6,11 @@ import (
"net/http"
"sync"
"ehang.io/nps/bridge"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/bridge"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/file"
)
type Service interface {

View File

@ -13,13 +13,13 @@ import (
"strings"
"sync"
"ehang.io/nps/bridge"
"ehang.io/nps/lib/cache"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"ehang.io/nps/server/connection"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/bridge"
"github.com/cnlh/nps/lib/cache"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/server/connection"
)
type httpServer struct {
@ -171,11 +171,11 @@ reset:
}
}()
for {
if resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != nil {
if resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != nil || resp == nil {
return
} else {
//if the cache is start and the response is in the extension,store the response to the cache list
if s.useCache && strings.Contains(r.URL.Path, ".") {
if s.useCache && r.URL != nil && strings.Contains(r.URL.Path, ".") {
b, err := httputil.DumpResponse(resp, true)
if err != nil {
return

View File

@ -6,13 +6,13 @@ import (
"net/url"
"sync"
"ehang.io/nps/lib/cache"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/file"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/cache"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/crypt"
"github.com/cnlh/nps/lib/file"
"github.com/pkg/errors"
)

View File

@ -5,8 +5,8 @@ import (
"strings"
"time"
"ehang.io/nps/lib/common"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/common"
)
type P2PServer struct {

View File

@ -7,10 +7,10 @@ import (
"net"
"strconv"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/file"
)
const (
@ -154,27 +154,129 @@ func (s *Sock5ModeServer) handleConnect(c net.Conn) {
// passive mode
func (s *Sock5ModeServer) handleBind(c net.Conn) {
}
func (s *Sock5ModeServer) sendUdpReply(writeConn net.Conn, c net.Conn, rep uint8, serverIp string) {
reply := []byte{
5,
rep,
0,
1,
}
localHost, localPort, _ := net.SplitHostPort(c.LocalAddr().String())
localHost = serverIp
ipBytes := net.ParseIP(localHost).To4()
nPort, _ := strconv.Atoi(localPort)
reply = append(reply, ipBytes...)
portBytes := make([]byte, 2)
binary.BigEndian.PutUint16(portBytes, uint16(nPort))
reply = append(reply, portBytes...)
writeConn.Write(reply)
}
//udp
func (s *Sock5ModeServer) handleUDP(c net.Conn) {
/*
+----+------+------+----------+----------+----------+
|RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA |
+----+------+------+----------+----------+----------+
| 2 | 1 | 1 | Variable | 2 | Variable |
+----+------+------+----------+----------+----------+
*/
buf := make([]byte, 3)
c.Read(buf)
// relay udp datagram silently, without any notification to the requesting client
if buf[2] != 0 {
// does not support fragmentation, drop it
logs.Warn("does not support fragmentation, drop")
dummy := make([]byte, maxUDPPacketSize)
c.Read(dummy)
defer c.Close()
addrType := make([]byte, 1)
c.Read(addrType)
var host string
switch addrType[0] {
case ipV4:
ipv4 := make(net.IP, net.IPv4len)
c.Read(ipv4)
host = ipv4.String()
case ipV6:
ipv6 := make(net.IP, net.IPv6len)
c.Read(ipv6)
host = ipv6.String()
case domainName:
var domainLen uint8
binary.Read(c, binary.BigEndian, &domainLen)
domain := make([]byte, domainLen)
c.Read(domain)
host = string(domain)
default:
s.sendReply(c, addrTypeNotSupported)
return
}
//读取端口
var port uint16
binary.Read(c, binary.BigEndian, &port)
logs.Warn(host, string(port))
replyAddr, err := net.ResolveUDPAddr("udp", s.task.ServerIp+":0")
if err != nil {
logs.Error("build local reply addr error", err)
return
}
reply, err := net.ListenUDP("udp", replyAddr)
if err != nil {
s.sendReply(c, addrTypeNotSupported)
logs.Error("listen local reply udp port error")
return
}
// reply the local addr
s.sendUdpReply(c, reply, succeeded, common.GetServerIpByClientIp(c.RemoteAddr().(*net.TCPAddr).IP))
defer reply.Close()
// new a tunnel to client
link := conn.NewLink("udp5", "", s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, c.RemoteAddr().String(), false)
target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task)
if err != nil {
logs.Warn("get connection from client id %d error %s", s.task.Client.Id, err.Error())
return
}
s.doConnect(c, associateMethod)
var clientAddr net.Addr
// copy buffer
go func() {
b := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(b)
defer c.Close()
for {
n, laddr, err := reply.ReadFrom(b)
if err != nil {
logs.Error("read data from %s err %s", reply.LocalAddr().String(), err.Error())
return
}
if clientAddr == nil {
clientAddr = laddr
}
if _, err := target.Write(b[:n]); err != nil {
logs.Error("write data to client error", err.Error())
return
}
}
}()
go func() {
var l int32
b := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(b)
defer c.Close()
for {
if err := binary.Read(target, binary.LittleEndian, &l); err != nil || l >= common.PoolSizeUdp || l <= 0 {
logs.Warn("read len bytes error", err.Error())
return
}
binary.Read(target, binary.LittleEndian, b[:l])
if err != nil {
logs.Warn("read data form client error", err.Error())
return
}
if _, err := reply.WriteTo(b[:l], clientAddr); err != nil {
logs.Warn("write data to user ", err.Error())
return
}
}
}()
b := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(b)
for {
_, err := c.Read(b)
if err != nil {
c.Close()
return
}
}
}
//new conn

View File

@ -7,13 +7,13 @@ import (
"path/filepath"
"strconv"
"ehang.io/nps/bridge"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"ehang.io/nps/server/connection"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/bridge"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/server/connection"
)
type TunnelModeServer struct {
@ -63,15 +63,23 @@ func (s *WebServer) Start() error {
<-stop
}
beego.BConfig.WebConfig.Session.SessionOn = true
beego.SetStaticPath("/static", filepath.Join(common.GetRunPath(), "web", "static"))
beego.SetStaticPath(beego.AppConfig.String("web_base_url")+"/static", filepath.Join(common.GetRunPath(), "web", "static"))
beego.SetViewsPath(filepath.Join(common.GetRunPath(), "web", "views"))
if l, err := connection.GetWebManagerListener(); err == nil {
err := errors.New("Web management startup failure ")
var l net.Listener
if l, err = connection.GetWebManagerListener(); err == nil {
beego.InitBeforeHTTPRun()
http.Serve(l, beego.BeeApp.Handlers)
if beego.AppConfig.String("web_open_ssl") == "true" {
keyPath := beego.AppConfig.String("web_key_file")
certPath := beego.AppConfig.String("web_cert_file")
err = http.ServeTLS(l, beego.BeeApp.Handlers, certPath, keyPath)
} else {
err = http.Serve(l, beego.BeeApp.Handlers)
}
} else {
logs.Error(err)
}
return errors.New("Web management startup failure")
return err
}
func (s *WebServer) Close() error {

View File

@ -7,8 +7,8 @@ import (
"strconv"
"syscall"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/conn"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
)
func HandleTrans(c *conn.Conn, s *TunnelModeServer) error {

View File

@ -3,7 +3,7 @@
package proxy
import (
"github.com/cnlh/nps/lib/conn"
"ehang.io/nps/lib/conn"
)
func HandleTrans(c *conn.Conn, s *TunnelModeServer) error {

View File

@ -4,11 +4,11 @@ import (
"net"
"strings"
"ehang.io/nps/bridge"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/conn"
"ehang.io/nps/lib/file"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/bridge"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/file"
)
type UdpModeServer struct {

View File

@ -1,6 +1,7 @@
package server
import (
"ehang.io/nps/lib/version"
"errors"
"math"
"os"
@ -8,13 +9,13 @@ import (
"strings"
"time"
"ehang.io/nps/bridge"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"ehang.io/nps/server/proxy"
"ehang.io/nps/server/tool"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/bridge"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/server/proxy"
"github.com/cnlh/nps/server/tool"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
@ -109,6 +110,7 @@ func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string) {
func dealClientFlow() {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@ -270,8 +272,9 @@ func GetClientList(start, length int, search, sort, order string, clientId int)
func dealClientData() {
file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool {
v := value.(*file.Client)
if _, ok := Bridge.Client.Load(v.Id); ok {
if vv, ok := Bridge.Client.Load(v.Id); ok {
v.IsConnect = true
v.Version = vv.(*bridge.Client).Version
} else {
v.IsConnect = false
}
@ -337,8 +340,12 @@ func DelClientConnect(clientId int) {
func GetDashboardData() map[string]interface{} {
data := make(map[string]interface{})
data["version"] = version.VERSION
data["hostCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Hosts)
data["clientCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Clients) - 1 //Remove the public key client
data["clientCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Clients)
if beego.AppConfig.String("public_vkey") != "" { //remove public vkey
data["clientCount"] = data["clientCount"].(int) - 1
}
dealClientData()
c := 0
var in, out int64
@ -430,6 +437,7 @@ func GetDashboardData() map[string]interface{} {
func flowSession(m time.Duration) {
ticker := time.NewTicker(m)
defer ticker.Stop()
for {
select {
case <-ticker.C:

View File

@ -5,9 +5,9 @@ import (
"path/filepath"
"strconv"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"github.com/astaxie/beego"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/file"
)
func TestServerConfig() {

View File

@ -5,8 +5,8 @@ import (
"strconv"
"time"
"ehang.io/nps/lib/common"
"github.com/astaxie/beego"
"github.com/cnlh/nps/lib/common"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
@ -31,6 +31,9 @@ func InitAllowPort() {
}
func TestServerPort(p int, m string) (b bool) {
if m == "p2p" || m == "secret" {
return true
}
if p > 65535 || p < 0 {
return false
}

View File

@ -4,8 +4,8 @@ import (
"encoding/hex"
"time"
"ehang.io/nps/lib/crypt"
"github.com/astaxie/beego"
"github.com/cnlh/nps/lib/crypt"
)
type AuthController struct {

View File

@ -7,11 +7,11 @@ import (
"strings"
"time"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/file"
"ehang.io/nps/server"
"github.com/astaxie/beego"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/crypt"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/server"
)
type BaseController struct {
@ -22,6 +22,7 @@ type BaseController struct {
//初始化参数
func (s *BaseController) Prepare() {
s.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
controllerName, actionName := s.GetControllerAndAction()
s.controllerName = strings.ToLower(controllerName[0 : len(controllerName)-10])
s.actionName = strings.ToLower(actionName)
@ -34,7 +35,7 @@ func (s *BaseController) Prepare() {
timeNowUnix := time.Now().Unix()
if !((math.Abs(float64(timeNowUnix-int64(timestamp))) <= 20) && (crypt.Md5(configKey+strconv.Itoa(timestamp)) == md5Key)) {
if s.GetSession("auth") != true {
s.Redirect("/login/index", 302)
s.Redirect(beego.AppConfig.String("web_base_url")+"/login/index", 302)
}
}
if s.GetSession("isAdmin") != nil && !s.GetSession("isAdmin").(bool) {
@ -60,6 +61,7 @@ func (s *BaseController) Prepare() {
//加载模板
func (s *BaseController) display(tpl ...string) {
s.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
var tplname string
if s.Data["menu"] == nil {
s.Data["menu"] = s.actionName
@ -83,6 +85,7 @@ func (s *BaseController) display(tpl ...string) {
//错误
func (s *BaseController) error() {
s.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
s.Layout = "public/layout.html"
s.TplName = "public/error.html"
}

View File

@ -1,11 +1,11 @@
package controllers
import (
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/rate"
"ehang.io/nps/server"
"github.com/astaxie/beego"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/lib/rate"
"github.com/cnlh/nps/server"
)
type ClientController struct {

View File

@ -1,9 +1,11 @@
package controllers
import (
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/server"
"github.com/cnlh/nps/server/tool"
"ehang.io/nps/lib/file"
"ehang.io/nps/server"
"ehang.io/nps/server/tool"
"github.com/astaxie/beego"
)
type IndexController struct {
@ -11,6 +13,7 @@ type IndexController struct {
}
func (s *IndexController) Index() {
s.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
s.Data["data"] = server.GetDashboardData()
s.SetInfo("dashboard")
s.display("index/index")

View File

@ -1,26 +1,52 @@
package controllers
import (
"math/rand"
"net"
"sync"
"time"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"ehang.io/nps/server"
"github.com/astaxie/beego"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/file"
"github.com/cnlh/nps/server"
)
type LoginController struct {
beego.Controller
}
var ipRecord sync.Map
type record struct {
hasLoginFailTimes int
lastLoginTime time.Time
}
func (self *LoginController) Index() {
self.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
self.Data["register_allow"], _ = beego.AppConfig.Bool("allow_user_register")
self.TplName = "login/index.html"
}
func (self *LoginController) Verify() {
clearIprecord()
ip, _, _ := net.SplitHostPort(self.Ctx.Request.RemoteAddr)
if v, ok := ipRecord.Load(ip); ok {
vv := v.(*record)
if (time.Now().Unix() - vv.lastLoginTime.Unix()) >= 60 {
vv.hasLoginFailTimes = 0
}
if vv.hasLoginFailTimes >= 10 {
self.Data["json"] = map[string]interface{}{"status": 0, "msg": "username or password incorrect"}
self.ServeJSON()
return
}
}
var auth bool
if self.GetString("password") == beego.AppConfig.String("web_password") && self.GetString("username") == beego.AppConfig.String("web_username") {
self.SetSession("isAdmin", true)
self.DelSession("clientId")
self.DelSession("username")
auth = true
server.Bridge.Register.Store(common.GetIpByAddr(self.Ctx.Input.IP()), time.Now().Add(time.Hour*time.Duration(2)))
}
@ -53,13 +79,21 @@ func (self *LoginController) Verify() {
if auth {
self.SetSession("auth", true)
self.Data["json"] = map[string]interface{}{"status": 1, "msg": "login success"}
ipRecord.Delete(ip)
} else {
if v, load := ipRecord.LoadOrStore(ip, &record{hasLoginFailTimes: 1, lastLoginTime: time.Now()}); load {
vv := v.(*record)
vv.lastLoginTime = time.Now()
vv.hasLoginFailTimes += 1
ipRecord.Store(ip, vv)
}
self.Data["json"] = map[string]interface{}{"status": 0, "msg": "username or password incorrect"}
}
self.ServeJSON()
}
func (self *LoginController) Register() {
if self.Ctx.Request.Method == "GET" {
self.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
self.TplName = "login/register.html"
} else {
if b, err := beego.AppConfig.Bool("allow_user_register"); err != nil || !b {
@ -91,5 +125,19 @@ func (self *LoginController) Register() {
func (self *LoginController) Out() {
self.SetSession("auth", false)
self.Redirect("/login/index", 302)
self.Redirect(beego.AppConfig.String("web_base_url")+"/login/index", 302)
}
func clearIprecord() {
rand.Seed(time.Now().UnixNano())
x := rand.Intn(100)
if x == 1 {
ipRecord.Range(func(key, value interface{}) bool {
v := value.(*record)
if time.Now().Unix()-v.lastLoginTime.Unix() >= 60 {
ipRecord.Delete(key)
}
return true
})
}
}

View File

@ -1,14 +1,26 @@
package routers
import (
"ehang.io/nps/web/controllers"
"github.com/astaxie/beego"
"github.com/cnlh/nps/web/controllers"
)
func init() {
beego.Router("/", &controllers.IndexController{}, "*:Index")
beego.AutoRouter(&controllers.IndexController{})
beego.AutoRouter(&controllers.LoginController{})
beego.AutoRouter(&controllers.ClientController{})
beego.AutoRouter(&controllers.AuthController{})
func Init() {
web_base_url := beego.AppConfig.String("web_base_url")
if len(web_base_url) > 0 {
ns := beego.NewNamespace(web_base_url,
beego.NSRouter("/", &controllers.IndexController{}, "*:Index"),
beego.NSAutoRouter(&controllers.IndexController{}),
beego.NSAutoRouter(&controllers.LoginController{}),
beego.NSAutoRouter(&controllers.ClientController{}),
beego.NSAutoRouter(&controllers.AuthController{}),
)
beego.AddNamespace(ns)
} else {
beego.Router("/", &controllers.IndexController{}, "*:Index")
beego.AutoRouter(&controllers.IndexController{})
beego.AutoRouter(&controllers.LoginController{})
beego.AutoRouter(&controllers.ClientController{})
beego.AutoRouter(&controllers.AuthController{})
}
}

View File

@ -12,7 +12,7 @@
$.ajax({
type: "GET",
url: defaults.file,
url: window.nps.web_base_url + defaults.file,
dataType: "xml",
success: function (xml) {
$(xml).find('text').each(function () {
@ -65,4 +65,4 @@ $(document).ready(function () {
setCookie("lang", "zh")
$("body").cloudLang({lang: "zh", file: "/static/page/lang-example.xml"});
});
});
});

View File

@ -5,6 +5,6 @@
<title>nps error</title>
</head>
<body>
404 not found,power by <a href="//github.com/cnlh/nps">nps</a>
404 not found,power by <a href="//ehang.io/nps">nps</a>
</body>
</html>

View File

@ -0,0 +1,189 @@
<content>
<text id="menu-dashboard">
<zh>仪表盘</zh>
<en>dashboard</en>
</text>
<text id="menu-client">
<zh>客户端</zh>
<en>client</en>
</text>
<text id="menu-host">
<zh>域名解析</zh>
<en>host</en>
</text>
<text id="menu-tcp">
<zh>tcp隧道</zh>
<en>tcp</en>
</text>
<text id="menu-udp">
<zh>udp隧道</zh>
<en>udp</en>
</text>
<text id="menu-http">
<zh>http代理</zh>
<en>http</en>
</text>
<text id="menu-socks5">
<zh>socks5代理</zh>
<en>socks5</en>
</text>
<text id="menu-secret">
<zh>私密代理</zh>
<en>secret</en>
</text>
<text id="menu-p2p">
<zh>p2p代理</zh>
<en>p2p</en>
</text>
<text id="menu-file">
<zh>文件代理</zh>
<en>file</en>
</text>
<text id="info-remark">
<zh>备注</zh>
<en>remark</en>
</text>
<text id="info-flow-limit">
<zh>流量限制</zh>
<en>flow limit</en>
</text>
<text id="info-bandwidth">
<zh>带宽限制</zh>
<en>bandwidth</en>
</text>
<text id="info-max-conn-num">
<zh>最大连接数限制</zh>
<en>maximum number of client connections
</en>
</text>
<text id="info-web-auth-username">
<zh>basic权限验证用户名</zh>
<en>web authentication username</en>
</text>
<text id="info-web-auth-password">
<zh>basic权限验证密码</zh>
<en>web authentication password</en>
</text>
<text id="info-client-vkey">
<zh>客户端连接密钥</zh>
<en>client connection key</en>
</text>
<text id="info-compress">
<zh>压缩</zh>
<en>compress</en>
</text>
<text id="info-crypt">
<zh>加密</zh>
<en>crypt</en>
</text>
<text id="info-host">
<zh>域名</zh>
<en>host</en>
</text>
<text id="info-scheme">
<zh>协议类型</zh>
<en>scheme</en>
</text>
<text id="info-url-router">
<zh>url路由</zh>
<en>url router</en>
</text>
<text id="info-client-id">
<zh>客户端id</zh>
<en>client id</en>
</text>
<text id="info-target">
<zh>内网目标ip:端口)</zh>
<en>target of Intranet(ip:port)</en>
</text>
<text id="info-header-modify">
<zh>request header修改</zh>
<en>header modify</en>
</text>
<text id="info-host-change">
<zh>request host修改</zh>
<en>host modify</en>
</text>
<text id="info-mode">
<zh>隧道类型</zh>
<en>mode</en>
</text>
<text id="info-server-port">
<zh>服务端端口</zh>
<en>server port</en>
</text>
<text id="info-server-ip">
<zh>服务端端口</zh>
<en>server ip</en>
</text>
<text id="info-crypt">
<zh>加密</zh>
<en>crypt</en>
</text>
<text id="info-local-path">
<zh>本地路径</zh>
<en>local path</en>
</text>
<text id="info-strip-pre">
<zh>访问前缀</zh>
<en>strip pre</en>
</text>
<text id="info-unique-vkey">
<zh>唯一识别密钥</zh>
<en>unique vkey</en>
</text>
<text id="info-new">
<zh>新增</zh>
<en>add</en>
</text>
<text id="info-now-conn-num">
<zh>当前连接数</zh>
<en>now conn num</en>
</text>
<text id="info-export-flow">
<en>export flow</en>
</text>
<text id="info-inlet-flow">
<en>inlet flow</en>
</text>
<text id="info-command">
<en>command</en>
</text>
<text id="info-config-conn-allow">
<en>allow client connect by config file</en>
</text>
<text id="info-client-web-username">
<en>username of web login</en>
</text>
<text id="info-client-web-password">
<en>password of web login</en>
</text>
<text id="info-https-cert">
<en>https cert file path</en>
</text>
<text id="info-https-key">
<en>https key file path</en>
</text>
<text id="info-max-tunnel-num">
<en>max tunnel num</en>
</text>
<text id="info-local-proxy">
<en>Is the proxy local to the server?</en>
</text>
<text id="info-save">
<en>save</en>
</text>
</content>

View File

@ -119,7 +119,7 @@
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-success" href="#" id="add"><i
<button class="btn btn-success" type="button" id="add"><i
class="fa fa-fw fa-lg fa-eye"></i>新增
</button>
</div>
@ -134,7 +134,7 @@
$("#add").on("click", function () {
$.ajax({
type: "POST",
url: "/client/add",
url: "{{.web_base_url}}/client/add",
data: $("form").serializeArray(),
success: function (res) {
alert(res.msg)

View File

@ -128,7 +128,7 @@
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-success" href="#" id="add"><i
<button class="btn btn-success" type="button" id="add"><i
class="fa fa-fw fa-lg fa-eye"></i><span langtag="info-save">保存</span>
</button>
</div>
@ -145,7 +145,7 @@
$("#add").on("click", function () {
$.ajax({
type: "POST",
url: "/client/edit",
url: "{{.web_base_url}}/client/edit",
data: $("form").serializeArray(),
success: function (res) {
alert(res.msg)

View File

@ -20,7 +20,7 @@
<div class="table-responsive">
<div id="toolbar">
<a href="/client/add" class="btn btn-primary dim" type="button" langtag="info-new">新增</a>
<a href="{{.web_base_url}}/client/add" class="btn btn-primary dim" type="button" langtag="info-new">新增</a>
</div>
<table id="taskList_table" class="table-striped table-hover"
data-mobile-responsive="true"></table>
@ -42,7 +42,7 @@
if (confirm("Are you sure you want to delete it")) {
$.ajax({
type: "POST",
url: "/client/del",
url: "{{.web_base_url}}/client/del",
data: {"id": id},
success: function (res) {
alert(res.msg)
@ -58,7 +58,7 @@
if (confirm("Are you sure you want to start it")) {
$.ajax({
type: "POST",
url: "/client/changestatus",
url: "{{.web_base_url}}/client/changestatus",
data: {"id": id, "status": 1},
success: function (res) {
alert(res.msg)
@ -74,7 +74,7 @@
if (confirm("Are you sure you want to stop it")) {
$.ajax({
type: "POST",
url: "/client/changestatus",
url: "{{.web_base_url}}/client/changestatus",
data: {
"id": id, "status": 0
},
@ -90,26 +90,26 @@
}
function edit(id) {
window.location.href = "/client/edit?id=" + id
window.location.href = "{{.web_base_url}}/client/edit?id=" + id
}
function add() {
window.location.href = "/client/add"
window.location.href = "{{.web_base_url}}/client/add"
}
function tunnel(id) {
window.location.href = "/index/all?client_id=" + id
window.location.href = "{{.web_base_url}}/index/all?client_id=" + id
}
function host(id) {
window.location.href = "/index/hostlist?client_id=" + id
window.location.href = "{{.web_base_url}}/index/hostlist?client_id=" + id
}
/*bootstrap table*/
$('#table').bootstrapTable({
toolbar: "#toolbar",
method: 'post', // 服务器数据的请求方式 get or post
url: "/client/list", // 服务器数据的加载地址
url: "{{.web_base_url}}/client/list", // 服务器数据的加载地址
contentType: "application/x-www-form-urlencoded",
striped: true, // 设置为true会有隔行变色效果
search: true,
@ -150,6 +150,11 @@
title: 'remark',//标题
visible: true,//false表示不显示
},
{
field: 'Version',//域值
title: 'version',//标题
visible: true,//false表示不显示
},
{
field: 'VerifyKey',//域值
title: 'vkey',//标题

View File

@ -101,7 +101,7 @@
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
&nbsp;<button class="btn btn-success" href="#" id="add"><i
&nbsp;<button class="btn btn-success" type="button" id="add"><i
class="fa fa-fw fa-lg fa-eye"></i>新增
</button>
</div>
@ -162,7 +162,7 @@
$("#add").on("click", function () {
$.ajax({
type: "POST",
url: "/index/add",
url: "{{.web_base_url}}/index/add",
data: $("form").serializeArray(),
success: function (res) {
alert(res.msg)

Some files were not shown because too many files have changed in this diff Show More