刘河 6 anni fa
parent
commit
750ecb824a

+ 47 - 23
bridge/bridge.go

@@ -8,11 +8,11 @@ import (
 	"github.com/cnlh/nps/lib/conn"
 	"github.com/cnlh/nps/lib/crypt"
 	"github.com/cnlh/nps/lib/file"
-	"github.com/cnlh/nps/lib/lg"
 	"github.com/cnlh/nps/lib/pool"
+	"github.com/cnlh/nps/lib/version"
 	"github.com/cnlh/nps/server/tool"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"github.com/cnlh/nps/vender/github.com/xtaci/kcp"
-	"log"
 	"net"
 	"strconv"
 	"sync"
@@ -48,6 +48,7 @@ type Bridge struct {
 	tunnelType   string //bridge type kcp or tcp
 	OpenTask     chan *file.Tunnel
 	CloseClient  chan int
+	SecretChan   chan *conn.Secret
 	clientLock   sync.RWMutex
 	Register     map[string]time.Time
 	registerLock sync.RWMutex
@@ -65,6 +66,7 @@ func NewTunnel(tunnelPort int, tunnelType string, ipVerify bool, runList map[int
 	t.Register = make(map[string]time.Time)
 	t.ipVerify = ipVerify
 	t.runList = runList
+	t.SecretChan = make(chan *conn.Secret)
 	return t
 }
 
@@ -80,7 +82,7 @@ func (s *Bridge) StartTunnel() error {
 				c, err := s.kcpListener.AcceptKCP()
 				conn.SetUdpSession(c)
 				if err != nil {
-					lg.Println(err)
+					logs.Warn(err)
 					continue
 				}
 				go s.cliProcess(conn.NewConn(c))
@@ -95,7 +97,7 @@ func (s *Bridge) StartTunnel() error {
 			for {
 				c, err := s.tcpListener.Accept()
 				if err != nil {
-					lg.Println(err)
+					logs.Warn(err)
 					continue
 				}
 				go s.cliProcess(conn.NewConn(c))
@@ -116,6 +118,12 @@ func (s *Bridge) verifySuccess(c *conn.Conn) {
 }
 
 func (s *Bridge) cliProcess(c *conn.Conn) {
+	c.Write([]byte(crypt.Md5(version.GetVersion())))
+	if b, err := c.ReadFlag(); err != nil || string(b) != version.VERSION_OK {
+		logs.Info("The client %s version does not match", c.Conn.RemoteAddr())
+		c.Close()
+		return
+	}
 	c.SetReadDeadline(5, s.tunnelType)
 	var buf []byte
 	var err error
@@ -126,7 +134,7 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
 	//验证
 	id, err := file.GetCsvDb().GetIdByVerifyKey(string(buf), c.Conn.RemoteAddr().String())
 	if err != nil {
-		lg.Println("当前客户端连接校验错误,关闭此客户端:", c.Conn.RemoteAddr())
+		logs.Info("Current client connection validation error, close this client:", c.Conn.RemoteAddr())
 		s.verifyError(c)
 		return
 	} else {
@@ -136,7 +144,7 @@ func (s *Bridge) cliProcess(c *conn.Conn) {
 	if flag, err := c.ReadFlag(); err == nil {
 		s.typeDeal(flag, c, id)
 	} else {
-		log.Println(err, flag)
+		logs.Warn(err, flag)
 	}
 	return
 }
@@ -182,7 +190,7 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
 			s.Client[id] = NewClient(nil, c, nil)
 			s.clientLock.Unlock()
 		}
-		lg.Printf("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr())
+		logs.Info("clientId %d connection succeeded, address:%s ", id, c.Conn.RemoteAddr())
 		go s.GetStatus(id)
 	case common.WORK_CHAN:
 		s.clientLock.Lock()
@@ -200,6 +208,10 @@ func (s *Bridge) typeDeal(typeVal string, c *conn.Conn, id int) {
 		go s.GetConfig(c)
 	case common.WORK_REGISTER:
 		go s.register(c)
+	case common.WORD_SECRET:
+		if b, err := c.ReadLen(32); err == nil {
+			s.SecretChan <- conn.NewSecret(string(b), c)
+		}
 	case common.WORK_SEND_STATUS:
 		s.clientLock.Lock()
 		if v, ok := s.Client[id]; ok {
@@ -293,7 +305,7 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string) (t
 
 		v.signal.SendLinkInfo(link)
 		if err != nil {
-			lg.Println("send link information error:", err, link.Id)
+			logs.Warn("send link information error:", err, link.Id)
 			s.DelClient(clientId)
 			return
 		}
@@ -314,7 +326,7 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string) (t
 		}
 	} else {
 		s.clientLock.Unlock()
-		err = errors.New("the connection is not connect")
+		err = errors.New(fmt.Sprintf("the client %d is not connect", clientId))
 	}
 	return
 }
@@ -328,6 +340,7 @@ func (s *Bridge) DelClient(id int) {
 func (s *Bridge) GetConfig(c *conn.Conn) {
 	var client *file.Client
 	var fail bool
+
 	for {
 		flag, err := c.ReadFlag()
 		if err != nil {
@@ -357,19 +370,15 @@ func (s *Bridge) GetConfig(c *conn.Conn) {
 				binary.Write(c, binary.LittleEndian, []byte(str))
 			}
 		case common.NEW_CONF:
-			//new client ,Set the client not to store to the file
-			client = file.NewClient(crypt.GetRandomString(16), true, false)
-			client.Remark = "public veky"
-			//Send the key to the client
-			file.GetCsvDb().NewClient(client)
-			c.Write([]byte(client.VerifyKey))
-
-			if config, err := c.GetConfigInfo(); err != nil {
+			var err error
+			if client, err = c.GetConfigInfo(); err != nil {
+				c.Write([]byte(client.VerifyKey))
 				fail = true
 				c.WriteAddFail()
 				break
 			} else {
-				client.Cnf = config
+				c.Write([]byte(client.VerifyKey))
+				file.GetCsvDb().NewClient(client)
 				c.WriteAddOk()
 			}
 		case common.NEW_HOST:
@@ -380,6 +389,7 @@ func (s *Bridge) GetConfig(c *conn.Conn) {
 			} else if file.GetCsvDb().IsHostExist(h) {
 				fail = true
 				c.WriteAddFail()
+				break
 			} else {
 				h.Client = client
 				file.GetCsvDb().NewHost(h)
@@ -397,6 +407,13 @@ func (s *Bridge) GetConfig(c *conn.Conn) {
 					fail = true
 					c.WriteAddFail()
 					break
+				} else if t.Mode == "secretServer" {
+					ports = append(ports, 0)
+				}
+				if len(ports) == 0 {
+					fail = true
+					c.WriteAddFail()
+					break
 				}
 				for i := 0; i < len(ports); i++ {
 					tl := new(file.Tunnel)
@@ -407,17 +424,24 @@ func (s *Bridge) GetConfig(c *conn.Conn) {
 						tl.Remark = t.Remark
 					} else {
 						tl.Remark = t.Remark + "_" + strconv.Itoa(tl.Port)
-						tl.Target = strconv.Itoa(targets[i])
+						tl.Target = t.TargetAddr + ":" + strconv.Itoa(targets[i])
 					}
 					tl.Id = file.GetCsvDb().GetTaskId()
 					tl.Status = true
 					tl.Flow = new(file.Flow)
 					tl.NoStore = true
 					tl.Client = client
-					file.GetCsvDb().NewTask(tl)
-					if b := tool.TestServerPort(tl.Port, tl.Mode); !b {
+					tl.Password = t.Password
+					if err := file.GetCsvDb().NewTask(tl); err != nil {
+						logs.Notice("Add task error ", err.Error())
+						fail = true
+						c.WriteAddFail()
+						break
+					}
+					if b := tool.TestServerPort(tl.Port, tl.Mode); !b && t.Mode != "secretServer" {
 						fail = true
 						c.WriteAddFail()
+						break
 					} else {
 						s.OpenTask <- tl
 					}
@@ -460,7 +484,7 @@ func (s *Bridge) clientCopy(clientId int) {
 
 	for {
 		if id, err := client.tunnel.GetLen(); err != nil {
-			lg.Println("read msg content length error close client")
+			logs.Info("read msg content length error close client")
 			s.delClient(clientId)
 			break
 		} else {
@@ -470,7 +494,7 @@ func (s *Bridge) clientCopy(clientId int) {
 				if content, err := client.tunnel.GetMsgContent(link); err != nil {
 					pool.PutBufPoolCopy(content)
 					s.delClient(clientId)
-					lg.Println("read msg content error", err, "close client")
+					logs.Notice("read msg content error", err, "close client")
 					break
 				} else {
 					link.MsgCh <- content

+ 14 - 11
client/client.go

@@ -3,9 +3,10 @@ package client
 import (
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/conn"
-	"github.com/cnlh/nps/lib/lg"
 	"github.com/cnlh/nps/lib/pool"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"net"
+	"os"
 	"sync"
 	"time"
 )
@@ -40,11 +41,11 @@ func (s *TRPClient) Start() {
 retry:
 	c, err := NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_MAIN, s.proxyUrl)
 	if err != nil {
-		lg.Println("The connection server failed and will be reconnected in five seconds")
+		logs.Error("The connection server failed and will be reconnected in five seconds")
 		time.Sleep(time.Second * 5)
 		goto retry
 	}
-	lg.Printf("Successful connection with server %s", s.svrAddr)
+	logs.Info("Successful connection with server %s", s.svrAddr)
 	s.processor(c)
 }
 
@@ -65,12 +66,13 @@ func (s *TRPClient) processor(c *conn.Conn) {
 	for {
 		flags, err := c.ReadFlag()
 		if err != nil {
-			lg.Printf("Accept server data error %s, end this service", err.Error())
+			logs.Error("Accept server data error %s, end this service", err.Error())
 			break
 		}
 		switch flags {
 		case common.VERIFY_EER:
-			lg.Fatalf("VKey:%s is incorrect, the server refuses to connect, please check", s.vKey)
+			logs.Error("VKey:%s is incorrect, the server refuses to connect, please check", s.vKey)
+			os.Exit(0)
 		case common.NEW_CONN:
 			if link, err := c.GetLinkInfo(); err != nil {
 				break
@@ -83,12 +85,13 @@ func (s *TRPClient) processor(c *conn.Conn) {
 				link.Run(false)
 			}
 		case common.RES_CLOSE:
-			lg.Fatalln("The authentication key is connected by another client or the server closes the client.")
+			logs.Error("The authentication key is connected by another client or the server closes the client.")
+			os.Exit(0)
 		case common.RES_MSG:
-			lg.Println("Server-side return error")
+			logs.Error("Server-side return error")
 			break
 		default:
-			lg.Println("The error could not be resolved")
+			logs.Warn("The error could not be resolved")
 			break
 		}
 	}
@@ -103,7 +106,7 @@ func (s *TRPClient) linkProcess(link *conn.Link, c *conn.Conn) {
 
 	if err != nil {
 		c.WriteFail(link.Id)
-		lg.Println("connect to ", link.Host, "error:", err)
+		logs.Warn("connect to ", link.Host, "error:", err)
 		return
 	}
 	c.WriteSuccess(link.Id)
@@ -134,7 +137,7 @@ func (s *TRPClient) getMsgStatus() {
 	var err error
 	s.msgTunnel, err = NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_SEND_STATUS, s.proxyUrl)
 	if err != nil {
-		lg.Println("connect to ", s.svrAddr, "error:", err)
+		logs.Error("connect to ", s.svrAddr, "error:", err)
 		return
 	}
 	go func() {
@@ -160,7 +163,7 @@ func (s *TRPClient) dealChan() {
 	var err error
 	s.tunnel, err = NewConn(s.bridgeConnType, s.vKey, s.svrAddr, common.WORK_CHAN, s.proxyUrl)
 	if err != nil {
-		lg.Println("connect to ", s.svrAddr, "error:", err)
+		logs.Error("connect to ", s.svrAddr, "error:", err)
 		return
 	}
 	go func() {

+ 43 - 21
client/control.go

@@ -1,11 +1,14 @@
 package client
 
 import (
+	"encoding/binary"
 	"errors"
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/config"
 	"github.com/cnlh/nps/lib/conn"
-	"github.com/cnlh/nps/lib/lg"
+	"github.com/cnlh/nps/lib/crypt"
+	"github.com/cnlh/nps/lib/version"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"github.com/cnlh/nps/vender/github.com/xtaci/kcp"
 	"github.com/cnlh/nps/vender/golang.org/x/net/proxy"
 	"io/ioutil"
@@ -39,7 +42,7 @@ func GetTaskStatus(path string) {
 	if l, err := c.GetLen(); err != nil {
 		log.Fatalln(err)
 	} else if b, err := c.ReadLen(l); err != nil {
-		lg.Fatalln(err)
+		log.Fatalln(err)
 	} else {
 		arr := strings.Split(string(b), common.CONN_DATA_SEQ)
 		for _, v := range cnf.Hosts {
@@ -74,14 +77,16 @@ var errAdd = errors.New("The server returned an error, which port or host may ha
 func StartFromFile(path string) {
 	first := true
 	cnf, err := config.NewConfig(path)
-	if err != nil {
-		lg.Fatalln(err)
+	if err != nil || cnf.CommonConfig == nil {
+		logs.Error("Config file %s loading error", path)
+		os.Exit(0)
 	}
-	lg.Printf("Loading configuration file %s successfully", path)
+
+	logs.Info("Loading configuration file %s successfully", path)
 re:
 	if first || cnf.CommonConfig.AutoReconnection {
 		if !first {
-			lg.Println("Reconnecting...")
+			logs.Info("Reconnecting...")
 			time.Sleep(time.Second * 5)
 		}
 	} else {
@@ -90,48 +95,51 @@ re:
 	first = false
 	c, err := NewConn(cnf.CommonConfig.Tp, cnf.CommonConfig.VKey, cnf.CommonConfig.Server, common.WORK_CONFIG, cnf.CommonConfig.ProxyUrl)
 	if err != nil {
-		lg.Println(err)
+		logs.Error(err)
 		goto re
 	}
-	if _, err := c.SendConfigInfo(cnf.CommonConfig.Cnf); err != nil {
-		lg.Println(err)
+	if _, err := c.SendConfigInfo(cnf.CommonConfig); err != nil {
+		logs.Error(err)
 		goto re
 	}
 	var b []byte
 	if b, err = c.ReadLen(16); err != nil {
-		lg.Println(err)
+		logs.Error(err)
 		goto re
 	} else {
 		ioutil.WriteFile(filepath.Join(common.GetTmpPath(), "npc_vkey.txt"), []byte(string(b)), 0600)
 	}
 	if !c.GetAddStatus() {
-		lg.Println(errAdd)
+		logs.Error(errAdd)
 		goto re
 	}
+
 	for _, v := range cnf.Hosts {
 		if _, err := c.SendHostInfo(v); err != nil {
-			lg.Println(err)
+			logs.Error(err)
 			goto re
 		}
 		if !c.GetAddStatus() {
-			lg.Println(errAdd, v.Host)
+			logs.Error(errAdd, v.Host)
 			goto re
 		}
 	}
 	for _, v := range cnf.Tasks {
 		if _, err := c.SendTaskInfo(v); err != nil {
-			lg.Println(err)
+			logs.Error(err)
 			goto re
 		}
 		if !c.GetAddStatus() {
-			lg.Println(errAdd, v.Ports)
+			logs.Error(errAdd, v.Ports)
 			goto re
 		}
 	}
-
+	for _, v := range cnf.LocalServer {
+		go StartLocalServer(v, cnf.CommonConfig)
+	}
 	c.Close()
-
 	NewRPClient(cnf.CommonConfig.Server, string(b), cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl).Start()
+	CloseLocalServer()
 	goto re
 }
 
@@ -163,16 +171,30 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
 		return nil, err
 	}
 	c := conn.NewConn(connection)
+	if b, err := c.ReadLen(32); err != nil {
+		logs.Error(err)
+		os.Exit(0)
+	} else if crypt.Md5(version.GetVersion()) != string(b) {
+		logs.Error("The client does not match the server version. The current version of the client is", version.GetVersion())
+		os.Exit(0)
+	} else if binary.Write(c, binary.LittleEndian, []byte(version.VERSION_OK)); err != nil {
+		logs.Error(err)
+		os.Exit(0)
+	}
 	if _, err := c.Write([]byte(common.Getverifyval(vkey))); err != nil {
-		lg.Println(err)
+		logs.Error(err)
+		os.Exit(0)
 	}
 	if s, err := c.ReadFlag(); err != nil {
-		lg.Println(err)
+		logs.Error(err)
+		os.Exit(0)
 	} else if s == common.VERIFY_EER {
-		lg.Fatalf("Validation key %s incorrect", vkey)
+		logs.Error("Validation key %s incorrect", vkey)
+		os.Exit(0)
 	}
 	if _, err := c.Write([]byte(connType)); err != nil {
-		lg.Println(err)
+		logs.Error(err)
+		os.Exit(0)
 	}
 	c.SetAlive(tp)
 

+ 54 - 0
client/local.go

@@ -0,0 +1,54 @@
+package client
+
+import (
+	"github.com/cnlh/nps/lib/common"
+	"github.com/cnlh/nps/lib/config"
+	"github.com/cnlh/nps/lib/crypt"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
+	"net"
+	"strings"
+)
+
+var LocalServer []*net.TCPListener
+
+func CloseLocalServer() {
+	for _, v := range LocalServer {
+		v.Close()
+	}
+}
+
+func StartLocalServer(l *config.LocalServer, config *config.CommonConfig) error {
+	listener, err := net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), l.Port, ""})
+	if err != nil {
+		logs.Error("Local listener startup failed port %d, error %s", l.Port, err.Error())
+		return err
+	}
+	LocalServer = append(LocalServer, listener)
+	logs.Info("Successful start-up of local monitoring, port", l.Port)
+	for {
+		c, err := listener.AcceptTCP()
+		if err != nil {
+			if strings.Contains(err.Error(), "use of closed network connection") {
+				break
+			}
+			logs.Info(err)
+			continue
+		}
+		go process(c, config, l)
+	}
+	return nil
+}
+
+func process(conn net.Conn, config *config.CommonConfig, l *config.LocalServer) {
+	c, err := NewConn(config.Tp, config.VKey, config.Server, common.WORD_SECRET, config.ProxyUrl)
+	if err != nil {
+		logs.Error("Local connection server failed ", err.Error())
+	}
+	if _, err := c.Write([]byte(crypt.Md5(l.Password))); err != nil {
+		logs.Error("Local connection server failed ", err.Error())
+	}
+	go common.CopyBuffer(c, conn)
+	common.CopyBuffer(conn, c)
+	c.Close()
+	conn.Close()
+}

+ 5 - 6
cmd/npc/npc.go

@@ -5,14 +5,12 @@ import (
 	"github.com/cnlh/nps/client"
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/daemon"
-	"github.com/cnlh/nps/lib/lg"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"os"
 	"strings"
 	"time"
 )
 
-const VERSION = "v0.0.15"
-
 var (
 	serverAddr   = flag.String("server", "", "Server addr (ip:port)")
 	configPath   = flag.String("config", "npc.conf", "Configuration file path")
@@ -20,6 +18,7 @@ var (
 	logType      = flag.String("log", "stdout", "Log output mode(stdout|file)")
 	connType     = flag.String("type", "tcp", "Connection type with the server(kcp|tcp)")
 	proxyUrl     = flag.String("proxy", "", "proxy socks5 url(eg:socks5://111:222@127.0.0.1:9007)")
+	logLevel     = flag.String("log_level", "7", "log level 0~7")
 	registerTime = flag.Int("time", 2, "register time long /h")
 )
 
@@ -37,14 +36,14 @@ func main() {
 	}
 	daemon.InitDaemon("npc", common.GetRunPath(), common.GetTmpPath())
 	if *logType == "stdout" {
-		lg.InitLogFile("npc", true, common.GetLogPath())
+		logs.SetLogger(logs.AdapterConsole, `{"level":`+*logLevel+`,"color":true}`)
 	} else {
-		lg.InitLogFile("npc", false, common.GetLogPath())
+		logs.SetLogger(logs.AdapterFile, `{"level":`+*logLevel+`,"filename":"npc_log.log"}`)
 	}
 	if *verifyKey != "" && *serverAddr != "" {
 		for {
 			client.NewRPClient(*serverAddr, *verifyKey, *connType, *proxyUrl).Start()
-			lg.Println("It will be reconnected in five seconds")
+			logs.Info("It will be reconnected in five seconds")
 			time.Sleep(time.Second * 5)
 		}
 	} else {

+ 10 - 6
cmd/nps/nps.go

@@ -6,19 +6,18 @@ import (
 	"github.com/cnlh/nps/lib/daemon"
 	"github.com/cnlh/nps/lib/file"
 	"github.com/cnlh/nps/lib/install"
-	"github.com/cnlh/nps/lib/lg"
 	"github.com/cnlh/nps/server"
 	"github.com/cnlh/nps/server/test"
 	"github.com/cnlh/nps/vender/github.com/astaxie/beego"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	_ "github.com/cnlh/nps/web/routers"
 	"log"
 	"os"
 	"path/filepath"
 )
 
-const VERSION = "v0.0.15"
-
 var (
+	level   string
 	logType = flag.String("log", "stdout", "Log output mode(stdout|file)")
 )
 
@@ -37,17 +36,22 @@ func main() {
 			return
 		}
 	}
+	if level = beego.AppConfig.String("logLevel"); level == "" {
+		level = "7"
+	}
+	logs.Reset()
 	if *logType == "stdout" {
-		lg.InitLogFile("nps", true, common.GetLogPath())
+		logs.SetLogger(logs.AdapterConsole, `{"level":`+level+`,"color":true}`)
 	} else {
-		lg.InitLogFile("nps", false, common.GetLogPath())
+		logs.SetLogger(logs.AdapterFile, `{"level":`+level+`,"filename":"nps_log.log"}`)
 	}
 	task := &file.Tunnel{
 		Mode: "webServer",
 	}
 	bridgePort, err := beego.AppConfig.Int("bridgePort")
 	if err != nil {
-		lg.Fatalln("Getting bridgePort error", err)
+		logs.Error("Getting bridgePort error", err)
+		os.Exit(0)
 	}
 	beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "app.conf"))
 	server.StartNewServer(bridgePort, task, beego.AppConfig.String("bridgeType"))

+ 8 - 1
conf/app.conf

@@ -36,4 +36,11 @@ bridgeType=tcp
 # After the connection, the server will be able to open relevant ports and parse related domain names according to its own configuration file.
 publicVkey=123
 
-
+#Traffic data persistence interval(minute)
+#Ignorance means no persistence
+flowStoreInterval=1
+
+#log level
+#LevelEmergency->0  LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4
+#LevelNotice->5 LevelInformational->6 LevelDebug->7
+logLevel=7

+ 1 - 1
conf/clients.csv

@@ -1 +1 @@
-7,7hv3avgeg2ldzvx7,,true,,,0,,0,0
+7,7hv3avgeg2ldzvx7,,true,,,1,,0,999,10

+ 1 - 0
conf/hosts.csv

@@ -0,0 +1 @@
+b.o.com,127.0.0.1:8082,7,,,,/,3,12995709,38827

+ 12 - 20
conf/npc.conf

@@ -4,32 +4,24 @@ tp=tcp
 vkey=123
 auto_reconnection=true
 crypt=true
+
 [web1]
 host=a.o.com
 host_change=www.proxy.com
-target=127.0.0.1:8082
+target=127.0.0.1:8080
 location=/
 
 [web2]
-host=a.proxy.com
-target=127.0.0.1:8080,127.0.0.1:8082
+host=127.0.0.1
 host_change=www.proxy.com
-header_set_proxy=nps
-
-[tcp]
-mode=tcpServer
-target=8001-8005,8082
-port=9001-9005,9006
-
-[socks5]
-mode=socks5Server
-port=9007
+target=127.0.0.1:8080
+location=/cdn
 
-[http]
-mode=httpProxyServer
-port=9008
+[ssh_1118]
+mode=secretServer
+password=1111
+target=123.206.77.88:22
 
-[udp]
-mode=udpServer
-port=53
-target=114.114.114.114:53
+[secret_ssh]
+password=1111
+port=1000

+ 3 - 1
conf/tasks.csv

@@ -1 +1,3 @@
-9010,socks5Server,,1,27,7,
+9010,socks5Server,,1,27,7,,0,0,
+9002,tcpServer,127.0.0.1:8082,1,28,7,,9175971,17054,
+0,secretServer,123.206.77.88:22,1,30,7,私密隧道测试,15620,12628,tests

+ 16 - 16
lib/common/const.go

@@ -6,22 +6,22 @@ const (
 	COMPRESS_NONE_DECODE
 	COMPRESS_SNAPY_ENCODE
 	COMPRESS_SNAPY_DECODE
-	VERIFY_EER       = "vkey"
-	VERIFY_SUCCESS   = "sucs"
-	WORK_MAIN        = "main"
-	WORK_CHAN        = "chan"
-	WORK_SEND_STATUS = "sdst"
-	WORK_CONFIG      = "conf"
-	WORK_REGISTER    = "rgst"
-	WORK_STATUS      = "stus"
-	RES_SIGN         = "sign"
-	RES_MSG          = "msg0"
-	RES_CLOSE        = "clse"
-	NEW_CONN         = "conn" //新连接标志
-	NEW_TASK         = "task" //新连接标志
-	NEW_CONF         = "conf" //新连接标志
-	NEW_HOST         = "host" //新连接标志
-
+	VERIFY_EER        = "vkey"
+	VERIFY_SUCCESS    = "sucs"
+	WORK_MAIN         = "main"
+	WORK_CHAN         = "chan"
+	WORK_SEND_STATUS  = "sdst"
+	WORK_CONFIG       = "conf"
+	WORK_REGISTER     = "rgst"
+	WORD_SECRET       = "sert"
+	WORK_STATUS       = "stus"
+	RES_SIGN          = "sign"
+	RES_MSG           = "msg0"
+	RES_CLOSE         = "clse"
+	NEW_CONN          = "conn" //新连接标志
+	NEW_TASK          = "task" //新连接标志
+	NEW_CONF          = "conf" //新连接标志
+	NEW_HOST          = "host" //新连接标志
 	CONN_TCP          = "tcp"
 	CONN_UDP          = "udp"
 	UnauthorizedBytes = `HTTP/1.1 401 Unauthorized

+ 9 - 0
lib/common/util.go

@@ -5,6 +5,8 @@ import (
 	"encoding/base64"
 	"encoding/binary"
 	"github.com/cnlh/nps/lib/crypt"
+	"github.com/cnlh/nps/lib/pool"
+	"io"
 	"io/ioutil"
 	"net"
 	"net/http"
@@ -246,3 +248,10 @@ func GetIpByAddr(addr string) string {
 	arr := strings.Split(addr, ":")
 	return arr[0]
 }
+
+func CopyBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
+	buf := pool.BufPoolCopy.Get().([]byte)
+	io.CopyBuffer(dst, src, buf)
+	pool.PutBufPoolCopy(buf)
+	return written, err
+}

+ 54 - 0
lib/config/config.go

@@ -1,6 +1,7 @@
 package config
 
 import (
+	"errors"
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/file"
 	"regexp"
@@ -14,6 +15,11 @@ type CommonConfig struct {
 	AutoReconnection bool
 	Cnf              *file.Config
 	ProxyUrl         string
+	Client           *file.Client
+}
+type LocalServer struct {
+	Port     int
+	Password string
 }
 type Config struct {
 	content      string
@@ -21,6 +27,7 @@ type Config struct {
 	CommonConfig *CommonConfig
 	Hosts        []*file.Host
 	Tasks        []*file.Tunnel
+	LocalServer  []*LocalServer
 }
 
 func NewConfig(path string) (c *Config, err error) {
@@ -44,6 +51,11 @@ func NewConfig(path string) (c *Config, err error) {
 				nextIndex = len(c.content)
 			}
 			nowContent = c.content[nowIndex:nextIndex]
+
+			if strings.Index(getTitleContent(c.title[i]), "secret") == 0 {
+				c.LocalServer = append(c.LocalServer, delLocalService(nowContent))
+				continue
+			}
 			switch c.title[i] {
 			case "[common]":
 				c.CommonConfig = dealCommon(nowContent)
@@ -68,9 +80,11 @@ func getTitleContent(s string) string {
 	re, _ := regexp.Compile(`[\[\]]`)
 	return re.ReplaceAllString(s, "")
 }
+
 func dealCommon(s string) *CommonConfig {
 	c := &CommonConfig{}
 	c.Cnf = new(file.Config)
+	c.Client = file.NewClient("", true, true)
 	for _, v := range strings.Split(s, "\n") {
 		item := strings.Split(v, "=")
 		if len(item) == 0 {
@@ -97,10 +111,19 @@ func dealCommon(s string) *CommonConfig {
 			c.Cnf.Crypt = common.GetBoolByStr(item[1])
 		case "proxy_socks5_url":
 			c.ProxyUrl = item[1]
+		case "rate_limit":
+			c.Client.RateLimit = common.GetIntNoErrByStr(item[1])
+		case "flow_limit":
+			c.Client.Flow.FlowLimit = int64(common.GetIntNoErrByStr(item[1]))
+		case "max_conn":
+			c.Client.MaxConn = common.GetIntNoErrByStr(item[1])
+		case "remark":
+			c.Client.Remark = item[1]
 		}
 	}
 	return c
 }
+
 func dealHost(s string) *file.Host {
 	h := &file.Host{}
 	var headerChange string
@@ -146,12 +169,35 @@ func dealTunnel(s string) *file.Tunnel {
 			t.Mode = item[1]
 		case "target":
 			t.Target = item[1]
+		case "targetAddr":
+			t.TargetAddr = item[1]
+		case "password":
+			t.Password = item[1]
 		}
 	}
 	return t
 
 }
 
+func delLocalService(s string) *LocalServer {
+	l := new(LocalServer)
+	for _, v := range strings.Split(s, "\n") {
+		item := strings.Split(v, "=")
+		if len(item) == 0 {
+			continue
+		} else if len(item) == 1 {
+			item = append(item, "")
+		}
+		switch item[0] {
+		case "port":
+			l.Port = common.GetIntNoErrByStr(item[1])
+		case "password":
+			l.Password = item[1]
+		}
+	}
+	return l
+}
+
 func getAllTitle(content string) (arr []string, err error) {
 	var re *regexp.Regexp
 	re, err = regexp.Compile(`\[.+?\]`)
@@ -159,5 +205,13 @@ func getAllTitle(content string) (arr []string, err error) {
 		return
 	}
 	arr = re.FindAllString(content, -1)
+	m := make(map[string]bool)
+	for _, v := range arr {
+		if _, ok := m[v]; ok {
+			err = errors.New("Item names are not allowed to be duplicated")
+			return
+		}
+		m[v] = true
+	}
 	return
 }

+ 19 - 10
lib/conn/conn.go

@@ -6,6 +6,8 @@ import (
 	"encoding/binary"
 	"errors"
 	"github.com/cnlh/nps/lib/common"
+	"github.com/cnlh/nps/lib/config"
+	"github.com/cnlh/nps/lib/crypt"
 	"github.com/cnlh/nps/lib/file"
 	"github.com/cnlh/nps/lib/pool"
 	"github.com/cnlh/nps/lib/rate"
@@ -317,7 +319,7 @@ func (s *Conn) GetHostInfo() (h *file.Host, err error) {
 }
 
 //send task info
-func (s *Conn) SendConfigInfo(c *file.Config) (int, error) {
+func (s *Conn) SendConfigInfo(c *config.CommonConfig) (int, error) {
 	/*
 		The task info is formed as follows:
 		+----+-----+---------+
@@ -328,14 +330,15 @@ func (s *Conn) SendConfigInfo(c *file.Config) (int, error) {
 	*/
 	raw := bytes.NewBuffer([]byte{})
 	binary.Write(raw, binary.LittleEndian, []byte(common.NEW_CONF))
-	common.BinaryWrite(raw, c.U, c.P, common.GetStrByBool(c.Crypt), c.Compress)
+	common.BinaryWrite(raw, c.Cnf.U, c.Cnf.P, common.GetStrByBool(c.Cnf.Crypt), c.Cnf.Compress, strconv.Itoa(c.Client.RateLimit),
+		strconv.Itoa(int(c.Client.Flow.FlowLimit)), strconv.Itoa(c.Client.MaxConn), c.Client.Remark)
 	s.Lock()
 	defer s.Unlock()
 	return s.Write(raw.Bytes())
 }
 
 //get task info
-func (s *Conn) GetConfigInfo() (c *file.Config, err error) {
+func (s *Conn) GetConfigInfo() (c *file.Client, err error) {
 	var l int
 	var b []byte
 	if l, err = s.GetLen(); err != nil {
@@ -344,12 +347,16 @@ func (s *Conn) GetConfigInfo() (c *file.Config, err error) {
 		return
 	} else {
 		arr := strings.Split(string(b), common.CONN_DATA_SEQ)
-		c = new(file.Config)
-		c.U = arr[0]
-		c.P = arr[1]
-		c.Crypt = common.GetBoolByStr(arr[2])
-		c.Compress = arr[3]
-		c.CompressDecode, c.CompressDecode = common.GetCompressType(arr[3])
+		c = file.NewClient(crypt.GetRandomString(16), true, false)
+		c.Cnf.U = arr[0]
+		c.Cnf.P = arr[1]
+		c.Cnf.Crypt = common.GetBoolByStr(arr[2])
+		c.Cnf.Compress = arr[3]
+		c.RateLimit = common.GetIntNoErrByStr(arr[4])
+		c.Flow.FlowLimit = int64(common.GetIntNoErrByStr(arr[5]))
+		c.MaxConn = common.GetIntNoErrByStr(arr[6])
+		c.Remark = arr[7]
+		c.Cnf.CompressDecode, c.Cnf.CompressDecode = common.GetCompressType(arr[3])
 	}
 	return
 }
@@ -366,7 +373,7 @@ func (s *Conn) SendTaskInfo(t *file.Tunnel) (int, error) {
 	*/
 	raw := bytes.NewBuffer([]byte{})
 	binary.Write(raw, binary.LittleEndian, []byte(common.NEW_TASK))
-	common.BinaryWrite(raw, t.Mode, t.Ports, t.Target, t.Remark)
+	common.BinaryWrite(raw, t.Mode, t.Ports, t.Target, t.Remark, t.TargetAddr, t.Password)
 	s.Lock()
 	defer s.Unlock()
 	return s.Write(raw.Bytes())
@@ -390,6 +397,8 @@ func (s *Conn) GetTaskInfo() (t *file.Tunnel, err error) {
 		t.Status = true
 		t.Flow = new(file.Flow)
 		t.Remark = arr[3]
+		t.TargetAddr = arr[4]
+		t.Password = arr[5]
 		t.NoStore = true
 	}
 	return

+ 12 - 0
lib/conn/link.go

@@ -8,6 +8,18 @@ import (
 	"net"
 )
 
+type Secret struct {
+	Password string
+	Conn     *Conn
+}
+
+func NewSecret(p string, conn *Conn) *Secret {
+	return &Secret{
+		Password: p,
+		Conn:     conn,
+	}
+}
+
 type Link struct {
 	Id            int    //id
 	ConnType      string //连接类型

+ 3 - 1
lib/file/csv.go

@@ -14,7 +14,9 @@ var (
 func GetCsvDb() *Csv {
 	once.Do(func() {
 		CsvDb = NewCsv(common.GetRunPath())
-		CsvDb.Init()
+		CsvDb.LoadClientFromCsv()
+		CsvDb.LoadTaskFromCsv()
+		CsvDb.LoadHostFromCsv()
 	})
 	return CsvDb
 }

+ 53 - 22
lib/file/file.go

@@ -3,9 +3,11 @@ package file
 import (
 	"encoding/csv"
 	"errors"
+	"fmt"
 	"github.com/cnlh/nps/lib/common"
-	"github.com/cnlh/nps/lib/lg"
+	"github.com/cnlh/nps/lib/crypt"
 	"github.com/cnlh/nps/lib/rate"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"net/http"
 	"os"
 	"path/filepath"
@@ -33,17 +35,11 @@ type Csv struct {
 	sync.Mutex
 }
 
-func (s *Csv) Init() {
-	s.LoadClientFromCsv()
-	s.LoadTaskFromCsv()
-	s.LoadHostFromCsv()
-}
-
 func (s *Csv) StoreTasksToCsv() {
 	// 创建文件
 	csvFile, err := os.Create(filepath.Join(s.RunPath, "conf", "tasks.csv"))
 	if err != nil {
-		lg.Fatalf(err.Error())
+		logs.Error(err.Error())
 	}
 	defer csvFile.Close()
 	writer := csv.NewWriter(csvFile)
@@ -59,10 +55,13 @@ func (s *Csv) StoreTasksToCsv() {
 			strconv.Itoa(task.Id),
 			strconv.Itoa(task.Client.Id),
 			task.Remark,
+			strconv.Itoa(int(task.Flow.ExportFlow)),
+			strconv.Itoa(int(task.Flow.InletFlow)),
+			task.Password,
 		}
 		err := writer.Write(record)
 		if err != nil {
-			lg.Fatalf(err.Error())
+			logs.Error(err.Error())
 		}
 	}
 	writer.Flush()
@@ -90,20 +89,24 @@ func (s *Csv) LoadTaskFromCsv() {
 	path := filepath.Join(s.RunPath, "conf", "tasks.csv")
 	records, err := s.openFile(path)
 	if err != nil {
-		lg.Fatalln("配置文件打开错误:", path)
+		logs.Error("Profile Opening Error:", path)
+		os.Exit(0)
 	}
 	var tasks []*Tunnel
 	// 将每一行数据保存到内存slice中
 	for _, item := range records {
 		post := &Tunnel{
-			Port:   common.GetIntNoErrByStr(item[0]),
-			Mode:   item[1],
-			Target: item[2],
-			Status: common.GetBoolByStr(item[3]),
-			Id:     common.GetIntNoErrByStr(item[4]),
-			Remark: item[6],
+			Port:     common.GetIntNoErrByStr(item[0]),
+			Mode:     item[1],
+			Target:   item[2],
+			Status:   common.GetBoolByStr(item[3]),
+			Id:       common.GetIntNoErrByStr(item[4]),
+			Remark:   item[6],
+			Password: item[9],
 		}
 		post.Flow = new(Flow)
+		post.Flow.ExportFlow = int64(common.GetIntNoErrByStr(item[7]))
+		post.Flow.InletFlow = int64(common.GetIntNoErrByStr(item[8]))
 		if post.Client, err = s.GetClient(common.GetIntNoErrByStr(item[5])); err != nil {
 			continue
 		}
@@ -142,10 +145,16 @@ func (s *Csv) GetIdByVerifyKey(vKey string, addr string) (int, error) {
 	return 0, errors.New("not found")
 }
 
-func (s *Csv) NewTask(t *Tunnel) {
+func (s *Csv) NewTask(t *Tunnel) error {
+	for _, v := range s.Tasks {
+		if v.Mode == "secretServer" && v.Password == t.Password {
+			return errors.New(fmt.Sprintf("Secret mode keys %s must be unique", t.Password))
+		}
+	}
 	t.Flow = new(Flow)
 	s.Tasks = append(s.Tasks, t)
 	s.StoreTasksToCsv()
+	return nil
 }
 
 func (s *Csv) UpdateTask(t *Tunnel) error {
@@ -171,6 +180,16 @@ func (s *Csv) DelTask(id int) error {
 	return errors.New("不存在")
 }
 
+//md5 password
+func (s *Csv) GetSecretTask(p string) *Tunnel {
+	for _, v := range s.Tasks {
+		if crypt.Md5(v.Password) == p {
+			return v
+		}
+	}
+	return nil
+}
+
 func (s *Csv) GetTask(id int) (v *Tunnel, err error) {
 	for _, v = range s.Tasks {
 		if v.Id == id {
@@ -205,6 +224,8 @@ func (s *Csv) StoreHostToCsv() {
 			host.Remark,
 			host.Location,
 			strconv.Itoa(host.Id),
+			strconv.Itoa(int(host.Flow.ExportFlow)),
+			strconv.Itoa(int(host.Flow.InletFlow)),
 		}
 		err1 := writer.Write(record)
 		if err1 != nil {
@@ -219,7 +240,8 @@ func (s *Csv) LoadClientFromCsv() {
 	path := filepath.Join(s.RunPath, "conf", "clients.csv")
 	records, err := s.openFile(path)
 	if err != nil {
-		lg.Fatalln("配置文件打开错误:", path)
+		logs.Error("Profile Opening Error:", path)
+		os.Exit(0)
 	}
 	var clients []*Client
 	// 将每一行数据保存到内存slice中
@@ -236,6 +258,7 @@ func (s *Csv) LoadClientFromCsv() {
 				Crypt:    common.GetBoolByStr(item[6]),
 				Compress: item[7],
 			},
+			MaxConn: common.GetIntNoErrByStr(item[10]),
 		}
 		if post.Id > s.ClientIncreaseId {
 			s.ClientIncreaseId = post.Id
@@ -255,7 +278,8 @@ func (s *Csv) LoadHostFromCsv() {
 	path := filepath.Join(s.RunPath, "conf", "hosts.csv")
 	records, err := s.openFile(path)
 	if err != nil {
-		lg.Fatalln("配置文件打开错误:", path)
+		logs.Error("Profile Opening Error:", path)
+		os.Exit(0)
 	}
 	var hosts []*Host
 	// 将每一行数据保存到内存slice中
@@ -273,6 +297,8 @@ func (s *Csv) LoadHostFromCsv() {
 			continue
 		}
 		post.Flow = new(Flow)
+		post.Flow.ExportFlow = int64(common.GetIntNoErrByStr(item[8]))
+		post.Flow.InletFlow = int64(common.GetIntNoErrByStr(item[9]))
 		hosts = append(hosts, post)
 		if post.Id > s.HostIncreaseId {
 			s.HostIncreaseId = post.Id
@@ -350,7 +376,9 @@ func (s *Csv) NewClient(c *Client) {
 	if c.Id == 0 {
 		c.Id = s.GetClientId()
 	}
-	c.Flow = new(Flow)
+	if c.Flow == nil {
+		c.Flow = new(Flow)
+	}
 	s.Lock()
 	defer s.Unlock()
 	s.Clients = append(s.Clients, c)
@@ -433,6 +461,8 @@ func (s *Csv) GetHostById(id int) (h *Host, err error) {
 //get key by host from x
 func (s *Csv) GetInfoByHost(host string, r *http.Request) (h *Host, err error) {
 	var hosts []*Host
+	//Handling Ported Access
+	host = common.GetIpByAddr(host)
 	for _, v := range s.Hosts {
 		//Remove http(s) http(s)://a.proxy.com
 		//*.proxy.com *.a.proxy.com  Do some pan-parsing
@@ -467,7 +497,7 @@ func (s *Csv) StoreClientsToCsv() {
 	// 创建文件
 	csvFile, err := os.Create(filepath.Join(s.RunPath, "conf", "clients.csv"))
 	if err != nil {
-		lg.Fatalln(err.Error())
+		logs.Error(err.Error())
 	}
 	defer csvFile.Close()
 	writer := csv.NewWriter(csvFile)
@@ -486,10 +516,11 @@ func (s *Csv) StoreClientsToCsv() {
 			client.Cnf.Compress,
 			strconv.Itoa(client.RateLimit),
 			strconv.Itoa(int(client.Flow.FlowLimit)),
+			strconv.Itoa(int(client.MaxConn)),
 		}
 		err := writer.Write(record)
 		if err != nil {
-			lg.Fatalln(err.Error())
+			logs.Error(err.Error())
 		}
 	}
 	writer.Flush()

+ 35 - 23
lib/file/obj.go

@@ -33,6 +33,8 @@ type Client struct {
 	Rate      *rate.Rate //速度控制
 	NoStore   bool
 	NoDisplay bool
+	MaxConn   int //客户端最大连接数
+	NowConn   int //当前连接数
 	id        int
 	sync.RWMutex
 }
@@ -62,18 +64,40 @@ func (s *Client) GetId() int {
 	return s.id
 }
 
+func (s *Client) CutConn() {
+	s.Lock()
+	defer s.Unlock()
+	s.NowConn++
+}
+
+func (s *Client) AddConn() {
+	s.Lock()
+	defer s.Unlock()
+	s.NowConn--
+}
+
+func (s *Client) GetConn() bool {
+	s.CutConn()
+	if s.MaxConn == 0 || s.NowConn < s.MaxConn {
+		return true
+	}
+	return false
+}
+
 type Tunnel struct {
-	Id        int     //Id
-	Port      int     //服务端监听端口
-	Mode      string  //启动方式
-	Target    string  //目标
-	Status    bool    //设置是否开启
-	RunStatus bool    //当前运行状态
-	Client    *Client //所属客户端id
-	Ports     string  //客户端与服务端传递
-	Flow      *Flow
-	Remark    string //备注
-	NoStore   bool
+	Id         int     //Id
+	Port       int     //服务端监听端口
+	Mode       string  //启动方式
+	Target     string  //目标
+	Status     bool    //设置是否开启
+	RunStatus  bool    //当前运行状态
+	Client     *Client //所属客户端id
+	Ports      string  //客户端与服务端传递
+	Flow       *Flow
+	Password   string //私密模式密码,唯一
+	Remark     string //备注
+	TargetAddr string
+	NoStore    bool
 }
 
 type Config struct {
@@ -114,15 +138,3 @@ func (s *Host) GetRandomTarget() string {
 	}
 	return s.TargetArr[s.NowIndex]
 }
-
-//深拷贝Config
-func DeepCopyConfig(c *Config) *Config {
-	return &Config{
-		U:              c.U,
-		P:              c.P,
-		Compress:       c.Compress,
-		Crypt:          c.Crypt,
-		CompressEncode: c.CompressEncode,
-		CompressDecode: c.CompressDecode,
-	}
-}

+ 0 - 45
lib/lg/log.go

@@ -1,45 +0,0 @@
-package lg
-
-import (
-	"log"
-	"os"
-	"path/filepath"
-	"runtime"
-)
-
-var Log *log.Logger
-
-func InitLogFile(f string, isStdout bool, logPath string) {
-	var prefix string
-	if !isStdout {
-		logFile, err := os.OpenFile(filepath.Join(logPath, f+"_log.txt"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
-		if err != nil {
-			log.Fatalln("open file error !", err)
-		}
-		if runtime.GOOS == "windows" {
-			prefix = "\r\n"
-		}
-		Log = log.New(logFile, prefix, log.Ldate|log.Ltime)
-	} else {
-		Log = log.New(os.Stdout, "", log.Ldate|log.Ltime)
-	}
-}
-
-func Println(v ...interface{}) {
-	Log.Println(v...)
-}
-
-func Fatalln(v ...interface{}) {
-	Log.SetPrefix("error ")
-	Log.Fatalln(v...)
-	Log.SetPrefix("")
-}
-func Fatalf(format string, v ...interface{}) {
-	Log.SetPrefix("error ")
-	Log.Fatalf(format, v...)
-	Log.SetPrefix("")
-}
-
-func Printf(format string, v ...interface{}) {
-	Log.Printf(format, v...)
-}

+ 8 - 0
lib/version/version.go

@@ -0,0 +1,8 @@
+package version
+
+const VERSION = "0.0.16"
+const VERSION_OK = "vrok"
+
+func GetVersion() string {
+	return VERSION
+}

+ 32 - 8
server/proxy/base.go

@@ -17,8 +17,8 @@ type Service interface {
 	Close() error
 }
 
-//server base struct
-type server struct {
+//Server BaseServer struct
+type BaseServer struct {
 	id           int
 	bridge       *bridge.Bridge
 	task         *file.Tunnel
@@ -26,21 +26,30 @@ type server struct {
 	sync.Mutex
 }
 
-func (s *server) FlowAdd(in, out int64) {
+func NewBaseServer(bridge *bridge.Bridge, task *file.Tunnel) *BaseServer {
+	return &BaseServer{
+		bridge:       bridge,
+		task:         task,
+		errorContent: nil,
+		Mutex:        sync.Mutex{},
+	}
+}
+
+func (s *BaseServer) FlowAdd(in, out int64) {
 	s.Lock()
 	defer s.Unlock()
 	s.task.Flow.ExportFlow += out
 	s.task.Flow.InletFlow += in
 }
 
-func (s *server) FlowAddHost(host *file.Host, in, out int64) {
+func (s *BaseServer) FlowAddHost(host *file.Host, in, out int64) {
 	s.Lock()
 	defer s.Unlock()
 	host.Flow.ExportFlow += out
 	host.Flow.InletFlow += in
 }
 
-func (s *server) linkCopy(link *conn.Link, c *conn.Conn, rb []byte, tunnel *conn.Conn, flow *file.Flow) {
+func (s *BaseServer) linkCopy(link *conn.Link, c *conn.Conn, rb []byte, tunnel *conn.Conn, flow *file.Flow) {
 	if rb != nil {
 		if _, err := tunnel.SendMsg(rb, link); err != nil {
 			c.Close()
@@ -68,16 +77,17 @@ func (s *server) linkCopy(link *conn.Link, c *conn.Conn, rb []byte, tunnel *conn
 		}
 		<-link.StatusCh
 	}
+	s.task.Client.AddConn()
 	pool.PutBufPoolCopy(buf)
 }
 
-func (s *server) writeConnFail(c net.Conn) {
+func (s *BaseServer) writeConnFail(c net.Conn) {
 	c.Write([]byte(common.ConnectionFailBytes))
 	c.Write(s.errorContent)
 }
 
 //权限认证
-func (s *server) auth(r *http.Request, c *conn.Conn, u, p string) error {
+func (s *BaseServer) auth(r *http.Request, c *conn.Conn, u, p string) error {
 	if u != "" && p != "" && !common.CheckAuth(r, u, p) {
 		c.Write([]byte(common.UnauthorizedBytes))
 		c.Close()
@@ -86,9 +96,23 @@ func (s *server) auth(r *http.Request, c *conn.Conn, u, p string) error {
 	return nil
 }
 
-func (s *server) checkFlow() error {
+func (s *BaseServer) checkFlow() error {
 	if s.task.Client.Flow.FlowLimit > 0 && (s.task.Client.Flow.FlowLimit<<20) < (s.task.Client.Flow.ExportFlow+s.task.Client.Flow.InletFlow) {
 		return errors.New("Traffic exceeded")
 	}
 	return nil
 }
+
+//与客户端建立通道
+func (s *BaseServer) DealClient(c *conn.Conn, addr string, rb []byte) error {
+	link := conn.NewLink(s.task.Client.GetId(), common.CONN_TCP, addr, s.task.Client.Cnf.CompressEncode, s.task.Client.Cnf.CompressDecode, s.task.Client.Cnf.Crypt, c, s.task.Flow, nil, s.task.Client.Rate, nil)
+
+	if tunnel, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.Conn.RemoteAddr().String()); err != nil {
+		c.Close()
+		return err
+	} else {
+		link.Run(true)
+		s.linkCopy(link, c, rb, tunnel, s.task.Flow)
+	}
+	return nil
+}

+ 27 - 12
server/proxy/http.go

@@ -7,17 +7,18 @@ import (
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/conn"
 	"github.com/cnlh/nps/lib/file"
-	"github.com/cnlh/nps/lib/lg"
 	"github.com/cnlh/nps/vender/github.com/astaxie/beego"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"net/http"
 	"net/http/httputil"
+	"os"
 	"path/filepath"
 	"strconv"
 	"sync"
 )
 
 type httpServer struct {
-	server
+	BaseServer
 	httpPort  int //http端口
 	httpsPort int //https监听端口
 	pemPath   string
@@ -31,7 +32,7 @@ func NewHttp(bridge *bridge.Bridge, c *file.Tunnel) *httpServer {
 	pemPath := beego.AppConfig.String("pemPath")
 	keyPath := beego.AppConfig.String("keyPath")
 	return &httpServer{
-		server: server{
+		BaseServer: BaseServer{
 			task:   c,
 			bridge: bridge,
 			Mutex:  sync.Mutex{},
@@ -54,26 +55,30 @@ func (s *httpServer) Start() error {
 	if s.httpPort > 0 {
 		http = s.NewServer(s.httpPort)
 		go func() {
-			lg.Println("Start http listener, port is", s.httpPort)
+			logs.Info("Start http listener, port is", s.httpPort)
 			err := http.ListenAndServe()
 			if err != nil {
-				lg.Fatalln(err)
+				logs.Error(err)
+				os.Exit(0)
 			}
 		}()
 	}
 	if s.httpsPort > 0 {
 		if !common.FileExists(s.pemPath) {
-			lg.Fatalf("ssl certFile %s is not exist", s.pemPath)
+			logs.Error("ssl certFile %s is not exist", s.pemPath)
+			os.Exit(0)
 		}
 		if !common.FileExists(s.keyPath) {
-			lg.Fatalf("ssl keyFile %s exist", s.keyPath)
+			logs.Error("ssl keyFile %s exist", s.keyPath)
+			os.Exit(0)
 		}
 		https = s.NewServer(s.httpsPort)
 		go func() {
-			lg.Println("Start https listener, port is", s.httpsPort)
+			logs.Info("Start https listener, port is", s.httpsPort)
 			err := https.ListenAndServeTLS(s.pemPath, s.keyPath)
 			if err != nil {
-				lg.Fatalln(err)
+				logs.Error(err)
+				os.Exit(0)
 			}
 		}()
 	}
@@ -118,9 +123,14 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) {
 		err      error
 	)
 	if host, err = file.GetCsvDb().GetInfoByHost(r.Host, r); err != nil {
-		lg.Printf("the url %s %s can't be parsed!", r.Host, r.RequestURI)
+		logs.Notice("the url %s %s can't be parsed!", r.Host, r.RequestURI)
 		goto end
+	} else if !host.Client.GetConn() {
+		logs.Notice("Connections exceed the current client %d limit", host.Client.Id)
+		c.Close()
+		return
 	} else {
+		logs.Trace("New http(s) connection,clientId %d,host %s,url %s,remote address %s", host.Client.Id, r.Host, r.URL, r.RemoteAddr)
 		lastHost = host
 	}
 	for {
@@ -137,22 +147,24 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) {
 			}
 			lk = conn.NewLink(host.Client.GetId(), common.CONN_TCP, host.GetRandomTarget(), host.Client.Cnf.CompressEncode, host.Client.Cnf.CompressDecode, host.Client.Cnf.Crypt, c, host.Flow, nil, host.Client.Rate, nil)
 			if tunnel, err = s.bridge.SendLinkInfo(host.Client.Id, lk, c.Conn.RemoteAddr().String()); err != nil {
-				lg.Println(err)
+				logs.Notice(err)
 				break
 			}
 			lk.Run(true)
 			isConn = false
 		} else {
 			r, err = http.ReadRequest(bufio.NewReader(c))
+			logs.Trace("New http(s) connection,clientId %d,host %s,url %s,remote address %s", host.Client.Id, r.Host, r.URL, r.RemoteAddr)
 			if err != nil {
 				break
 			}
 			if host, err = file.GetCsvDb().GetInfoByHost(r.Host, r); err != nil {
-				lg.Printf("the url %s %s is not found !", r.Host, r.RequestURI)
+				logs.Notice("the url %s %s can't be parsed!", r.Host, r.RequestURI)
 				break
 			} else if host != lastHost {
 				lastHost = host
 				isConn = true
+				host.Client.AddConn()
 				goto start
 			}
 		}
@@ -176,6 +188,9 @@ end:
 		tunnel.SendMsg([]byte(common.IO_EOF), lk)
 	}
 	c.Close()
+	if host != nil {
+		host.Client.AddConn()
+	}
 }
 
 func (s *httpServer) NewServer(port int) *http.Server {

+ 16 - 11
server/proxy/socks5.go

@@ -7,7 +7,7 @@ import (
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/conn"
 	"github.com/cnlh/nps/lib/file"
-	"github.com/cnlh/nps/lib/lg"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"io"
 	"net"
 	"strconv"
@@ -48,7 +48,7 @@ const (
 )
 
 type Sock5ModeServer struct {
-	server
+	BaseServer
 	listener net.Listener
 }
 
@@ -67,7 +67,7 @@ func (s *Sock5ModeServer) handleRequest(c net.Conn) {
 	_, err := io.ReadFull(c, header)
 
 	if err != nil {
-		lg.Println("illegal request", err)
+		logs.Warn("illegal request", err)
 		c.Close()
 		return
 	}
@@ -165,7 +165,6 @@ func (s *Sock5ModeServer) handleBind(c net.Conn) {
 
 //udp
 func (s *Sock5ModeServer) handleUDP(c net.Conn) {
-	lg.Println("UDP Associate")
 	/*
 	   +----+------+------+----------+----------+----------+
 	   |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
@@ -178,7 +177,7 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) {
 	// relay udp datagram silently, without any notification to the requesting client
 	if buf[2] != 0 {
 		// does not support fragmentation, drop it
-		lg.Println("does not support fragmentation, drop")
+		logs.Warn("does not support fragmentation, drop")
 		dummy := make([]byte, maxUDPPacketSize)
 		c.Read(dummy)
 	}
@@ -190,13 +189,13 @@ func (s *Sock5ModeServer) handleUDP(c net.Conn) {
 func (s *Sock5ModeServer) handleConn(c net.Conn) {
 	buf := make([]byte, 2)
 	if _, err := io.ReadFull(c, buf); err != nil {
-		lg.Println("negotiation err", err)
+		logs.Warn("negotiation err", err)
 		c.Close()
 		return
 	}
 
 	if version := buf[0]; version != 5 {
-		lg.Println("only support socks5, request from: ", c.RemoteAddr())
+		logs.Warn("only support socks5, request from: ", c.RemoteAddr())
 		c.Close()
 		return
 	}
@@ -204,7 +203,7 @@ func (s *Sock5ModeServer) handleConn(c net.Conn) {
 
 	methods := make([]byte, nMethods)
 	if len, err := c.Read(methods); len != int(nMethods) || err != nil {
-		lg.Println("wrong method")
+		logs.Warn("wrong method")
 		c.Close()
 		return
 	}
@@ -213,7 +212,7 @@ func (s *Sock5ModeServer) handleConn(c net.Conn) {
 		c.Write(buf)
 		if err := s.Auth(c); err != nil {
 			c.Close()
-			lg.Println("Validation failed:", err)
+			logs.Warn("Validation failed:", err)
 			return
 		}
 	} else {
@@ -271,9 +270,15 @@ func (s *Sock5ModeServer) Start() error {
 			if strings.Contains(err.Error(), "use of closed network connection") {
 				break
 			}
-			lg.Fatalln("accept error: ", err)
+			logs.Warn("accept error: ", err)
+		}
+		if s.task.Client.GetConn() {
+			logs.Trace("New socks5 connection,client %d,remote address %s", s.task.Client.Id, conn.RemoteAddr())
+			go s.handleConn(conn)
+		} else {
+			logs.Warn("Connections exceed the current client %d limit", s.task.Client.Id)
+			conn.Close()
 		}
-		go s.handleConn(conn)
 	}
 	return nil
 }

+ 20 - 26
server/proxy/tcp.go

@@ -6,15 +6,16 @@ import (
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/conn"
 	"github.com/cnlh/nps/lib/file"
-	"github.com/cnlh/nps/lib/lg"
 	"github.com/cnlh/nps/vender/github.com/astaxie/beego"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"net"
+	"os"
 	"path/filepath"
 	"strings"
 )
 
 type TunnelModeServer struct {
-	server
+	BaseServer
 	process  process
 	listener *net.TCPListener
 }
@@ -41,24 +42,16 @@ func (s *TunnelModeServer) Start() error {
 			if strings.Contains(err.Error(), "use of closed network connection") {
 				break
 			}
-			lg.Println(err)
+			logs.Info(err)
 			continue
 		}
-		go s.process(conn.NewConn(c), s)
-	}
-	return nil
-}
-
-//与客户端建立通道
-func (s *TunnelModeServer) dealClient(c *conn.Conn, cnf *file.Config, addr string, method string, rb []byte) error {
-	link := conn.NewLink(s.task.Client.GetId(), common.CONN_TCP, addr, cnf.CompressEncode, cnf.CompressDecode, cnf.Crypt, c, s.task.Flow, nil, s.task.Client.Rate, nil)
-
-	if tunnel, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, c.Conn.RemoteAddr().String()); err != nil {
-		c.Close()
-		return err
-	} else {
-		link.Run(true)
-		s.linkCopy(link, c, rb, tunnel, s.task.Flow)
+		if s.task.Client.GetConn() {
+			logs.Trace("New tcp connection,client %d,remote address %s", s.task.Client.Id, c.RemoteAddr())
+			go s.process(conn.NewConn(c), s)
+		} else {
+			logs.Info("Connections exceed the current client %d limit", s.task.Client.Id)
+			c.Close()
+		}
 	}
 	return nil
 }
@@ -70,17 +63,18 @@ func (s *TunnelModeServer) Close() error {
 
 //web管理方式
 type WebServer struct {
-	server
+	BaseServer
 }
 
 //开始
 func (s *WebServer) Start() error {
 	p, _ := beego.AppConfig.Int("httpport")
 	if !common.TestTcpPort(p) {
-		lg.Fatalf("Web management port %d is occupied", p)
+		logs.Error("Web management port %d is occupied", p)
+		os.Exit(0)
 	}
 	beego.BConfig.WebConfig.Session.SessionOn = true
-	lg.Println("Web management start, access port is", p)
+	logs.Info("Web management start, access port is", p)
 	beego.SetStaticPath("/static", filepath.Join(common.GetRunPath(), "web", "static"))
 	beego.SetViewsPath(filepath.Join(common.GetRunPath(), "web", "views"))
 	beego.Run()
@@ -102,23 +96,23 @@ type process func(c *conn.Conn, s *TunnelModeServer) error
 
 //tcp隧道模式
 func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error {
-	return s.dealClient(c, s.task.Client.Cnf, s.task.Target, "", nil)
+	return s.DealClient(c, s.task.Target, nil)
 }
 
 //http代理模式
 func ProcessHttp(c *conn.Conn, s *TunnelModeServer) error {
-	method, addr, rb, err, r := c.GetHost()
+	_, addr, rb, err, r := c.GetHost()
 	if err != nil {
 		c.Close()
-		lg.Println(err)
+		logs.Info(err)
 		return err
 	}
 	if r.Method == "CONNECT" {
 		c.Write([]byte("HTTP/1.1 200 Connection Established\r\n"))
-		rb = nil //reset
+		rb = nil
 	}
 	if err := s.auth(r, c, s.task.Client.Cnf.U, s.task.Client.Cnf.P); err != nil {
 		return err
 	}
-	return s.dealClient(c, s.task.Client.Cnf, addr, method, rb)
+	return s.DealClient(c, addr, rb)
 }

+ 3 - 1
server/proxy/udp.go

@@ -6,12 +6,13 @@ import (
 	"github.com/cnlh/nps/lib/conn"
 	"github.com/cnlh/nps/lib/file"
 	"github.com/cnlh/nps/lib/pool"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"net"
 	"strings"
 )
 
 type UdpModeServer struct {
-	server
+	BaseServer
 	listener *net.UDPConn
 	udpMap   map[string]*conn.Conn
 }
@@ -40,6 +41,7 @@ func (s *UdpModeServer) Start() error {
 			}
 			continue
 		}
+		logs.Trace("New ydo connection,client %d,remote address %s", s.task.Client.Id, addr)
 		go s.process(addr, buf[:n])
 	}
 	return nil

+ 54 - 14
server/server.go

@@ -5,10 +5,12 @@ import (
 	"github.com/cnlh/nps/bridge"
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/file"
-	"github.com/cnlh/nps/lib/lg"
 	"github.com/cnlh/nps/server/proxy"
 	"github.com/cnlh/nps/server/tool"
 	"github.com/cnlh/nps/vender/github.com/astaxie/beego"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
+	"os"
+	"time"
 )
 
 var (
@@ -31,7 +33,6 @@ func InitFromCsv() {
 	//Initialize services in server-side files
 	for _, v := range file.GetCsvDb().Tasks {
 		if v.Status {
-			lg.Println("task start info: mode:", v.Mode, "port:", v.Port)
 			AddTask(v)
 		}
 	}
@@ -45,6 +46,19 @@ func DealBridgeTask() {
 		case id := <-Bridge.CloseClient:
 			DelTunnelAndHostByClientId(id)
 			file.GetCsvDb().DelClient(id)
+		case s := <-Bridge.SecretChan:
+			logs.Trace("New secret connection, addr", s.Conn.Conn.RemoteAddr())
+			if t := file.GetCsvDb().GetSecretTask(s.Password); t != nil {
+				if !t.Client.GetConn() {
+					logs.Info("Connections exceed the current client %d limit", t.Client.Id)
+					s.Conn.Close()
+				} else {
+					go proxy.NewBaseServer(Bridge, t).DealClient(s.Conn, t.Target, nil)
+				}
+			} else {
+				logs.Trace("This key %s cannot be processed", s.Password)
+				s.Conn.Close()
+			}
 		}
 	}
 }
@@ -53,18 +67,19 @@ func DealBridgeTask() {
 func StartNewServer(bridgePort int, cnf *file.Tunnel, bridgeType string) {
 	Bridge = bridge.NewTunnel(bridgePort, bridgeType, common.GetBoolByStr(beego.AppConfig.String("ipLimit")), RunList)
 	if err := Bridge.StartTunnel(); err != nil {
-		lg.Fatalln("服务端开启失败", err)
+		logs.Error("服务端开启失败", err)
+		os.Exit(0)
 	} else {
-		lg.Printf("Server startup, the bridge type is %s, the bridge port is %d", bridgeType, bridgePort)
+		logs.Info("Server startup, the bridge type is %s, the bridge port is %d", bridgeType, bridgePort)
 	}
 	go DealBridgeTask()
 	if svr := NewMode(Bridge, cnf); svr != nil {
 		if err := svr.Start(); err != nil {
-			lg.Fatalln(err)
+			logs.Error(err)
 		}
 		RunList[cnf.Id] = svr
 	} else {
-		lg.Fatalln("启动模式%s不正确", cnf.Mode)
+		logs.Error("Incorrect startup mode %s", cnf.Mode)
 	}
 }
 
@@ -103,12 +118,12 @@ func StopServer(id int) error {
 			if err := svr.Close(); err != nil {
 				return err
 			}
-			if t, err := file.GetCsvDb().GetTask(id); err != nil {
-				return err
-			} else {
-				t.Status = false
-				file.GetCsvDb().UpdateTask(t)
-			}
+		}
+		if t, err := file.GetCsvDb().GetTask(id); err != nil {
+			return err
+		} else {
+			t.Status = false
+			file.GetCsvDb().UpdateTask(t)
 		}
 		delete(RunList, id)
 		return nil
@@ -118,15 +133,24 @@ func StopServer(id int) error {
 
 //add task
 func AddTask(t *file.Tunnel) error {
+	if t.Mode == "secretServer" {
+		logs.Info("secret task %s start ", t.Remark)
+		RunList[t.Id] = nil
+		return nil
+	}
 	if b := tool.TestServerPort(t.Port, t.Mode); !b && t.Mode != "httpHostServer" {
-		lg.Printf("taskId %d start error port %d Open Failed", t.Id, t.Port)
+		logs.Error("taskId %d start error port %d open failed", t.Id, t.Port)
 		return errors.New("the port open error")
 	}
+	if minute, err := beego.AppConfig.Int("flowStoreInterval"); err == nil && minute > 0 {
+		go flowSession(time.Minute * time.Duration(minute))
+	}
 	if svr := NewMode(Bridge, t); svr != nil {
+		logs.Info("tunnel task %s start mode:%s port %d", t.Remark, t.Mode, t.Port)
 		RunList[t.Id] = svr
 		go func() {
 			if err := svr.Start(); err != nil {
-				lg.Println("clientId %d taskId %d start error %s", t.Client.Id, t.Id, err)
+				logs.Error("clientId %d taskId %d start error %s", t.Client.Id, t.Id, err)
 				delete(RunList, t.Id)
 				return
 			}
@@ -272,5 +296,21 @@ func GetDashboardData() map[string]int {
 			data["udpServerCount"] += 1
 		}
 	}
+	tcpCount := 0
+	for _, v := range file.GetCsvDb().Clients {
+		tcpCount += v.NowConn
+	}
+	data["tcpCount"] = tcpCount
 	return data
 }
+
+func flowSession(m time.Duration) {
+	ticker := time.NewTicker(m)
+	for {
+		select {
+		case <-ticker.C:
+			file.GetCsvDb().StoreHostToCsv()
+			file.GetCsvDb().StoreTasksToCsv()
+		}
+	}
+}

+ 3 - 0
server/tool/utils.go

@@ -13,6 +13,9 @@ func init() {
 }
 
 func TestServerPort(p int, m string) (b bool) {
+	if p > 65535 || p <= 0 {
+		return false
+	}
 	if len(ports) != 0 {
 		if !common.InIntArr(ports, p) {
 			return false

+ 2 - 0
web/controllers/client.go

@@ -42,6 +42,7 @@ func (s *ClientController) Add() {
 				Crypt:    s.GetBoolNoErr("crypt"),
 			},
 			RateLimit: s.GetIntNoErr("rate_limit"),
+			MaxConn:   s.GetIntNoErr("max_conn"),
 			Flow: &file.Flow{
 				ExportFlow: 0,
 				InletFlow:  0,
@@ -94,6 +95,7 @@ func (s *ClientController) Edit() {
 			c.Cnf.Crypt = s.GetBoolNoErr("crypt")
 			c.Flow.FlowLimit = int64(s.GetIntNoErr("flow_limit"))
 			c.RateLimit = s.GetIntNoErr("rate_limit")
+			c.MaxConn = s.GetIntNoErr("max_conn")
 			if c.Rate != nil {
 				c.Rate.Stop()
 			}

+ 15 - 7
web/controllers/index.go

@@ -44,6 +44,12 @@ func (s *IndexController) Http() {
 	s.display("index/list")
 }
 
+func (s *IndexController) Secret() {
+	s.SetInfo("私密代理管理")
+	s.SetType("secretServer")
+	s.display("index/list")
+}
+
 func (s *IndexController) Host() {
 	s.SetInfo("host模式管理")
 	s.SetType("hostServer")
@@ -74,13 +80,14 @@ func (s *IndexController) Add() {
 		s.display()
 	} else {
 		t := &file.Tunnel{
-			Port:   s.GetIntNoErr("port"),
-			Mode:   s.GetString("type"),
-			Target: s.GetString("target"),
-			Id:     file.GetCsvDb().GetTaskId(),
-			Status: true,
-			Remark: s.GetString("remark"),
-			Flow:   &file.Flow{},
+			Port:     s.GetIntNoErr("port"),
+			Mode:     s.GetString("type"),
+			Target:   s.GetString("target"),
+			Id:       file.GetCsvDb().GetTaskId(),
+			Status:   true,
+			Remark:   s.GetString("remark"),
+			Password: s.GetString("password"),
+			Flow:     &file.Flow{},
 		}
 		if !tool.TestServerPort(t.Port, t.Mode) {
 			s.AjaxErr("The port cannot be opened because it may has been occupied or is no longer allowed.")
@@ -126,6 +133,7 @@ func (s *IndexController) Edit() {
 			t.Port = s.GetIntNoErr("port")
 			t.Mode = s.GetString("type")
 			t.Target = s.GetString("target")
+			t.Password = s.GetString("password")
 			t.Id = id
 			t.Remark = s.GetString("remark")
 			if t.Client, err = file.GetCsvDb().GetClient(s.GetIntNoErr("client_id")); err != nil {

+ 4 - 0
web/views/client/add.html

@@ -16,6 +16,10 @@
                         <label class="control-label">速度限制(单位:KB,为空不限制)</label>
                         <input class="form-control" type="text" name="rate_limit" placeholder="为空不限制">
                     </div>
+                    <div class="form-group" id="max_conn">
+                        <label class="control-label">最大客户端连接数</label>
+                        <input class="form-control" type="text" name="max_conn" placeholder="为空不限制">
+                    </div>
                     <div class="form-group" id="u">
                         <label class="control-label">验证用户名(仅socks5,web穿透支持)</label>
                         <input class="form-control" type="text" name="u" placeholder="不填则无需验证">

+ 5 - 0
web/views/client/edit.html

@@ -19,6 +19,11 @@
                         <input class="form-control" value="{{.c.RateLimit}}" type="text" name="rate_limit"
                                placeholder="为空不限制">
                     </div>
+                    <div class="form-group" id="max_conn">
+                        <label class="control-label">最大客户端连接数</label>
+                        <input class="form-control" value="{{.c.MaxConn}}" type="text" name="max_conn"
+                               placeholder="为空不限制">
+                    </div>
                     <div class="form-group" id="u">
                         <label class="control-label">验证用户名(仅socks5,web穿透支持)</label>
                         <input class="form-control" value="{{.c.Cnf.U}}" type="text" name="u"

+ 7 - 0
web/views/index/add.html

@@ -11,6 +11,7 @@
                             <option {{if eq "udpServer" .type}}selected{{end}} value="udpServer">udp隧道</option>
                             <option {{if eq "socks5Server" .type}}selected{{end}} value="socks5Server">socks5代理</option>
                             <option {{if eq "httpProxyServer" .type}}selected{{end}} value="httpProxyServer">http代理
+                            <option {{if eq "secretServer" .type}}selected{{end}} value="secretServer">私密代理
                         </select>
                     </div>
                     <div class="form-group">
@@ -30,6 +31,11 @@
                         <input value="{{.client_id}}" class="form-control" type="text" name="client_id"
                                placeholder="客户端id">
                     </div>
+                    <div class="form-group" id="password">
+                        <label class="control-label">私密模式唯一密钥</label>
+                        <input class="form-control" type="text" name="password"
+                               placeholder="私密模式唯一密钥">
+                    </div>
                 </form>
             </div>
             <div class="tile-footer">
@@ -48,6 +54,7 @@
     arr["udpServer"] = ["type", "port", "target", "compress", "udp隧道模式,提供一条udp隧道,适用于dns、内网dns访问等,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,访问公网服务器的设定端口,则相当于访问内网目标地址的udp目标端口"]
     arr["socks5Server"] = ["type", "port", "compress", "u", "p", "socks5代理模式,内网socks5代理,配合proxifer,可如同使用vpn一样访问内网设备或资源,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置socks5代理,即访问内网设备或者资源 "]
     arr["httpProxyServer"] = ["type", "port", "compress", "u", "p", " http代理模式,内网http代理,可访问内网网站,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置http代理,即访问内网站点"]
+    arr["secretServer"] = ["type", "target", "compress", "password", "u", "p", " http代理模式,内网http代理,可访问内网网站,添加后会自动生成一个客户端验证key<br>在内网机器执行<span style='color: red'>./easyProxy -vkey=生成的key -server=公网服务器ip:下面设定的端口</span><br>建立成功后,在外网环境下本机配置http代理,即访问内网站点"]
     arrClientHide = ["compress", "u", "p", "crypt", "mux"]
 
     function resetForm() {

+ 7 - 0
web/views/index/edit.html

@@ -13,6 +13,7 @@
                             <option {{if eq "socks5Server" .t.Mode}}selected{{end}} value="socks5Server">socks5代理
                             </option>
                             <option {{if eq "httpProxyServer" .t.Mode}}selected{{end}} value="httpProxyServer">http代理
+                            <option {{if eq "secretServer" .t.Mode}}selected{{end}} value="secretServer">私密代理
                         </select>
                     </div>
                     <div class="form-group">
@@ -34,6 +35,11 @@
                         <input class="form-control" value="{{.t.Client.Id}}" type="text" name="client_id"
                                placeholder="客户端id">
                     </div>
+                    <div class="form-group" id="password">
+                        <label class="control-label">私密模式唯一密钥</label>
+                        <input class="form-control" value="{{.t.Password}}" type="text" name="password"
+                               placeholder="私密模式唯一密钥">
+                    </div>
                 </form>
             </div>
             <div class="tile-footer">
@@ -52,6 +58,7 @@
     arr["udpServer"] = ["type", "port", "target", "compress"]
     arr["socks5Server"] = ["type", "port", "compress", "u", "p"]
     arr["httpProxyServer"] = ["type", "port", "compress", "u", "p"]
+    arr["secretServer"] = ["type", "target", "compress", "u", "p","password"]
     arrClientHide = ["compress", "u", "p", "crypt", "mux"]
 
     function resetForm() {

+ 2 - 2
web/views/index/index.html

@@ -10,8 +10,8 @@
     <div class="col-md-3">
         <div class="widget-small danger coloured-icon"><i class="icon fa fa-home fa-3x"></i>
             <div class="info">
-                <h4>域名解析数</h4>
-                <p><b>{{.data.hostCount}}</b></p>
+                <h4>当前TCP连接总数</h4>
+                <p><b>{{.data.tcpCount}}</b></p>
             </div>
         </div>
     </div>

+ 4 - 2
web/views/public/layout.html

@@ -51,9 +51,11 @@
         <li><a class="app-menu__item {{if eq "udp" .menu}}active{{end}}" href="/index/udp"><i
                 class="app-menu__icon fa fa-laptop"></i><span class="app-menu__label">udp隧道管理</span></a></li>
         <li><a class="app-menu__item {{if eq "socks5" .menu}}active{{end}}" href="/index/socks5"><i
-                class="app-menu__icon fa fa-lightbulb-o"></i><span class="app-menu__label">socks5代理</span></a></li>
+                class="app-menu__icon fa fa-lightbulb-o"></i><span class="app-menu__label">socks5代理管理</span></a></li>
         <li><a class="app-menu__item {{if eq "http" .menu}}active{{end}}" href="/index/http"><i
-                class="app-menu__icon fa fa-magic"></i><span class="app-menu__label">http代理</span></a></li>
+                class="app-menu__icon fa fa-magic"></i><span class="app-menu__label">http代理管理</span></a></li>
+        <li><a class="app-menu__item {{if eq "secret" .menu}}active{{end}}" href="/index/secret"><i
+                class="app-menu__icon fa fa-dashcube"></i><span class="app-menu__label">私密代理管理</span></a></li>
         <li><a class="app-menu__item {{if eq "help" .menu}}active{{end}}" href="/index/help"><i
                 class="app-menu__icon fa fa-question-circle"></i><span class="app-menu__label">使用说明</span></a></li>
     </ul>