Ver código fonte

Merge pull request #291 from cnlh/dev

Fix bugs and optimize functions
ffdfgdfg 5 anos atrás
pai
commit
f97a9176e7

+ 57 - 0
.travis.yml

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

+ 26 - 0
README.md

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

+ 22 - 12
bridge/bridge.go

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

+ 136 - 0
build.bash

@@ -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 - 0
build.sh

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

+ 69 - 4
client/client.go

@@ -2,18 +2,20 @@ package client
 
 import (
 	"bufio"
+	"bytes"
 	"net"
 	"net/http"
 	"strconv"
 	"time"
 
 	"github.com/astaxie/beego/logs"
+	"github.com/xtaci/kcp-go"
+
 	"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"
 )
 
 type TRPClient struct {
@@ -156,7 +158,7 @@ func (s *TRPClient) newChan() {
 
 func (s *TRPClient) handleChan(src net.Conn) {
 	lk, err := conn.NewConn(src).GetLinkInfo()
-	if err != nil {
+	if err != nil || lk == nil {
 		src.Close()
 		logs.Error("get connection info from server error ", err)
 		return
@@ -165,7 +167,7 @@ func (s *TRPClient) handleChan(src net.Conn) {
 	lk.Host = common.FormatAddress(lk.Host)
 	//if Conn type is http, read the request and log
 	if lk.ConnType == "http" {
-		if targetConn, err := net.Dial(common.CONN_TCP, lk.Host); err != nil {
+		if targetConn, err := net.DialTimeout(common.CONN_TCP, lk.Host, lk.Option.Timeout); err != nil {
 			logs.Warn("connect to %s error %s", lk.Host, err.Error())
 			src.Close()
 		} else {
@@ -188,8 +190,12 @@ func (s *TRPClient) handleChan(src net.Conn) {
 		}
 		return
 	}
+	if lk.ConnType == "udp5" {
+		logs.Trace("new %s connection with the goal of %s, remote address:%s", lk.ConnType, lk.Host, lk.RemoteAddr)
+		s.handleUdp(src)
+	}
 	//connect to target if conn type is tcp or udp
-	if targetConn, err := net.Dial(lk.ConnType, lk.Host); err != nil {
+	if targetConn, err := net.DialTimeout(lk.ConnType, lk.Host, lk.Option.Timeout); err != nil {
 		logs.Warn("connect to %s error %s", lk.Host, err.Error())
 		src.Close()
 	} else {
@@ -198,6 +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
 func (s *TRPClient) ping() {
 	s.ticker = time.NewTicker(time.Second * 5)

+ 0 - 75
client/client_test.go

@@ -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()
-}

+ 4 - 1
client/control.go

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

+ 11 - 0
client/local.go

@@ -1,8 +1,10 @@
 package client
 
 import (
+	"errors"
 	"net"
 	"net/http"
+	"runtime"
 	"sync"
 	"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) {
+	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
@@ -158,6 +168,7 @@ func handleP2PVisitor(localTcpConn net.Conn, config *config.CommonConfig, l *con
 	if udpConn == nil {
 		logs.Notice("new conn, P2P can not penetrate successfully, traffic will be transferred through the server")
 		handleSecret(localTcpConn, config, l)
+		return
 	}
 	logs.Trace("start trying to connect with the server")
 	//TODO just support compress now because there is not tls file in client packages

+ 53 - 0
cmd/npc/sdk.go

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

+ 3 - 1
cmd/nps/nps.go

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

+ 3 - 0
conf/nps.conf

@@ -41,6 +41,9 @@ web_username=admin
 web_password=123
 web_port = 8080
 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)
 auth_key=test

+ 1 - 1
go.mod

@@ -15,7 +15,7 @@ require (
 	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 v2.18.12+incompatible
+	github.com/shirou/gopsutil v2.19.11+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-20181023030647-4e92f724b73b // indirect

+ 3 - 1
go.sum

@@ -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/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
 github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/gopsutil v2.19.11+incompatible h1:lJHR0foqAjI4exXqWsU3DbH7bX1xvdhGdnXTIARA9W4=
+github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
 github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
 github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
@@ -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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

+ 208 - 1
lib/common/netpackager.go

@@ -6,6 +6,9 @@ import (
 	"encoding/json"
 	"errors"
 	"io"
+	"io/ioutil"
+	"net"
+	"strconv"
 	"strings"
 )
 
@@ -119,7 +122,8 @@ func (Self *BasePackager) Split() (strList []string) {
 	return
 }
 
-type ConnPackager struct { // Todo
+type ConnPackager struct {
+	// Todo
 	ConnType uint8
 	BasePackager
 }
@@ -233,3 +237,206 @@ func (Self *MuxPackager) UnPack(reader io.Reader) (n uint16, err error) {
 	n += 5 //uint8 int32
 	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),
+	}
+}

+ 1 - 1
lib/common/pool.go

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

+ 62 - 2
lib/common/util.go

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

+ 3 - 3
lib/conn/conn.go

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

+ 31 - 1
lib/conn/link.go

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

+ 1 - 8
lib/conn/snappy.go

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

+ 1 - 0
lib/file/obj.go

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

+ 10 - 2
lib/mux/conn.go

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

+ 9 - 3
lib/mux/queue.go

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

+ 2 - 2
lib/version/version.go

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

+ 2 - 2
server/proxy/http.go

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

+ 119 - 17
server/proxy/socks5.go

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

+ 1 - 1
server/proxy/tcp.go

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

+ 4 - 1
server/server.go

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

+ 50 - 0
update.sh

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

+ 4 - 1
web/controllers/base.go

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

+ 3 - 0
web/controllers/index.go

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

+ 3 - 1
web/controllers/login.go

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

+ 18 - 6
web/routers/router.go

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

+ 2 - 2
web/static/js/langchange.js

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

+ 1 - 1
web/views/client/add.html

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

+ 1 - 1
web/views/client/edit.html

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

+ 14 - 9
web/views/client/list.html

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

+ 1 - 1
web/views/index/add.html

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

+ 1 - 1
web/views/index/edit.html

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

+ 1 - 1
web/views/index/hadd.html

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

+ 1 - 1
web/views/index/hedit.html

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

+ 3 - 3
web/views/index/hlist.html

@@ -18,7 +18,7 @@
                 <div class="content">
                     <div class="table-responsive">
                         <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"
                                type="button" langtag="info-new">新增</a>
                         </div>
@@ -158,7 +158,7 @@
         if (confirm("Are you sure you want to delete it??")) {
             $.ajax({
                 type: "POST",
-                url: "/index/delhost",
+                url: "{{.web_base_url}}/index/delhost",
                 data: {"id": id},
                 success: function (res) {
                     alert(res.msg)
@@ -171,7 +171,7 @@
     }
 
     function edit(id) {
-        window.location.href = "/index/edithost?id=" + id
+        window.location.href = "{{.web_base_url}}/index/edithost?id=" + id
     }
 
 

+ 11 - 1
web/views/index/index.html

@@ -142,6 +142,16 @@
                                 </div>
                             </div>
                         </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>
                 </div>
             </div>
@@ -754,4 +764,4 @@
         this.myChart7.resize();
     });
 
-</script>
+</script>

+ 6 - 6
web/views/index/list.html

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

+ 7 - 7
web/views/login/index.html

@@ -8,10 +8,10 @@
 
     <title>nps admin login</title>
 
-    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
-    <link href="/static/font-awesome/css/font-awesome.css" rel="stylesheet">
+    <link href="{{.web_base_url}}/static/css/bootstrap.min.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>
 
@@ -49,7 +49,7 @@
                     </div>
                     <button onclick="login()" class="btn btn-primary block full-width m-b">login</button>
                 {{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}}
                 </form>
             </div>
@@ -59,7 +59,7 @@
 </div>
 
 </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>
 
 
@@ -68,11 +68,11 @@
     function login() {
         $.ajax({
             type: "POST",
-            url: "/login/verify",
+            url: "{{.web_base_url}}/login/verify",
             data: $("form").serializeArray(),
             success: function (res) {
                 if (res.status) {
-                    window.location.href = "/index/index"
+                    window.location.href = "{{.web_base_url}}/index/index"
                 } else {
                     alert(res.msg)
                 }

+ 7 - 7
web/views/login/register.html

@@ -8,10 +8,10 @@
 
     <title>nps register</title>
 
-    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
-    <link href="/static/font-awesome/css/font-awesome.css" rel="stylesheet">
+    <link href="{{.web_base_url}}/static/css/bootstrap.min.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>
 
@@ -34,22 +34,22 @@
             </div>
             <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>
     </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>
     function register() {
         $.ajax({
             type: "POST",
-            url: "/login/register",
+            url: "{{.web_base_url}}/login/register",
             data: $("form").serializeArray(),
             success: function (res) {
                 alert(res.msg)
                 if (res.status) {
-                    window.location.href = "/login/index"
+                    window.location.href = "{{.web_base_url}}/login/index"
                 }
             }
         })

+ 26 - 23
web/views/public/layout.html

@@ -9,22 +9,22 @@
 
     <title>nps admin</title>
 
-    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
-    <link href="/static/font-awesome/css/font-awesome.css" rel="stylesheet">
-    <link href="/static/css/style.css" rel="stylesheet">
-    <script src="/static/js/main.js"></script>
+    <link href="{{.web_base_url}}/static/css/bootstrap.min.css" rel="stylesheet">
+    <link href="{{.web_base_url}}/static/font-awesome/css/font-awesome.css" rel="stylesheet">
+    <link href="{{.web_base_url}}/static/css/style.css" rel="stylesheet">
+    <script src="{{.web_base_url}}/static/js/main.js"></script>
     <!-- Mainly scripts -->
-    <script src="/static/js/jquery-2.1.1.js"></script>
-    <script src="/static/js/bootstrap.min.js"></script>
-    <script src="/static/js/echarts.min.js"></script>
+    <script src="{{.web_base_url}}/static/js/jquery-2.1.1.js"></script>
+    <script src="{{.web_base_url}}/static/js/bootstrap.min.js"></script>
+    <script src="{{.web_base_url}}/static/js/echarts.min.js"></script>
     <!-- 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 -->
-    <script src="/static/js/bootstrap-table.min.js"></script>
-    <script src="/static/js/inspinia.js"></script>
+    <script src="{{.web_base_url}}/static/js/bootstrap-table.min.js"></script>
+    <script src="{{.web_base_url}}/static/js/inspinia.js"></script>
     <!-- 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>
 
@@ -35,7 +35,7 @@
             <ul class="nav metismenu" id="side-menu">
                 <li class="nav-header">
                     <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>
                         <a href="#">
                             <span class="clear"> <span class="block m-t-xs"> <strong class="font-bold">
@@ -53,42 +53,42 @@
                     </div>
                 </li>
                 <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>
                 </li>
                 <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>
                 </li>
                 <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>
                 </li>
                 <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>
                 </li>
                 <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>
                 </li>
                 <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>
                 </li>
                 <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 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>
                 </li>
                 <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>
                 </li>
                 <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>
                 </li>
             </ul>
@@ -115,7 +115,7 @@
                     </li>
 
                     <li>
-                        <a href="/login/out">
+                        <a href="{{.web_base_url}}/login/out">
                             <i class="fa fa-sign-out"></i> logout
                         </a>
                     </li>
@@ -142,6 +142,9 @@
 </html>
 
 <script>
+   window.nps = {
+       "web_base_url": {{.web_base_url}},
+   }
     // googleTranslateElementInit()
     //
     // function googleTranslateElementInit() {