Merge pull request #291 from cnlh/dev

Fix bugs and optimize functions
pull/302/head v0.25.0
ffdfgdfg 2019-12-05 22:44:37 +08:00 committed by GitHub
commit f97a9176e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1180 additions and 211 deletions

57
.travis.yml Normal file
View File

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

View File

@ -2,6 +2,7 @@
# nps # nps
![](https://img.shields.io/github/stars/cnlh/nps.svg) ![](https://img.shields.io/github/forks/cnlh/nps.svg) ![](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) [![Gitter](https://badges.gitter.im/cnlh-nps/community.svg)](https://gitter.im/cnlh-nps/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Build Status](https://travis-ci.org/cnlh/nps.svg?branch=master)](https://travis-ci.org/cnlh/nps)
nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何**tcp、udp**上层协议访问内网网站、本地支付接口调试、ssh访问、远程桌面内网dns解析等等……此外还**支持内网http代理、内网socks5代理**、**p2p等**并带有功能强大的web管理端。 nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何**tcp、udp**上层协议访问内网网站、本地支付接口调试、ssh访问、远程桌面内网dns解析等等……此外还**支持内网http代理、内网socks5代理**、**p2p等**并带有功能强大的web管理端。
@ -47,6 +48,7 @@ nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务
* [配置文件说明](#服务端配置文件) * [配置文件说明](#服务端配置文件)
* [使用https](#使用https) * [使用https](#使用https)
* [与nginx配合](#与nginx配合) * [与nginx配合](#与nginx配合)
* [web使用Caddy代理](#web使用Caddy代理)
* [关闭http|https代理](#关闭代理) * [关闭http|https代理](#关闭代理)
* [将nps安装到系统](#将nps安装到系统) * [将nps安装到系统](#将nps安装到系统)
* [流量数据持久化](#流量数据持久化) * [流量数据持久化](#流量数据持久化)
@ -317,6 +319,7 @@ nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务
web_port | web管理端口 web_port | web管理端口
web_password | web界面管理密码 web_password | web界面管理密码
web_username | web界面管理账号 web_username | web界面管理账号
web_base_url | web管理主路径,用于将web管理置于代理子路径后面
bridge_port | 服务端客户端通信端口 bridge_port | 服务端客户端通信端口
https_proxy_port | 域名代理https代理监听端口 https_proxy_port | 域名代理https代理监听端口
http_proxy_port | 域名代理http代理监听端口 http_proxy_port | 域名代理http代理监听端口
@ -375,6 +378,29 @@ server {
} }
} }
``` ```
### 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设置为空。 如需关闭http代理可在配置文件中将http_proxy_port设置为空如需关闭https代理可在配置文件中将https_proxy_port设置为空。

View File

@ -27,14 +27,16 @@ type Client struct {
tunnel *mux.Mux tunnel *mux.Mux
signal *conn.Conn signal *conn.Conn
file *mux.Mux file *mux.Mux
Version string
retryTime int // it will be add 1 when ping not ok until to 3 will close the client retryTime int // it will be add 1 when ping not ok until to 3 will close the client
} }
func NewClient(t, f *mux.Mux, s *conn.Conn) *Client { func NewClient(t, f *mux.Mux, s *conn.Conn, vs string) *Client {
return &Client{ return &Client{
signal: s, signal: s,
tunnel: t, tunnel: t,
file: f, file: f,
Version: vs,
} }
} }
@ -166,16 +168,23 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
return return
} }
//version check //version check
if b, err := c.GetShortContent(32); err != nil || string(b) != crypt.Md5(version.GetVersion()) { if b, err := c.GetShortLenContent(); err != nil || string(b) != version.GetVersion() {
logs.Info("The client %s version does not match", c.Conn.RemoteAddr()) logs.Info("The client %s version does not match", c.Conn.RemoteAddr())
c.Close() c.Close()
return 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 //write server version to client
c.Write([]byte(crypt.Md5(version.GetVersion()))) c.Write([]byte(crypt.Md5(version.GetVersion())))
c.SetReadDeadlineBySecond(5) c.SetReadDeadlineBySecond(5)
var buf []byte var buf []byte
var err error
//get vKey from client //get vKey from client
if buf, err = c.GetShortContent(32); err != nil { if buf, err = c.GetShortContent(32); err != nil {
c.Close() c.Close()
@ -191,7 +200,7 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
s.verifySuccess(c) s.verifySuccess(c)
} }
if flag, err := c.ReadFlag(); err == nil { if flag, err := c.ReadFlag(); err == nil {
s.typeDeal(flag, c, id) s.typeDeal(flag, c, id, string(vs))
} else { } else {
logs.Warn(err, flag) logs.Warn(err, flag)
} }
@ -214,7 +223,7 @@ func (s *Bridge) DelClient(id int) {
} }
//use different //use different
func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) { func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int, vs string) {
isPub := file.GetDb().IsPubClient(id) isPub := file.GetDb().IsPubClient(id)
switch typeVal { switch typeVal {
case common.WORK_MAIN: case common.WORK_MAIN:
@ -223,17 +232,18 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
return return
} }
//the vKey connect by another ,close the client of before //the vKey connect by another ,close the client of before
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, nil, c)); ok { if v, ok := s.Client.LoadOrStore(id, NewClient(nil, nil, c, vs)); ok {
if v.(*Client).signal != nil { if v.(*Client).signal != nil {
v.(*Client).signal.WriteClose() v.(*Client).signal.WriteClose()
} }
v.(*Client).signal = c v.(*Client).signal = c
v.(*Client).Version = vs
} }
go s.GetHealthFromClient(id, c) go s.GetHealthFromClient(id, c)
logs.Info("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr()) logs.Info("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr())
case common.WORK_CHAN: case common.WORK_CHAN:
muxConn := mux.NewMux(c.Conn, s.tunnelType) muxConn := mux.NewMux(c.Conn, s.tunnelType)
if v, ok := s.Client.LoadOrStore(id, NewClient(muxConn, nil, nil)); ok { if v, ok := s.Client.LoadOrStore(id, NewClient(muxConn, nil, nil, vs)); ok {
v.(*Client).tunnel = muxConn v.(*Client).tunnel = muxConn
} }
case common.WORK_CONFIG: case common.WORK_CONFIG:
@ -254,7 +264,7 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
} }
case common.WORK_FILE: case common.WORK_FILE:
muxConn := mux.NewMux(c.Conn, s.tunnelType) muxConn := mux.NewMux(c.Conn, s.tunnelType)
if v, ok := s.Client.LoadOrStore(id, NewClient(nil, muxConn, nil)); ok { if v, ok := s.Client.LoadOrStore(id, NewClient(nil, muxConn, nil, vs)); ok {
v.(*Client).file = muxConn v.(*Client).file = muxConn
} }
case common.WORK_P2P: case common.WORK_P2P:
@ -419,7 +429,7 @@ loop:
} }
c.WriteAddOk() c.WriteAddOk()
c.Write([]byte(client.VerifyKey)) 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: case common.NEW_HOST:
h, err := c.GetHostInfo() h, err := c.GetHostInfo()

