Compare commits

..

No commits in common. "master" and "v0.24.0" have entirely different histories.

164 changed files with 30176 additions and 18871 deletions

View File

@ -1,163 +0,0 @@
name: Release
on:
release:
types: [published]
branches: [ master ]
jobs:
build_assets:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: 1.15
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Build
run: |
chmod +x build.assets.sh
./build.assets.sh
- name: Upload
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
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_sdk.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build_android:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Build
run: |
chmod +x build.android.sh
docker run --rm -i -w /app -v $(pwd):/app -e ANDROID_HOME=/usr/local/android_sdk -e GOPROXY=direct fyneio/fyne-cross:android-latest /app/build.android.sh
- name: Upload
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
android_client.apk
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build_spk:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Build
run: |
git clone https://github.com/cnlh/spksrc.git ~/spksrc
mkdir ~/spksrc/nps && cp -rf ./* ~/spksrc/nps/
docker run -id --name spksrc --env VERSION=${{ env.RELEASE_VERSION }} -e GOPROXY=direct -v ~/spksrc:/spksrc synocommunity/spksrc /bin/bash
docker exec spksrc /bin/bash -c 'cd /spksrc && make setup && cd /spksrc/spk/npc && make'
cp ~/spksrc/packages/npc_noarch-all_${{ env.RELEASE_VERSION }}-1.spk ./npc_syno.spk
- name: Upload
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
npc_syno.spk
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build_docker:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push nps
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile.nps
platforms: linux/amd64,linux/arm,linux/arm64
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/nps:latest
${{ secrets.DOCKERHUB_USERNAME }}/nps:${{ env.RELEASE_VERSION }}
- name: Build and push npc
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile.npc
platforms: linux/amd64,linux/arm,linux/arm64
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/npc:latest
${{ secrets.DOCKERHUB_USERNAME }}/npc:${{ env.RELEASE_VERSION }}

View File

@ -1,57 +0,0 @@
language: go
go:
- 1.14.x
services:
- docker
script:
- GOPROXY=direct go test -v ./cmd/nps/
os:
- linux
before_deploy:
- chmod +x ./build.sh && chmod +x ./build.android.sh && ./build.sh
deploy:
provider: releases
edge: true
token: ${GH_TOKEN}
cleanup: false
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,11 +1,10 @@
FROM golang:1.15 as builder
ARG GOPROXY=direct
WORKDIR /go/src/ehang.io/nps
FROM golang as builder
WORKDIR /go/src/github.com/cnlh/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/ehang.io/nps/npc /
COPY --from=builder /go/src/github.com/cnlh/nps/npc /
VOLUME /conf
ENTRYPOINT ["/npc"]

View File

@ -1,12 +1,11 @@
FROM golang:1.15 as builder
ARG GOPROXY=direct
WORKDIR /go/src/ehang.io/nps
FROM golang as builder
WORKDIR /go/src/github.com/cnlh/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/ehang.io/nps/nps /
COPY --from=builder /go/src/ehang.io/nps/web /web
COPY --from=builder /go/src/github.com/cnlh/nps/nps /
COPY --from=builder /go/src/github.com/cnlh/nps/web /web
VOLUME /conf
CMD ["/nps"]

1090
README.md

File diff suppressed because it is too large Load Diff

View File

@ -1,90 +0,0 @@
# 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)
![Release](https://github.com/ehang-io/nps/workflows/Release/badge.svg)
![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```
- 默认端口
nps默认配置文件使用了8044380808024端口
80与443端口为域名解析模式默认端口
8080为web管理访问端口
8024为网桥端口用于客户端与服务器通信
- 启动
对于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

@ -1,7 +1,6 @@
package bridge
import (
"ehang.io/nps-mux"
"encoding/binary"
"errors"
"fmt"
@ -12,59 +11,56 @@ 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/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 *nps_mux.Mux
tunnel *mux.Mux
signal *conn.Conn
file *nps_mux.Mux
Version string
file *mux.Mux
retryTime int // it will be add 1 when ping not ok until to 3 will close the client
}
func NewClient(t, f *nps_mux.Mux, s *conn.Conn, vs string) *Client {
func NewClient(t, f *mux.Mux, s *conn.Conn) *Client {
return &Client{
signal: s,
tunnel: t,
file: f,
Version: vs,
signal: s,
tunnel: t,
file: f,
}
}
type Bridge struct {
TunnelPort int //通信隧道端口
Client sync.Map
Register sync.Map
tunnelType string //bridge type kcp or tcp
OpenTask chan *file.Tunnel
CloseTask chan *file.Tunnel
CloseClient chan int
SecretChan chan *conn.Secret
ipVerify bool
runList sync.Map //map[int]interface{}
disconnectTime int
TunnelPort int //通信隧道端口
Client sync.Map
Register sync.Map
tunnelType string //bridge type kcp or tcp
OpenTask chan *file.Tunnel
CloseTask chan *file.Tunnel
CloseClient chan int
SecretChan chan *conn.Secret
ipVerify bool
runList map[int]interface{}
}
func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList sync.Map, disconnectTime int) *Bridge {
func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList map[int]interface{}) *Bridge {
return &Bridge{
TunnelPort: tunnelPort,
tunnelType: tunnelType,
OpenTask: make(chan *file.Tunnel),
CloseTask: make(chan *file.Tunnel),
CloseClient: make(chan int),
SecretChan: make(chan *conn.Secret),
ipVerify: ipVerify,
runList: runList,
disconnectTime: disconnectTime,
TunnelPort: tunnelPort,
tunnelType: tunnelType,
OpenTask: make(chan *file.Tunnel),
CloseTask: make(chan *file.Tunnel),
CloseClient: make(chan int),
SecretChan: make(chan *conn.Secret),
ipVerify: ipVerify,
runList: runList,
}
}
@ -170,23 +166,16 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
return
}
//version check
if b, err := c.GetShortLenContent(); err != nil || string(b) != version.GetVersion() {
if b, err := c.GetShortContent(32); err != nil || string(b) != crypt.Md5(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()
@ -202,7 +191,7 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
s.verifySuccess(c)
}
if flag, err := c.ReadFlag(); err == nil {
s.typeDeal(flag, c, id, string(vs))
s.typeDeal(flag, c, id)
} else {
logs.Warn(err, flag)
}
@ -225,7 +214,7 @@ func (s *Bridge) DelClient(id int) {
}
//use different
func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int, vs string) {
func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
isPub := file.GetDb().IsPubClient(id)
switch typeVal {
case common.WORK_MAIN:
@ -233,25 +222,18 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int, vs string) {
c.Close()
return
}
tcpConn, ok := c.Conn.(*net.TCPConn)
if ok {
// add tcp keep alive option for signal connection
_ = tcpConn.SetKeepAlive(true)
_ = tcpConn.SetKeepAlivePeriod(5 * time.Second)
}
//the vKey connect by another ,close the client of before
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, nil, c, vs)); ok {
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, nil, c)); 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 := nps_mux.NewMux(c.Conn, s.tunnelType, s.disconnectTime)
if v, ok := s.Client.LoadOrStore(id, NewClient(muxConn, nil, nil, vs)); ok {
muxConn := mux.NewMux(c.Conn, s.tunnelType)
if v, ok := s.Client.LoadOrStore(id, NewClient(muxConn, nil, nil)); ok {
v.(*Client).tunnel = muxConn
}
case common.WORK_CONFIG:
@ -271,8 +253,8 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int, vs string) {
logs.Error("secret error, failed to match the key successfully")
}
case common.WORK_FILE:
muxConn := nps_mux.NewMux(c.Conn, s.tunnelType, s.disconnectTime)
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, muxConn, nil, vs)); ok {
muxConn := mux.NewMux(c.Conn, s.tunnelType)
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, muxConn, nil)); ok {
v.(*Client).file = muxConn
}
case common.WORK_P2P:
@ -329,7 +311,7 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (ta
}
}
}
var tunnel *nps_mux.Mux
var tunnel *mux.Mux
if t != nil && t.Mode == "file" {
tunnel = v.(*Client).file
} else {
@ -360,7 +342,6 @@ 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:
@ -415,8 +396,7 @@ loop:
})
file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
v := value.(*file.Tunnel)
//if _, ok := s.runList[v.Id]; ok && v.Client.Id == id {
if _, ok := s.runList.Load(v.Id); ok && v.Client.Id == id {
if _, ok := s.runList[v.Id]; ok && v.Client.Id == id {
str += v.Remark + common.CONN_DATA_SEQ
}
return true
@ -438,7 +418,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()

View File

@ -1,34 +0,0 @@
#/bin/bash
cd /go
apt-get install libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev -y
go get -u fyne.io/fyne/v2/cmd/fyne fyne.io/fyne/v2
#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
fyne package -appID org.nps.client -os android -icon ../../docs/logo.png
mv npc.apk /app/android_client.apk
echo "android build success"

View File

@ -1,156 +0,0 @@
export GOPROXY=direct
sudo apt-get update
sudo apt-get install gcc-mingw-w64-i686 gcc-multilib
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
env GOOS=linux GOARCH=386 CGO_ENABLED=1 CC=gcc go build -ldflags "-s -w -extldflags -static -extldflags -static" -buildmode=c-shared -o npc_sdk.so cmd/npc/sdk.go
tar -czvf npc_sdk.tar.gz npc_sdk.dll npc_sdk.so npc_sdk.h
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

168
build.sh
View File

@ -1,168 +0,0 @@
#/bash/sh
export VERSION=0.26.10
export GOPROXY=direct
sudo apt-get update
sudo apt-get install gcc-mingw-w64-i686 gcc-multilib
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
env GOOS=linux GOARCH=386 CGO_ENABLED=1 CC=gcc go build -ldflags "-s -w -extldflags -static -extldflags -static" -buildmode=c-shared -o npc_sdk.so cmd/npc/sdk.go
tar -czvf npc_sdk.tar.gz npc_sdk.dll npc_sdk.so 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=darwin GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf darwin_arm64_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=darwin GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
tar -czvf darwin_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=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 -e GOPROXY=direct lucor/fyne-cross:android-latest /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 -e GOPROXY=direct -v ~/spksrc:/spksrc synocommunity/spksrc /bin/bash
docker exec -it spksrc /bin/bash -c 'cd /spksrc && make setup && cd /spksrc/spk/npc && make arch-x64-7.0'
cp ~/spksrc/packages/npc_x64-7.0_$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,21 +2,18 @@ package client
import (
"bufio"
"bytes"
"ehang.io/nps-mux"
"net"
"net/http"
"strconv"
"sync"
"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"
)
type TRPClient struct {
@ -25,16 +22,14 @@ type TRPClient struct {
proxyUrl string
vKey string
p2pAddr map[string]string
tunnel *nps_mux.Mux
tunnel *mux.Mux
signal *conn.Conn
ticker *time.Ticker
cnf *config.Config
disconnectTime int
once sync.Once
}
//new client
func NewRPClient(svraddr string, vKey string, bridgeConnType string, proxyUrl string, cnf *config.Config, disconnectTime int) *TRPClient {
func NewRPClient(svraddr string, vKey string, bridgeConnType string, proxyUrl string, cnf *config.Config) *TRPClient {
return &TRPClient{
svrAddr: svraddr,
p2pAddr: make(map[string]string, 0),
@ -42,25 +37,15 @@ func NewRPClient(svraddr string, vKey string, bridgeConnType string, proxyUrl st
bridgeConnType: bridgeConnType,
proxyUrl: proxyUrl,
cnf: cnf,
disconnectTime: disconnectTime,
once: sync.Once{},
}
}
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, error", err.Error())
logs.Error("The connection server failed and will be reconnected in five seconds")
time.Sleep(time.Second * 5)
goto retry
}
@ -79,7 +64,6 @@ 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()
}
@ -143,7 +127,7 @@ func (s *TRPClient) newUdpConn(localAddr, rAddr string, md5Password string) {
conn.SetUdpSession(udpTunnel)
logs.Trace("successful connection with client ,address %s", udpTunnel.RemoteAddr().String())
//read link info from remote
conn.Accept(nps_mux.NewMux(udpTunnel, s.bridgeConnType, s.disconnectTime), func(c net.Conn) {
conn.Accept(mux.NewMux(udpTunnel, s.bridgeConnType), func(c net.Conn) {
go s.handleChan(c)
})
break
@ -151,14 +135,14 @@ func (s *TRPClient) newUdpConn(localAddr, rAddr string, md5Password string) {
}
}
//pmux tunnel
//mux tunnel
func (s *TRPClient) newChan() {
tunnel, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_CHAN, s.proxyUrl)
if err != nil {
logs.Error("connect to ", s.svrAddr, "error:", err)
return
}
s.tunnel = nps_mux.NewMux(tunnel.Conn, s.bridgeConnType, s.disconnectTime)
s.tunnel = mux.NewMux(tunnel.Conn, s.bridgeConnType)
for {
src, err := s.tunnel.Accept()
if err != nil {
@ -172,7 +156,7 @@ func (s *TRPClient) newChan() {
func (s *TRPClient) handleChan(src net.Conn) {
lk, err := conn.NewConn(src).GetLinkInfo()
if err != nil || lk == nil {
if err != nil {
src.Close()
logs.Error("get connection info from server error ", err)
return
@ -181,7 +165,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.DialTimeout(common.CONN_TCP, lk.Host, lk.Option.Timeout); err != nil {
if targetConn, err := net.Dial(common.CONN_TCP, lk.Host); err != nil {
logs.Warn("connect to %s error %s", lk.Host, err.Error())
src.Close()
} else {
@ -204,12 +188,8 @@ 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.DialTimeout(lk.ConnType, lk.Host, lk.Option.Timeout); err != nil {
if targetConn, err := net.Dial(lk.ConnType, lk.Host); err != nil {
logs.Warn("connect to %s error %s", lk.Host, err.Error())
src.Close()
} else {
@ -218,65 +198,6 @@ 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 serverConn.Close()
if err != nil {
logs.Error("bind local udp port error ", err.Error())
return
}
defer local.Close()
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)
@ -293,17 +214,11 @@ loop:
}
func (s *TRPClient) Close() {
s.once.Do(s.closing)
}
func (s *TRPClient) closing() {
CloseClient = true
NowStatus = 0
if s.tunnel != nil {
_ = s.tunnel.Close()
s.tunnel.Close()
}
if s.signal != nil {
_ = s.signal.Close()
s.signal.Close()
}
if s.ticker != nil {
s.ticker.Stop()

75
client/client_test.go Normal file
View File

@ -0,0 +1,75 @@
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

@ -1,7 +1,6 @@
package client
import (
"bufio"
"encoding/base64"
"encoding/binary"
"errors"
@ -12,6 +11,7 @@ import (
"math/rand"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
@ -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"
)
@ -175,7 +175,7 @@ re:
} else {
logs.Notice("web access login username:%s password:%s", cnf.CommonConfig.Client.WebUserName, cnf.CommonConfig.Client.WebPassword)
}
NewRPClient(cnf.CommonConfig.Server, vkey, cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl, cnf, cnf.CommonConfig.DisconnectTime).Start()
NewRPClient(cnf.CommonConfig.Server, vkey, cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl, cnf).Start()
CloseLocalServer()
goto re
}
@ -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)
default:
case "http":
connection, err = NewHttpProxyConn(u, server)
}
} else {
@ -220,10 +220,7 @@ 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.WriteLenContent([]byte(version.GetVersion())); err != nil {
return nil, err
}
if err := c.WriteLenContent([]byte(version.VERSION)); err != nil {
if _, err := c.Write([]byte(crypt.Md5(version.GetVersion()))); err != nil {
return nil, err
}
b, err := c.GetShortContent(32)
@ -241,7 +238,8 @@ 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 {
return nil, errors.New(fmt.Sprintf("Validation key %s incorrect", vkey))
logs.Error("Validation key %s incorrect", vkey)
os.Exit(0)
}
if _, err := c.Write([]byte(connType)); err != nil {
return nil, err
@ -253,28 +251,30 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
//http proxy connection
func NewHttpProxyConn(url *url.URL, remoteAddr string) (net.Conn, error) {
req, err := http.NewRequest("CONNECT", "http://"+remoteAddr, nil)
req := &http.Request{
Method: "CONNECT",
URL: url,
Host: remoteAddr,
Header: http.Header{},
Proto: "HTTP/1.1",
}
password, _ := url.User.Password()
req.Header.Set("Proxy-Authorization", "Basic "+basicAuth(url.User.Username(), password))
b, err := httputil.DumpRequest(req, false)
if err != nil {
return nil, err
}
password, _ := url.User.Password()
req.Header.Set("Authorization", "Basic "+basicAuth(strings.Trim(url.User.Username(), " "), password))
// we make a http proxy request
proxyConn, err := net.Dial("tcp", url.Host)
if err != nil {
return nil, err
}
if err := req.Write(proxyConn); err != nil {
if _, err := proxyConn.Write(b); err != nil {
return nil, err
}
res, err := http.ReadResponse(bufio.NewReader(proxyConn), req)
if err != nil {
buf := make([]byte, 1024)
if _, err := proxyConn.Read(buf); err != nil {
return nil, err
}
_ = res.Body.Close()
if res.StatusCode != 200 {
return nil, errors.New("Proxy error " + res.Status)
}
return proxyConn, nil
}
@ -369,7 +369,6 @@ 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:
@ -395,7 +394,6 @@ 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,28 +1,26 @@
package client
import (
"ehang.io/nps-mux"
"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/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"
)
var (
LocalServer []*net.TCPListener
udpConn net.Conn
muxSession *nps_mux.Mux
muxSession *mux.Mux
fileServer []*http.Server
p2pNetBridge *p2pBridge
lock sync.RWMutex
@ -33,14 +31,6 @@ 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
@ -73,7 +63,7 @@ func startLocalFileServer(config *config.CommonConfig, t *file.Tunnel, vkey stri
}
logs.Info("start local file system, local path %s, strip prefix %s ,remote port %s ", t.LocalPath, t.StripPre, t.Ports)
fileServer = append(fileServer, srv)
listener := nps_mux.NewMux(remoteConn.Conn, common.CONN_TCP, config.DisconnectTime)
listener := mux.NewMux(remoteConn.Conn, common.CONN_TCP)
logs.Error(srv.Serve(listener))
}
@ -127,7 +117,6 @@ 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:
@ -168,7 +157,6 @@ 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
@ -214,6 +202,6 @@ func newUdpConn(localAddr string, config *config.CommonConfig, l *config.LocalSe
logs.Trace("successful create a connection with server", remoteAddress)
conn.SetUdpSession(udpTunnel)
udpConn = udpTunnel
muxSession = nps_mux.NewMux(udpConn, "kcp", config.DisconnectTime)
muxSession = mux.NewMux(udpConn, "kcp")
p2pNetBridge = &p2pBridge{}
}

View File

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

View File

@ -1,105 +1,40 @@
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/kardianos/service"
"os"
"os/exec"
"runtime"
"strings"
"sync"
"time"
"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"
)
var (
serverAddr = flag.String("server", "", "Server addr (ip:port)")
configPath = flag.String("config", "", "Configuration file path")
verifyKey = flag.String("vkey", "", "Authentication key")
logType = flag.String("log", "stdout", "Log output modestdout|file")
connType = flag.String("type", "tcp", "Connection type with the serverkcp|tcp")
proxyUrl = flag.String("proxy", "", "proxy socks5 url(eg:socks5://111:222@127.0.0.1:9007)")
logLevel = flag.String("log_level", "7", "log level 0~7")
registerTime = flag.Int("time", 2, "register time long /h")
localPort = flag.Int("local_port", 2000, "p2p local port")
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 path")
debug = flag.Bool("debug", true, "npc debug")
pprofAddr = flag.String("pprof", "", "PProf debug addr (ip:port)")
stunAddr = flag.String("stun_addr", "stun.stunprotocol.org:3478", "stun server address (eg:stun.stunprotocol.org:3478)")
ver = flag.Bool("version", false, "show current version")
disconnectTime = flag.Int("disconnect_timeout", 60, "not receiving check packet times, until timeout will disconnect the client")
serverAddr = flag.String("server", "", "Server addr (ip:port)")
configPath = flag.String("config", "", "Configuration file path")
verifyKey = flag.String("vkey", "", "Authentication key")
logType = flag.String("log", "stdout", "Log output modestdout|file")
connType = flag.String("type", "tcp", "Connection type with the serverkcp|tcp")
proxyUrl = flag.String("proxy", "", "proxy socks5 url(eg:socks5://111:222@127.0.0.1:9007)")
logLevel = flag.String("log_level", "7", "log level 0~7")
registerTime = flag.Int("time", 2, "register time long /h")
localPort = flag.Int("local_port", 2000, "p2p local port")
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")
)
func main() {
flag.Parse()
logs.Reset()
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(3)
if *ver {
common.PrintVersion()
return
}
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)
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"}
svcConfig.Option["SystemdScript"] = install.SystemdScript
svcConfig.Option["SysvScript"] = install.SysvScript
}
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, "service function disabled")
run()
// run without service
wg := sync.WaitGroup{}
wg.Add(1)
wg.Wait()
return
}
if len(os.Args) >= 2 {
switch os.Args[1] {
case "status":
@ -110,101 +45,24 @@ func main() {
case "register":
flag.CommandLine.Parse(os.Args[2:])
client.RegisterLocalIp(*serverAddr, *verifyKey, *connType, *proxyUrl, *registerTime)
case "update":
install.UpdateNpc()
return
case "nat":
c := stun.NewClient()
c.SetServerAddr(*stunAddr)
nat, host, err := c.Discover()
nat, host, err := stun.NewClient().Discover()
if err != nil || host == nil {
logs.Error("get nat type error", err)
return
}
fmt.Printf("nat type: %s \npublic address: %s\n", nat.String(), host.String())
os.Exit(0)
case "start", "stop", "restart":
// support busyBox and sysV, for openWrt
if service.Platform() == "unix-systemv" {
logs.Info("unix-systemv service")
cmd := exec.Command("/etc/init.d/"+svcConfig.Name, os.Args[1])
err := cmd.Run()
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 "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())
}
if service.Platform() == "unix-systemv" {
logs.Info("unix-systemv service")
confPath := "/etc/init.d/" + svcConfig.Name
os.Symlink(confPath, "/etc/rc.d/S90"+svcConfig.Name)
os.Symlink(confPath, "/etc/rc.d/K02"+svcConfig.Name)
}
return
case "uninstall":
err := service.Control(s, os.Args[1])
if err != nil {
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
}
if service.Platform() == "unix-systemv" {
logs.Info("unix-systemv service")
os.Remove("/etc/rc.d/S90" + svcConfig.Name)
os.Remove("/etc/rc.d/K02" + svcConfig.Name)
}
return
}
}
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)
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}`)
}
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))
}
}()
run()
select {
case <-p.exit:
logs.Warning("stop...")
}
return nil
}
func run() {
common.InitPProfFromArg(*pprofAddr)
//p2p or secret command
if *password != "" {
commonConfig := new(config.CommonConfig)
@ -218,7 +76,7 @@ func run() {
localServer.Port = *localPort
commonConfig.Client = new(file.Client)
commonConfig.Client.Cnf = new(file.Config)
go client.StartLocalServer(localServer, commonConfig)
client.StartLocalServer(localServer, commonConfig)
return
}
env := common.GetEnvMap()
@ -230,17 +88,15 @@ func run() {
}
logs.Info("the version of client is %s, the core version of client is %s", version.VERSION, version.GetVersion())
if *verifyKey != "" && *serverAddr != "" && *configPath == "" {
go func() {
for {
client.NewRPClient(*serverAddr, *verifyKey, *connType, *proxyUrl, nil, *disconnectTime).Start()
logs.Info("Client closed! It will be reconnected in five seconds")
time.Sleep(time.Second * 5)
}
}()
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 = common.GetConfigPath()
*configPath = "npc.conf"
}
go client.StartFromFile(*configPath)
client.StartFromFile(*configPath)
}
}

View File

@ -1,48 +0,0 @@
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, 60)
cl.Start()
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

@ -4,193 +4,55 @@ import (
"flag"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/install"
"ehang.io/nps/lib/version"
"ehang.io/nps/server"
"ehang.io/nps/server/connection"
"ehang.io/nps/server/tool"
"ehang.io/nps/web/routers"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/daemon"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/kardianos/service"
"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"
)
var (
level string
ver = flag.Bool("version", false, "show current version")
level string
logType = flag.String("log", "stdout", "Log output modestdout|file")
)
func main() {
flag.Parse()
// init log
if *ver {
common.PrintVersion()
return
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
}
}
if err := beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf")); err != nil {
log.Fatalln("load config file error", err.Error())
}
common.InitPProfFromFile()
if level = beego.AppConfig.String("log_level"); level == "" {
level = "7"
}
logs.Reset()
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(3)
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)
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}`)
if *logType == "stdout" {
logs.SetLogger(logs.AdapterConsole, `{"level":`+level+`,"color":true}`)
} else {
_ = logs.SetLogger(logs.AdapterConsole, `{"level":`+level+`,"color":true}`)
logs.SetLogger(logs.AdapterFile, `{"level":`+level+`,"filename":"`+beego.AppConfig.String("log_path")+`","daily":false,"maxlines":100000,"color":true}`)
}
if !common.IsWindows() {
svcConfig.Dependencies = []string{
"Requires=network.target",
"After=network-online.target syslog.target"}
svcConfig.Option["SystemdScript"] = install.SystemdScript
svcConfig.Option["SysvScript"] = install.SysvScript
}
prg := &nps{}
prg.exit = make(chan struct{})
s, err := service.New(prg, svcConfig)
if err != nil {
logs.Error(err, "service function disabled")
run()
// run without service
wg := sync.WaitGroup{}
wg.Add(1)
wg.Wait()
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())
}
if service.Platform() == "unix-systemv" {
logs.Info("unix-systemv service")
confPath := "/etc/init.d/" + svcConfig.Name
os.Symlink(confPath, "/etc/rc.d/S90"+svcConfig.Name)
os.Symlink(confPath, "/etc/rc.d/K02"+svcConfig.Name)
}
return
case "start", "restart", "stop":
if service.Platform() == "unix-systemv" {
logs.Info("unix-systemv service")
cmd := exec.Command("/etc/init.d/"+svcConfig.Name, os.Args[1])
err := cmd.Run()
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 "uninstall":
err := service.Control(s, os.Args[1])
if err != nil {
logs.Error("Valid actions: %q\n%s", service.ControlAction, err.Error())
}
if service.Platform() == "unix-systemv" {
logs.Info("unix-systemv service")
os.Remove("/etc/rc.d/S90" + svcConfig.Name)
os.Remove("/etc/rc.d/K02" + svcConfig.Name)
}
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 {
_, _ = s.Status()
go p.run()
return nil
}
func (p *nps) Stop(s service.Service) error {
_, _ = s.Status()
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))
}
}()
run()
select {
case <-p.exit:
logs.Warning("stop...")
}
return nil
}
func run() {
routers.Init()
task := &file.Tunnel{
Mode: "webServer",
}
@ -201,13 +63,8 @@ func run() {
}
logs.Info("the version of server is %s ,allow client core version to be %s", version.VERSION, version.GetVersion())
connection.InitConnectionService()
//crypt.InitTls(filepath.Join(common.GetRunPath(), "conf", "server.pem"), filepath.Join(common.GetRunPath(), "conf", "server.key"))
crypt.InitTls()
crypt.InitTls(filepath.Join(common.GetRunPath(), "conf", "server.pem"), filepath.Join(common.GetRunPath(), "conf", "server.key"))
tool.InitAllowPort()
tool.StartSystemInfo()
timeout, err := beego.AppConfig.Int("disconnect_timeout")
if err != nil {
timeout = 60
}
go server.StartNewServer(bridgePort, task, beego.AppConfig.String("bridge_type"), timeout)
server.StartNewServer(bridgePort, task, beego.AppConfig.String("bridge_type"))
}

View File

@ -12,8 +12,6 @@ web_username=user
web_password=1234
crypt=true
compress=true
#pprof_addr=0.0.0.0:9999
disconnect_timeout=60
[health_check_test1]
health_check_timeout=1

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,16 +41,9 @@ 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)
#Remove comments if needed
#auth_key=test
auth_key=test
auth_crypt_key =1234567812345678
#allow_ports=9001-9009,10001,11000-12000
@ -74,12 +67,4 @@ system_info_display=false
http_cache=false
http_cache_length=100
#get origin ip
http_add_origin_header=false
#pprof debug options
#pprof_ip=0.0.0.0
#pprof_port=9999
#client disconnect timeout
disconnect_timeout=60

View File

View File

@ -1,21 +0,0 @@
# 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/ehang-io/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代理模式

View File

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

View File

@ -1,3 +0,0 @@
* [![GitHub stars](https://img.shields.io/github/stars/ehang-io/nps?style=social)](https://github.com/ehang-io/nps/stargazers)
* [![GitHub forks](https://img.shields.io/github/forks/ehang-io/nps?style=social)](https://github.com/ehang-io/nps/network)

View File

@ -1,29 +0,0 @@
* 入门
* [安装](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)
* 其他
* [FAQ](faq.md)
* [贡献](contribute.md)
* [捐助](donate.md)
* [致谢](thanks.md)
* [交流](discuss.md)

View File

@ -1,45 +0,0 @@
# web api
需要开启请先去掉`nps.conf``auth_key`的注释并配置一个合适的密钥
## 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
- 解密串编码方式 十六进制
## 详细文档
- **[详见](webapi.md)** (感谢@avengexyz)

View File

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

View File

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

View File

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

View File

@ -1,7 +0,0 @@
# 捐助
如果您觉得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)

View File

@ -1,126 +0,0 @@
# 使用示例
## 统一准备工作(必做)
- 开启服务端假设公网服务器ip为1.1.1.1,配置文件中`bridge_port`为8024配置文件中`web_port`为8080
- 访问1.1.1.1:8080
- 在客户端管理中创建一个客户端,记录下验证密钥
- 内网客户端运行windows使用cmd运行加.exe
```shell
./npc -server=1.1.1.1:8024 -vkey=客户端的密钥
```
**注意:运行服务端后,请确保能从客户端设备上正常访问配置文件中所配置的`bridge_port`端口telnetnetcat这类的来检查**
## 域名解析
**适用范围:** 小程序开发、微信公众号开发、产品演示
**注意域名解析模式为http反向代理不是dns服务器在web上能够轻松灵活配置**
**假设场景:**
- 有一个域名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),即可访问了
**注意对于私密代理与p2p除了统一配置的客户端和服务端还需要一个客户端作为访问端提供一个端口来访问**
## 私密代理
**适用范围:** 无需占用多余的端口、安全性要求较高可以防止其他人连接的tcp服务例如ssh。
**假设场景:**
无需新增多的端口实现访问内网服务器10.1.50.2的22端口
**使用步骤**
- 在刚才创建的客户端中添加一条私密代理并设置唯一密钥secrettest和内网目标10.1.50.2:22
- 在需要连接ssh的机器上以执行命令
```
./npc -server=1.1.1.1:8024 -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@127.0.0.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_port` 设置为6000请在防火墙开放6000~6002(额外添加2个端口)udp端口
- 在刚才刚才创建的客户端中添加一条p2p代理并设置唯一密钥p2pssh
- 在使用端机器(本机)执行命令
```
./npc -server=1.1.1.1:8024 -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端口即可。

View File

@ -1,20 +0,0 @@
# FAQ
- 服务端无法启动
```
服务端默认配置启用了8024808080443端口端口冲突无法启动请修改配置
```
- 客户端无法连接服务端
```
请检查配置文件中的所有端口是否在安全组,防火墙放行
请检查vkey是否对应
请检查版本是否对应
```
- 服务端配置文件修改无效
```
install 之后Linux 配置文件在 /etc/nps
```
- p2p穿透失败 [p2p服务](https://ehang-io.github.io/nps/#/example?id=p2p%e6%9c%8d%e5%8a%a1)
```
双方nat类型都是Symmetric Nat一定不成功建议先查看nat类型。请按照文档操作(标题上有超链接)
```

View File

@ -1,254 +0,0 @@
# 扩展功能
## 缓存支持
对于web站点来说一些静态文件往往消耗更大的流量且在内网穿透中静态文件还需到客户端获取一次这将导致更大的流量消耗。nps在域名解析代理中支持对静态文件进行缓存。
即假设一个站点有a.cssnps将只需从npc客户端读取一次该文件然后把该文件的内容放在内存中下一次将不再对npc客户端进行请求而直接返回内存中的对应内容。该功能默认是关闭的如需开启请在`nps.conf`中设置`http_cache=true`,并设置`http_cache_length`缓存文件的个数消耗内存不宜过大0表示不限制个数
## 数据压缩支持
由于是内网穿透内网客户端与服务端之间的隧道存在大量的数据交换为节省流量加快传输速度由此本程序支持SNNAPY形式的压缩。
- 所有模式均支持数据压缩
- 在web管理或客户端配置文件中设置
## 加密传输
如果公司内网防火墙对外网访问进行了流量识别与屏蔽例如禁止了ssh协议等通过设置 配置文件,将服务端与客户端之间的通信内容加密传输,将会有效防止流量被拦截。
- nps现在默认每次启动时随机生成tls证书用于加密传输
## 站点保护
域名代理模式所有客户端共用一个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,仅范围映射时有效
## KCP协议支持
在网络质量非常好的情况下例如专线内网可以开启略微降低延迟。如需使用可在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:8024
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`中设置相关配置即可
## pprof性能分析与调试
可在服务端与客户端配置中开启pprof端口用于性能分析与调试注释或留空相应参数为关闭。
默认为关闭状态
## 自定义客户端超时检测断开时间
客户端与服务端间会间隔5s相互发送延迟测量包这个时间间隔不可修改。
可修改延迟测量包丢包的次数默认为60也就是5分钟都收不到一个延迟测量回包则会断开客户端连接。
值得注意的是需要客户端的socket关闭才会进行重连也就是当客户端无法收到服务端的fin包时只有客户端自行关闭socket才行。
也就是假如服务端设置为较低值而客户端设置较高值而此时服务端断开连接而客户端无法收到服务端的fin包客户端也会继续等着直到触发客户端的超时设置。
`nps.conf``npc.conf`中设置`disconnect_timeout`即可,客户端还可附带`-disconnect_timeout=60`参数启动

View File

@ -1,43 +0,0 @@
<!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,
loadNavbar: 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>

View File

@ -1,18 +0,0 @@
# 安装
## 安装包安装
[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)

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1,36 +0,0 @@
# 增强功能
## nat类型检测
```
./npc nat -stun_addr=stun.stunprotocol.org:3478
```
如果p2p双方都是Symmetric Nat肯定不能成功其他组合都有较大成功率。`stun_addr`可以指定stun服务器地址。
## 状态检查
```
./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`

View File

@ -1,24 +0,0 @@
# npc sdk文档
```
命令行模式启动客户端
从v0.26.10开始,此函数会阻塞,直到客户端退出返回,请自行管理是否重连
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();
```

View File

@ -1,107 +0,0 @@
# 增强功能
## 使用https
**方式一:** 类似于nginx实现https的处理
在配置文件中将https_proxy_port设置为443或者其他你想配置的端口`https_just_proxy`设置为falsenps 重启后在web管理界面域名新增或修改界面中修改域名证书和密钥。
**此外:** 可以在`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为8010时
```
server {
listen 80;
server_name *.proxy.com;
location / {
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:8010;
}
}
```
如需使用https也可在nginx监听443端口并配置ssl并将本代理的httpsProxyPort设置为空关闭https即可例如httpProxyPort为8020时
```
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:8020;
}
}
```
## 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上设置想转发的隧道或者域名然后选择转发到本地选项即可成功。

View File

@ -1,47 +0,0 @@
# 使用
**提示使用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目录
注意:`nps install` 之后的 nps 不在原位置,请使用 `whereis nps` 查找具体目录覆盖 nps 二进制文件

View File

@ -1,42 +0,0 @@
# 启动
## 服务端
下载完服务器压缩包后,解压,然后进入解压后的文件夹
- 执行安装命令
对于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执行**
如果使用`powershell`运行,**请将ip括起来**
如果需要注册到系统服务可查看[注册到系统服务](/use?id=注册到系统服务)
## 版本检查
- 对客户端以及服务的均可以使用参数`-version`打印版本
- `nps -version``./nps -version`
- `npc -version``./npc -version`
## 配置
- 客户端连接后在web中配置对应穿透服务即可
- 可以查看[使用示例](/example)

View File

@ -1,24 +0,0 @@
# 服务端配置文件
- /etc/nps/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端口
pprof_ip|debug pprof 服务端ip
pprof_port|debug pprof 端口
disconnect_timeout|客户端连接超时,单位 5s默认值 60即 300s = 5mins

View File

@ -1,5 +0,0 @@
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>

View File

@ -1,225 +0,0 @@
# 基本使用
## 无配置文件模式
此模式的各种配置在服务端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`,再重新注册
- 如果需要当客户端退出时自动重启客户端,请按照如图所示配置
![image](https://github.com/ehang-io/nps/blob/master/docs/windows_client_service_configuration.png?raw=true)
注册到服务后日志文件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:8024
conn_type=tcp
vkey=123
username=111
password=222
compress=true
crypt=true
rate_limit=10000
flow_limit=100
remark=test
max_conn=10
#pprof_addr=0.0.0.0:9999
```
项 | 含义
---|---
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|最大连接数,可忽略
pprof_addr|debug pprof ip:port
#### 域名代理
```ini
[common]
server_addr=1.1.1.1:8024
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:8024
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:8024
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:8024
vkey=123
[http]
mode=httpProxy
server_port=9003
```
项 | 含义
---|---
mode | httpProxy
server_port | 在服务端的代理端口
#### socks5代理模式
```ini
[common]
server_addr=1.1.1.1:8024
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:8024
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:8024
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:8024
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
```

View File

@ -1,233 +0,0 @@
获取客户端列表
```
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 | 客户端最大隧道数量 空则为不限制 |
***
修改客户端
```
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 |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

41
go.mod
View File

@ -1,34 +1,29 @@
module ehang.io/nps
module github.com/cnlh/nps
go 1.15
go 1.12
require (
ehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992
fyne.io/fyne/v2 v2.0.2
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/astaxie/beego v1.12.0
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff // indirect
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d
github.com/dsnet/compress v0.0.1 // indirect
github.com/golang/snappy v0.0.3
github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 // indirect
github.com/kardianos/service v1.2.0
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
github.com/klauspost/pgzip v1.2.1 // indirect
github.com/klauspost/reedsolomon v1.9.12 // indirect
github.com/panjf2000/ants/v2 v2.4.2
github.com/pkg/errors v0.9.1
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db
github.com/klauspost/cpuid 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/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
github.com/shirou/gopsutil/v3 v3.21.3
github.com/shirou/gopsutil v2.18.12+incompatible
github.com/stretchr/testify v1.3.0 // indirect
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
github.com/tjfoc/gmsm v1.4.0 // indirect
github.com/xtaci/kcp-go v5.4.20+incompatible
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/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa // indirect
)
replace github.com/astaxie/beego => github.com/exfly/beego v1.12.0-export-init

233
go.sum
View File

@ -1,263 +1,92 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
ehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992 h1:LvlcB+8JveSBprHnva0g+OyLwAH8CRxEwtWzTe6ocoE=
ehang.io/nps-mux v0.0.0-20210407130203-4afa0c10c992/go.mod h1:v54y/8ICChiM/aVUuKxGIcWwjm4HGNRyyAwbgLBoMbI=
fyne.io/fyne/v2 v2.0.2 h1:6pDvFuCmL1odyT/fPI+2L54hMJW1Zt9Dno41HmLInRs=
fyne.io/fyne/v2 v2.0.2/go.mod h1:3+FYmLJVgeb8EvTPJ5YzZeo7LkAq4bbuY3Zrir6xHbg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
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/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-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
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=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
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/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f h1:rguJ/t99j/6zRSFzsBKlsmmyl+vOvCeTJ+2uTBvuXFI=
github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
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/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
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/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
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/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=
github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
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.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.2 h1:pd2FBxFydtPn2ywTLStbFg9CJKrojATnpeJWSP7Ys4k=
github.com/klauspost/cpuid/v2 v2.0.2/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
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.12 h1:EyOucRmcrLH+2hqKGdoA5SM8pwPKR6BJsf3r6zpYOA0=
github.com/klauspost/reedsolomon v1.9.12/go.mod h1:nLvuzNvy1ZDNQW30IuMc2ZWCbiqrJgdLoUS2X8HAUVg=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
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/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/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/panjf2000/ants/v2 v2.4.2 h1:kesjjo8JipN3vNNg1XaiXaeSs6xJweBTgenkBtsrHf8=
github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
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/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.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
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/v3 v3.21.3 h1:wgcdAHZS2H6qy4JFewVTtqfiYxFzCeEJod/mLztdPG8=
github.com/shirou/gopsutil/v3 v3.21.3/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
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/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-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/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=
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/tjfoc/gmsm v1.4.0 h1:8nbaiZG+iVdh+fXVw0DZoZZa7a4TGm3Qab+xdrdzj8s=
github.com/tjfoc/gmsm v1.4.0/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M=
github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc=
github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM=
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/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg=
github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
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=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.nps.client"
android:versionCode="1"
android:versionName="0.26.10">
<application android:label="Npc" android:debuggable="true">
<activity android:name="org.golang.app.GoNativeActivity"
android:label="Npc"
android:configChanges="orientation|keyboardHidden">
<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>
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -1,194 +0,0 @@
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/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/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
closing bool
status = "Start!"
connType = "tcp"
cl = new(client.TRPClient)
refreshCh = make(chan struct{})
)
func WidgetScreen() fyne.CanvasObject {
return fyne.NewContainerWithLayout(layout.NewBorderLayout(nil, nil, nil, nil),
makeMainTab(),
)
}
func makeMainTab() *fyne.Container {
serverPort := widget.NewEntry()
serverPort.SetPlaceHolder("Server:Port")
vKey := widget.NewEntry()
vKey.SetPlaceHolder("Vkey")
radio := widget.NewRadioGroup([]string{"tcp", "kcp"}, func(s string) { connType = s })
radio.Horizontal = true
button := widget.NewButton(status, func() {
onclick(serverPort.Text, vKey.Text, connType)
})
go func() {
for {
<-refreshCh
button.SetText(status)
}
}()
lo := widget.NewMultiLineEntry()
lo.Disable()
lo.Resize(fyne.NewSize(910, 250))
slo := container.NewScroll(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)
onclick(sp, vk, ct)
}
return container.NewVBox(
widget.NewLabel("Npc "+version.VERSION),
serverPort,
vKey,
radio,
button,
slo,
)
}
func onclick(s, v, c string) {
start = !start
if start {
closing = false
status = "Stop!"
// init the npc
fmt.Println("submit", s, v, c)
sp, vk, ct := loadConfig()
if sp != s || vk != v || ct != c {
saveConfig(s, v, c)
}
go func() {
for {
cl = client.NewRPClient(s, v, c, "", nil, 60)
status = "Stop!"
refreshCh <- struct{}{}
cl.Start()
logs.Warn("client closed, reconnecting in 5 seconds...")
if closing {
return
}
status = "Reconnecting..."
refreshCh <- struct{}{}
time.Sleep(time.Second * 5)
}
}()
} else {
// close the npc
status = "Start!"
closing = true
if cl != nil {
go cl.Close()
cl = nil
}
}
refreshCh <- struct{}{}
}
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
}

View File

@ -1,821 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@ -36,3 +36,19 @@ WWW-Authenticate: Basic realm="easyProxy"
`
)
const (
MUX_PING_FLAG uint8 = iota
MUX_NEW_CONN_OK
MUX_NEW_CONN_Fail
MUX_NEW_MSG
MUX_NEW_MSG_PART
MUX_MSG_SEND_OK
MUX_NEW_CONN
MUX_CONN_CLOSE
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
)

View File

@ -1,48 +0,0 @@
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

@ -3,11 +3,10 @@ package common
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"io"
"io/ioutil"
"net"
"strconv"
"strings"
)
type NetPackager interface {
@ -15,205 +14,222 @@ type NetPackager interface {
UnPack(reader io.Reader) (err error)
}
const (
ipV4 = 1
domainName = 3
ipV6 = 4
)
type UDPHeader struct {
Rsv uint16
Frag uint8
Addr *Addr
type BasePackager struct {
Length uint16
Content []byte
}
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()
func (Self *BasePackager) NewPac(contents ...interface{}) (err error) {
Self.clean()
for _, content := range contents {
switch content.(type) {
case nil:
Self.Content = Self.Content[:0]
case []byte:
err = Self.appendByte(content.([]byte))
case string:
err = Self.appendByte([]byte(content.(string)))
if err != nil {
return
}
err = Self.appendByte([]byte(CONN_DATA_SEQ))
default:
err = Self.marshal(content)
}
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
Self.setLength()
return
}
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{}
func (Self *BasePackager) appendByte(data []byte) (err error) {
m := len(Self.Content)
n := m + len(data)
if n <= cap(Self.Content) {
Self.Content = Self.Content[0:n] // grow the length for copy
copy(Self.Content[m:n], data)
return nil
} else {
return errors.New("pack content too large")
}
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])
//似乎这里涉及到父类作用域问题当子类调用父类的方法时其struct仅仅为父类的
func (Self *BasePackager) Pack(writer io.Writer) (err error) {
err = binary.Write(writer, binary.LittleEndian, Self.Length)
if err != nil {
return nil, err
return
}
err = binary.Write(writer, binary.LittleEndian, Self.Content)
return
}
header := &UDPHeader{
Rsv: binary.BigEndian.Uint16(b[:2]),
Frag: b[2],
//Unpack 会导致传入的数字类型转化成float64
//主要原因是json unmarshal并未传入正确的数据类型
func (Self *BasePackager) UnPack(reader io.Reader) (n uint16, err error) {
Self.clean()
n += 2 // uint16
err = binary.Read(reader, binary.LittleEndian, &Self.Length)
if err != nil {
return
}
if int(Self.Length) > cap(Self.Content) {
err = errors.New("unpack err, content length too large")
}
Self.Content = Self.Content[:int(Self.Length)]
//n, err := io.ReadFull(reader, Self.Content)
//if n != int(Self.Length) {
// err = io.ErrUnexpectedEOF
//}
err = binary.Read(reader, binary.LittleEndian, Self.Content)
n += Self.Length
return
}
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")
func (Self *BasePackager) marshal(content interface{}) (err error) {
tmp, err := json.Marshal(content)
if err != nil {
return err
}
dlen := int(header.Rsv)
if dlen == 0 { // standard SOCKS5 UDP datagram
extra, err := ioutil.ReadAll(r) // we assume no redundant data
err = Self.appendByte(tmp)
return
}
func (Self *BasePackager) Unmarshal(content interface{}) (err error) {
err = json.Unmarshal(Self.Content, content)
if err != nil {
return err
}
return
}
func (Self *BasePackager) setLength() {
Self.Length = uint16(len(Self.Content))
return
}
func (Self *BasePackager) clean() {
Self.Length = 0
Self.Content = Self.Content[:0] // reset length
}
func (Self *BasePackager) Split() (strList []string) {
n := bytes.IndexByte(Self.Content, 0)
strList = strings.Split(string(Self.Content[:n]), CONN_DATA_SEQ)
strList = strList[0 : len(strList)-1]
return
}
type ConnPackager struct { // Todo
ConnType uint8
BasePackager
}
func (Self *ConnPackager) NewPac(connType uint8, content ...interface{}) (err error) {
Self.ConnType = connType
err = Self.BasePackager.NewPac(content...)
return
}
func (Self *ConnPackager) Pack(writer io.Writer) (err error) {
err = binary.Write(writer, binary.LittleEndian, Self.ConnType)
if err != nil {
return
}
err = Self.BasePackager.Pack(writer)
return
}
func (Self *ConnPackager) UnPack(reader io.Reader) (n uint16, err error) {
err = binary.Read(reader, binary.LittleEndian, &Self.ConnType)
if err != nil && err != io.EOF {
return
}
n, err = Self.BasePackager.UnPack(reader)
n += 2
return
}
type MuxPackager struct {
Flag uint8
Id int32
Window uint32
ReadLength uint32
BasePackager
}
func (Self *MuxPackager) NewPac(flag uint8, id int32, content ...interface{}) (err error) {
Self.Flag = flag
Self.Id = id
switch flag {
case MUX_PING_FLAG, MUX_PING_RETURN, MUX_NEW_MSG, MUX_NEW_MSG_PART:
Self.Content = WindowBuff.Get()
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)
}
}
return
}
func (Self *MuxPackager) Pack(writer io.Writer) (err error) {
err = binary.Write(writer, binary.LittleEndian, Self.Flag)
if err != nil {
return
}
err = binary.Write(writer, binary.LittleEndian, Self.Id)
if err != nil {
return
}
switch Self.Flag {
case MUX_NEW_MSG, MUX_NEW_MSG_PART, MUX_PING_FLAG, MUX_PING_RETURN:
err = Self.BasePackager.Pack(writer)
WindowBuff.Put(Self.Content)
case MUX_MSG_SEND_OK:
err = binary.Write(writer, binary.LittleEndian, Self.Window)
if err != nil {
return nil, err
return
}
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
err = binary.Write(writer, binary.LittleEndian, Self.ReadLength)
}
return
}
func (Self *MuxPackager) UnPack(reader io.Reader) (n uint16, err error) {
err = binary.Read(reader, binary.LittleEndian, &Self.Flag)
if err != nil {
return
}
err = binary.Read(reader, binary.LittleEndian, &Self.Id)
if err != nil {
return
}
switch Self.Flag {
case MUX_NEW_MSG, MUX_NEW_MSG_PART, MUX_PING_FLAG, MUX_PING_RETURN:
Self.Content = WindowBuff.Get() // need get a window buf from pool
Self.BasePackager.clean() // also clean the content
n, err = Self.BasePackager.UnPack(reader)
//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 = 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),
n += 4 // uint32
err = binary.Read(reader, binary.LittleEndian, &Self.ReadLength)
n += 4 // uint32
}
n += 5 //uint8 int32
return
}

View File

@ -1,13 +1,16 @@
package common
import (
"bytes"
"sync"
)
const PoolSize = 64 * 1024
const PoolSizeSmall = 100
const PoolSizeUdp = 1472 + 200
const PoolSizeUdp = 1472
const PoolSizeCopy = 32 << 10
const PoolSizeBuffer = 4096
const PoolSizeWindow = PoolSizeBuffer - 2 - 4 - 4 - 1
var BufPool = sync.Pool{
New: func() interface{} {
@ -83,11 +86,112 @@ func (Self *copyBufferPool) Put(x []byte) {
}
}
type windowBufferPool struct {
pool sync.Pool
}
func (Self *windowBufferPool) New() {
Self.pool = sync.Pool{
New: func() interface{} {
return make([]byte, PoolSizeWindow, PoolSizeWindow)
},
}
}
func (Self *windowBufferPool) Get() (buf []byte) {
buf = Self.pool.Get().([]byte)
return buf[:PoolSizeWindow]
}
func (Self *windowBufferPool) Put(x []byte) {
Self.pool.Put(x[:PoolSizeWindow]) // make buf to full
}
type bufferPool struct {
pool sync.Pool
}
func (Self *bufferPool) New() {
Self.pool = sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 0, PoolSizeBuffer))
},
}
}
func (Self *bufferPool) Get() *bytes.Buffer {
return Self.pool.Get().(*bytes.Buffer)
}
func (Self *bufferPool) Put(x *bytes.Buffer) {
x.Reset()
Self.pool.Put(x)
}
type muxPackagerPool struct {
pool sync.Pool
}
func (Self *muxPackagerPool) New() {
Self.pool = sync.Pool{
New: func() interface{} {
pack := MuxPackager{}
return &pack
},
}
}
func (Self *muxPackagerPool) Get() *MuxPackager {
return Self.pool.Get().(*MuxPackager)
}
func (Self *muxPackagerPool) Put(pack *MuxPackager) {
Self.pool.Put(pack)
}
type ListElement struct {
Buf []byte
L uint16
Part bool
}
type listElementPool struct {
pool sync.Pool
}
func (Self *listElementPool) New() {
Self.pool = sync.Pool{
New: func() interface{} {
element := ListElement{}
return &element
},
}
}
func (Self *listElementPool) Get() *ListElement {
return Self.pool.Get().(*ListElement)
}
func (Self *listElementPool) Put(element *ListElement) {
element.L = 0
element.Buf = nil
element.Part = false
Self.pool.Put(element)
}
var once = sync.Once{}
var BuffPool = bufferPool{}
var CopyBuff = copyBufferPool{}
var MuxPack = muxPackagerPool{}
var WindowBuff = windowBufferPool{}
var ListElementPool = listElementPool{}
func newPool() {
BuffPool.New()
CopyBuff.New()
MuxPack.New()
WindowBuff.New()
ListElementPool.New()
}
func init() {

View File

@ -1,29 +0,0 @@
package common
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"net/http"
_ "net/http/pprof"
)
func InitPProfFromFile() {
ip := beego.AppConfig.String("pprof_ip")
p := beego.AppConfig.String("pprof_port")
if len(ip) > 0 && len(p) > 0 && IsPort(p) {
runPProf(ip + ":" + p)
}
}
func InitPProfFromArg(arg string) {
if len(arg) > 0 {
runPProf(arg)
}
}
func runPProf(ipPort string) {
go func() {
_ = http.ListenAndServe(ipPort, nil)
}()
logs.Info("PProf debug listen on", ipPort)
}

View File

@ -48,20 +48,9 @@ func IsWindows() bool {
func GetLogPath() string {
var path string
if IsWindows() {
path = filepath.Join(GetAppPath(), "nps.log")
path = GetAppPath()
} else {
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"
path = "/tmp"
}
return path
}
@ -76,14 +65,3 @@ func GetTmpPath() string {
}
return path
}
//config file path
func GetConfigPath() string {
var path string
if IsWindows() {
path = filepath.Join(GetAppPath(), "conf/npc.conf")
} else {
path = "conf/npc.conf"
}
return path
}

View File

@ -2,11 +2,8 @@ package common
import (
"bytes"
"ehang.io/nps/lib/version"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"html/template"
"io"
"io/ioutil"
@ -18,7 +15,7 @@ import (
"strings"
"sync"
"ehang.io/nps/lib/crypt"
"github.com/cnlh/nps/lib/crypt"
)
//Get the corresponding IP address through domain name
@ -53,10 +50,7 @@ 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 {
s = strings.SplitN(r.Header.Get("Proxy-Authorization"), " ", 2)
if len(s) != 2 {
return false
}
return false
}
b, err := base64.StdEncoding.DecodeString(s[1])
@ -100,7 +94,7 @@ func Getverifyval(vkey string) string {
}
//Change headers and host of request
func ChangeHostAndHeader(r *http.Request, host string, header string, addr string, addOrigin bool) {
func ChangeHostAndHeader(r *http.Request, host string, header string, addr string) {
if host != "" {
r.Host = host
}
@ -115,12 +109,10 @@ 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
}
if addOrigin {
r.Header.Set("X-Forwarded-For", addr)
r.Header.Set("X-Real-IP", addr)
}
addr = strings.Join(prior, ", ") + ", " + addr
}
r.Header.Set("X-Forwarded-For", addr)
r.Header.Set("X-Real-IP", addr)
}
//Read file content by file path
@ -129,7 +121,6 @@ func ReadAllFromFile(filePath string) ([]byte, error) {
if err != nil {
return nil, err
}
defer f.Close()
return ioutil.ReadAll(f)
}
@ -404,66 +395,3 @@ 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
}
func PrintVersion() {
fmt.Printf("Version: %s\nCore version: %s\nSame core version of client and server can connect each other\n", version.VERSION, version.GetVersion())
}

View File

@ -6,8 +6,8 @@ import (
"regexp"
"strings"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/file"
)
type CommonConfig struct {
@ -17,7 +17,6 @@ type CommonConfig struct {
AutoReconnection bool
ProxyUrl string
Client *file.Client
DisconnectTime int
}
type LocalServer struct {
@ -146,10 +145,6 @@ func dealCommon(s string) *CommonConfig {
c.Client.MaxConn = common.GetIntNoErrByStr(item[1])
case "remark":
c.Client.Remark = item[1]
case "pprof_addr":
common.InitPProfFromArg(item[1])
case "disconnect_timeout":
c.DisconnectTime = common.GetIntNoErrByStr(item[1])
}
}
return c
@ -246,15 +241,13 @@ func dealTunnel(s string) *file.Tunnel {
t.StripPre = item[1]
case "multi_account":
t.MultiAccount = &file.MultiAccount{}
if common.FileExists(item[1]) {
if b, err := common.ReadAllFromFile(item[1]); err != nil {
if b, err := common.ReadAllFromFile(item[1]); err != nil {
panic(err)
} else {
if content, err := common.ParseStr(string(b)); err != nil {
panic(err)
} else {
if content, err := common.ParseStr(string(b)); err != nil {
panic(err)
} else {
t.MultiAccount.AccountMap = dealMultiUser(content)
}
t.MultiAccount.AccountMap = dealMultiUser(content)
}
}
}
@ -302,7 +295,7 @@ func delLocalService(s string) *LocalServer {
func getAllTitle(content string) (arr []string, err error) {
var re *regexp.Regexp
re, err = regexp.Compile(`(?m)^\[[^\[\]\r\n]+\]`)
re, err = regexp.Compile(`\[.+?\]`)
if err != nil {
return
}

View File

@ -3,25 +3,24 @@ 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"
"net/url"
"strconv"
"strings"
"sync"
"time"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/file"
"ehang.io/nps/lib/pmux"
"ehang.io/nps/lib/rate"
"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"
"github.com/xtaci/kcp-go"
)
@ -35,33 +34,11 @@ func NewConn(conn net.Conn) *Conn {
return &Conn{Conn: conn}
}
func (s *Conn) readRequest(buf []byte) (n int, err error) {
var rd int
for {
rd, err = s.Read(buf[n:])
if err != nil {
return
}
n += rd
if n < 4 {
continue
}
if string(buf[n-4:n]) == "\r\n\r\n" {
return
}
// buf is full, can't contain the request
if n == cap(buf) {
err = io.ErrUnexpectedEOF
return
}
}
}
//get host 、connection type、method...from connection
func (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http.Request) {
var b [32 * 1024]byte
var n int
if n, err = s.readRequest(b[:]); err != nil {
if n, err = s.Read(b[:]); err != nil {
return
}
rb = b[:n]
@ -110,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) || cLen <= 0 {
if cLen > len(buf) {
return 0, errors.New("长度错误" + strconv.Itoa(cLen))
}
if n, err := io.ReadFull(s, buf[:cLen]); err != nil || n != cLen {
@ -147,10 +124,10 @@ func (s *Conn) SetAlive(tp string) {
case *net.TCPConn:
conn := s.Conn.(*net.TCPConn)
conn.SetReadDeadline(time.Time{})
//conn.SetKeepAlive(false)
//conn.SetKeepAlivePeriod(time.Duration(2 * time.Second))
case *pmux.PortConn:
s.Conn.(*pmux.PortConn).SetReadDeadline(time.Time{})
conn.SetKeepAlive(true)
conn.SetKeepAlivePeriod(time.Duration(2 * time.Second))
case *mux.PortConn:
s.Conn.(*mux.PortConn).SetReadDeadline(time.Time{})
}
}
@ -161,8 +138,8 @@ func (s *Conn) SetReadDeadlineBySecond(t time.Duration) {
s.Conn.(*kcp.UDPSession).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
case *net.TCPConn:
s.Conn.(*net.TCPConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
case *pmux.PortConn:
s.Conn.(*pmux.PortConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
case *mux.PortConn:
s.Conn.(*mux.PortConn).SetReadDeadline(time.Now().Add(time.Duration(t) * time.Second))
}
}
@ -394,10 +371,7 @@ func CopyWaitGroup(conn1, conn2 net.Conn, crypt bool, snappy bool, rate *rate.Ra
//if flow != nil {
// flow.Add(in, out)
//}
wg := new(sync.WaitGroup)
wg.Add(1)
err := goroutine.CopyConnsPool.Invoke(goroutine.NewConns(connHandle, conn2, flow, wg))
wg.Wait()
err := goroutine.CopyConnsPool.Invoke(goroutine.NewConns(connHandle, conn2, flow))
if err != nil {
logs.Error(err)
}

View File

@ -1,7 +1,5 @@
package conn
import "time"
type Secret struct {
Password string
Conn *Conn
@ -21,20 +19,9 @@ type Link struct {
Compress bool
LocalProxy bool
RemoteAddr string
Option Options
}
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...)
func NewLink(connType string, host string, crypt bool, compress bool, remoteAddr string, localProxy bool) *Link {
return &Link{
RemoteAddr: remoteAddr,
ConnType: connType,
@ -42,22 +29,5 @@ 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

@ -1,23 +1,21 @@
package conn
import (
"errors"
"io"
"github.com/cnlh/nps/lib/common"
"github.com/golang/snappy"
)
type SnappyConn struct {
w *snappy.Writer
r *snappy.Reader
c io.Closer
}
func NewSnappyConn(conn io.ReadWriteCloser) *SnappyConn {
c := new(SnappyConn)
c.w = snappy.NewBufferedWriter(conn)
c.r = snappy.NewReader(conn)
c.c = conn.(io.Closer)
return c
}
@ -34,20 +32,16 @@ func (s *SnappyConn) Write(b []byte) (n int, err error) {
//snappy压缩读
func (s *SnappyConn) Read(b []byte) (n int, err error) {
return s.r.Read(b)
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
}
func (s *SnappyConn) Close() error {
err := s.w.Close()
err2 := s.c.Close()
if err != nil && err2 == nil {
return err
}
if err == nil && err2 != nil {
return err2
}
if err != nil && err2 != nil {
return errors.New(err.Error() + err2.Error())
}
return nil
s.w.Close()
return s.w.Close()
}

View File

@ -1,37 +1,22 @@
package crypt
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"net"
"os"
"time"
"github.com/astaxie/beego/logs"
)
var (
cert tls.Certificate
)
var pemPath, keyPath string
func InitTls() {
c, k, err := generateKeyPair("NPS Org")
if err == nil {
cert, err = tls.X509KeyPair(c, k)
}
if err != nil {
log.Fatalln("Error initializing crypto certs", err)
}
func InitTls(pem, key string) {
pemPath = pem
keyPath = key
}
func NewTlsServerConn(conn net.Conn) net.Conn {
var err error
cert, err := tls.LoadX509KeyPair(pemPath, keyPath)
if err != nil {
logs.Error(err)
os.Exit(0)
@ -47,41 +32,3 @@ func NewTlsClientConn(conn net.Conn) net.Conn {
}
return tls.Client(conn, conf)
}
func generateKeyPair(CommonName string) (rawCert, rawKey []byte, err error) {
// Create private key and self-signed certificate
// Adapted from https://golang.org/src/crypto/tls/generate_cert.go
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return
}
validFor := time.Hour * 24 * 365 * 10 // ten years
notBefore := time.Now()
notAfter := notBefore.Add(validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"My Company Name LTD."},
CommonName: CommonName,
Country: []string{"US"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return
}
rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
return
}

View File

@ -9,7 +9,7 @@ import (
"strconv"
"strings"
"ehang.io/nps/lib/common"
"github.com/cnlh/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

@ -4,13 +4,14 @@ import (
"errors"
"fmt"
"net/http"
"regexp"
"sort"
"strings"
"sync"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/crypt"
"ehang.io/nps/lib/rate"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/crypt"
"github.com/cnlh/nps/lib/rate"
)
type DbUtils struct {
@ -327,16 +328,13 @@ func (s *DbUtils) GetInfoByHost(host string, r *http.Request) (h *Host, err erro
}
//Remove http(s) http(s)://a.proxy.com
//*.proxy.com *.a.proxy.com Do some pan-parsing
if v.Scheme != "all" && v.Scheme != r.URL.Scheme {
tmp := strings.Replace(v.Host, "*", `\w+?`, -1)
var re *regexp.Regexp
if re, err = regexp.Compile(tmp); err != nil {
return true
}
tmpHost := v.Host
if strings.Contains(tmpHost, "*") {
tmpHost = strings.Replace(tmpHost, "*", "", -1)
if strings.Contains(host, tmpHost) {
hosts = append(hosts, v)
}
} else if v.Host == host {
if len(re.FindAllString(host, -1)) > 0 && (v.Scheme == "all" || v.Scheme == r.URL.Scheme) {
//URL routing
hosts = append(hosts, v)
}
return true

View File

@ -3,15 +3,14 @@ package file
import (
"encoding/json"
"errors"
"github.com/astaxie/beego/logs"
"os"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/rate"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/rate"
)
func NewJsonDb(runPath string) *JsonDb {
@ -100,28 +99,16 @@ func (s *JsonDb) GetClient(id int) (c *Client, err error) {
return
}
var hostLock sync.Mutex
func (s *JsonDb) StoreHostToJsonFile() {
hostLock.Lock()
storeSyncMapToFile(s.Hosts, s.HostFilePath)
hostLock.Unlock()
}
var taskLock sync.Mutex
func (s *JsonDb) StoreTasksToJsonFile() {
taskLock.Lock()
storeSyncMapToFile(s.Tasks, s.TaskFilePath)
taskLock.Unlock()
}
var clientLock sync.Mutex
func (s *JsonDb) StoreClientsToJsonFile() {
clientLock.Lock()
storeSyncMapToFile(s.Clients, s.ClientFilePath)
clientLock.Unlock()
}
func (s *JsonDb) GetClientId() int32 {
@ -147,11 +134,11 @@ func loadSyncMapFromFile(filePath string, f func(value string)) {
}
func storeSyncMapToFile(m sync.Map, filePath string) {
file, err := os.Create(filePath + ".tmp")
// first create a temporary file to store
file, err := os.Create(filePath)
if err != nil {
panic(err)
}
defer file.Close()
m.Range(func(key, value interface{}) bool {
var b []byte
var err error
@ -190,12 +177,4 @@ func storeSyncMapToFile(m sync.Map, filePath string) {
}
return true
})
_ = file.Sync()
_ = file.Close()
// must close file first, then rename it
err = os.Rename(filePath+".tmp", filePath)
if err != nil {
logs.Error(err, "store to file err, data will lost")
}
// replace the file, maybe provides atomic operation
}

View File

@ -6,7 +6,7 @@ import (
"sync/atomic"
"time"
"ehang.io/nps/lib/rate"
"github.com/cnlh/nps/lib/rate"
"github.com/pkg/errors"
)
@ -50,7 +50,6 @@ 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 (
"ehang.io/nps/lib/common"
"ehang.io/nps/lib/file"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/file"
"github.com/panjf2000/ants/v2"
"io"
"net"
@ -44,15 +44,13 @@ type Conns struct {
conn1 io.ReadWriteCloser // mux connection
conn2 net.Conn // outside connection
flow *file.Flow
wg *sync.WaitGroup
}
func NewConns(c1 io.ReadWriteCloser, c2 net.Conn, flow *file.Flow, wg *sync.WaitGroup) Conns {
func NewConns(c1 io.ReadWriteCloser, c2 net.Conn, flow *file.Flow) Conns {
return Conns{
conn1: c1,
conn2: c2,
flow: flow,
wg: wg,
}
}
@ -69,7 +67,6 @@ func copyConns(group interface{}) {
if conns.flow != nil {
conns.flow.Add(in, out)
}
conns.wg.Done()
}
var connCopyPool, _ = ants.NewPoolWithFunc(200000, copyConnGroup, ants.WithNonblocking(false))

View File

@ -1,269 +1,88 @@
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"
)
// Keep it in sync with the template from service_sysv_linux.go file
// Use "ps | grep -v grep | grep $(get_pid)" because "ps PID" may not work on OpenWrt
const SysvScript = `#!/bin/sh
# For RedHat and cousins:
# chkconfig: - 99 01
# description: {{.Description}}
# processname: {{.Path}}
### BEGIN INIT INFO
# Provides: {{.Path}}
# Required-Start:
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: {{.DisplayName}}
# Description: {{.Description}}
### END INIT INFO
cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}"
name=$(basename $(readlink -f $0))
pid_file="/var/run/$name.pid"
stdout_log="/var/log/$name.log"
stderr_log="/var/log/$name.err"
[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name
get_pid() {
cat "$pid_file"
}
is_running() {
[ -f "$pid_file" ] && ps | grep -v grep | grep $(get_pid) > /dev/null 2>&1
}
case "$1" in
start)
if is_running; then
echo "Already started"
else
echo "Starting $name"
{{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}}
$cmd >> "$stdout_log" 2>> "$stderr_log" &
echo $! > "$pid_file"
if ! is_running; then
echo "Unable to start, see $stdout_log and $stderr_log"
exit 1
fi
fi
;;
stop)
if is_running; then
echo -n "Stopping $name.."
kill $(get_pid)
for i in $(seq 1 10)
do
if ! is_running; then
break
fi
echo -n "."
sleep 1
done
echo
if is_running; then
echo "Not stopped; may still be shutting down or shutdown may have failed"
exit 1
else
echo "Stopped"
if [ -f "$pid_file" ]; then
rm "$pid_file"
fi
fi
else
echo "Not running"
fi
;;
restart)
$0 stop
if is_running; then
echo "Unable to stop, will not attempt to start"
exit 1
fi
$0 start
;;
status)
if is_running; then
echo "Running"
else
echo "Stopped"
exit 1
fi
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
exit 0
`
const SystemdScript = `[Unit]
Description={{.Description}}
ConditionFileIsExecutable={{.Path|cmdEscape}}
{{range $i, $dep := .Dependencies}}
{{$dep}} {{end}}
[Service]
LimitNOFILE=65536
StartLimitInterval=5
StartLimitBurst=10
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
{{if .UserName}}User={{.UserName}}{{end}}
{{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}}
{{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}
{{if and .LogOutput .HasOutputFileSupport -}}
StandardOutput=file:/var/log/{{.Name}}.out
StandardError=file:/var/log/{{.Name}}.err
{{- end}}
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=120
[Install]
WantedBy=multi-user.target
`
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`
func UpdateNps() {
destPath := downloadLatest("server")
//复制文件到对应目录
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/ehang-io/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(srcPath, bin), "/usr/bin/"+bin); err != nil {
if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin); err != nil {
log.Fatalln(err)
} else {
copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin+"-update")
chMod("/usr/local/bin/"+bin+"-update", 0755)
binPath = "/usr/local/bin/" + bin
}
} else {
copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin+"-update")
chMod("/usr/bin/"+bin+"-update", 0755)
binPath = "/usr/bin/" + bin
}
} 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)
log.Fatalf("the path %s has exist, does not support install", path)
}
MkidrDirAll(path, "conf", "web/static", "web/views")
//复制文件到对应目录
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)
}
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 {
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")
}
} else {
os.Chmod("/usr/bin/nps", 0755)
service += "/usr/bin/nps"
log.Println("Executable files have been copied to", "/usr/bin/nps")
}
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)
}
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:
nps start|stop|restart|uninstall|update or nps-update update
sudo systemctl enable|disable|start|stop|restart|status nps
or:
nps test|start|stop|restart|status
anywhere!`)
} else {
log.Println(`You can copy executable files to any directory and start working with:
nps.exe start|stop|restart|uninstall|update or nps-update.exe update
nps.exe test|start|stop|restart|status
now!`)
}
chMod(common.GetLogPath(), 0777)
return binPath
}
func MkidrDirAll(path string, v ...string) {
for _, item := range v {
@ -300,9 +119,6 @@ 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
})
@ -355,9 +171,3 @@ func pathExists(path string) (bool, error) {
}
return false, err
}
func chMod(name string, mode os.FileMode) {
if !common.IsWindows() {
os.Chmod(name, mode)
}
}

32
lib/mux/bytes.go Normal file
View File

@ -0,0 +1,32 @@
package mux
import (
"bytes"
"encoding/binary"
"io"
)
//write bytes with int32 length
func WriteLenBytes(buf []byte, w io.Writer) (int, error) {
raw := bytes.NewBuffer([]byte{})
if err := binary.Write(raw, binary.LittleEndian, int32(len(buf))); err != nil {
return 0, err
}
if err := binary.Write(raw, binary.LittleEndian, buf); err != nil {
return 0, err
}
return w.Write(raw.Bytes())
}
//read bytes by length
func ReadLenBytes(buf []byte, r io.Reader) (int, error) {
var l uint32
var err error
if binary.Read(r, binary.LittleEndian, &l) != nil {
return 0, err
}
if _, err = io.ReadFull(r, buf[:l]); err != nil {
return 0, err
}
return int(l), nil
}

635
lib/mux/conn.go Normal file
View File

@ -0,0 +1,635 @@
package mux
import (
"errors"
"io"
"math"
"net"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/cnlh/nps/lib/common"
)
type conn struct {
net.Conn
getStatusCh chan struct{}
connStatusOkCh chan struct{}
connStatusFailCh chan struct{}
connId int32
isClose bool
closeFlag bool // close conn flag
receiveWindow *ReceiveWindow
sendWindow *SendWindow
once sync.Once
//label string
}
func NewConn(connId int32, mux *Mux, label ...string) *conn {
c := &conn{
getStatusCh: make(chan struct{}),
connStatusOkCh: make(chan struct{}),
connStatusFailCh: make(chan struct{}),
connId: connId,
receiveWindow: new(ReceiveWindow),
sendWindow: new(SendWindow),
once: sync.Once{},
}
//if len(label) > 0 {
// c.label = label[0]
//}
c.receiveWindow.New(mux)
c.sendWindow.New(mux)
//logm := &connLog{
// startTime: time.Now(),
// isClose: false,
// logs: []string{c.label + "new conn success"},
//}
//setM(label[0], int(connId), logm)
return c
}
func (s *conn) Read(buf []byte) (n int, err error) {
if s.isClose || buf == nil {
return 0, errors.New("the conn has closed")
}
if len(buf) == 0 {
return 0, nil
}
// waiting for takeout from receive window finish or timeout
//now := time.Now()
n, err = s.receiveWindow.Read(buf, s.connId)
//t := time.Now().Sub(now)
//if t.Seconds() > 0.5 {
//logs.Warn("conn read long", n, t.Seconds())
//}
//var errstr string
//if err == nil {
// errstr = "err:nil"
//} else {
// errstr = err.Error()
//}
//d := getM(s.label, int(s.connId))
//d.logs = append(d.logs, s.label+"read "+strconv.Itoa(n)+" "+errstr+" "+string(buf[:100]))
//setM(s.label, int(s.connId), d)
return
}
func (s *conn) Write(buf []byte) (n int, err error) {
if s.isClose {
return 0, errors.New("the conn has closed")
}
if s.closeFlag {
//s.Close()
return 0, errors.New("io: write on closed conn")
}
if len(buf) == 0 {
return 0, nil
}
//logs.Warn("write buf", len(buf))
//now := time.Now()
n, err = s.sendWindow.WriteFull(buf, s.connId)
//t := time.Now().Sub(now)
//if t.Seconds() > 0.5 {
// logs.Warn("conn write long", n, t.Seconds())
//}
return
}
func (s *conn) Close() (err error) {
s.once.Do(s.closeProcess)
return
}
func (s *conn) closeProcess() {
s.isClose = true
s.receiveWindow.mux.connMap.Delete(s.connId)
if !s.receiveWindow.mux.IsClose {
// if server or user close the conn while reading, will get a io.EOF
// and this Close method will be invoke, send this signal to close other side
s.receiveWindow.mux.sendInfo(common.MUX_CONN_CLOSE, s.connId, nil)
}
s.sendWindow.CloseWindow()
s.receiveWindow.CloseWindow()
//d := getM(s.label, int(s.connId))
//d.isClose = true
//d.logs = append(d.logs, s.label+"close "+time.Now().String())
//setM(s.label, int(s.connId), d)
return
}
func (s *conn) LocalAddr() net.Addr {
return s.receiveWindow.mux.conn.LocalAddr()
}
func (s *conn) RemoteAddr() net.Addr {
return s.receiveWindow.mux.conn.RemoteAddr()
}
func (s *conn) SetDeadline(t time.Time) error {
_ = s.SetReadDeadline(t)
_ = s.SetWriteDeadline(t)
return nil
}
func (s *conn) SetReadDeadline(t time.Time) error {
s.receiveWindow.SetTimeOut(t)
return nil
}
func (s *conn) SetWriteDeadline(t time.Time) error {
s.sendWindow.SetTimeOut(t)
return nil
}
type window struct {
remainingWait uint64 // 64bit alignment
off uint32
maxSize 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)
return
}
func (Self *window) pack(remaining, wait uint32) uint64 {
const mask = 1<<dequeueBits - 1
return (uint64(remaining) << dequeueBits) |
uint64(wait&mask)
}
func (Self *window) New() {
Self.closeOpCh = make(chan struct{}, 2)
}
func (Self *window) CloseWindow() {
if !Self.closeOp {
Self.closeOp = true
Self.closeOpCh <- struct{}{}
Self.closeOpCh <- struct{}{}
}
}
type ReceiveWindow struct {
window
bufQueue ReceiveWindowQueue
element *common.ListElement
count int8
once sync.Once
}
func (Self *ReceiveWindow) New(mux *Mux) {
// initial a window for receive
Self.bufQueue.New()
Self.element = common.ListElementPool.Get()
Self.maxSize = common.MAXIMUM_SEGMENT_SIZE * 10
Self.mux = mux
Self.window.New()
}
func (Self *ReceiveWindow) remainingSize(delta uint16) (n uint32) {
// receive window remaining
l := int64(atomic.LoadUint32(&Self.maxSize)) - int64(Self.bufQueue.Len())
l -= int64(delta)
if l > 0 {
n = uint32(l)
}
return
}
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
}
bufLen := Self.bufQueue.Len()
if n < bufLen {
n = bufLen
}
if n < Self.maxSize/2 {
n = Self.maxSize / 2
}
// 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
return
}
func (Self *ReceiveWindow) Write(buf []byte, l uint16, part bool, id int32) (err error) {
if Self.closeOp {
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)
if err != nil {
return
}
Self.calcSize() // calculate the max window size
var wait uint32
start:
ptrs := atomic.LoadUint64(&Self.remainingWait)
_, wait = Self.unpack(ptrs)
newRemaining := Self.remainingSize(l)
// calculate the remaining window size now, plus the element we will push
if newRemaining == 0 {
//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
}
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
}
return nil
}
func (Self *ReceiveWindow) Read(p []byte, id int32) (n int, err error) {
if Self.closeOp {
return 0, io.EOF // receive close signal, returns eof
}
pOff := 0
l := 0
//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
// both zero value
common.ListElementPool.Put(Self.element)
if Self.closeOp {
return 0, io.EOF
}
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
Self.off = 0
if err != nil {
return // queue receive stop or time out, break the loop and return
}
//logs.Warn("pop element", Self.element.l, Self.element.part)
}
l = copy(p[pOff:], Self.element.Buf[Self.off:Self.element.L])
pOff += l
Self.off += uint32(l)
//logs.Warn("window read length buf len", Self.readLength, Self.bufQueue.Len())
n += l
l = 0
if Self.off == uint32(Self.element.L) {
//logs.Warn("put the element end ", string(Self.element.buf[:15]))
common.WindowBuff.Put(Self.element.Buf)
Self.sendStatus(id, Self.element.L)
// check the window full status
}
if pOff < len(p) && Self.element.Part {
// element is a part of the segments, trying to fill up buf p
goto copyData
}
return // buf p is full or all of segments in buf, return
}
func (Self *ReceiveWindow) sendStatus(id int32, l uint16) {
var remaining, wait uint32
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
}
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
}
func (Self *ReceiveWindow) SetTimeOut(t time.Time) {
// waiting for FIFO queue Pop method
Self.bufQueue.SetTimeOut(t)
}
func (Self *ReceiveWindow) Stop() {
// queue has no more data to push, so unblock pop method
Self.once.Do(Self.bufQueue.Stop)
}
func (Self *ReceiveWindow) CloseWindow() {
Self.window.CloseWindow()
Self.Stop()
Self.release()
}
func (Self *ReceiveWindow) release() {
//if Self.element != nil {
// if Self.element.Buf != nil {
// common.WindowBuff.Put(Self.element.Buf)
// }
// common.ListElementPool.Put(Self.element)
//}
for {
Self.element = Self.bufQueue.TryPop()
if Self.element == nil {
return
}
if Self.element.Buf != nil {
common.WindowBuff.Put(Self.element.Buf)
}
common.ListElementPool.Put(Self.element)
} // release resource
}
type SendWindow struct {
window
buf []byte
setSizeCh chan struct{}
timeout time.Time
}
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.mux = mux
Self.window.New()
}
func (Self *SendWindow) SetSendBuf(buf []byte) {
// send window buff from conn write method, set it to send window
Self.buf = buf
Self.off = 0
}
func (Self *SendWindow) SetSize(windowSize, newRemaining uint32) (closed bool) {
// set the window size from receive window
defer func() {
if recover() != nil {
closed = true
}
}()
if Self.closeOp {
close(Self.setSizeCh)
return true
}
//logs.Warn("set send window size to ", windowSize, newRemaining)
var remaining, wait, newWait uint32
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
}
if newRemaining == 0 && wait == 1 {
newWait = 1 // keep the wait status,
// also if newRemaining is not zero, change wait to 0
}
if atomic.CompareAndSwapUint64(&Self.remainingWait, ptrs, Self.pack(newRemaining, newWait)) {
break
}
// anther goroutine change wait status or window size
}
if wait == 1 {
// send window into the wait status, need notice the channel
//logs.Warn("send window remaining size is 0")
Self.allow()
}
// send window not into the wait status, so just do slide
return false
}
func (Self *SendWindow) allow() {
select {
case Self.setSizeCh <- struct{}{}:
//logs.Warn("send window remaining size is 0 finish")
return
case <-Self.closeOpCh:
close(Self.setSizeCh)
return
}
}
func (Self *SendWindow) sent(sentSize uint32) {
atomic.AddUint64(&Self.remainingWait, ^(uint64(sentSize)<<dequeueBits - 1))
}
func (Self *SendWindow) WriteTo() (p []byte, sendSize uint32, part bool, err error) {
// returns buf segments, return only one segments, need a loop outside
// until err = io.EOF
if Self.closeOp {
return nil, 0, false, errors.New("conn.writeWindow: window closed")
}
if Self.off == uint32(len(Self.buf)) {
return nil, 0, false, io.EOF
// send window buff is drain, return eof and get another one
}
var remaining uint32
start:
ptrs := atomic.LoadUint64(&Self.remainingWait)
remaining, _ = Self.unpack(ptrs)
if remaining == 0 {
if !atomic.CompareAndSwapUint64(&Self.remainingWait, ptrs, Self.pack(0, 1)) {
goto start // another goroutine change the window, try again
}
// into the wait status
//logs.Warn("send window into wait status")
err = Self.waitReceiveWindow()
if err != nil {
return nil, 0, false, err
}
//logs.Warn("rem into wait finish")
goto start
}
// there are still remaining window
//logs.Warn("rem", remaining)
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 {
// usable window size is small than
// window MAXIMUM_SEGMENT_SIZE or send buf left
sendSize = remaining
//logs.Warn("cut buf by remainingsize", sendSize, len(Self.buf[Self.off:]))
}
//logs.Warn("send size", sendSize)
if sendSize < uint32(len(Self.buf[Self.off:])) {
part = true
}
p = Self.buf[Self.off : sendSize+Self.off]
Self.off += sendSize
Self.sent(sendSize)
return
}
func (Self *SendWindow) waitReceiveWindow() (err error) {
t := Self.timeout.Sub(time.Now())
if t < 0 {
t = time.Minute * 5
}
timer := time.NewTimer(t)
defer timer.Stop()
// waiting for receive usable window size, or timeout
select {
case _, ok := <-Self.setSizeCh:
if !ok {
return errors.New("conn.writeWindow: window closed")
}
return nil
case <-timer.C:
return errors.New("conn.writeWindow: write to time out")
case <-Self.closeOpCh:
return errors.New("conn.writeWindow: window closed")
}
}
func (Self *SendWindow) WriteFull(buf []byte, id int32) (n int, err error) {
Self.SetSendBuf(buf) // set the buf to send window
//logs.Warn("set the buf to send window")
var bufSeg []byte
var part bool
var l uint32
for {
bufSeg, l, part, err = Self.WriteTo()
//logs.Warn("buf seg", len(bufSeg), part, err)
// get the buf segments from send window
if bufSeg == nil && part == false && err == io.EOF {
// send window is drain, break the loop
err = nil
break
}
if err != nil {
break
}
n += int(l)
l = 0
if part {
Self.mux.sendInfo(common.MUX_NEW_MSG_PART, id, bufSeg)
} else {
Self.mux.sendInfo(common.MUX_NEW_MSG, id, bufSeg)
//logs.Warn("buf seg sent", len(bufSeg), part, err)
}
// send to other side, not send nil data to other side
}
//logs.Warn("buf seg write success")
return
}
func (Self *SendWindow) SetTimeOut(t time.Time) {
// waiting for receive a receive window size
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
//}

75
lib/mux/map.go Normal file
View File

@ -0,0 +1,75 @@
package mux
import (
"sync"
)
type connMap struct {
connMap map[int32]*conn
//closeCh chan struct{}
sync.RWMutex
}
func NewConnMap() *connMap {
connMap := &connMap{
connMap: make(map[int32]*conn),
//closeCh: make(chan struct{}),
}
//go connMap.clean()
return connMap
}
func (s *connMap) Size() (n int) {
s.Lock()
n = len(s.connMap)
s.Unlock()
return
}
func (s *connMap) Get(id int32) (*conn, bool) {
s.Lock()
v, ok := s.connMap[id]
s.Unlock()
if ok && v != nil {
return v, true
}
return nil, false
}
func (s *connMap) Set(id int32, v *conn) {
s.Lock()
s.connMap[id] = v
s.Unlock()
}
func (s *connMap) Close() {
//s.closeCh <- struct{}{} // stop the clean goroutine first
for _, v := range s.connMap {
v.Close() // close all the connections in the mux
}
}
func (s *connMap) Delete(id int32) {
s.Lock()
delete(s.connMap, id)
s.Unlock()
}
//func (s *connMap) clean() {
// ticker := time.NewTimer(time.Minute * 1)
// for {
// select {
// case <-ticker.C:
// s.Lock()
// for _, v := range s.connMap {
// if v.isClose {
// delete(s.connMap, v.connId)
// }
// }
// s.Unlock()
// case <-s.closeCh:
// ticker.Stop()
// return
// }
// }
//}

508
lib/mux/mux.go Normal file
View File

@ -0,0 +1,508 @@
package mux
import (
"errors"
"io"
"math"
"net"
"sync/atomic"
"time"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/common"
)
type Mux struct {
latency uint64 // we store latency in bits, but it's float64
net.Listener
conn net.Conn
connMap *connMap
newConnCh chan *conn
id int32
closeChan chan struct{}
IsClose bool
pingOk uint32
counter *latencyCounter
bw *bandwidth
pingCh chan []byte
pingCheckTime uint32
connType string
writeQueue PriorityQueue
newConnQueue ConnQueue
}
func NewMux(c net.Conn, connType string) *Mux {
//c.(*net.TCPConn).SetReadBuffer(0)
//c.(*net.TCPConn).SetWriteBuffer(0)
m := &Mux{
conn: c,
connMap: NewConnMap(),
id: 0,
closeChan: make(chan struct{}, 1),
newConnCh: make(chan *conn),
bw: new(bandwidth),
IsClose: false,
connType: connType,
pingCh: make(chan []byte),
counter: newLatencyCounter(),
}
m.writeQueue.New()
m.newConnQueue.New()
//read session by flag
m.readSession()
//ping
m.ping()
m.pingReturn()
m.writeSession()
return m
}
func (s *Mux) NewConn() (*conn, error) {
if s.IsClose {
return nil, errors.New("the mux has closed")
}
conn := NewConn(s.getId(), s, "nps ")
//it must be set before send
s.connMap.Set(conn.connId, conn)
s.sendInfo(common.MUX_NEW_CONN, conn.connId, nil)
//set a timer timeout 30 second
timer := time.NewTimer(time.Minute * 2)
defer timer.Stop()
select {
case <-conn.connStatusOkCh:
return conn, nil
case <-conn.connStatusFailCh:
case <-timer.C:
}
return nil, errors.New("create connection failthe server refused the connection")
}
func (s *Mux) Accept() (net.Conn, error) {
if s.IsClose {
return nil, errors.New("accpet error,the mux has closed")
}
conn := <-s.newConnCh
if conn == nil {
return nil, errors.New("accpet error,the conn has closed")
}
return conn, nil
}
func (s *Mux) Addr() net.Addr {
return s.conn.LocalAddr()
}
func (s *Mux) sendInfo(flag uint8, id int32, data ...interface{}) {
if s.IsClose {
return
}
var err error
pack := common.MuxPack.Get()
err = pack.NewPac(flag, id, data...)
if err != nil {
common.MuxPack.Put(pack)
logs.Error("mux: new pack err", err)
s.Close()
return
}
s.writeQueue.Push(pack)
return
}
func (s *Mux) writeSession() {
go s.packBuf()
//go s.writeBuf()
}
func (s *Mux) packBuf() {
//buffer := common.BuffPool.Get()
for {
if s.IsClose {
break
}
//buffer.Reset()
pack := s.writeQueue.Pop()
if s.IsClose {
break
}
//buffer := common.BuffPool.Get()
err := pack.Pack(s.conn)
common.MuxPack.Put(pack)
if err != nil {
logs.Error("mux: pack err", err)
//common.BuffPool.Put(buffer)
s.Close()
break
}
//logs.Warn(buffer.String())
//s.bufQueue.Push(buffer)
//l := buffer.Len()
//n, err := buffer.WriteTo(s.conn)
//common.BuffPool.Put(buffer)
//if err != nil || int(n) != l {
// logs.Error("mux: close from write session fail ", err, n, l)
// s.Close()
// break
//}
}
}
//func (s *Mux) writeBuf() {
// for {
// if s.IsClose {
// break
// }
// buffer, err := s.bufQueue.Pop()
// if err != nil {
// break
// }
// l := buffer.Len()
// n, err := buffer.WriteTo(s.conn)
// common.BuffPool.Put(buffer)
// if err != nil || int(n) != l {
// logs.Warn("close from write session fail ", err, n, l)
// s.Close()
// break
// }
// }
//}
func (s *Mux) ping() {
go func() {
now, _ := time.Now().UTC().MarshalText()
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)
for {
if s.IsClose {
ticker.Stop()
break
}
select {
case <-ticker.C:
}
if atomic.LoadUint32(&s.pingCheckTime) >= 60 {
logs.Error("mux: ping time out")
s.Close()
// more than 5 minutes not receive the ping return package,
// mux conn is damaged, maybe a packet drop, close it
break
}
now, _ := time.Now().UTC().MarshalText()
s.sendInfo(common.MUX_PING_FLAG, common.MUX_PING, now)
atomic.AddUint32(&s.pingCheckTime, 1)
if atomic.LoadUint32(&s.pingOk) > 10 && s.connType == "kcp" {
logs.Error("mux: kcp ping err")
s.Close()
break
}
atomic.AddUint32(&s.pingOk, 1)
}
}()
}
func (s *Mux) pingReturn() {
go func() {
var now time.Time
var data []byte
for {
if s.IsClose {
break
}
select {
case data = <-s.pingCh:
atomic.StoreUint32(&s.pingCheckTime, 0)
case <-s.closeChan:
break
}
_ = now.UnmarshalText(data)
latency := time.Now().UTC().Sub(now).Seconds() / 2
if latency > 0 {
atomic.StoreUint64(&s.latency, math.Float64bits(s.counter.Latency(latency)))
// convert float64 to bits, store it atomic
}
//logs.Warn("latency", math.Float64frombits(atomic.LoadUint64(&s.latency)))
if cap(data) > 0 {
common.WindowBuff.Put(data)
}
}
}()
}
func (s *Mux) readSession() {
go func() {
var connection *conn
for {
if s.IsClose {
break
}
connection = s.newConnQueue.Pop()
if s.IsClose {
break // make sure that is closed
}
s.connMap.Set(connection.connId, connection) //it has been set before send ok
s.newConnCh <- connection
s.sendInfo(common.MUX_NEW_CONN_OK, connection.connId, nil)
}
}()
go func() {
pack := common.MuxPack.Get()
var l uint16
var err error
for {
if s.IsClose {
break
}
pack = common.MuxPack.Get()
s.bw.StartRead()
if l, err = pack.UnPack(s.conn); err != nil {
logs.Error("mux: read session unpack from connection err", err)
s.Close()
break
}
s.bw.SetCopySize(l)
atomic.StoreUint32(&s.pingOk, 0)
switch pack.Flag {
case common.MUX_NEW_CONN: //new connection
connection := NewConn(pack.Id, s)
s.newConnQueue.Push(connection)
continue
case common.MUX_PING_FLAG: //ping
s.sendInfo(common.MUX_PING_RETURN, common.MUX_PING, pack.Content)
common.WindowBuff.Put(pack.Content)
continue
case common.MUX_PING_RETURN:
//go func(content []byte) {
s.pingCh <- pack.Content
//}(pack.Content)
continue
}
if connection, ok := s.connMap.Get(pack.Id); ok && !connection.isClose {
switch pack.Flag {
case common.MUX_NEW_MSG, common.MUX_NEW_MSG_PART: //new msg from remote connection
err = s.newMsg(connection, pack)
if err != nil {
logs.Error("mux: read session connection new msg err", err)
connection.Close()
}
continue
case common.MUX_NEW_CONN_OK: //connection ok
connection.connStatusOkCh <- struct{}{}
continue
case common.MUX_NEW_CONN_Fail:
connection.connStatusFailCh <- struct{}{}
continue
case common.MUX_MSG_SEND_OK:
if connection.isClose {
continue
}
connection.sendWindow.SetSize(pack.Window, pack.ReadLength)
continue
case common.MUX_CONN_CLOSE: //close the connection
connection.closeFlag = true
//s.connMap.Delete(pack.Id)
//go func(connection *conn) {
connection.receiveWindow.Stop() // close signal to receive window
//}(connection)
continue
}
} else if pack.Flag == common.MUX_CONN_CLOSE {
continue
}
common.MuxPack.Put(pack)
}
common.MuxPack.Put(pack)
s.Close()
}()
}
func (s *Mux) newMsg(connection *conn, pack *common.MuxPackager) (err error) {
if connection.isClose {
err = io.ErrClosedPipe
return
}
//logs.Warn("read session receive new msg", pack.Length)
//go func(connection *conn, pack *common.MuxPackager) { // do not block read session
//insert into queue
if pack.Flag == common.MUX_NEW_MSG_PART {
err = connection.receiveWindow.Write(pack.Content, pack.Length, true, pack.Id)
}
if pack.Flag == common.MUX_NEW_MSG {
err = connection.receiveWindow.Write(pack.Content, pack.Length, false, pack.Id)
}
//logs.Warn("read session write success", pack.Length)
return
}
func (s *Mux) Close() (err error) {
logs.Warn("close mux")
if s.IsClose {
return errors.New("the mux has closed")
}
s.IsClose = true
s.connMap.Close()
s.connMap = nil
//s.bufQueue.Stop()
s.closeChan <- struct{}{}
close(s.newConnCh)
err = s.conn.Close()
s.release()
return
}
func (s *Mux) release() {
for {
pack := s.writeQueue.TryPop()
if pack == nil {
break
}
if pack.BasePackager.Content != nil {
common.WindowBuff.Put(pack.BasePackager.Content)
}
common.MuxPack.Put(pack)
}
for {
connection := s.newConnQueue.TryPop()
if connection == nil {
break
}
connection = nil
}
s.writeQueue.Stop()
s.newConnQueue.Stop()
}
//get new connId as unique flag
func (s *Mux) getId() (id int32) {
//Avoid going beyond the scope
if (math.MaxInt32 - s.id) < 10000 {
atomic.StoreInt32(&s.id, 0)
}
id = atomic.AddInt32(&s.id, 1)
if _, ok := s.connMap.Get(id); ok {
return s.getId()
}
return
}
type bandwidth struct {
readBandwidth uint64 // store in bits, but it's float64
readStart time.Time
lastReadStart time.Time
bufLength uint32
}
func (Self *bandwidth) StartRead() {
if Self.readStart.IsZero() {
Self.readStart = time.Now()
}
if Self.bufLength >= common.MAXIMUM_SEGMENT_SIZE*300 {
Self.lastReadStart, Self.readStart = Self.readStart, time.Now()
Self.calcBandWidth()
}
}
func (Self *bandwidth) SetCopySize(n uint16) {
Self.bufLength += uint32(n)
}
func (Self *bandwidth) calcBandWidth() {
t := Self.readStart.Sub(Self.lastReadStart)
atomic.StoreUint64(&Self.readBandwidth, math.Float64bits(float64(Self.bufLength)/t.Seconds()))
Self.bufLength = 0
}
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
}
//logs.Warn(bw)
return
}
const counterBits = 4
const counterMask = 1<<counterBits - 1
func newLatencyCounter() *latencyCounter {
return &latencyCounter{
buf: make([]float64, 1<<counterBits, 1<<counterBits),
headMin: 0,
}
}
type latencyCounter struct {
buf []float64 //buf is a fixed length ring buffer,
// if buffer is full, new value will replace the oldest one.
headMin uint8 //head indicate the head in ring buffer,
// in meaning, slot in list will be replaced;
// min indicate this slot value is minimal in list.
}
func (Self *latencyCounter) unpack(idxs uint8) (head, min uint8) {
head = uint8((idxs >> counterBits) & counterMask)
// we set head is 4 bits
min = uint8(idxs & counterMask)
return
}
func (Self *latencyCounter) pack(head, min uint8) uint8 {
return uint8(head<<counterBits) |
uint8(min&counterMask)
}
func (Self *latencyCounter) add(value float64) {
head, min := Self.unpack(Self.headMin)
Self.buf[head] = value
if head == min {
min = Self.minimal()
//if head equals min, means the min slot already be replaced,
// so we need to find another minimal value in the list,
// and change the min indicator
}
if Self.buf[min] > value {
min = head
}
head++
Self.headMin = Self.pack(head, min)
}
func (Self *latencyCounter) minimal() (min uint8) {
var val float64
var i uint8
for i = 0; i < counterMask; i++ {
if Self.buf[i] > 0 {
if val > Self.buf[i] {
val = Self.buf[i]
min = i
}
}
}
return
}
func (Self *latencyCounter) Latency(value float64) (latency float64) {
Self.add(value)
_, min := Self.unpack(Self.headMin)
latency = Self.buf[min] * Self.countSuccess()
return
}
const lossRatio = 1.6
func (Self *latencyCounter) countSuccess() (successRate float64) {
var success, loss, i uint8
_, min := Self.unpack(Self.headMin)
for i = 0; i < counterMask; i++ {
if Self.buf[i] > lossRatio*Self.buf[min] && Self.buf[i] > 0 {
loss++
}
if Self.buf[i] <= lossRatio*Self.buf[min] && Self.buf[i] > 0 {
success++
}
}
// counting all the data in the ring buf, except zero
successRate = float64(success) / float64(loss+success)
return
}

447
lib/mux/mux_test.go Normal file
View File

@ -0,0 +1,447 @@
package mux
import (
"bufio"
"fmt"
"github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/goroutine"
"io"
"log"
"net"
"net/http"
"net/http/httputil"
_ "net/http/pprof"
"strconv"
"testing"
"time"
"unsafe"
"github.com/astaxie/beego/logs"
)
var conn1 net.Conn
var conn2 net.Conn
func TestNewMux(t *testing.T) {
go func() {
http.ListenAndServe("0.0.0.0:8889", nil)
}()
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(3)
server()
client()
//poolConnCopy, _ := ants.NewPoolWithFunc(200000, common.copyConn, ants.WithNonblocking(false))
time.Sleep(time.Second * 3)
go func() {
m2 := NewMux(conn2, "tcp")
for {
//logs.Warn("npc starting accept")
c, err := m2.Accept()
if err != nil {
logs.Warn(err)
continue
}
//logs.Warn("npc accept success ")
c2, err := net.Dial("tcp", "127.0.0.1:80")
if err != nil {
logs.Warn(err)
c.Close()
continue
}
//c2.(*net.TCPConn).SetReadBuffer(0)
//c2.(*net.TCPConn).SetReadBuffer(0)
_ = goroutine.CopyConnsPool.Invoke(goroutine.NewConns(c, c2, nil))
//go func(c2 net.Conn, c *conn) {
// wg := new(sync.WaitGroup)
// wg.Add(2)
// _ = poolConnCopy.Invoke(common.newConnGroup(c2, c, wg))
// //go func() {
// // _, err = common.CopyBuffer(c2, c)
// // if err != nil {
// // c2.Close()
// // c.Close()
// // //logs.Warn("close npc by copy from nps", err, c.connId)
// // }
// // wg.Done()
// //}()
// //wg.Add(1)
// _ = poolConnCopy.Invoke(common.newConnGroup(c, c2, wg))
// //go func() {
// // _, err = common.CopyBuffer(c, c2)
// // if err != nil {
// // c2.Close()
// // c.Close()
// // //logs.Warn("close npc by copy from server", err, c.connId)
// // }
// // wg.Done()
// //}()
// //logs.Warn("npc wait")
// wg.Wait()
//}(c2, c.(*conn))
}
}()
go func() {
m1 := NewMux(conn1, "tcp")
l, err := net.Listen("tcp", "127.0.0.1:7777")
if err != nil {
logs.Warn(err)
}
for {
//logs.Warn("nps starting accept")
conns, err := l.Accept()
if err != nil {
logs.Warn(err)
continue
}
//conns.(*net.TCPConn).SetReadBuffer(0)
//conns.(*net.TCPConn).SetReadBuffer(0)
//logs.Warn("nps accept success starting new conn")
tmpCpnn, err := m1.NewConn()
if err != nil {
logs.Warn("nps new conn err ", err)
continue
}
//logs.Warn("nps new conn success ", tmpCpnn.connId)
_ = goroutine.CopyConnsPool.Invoke(goroutine.NewConns(tmpCpnn, conns, nil))
//go func(tmpCpnn *conn, conns net.Conn) {
// wg := new(sync.WaitGroup)
// wg.Add(2)
// _ = poolConnCopy.Invoke(common.newConnGroup(tmpCpnn, conns, wg))
// //go func() {
// // _, err := common.CopyBuffer(tmpCpnn, conns)
// // if err != nil {
// // conns.Close()
// // tmpCpnn.Close()
// // //logs.Warn("close nps by copy from user", tmpCpnn.connId, err)
// // }
// //}()
// //wg.Add(1)
// _ = poolConnCopy.Invoke(common.newConnGroup(conns, tmpCpnn, wg))
// //time.Sleep(time.Second)
// //_, err = common.CopyBuffer(conns, tmpCpnn)
// //if err != nil {
// // conns.Close()
// // tmpCpnn.Close()
// // //logs.Warn("close nps by copy from npc ", tmpCpnn.connId, err)
// //}
// wg.Wait()
//}(tmpCpnn, conns)
}
}()
//go NewLogServer()
time.Sleep(time.Second * 5)
//for i := 0; i < 1; i++ {
// go test_raw(i)
//}
//test_request()
for {
time.Sleep(time.Second * 5)
}
}
func server() {
var err error
l, err := net.Listen("tcp", "127.0.0.1:9999")
if err != nil {
logs.Warn(err)
}
go func() {
conn1, err = l.Accept()
if err != nil {
logs.Warn(err)
}
}()
return
}
func client() {
var err error
conn2, err = net.Dial("tcp", "127.0.0.1:9999")
if err != nil {
logs.Warn(err)
}
}
func test_request() {
conn, _ := net.Dial("tcp", "127.0.0.1:7777")
for i := 0; i < 1000; i++ {
conn.Write([]byte(`GET / HTTP/1.1
Host: 127.0.0.1:7777
Connection: keep-alive
`))
r, err := http.ReadResponse(bufio.NewReader(conn), nil)
if err != nil {
logs.Warn("close by read response err", err)
break
}
logs.Warn("read response success", r)
b, err := httputil.DumpResponse(r, true)
if err != nil {
logs.Warn("close by dump response err", err)
break
}
fmt.Println(string(b[:20]), err)
//time.Sleep(time.Second)
}
logs.Warn("finish")
}
func test_raw(k int) {
for i := 0; i < 1000; i++ {
ti := time.Now()
conn, err := net.Dial("tcp", "127.0.0.1:7777")
if err != nil {
logs.Warn("conn dial err", err)
}
tid := time.Now()
conn.Write([]byte(`GET /videojs5/video.js HTTP/1.1
Host: 127.0.0.1:7777
`))
tiw := time.Now()
buf := make([]byte, 3572)
n, err := io.ReadFull(conn, buf)
//n, err := conn.Read(buf)
if err != nil {
logs.Warn("close by read response err", err)
break
}
logs.Warn(n, string(buf[:50]), "\n--------------\n", string(buf[n-50:n]))
//time.Sleep(time.Second)
err = conn.Close()
if err != nil {
logs.Warn("close conn err ", err)
}
now := time.Now()
du := now.Sub(ti).Seconds()
dud := now.Sub(tid).Seconds()
duw := now.Sub(tiw).Seconds()
if du > 1 {
logs.Warn("duration long", du, dud, duw, k, i)
}
if n != 3572 {
logs.Warn("n loss", n, string(buf))
}
}
logs.Warn("finish")
}
func TestNewConn(t *testing.T) {
buf := common.GetBufPoolCopy()
logs.Warn(len(buf), cap(buf))
//b := pool.GetBufPoolCopy()
//b[0] = 1
//b[1] = 2
//b[2] = 3
b := []byte{1, 2, 3}
logs.Warn(copy(buf[:3], b), len(buf), cap(buf))
logs.Warn(len(buf), buf[0])
}
func TestDQueue(t *testing.T) {
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(3)
d := new(bufDequeue)
d.vals = make([]unsafe.Pointer, 8)
go func() {
time.Sleep(time.Second)
for i := 0; i < 10; i++ {
logs.Warn(i)
logs.Warn(d.popTail())
}
}()
go func() {
time.Sleep(time.Second)
for i := 0; i < 10; i++ {
data := "test"
go logs.Warn(i, unsafe.Pointer(&data), d.pushHead(unsafe.Pointer(&data)))
}
}()
time.Sleep(time.Second * 3)
}
func TestChain(t *testing.T) {
go func() {
log.Println(http.ListenAndServe("0.0.0.0:8889", nil))
}()
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(3)
time.Sleep(time.Second * 5)
d := new(bufChain)
d.new(256)
go func() {
time.Sleep(time.Second)
for i := 0; i < 30000; i++ {
unsa, ok := d.popTail()
str := (*string)(unsa)
if ok {
fmt.Println(i, str, *str, ok)
//logs.Warn(i, str, *str, ok)
} else {
fmt.Println("nil", i, ok)
//logs.Warn("nil", i, ok)
}
}
}()
go func() {
time.Sleep(time.Second)
for i := 0; i < 3000; i++ {
go func(i int) {
for n := 0; n < 10; n++ {
data := "test " + strconv.Itoa(i) + strconv.Itoa(n)
fmt.Println(data, unsafe.Pointer(&data))
//logs.Warn(data, unsafe.Pointer(&data))
d.pushHead(unsafe.Pointer(&data))
}
}(i)
}
}()
time.Sleep(time.Second * 100000)
}
func TestFIFO(t *testing.T) {
go func() {
log.Println(http.ListenAndServe("0.0.0.0:8889", nil))
}()
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(3)
time.Sleep(time.Second * 5)
d := new(ReceiveWindowQueue)
d.New()
go func() {
time.Sleep(time.Second)
for i := 0; i < 1001; i++ {
data, err := d.Pop()
if err == nil {
//fmt.Println(i, string(data.buf), err)
logs.Warn(i, string(data.Buf), err)
common.ListElementPool.Put(data)
} else {
//fmt.Println("err", err)
logs.Warn("err", err)
}
//logs.Warn(d.Len())
}
logs.Warn("pop finish")
}()
go func() {
time.Sleep(time.Second * 10)
for i := 0; i < 1000; i++ {
by := []byte("test " + strconv.Itoa(i) + " ") //
data, _ := NewListElement(by, uint16(len(by)), true)
//fmt.Println(string((*data).buf), data)
//logs.Warn(string((*data).buf), data)
d.Push(data)
}
}()
time.Sleep(time.Second * 100000)
}
func TestPriority(t *testing.T) {
go func() {
log.Println(http.ListenAndServe("0.0.0.0:8889", nil))
}()
logs.EnableFuncCallDepth(true)
logs.SetLogFuncCallDepth(3)
time.Sleep(time.Second * 5)
d := new(PriorityQueue)
d.New()
go func() {
time.Sleep(time.Second)
for i := 0; i < 360050; i++ {
data := d.Pop()
//fmt.Println(i, string(data.buf), err)
logs.Warn(i, string(data.Content), data)
}
logs.Warn("pop finish")
}()
go func() {
time.Sleep(time.Second * 10)
for i := 0; i < 30000; i++ {
go func(i int) {
for n := 0; n < 10; n++ {
data := new(common.MuxPackager)
by := []byte("test " + strconv.Itoa(i) + strconv.Itoa(n))
_ = data.NewPac(common.MUX_NEW_MSG_PART, int32(i), by)
//fmt.Println(string((*data).buf), data)
logs.Warn(string((*data).Content), data)
d.Push(data)
}
}(i)
go func(i int) {
data := new(common.MuxPackager)
_ = data.NewPac(common.MUX_NEW_CONN, int32(i), nil)
//fmt.Println(string((*data).buf), data)
logs.Warn(data)
d.Push(data)
}(i)
go func(i int) {
data := new(common.MuxPackager)
_ = data.NewPac(common.MUX_NEW_CONN_OK, int32(i), nil)
//fmt.Println(string((*data).buf), data)
logs.Warn(data)
d.Push(data)
}(i)
}
}()
time.Sleep(time.Second * 100000)
}
//func TestReceive(t *testing.T) {
// go func() {
// log.Println(http.ListenAndServe("0.0.0.0:8889", nil))
// }()
// logs.EnableFuncCallDepth(true)
// logs.SetLogFuncCallDepth(3)
// time.Sleep(time.Second * 5)
// mux := new(Mux)
// mux.bw.readBandwidth = float64(1*1024*1024)
// mux.latency = float64(1/1000)
// wind := new(ReceiveWindow)
// wind.New(mux)
// wind.
// go func() {
// time.Sleep(time.Second)
// for i := 0; i < 36000; i++ {
// data := d.Pop()
// //fmt.Println(i, string(data.buf), err)
// logs.Warn(i, string(data.Content), data)
// }
// }()
// go func() {
// time.Sleep(time.Second*10)
// for i := 0; i < 3000; i++ {
// go func(i int) {
// for n := 0; n < 10; n++{
// data := new(common.MuxPackager)
// by := []byte("test " + strconv.Itoa(i) + strconv.Itoa(n))
// _ = data.NewPac(common.MUX_NEW_MSG_PART, int32(i), by)
// //fmt.Println(string((*data).buf), data)
// logs.Warn(string((*data).Content), data)
// d.Push(data)
// }
// }(i)
// go func(i int) {
// data := new(common.MuxPackager)
// _ = data.NewPac(common.MUX_NEW_CONN, int32(i), nil)
// //fmt.Println(string((*data).buf), data)
// logs.Warn(data)
// d.Push(data)
// }(i)
// go func(i int) {
// data := new(common.MuxPackager)
// _ = data.NewPac(common.MUX_NEW_CONN_OK, int32(i), nil)
// //fmt.Println(string((*data).buf), data)
// logs.Warn(data)
// d.Push(data)
// }(i)
// }
// }()
// time.Sleep(time.Second * 100000)
//}

View File

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

View File

@ -1,4 +1,4 @@
package pmux
package mux
import (
"errors"

View File

@ -1,6 +1,6 @@
// This module is used for port reuse
// Distinguish client, web manager , HTTP and HTTPS according to the difference of protocol
package pmux
package mux
import (
"bufio"
@ -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,7 +89,6 @@ 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()
@ -124,7 +123,6 @@ 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 {
@ -133,13 +131,13 @@ func (pMux *PortMux) process(conn net.Conn) {
timer := time.NewTimer(ACCEPT_TIME_OUT)
select {
case <-timer.C:
case ch <- newPortConn(conn, rs, readMore):
case ch <- newPortConn(conn, rs):
}
}
func (pMux *PortMux) Close() error {
if pMux.isClose {
return errors.New("the port pmux has closed")
return errors.New("the port mux has closed")
}
pMux.isClose = true
close(pMux.clientConn)

View File

@ -1,4 +1,4 @@
package pmux
package mux
import (
"testing"

583
lib/mux/queue.go Normal file
View File

@ -0,0 +1,583 @@
package mux
import (
"errors"
"github.com/cnlh/nps/lib/common"
"io"
"math"
"runtime"
"sync"
"sync/atomic"
"time"
"unsafe"
)
type PriorityQueue struct {
highestChain *bufChain
middleChain *bufChain
lowestChain *bufChain
starving uint8
stop bool
cond *sync.Cond
}
func (Self *PriorityQueue) New() {
Self.highestChain = new(bufChain)
Self.highestChain.new(4)
Self.middleChain = new(bufChain)
Self.middleChain.new(32)
Self.lowestChain = new(bufChain)
Self.lowestChain.new(256)
locker := new(sync.Mutex)
Self.cond = sync.NewCond(locker)
}
func (Self *PriorityQueue) Push(packager *common.MuxPackager) {
//logs.Warn("push start")
Self.push(packager)
Self.cond.Broadcast()
//logs.Warn("push finish")
return
}
func (Self *PriorityQueue) push(packager *common.MuxPackager) {
switch packager.Flag {
case common.MUX_PING_FLAG, common.MUX_PING_RETURN:
Self.highestChain.pushHead(unsafe.Pointer(packager))
// the ping package need highest priority
// prevent ping calculation error
case common.MUX_NEW_CONN, common.MUX_NEW_CONN_OK, common.MUX_NEW_CONN_Fail:
// the new conn package need some priority too
Self.middleChain.pushHead(unsafe.Pointer(packager))
default:
Self.lowestChain.pushHead(unsafe.Pointer(packager))
}
}
const maxStarving uint8 = 8
func (Self *PriorityQueue) Pop() (packager *common.MuxPackager) {
var iter bool
for {
packager = Self.TryPop()
if packager != nil {
return
}
if Self.stop {
return
}
if iter {
break
// trying to pop twice
}
iter = true
runtime.Gosched()
}
Self.cond.L.Lock()
defer Self.cond.L.Unlock()
for packager = Self.TryPop(); packager == nil; {
if Self.stop {
return
}
//logs.Warn("queue into wait")
Self.cond.Wait()
// wait for it with no more iter
packager = Self.TryPop()
//logs.Warn("queue wait finish", packager)
}
return
}
func (Self *PriorityQueue) TryPop() (packager *common.MuxPackager) {
ptr, ok := Self.highestChain.popTail()
if ok {
packager = (*common.MuxPackager)(ptr)
return
}
if Self.starving < maxStarving {
// not pop too much, lowestChain will wait too long
ptr, ok = Self.middleChain.popTail()
if ok {
packager = (*common.MuxPackager)(ptr)
Self.starving++
return
}
}
ptr, ok = Self.lowestChain.popTail()
if ok {
packager = (*common.MuxPackager)(ptr)
if Self.starving > 0 {
Self.starving = uint8(Self.starving / 2)
}
return
}
if Self.starving > 0 {
ptr, ok = Self.middleChain.popTail()
if ok {
packager = (*common.MuxPackager)(ptr)
Self.starving++
return
}
}
return
}
func (Self *PriorityQueue) Stop() {
Self.stop = true
Self.cond.Broadcast()
}
type ConnQueue struct {
chain *bufChain
starving uint8
stop bool
cond *sync.Cond
}
func (Self *ConnQueue) New() {
Self.chain = new(bufChain)
Self.chain.new(32)
locker := new(sync.Mutex)
Self.cond = sync.NewCond(locker)
}
func (Self *ConnQueue) Push(connection *conn) {
Self.chain.pushHead(unsafe.Pointer(connection))
Self.cond.Broadcast()
return
}
func (Self *ConnQueue) Pop() (connection *conn) {
var iter bool
for {
connection = Self.TryPop()
if connection != nil {
return
}
if Self.stop {
return
}
if iter {
break
// trying to pop twice
}
iter = true
runtime.Gosched()
}
Self.cond.L.Lock()
defer Self.cond.L.Unlock()
for connection = Self.TryPop(); connection == nil; {
if Self.stop {
return
}
//logs.Warn("queue into wait")
Self.cond.Wait()
// wait for it with no more iter
connection = Self.TryPop()
//logs.Warn("queue wait finish", packager)
}
return
}
func (Self *ConnQueue) TryPop() (connection *conn) {
ptr, ok := Self.chain.popTail()
if ok {
connection = (*conn)(ptr)
return
}
return
}
func (Self *ConnQueue) Stop() {
Self.stop = true
Self.cond.Broadcast()
}
func NewListElement(buf []byte, l uint16, part bool) (element *common.ListElement, err error) {
if uint16(len(buf)) != l {
err = errors.New("ListElement: buf length not match")
return
}
//if l == 0 {
// logs.Warn("push zero")
//}
element = common.ListElementPool.Get()
element.Buf = buf
element.L = l
element.Part = part
return
}
type ReceiveWindowQueue struct {
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
}
func (Self *ReceiveWindowQueue) New() {
Self.readOp = make(chan struct{})
Self.chain = new(bufChain)
Self.chain.new(64)
Self.stopOp = make(chan struct{}, 2)
}
func (Self *ReceiveWindowQueue) Push(element *common.ListElement) {
var length, wait uint32
for {
ptrs := atomic.LoadUint64(&Self.lengthWait)
length, wait = Self.chain.head.unpack(ptrs)
length += uint32(element.L)
if atomic.CompareAndSwapUint64(&Self.lengthWait, ptrs, Self.chain.head.pack(length, 0)) {
break
}
// another goroutine change the length or into wait, make sure
}
//logs.Warn("window push before", Self.Len(), uint32(element.l), len(element.buf))
Self.chain.pushHead(unsafe.Pointer(element))
//logs.Warn("window push", Self.Len())
if wait == 1 {
Self.allowPop()
}
return
}
func (Self *ReceiveWindowQueue) Pop() (element *common.ListElement, err error) {
var length uint32
startPop:
ptrs := atomic.LoadUint64(&Self.lengthWait)
length, _ = Self.chain.head.unpack(ptrs)
if length == 0 {
if !atomic.CompareAndSwapUint64(&Self.lengthWait, ptrs, Self.chain.head.pack(0, 1)) {
goto startPop // another goroutine is pushing
}
err = Self.waitPush()
// there is no more data in queue, wait for it
if err != nil {
return
}
goto startPop // wait finish, trying to get the new status
}
// length is not zero, so try to pop
for {
element = Self.TryPop()
if element != nil {
return
}
runtime.Gosched() // another goroutine is still pushing
}
}
func (Self *ReceiveWindowQueue) TryPop() (element *common.ListElement) {
ptr, ok := Self.chain.popTail()
if ok {
//logs.Warn("window pop before", Self.Len())
element = (*common.ListElement)(ptr)
atomic.AddUint64(&Self.lengthWait, ^(uint64(element.L)<<dequeueBits - 1))
//logs.Warn("window pop", Self.Len(), uint32(element.l))
return
}
return nil
}
func (Self *ReceiveWindowQueue) allowPop() (closed bool) {
//logs.Warn("allow pop", Self.Len())
select {
case Self.readOp <- struct{}{}:
return false
case <-Self.stopOp:
return true
}
}
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
}
timer := time.NewTimer(t)
defer timer.Stop()
//logs.Warn("queue into wait")
select {
case <-Self.readOp:
//logs.Warn("queue wait finish")
return nil
case <-Self.stopOp:
err = io.EOF
return
case <-timer.C:
err = errors.New("mux.queue: read time out")
return
}
}
func (Self *ReceiveWindowQueue) Len() (n uint32) {
ptrs := atomic.LoadUint64(&Self.lengthWait)
n, _ = Self.chain.head.unpack(ptrs)
return
}
func (Self *ReceiveWindowQueue) Stop() {
Self.stopOp <- struct{}{}
Self.stopOp <- struct{}{}
}
func (Self *ReceiveWindowQueue) SetTimeOut(t time.Time) {
Self.timeout = t
}
// https://golang.org/src/sync/poolqueue.go
type bufDequeue struct {
// headTail packs together a 32-bit head index and a 32-bit
// tail index. Both are indexes into vals modulo len(vals)-1.
//
// tail = index of oldest data in queue
// head = index of next slot to fill
//
// Slots in the range [tail, head) are owned by consumers.
// A consumer continues to own a slot outside this range until
// it nils the slot, at which point ownership passes to the
// producer.
//
// The head index is stored in the most-significant bits so
// that we can atomically add to it and the overflow is
// harmless.
headTail uint64
// vals is a ring buffer of interface{} values stored in this
// dequeue. The size of this must be a power of 2.
//
// A slot is still in use until *both* the tail
// index has moved beyond it and typ has been set to nil. This
// is set to nil atomically by the consumer and read
// atomically by the producer.
vals []unsafe.Pointer
starving uint32
}
const dequeueBits = 32
// dequeueLimit is the maximum size of a bufDequeue.
//
// This must be at most (1<<dequeueBits)/2 because detecting fullness
// depends on wrapping around the ring buffer without wrapping around
// the index. We divide by 4 so this fits in an int on 32-bit.
const dequeueLimit = (1 << dequeueBits) / 4
func (d *bufDequeue) unpack(ptrs uint64) (head, tail uint32) {
const mask = 1<<dequeueBits - 1
head = uint32((ptrs >> dequeueBits) & mask)
tail = uint32(ptrs & mask)
return
}
func (d *bufDequeue) pack(head, tail uint32) uint64 {
const mask = 1<<dequeueBits - 1
return (uint64(head) << dequeueBits) |
uint64(tail&mask)
}
// pushHead adds val at the head of the queue. It returns false if the
// queue is full.
func (d *bufDequeue) pushHead(val unsafe.Pointer) bool {
var slot *unsafe.Pointer
var starve uint8
if atomic.LoadUint32(&d.starving) > 0 {
runtime.Gosched()
}
for {
ptrs := atomic.LoadUint64(&d.headTail)
head, tail := d.unpack(ptrs)
if (tail+uint32(len(d.vals)))&(1<<dequeueBits-1) == head {
// Queue is full.
return false
}
ptrs2 := d.pack(head+1, tail)
if atomic.CompareAndSwapUint64(&d.headTail, ptrs, ptrs2) {
slot = &d.vals[head&uint32(len(d.vals)-1)]
if starve >= 3 && atomic.LoadUint32(&d.starving) > 0 {
atomic.StoreUint32(&d.starving, 0)
}
break
}
starve++
if starve >= 3 {
atomic.StoreUint32(&d.starving, 1)
}
}
// The head slot is free, so we own it.
*slot = val
return true
}
// popTail removes and returns the element at the tail of the queue.
// It returns false if the queue is empty. It may be called by any
// number of consumers.
func (d *bufDequeue) popTail() (unsafe.Pointer, bool) {
ptrs := atomic.LoadUint64(&d.headTail)
head, tail := d.unpack(ptrs)
if tail == head {
// Queue is empty.
return nil, false
}
slot := &d.vals[tail&uint32(len(d.vals)-1)]
var val unsafe.Pointer
for {
val = atomic.LoadPointer(slot)
if val != nil {
// We now own slot.
break
}
// Another goroutine is still pushing data on the tail.
}
// Tell pushHead that we're done with this slot. Zeroing the
// slot is also important so we don't leave behind references
// that could keep this object live longer than necessary.
//
// We write to val first and then publish that we're done with
atomic.StorePointer(slot, nil)
// At this point pushHead owns the slot.
if tail < math.MaxUint32 {
atomic.AddUint64(&d.headTail, 1)
} else {
atomic.AddUint64(&d.headTail, ^uint64(math.MaxUint32-1))
}
return val, true
}
// bufChain is a dynamically-sized version of bufDequeue.
//
// This is implemented as a doubly-linked list queue of poolDequeues
// where each dequeue is double the size of the previous one. Once a
// dequeue fills up, this allocates a new one and only ever pushes to
// the latest dequeue. Pops happen from the other end of the list and
// once a dequeue is exhausted, it gets removed from the list.
type bufChain struct {
// head is the bufDequeue to push to. This is only accessed
// by the producer, so doesn't need to be synchronized.
head *bufChainElt
// tail is the bufDequeue to popTail from. This is accessed
// by consumers, so reads and writes must be atomic.
tail *bufChainElt
newChain uint32
}
type bufChainElt struct {
bufDequeue
// next and prev link to the adjacent poolChainElts in this
// bufChain.
//
// next is written atomically by the producer and read
// atomically by the consumer. It only transitions from nil to
// non-nil.
//
// prev is written atomically by the consumer and read
// atomically by the producer. It only transitions from
// non-nil to nil.
next, prev *bufChainElt
}
func storePoolChainElt(pp **bufChainElt, v *bufChainElt) {
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(pp)), unsafe.Pointer(v))
}
func loadPoolChainElt(pp **bufChainElt) *bufChainElt {
return (*bufChainElt)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(pp))))
}
func (c *bufChain) new(initSize int) {
// Initialize the chain.
// initSize must be a power of 2
d := new(bufChainElt)
d.vals = make([]unsafe.Pointer, initSize)
storePoolChainElt(&c.head, d)
storePoolChainElt(&c.tail, d)
}
func (c *bufChain) pushHead(val unsafe.Pointer) {
startPush:
for {
if atomic.LoadUint32(&c.newChain) > 0 {
runtime.Gosched()
} else {
break
}
}
d := loadPoolChainElt(&c.head)
if d.pushHead(val) {
return
}
// The current dequeue is full. Allocate a new one of twice
// the size.
if atomic.CompareAndSwapUint32(&c.newChain, 0, 1) {
newSize := len(d.vals) * 2
if newSize >= dequeueLimit {
// Can't make it any bigger.
newSize = dequeueLimit
}
d2 := &bufChainElt{prev: d}
d2.vals = make([]unsafe.Pointer, newSize)
d2.pushHead(val)
storePoolChainElt(&c.head, d2)
storePoolChainElt(&d.next, d2)
atomic.StoreUint32(&c.newChain, 0)
return
}
goto startPush
}
func (c *bufChain) popTail() (unsafe.Pointer, bool) {
d := loadPoolChainElt(&c.tail)
if d == nil {
return nil, false
}
for {
// It's important that we load the next pointer
// *before* popping the tail. In general, d may be
// transiently empty, but if next is non-nil before
// the TryPop and the TryPop fails, then d is permanently
// empty, which is the only condition under which it's
// safe to drop d from the chain.
d2 := loadPoolChainElt(&d.next)
if val, ok := d.popTail(); ok {
return val, ok
}
if d2 == nil {
// This is the only dequeue. It's empty right
// now, but could be pushed to in the future.
return nil, false
}
// The tail of the chain has been drained, so move on
// to the next dequeue. Try to drop it from the chain
// so the next TryPop doesn't have to look at the empty
// dequeue again.
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.tail)), unsafe.Pointer(d), unsafe.Pointer(d2)) {
// We won the race. Clear the prev pointer so
// the garbage collector can collect the empty
// dequeue and so popHead doesn't back up
// further than necessary.
storePoolChainElt(&d2.prev, nil)
}
d = d2
}
}

154
lib/mux/web.go Normal file
View File

@ -0,0 +1,154 @@
package mux
import (
"fmt"
"github.com/astaxie/beego/logs"
"net/http"
"sort"
"strconv"
"strings"
"sync"
"time"
)
type connLog struct {
startTime time.Time
isClose bool
logs []string
}
var logms map[int]*connLog
var logmc map[int]*connLog
var copyMaps map[int]*connLog
var copyMapc map[int]*connLog
var stashTimeNow time.Time
var mutex sync.Mutex
func deepCopyMaps() {
copyMaps = make(map[int]*connLog)
for k, v := range logms {
copyMaps[k] = &connLog{
startTime: v.startTime,
isClose: v.isClose,
logs: v.logs,
}
}
}
func deepCopyMapc() {
copyMapc = make(map[int]*connLog)
for k, v := range logmc {
copyMapc[k] = &connLog{
startTime: v.startTime,
isClose: v.isClose,
logs: v.logs,
}
}
}
func init() {
logms = make(map[int]*connLog)
logmc = make(map[int]*connLog)
}
type IntSlice []int
func (s IntSlice) Len() int { return len(s) }
func (s IntSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s IntSlice) Less(i, j int) bool { return s[i] < s[j] }
func NewLogServer() {
http.HandleFunc("/", index)
http.HandleFunc("/detail", detail)
http.HandleFunc("/stash", stash)
fmt.Println(http.ListenAndServe(":8899", nil))
}
func stash(w http.ResponseWriter, r *http.Request) {
stashTimeNow = time.Now()
deepCopyMaps()
deepCopyMapc()
w.Write([]byte("ok"))
}
func getM(label string, id int) (cL *connLog) {
label = strings.TrimSpace(label)
mutex.Lock()
defer mutex.Unlock()
if label == "nps" {
cL = logms[id]
}
if label == "npc" {
cL = logmc[id]
}
return
}
func setM(label string, id int, cL *connLog) {
label = strings.TrimSpace(label)
mutex.Lock()
defer mutex.Unlock()
if label == "nps" {
logms[id] = cL
}
if label == "npc" {
logmc[id] = cL
}
}
func index(w http.ResponseWriter, r *http.Request) {
var keys []int
for k := range copyMaps {
keys = append(keys, k)
}
sort.Sort(IntSlice(keys))
var s string
s += "<h1>nps</h1>"
for _, v := range keys {
connL := copyMaps[v]
s += "<a href='/detail?id=" + strconv.Itoa(v) + "&label=nps" + "'>" + strconv.Itoa(v) + "</a>----------"
s += strconv.Itoa(int(stashTimeNow.Sub(connL.startTime).Milliseconds())) + "ms----------"
s += strconv.FormatBool(connL.isClose)
s += "<br>"
}
keys = keys[:0]
s += "<h1>npc</h1>"
for k := range copyMapc {
keys = append(keys, k)
}
sort.Sort(IntSlice(keys))
for _, v := range keys {
connL := copyMapc[v]
s += "<a href='/detail?id=" + strconv.Itoa(v) + "&label=npc" + "'>" + strconv.Itoa(v) + "</a>----------"
s += strconv.Itoa(int(stashTimeNow.Sub(connL.startTime).Milliseconds())) + "ms----------"
s += strconv.FormatBool(connL.isClose)
s += "<br>"
}
w.Write([]byte(s))
}
func detail(w http.ResponseWriter, r *http.Request) {
id := r.FormValue("id")
label := r.FormValue("label")
logs.Warn(label)
i, _ := strconv.Atoi(id)
var v *connLog
if label == "nps" {
v, _ = copyMaps[i]
}
if label == "npc" {
v, _ = copyMapc[i]
}
var s string
if v != nil {
for i, vv := range v.logs {
s += "<p>" + strconv.Itoa(i+1) + ":" + vv + "</p>"
}
}
w.Write([]byte(s))
}

7
lib/mux/web_test.go Normal file
View File

@ -0,0 +1,7 @@
package mux
import "testing"
func TestWeb(t *testing.T) {
NewLogServer()
}

View File

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

View File

@ -5,12 +5,12 @@ import (
"os"
"strconv"
"ehang.io/nps/lib/pmux"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/lib/mux"
)
var pMux *pmux.PortMux
var pMux *mux.PortMux
var bridgePort string
var httpsPort string
var httpPort string
@ -28,7 +28,7 @@ func InitConnectionService() {
logs.Error(err)
os.Exit(0)
}
pMux = pmux.NewPortMux(port, beego.AppConfig.String("web_host"))
pMux = mux.NewPortMux(port, beego.AppConfig.String("web_host"))
}
}

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 {
@ -30,12 +30,11 @@ type httpServer struct {
httpsServer *http.Server
httpsListener net.Listener
useCache bool
addOrigin bool
cache *cache.Cache
cacheLen int
}
func NewHttp(bridge *bridge.Bridge, c *file.Tunnel, httpPort, httpsPort int, useCache bool, cacheLen int, addOrigin bool) *httpServer {
func NewHttp(bridge *bridge.Bridge, c *file.Tunnel, httpPort, httpsPort int, useCache bool, cacheLen int) *httpServer {
httpServer := &httpServer{
BaseServer: BaseServer{
task: c,
@ -46,7 +45,6 @@ func NewHttp(bridge *bridge.Bridge, c *file.Tunnel, httpPort, httpsPort int, use
httpsPort: httpsPort,
useCache: useCache,
cacheLen: cacheLen,
addOrigin: addOrigin,
}
if useCache {
httpServer.cache = cache.New(cacheLen)
@ -57,7 +55,7 @@ func NewHttp(bridge *bridge.Bridge, c *file.Tunnel, httpPort, httpsPort int, use
func (s *httpServer) Start() error {
var err error
if s.errorContent, err = common.ReadAllFromFile(filepath.Join(common.GetRunPath(), "web", "static", "page", "error.html")); err != nil {
s.errorContent = []byte("nps 404")
s.errorContent = []byte("easyProxy 404")
}
if s.httpPort > 0 {
s.httpServer = s.NewServer(s.httpPort, "http")
@ -118,6 +116,7 @@ func (s *httpServer) handleHttp(c *conn.Conn, r *http.Request) {
var (
host *file.Host
target net.Conn
lastHost *file.Host
err error
connClient io.ReadWriteCloser
scheme = r.URL.Scheme
@ -129,16 +128,11 @@ func (s *httpServer) handleHttp(c *conn.Conn, r *http.Request) {
)
defer func() {
if connClient != nil {
connClient.Close()
} else {
s.writeConnFail(c.Conn)
connClient.Close()
}
c.Close()
}()
reset:
if isReset {
host.Client.AddConn()
}
if host, err = file.GetDb().GetInfoByHost(r.Host, r); err != nil {
logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI)
return
@ -147,13 +141,12 @@ reset:
logs.Warn("client id %d, host id %d, error %s, when https connection", host.Client.Id, host.Id, err.Error())
return
}
if !isReset {
defer host.Client.AddConn()
}
defer host.Client.AddConn()
if err = s.auth(r, c, host.Client.Cnf.U, host.Client.Cnf.P); err != nil {
logs.Warn("auth error", err, r.RemoteAddr)
return
}
reset:
if targetAddr, err = host.Target.GetRandomTarget(); err != nil {
logs.Warn(err.Error())
return
@ -164,6 +157,7 @@ reset:
return
}
connClient = conn.GetConn(target, lk.Crypt, lk.Compress, host.Client.Rate, true)
lastHost = host
//read from inc-client
go func() {
@ -177,12 +171,11 @@ reset:
}
}()
for {
if resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != nil || resp == nil || r == nil {
// if there got broken pipe, http.ReadResponse will get a nil
if resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != 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 && r.URL != nil && strings.Contains(r.URL.Path, ".") {
if s.useCache && strings.Contains(r.URL.Path, ".") {
b, err := httputil.DumpResponse(resp, true)
if err != nil {
return
@ -221,7 +214,7 @@ reset:
}
//change the host and header and set proxy setting
common.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String(), s.addOrigin)
common.ChangeHostAndHeader(r, host.HostChange, host.HeaderChange, c.Conn.RemoteAddr().String())
logs.Trace("%s request, method %s, host %s, url %s, remote address %s, target %s", r.URL.Scheme, r.Method, r.Host, r.URL.Path, c.RemoteAddr().String(), lk.Host)
//write
lenConn = conn.NewLenConn(connClient)
@ -242,8 +235,9 @@ reset:
if hostTmp, err := file.GetDb().GetInfoByHost(r.Host, r); err != nil {
logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI)
break
} else if host != hostTmp {
} else if host != lastHost {
host = hostTmp
lastHost = host
isReset = true
connClient.Close()
goto reset

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,130 +154,27 @@ 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) {
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
/*
+----+------+------+----------+----------+----------+
|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)
}
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)
defer target.Close()
for {
_, err := c.Read(b)
if err != nil {
c.Close()
return
}
}
s.doConnect(c, associateMethod)
}
//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,23 +63,15 @@ func (s *WebServer) Start() error {
<-stop
}
beego.BConfig.WebConfig.Session.SessionOn = true
beego.SetStaticPath(beego.AppConfig.String("web_base_url")+"/static", filepath.Join(common.GetRunPath(), "web", "static"))
beego.SetStaticPath("/static", filepath.Join(common.GetRunPath(), "web", "static"))
beego.SetViewsPath(filepath.Join(common.GetRunPath(), "web", "views"))
err := errors.New("Web management startup failure ")
var l net.Listener
if l, err = connection.GetWebManagerListener(); err == nil {
if l, err := connection.GetWebManagerListener(); err == nil {
beego.InitBeforeHTTPRun()
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)
}
http.Serve(l, beego.BeeApp.Handlers)
} else {
logs.Error(err)
}
return err
return errors.New("Web management startup failure")
}
func (s *WebServer) Close() error {

View File

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

View File

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

View File

@ -1,22 +1,18 @@
package proxy
import (
"io"
"net"
"strings"
"sync"
"time"
"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 {
BaseServer
addrMap sync.Map
listener *net.UDPConn
}
@ -37,8 +33,8 @@ func (s *UdpModeServer) Start() error {
if err != nil {
return err
}
buf := common.BufPoolUdp.Get().([]byte)
for {
buf := common.BufPoolUdp.Get().([]byte)
n, addr, err := s.listener.ReadFromUDP(buf)
if err != nil {
if strings.Contains(err.Error(), "use of closed network connection") {
@ -53,43 +49,26 @@ func (s *UdpModeServer) Start() error {
}
func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
if v, ok := s.addrMap.Load(addr.String()); ok {
clientConn, ok := v.(io.ReadWriteCloser)
if ok {
clientConn.Write(data)
s.task.Flow.Add(int64(len(data)), 0)
}
if err := s.CheckFlowAndConnNum(s.task.Client); err != nil {
logs.Warn("client id %d, task id %d,error %s, when udp connection", s.task.Client.Id, s.task.Id, err.Error())
return
}
defer s.task.Client.AddConn()
link := conn.NewLink(common.CONN_UDP, s.task.Target.TargetStr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, addr.String(), s.task.Target.LocalProxy)
if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task); err != nil {
return
} else {
if err := s.CheckFlowAndConnNum(s.task.Client); err != nil {
logs.Warn("client id %d, task id %d,error %s, when udp connection", s.task.Client.Id, s.task.Id, err.Error())
return
}
defer s.task.Client.AddConn()
link := conn.NewLink(common.CONN_UDP, s.task.Target.TargetStr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, addr.String(), s.task.Target.LocalProxy)
if clientConn, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task); err != nil {
s.task.Flow.Add(int64(len(data)), 0)
buf := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(buf)
target.Write(data)
s.task.Flow.Add(int64(len(data)), 0)
if n, err := target.Read(buf); err != nil {
logs.Warn(err)
return
} else {
target := conn.GetConn(clientConn, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, nil, true)
s.addrMap.Store(addr.String(), target)
defer target.Close()
target.Write(data)
buf := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(buf)
s.task.Flow.Add(int64(len(data)), 0)
for {
clientConn.SetReadDeadline(time.Now().Add(time.Minute * 10))
if n, err := target.Read(buf); err != nil {
s.addrMap.Delete(addr.String())
logs.Warn(err)
return
} else {
s.listener.WriteTo(buf[:n], addr)
s.task.Flow.Add(0, int64(n))
}
}
s.listener.WriteTo(buf[:n], addr)
s.task.Flow.Add(0, int64(n))
}
}
}

View File

@ -1,35 +1,33 @@
package server
import (
"ehang.io/nps/lib/version"
"errors"
"math"
"os"
"strconv"
"strings"
"sync"
"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/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
"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"
"github.com/shirou/gopsutil/net"
)
var (
Bridge *bridge.Bridge
RunList sync.Map //map[int]interface{}
RunList map[int]interface{}
)
func init() {
RunList = sync.Map{}
RunList = make(map[int]interface{})
}
//init task from db
@ -38,8 +36,7 @@ func InitFromCsv() {
if vkey := beego.AppConfig.String("public_vkey"); vkey != "" {
c := file.NewClient(vkey, true, true)
file.GetDb().NewClient(c)
RunList.Store(c.Id, nil)
//RunList[c.Id] = nil
RunList[c.Id] = nil
}
//Initialize services in server-side files
file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool {
@ -85,8 +82,8 @@ func DealBridgeTask() {
}
//start a new server
func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string, bridgeDisconnect int) {
Bridge = bridge.NewTunnel(bridgePort, bridgeType, common.GetBoolByStr(beego.AppConfig.String("ip_limit")), RunList, bridgeDisconnect)
func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string) {
Bridge = bridge.NewTunnel(bridgePort, bridgeType, common.GetBoolByStr(beego.AppConfig.String("ip_limit")), RunList)
go func() {
if err := Bridge.StartTunnel(); err != nil {
logs.Error("start server bridge error", err)
@ -104,8 +101,7 @@ func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string, bridgeD
if err := svr.Start(); err != nil {
logs.Error(err)
}
RunList.Store(cnf.Id, svr)
//RunList[cnf.Id] = svr
RunList[cnf.Id] = svr
} else {
logs.Error("Incorrect startup mode %s", cnf.Mode)
}
@ -113,7 +109,6 @@ func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string, bridgeD
func dealClientFlow() {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@ -150,16 +145,14 @@ func NewMode(Bridge *bridge.Bridge, c *file.Tunnel) proxy.Service {
httpsPort, _ := beego.AppConfig.Int("https_proxy_port")
useCache, _ := beego.AppConfig.Bool("http_cache")
cacheLen, _ := beego.AppConfig.Int("http_cache_length")
addOrigin, _ := beego.AppConfig.Bool("http_add_origin_header")
service = proxy.NewHttp(Bridge, c, httpPort, httpsPort, useCache, cacheLen, addOrigin)
service = proxy.NewHttp(Bridge, c, httpPort, httpsPort, useCache, cacheLen)
}
return service
}
//stop server
func StopServer(id int) error {
//if v, ok := RunList[id]; ok {
if v, ok := RunList.Load(id); ok {
if v, ok := RunList[id]; ok {
if svr, ok := v.(proxy.Service); ok {
if err := svr.Close(); err != nil {
return err
@ -174,8 +167,7 @@ func StopServer(id int) error {
t.Status = false
file.GetDb().UpdateTask(t)
}
//delete(RunList, id)
RunList.Delete(id)
delete(RunList, id)
return nil
}
return errors.New("task is not running")
@ -185,8 +177,7 @@ func StopServer(id int) error {
func AddTask(t *file.Tunnel) error {
if t.Mode == "secret" || t.Mode == "p2p" {
logs.Info("secret task %s start ", t.Remark)
//RunList[t.Id] = nil
RunList.Store(t.Id, nil)
RunList[t.Id] = nil
return nil
}
if b := tool.TestServerPort(t.Port, t.Mode); !b && t.Mode != "httpHostServer" {
@ -198,13 +189,11 @@ func AddTask(t *file.Tunnel) error {
}
if svr := NewMode(Bridge, t); svr != nil {
logs.Info("tunnel task %s start mode%s port %d", t.Remark, t.Mode, t.Port)
//RunList[t.Id] = svr
RunList.Store(t.Id, svr)
RunList[t.Id] = svr
go func() {
if err := svr.Start(); err != nil {
logs.Error("clientId %d taskId %d start error %s", t.Client.Id, t.Id, err)
//delete(RunList, t.Id)
RunList.Delete(t.Id)
delete(RunList, t.Id)
return
}
}()
@ -228,8 +217,7 @@ func StartTask(id int) error {
//delete task
func DelTask(id int) error {
//if _, ok := RunList[id]; ok {
if _, ok := RunList.Load(id); ok {
if _, ok := RunList[id]; ok {
if err := StopServer(id); err != nil {
return err
}
@ -259,8 +247,7 @@ func GetTunnel(start, length int, typeVal string, clientId int, search string) (
}
if start--; start < 0 {
if length--; length >= 0 {
//if _, ok := RunList[v.Id]; ok {
if _, ok := RunList.Load(v.Id); ok {
if _, ok := RunList[v.Id]; ok {
v.RunStatus = true
} else {
v.RunStatus = false
@ -283,9 +270,8 @@ 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 vv, ok := Bridge.Client.Load(v.Id); ok {
if _, ok := Bridge.Client.Load(v.Id); ok {
v.IsConnect = true
v.Version = vv.(*bridge.Client).Version
} else {
v.IsConnect = false
}
@ -351,12 +337,8 @@ 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)
if beego.AppConfig.String("public_vkey") != "" { //remove public vkey
data["clientCount"] = data["clientCount"].(int) - 1
}
data["clientCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Clients) - 1 //Remove the public key client
dealClientData()
c := 0
var in, out int64
@ -448,7 +430,6 @@ 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() {
@ -52,10 +52,10 @@ func TestServerConfig() {
if port, err := strconv.Atoi(p); err != nil {
log.Fatalln("get https port error", err)
} else {
if beego.AppConfig.String("pemPath") != "" && !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("pemPath"))) {
if !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("pemPath"))) {
log.Fatalf("ssl certFile %s is not exist", beego.AppConfig.String("pemPath"))
}
if beego.AppConfig.String("keyPath") != "" && !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("keyPath"))) {
if !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("ketPath"))) {
log.Fatalf("ssl keyFile %s is not exist", beego.AppConfig.String("pemPath"))
}
isInArr(&postTcpArr, port, "http port", "tcp")

View File

@ -5,12 +5,12 @@ import (
"strconv"
"time"
"ehang.io/nps/lib/common"
"github.com/astaxie/beego"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
"github.com/cnlh/nps/lib/common"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/net"
)
var (
@ -31,9 +31,6 @@ 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 {

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