刘河 6 роки тому
батько
коміт
cc6d053b6d

+ 16 - 17
bridge/bridge.go

@@ -92,12 +92,12 @@ func (s *Bridge) GetHealthFromClient(id int, c *conn.Conn) {
 		} else if !status { //the status is true , return target to the targetArr
 			file.GetCsvDb().Tasks.Range(func(key, value interface{}) bool {
 				v := value.(*file.Tunnel)
-				if v.Client.Id == id && v.Mode == "tcp" && strings.Contains(v.Target, info) {
+				if v.Client.Id == id && v.Mode == "tcp" && strings.Contains(v.Target.TargetStr, info) {
 					v.Lock()
-					if v.TargetArr == nil || (len(v.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) {
-						v.TargetArr = common.TrimArr(strings.Split(v.Target, "\n"))
+					if v.Target.TargetArr == nil || (len(v.Target.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) {
+						v.Target.TargetArr = common.TrimArr(strings.Split(v.Target.TargetStr, "\n"))
 					}
-					v.TargetArr = common.RemoveArrVal(v.TargetArr, info)
+					v.Target.TargetArr = common.RemoveArrVal(v.Target.TargetArr, info)
 					if v.HealthRemoveArr == nil {
 						v.HealthRemoveArr = make([]string, 0)
 					}
@@ -108,12 +108,12 @@ func (s *Bridge) GetHealthFromClient(id int, c *conn.Conn) {
 			})
 			file.GetCsvDb().Hosts.Range(func(key, value interface{}) bool {
 				v := value.(*file.Host)
-				if v.Client.Id == id && strings.Contains(v.Target, info) {
+				if v.Client.Id == id && strings.Contains(v.Target.TargetStr, info) {
 					v.Lock()
-					if v.TargetArr == nil || (len(v.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) {
-						v.TargetArr = common.TrimArr(strings.Split(v.Target, "\n"))
+					if v.Target.TargetArr == nil || (len(v.Target.TargetArr) == 0 && len(v.HealthRemoveArr) == 0) {
+						v.Target.TargetArr = common.TrimArr(strings.Split(v.Target.TargetStr, "\n"))
 					}
-					v.TargetArr = common.RemoveArrVal(v.TargetArr, info)
+					v.Target.TargetArr = common.RemoveArrVal(v.Target.TargetArr, info)
 					if v.HealthRemoveArr == nil {
 						v.HealthRemoveArr = make([]string, 0)
 					}
@@ -125,9 +125,9 @@ func (s *Bridge) GetHealthFromClient(id int, c *conn.Conn) {
 		} else { //the status is false,remove target from the targetArr
 			file.GetCsvDb().Tasks.Range(func(key, value interface{}) bool {
 				v := value.(*file.Tunnel)
-				if v.Client.Id == id && v.Mode == "tcp" && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.TargetArr, info) {
+				if v.Client.Id == id && v.Mode == "tcp" && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.Target.TargetArr, info) {
 					v.Lock()
-					v.TargetArr = append(v.TargetArr, info)
+					v.Target.TargetArr = append(v.Target.TargetArr, info)
 					v.HealthRemoveArr = common.RemoveArrVal(v.HealthRemoveArr, info)
 					v.Unlock()
 				}
@@ -136,9 +136,9 @@ func (s *Bridge) GetHealthFromClient(id int, c *conn.Conn) {
 
 			file.GetCsvDb().Hosts.Range(func(key, value interface{}) bool {
 				v := value.(*file.Host)
-				if v.Client.Id == id && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.TargetArr, info) {
+				if v.Client.Id == id && common.IsArrContains(v.HealthRemoveArr, info) && !common.IsArrContains(v.Target.TargetArr, info) {
 					v.Lock()
-					v.TargetArr = append(v.TargetArr, info)
+					v.Target.TargetArr = append(v.Target.TargetArr, info)
 					v.HealthRemoveArr = common.RemoveArrVal(v.HealthRemoveArr, info)
 					v.Unlock()
 				}
@@ -207,7 +207,7 @@ func (s *Bridge) DelClient(id int) {
 		if file.GetCsvDb().IsPubClient(id) {
 			return
 		}
-		if c, err := file.GetCsvDb().GetClient(id); err == nil && c.NoStore {
+		if c, err := file.GetCsvDb().GetClient(id); err == nil {
 			s.CloseClient <- c.Id
 		}
 	}
@@ -320,7 +320,6 @@ func (s *Bridge) SendLinkInfo(clientId int, link *conn.Link, linkAddr string, t
 		if t != nil && t.Mode == "file" {
 			return
 		}
-
 		if _, err = conn.NewConn(target).SendLinkInfo(link); err != nil {
 			logs.Info("new connect error ,the target %s refuse to connect", link.Host)
 			return
@@ -441,7 +440,7 @@ loop:
 				break loop
 			} else {
 				ports := common.GetPorts(t.Ports)
-				targets := common.GetPorts(t.Target)
+				targets := common.GetPorts(t.Target.TargetStr)
 				if len(ports) > 1 && (t.Mode == "tcp" || t.Mode == "udp") && (len(ports) != len(targets)) {
 					fail = true
 					c.WriteAddFail()
@@ -465,9 +464,9 @@ loop:
 					} else {
 						tl.Remark = t.Remark + "_" + strconv.Itoa(tl.Port)
 						if t.TargetAddr != "" {
-							tl.Target = t.TargetAddr + ":" + strconv.Itoa(targets[i])
+							tl.Target.TargetStr = t.TargetAddr + ":" + strconv.Itoa(targets[i])
 						} else {
-							tl.Target = strconv.Itoa(targets[i])
+							tl.Target.TargetStr = strconv.Itoa(targets[i])
 						}
 					}
 					tl.Id = int(file.GetCsvDb().GetTaskId())

+ 1 - 1
client/control.go

@@ -163,7 +163,7 @@ re:
 	}
 
 	c.Close()
-	logs.Notice("Temporary access login key ", vkey)
+	logs.Notice("web access login key ", vkey)
 	NewRPClient(cnf.CommonConfig.Server, vkey, cnf.CommonConfig.Tp, cnf.CommonConfig.ProxyUrl, cnf).Start()
 	CloseLocalServer()
 	goto re

+ 2 - 2
client/health.go

@@ -63,7 +63,7 @@ func session(healths []*file.Health, h *sheap.IntHeap) {
 	}
 }
 
-//只针对一个端口 面向多个目标的情况
+// work when just one port and many target
 func check(t *file.Health) {
 	arr := strings.Split(t.HealthCheckTarget, ",")
 	var err error
@@ -88,7 +88,7 @@ func check(t *file.Health) {
 			t.HealthMap[v] = 0
 		}
 
-		if t.HealthMap[v] == t.HealthMaxFail {
+		if t.HealthMap[v] > 0 && t.HealthMap[v]%t.HealthMaxFail == 0 {
 			//send fail remove
 			serverConn.SendHealthInfo(v, "0")
 		}

+ 3 - 1
cmd/npc/npc.go

@@ -6,8 +6,10 @@ import (
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/daemon"
 	"github.com/cnlh/nps/lib/version"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego"
 	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"os"
+	"path/filepath"
 	"strings"
 	"time"
 )
@@ -59,7 +61,7 @@ func main() {
 		}
 	} else {
 		if *configPath == "" {
-			*configPath = "npc.conf"
+			*configPath = filepath.Join(beego.AppPath, "conf", "npc.conf")
 		}
 		client.StartFromFile(*configPath)
 	}

+ 0 - 11
lib/common/util.go

@@ -13,10 +13,8 @@ import (
 	"net/http"
 	"os"
 	"regexp"
-	"sort"
 	"strconv"
 	"strings"
-	"sync"
 )
 
 //Get the corresponding IP address through domain name
@@ -346,12 +344,3 @@ func BytesToNum(b []byte) int {
 	x, _ := strconv.Atoi(str)
 	return int(x)
 }
-
-func GetMapKeys(m sync.Map) (keys []int) {
-	m.Range(func(key, value interface{}) bool {
-		keys = append(keys, key.(int))
-		return true
-	})
-	sort.Ints(keys)
-	return
-}

+ 6 - 2
lib/config/config.go

@@ -17,6 +17,7 @@ type CommonConfig struct {
 	ProxyUrl         string
 	Client           *file.Client
 }
+
 type LocalServer struct {
 	Type     string
 	Port     int
@@ -24,6 +25,7 @@ type LocalServer struct {
 	Password string
 	Target   string
 }
+
 type Config struct {
 	content      string
 	title        []string
@@ -146,6 +148,7 @@ func dealCommon(s string) *CommonConfig {
 
 func dealHost(s string) *file.Host {
 	h := &file.Host{}
+	h.Target = new(file.Target)
 	var headerChange string
 	for _, v := range splitStr(s) {
 		item := strings.Split(v, "=")
@@ -158,7 +161,7 @@ func dealHost(s string) *file.Host {
 		case "host":
 			h.Host = item[1]
 		case "target_addr":
-			h.Target = strings.Replace(item[1], ",", "\n", -1)
+			h.Target.TargetStr = strings.Replace(item[1], ",", "\n", -1)
 		case "host_change":
 			h.HostChange = item[1]
 		case "scheme":
@@ -204,6 +207,7 @@ func dealHealth(s string) *file.Health {
 
 func dealTunnel(s string) *file.Tunnel {
 	t := &file.Tunnel{}
+	t.Target = new(file.Target)
 	for _, v := range splitStr(s) {
 		item := strings.Split(v, "=")
 		if len(item) == 0 {
@@ -219,7 +223,7 @@ func dealTunnel(s string) *file.Tunnel {
 		case "mode":
 			t.Mode = item[1]
 		case "target_port", "target_addr":
-			t.Target = strings.Replace(item[1], ",", "\n", -1)
+			t.Target.TargetStr = strings.Replace(item[1], ",", "\n", -1)
 		case "target_ip":
 			t.TargetAddr = item[1]
 		case "password":

+ 17 - 37
lib/conn/conn.go

@@ -25,17 +25,14 @@ import (
 
 type Conn struct {
 	Conn net.Conn
-	sync.Mutex
 }
 
 //new conn
 func NewConn(conn net.Conn) *Conn {
-	c := new(Conn)
-	c.Conn = conn
-	return c
+	return &Conn{Conn: conn}
 }
 
-//从tcp报文中解析出host,连接类型等
+//get host 、connection type、method...from connection
 func (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http.Request) {
 	var b [32 * 1024]byte
 	var n int
@@ -53,14 +50,14 @@ func (s *Conn) GetHost() (method, address string, rb []byte, err error, r *http.
 		err = nil
 		return
 	}
-	if hostPortURL.Opaque == "443" { //https访问
-		if strings.Index(r.Host, ":") == -1 { //host不带端口, 默认80
+	if hostPortURL.Opaque == "443" {
+		if strings.Index(r.Host, ":") == -1 {
 			address = r.Host + ":443"
 		} else {
 			address = r.Host
 		}
-	} else { //http访问
-		if strings.Index(r.Host, ":") == -1 { //host不带端口, 默认80
+	} else {
+		if strings.Index(r.Host, ":") == -1 {
 			address = r.Host + ":80"
 		} else {
 			address = r.Host
@@ -117,7 +114,7 @@ func (s *Conn) ReadFlag() (string, error) {
 	return string(buf), binary.Read(s, binary.LittleEndian, &buf)
 }
 
-//设置连接为长连接
+//set alive
 func (s *Conn) SetAlive(tp string) {
 	switch s.Conn.(type) {
 	case *kcp.UDPSession:
@@ -132,7 +129,7 @@ func (s *Conn) SetAlive(tp string) {
 	}
 }
 
-//设置连接为长连接
+//set read deadline
 func (s *Conn) SetReadDeadline(t time.Duration, tp string) {
 	switch s.Conn.(type) {
 	case *kcp.UDPSession:
@@ -176,8 +173,6 @@ func (s *Conn) GetLinkInfo() (lk *Link, err error) {
 func (s *Conn) SendHealthInfo(info, status string) (int, error) {
 	raw := bytes.NewBuffer([]byte{})
 	common.BinaryWrite(raw, info, status)
-	s.Lock()
-	defer s.Unlock()
 	return s.Write(raw.Bytes())
 }
 
@@ -211,9 +206,7 @@ func (s *Conn) SendHostInfo(h *file.Host) (int, error) {
 	*/
 	raw := bytes.NewBuffer([]byte{})
 	binary.Write(raw, binary.LittleEndian, []byte(common.NEW_HOST))
-	common.BinaryWrite(raw, h.Host, h.Target, h.HeaderChange, h.HostChange, h.Remark, h.Location, h.Scheme)
-	s.Lock()
-	defer s.Unlock()
+	common.BinaryWrite(raw, h.Host, h.Target.TargetStr, h.HeaderChange, h.HostChange, h.Remark, h.Location, h.Scheme)
 	return s.Write(raw.Bytes())
 }
 
@@ -244,9 +237,10 @@ func (s *Conn) GetHostInfo() (h *file.Host, err error) {
 	} else {
 		arr := strings.Split(string(buf[:l]), common.CONN_DATA_SEQ)
 		h = new(file.Host)
+		h.Target = new(file.Target)
 		h.Id = int(file.GetCsvDb().GetHostId())
 		h.Host = arr[0]
-		h.Target = arr[1]
+		h.Target.TargetStr = arr[1]
 		h.HeaderChange = arr[2]
 		h.HostChange = arr[3]
 		h.Remark = arr[4]
@@ -275,8 +269,6 @@ func (s *Conn) SendConfigInfo(c *config.CommonConfig) (int, error) {
 	binary.Write(raw, binary.LittleEndian, []byte(common.NEW_CONF))
 	common.BinaryWrite(raw, c.Cnf.U, c.Cnf.P, common.GetStrByBool(c.Cnf.Crypt), common.GetStrByBool(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())
 }
 
@@ -316,9 +308,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, t.TargetAddr, t.Password, t.LocalPath, t.StripPre, t.ServerIp)
-	s.Lock()
-	defer s.Unlock()
+	common.BinaryWrite(raw, t.Mode, t.Ports, t.Target.TargetStr, t.Remark, t.TargetAddr, t.Password, t.LocalPath, t.StripPre, t.ServerIp)
 	return s.Write(raw.Bytes())
 }
 
@@ -334,9 +324,10 @@ func (s *Conn) GetTaskInfo() (t *file.Tunnel, err error) {
 	} else {
 		arr := strings.Split(string(buf[:l]), common.CONN_DATA_SEQ)
 		t = new(file.Tunnel)
+		t.Target = new(file.Target)
 		t.Mode = arr[0]
 		t.Ports = arr[1]
-		t.Target = arr[2]
+		t.Target.TargetStr = arr[2]
 		t.Id = int(file.GetCsvDb().GetTaskId())
 		t.Status = true
 		t.Flow = new(file.Flow)
@@ -375,26 +366,20 @@ func (s *Conn) WriteClose() (int, error) {
 
 //write main
 func (s *Conn) WriteMain() (int, error) {
-	s.Lock()
-	defer s.Unlock()
 	return s.Write([]byte(common.WORK_MAIN))
 }
 
 //write main
 func (s *Conn) WriteConfig() (int, error) {
-	s.Lock()
-	defer s.Unlock()
 	return s.Write([]byte(common.WORK_CONFIG))
 }
 
 //write chan
 func (s *Conn) WriteChan() (int, error) {
-	s.Lock()
-	defer s.Unlock()
 	return s.Write([]byte(common.WORK_CHAN))
 }
 
-//获取长度+内容
+//get the assembled amount data(len 4 and content)
 func GetLenBytes(buf []byte) (b []byte, err error) {
 	raw := bytes.NewBuffer([]byte{})
 	if err = binary.Write(raw, binary.LittleEndian, int32(len(buf))); err != nil {
@@ -407,6 +392,7 @@ func GetLenBytes(buf []byte) (b []byte, err error) {
 	return
 }
 
+//udp connection setting
 func SetUdpSession(sess *kcp.UDPSession) {
 	sess.SetStreamMode(true)
 	sess.SetWindowSize(1024, 1024)
@@ -450,13 +436,7 @@ func GetConn(conn net.Conn, cpt, snappy bool, rt *rate.Rate, isServer bool) (io.
 		}
 		return rate.NewRateConn(crypt.NewTlsClientConn(conn), rt)
 	} else if snappy {
-		return NewSnappyConn(conn, cpt, rt)
+		return rate.NewRateConn(NewSnappyConn(conn), rt)
 	}
 	return rate.NewRateConn(conn, rt)
 }
-
-//read length or id (content length=4)
-func GetLen(reader io.Reader) (int, error) {
-	var l int32
-	return int(l), binary.Read(reader, binary.LittleEndian, &l)
-}

+ 4 - 12
lib/conn/snappy.go

@@ -2,22 +2,20 @@ package conn
 
 import (
 	"github.com/cnlh/nps/lib/pool"
-	"github.com/cnlh/nps/lib/rate"
 	"github.com/cnlh/nps/vender/github.com/golang/snappy"
+	"github.com/fatedier/frp/utils/net"
 	"io"
 )
 
 type SnappyConn struct {
-	w    *snappy.Writer
-	r    *snappy.Reader
-	rate *rate.Rate
+	w *snappy.Writer
+	r *snappy.Reader
 }
 
-func NewSnappyConn(conn io.ReadWriteCloser, crypt bool, rate *rate.Rate) *SnappyConn {
+func NewSnappyConn(conn io.ReadWriteCloser) net.Conn {
 	c := new(SnappyConn)
 	c.w = snappy.NewBufferedWriter(conn)
 	c.r = snappy.NewReader(conn)
-	c.rate = rate
 	return c
 }
 
@@ -29,9 +27,6 @@ func (s *SnappyConn) Write(b []byte) (n int, err error) {
 	if err = s.w.Flush(); err != nil {
 		return
 	}
-	if s.rate != nil {
-		s.rate.Get(int64(n))
-	}
 	return
 }
 
@@ -43,9 +38,6 @@ func (s *SnappyConn) Read(b []byte) (n int, err error) {
 		return
 	}
 	copy(b, buf[:n])
-	if s.rate != nil {
-		s.rate.Get(int64(n))
-	}
 	return
 }
 

+ 13 - 0
lib/file/csv.go

@@ -2,6 +2,7 @@ package file
 
 import (
 	"github.com/cnlh/nps/lib/common"
+	"sort"
 	"sync"
 )
 
@@ -20,3 +21,15 @@ func GetCsvDb() *Csv {
 	})
 	return CsvDb
 }
+
+func GetMapKeys(m sync.Map, isSort bool, sortKey, order string) (keys []int) {
+	if sortKey != "" && isSort {
+		return sortClientByKey(m, sortKey, order)
+	}
+	m.Range(func(key, value interface{}) bool {
+		keys = append(keys, key.(int))
+		return true
+	})
+	sort.Ints(keys)
+	return
+}

+ 41 - 17
lib/file/file.go

@@ -7,7 +7,6 @@ import (
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/crypt"
 	"github.com/cnlh/nps/lib/rate"
-	"github.com/cnlh/nps/vender/github.com/astaxie/beego"
 	"github.com/cnlh/nps/vender/github.com/astaxie/beego/logs"
 	"net/http"
 	"os"
@@ -52,7 +51,7 @@ func (s *Csv) StoreTasksToCsv() {
 		record := []string{
 			strconv.Itoa(task.Port),
 			task.Mode,
-			task.Target,
+			task.Target.TargetStr,
 			common.GetStrByBool(task.Status),
 			strconv.Itoa(task.Id),
 			strconv.Itoa(task.Client.Id),
@@ -101,12 +100,13 @@ func (s *Csv) LoadTaskFromCsv() {
 		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],
 			Password: item[9],
 		}
+		post.Target = new(Target)
+		post.Target.TargetStr = item[2]
 		post.Flow = new(Flow)
 		post.Flow.ExportFlow = int64(common.GetIntNoErrByStr(item[7]))
 		post.Flow.InletFlow = int64(common.GetIntNoErrByStr(item[8]))
@@ -212,7 +212,7 @@ func (s *Csv) StoreHostToCsv() {
 		}
 		record := []string{
 			host.Host,
-			host.Target,
+			host.Target.TargetStr,
 			strconv.Itoa(host.Client.Id),
 			host.HeaderChange,
 			host.HostChange,
@@ -274,6 +274,16 @@ func (s *Csv) LoadClientFromCsv() {
 		} else {
 			post.ConfigConnAllow = true
 		}
+		if len(item) >= 13 {
+			post.WebUserName = item[12]
+		} else {
+			post.WebUserName = ""
+		}
+		if len(item) >= 14 {
+			post.WebPassword = item[13]
+		} else {
+			post.WebPassword = ""
+		}
 		s.Clients.Store(post.Id, post)
 	}
 }
@@ -289,7 +299,6 @@ func (s *Csv) LoadHostFromCsv() {
 	for _, item := range records {
 		post := &Host{
 			Host:         item[0],
-			Target:       item[1],
 			HeaderChange: item[3],
 			HostChange:   item[4],
 			Remark:       item[5],
@@ -299,6 +308,8 @@ func (s *Csv) LoadHostFromCsv() {
 		if post.Client, err = s.GetClient(common.GetIntNoErrByStr(item[2])); err != nil {
 			continue
 		}
+		post.Target = new(Target)
+		post.Target.TargetStr = item[1]
 		post.Flow = new(Flow)
 		post.Flow.ExportFlow = int64(common.GetIntNoErrByStr(item[8]))
 		post.Flow.InletFlow = int64(common.GetIntNoErrByStr(item[9]))
@@ -344,12 +355,12 @@ func (s *Csv) IsHostExist(h *Host) bool {
 }
 
 func (s *Csv) NewHost(t *Host) error {
-	if s.IsHostExist(t) {
-		return errors.New("host has exist")
-	}
 	if t.Location == "" {
 		t.Location = "/"
 	}
+	if s.IsHostExist(t) {
+		return errors.New("host has exist")
+	}
 	t.Flow = new(Flow)
 	s.Hosts.Store(t.Id, t)
 	s.StoreHostToCsv()
@@ -359,7 +370,7 @@ func (s *Csv) NewHost(t *Host) error {
 func (s *Csv) GetHost(start, length int, id int, search string) ([]*Host, int) {
 	list := make([]*Host, 0)
 	var cnt int
-	keys := common.GetMapKeys(s.Hosts)
+	keys := GetMapKeys(s.Hosts, false, "", "")
 	for _, key := range keys {
 		if value, ok := s.Hosts.Load(key); ok {
 			v := value.(*Host)
@@ -387,6 +398,9 @@ func (s *Csv) DelClient(id int) error {
 
 func (s *Csv) NewClient(c *Client) error {
 	var isNotSet bool
+	if c.WebUserName != "" && !s.VerifyUserName(c.WebUserName, c.Id) {
+		return errors.New("web login username duplicate, please reset")
+	}
 reset:
 	if c.VerifyKey == "" || isNotSet {
 		isNotSet = true
@@ -396,7 +410,7 @@ reset:
 		c.Rate = rate.NewRate(int64(2 << 23))
 		c.Rate.Start()
 	}
-	if !s.VerifyVkey(c.VerifyKey, c.id) {
+	if !s.VerifyVkey(c.VerifyKey, c.Id) {
 		if isNotSet {
 			goto reset
 		}
@@ -425,6 +439,18 @@ func (s *Csv) VerifyVkey(vkey string, id int) (res bool) {
 	})
 	return res
 }
+func (s *Csv) VerifyUserName(username string, id int) (res bool) {
+	res = true
+	s.Clients.Range(func(key, value interface{}) bool {
+		v := value.(*Client)
+		if v.WebUserName == username && v.Id != id {
+			res = false
+			return false
+		}
+		return true
+	})
+	return res
+}
 
 func (s *Csv) GetClientId() int32 {
 	return atomic.AddInt32(&s.ClientIncreaseId, 1)
@@ -447,10 +473,10 @@ func (s *Csv) UpdateClient(t *Client) error {
 	return nil
 }
 
-func (s *Csv) GetClientList(start, length int, search string, clientId int) ([]*Client, int) {
+func (s *Csv) GetClientList(start, length int, search, sort, order string, clientId int) ([]*Client, int) {
 	list := make([]*Client, 0)
 	var cnt int
-	keys := common.GetMapKeys(s.Clients)
+	keys := GetMapKeys(s.Clients, true, sort, order)
 	for _, key := range keys {
 		if value, ok := s.Clients.Load(key); ok {
 			v := value.(*Client)
@@ -477,11 +503,7 @@ func (s *Csv) GetClientList(start, length int, search string, clientId int) ([]*
 func (s *Csv) IsPubClient(id int) bool {
 	client, err := s.GetClient(id)
 	if err == nil {
-		if client.VerifyKey == beego.AppConfig.String("public_vkey") {
-			return true
-		} else {
-			return false
-		}
+		return client.NoDisplay
 	}
 	return false
 }
@@ -589,6 +611,8 @@ func (s *Csv) StoreClientsToCsv() {
 			strconv.Itoa(int(client.Flow.FlowLimit)),
 			strconv.Itoa(int(client.MaxConn)),
 			common.GetStrByBool(client.ConfigConnAllow),
+			client.WebUserName,
+			client.WebPassword,
 		}
 		err := writer.Write(record)
 		if err != nil {

+ 54 - 77
lib/file/obj.go

@@ -5,13 +5,14 @@ import (
 	"github.com/pkg/errors"
 	"strings"
 	"sync"
+	"sync/atomic"
 	"time"
 )
 
 type Flow struct {
-	ExportFlow int64 //出口流量
-	InletFlow  int64 //入口流量
-	FlowLimit  int64 //流量限制,出口+入口 /M
+	ExportFlow int64
+	InletFlow  int64
+	FlowLimit  int64
 	sync.RWMutex
 }
 
@@ -25,20 +26,21 @@ func (s *Flow) Add(in, out int64) {
 type Client struct {
 	Cnf             *Config
 	Id              int        //id
-	VerifyKey       string     //验证密钥
-	Addr            string     //客户端ip地址
-	Remark          string     //备注
-	Status          bool       //是否开启
-	IsConnect       bool       //是否连接
-	RateLimit       int        //速度限制 /kb
-	Flow            *Flow      //流量
-	Rate            *rate.Rate //速度控制
-	NoStore         bool
-	NoDisplay       bool
-	MaxConn         int //客户端最大连接数
-	NowConn         int //当前连接数
-	id              int
-	ConfigConnAllow bool
+	VerifyKey       string     //verify key
+	Addr            string     //the ip of client
+	Remark          string     //remark
+	Status          bool       //is allow connect
+	IsConnect       bool       //is the client connect
+	RateLimit       int        //rate /kb
+	Flow            *Flow      //flow setting
+	Rate            *rate.Rate //rate limit
+	NoStore         bool       //no store to file
+	NoDisplay       bool       //no display on web
+	MaxConn         int        //the max connection num of client allow
+	NowConn         int32      //the connection num of now
+	WebUserName     string     //the username of web login
+	WebPassword     string     //the password of web login
+	ConfigConnAllow bool       //is allow connected by config file
 	sync.RWMutex
 }
 
@@ -61,30 +63,21 @@ func NewClient(vKey string, noStore bool, noDisplay bool) *Client {
 }
 
 func (s *Client) CutConn() {
-	s.Lock()
-	defer s.Unlock()
-	s.NowConn++
+	atomic.AddInt32(&s.NowConn, 1)
 }
 
 func (s *Client) AddConn() {
-	s.Lock()
-	defer s.Unlock()
-	s.NowConn--
+	atomic.AddInt32(&s.NowConn, -1)
 }
 
 func (s *Client) GetConn() bool {
-	if s.MaxConn == 0 || s.NowConn < s.MaxConn {
+	if s.MaxConn == 0 || int(s.NowConn) < s.MaxConn {
 		s.CutConn()
 		return true
 	}
 	return false
 }
 
-//modify the hosts and the tunnels by health information
-func (s *Client) ModifyTarget() {
-
-}
-
 func (s *Client) HasTunnel(t *Tunnel) (exist bool) {
 	GetCsvDb().Tasks.Range(func(key, value interface{}) bool {
 		v := value.(*Tunnel)
@@ -114,13 +107,11 @@ type Tunnel struct {
 	Id         int //Id
 	Port       int //服务端监听端口
 	ServerIp   string
-	Mode       string   //启动方式
-	Target     string   //目标
-	TargetArr  []string //目标
-	Status     bool     //设置是否开启
-	RunStatus  bool     //当前运行状态
-	Client     *Client  //所属客户端id
-	Ports      string   //客户端与服务端传递
+	Mode       string  //启动方式
+	Status     bool    //设置是否开启
+	RunStatus  bool    //当前运行状态
+	Client     *Client //所属客户端id
+	Ports      string  //客户端与服务端传递
 	Flow       *Flow
 	Password   string //私密模式密码,唯一
 	Remark     string //备注
@@ -128,7 +119,7 @@ type Tunnel struct {
 	NoStore    bool
 	LocalPath  string
 	StripPre   string
-	NowIndex   int
+	Target     *Target
 	Health
 	sync.RWMutex
 }
@@ -146,9 +137,16 @@ type Health struct {
 	sync.RWMutex
 }
 
-func (s *Tunnel) GetRandomTarget() (string, error) {
+type Target struct {
+	nowIndex  int
+	TargetStr string
+	TargetArr []string //目标
+	sync.RWMutex
+}
+
+func (s *Target) GetRandomTarget() (string, error) {
 	if s.TargetArr == nil {
-		s.TargetArr = strings.Split(s.Target, "\n")
+		s.TargetArr = strings.Split(s.TargetStr, "\n")
 	}
 	if len(s.TargetArr) == 1 {
 		return s.TargetArr[0], nil
@@ -158,54 +156,33 @@ func (s *Tunnel) GetRandomTarget() (string, error) {
 	}
 	s.Lock()
 	defer s.Unlock()
-	if s.NowIndex >= len(s.TargetArr)-1 {
-		s.NowIndex = -1
+	if s.nowIndex >= len(s.TargetArr)-1 {
+		s.nowIndex = -1
 	}
-	s.NowIndex++
-	return s.TargetArr[s.NowIndex], nil
+	s.nowIndex++
+	return s.TargetArr[s.nowIndex], nil
 }
 
 type Config struct {
-	U        string //socks5验证用户名
-	P        string //socks5验证密码
-	Compress bool   //压缩方式
-	Crypt    bool   //是否加密
+	U        string
+	P        string
+	Compress bool
+	Crypt    bool
 }
 
 type Host struct {
 	Id           int
-	Host         string //启动方式
-	Target       string //目标
-	HeaderChange string //host修改
-	HostChange   string //host修改
-	Location     string //url 路由
-	Flow         *Flow
-	Client       *Client
-	Remark       string //备注
-	NowIndex     int
-	TargetArr    []string
-	NoStore      bool
+	Host         string //host
+	HeaderChange string //header change
+	HostChange   string //host change
+	Location     string //url router
+	Remark       string //remark
 	Scheme       string //http https all
+	NoStore      bool
 	IsClose      bool
+	Flow         *Flow
+	Client       *Client
+	Target       *Target //目标
 	Health
 	sync.RWMutex
 }
-
-func (s *Host) GetRandomTarget() (string, error) {
-	if s.TargetArr == nil {
-		s.TargetArr = strings.Split(s.Target, "\n")
-	}
-	if len(s.TargetArr) == 1 {
-		return s.TargetArr[0], nil
-	}
-	if len(s.TargetArr) == 0 {
-		return "", errors.New("all inward-bending targets are offline")
-	}
-	s.Lock()
-	defer s.Unlock()
-	if s.NowIndex >= len(s.TargetArr)-1 {
-		s.NowIndex = -1
-	}
-	s.NowIndex++
-	return s.TargetArr[s.NowIndex], nil
-}

+ 41 - 0
lib/file/sort.go

@@ -0,0 +1,41 @@
+package file
+
+import (
+	"reflect"
+	"sort"
+	"sync"
+)
+
+// A data structure to hold a key/value pair.
+type Pair struct {
+	key        string //sort key
+	cId        int
+	order      string
+	clientFlow *Flow
+}
+
+// A slice of Pairs that implements sort.Interface to sort by Value.
+type PairList []*Pair
+
+func (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+func (p PairList) Len() int      { return len(p) }
+func (p PairList) Less(i, j int) bool {
+	if p[i].order == "desc" {
+		return reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() < reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int()
+	}
+	return reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() > reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int()
+}
+
+// A function to turn a map into a PairList, then sort and return it.
+func sortClientByKey(m sync.Map, sortKey, order string) (res []int) {
+	p := make(PairList, 0)
+	m.Range(func(key, value interface{}) bool {
+		p = append(p, &Pair{sortKey, value.(*Client).Id, order, value.(*Client).Flow})
+		return true
+	})
+	sort.Sort(p)
+	for _, v := range p {
+		res = append(res, v.cId)
+	}
+	return
+}

+ 1 - 8
lib/mux/mux.go

@@ -59,16 +59,9 @@ func (s *Mux) NewConn() (*conn, error) {
 		return nil, errors.New("the mux has closed")
 	}
 	conn := NewConn(s.getId(), s)
-	raw := bytes.NewBuffer([]byte{})
-	if err := binary.Write(raw, binary.LittleEndian, MUX_NEW_CONN); err != nil {
-		return nil, err
-	}
-	if err := binary.Write(raw, binary.LittleEndian, conn.connId); err != nil {
-		return nil, err
-	}
 	//it must be set before send
 	s.connMap.Set(conn.connId, conn)
-	if _, err := s.conn.Write(raw.Bytes()); err != nil {
+	if err := s.sendInfo(MUX_NEW_CONN, conn.connId, nil); err != nil {
 		return nil, err
 	}
 	//set a timer timeout 30 second

+ 3 - 3
lib/mux/mux_test.go

@@ -25,14 +25,14 @@ func TestNewMux(t *testing.T) {
 	client()
 	time.Sleep(time.Second * 3)
 	go func() {
-		m2 := NewMux(conn2)
+		m2 := NewMux(conn2, "tcp")
 		for {
 			c, err := m2.Accept()
 			if err != nil {
 				log.Fatalln(err)
 			}
 			go func(c net.Conn) {
-				c2, err := net.Dial("tcp", "10.1.50.196:4000")
+				c2, err := net.Dial("tcp", "127.0.0.1:8082")
 				if err != nil {
 					log.Fatalln(err)
 				}
@@ -45,7 +45,7 @@ func TestNewMux(t *testing.T) {
 	}()
 
 	go func() {
-		m1 := NewMux(conn1)
+		m1 := NewMux(conn1, "tcp")
 		l, err := net.Listen("tcp", "127.0.0.1:7777")
 		if err != nil {
 			log.Fatalln(err)

+ 0 - 6
lib/pool/pool.go

@@ -52,12 +52,6 @@ func GetBufPoolCopy() ([]byte) {
 	return (*BufPoolCopy.Get().(*[]byte))[:PoolSizeCopy]
 }
 
-func PutBufPoolSmall(buf []byte) {
-	if cap(buf) == PoolSizeSmall {
-		BufPoolSmall.Put(buf[:PoolSizeSmall])
-	}
-}
-
 func PutBufPoolMax(buf []byte) {
 	if cap(buf) == PoolSize {
 		BufPoolMax.Put(buf[:PoolSize])

+ 4 - 4
lib/rate/rate.go

@@ -6,10 +6,10 @@ import (
 )
 
 type Rate struct {
-	bucketSize        int64     //木桶容量
-	bucketSurplusSize int64     //当前桶中体积
-	bucketAddSize     int64     //每次加水大小
-	stopChan          chan bool //停止
+	bucketSize        int64
+	bucketSurplusSize int64
+	bucketAddSize     int64
+	stopChan          chan bool
 	NowRate           int64
 }
 

+ 0 - 1
server/proxy/base.go

@@ -87,6 +87,5 @@ func (s *BaseServer) DealClient(c *conn.Conn, client *file.Client, addr string,
 		}
 		conn.CopyWaitGroup(target, c.Conn, link.Crypt, link.Compress, client.Rate, flow, true, rb)
 	}
-	client.AddConn()
 	return nil
 }

+ 4 - 11
server/proxy/http.go

@@ -85,11 +85,12 @@ func (s *httpServer) processHttps(c net.Conn) {
 		c.Close()
 		return
 	}
+	defer host.Client.AddConn()
 	if err = s.auth(r, conn.NewConn(c), host.Client.Cnf.U, host.Client.Cnf.P); err != nil {
 		logs.Warn("auth error", err, r.RemoteAddr)
 		return
 	}
-	if targetAddr, err = host.GetRandomTarget(); err != nil {
+	if targetAddr, err = host.Target.GetRandomTarget(); err != nil {
 		logs.Warn(err.Error())
 	}
 	logs.Trace("new https connection,clientId %d,host %s,remote address %s", host.Client.Id, r.Host, c.RemoteAddr().String())
@@ -101,7 +102,6 @@ func (s *httpServer) Start() error {
 	if s.errorContent, err = common.ReadAllFromFile(filepath.Join(common.GetRunPath(), "web", "static", "page", "error.html")); err != nil {
 		s.errorContent = []byte("easyProxy 404")
 	}
-
 	if s.httpPort > 0 {
 		s.httpServer = s.NewServer(s.httpPort, "http")
 		go func() {
@@ -181,7 +181,6 @@ func (s *httpServer) handleTunneling(w http.ResponseWriter, r *http.Request) {
 }
 
 func (s *httpServer) process(c *conn.Conn, r *http.Request) {
-	//多客户端域名代理
 	var (
 		isConn     = true
 		host       *file.Host
@@ -203,6 +202,7 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) {
 		c.Close()
 		return
 	}
+	defer host.Client.AddConn()
 	logs.Trace("new %s connection,clientId %d,host %s,url %s,remote address %s", r.URL.Scheme, host.Client.Id, r.Host, r.URL, r.RemoteAddr)
 	lastHost = host
 	for {
@@ -212,7 +212,7 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) {
 				logs.Warn("auth error", err, r.RemoteAddr)
 				break
 			}
-			if targetAddr, err = host.GetRandomTarget(); err != nil {
+			if targetAddr, err = host.Target.GetRandomTarget(); err != nil {
 				logs.Warn(err.Error())
 				break
 			}
@@ -249,10 +249,6 @@ func (s *httpServer) process(c *conn.Conn, r *http.Request) {
 				logs.Notice("the url %s %s %s can't be parsed!", r.URL.Scheme, r.Host, r.RequestURI)
 				break
 			} else if host != lastHost {
-				host.Client.AddConn()
-				if !hostTmp.Client.GetConn() {
-					break
-				}
 				host = hostTmp
 				lastHost = host
 				isConn = true
@@ -283,9 +279,6 @@ end:
 		target.Close()
 	}
 	wg.Wait()
-	if host != nil {
-		host.Client.AddConn()
-	}
 }
 
 func (s *httpServer) NewServer(port int, scheme string) *http.Server {

+ 1 - 0
server/proxy/socks5.go

@@ -259,6 +259,7 @@ func (s *Sock5ModeServer) Start() error {
 		}
 		logs.Trace("New socks5 connection,client %d,remote address %s", s.task.Client.Id, c.RemoteAddr())
 		s.handleConn(c)
+		s.task.Client.AddConn()
 	}, &s.listener)
 }
 

+ 2 - 1
server/proxy/tcp.go

@@ -40,6 +40,7 @@ func (s *TunnelModeServer) Start() error {
 		}
 		logs.Trace("new tcp connection,local port %d,client %d,remote address %s", s.task.Port, s.task.Client.Id, c.RemoteAddr())
 		s.process(conn.NewConn(c), s)
+		s.task.Client.AddConn()
 	}, &s.listener)
 }
 
@@ -87,7 +88,7 @@ type process func(c *conn.Conn, s *TunnelModeServer) error
 
 //tcp隧道模式
 func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error {
-	targetAddr, err := s.task.GetRandomTarget()
+	targetAddr, err := s.task.Target.GetRandomTarget()
 	if err != nil {
 		c.Close()
 		logs.Warn("tcp port %d ,client id %d,task id %d connect error %s", s.task.Port, s.task.Client.Id, s.task.Id, err.Error())

+ 1 - 1
server/proxy/udp.go

@@ -54,7 +54,7 @@ func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) {
 		return
 	}
 	defer s.task.Client.AddConn()
-	link := conn.NewLink(common.CONN_UDP, s.task.Target, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, addr.String())
+	link := conn.NewLink(common.CONN_UDP, s.task.Target.TargetStr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, addr.String())
 	if target, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, addr.String(), s.task); err != nil {
 		return
 	} else {

+ 18 - 9
server/server.go

@@ -57,8 +57,12 @@ func DealBridgeTask() {
 		case t := <-Bridge.CloseTask:
 			StopServer(t.Id)
 		case id := <-Bridge.CloseClient:
-			DelTunnelAndHostByClientId(id)
-			file.GetCsvDb().DelClient(id)
+			DelTunnelAndHostByClientId(id, true)
+			if v, ok := file.GetCsvDb().Clients.Load(id); ok {
+				if v.(*file.Client).NoStore {
+					file.GetCsvDb().DelClient(id)
+				}
+			}
 		case tunnel := <-Bridge.OpenTask:
 			StartTask(tunnel.Id)
 		case s := <-Bridge.SecretChan:
@@ -68,7 +72,7 @@ func DealBridgeTask() {
 					logs.Info("Connections exceed the current client %d limit", t.Client.Id)
 					s.Conn.Close()
 				} else if t.Status {
-					go proxy.NewBaseServer(Bridge, t).DealClient(s.Conn, t.Client, t.Target, nil, common.CONN_TCP, nil, t.Flow)
+					go proxy.NewBaseServer(Bridge, t).DealClient(s.Conn, t.Client, t.Target.TargetStr, nil, common.CONN_TCP, nil, t.Flow)
 				} else {
 					s.Conn.Close()
 					logs.Trace("This key %s cannot be processed,status is close", s.Password)
@@ -133,7 +137,6 @@ func NewMode(Bridge *bridge.Bridge, c *file.Tunnel) proxy.Service {
 		t := &file.Tunnel{
 			Port:   0,
 			Mode:   "httpHostServer",
-			Target: "",
 			Status: true,
 		}
 		AddTask(t)
@@ -223,7 +226,7 @@ func DelTask(id int) error {
 func GetTunnel(start, length int, typeVal string, clientId int, search string) ([]*file.Tunnel, int) {
 	list := make([]*file.Tunnel, 0)
 	var cnt int
-	keys := common.GetMapKeys(file.GetCsvDb().Tasks)
+	keys := file.GetMapKeys(file.GetCsvDb().Tasks, false, "", "")
 	for _, key := range keys {
 		if value, ok := file.GetCsvDb().Tasks.Load(key); ok {
 			v := value.(*file.Tunnel)
@@ -255,8 +258,8 @@ func GetTunnel(start, length int, typeVal string, clientId int, search string) (
 }
 
 //获取客户端列表
-func GetClientList(start, length int, search string, clientId int) (list []*file.Client, cnt int) {
-	list, cnt = file.GetCsvDb().GetClientList(start, length, search, clientId)
+func GetClientList(start, length int, search, sort, order string, clientId int) (list []*file.Client, cnt int) {
+	list, cnt = file.GetCsvDb().GetClientList(start, length, search, sort, order, clientId)
 	dealClientData()
 	return
 }
@@ -293,10 +296,13 @@ func dealClientData() {
 }
 
 //根据客户端id删除其所属的所有隧道和域名
-func DelTunnelAndHostByClientId(clientId int) {
+func DelTunnelAndHostByClientId(clientId int, justDelNoStore bool) {
 	var ids []int
 	file.GetCsvDb().Tasks.Range(func(key, value interface{}) bool {
 		v := value.(*file.Tunnel)
+		if justDelNoStore && !v.NoStore {
+			return true
+		}
 		if v.Client.Id == clientId {
 			ids = append(ids, v.Id)
 		}
@@ -308,6 +314,9 @@ func DelTunnelAndHostByClientId(clientId int) {
 	ids = ids[:0]
 	file.GetCsvDb().Hosts.Range(func(key, value interface{}) bool {
 		v := value.(*file.Host)
+		if justDelNoStore && !v.NoStore {
+			return true
+		}
 		if v.Client.Id == clientId {
 			ids = append(ids, v.Id)
 		}
@@ -378,7 +387,7 @@ func GetDashboardData() map[string]interface{} {
 	tcpCount := 0
 
 	file.GetCsvDb().Clients.Range(func(key, value interface{}) bool {
-		tcpCount += value.(*file.Client).NowConn
+		tcpCount += int(value.(*file.Client).NowConn)
 		return true
 	})
 	data["tcpCount"] = tcpCount

+ 22 - 10
web/controllers/client.go

@@ -5,6 +5,7 @@ import (
 	"github.com/cnlh/nps/lib/file"
 	"github.com/cnlh/nps/lib/rate"
 	"github.com/cnlh/nps/server"
+	"github.com/cnlh/nps/vender/github.com/astaxie/beego"
 )
 
 type ClientController struct {
@@ -26,7 +27,7 @@ func (s *ClientController) List() {
 	} else {
 		clientId = clientIdSession.(int)
 	}
-	list, cnt := server.GetClientList(start, length, s.GetString("search"), clientId)
+	list, cnt := server.GetClientList(start, length, s.GetString("search"), s.GetString("sort"), s.GetString("order"), clientId)
 	s.AjaxTable(list, cnt, cnt)
 }
 
@@ -51,6 +52,8 @@ func (s *ClientController) Add() {
 			ConfigConnAllow: s.GetBoolNoErr("config_conn_allow"),
 			RateLimit:       s.GetIntNoErr("rate_limit"),
 			MaxConn:         s.GetIntNoErr("max_conn"),
+			WebUserName:     s.GetString("web_username"),
+			WebPassword:     s.GetString("web_password"),
 			Flow: &file.Flow{
 				ExportFlow: 0,
 				InletFlow:  0,
@@ -98,20 +101,29 @@ func (s *ClientController) Edit() {
 		if c, err := file.GetCsvDb().GetClient(id); err != nil {
 			s.error()
 		} else {
-			if !file.GetCsvDb().VerifyVkey(s.GetString("vkey"), c.Id) {
-				s.AjaxErr("Vkey duplicate, please reset")
+			if s.GetString("web_username") != "" {
+				if s.GetString("web_username") == beego.AppConfig.String("web_username") || !file.GetCsvDb().VerifyUserName(s.GetString("web_username"), c.Id) {
+					s.AjaxErr("web login username duplicate, please reset")
+					return
+				}
 			}
-			c.VerifyKey = s.GetString("vkey")
-			c.Remark = s.GetString("remark")
-			c.Cnf.U = s.GetString("u")
-			c.Cnf.P = s.GetString("p")
-			c.Cnf.Compress = common.GetBoolByStr(s.GetString("compress"))
-			c.Cnf.Crypt = s.GetBoolNoErr("crypt")
 			if s.GetSession("isAdmin").(bool) {
+				if !file.GetCsvDb().VerifyVkey(s.GetString("vkey"), c.Id) {
+					s.AjaxErr("Vkey duplicate, please reset")
+					return
+				}
+				c.VerifyKey = s.GetString("vkey")
 				c.Flow.FlowLimit = int64(s.GetIntNoErr("flow_limit"))
 				c.RateLimit = s.GetIntNoErr("rate_limit")
 				c.MaxConn = s.GetIntNoErr("max_conn")
 			}
+			c.Remark = s.GetString("remark")
+			c.Cnf.U = s.GetString("u")
+			c.Cnf.P = s.GetString("p")
+			c.Cnf.Compress = common.GetBoolByStr(s.GetString("compress"))
+			c.Cnf.Crypt = s.GetBoolNoErr("crypt")
+			c.WebUserName = s.GetString("web_username")
+			c.WebPassword = s.GetString("web_password")
 			c.ConfigConnAllow = s.GetBoolNoErr("config_conn_allow")
 			if c.Rate != nil {
 				c.Rate.Stop()
@@ -148,7 +160,7 @@ func (s *ClientController) Del() {
 	if err := file.GetCsvDb().DelClient(id); err != nil {
 		s.AjaxErr("delete error")
 	}
-	server.DelTunnelAndHostByClientId(id)
+	server.DelTunnelAndHostByClientId(id, false)
 	server.DelClientConnect(id)
 	s.AjaxOk("delete success")
 }

+ 29 - 19
web/controllers/index.go

@@ -93,7 +93,7 @@ func (s *IndexController) Add() {
 			Port:      s.GetIntNoErr("port"),
 			ServerIp:  s.GetString("server_ip"),
 			Mode:      s.GetString("type"),
-			Target:    s.GetString("target"),
+			Target:    &file.Target{TargetStr: s.GetString("target")},
 			Id:        int(file.GetCsvDb().GetTaskId()),
 			Status:    true,
 			Remark:    s.GetString("remark"),
@@ -145,29 +145,30 @@ func (s *IndexController) Edit() {
 		if t, err := file.GetCsvDb().GetTask(id); err != nil {
 			s.error()
 		} else {
-			var portChange bool
+			if client, err := file.GetCsvDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
+				s.AjaxErr("modified error,the client is not exist")
+				return
+			} else {
+				t.Client = client
+			}
 			if s.GetIntNoErr("port") != t.Port {
-				portChange = true
+				if !tool.TestServerPort(s.GetIntNoErr("port"), t.Mode) {
+					s.AjaxErr("The port cannot be opened because it may has been occupied or is no longer allowed.")
+					return
+				}
 				t.Port = s.GetIntNoErr("port")
 			}
 			t.ServerIp = s.GetString("server_ip")
 			t.Mode = s.GetString("type")
-			t.Target = s.GetString("target")
+			t.Target = &file.Target{TargetStr: s.GetString("target")}
 			t.Password = s.GetString("password")
 			t.Id = id
 			t.LocalPath = s.GetString("local_path")
 			t.StripPre = s.GetString("strip_pre")
 			t.Remark = s.GetString("remark")
-			if portChange && !tool.TestServerPort(t.Port, t.Mode) {
-				s.AjaxErr("The port cannot be opened because it may has been occupied or is no longer allowed.")
-			}
-			if t.Client, err = file.GetCsvDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
-				s.AjaxErr("modified error")
-			}
 			file.GetCsvDb().UpdateTask(t)
 			server.StopServer(t.Id)
 			server.StartTask(t.Id)
-			t.TargetArr = nil
 		}
 		s.AjaxOk("modified success")
 	}
@@ -243,7 +244,7 @@ func (s *IndexController) AddHost() {
 		h := &file.Host{
 			Id:           int(file.GetCsvDb().GetHostId()),
 			Host:         s.GetString("host"),
-			Target:       s.GetString("target"),
+			Target:       &file.Target{TargetStr: s.GetString("target")},
 			HeaderChange: s.GetString("header"),
 			HostChange:   s.GetString("hostchange"),
 			Remark:       s.GetString("remark"),
@@ -277,20 +278,29 @@ func (s *IndexController) EditHost() {
 		if h, err := file.GetCsvDb().GetHostById(id); err != nil {
 			s.error()
 		} else {
+			if h.Host != s.GetString("host") {
+				tmpHost := new(file.Host)
+				tmpHost.Host = s.GetString("host")
+				tmpHost.Location = s.GetString("location")
+				tmpHost.Scheme = s.GetString("scheme")
+				if file.GetCsvDb().IsHostExist(tmpHost) {
+					s.AjaxErr("host has exist")
+					return
+				}
+			}
+			if client, err := file.GetCsvDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
+				s.AjaxErr("modified error,the client is not exist")
+			} else {
+				h.Client = client
+			}
 			h.Host = s.GetString("host")
-			h.Target = s.GetString("target")
+			h.Target = &file.Target{TargetStr: s.GetString("target")}
 			h.HeaderChange = s.GetString("header")
 			h.HostChange = s.GetString("hostchange")
 			h.Remark = s.GetString("remark")
-			h.TargetArr = nil
 			h.Location = s.GetString("location")
 			h.Scheme = s.GetString("scheme")
 			file.GetCsvDb().StoreHostToCsv()
-			var err error
-			if h.Client, err = file.GetCsvDb().GetClient(s.GetIntNoErr("client_id")); err != nil {
-				s.AjaxErr("modified error")
-			}
-			h.TargetArr = nil
 		}
 		s.AjaxOk("modified success")
 	}

+ 15 - 3
web/controllers/login.go

@@ -23,13 +23,25 @@ func (self *LoginController) Verify() {
 		server.Bridge.Register.Store(common.GetIpByAddr(self.Ctx.Request.RemoteAddr), time.Now().Add(time.Hour*time.Duration(2)))
 	}
 	b, err := beego.AppConfig.Bool("allow_user_login")
-	if err == nil && b && self.GetString("username") == "user" && !auth {
+	if err == nil && b && !auth {
 		file.GetCsvDb().Clients.Range(func(key, value interface{}) bool {
 			v := value.(*file.Client)
-			if v.VerifyKey == self.GetString("password") && v.Status {
+			if !v.Status || v.NoDisplay {
+				return true
+			}
+			if v.WebUserName == "" && v.WebPassword == "" {
+				if self.GetString("username") != "user" || v.VerifyKey != self.GetString("password") {
+					return true
+				} else {
+					auth = true
+				}
+			}
+			if !auth && v.WebPassword == self.GetString("password") && self.GetString("username") == v.WebUserName {
+				auth = true
+			}
+			if auth {
 				self.SetSession("isAdmin", false)
 				self.SetSession("clientId", v.Id)
-				auth = true
 				return false
 			}
 			return true

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

@@ -60,6 +60,20 @@
                             <span class="help-block m-b-none">unique, non-filling will be generated automatically</span>
                         </div>
                     </div>
+                    <div class="form-group" id="web_username">
+                        <label class="control-label col-sm-2" langtag="info-client-web-username">web登陆用户名</label>
+                        <div class="col-sm-10">
+                            <input class="form-control" type="text" name="web_username"
+                                   placeholder="empty means to be unrestricted">
+                        </div>
+                    </div>
+                    <div class="form-group" id="web_password">
+                        <label class="control-label col-sm-2" langtag="info-client-web-password">web登陆密码</label>
+                        <div class="col-sm-10">
+                            <input class="form-control" type="text" name="web_password"
+                                   placeholder="empty means to be unrestricted">
+                        </div>
+                    </div>
                     <div class="form-group" id="config_conn_allow">
                         <label class="control-label col-sm-2" langtag="info-config-conn-allow">是否允许客户端以配置文件模式连接</label>
                         <div class="col-sm-10">

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

@@ -54,6 +54,7 @@
                             <span class="help-block m-b-none">only socks5 , web, HTTP forward proxy </span>
                         </div>
                     </div>
+                {{if eq true .isAdmin}}
                     <div class="form-group" id="vkey">
                         <label class="control-label col-sm-2" langtag="info-client-vkey">客户端验证密钥</label>
                         <div class="col-sm-10">
@@ -62,6 +63,21 @@
                             <span class="help-block m-b-none">unique, non-filling will be generated automatically</span>
                         </div>
                     </div>
+                {{end}}
+                    <div class="form-group" id="web_username">
+                        <label class="control-label col-sm-2" langtag="info-client-web-username">web登陆用户名</label>
+                        <div class="col-sm-10">
+                            <input class="form-control" value="{{.c.WebUserName}}" type="text" name="web_username"
+                                   placeholder="empty means to be unrestricted">
+                        </div>
+                    </div>
+                    <div class="form-group" id="web_password">
+                        <label class="control-label col-sm-2" langtag="info-client-web-password">web登陆密码</label>
+                        <div class="col-sm-10">
+                            <input class="form-control" value="{{.c.WebPassword}}" type="text" name="web_password"
+                                   placeholder="empty means to be unrestricted">
+                        </div>
+                    </div>
                     <div class="form-group" id="config_conn_allow">
                         <label class="control-label col-sm-2" langtag="info-config-conn-allow">是否允许客户端以配置文件模式连接</label>
                         <div class="col-sm-10">
@@ -93,7 +109,7 @@
                     <div class="form-group">
                         <div class="col-sm-4 col-sm-offset-2">
                             <button class="btn btn-success" href="#" id="add"><i
-                                    class="fa fa-fw fa-lg fa-eye"></i>新增
+                                    class="fa fa-fw fa-lg fa-eye"></i><span langtag="info-save">保存</span>
                             </button>
                         </div>
                     </div>

+ 4 - 11
web/views/client/list.html

@@ -127,6 +127,8 @@
                     + '<b langtag="info-now-conn-num">当前连接数</b>:' + row.NowConn + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
                     + '<b langtag="info-flow-limit">流量限制</b>:' + row.Flow.FlowLimit + `m&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
                     + '<b langtag="info-rate-limit">带宽限制</b>:' + row.RateLimit + `kb/s&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
+                    + '<b langtag="info-client-web-username">web登陆用户名</b>:' + row.WebUserName + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
+                    + '<b langtag="info-client-web-password">web登陆密码</b>:' + row.WebPassword + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
                     + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp` + "<br/><br>"
                     + '<b langtag="info-crypt">加密</b>:' + row.Cnf.Crypt + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
                     + '<b langtag="info-compress">压缩</b>:' + row.Cnf.Compress + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
@@ -141,19 +143,16 @@
                 field: 'Id',//域值
                 title: 'id',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
             },
             {
                 field: 'Remark',//域值
                 title: 'remark',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
             },
             {
                 field: 'VerifyKey',//域值
                 title: 'vkey',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
                 formatter: function (value, row, index) {
                     if (!row.NoStore) {
                         return value
@@ -166,10 +165,9 @@
                 field: 'Addr',//域值
                 title: 'client addr',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
             },
             {
-                field: 'Addr',//域值
+                field: 'InletFlow',//域值
                 title: 'in flow',//标题
                 visible: true,//false表示不显示
                 sortable: true,//启用排序
@@ -178,7 +176,7 @@
                 }
             },
             {
-                field: 'Addr',//域值
+                field: 'ExportFlow',//域值
                 title: 'out flow',//标题
                 visible: true,//false表示不显示
                 sortable: true,//启用排序
@@ -190,7 +188,6 @@
                 field: 'IsConnect',//域值
                 title: 'speed',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
                 formatter: function (value, row, index) {
                     return change(row.Rate.NowRate) + "/S"
                 }
@@ -199,7 +196,6 @@
                 field: 'Status',//域值
                 title: 'run',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
                 formatter: function (value, row, index) {
                     if (value) {
                         return '<span class="badge badge-primary">open</span>'
@@ -212,7 +208,6 @@
                 field: 'IsConnect',//域值
                 title: 'status',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
                 formatter: function (value, row, index) {
                     if (value) {
                         return '<span class="badge badge-primary">online</span>'
@@ -225,7 +220,6 @@
                 field: 'option',//域值
                 title: 'option',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
                 formatter: function (value, row, index) {
                     btn_group = '<div class="btn-group">'
                     btn = `{{if eq true .isAdmin}}<button onclick="del(` + row.Id + `)" class="btn-danger"><i class="fa fa-trash"></i></button>{{end}}<button onclick="edit(` + row.Id + `)" class="btn-primary"><i class="fa fa-edit"></i></button></div>`
@@ -241,7 +235,6 @@
                 field: 'show',//域值
                 title: 'show',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
                 formatter: function (value, row, index) {
                     return `<button onclick="tunnel(` + row.Id + `)" class="btn-info">tunnel</button><button onclick="host(` + row.Id + `)" class="btn-primary">host</button>`
                 }

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

@@ -46,7 +46,7 @@
                         <label class="col-sm-2 control-label" langtag="info-target">内网目标(ip:端口)</label>
                         <div class="col-sm-10">
                             <textarea class="form-control" name="target" rows="4" placeholder="10.1.50.203:22
-10.1.50.202:22">{{.t.Target}}</textarea>
+10.1.50.202:22">{{.t.Target.TargetStr}}</textarea>
                             <span class="help-block m-b-none">can only fill in ports if it is local machine proxy, only tcp supports load balancing
 
 </span></div>
@@ -89,7 +89,7 @@
                     <div class="form-group">
                         <div class="col-sm-4 col-sm-offset-2">
                             <button class="btn btn-success" href="#" id="add"><i
-                                    class="fa fa-fw fa-lg fa-eye"></i>新增
+                                    class="fa fa-fw fa-lg fa-eye"></i><span langtag="info-save">保存</span>
                             </button>
                         </div>
                     </div>

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

@@ -49,7 +49,7 @@
                         <textarea class="form-control" rows="4" type="text" name="target"
                                   placeholder="such as
 10.1.50.203:80
-10.1.50.202:80">{{.h.Target}}</textarea>
+10.1.50.202:80">{{.h.Target.TargetStr}}</textarea>
                             <span class="help-block m-b-none">Line break if load balancing</span>
 
                         </div>
@@ -74,7 +74,7 @@
                     <div class="form-group">
                         <div class="col-sm-4 col-sm-offset-2">
                             &nbsp;<button class="btn btn-success" href="#" id="add"><i
-                                class="fa fa-fw fa-lg fa-eye"></i>新增
+                                class="fa fa-fw fa-lg fa-eye"></i><span langtag="info-save">保存</span>
                         </button>
                         </div>
                     </div>

+ 12 - 9
web/views/index/hlist.html

@@ -77,13 +77,13 @@
                 field: 'Id',//域值
                 title: 'id',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+               
             },
             {
                 field: 'Id',//域值
                 title: 'client id',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+               
                 formatter: function (value, row, index) {
                     return row.Client.Id
                 }
@@ -92,37 +92,40 @@
                 field: 'Remark',//域值
                 title: 'remark',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+               
             },
             {
                 field: 'Host',//域值
                 title: 'host',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+               
             },
             {
                 field: 'Scheme',//域值
                 title: 'scheme',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+               
             },
             {
                 field: 'Target',//域值
                 title: 'target',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+               
+                formatter: function (value, row, index) {
+                    return row.Target.TargetStr
+                }
             },
             {
                 field: 'Location',//域值
                 title: 'location',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+               
             },
             {
                 field: '',//域值
                 title: 'client status',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+               
                 formatter: function (value, row, index) {
                     if (row.Client.IsConnect) {
                         return '<span class="badge badge-primary">online</span>'
@@ -135,7 +138,7 @@
                 field: 'option',//域值
                 title: 'option',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+               
                 formatter: function (value, row, index) {
                     btn_group = '<div class="btn-group">'
                     btn = `<button onclick="del(` + row.Id + `)" class="btn-danger"><i class="fa fa-trash"></i></button><button onclick="edit(` + row.Id + `)" class="btn-primary"><i class="fa fa-edit"></i></button></div>`

+ 8 - 11
web/views/index/list.html

@@ -76,13 +76,11 @@
                 field: 'Id',//域值
                 title: 'id',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
             },
             {
                 field: 'Id',//域值
                 title: 'client id',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
                 formatter: function (value, row, index) {
                     return row.Client.Id
                 }
@@ -91,37 +89,36 @@
                 field: 'Remark',//域值
                 title: 'remark',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
             },
             {
                 field: 'Mode',//域值
                 title: 'mode',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
             },
             {
                 field: 'Port',//域值
                 title: 'port',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
             },
             {
                 field: 'Target',//域值
                 title: 'target',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+                formatter: function (value, row, index) {
+                    return row.Target.TargetStr
+                }
             },
             {
                 field: 'Password',//域值
                 title: 'secret',//标题
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+                
             },
             {
                 field: 'Status',//域值
                 title: 'setting',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+                
                 formatter: function (value, row, index) {
                     if (value) {
                         return '<span class="badge badge-primary">open</span>'
@@ -134,7 +131,7 @@
                 field: 'RunStatus',//域值
                 title: 'run',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+                
                 formatter: function (value, row, index) {
                     if (value) {
                         return '<span class="badge badge-primary">open</span>'
@@ -147,7 +144,7 @@
                 field: '',//域值
                 title: 'client',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+                
                 formatter: function (value, row, index) {
                     if (row.Client.IsConnect) {
                         return '<span class="badge badge-primary">online</span>'
@@ -160,7 +157,7 @@
                 field: 'option',//域值
                 title: 'option',//内容
                 visible: true,//false表示不显示
-                sortable: true,//启用排序
+                
                 formatter: function (value, row, index) {
                     btn_group = '<div class="btn-group">'
                     btn = `<button onclick="del(` + row.Id + `)" class="btn-danger"><i class="fa fa-trash"></i></button><button onclick="edit(` + row.Id + `)" class="btn-primary"><i class="fa fa-edit"></i></button></div>`