136
build.bash Executable file
View File

@ -0,0 +1,136 @@
#!/bin/bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf linux_amd64_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf linux_386_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf freebsd_386_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf freebsd_amd64_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=freebsd GOARCH=arm go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf freebsd_arm_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf linux_arm_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf linux_arm64_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf linux_mips64_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf linux_mips64le_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf linux_mipsle_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf linux_mips_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf win_386_client.tar.gz npc.exe conf/npc.conf
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf win_amd64_client.tar.gz npc.exe conf/npc.conf
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
upx npc
tar -czvf macos_client.tar.gz npc conf/npc.conf
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
upx nps
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
upx nps
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 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
upx nps
tar -czvf linux_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=linux GOARCH=arm64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/nps/nps.go
upx nps
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
upx nps
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
upx nps
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
upx nps
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
upx nps
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
upx nps
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
upx nps
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
upx nps
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
upx nps
tar -czvf macos_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
upx nps.exe
tar -czvf win_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
upx nps.exe
tar -czvf win_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

178
build.sh Normal file
View File

@ -0,0 +1,178 @@
#/bash/sh
sudo apt-get install gcc-mingw-w64-i686
env GOOS=windows GOARCH=386 CGO_ENABLED=1 CC=i686-w64-mingw32-gcc go build -ldflags "-s -w -extldflags -static -extldflags -static" -buildmode=c-shared -o npc_sdk.dll cmd/npc/sdk.go
tar -czvf npc_sdk.tar.gz npc_sdk.dll npc_sdk.h
wget https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz
tar -xvf upx-3.95-amd64_linux.tar.xz
cp upx-3.95-amd64_linux/upx ./
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf linux_amd64_client.tar.gz npc conf/npc.conf
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
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
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
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
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
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
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
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
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
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
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
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
CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf win_386_client.tar.gz npc.exe conf/npc.conf
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf win_amd64_client.tar.gz npc.exe conf/npc.conf
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -extldflags -static -extldflags -static" ./cmd/npc/npc.go
tar -czvf macos_client.tar.gz npc conf/npc.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 macos_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 win_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 win_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
export VERSION=0.25.0
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
git clone https://github.com/cnlh/spksrc.git ~/spksrc
mkdir ~/spksrc/nps && cp -rf ./* ~/spksrc/nps/
docker run -itd --name spksrc --env VERSION=$VERSION -v ~/spksrc:/spksrc synocommunity/spksrc /bin/bash
docker exec -it spksrc /bin/bash -c 'cd /spksrc && make setup && cd /spksrc/spk/npc && make'
cp ~/spksrc/packages/npc_noarch-all_$VERSION-1.spk ./npc_syno.spk
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
export DOCKER_CLI_EXPERIMENTAL=enabled
docker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d
docker buildx create --use --name mybuilder
docker buildx build --tag ffdfgdfg/nps:$VERSION --output type=image,push=true --file Dockerfile.nps --platform=linux/amd64,linux/arm64,linux/386,linux/arm .
docker buildx build --tag ffdfgdfg/npc:$VERSION --output type=image,push=true --file Dockerfile.npc --platform=linux/amd64,linux/arm64,linux/386,linux/arm .

View File

@ -2,18 +2,20 @@ package client
import ( import (
"bufio" "bufio"
"bytes"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"time" "time"
"github.com/astaxie/beego/logs" "github.com/astaxie/beego/logs"
"github.com/xtaci/kcp-go"
"github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/common"
"github.com/cnlh/nps/lib/config" "github.com/cnlh/nps/lib/config"
"github.com/cnlh/nps/lib/conn" "github.com/cnlh/nps/lib/conn"
"github.com/cnlh/nps/lib/crypt" "github.com/cnlh/nps/lib/crypt"
"github.com/cnlh/nps/lib/mux" "github.com/cnlh/nps/lib/mux"
"github.com/xtaci/kcp-go"
) )
type TRPClient struct { type TRPClient struct {
@ -156,7 +158,7 @@ func (s *TRPClient) newChan() {
func (s *TRPClient) handleChan(src net.Conn) { func (s *TRPClient) handleChan(src net.Conn) {
lk, err := conn.NewConn(src).GetLinkInfo() lk, err := conn.NewConn(src).GetLinkInfo()
if err != nil { if err != nil || lk == nil {
src.Close() src.Close()
logs.Error("get connection info from server error ", err) logs.Error("get connection info from server error ", err)
return return
@ -165,7 +167,7 @@ func (s *TRPClient) handleChan(src net.Conn) {
lk.Host = common.FormatAddress(lk.Host) lk.Host = common.FormatAddress(lk.Host)
//if Conn type is http, read the request and log //if Conn type is http, read the request and log
if lk.ConnType == "http" { if lk.ConnType == "http" {
if targetConn, err := net.Dial(common.CONN_TCP, lk.Host); err != nil { if targetConn, err := net.DialTimeout(common.CONN_TCP, lk.Host, lk.Option.Timeout); err != nil {
logs.Warn("connect to %s error %s", lk.Host, err.Error()) logs.Warn("connect to %s error %s", lk.Host, err.Error())
src.Close() src.Close()
} else { } else {
@ -188,8 +190,12 @@ func (s *TRPClient) handleChan(src net.Conn) {
} }
return 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 //connect to target if conn type is tcp or udp
if targetConn, err := net.Dial(lk.ConnType, lk.Host); err != nil { if targetConn, err := net.DialTimeout(lk.ConnType, lk.Host, lk.Option.Timeout); err != nil {
logs.Warn("connect to %s error %s", lk.Host, err.Error()) logs.Warn("connect to %s error %s", lk.Host, err.Error())
src.Close() src.Close()
} else { } else {
@ -198,6 +204,65 @@ func (s *TRPClient) handleChan(src net.Conn) {
} }
} }
func (s *TRPClient) handleUdp(serverConn net.Conn) {
// bind a local udp port
local, err := net.ListenUDP("udp", nil)
defer local.Close()
defer serverConn.Close()
if err != nil {
logs.Error("bind local udp port error ", err.Error())
return
}
go func() {
defer serverConn.Close()
b := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(b)
for {
n, raddr, err := local.ReadFrom(b)
if err != nil {
logs.Error("read data from remote server error", err.Error())
}
buf := bytes.Buffer{}
dgram := common.NewUDPDatagram(common.NewUDPHeader(0, 0, common.ToSocksAddr(raddr)), b[:n])
dgram.Write(&buf)
b, err := conn.GetLenBytes(buf.Bytes())
if err != nil {
logs.Warn("get len bytes error", err.Error())
continue
}
if _, err := serverConn.Write(b); err != nil {
logs.Error("write data to remote error", err.Error())
return
}
}
}()
b := common.BufPoolUdp.Get().([]byte)
defer common.BufPoolUdp.Put(b)
for {
n, err := serverConn.Read(b)
if err != nil {
logs.Error("read udp data from server error ", err.Error())
return
}
udpData, err := common.ReadUDPDatagram(bytes.NewReader(b[:n]))
if err != nil {
logs.Error("unpack data error", err.Error())
return
}
raddr, err := net.ResolveUDPAddr("udp", udpData.Header.Addr.String())
if err != nil {
logs.Error("build remote addr err", err.Error())
continue // drop silently
}
_, err = local.WriteTo(udpData.Data, raddr)
if err != nil {
logs.Error("write data to remote ", raddr.String(), "error", err.Error())
return
}
}
}
// Whether the monitor channel is closed // Whether the monitor channel is closed
func (s *TRPClient) ping() { func (s *TRPClient) ping() {
s.ticker = time.NewTicker(time.Second * 5) s.ticker = time.NewTicker(time.Second * 5)

View File

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

View File

@ -220,7 +220,10 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
if _, err := c.Write([]byte(common.CONN_TEST)); err != nil { if _, err := c.Write([]byte(common.CONN_TEST)); err != nil {
return nil, err return nil, err
} }
if _, err := c.Write([]byte(crypt.Md5(version.GetVersion()))); err != nil { if err := c.WriteLenContent([]byte(version.GetVersion())); err != nil {
return nil, err
}
if err := c.WriteLenContent([]byte(version.VERSION)); err != nil {
return nil, err return nil, err
} }
b, err := c.GetShortContent(32) b, err := c.GetShortContent(32)

View File

@ -1,8 +1,10 @@
package client package client
import ( import (
"errors"
"net" "net"
"net/http" "net/http"
"runtime"
"sync" "sync"
"time" "time"
@ -31,6 +33,14 @@ type p2pBridge struct {
} }
func (p2pBridge *p2pBridge) SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) { 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() nowConn, err := muxSession.NewConn()
if err != nil { if err != nil {
udpConn = nil udpConn = nil
@ -158,6 +168,7 @@ func handleP2PVisitor(localTcpConn net.Conn, config *config.CommonConfig, l *con
if udpConn == nil { if udpConn == nil {
logs.Notice("new conn, P2P can not penetrate successfully, traffic will be transferred through the server") logs.Notice("new conn, P2P can not penetrate successfully, traffic will be transferred through the server")
handleSecret(localTcpConn, config, l) handleSecret(localTcpConn, config, l)
return
} }
logs.Trace("start trying to connect with the server") logs.Trace("start trying to connect with the server")
//TODO just support compress now because there is not tls file in client packages //TODO just support compress now because there is not tls file in client packages

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

@ -0,0 +1,53 @@
package main
import "C"
import (
"github.com/astaxie/beego/logs"
"github.com/cnlh/nps/client"
"time"
)
func init() {
logs.SetLogger(logs.AdapterFile, `{"filename":"npc.log","daily":false,"maxlines":100000,"color":true}`)
}
var status int
var closeBefore int
var cl *client.TRPClient
//export StartClientByVerifyKey
func StartClientByVerifyKey(serverAddr, verifyKey, connType, proxyUrl *C.char) int {
if cl != nil {
closeBefore = 1
cl.Close()
}
cl = client.NewRPClient(C.GoString(serverAddr), C.GoString(verifyKey), C.GoString(connType), C.GoString(proxyUrl), nil)
closeBefore = 0
go func() {
for {
status = 1
cl.Start()
status = 0
if closeBefore == 1 {
return
}
time.Sleep(time.Second * 5)
}
}()
return 1
}
//export GetClientStatus
func GetClientStatus() int {
return status
}
//export CloseClient
func CloseClient() {
closeBefore = 1
cl.Close()
}
func main() {
// Need a main function to make CGO compile package as C shared library
}

View File

@ -18,7 +18,8 @@ import (
"github.com/cnlh/nps/server/connection" "github.com/cnlh/nps/server/connection"
"github.com/cnlh/nps/server/test" "github.com/cnlh/nps/server/test"
"github.com/cnlh/nps/server/tool" "github.com/cnlh/nps/server/tool"
_ "github.com/cnlh/nps/web/routers"
"github.com/cnlh/nps/web/routers"
) )
var ( var (
@ -29,6 +30,7 @@ var (
func main() { func main() {
flag.Parse() flag.Parse()
beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf")) beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf"))
routers.Init()
if len(os.Args) > 1 { if len(os.Args) > 1 {
switch os.Args[1] { switch os.Args[1] {
case "test": case "test":

View File

@ -41,6 +41,9 @@ web_username=admin
web_password=123 web_password=123
web_port = 8080 web_port = 8080
web_ip=0.0.0.0 web_ip=0.0.0.0
web_base_url=
# 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) #Web API unauthenticated IP address(the len of auth_crypt_key must be 16)
auth_key=test auth_key=test

2
go.mod
View File

@ -15,7 +15,7 @@ require (
github.com/panjf2000/ants/v2 v2.2.2 github.com/panjf2000/ants/v2 v2.2.2
github.com/pkg/errors v0.8.0 github.com/pkg/errors v0.8.0
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
github.com/shirou/gopsutil v2.18.12+incompatible github.com/shirou/gopsutil v2.19.11+incompatible
github.com/stretchr/testify v1.3.0 // indirect github.com/stretchr/testify v1.3.0 // indirect
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect

4
go.sum
View File

@ -55,6 +55,8 @@ github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM= github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v2.19.11+incompatible h1:lJHR0foqAjI4exXqWsU3DbH7bX1xvdhGdnXTIARA9W4=
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/siddontang/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/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/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
@ -89,4 +91,4 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 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/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 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -6,6 +6,9 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"io" "io"
"io/ioutil"
"net"
"strconv"
"strings" "strings"
) )
@ -119,7 +122,8 @@ func (Self *BasePackager) Split() (strList []string) {
return return
} }
type ConnPackager struct { // Todo type ConnPackager struct {
// Todo
ConnType uint8 ConnType uint8
BasePackager BasePackager
} }
@ -233,3 +237,206 @@ func (Self *MuxPackager) UnPack(reader io.Reader) (n uint16, err error) {
n += 5 //uint8 int32 n += 5 //uint8 int32
return return
} }
const (
ipV4 = 1
domainName = 3
ipV6 = 4
)
type UDPHeader struct {
Rsv uint16
Frag uint8
Addr *Addr
}
func NewUDPHeader(rsv uint16, frag uint8, addr *Addr) *UDPHeader {
return &UDPHeader{
Rsv: rsv,
Frag: frag,
Addr: addr,
}
}
type Addr struct {
Type uint8
Host string
Port uint16
}
func (addr *Addr) String() string {
return net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port)))
}
func (addr *Addr) Decode(b []byte) error {
addr.Type = b[0]
pos := 1
switch addr.Type {
case ipV4:
addr.Host = net.IP(b[pos:pos+net.IPv4len]).String()
pos += net.IPv4len
case ipV6:
addr.Host = net.IP(b[pos:pos+net.IPv6len]).String()
pos += net.IPv6len
case domainName:
addrlen := int(b[pos])
pos++
addr.Host = string(b[pos : pos+addrlen])
pos += addrlen
default:
return errors.New("decode error")
}
addr.Port = binary.BigEndian.Uint16(b[pos:])
return nil
}
func (addr *Addr) Encode(b []byte) (int, error) {
b[0] = addr.Type
pos := 1
switch addr.Type {
case ipV4:
ip4 := net.ParseIP(addr.Host).To4()
if ip4 == nil {
ip4 = net.IPv4zero.To4()
}
pos += copy(b[pos:], ip4)
case domainName:
b[pos] = byte(len(addr.Host))
pos++
pos += copy(b[pos:], []byte(addr.Host))
case ipV6:
ip16 := net.ParseIP(addr.Host).To16()
if ip16 == nil {
ip16 = net.IPv6zero.To16()
}
pos += copy(b[pos:], ip16)
default:
b[0] = ipV4
copy(b[pos:pos+4], net.IPv4zero.To4())
pos += 4
}
binary.BigEndian.PutUint16(b[pos:], addr.Port)
pos += 2
return pos, nil
}
func (h *UDPHeader) Write(w io.Writer) error {
b := BufPoolUdp.Get().([]byte)
defer BufPoolUdp.Put(b)
binary.BigEndian.PutUint16(b[:2], h.Rsv)
b[2] = h.Frag
addr := h.Addr
if addr == nil {
addr = &Addr{}
}
length, _ := addr.Encode(b[3:])
_, err := w.Write(b[:3+length])
return err
}
type UDPDatagram struct {
Header *UDPHeader
Data []byte
}
func ReadUDPDatagram(r io.Reader) (*UDPDatagram, error) {
b := BufPoolUdp.Get().([]byte)
defer BufPoolUdp.Put(b)
// when r is a streaming (such as TCP connection), we may read more than the required data,
// but we don't know how to handle it. So we use io.ReadFull to instead of io.ReadAtLeast
// to make sure that no redundant data will be discarded.
n, err := io.ReadFull(r, b[:5])
if err != nil {
return nil, err
}
header := &UDPHeader{
Rsv: binary.BigEndian.Uint16(b[:2]),
Frag: b[2],
}
atype := b[3]
hlen := 0
switch atype {
case ipV4:
hlen = 10
case ipV6:
hlen = 22
case domainName:
hlen = 7 + int(b[4])
default:
return nil, errors.New("addr not support")
}
dlen := int(header.Rsv)
if dlen == 0 { // standard SOCKS5 UDP datagram
extra, err := ioutil.ReadAll(r) // we assume no redundant data
if err != nil {
return nil, err
}
copy(b[n:], extra)
n += len(extra) // total length
dlen = n - hlen // data length
} else { // extended feature, for UDP over TCP, using reserved field as data length
if _, err := io.ReadFull(r, b[n:hlen+dlen]); err != nil {
return nil, err
}
n = hlen + dlen
}
header.Addr = new(Addr)
if err := header.Addr.Decode(b[3:hlen]); err != nil {
return nil, err
}
data := make([]byte, dlen)
copy(data, b[hlen:n])
d := &UDPDatagram{
Header: header,
Data: data,
}
return d, nil
}
func NewUDPDatagram(header *UDPHeader, data []byte) *UDPDatagram {
return &UDPDatagram{
Header: header,
Data: data,
}
}
func (d *UDPDatagram) Write(w io.Writer) error {
h := d.Header
if h == nil {
h = &UDPHeader{}
}
buf := bytes.Buffer{}
if err := h.Write(&buf); err != nil {
return err
}
if _, err := buf.Write(d.Data); err != nil {
return err
}
_, err := buf.WriteTo(w)
return err
}
func ToSocksAddr(addr net.Addr) *Addr {
host := "0.0.0.0"
port := 0
if addr != nil {
h, p, _ := net.SplitHostPort(addr.String())
host = h
port, _ = strconv.Atoi(p)
}
return &Addr{
Type: ipV4,
Host: host,
Port: uint16(port),
}
}

View File

@ -7,7 +7,7 @@ import (
const PoolSize = 64 * 1024 const PoolSize = 64 * 1024
const PoolSizeSmall = 100 const PoolSizeSmall = 100
const PoolSizeUdp = 1472 const PoolSizeUdp = 1472 + 200
const PoolSizeCopy = 32 << 10 const PoolSizeCopy = 32 << 10
const PoolSizeBuffer = 4096 const PoolSizeBuffer = 4096
const PoolSizeWindow = PoolSizeBuffer - 2 - 4 - 4 - 1 const PoolSizeWindow = PoolSizeBuffer - 2 - 4 - 4 - 1

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"errors"
"html/template" "html/template"
"io" "io"
"io/ioutil" "io/ioutil"
@ -109,8 +110,8 @@ func ChangeHostAndHeader(r *http.Request, host string, header string, addr strin
} }
addr = strings.Split(addr, ":")[0] addr = strings.Split(addr, ":")[0]
if prior, ok := r.Header["X-Forwarded-For"]; ok { if prior, ok := r.Header["X-Forwarded-For"]; ok {
addr = strings.Join(prior, ", ") + ", " + addr addr = strings.Join(prior, ", ") + ", " + addr
} }
r.Header.Set("X-Forwarded-For", addr) r.Header.Set("X-Forwarded-For", addr)
r.Header.Set("X-Real-IP", addr) r.Header.Set("X-Real-IP", addr)
} }
@ -396,3 +397,62 @@ func GetExtFromPath(path string) string {
} }
return string(re.Find([]byte(s[0]))) return string(re.Find([]byte(s[0])))
} }
var externalIp string
func GetExternalIp() string {
if externalIp != "" {
return externalIp
}
resp, err := http.Get("http://myexternalip.com/raw")
if err != nil {
return ""
}
defer resp.Body.Close()
content, _ := ioutil.ReadAll(resp.Body)
externalIp = string(content)
return externalIp
}
func GetIntranetIp() (error, string) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, ""
}
for _, address := range addrs {
// 检查ip地址判断是否回环地址
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return nil, ipnet.IP.To4().String()
}
}
}
return errors.New("get intranet ip error"), ""
}
func IsPublicIP(IP net.IP) bool {
if IP.IsLoopback() || IP.IsLinkLocalMulticast() || IP.IsLinkLocalUnicast() {
return false
}
if ip4 := IP.To4(); ip4 != nil {
switch true {
case ip4[0] == 10:
return false
case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:
return false
case ip4[0] == 192 && ip4[1] == 168:
return false
default:
return true
}
}
return false
}
func GetServerIpByClientIp(clientIp net.IP) string {
if IsPublicIP(clientIp) {
return GetExternalIp()
}
_, ip := GetIntranetIp()
return ip
}

View File

@ -87,7 +87,7 @@ func (s *Conn) GetShortContent(l int) (b []byte, err error) {
//读取指定长度内容 //读取指定长度内容
func (s *Conn) ReadLen(cLen int, buf []byte) (int, error) { func (s *Conn) ReadLen(cLen int, buf []byte) (int, error) {
if cLen > len(buf) { if cLen > len(buf) || cLen <= 0 {
return 0, errors.New("长度错误" + strconv.Itoa(cLen)) return 0, errors.New("长度错误" + strconv.Itoa(cLen))
} }
if n, err := io.ReadFull(s, buf[:cLen]); err != nil || n != cLen { if n, err := io.ReadFull(s, buf[:cLen]); err != nil || n != cLen {
@ -124,8 +124,8 @@ func (s *Conn) SetAlive(tp string) {
case *net.TCPConn: case *net.TCPConn:
conn := s.Conn.(*net.TCPConn) conn := s.Conn.(*net.TCPConn)
conn.SetReadDeadline(time.Time{}) conn.SetReadDeadline(time.Time{})
conn.SetKeepAlive(true) //conn.SetKeepAlive(false)
conn.SetKeepAlivePeriod(time.Duration(2 * time.Second)) //conn.SetKeepAlivePeriod(time.Duration(2 * time.Second))
case *mux.PortConn: case *mux.PortConn:
s.Conn.(*mux.PortConn).SetReadDeadline(time.Time{}) s.Conn.(*mux.PortConn).SetReadDeadline(time.Time{})
} }

View File

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

View File

@ -3,7 +3,6 @@ package conn
import ( import (
"io" "io"
"github.com/cnlh/nps/lib/common"
"github.com/golang/snappy" "github.com/golang/snappy"
) )
@ -32,13 +31,7 @@ func (s *SnappyConn) Write(b []byte) (n int, err error) {
//snappy压缩读 //snappy压缩读
func (s *SnappyConn) Read(b []byte) (n int, err error) { func (s *SnappyConn) Read(b []byte) (n int, err error) {
buf := common.BufPool.Get().([]byte) return s.r.Read(b)
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 { func (s *SnappyConn) Close() error {

View File

@ -50,6 +50,7 @@ type Client struct {
WebPassword string //the password of web login WebPassword string //the password of web login
ConfigConnAllow bool //is allow connected by config file ConfigConnAllow bool //is allow connected by config file
MaxTunnelNum int MaxTunnelNum int
Version string
sync.RWMutex sync.RWMutex
} }

View File

@ -499,8 +499,16 @@ start:
func (Self *SendWindow) waitReceiveWindow() (err error) { func (Self *SendWindow) waitReceiveWindow() (err error) {
t := Self.timeout.Sub(time.Now()) t := Self.timeout.Sub(time.Now())
if t < 0 { if t < 0 { // not set the timeout, wait for it as long as connection close
t = time.Minute * 5 select {
case _, ok := <-Self.setSizeCh:
if !ok {
return errors.New("conn.writeWindow: window closed")
}
return nil
case <-Self.closeOpCh:
return errors.New("conn.writeWindow: window closed")
}
} }
timer := time.NewTimer(t) timer := time.NewTimer(t)
defer timer.Stop() defer timer.Stop()

View File

@ -218,7 +218,7 @@ type ReceiveWindowQueue struct {
// On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility // 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. // 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. // The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned.
timeout time.Time timeout time.Time
} }
func (Self *ReceiveWindowQueue) New() { func (Self *ReceiveWindowQueue) New() {
@ -300,8 +300,14 @@ func (Self *ReceiveWindowQueue) waitPush() (err error) {
//logs.Warn("wait push") //logs.Warn("wait push")
//defer logs.Warn("wait push finish") //defer logs.Warn("wait push finish")
t := Self.timeout.Sub(time.Now()) t := Self.timeout.Sub(time.Now())
if t <= 0 { if t <= 0 { // not set the timeout, so wait for it without timeout, just like a tcp connection
t = time.Minute * 5 select {
case <-Self.readOp:
return nil
case <-Self.stopOp:
err = io.EOF
return
}
} }
timer := time.NewTimer(t) timer := time.NewTimer(t)
defer timer.Stop() defer timer.Stop()

View File

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

View File

@ -171,11 +171,11 @@ reset:
} }
}() }()
for { for {
if resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != nil { if resp, err := http.ReadResponse(bufio.NewReader(connClient), r); err != nil || resp == nil {
return return
} else { } else {
//if the cache is start and the response is in the extension,store the response to the cache list //if the cache is start and the response is in the extension,store the response to the cache list
if s.useCache && strings.Contains(r.URL.Path, ".") { if s.useCache && r.URL != nil && strings.Contains(r.URL.Path, ".") {
b, err := httputil.DumpResponse(resp, true) b, err := httputil.DumpResponse(resp, true)
if err != nil { if err != nil {
return return

View File

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

View File

@ -63,7 +63,7 @@ func (s *WebServer) Start() error {
<-stop <-stop
} }
beego.BConfig.WebConfig.Session.SessionOn = true beego.BConfig.WebConfig.Session.SessionOn = true
beego.SetStaticPath("/static", filepath.Join(common.GetRunPath(), "web", "static")) beego.SetStaticPath(beego.AppConfig.String("web_base_url")+"/static", filepath.Join(common.GetRunPath(), "web", "static"))
beego.SetViewsPath(filepath.Join(common.GetRunPath(), "web", "views")) beego.SetViewsPath(filepath.Join(common.GetRunPath(), "web", "views"))
if l, err := connection.GetWebManagerListener(); err == nil { if l, err := connection.GetWebManagerListener(); err == nil {
beego.InitBeforeHTTPRun() beego.InitBeforeHTTPRun()

View File

@ -2,6 +2,7 @@ package server
import ( import (
"errors" "errors"
"github.com/cnlh/nps/lib/version"
"math" "math"
"os" "os"
"strconv" "strconv"
@ -271,8 +272,9 @@ func GetClientList(start, length int, search, sort, order string, clientId int)
func dealClientData() { func dealClientData() {
file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool { file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool {
v := value.(*file.Client) v := value.(*file.Client)
if _, ok := Bridge.Client.Load(v.Id); ok { if vv, ok := Bridge.Client.Load(v.Id); ok {
v.IsConnect = true v.IsConnect = true
v.Version = vv.(*bridge.Client).Version
} else { } else {
v.IsConnect = false v.IsConnect = false
} }
@ -338,6 +340,7 @@ func DelClientConnect(clientId int) {
func GetDashboardData() map[string]interface{} { func GetDashboardData() map[string]interface{} {
data := make(map[string]interface{}) data := make(map[string]interface{})
data["version"] = version.VERSION
data["hostCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Hosts) data["hostCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Hosts)
data["clientCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Clients) - 1 //Remove the public key client data["clientCount"] = common.GeSynctMapLen(file.GetDb().JsonDb.Clients) - 1 //Remove the public key client
dealClientData() dealClientData()

50
update.sh Normal file
View File

@ -0,0 +1,50 @@
#/bash/sh
echo "start upgrading to the latest version"
if [ $1 == "latest" ]
then
version=`wget -qO- -t1 -T2 "https://api.github.com/repos/cnlh/nps/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g'`
else
version=$1
fi
echo "the current latest version is "$version""
download_base_url=https://github.com/cnlh/nps/releases/download/$version/
if [ $4 ]
then
filename=""$2"_"$3"_v"$4"_"server".tar.gz"
else
filename=""$2"_"$3"_"server".tar.gz"
fi
complete_download_url=""$download_base_url""$filename""
echo "start download file from "$complete_download_url""
dir_name=`echo $RANDOM`
mkdir $dir_name && cd $dir_name
wget $complete_download_url >/dev/null 2>&1
if [ ! -f "$filename" ]; then
echo "download file failed!"
rm -rf $dir_name
exit
fi
echo "start extracting files"
mkdir nps
tar -xvf $filename -C ./nps >/dev/null 2>&1
cd nps
if [ -f "../../nps" ]; then
echo "replace "../../nps"!"
cp -rf nps ../../
fi
usr_dir=`which nps`
if [ -f "$usr_dir" ]; then
echo "replace "$usr_dir"!"
cp -rf nps $usr_dir
fi
cd ../../ && rm -rf $dir_name
echo "update complete!"
echo -e "\033[32m please restart nps \033[0m"

View File

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

View File

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

View File

@ -14,6 +14,7 @@ type LoginController struct {
} }
func (self *LoginController) Index() { func (self *LoginController) Index() {
self.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
self.Data["register_allow"], _ = beego.AppConfig.Bool("allow_user_register") self.Data["register_allow"], _ = beego.AppConfig.Bool("allow_user_register")
self.TplName = "login/index.html" self.TplName = "login/index.html"
} }
@ -60,6 +61,7 @@ func (self *LoginController) Verify() {
} }
func (self *LoginController) Register() { func (self *LoginController) Register() {
if self.Ctx.Request.Method == "GET" { if self.Ctx.Request.Method == "GET" {
self.Data["web_base_url"] = beego.AppConfig.String("web_base_url")
self.TplName = "login/register.html" self.TplName = "login/register.html"
} else { } else {
if b, err := beego.AppConfig.Bool("allow_user_register"); err != nil || !b { if b, err := beego.AppConfig.Bool("allow_user_register"); err != nil || !b {
@ -91,5 +93,5 @@ func (self *LoginController) Register() {
func (self *LoginController) Out() { func (self *LoginController) Out() {
self.SetSession("auth", false) self.SetSession("auth", false)
self.Redirect("/login/index", 302) self.Redirect(beego.AppConfig.String("web_base_url")+"/login/index", 302)
} }

View File

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

View File

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

View File

@ -134,7 +134,7 @@
$("#add").on("click", function () { $("#add").on("click", function () {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/client/add", url: "{{.web_base_url}}/client/add",
data: $("form").serializeArray(), data: $("form").serializeArray(),
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)

View File

@ -145,7 +145,7 @@
$("#add").on("click", function () { $("#add").on("click", function () {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/client/edit", url: "{{.web_base_url}}/client/edit",
data: $("form").serializeArray(), data: $("form").serializeArray(),
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)

View File

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

View File

@ -162,7 +162,7 @@
$("#add").on("click", function () { $("#add").on("click", function () {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/index/add", url: "{{.web_base_url}}/index/add",
data: $("form").serializeArray(), data: $("form").serializeArray(),
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)

View File

@ -160,7 +160,7 @@
$("#add").on("click", function () { $("#add").on("click", function () {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/index/edit", url: "{{.web_base_url}}/index/edit",
data: $("form").serializeArray(), data: $("form").serializeArray(),
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)

View File

@ -112,7 +112,7 @@
$("#add").on("click", function () { $("#add").on("click", function () {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/index/addhost", url: "{{.web_base_url}}/index/addhost",
data: $("form").serializeArray(), data: $("form").serializeArray(),
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)

View File

@ -116,7 +116,7 @@
$("#add").on("click", function () { $("#add").on("click", function () {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/index/edithost", url: "{{.web_base_url}}/index/edithost",
data: $("form").serializeArray(), data: $("form").serializeArray(),
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)

View File

@ -18,7 +18,7 @@
<div class="content"> <div class="content">
<div class="table-responsive"> <div class="table-responsive">
<div id="toolbar"> <div id="toolbar">
<a href="/index/addhost?vkey={{.task_id}}&client_id={{.client_id}}" <a href="{{.web_base_url}}/index/addhost?vkey={{.task_id}}&client_id={{.client_id}}"
class="btn btn-primary dim" class="btn btn-primary dim"
type="button" langtag="info-new"></a> type="button" langtag="info-new"></a>
</div> </div>
@ -158,7 +158,7 @@
if (confirm("Are you sure you want to delete it")) { if (confirm("Are you sure you want to delete it")) {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/index/delhost", url: "{{.web_base_url}}/index/delhost",
data: {"id": id}, data: {"id": id},
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)
@ -171,7 +171,7 @@
} }
function edit(id) { function edit(id) {
window.location.href = "/index/edithost?id=" + id window.location.href = "{{.web_base_url}}/index/edithost?id=" + id
} }

View File

@ -142,6 +142,16 @@
</div> </div>
</div> </div>
</li> </li>
<li class="list-group-item ">
<div class="row">
<div class="col-sm-6">
<strong></strong>
</div>
<div class="col-sm-6 text-right">
<strong>{{.data.version}}</strong>
</div>
</div>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -754,4 +764,4 @@
this.myChart7.resize(); this.myChart7.resize();
}); });
</script> </script>

View File

@ -18,7 +18,7 @@
<div class="content"> <div class="content">
<div class="table-responsive"> <div class="table-responsive">
<div id="toolbar"> <div id="toolbar">
<a href="/index/add?type={{.type}}&client_id={{.client_id}}" class="btn btn-primary dim" <a href="{{.web_base_url}}/index/add?type={{.type}}&client_id={{.client_id}}" class="btn btn-primary dim"
type="button" langtag="info-new"></a> type="button" langtag="info-new"></a>
</div> </div>
<table id="taskList_table" class="table-striped table-hover" <table id="taskList_table" class="table-striped table-hover"
@ -40,7 +40,7 @@
$('#table').bootstrapTable({ $('#table').bootstrapTable({
toolbar: "#toolbar", toolbar: "#toolbar",
method: 'post', // 服务器数据的请求方式 get or post method: 'post', // 服务器数据的请求方式 get or post
url: "/index/gettunnel", // 服务器数据的加载地址 url: "{{.web_base_url}}/index/gettunnel", // 服务器数据的加载地址
queryParams: function (params) { queryParams: function (params) {
return { return {
"offset": params.offset, "offset": params.offset,
@ -191,7 +191,7 @@
if (confirm("Are you sure you want to delete it")) { if (confirm("Are you sure you want to delete it")) {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/index/del", url: "{{.web_base_url}}/index/del",
data: {"id": id}, data: {"id": id},
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)
@ -207,7 +207,7 @@
if (confirm("Are you sure you want to start it")) { if (confirm("Are you sure you want to start it")) {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/index/start", url: "{{.web_base_url}}/index/start",
data: {"id": id}, data: {"id": id},
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)
@ -223,7 +223,7 @@
if (confirm("Are you sure you want to stop it")) { if (confirm("Are you sure you want to stop it")) {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/index/stop", url: "{{.web_base_url}}/index/stop",
data: {"id": id}, data: {"id": id},
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)
@ -236,7 +236,7 @@
} }
function edit(id) { function edit(id) {
window.location.href = "/index/edit?id=" + id window.location.href = "{{.web_base_url}}/index/edit?id=" + id
} }
</script> </script>

View File

@ -8,10 +8,10 @@
<title>nps admin login</title> <title>nps admin login</title>
<link href="/static/css/bootstrap.min.css" rel="stylesheet"> <link href="{{.web_base_url}}/static/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/font-awesome/css/font-awesome.css" rel="stylesheet"> <link href="{{.web_base_url}}/static/font-awesome/css/font-awesome.css" rel="stylesheet">
<link href="/static/css/style.css" rel="stylesheet"> <link href="{{.web_base_url}}/static/css/style.css" rel="stylesheet">
</head> </head>
@ -49,7 +49,7 @@
</div> </div>
<button onclick="login()" class="btn btn-primary block full-width m-b">login</button> <button onclick="login()" class="btn btn-primary block full-width m-b">login</button>
{{if eq true .register_allow}} {{if eq true .register_allow}}
<a class="btn btn-sm btn-white btn-block" href="/login/register">register</a> <a class="btn btn-sm btn-white btn-block" href="{{.web_base_url}}/login/register">register</a>
{{end}} {{end}}
</form> </form>
</div> </div>
@ -59,7 +59,7 @@
</div> </div>
</body> </body>
<script src="/static/js/jquery-2.1.1.js"></script> <script src="{{.web_base_url}}/static/js/jquery-2.1.1.js"></script>
</html> </html>
@ -68,11 +68,11 @@
function login() { function login() {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/login/verify", url: "{{.web_base_url}}/login/verify",
data: $("form").serializeArray(), data: $("form").serializeArray(),
success: function (res) { success: function (res) {
if (res.status) { if (res.status) {
window.location.href = "/index/index" window.location.href = "{{.web_base_url}}/index/index"
} else { } else {
alert(res.msg) alert(res.msg)
} }

View File

@ -8,10 +8,10 @@
<title>nps register</title> <title>nps register</title>
<link href="/static/css/bootstrap.min.css" rel="stylesheet"> <link href="{{.web_base_url}}/static/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/font-awesome/css/font-awesome.css" rel="stylesheet"> <link href="{{.web_base_url}}/static/font-awesome/css/font-awesome.css" rel="stylesheet">
<link href="/static/css/style.css" rel="stylesheet"> <link href="{{.web_base_url}}/static/css/style.css" rel="stylesheet">
</head> </head>
@ -34,22 +34,22 @@
</div> </div>
<button onclick="register()" type="submit" class="btn btn-primary block full-width m-b">register</button> <button onclick="register()" type="submit" class="btn btn-primary block full-width m-b">register</button>
<a class="btn btn-sm btn-white btn-block" href="/login/index">login</a> <a class="btn btn-sm btn-white btn-block" href="{{.web_base_url}}/login/index">login</a>
</form> </form>
</div> </div>
</div> </div>
<script src="/static/js/jquery-2.1.1.js"></script> <script src="{{.web_base_url}}/static/js/jquery-2.1.1.js"></script>
<script> <script>
function register() { function register() {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/login/register", url: "{{.web_base_url}}/login/register",
data: $("form").serializeArray(), data: $("form").serializeArray(),
success: function (res) { success: function (res) {
alert(res.msg) alert(res.msg)
if (res.status) { if (res.status) {
window.location.href = "/login/index" window.location.href = "{{.web_base_url}}/login/index"
} }
} }
}) })

View File

@ -9,22 +9,22 @@
<title>nps admin</title> <title>nps admin</title>
<link href="/static/css/bootstrap.min.css" rel="stylesheet"> <link href="{{.web_base_url}}/static/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/font-awesome/css/font-awesome.css" rel="stylesheet"> <link href="{{.web_base_url}}/static/font-awesome/css/font-awesome.css" rel="stylesheet">
<link href="/static/css/style.css" rel="stylesheet"> <link href="{{.web_base_url}}/static/css/style.css" rel="stylesheet">
<script src="/static/js/main.js"></script> <script src="{{.web_base_url}}/static/js/main.js"></script>
<!-- Mainly scripts --> <!-- Mainly scripts -->
<script src="/static/js/jquery-2.1.1.js"></script> <script src="{{.web_base_url}}/static/js/jquery-2.1.1.js"></script>
<script src="/static/js/bootstrap.min.js"></script> <script src="{{.web_base_url}}/static/js/bootstrap.min.js"></script>
<script src="/static/js/echarts.min.js"></script> <script src="{{.web_base_url}}/static/js/echarts.min.js"></script>
<!-- Latest compiled and minified CSS --> <!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="/static/css/bootstrap-table.min.css"> <link rel="stylesheet" href="{{.web_base_url}}/static/css/bootstrap-table.min.css">
<!-- Latest compiled and minified JavaScript --> <!-- Latest compiled and minified JavaScript -->
<script src="/static/js/bootstrap-table.min.js"></script> <script src="{{.web_base_url}}/static/js/bootstrap-table.min.js"></script>
<script src="/static/js/inspinia.js"></script> <script src="{{.web_base_url}}/static/js/inspinia.js"></script>
<!-- Latest compiled and minified Locales --> <!-- Latest compiled and minified Locales -->
<script src="/static/js/langchange.js" type="text/javascript"></script> <script src="{{.web_base_url}}/static/js/langchange.js" type="text/javascript"></script>
</head> </head>
@ -35,7 +35,7 @@
<ul class="nav metismenu" id="side-menu"> <ul class="nav metismenu" id="side-menu">
<li class="nav-header"> <li class="nav-header">
<div class="dropdown profile-element"> <span> <div class="dropdown profile-element"> <span>
{{/*<img alt="image" class="img-circle" src="/static/img/profile_small.jpg"/>*/}} {{/*<img alt="image" class="img-circle" src="{{.web_base_url}}/static/img/profile_small.jpg"/>*/}}
</span> </span>
<a href="#"> <a href="#">
<span class="clear"> <span class="block m-t-xs"> <strong class="font-bold"> <span class="clear"> <span class="block m-t-xs"> <strong class="font-bold">
@ -53,42 +53,42 @@
</div> </div>
</li> </li>
<li class="{{if eq "index" .menu}}active{{end}}"> <li class="{{if eq "index" .menu}}active{{end}}">
<a href="/"><i class="fa fa-dashboard"></i> <span langtag="menu-dashboard" <a href="{{.web_base_url}}/"><i class="fa fa-dashboard"></i> <span langtag="menu-dashboard"
class="nav-label"></span></a> class="nav-label"></span></a>
</li> </li>
<li class="{{if eq "client" .menu}}active{{end}}"> <li class="{{if eq "client" .menu}}active{{end}}">
<a href="/client/list"><i class="fa fa-clipboard"></i> <span langtag="menu-client" <a href="{{.web_base_url}}/client/list"><i class="fa fa-clipboard"></i> <span langtag="menu-client"
class="nav-label"></span></a> class="nav-label"></span></a>
</li> </li>
<li class="{{if eq "host" .menu}}active{{end}}"> <li class="{{if eq "host" .menu}}active{{end}}">
<a href="/index/hostlist"><i class="fa fa-paperclip"></i> <span langtag="menu-host" <a href="{{.web_base_url}}/index/hostlist"><i class="fa fa-paperclip"></i> <span langtag="menu-host"
class="nav-label"></span></a> class="nav-label"></span></a>
</li> </li>
<li class="{{if eq "tcp" .menu}}active{{end}}"> <li class="{{if eq "tcp" .menu}}active{{end}}">
<a href="/index/tcp"><i class="fa fa-line-chart"></i> <span langtag="menu-tcp" <a href="{{.web_base_url}}/index/tcp"><i class="fa fa-line-chart"></i> <span langtag="menu-tcp"
class="nav-label">tcp</span></a> class="nav-label">tcp</span></a>
</li> </li>
<li class="{{if eq "udp" .menu}}active{{end}}"> <li class="{{if eq "udp" .menu}}active{{end}}">
<a href="/index/udp"><i class="fa fa-server"></i> <span langtag="menu-udp" <a href="{{.web_base_url}}/index/udp"><i class="fa fa-server"></i> <span langtag="menu-udp"
class="nav-label">udp</span></a> class="nav-label">udp</span></a>
</li> </li>
<li class="{{if eq "http" .menu}}active{{end}}"> <li class="{{if eq "http" .menu}}active{{end}}">
<a href="/index/http"><i class="fa fa-html5"></i> <span langtag="menu-http" <a href="{{.web_base_url}}/index/http"><i class="fa fa-html5"></i> <span langtag="menu-http"
class="nav-label">http</span></a> class="nav-label">http</span></a>
</li> </li>
<li class="{{if eq "socks5" .menu}}active{{end}}"> <li class="{{if eq "socks5" .menu}}active{{end}}">
<a href="/index/socks5"><i class="fa fa-table"></i> <span langtag="menu-socks5" class="nav-label">socks5</span></a> <a href="{{.web_base_url}}/index/socks5"><i class="fa fa-table"></i> <span langtag="menu-socks5" class="nav-label">socks5</span></a>
</li> </li>
<li class="{{if eq "secret" .menu}}active{{end}}"> <li class="{{if eq "secret" .menu}}active{{end}}">
<a href="/index/secret"><i class="fa fa-backward"></i> <span langtag="menu-secret" <a href="{{.web_base_url}}/index/secret"><i class="fa fa-backward"></i> <span langtag="menu-secret"
class="nav-label"></span></a> class="nav-label"></span></a>
</li> </li>
<li class="{{if eq "p2p" .menu}}active{{end}}"> <li class="{{if eq "p2p" .menu}}active{{end}}">
<a href="/index/p2p"><i class="fa fa-dashcube"></i> <span langtag="menu-p2p" <a href="{{.web_base_url}}/index/p2p"><i class="fa fa-dashcube"></i> <span langtag="menu-p2p"
class="nav-label">p2p</span></a> class="nav-label">p2p</span></a>
</li> </li>
<li class="{{if eq "file" .menu}}active{{end}}"> <li class="{{if eq "file" .menu}}active{{end}}">
<a href="/index/file"><i class="fa fa-laptop"></i> <span langtag="menu-file" <a href="{{.web_base_url}}/index/file"><i class="fa fa-laptop"></i> <span langtag="menu-file"
class="nav-label"></span></a> class="nav-label"></span></a>
</li> </li>
</ul> </ul>
@ -115,7 +115,7 @@
</li> </li>
<li> <li>
<a href="/login/out"> <a href="{{.web_base_url}}/login/out">
<i class="fa fa-sign-out"></i> logout <i class="fa fa-sign-out"></i> logout
</a> </a>
</li> </li>
@ -142,6 +142,9 @@
</html> </html>
<script> <script>
window.nps = {
"web_base_url": {{.web_base_url}},
}
// googleTranslateElementInit() // googleTranslateElementInit()
// //
// function googleTranslateElementInit() { // function googleTranslateElementInit() {