浏览代码

Flow display and user login and rate limit bug

刘河 6 年之前
父节点
当前提交
7637cd448e

+ 4 - 2
client/control.go

@@ -189,8 +189,10 @@ func NewConn(tp string, vkey string, server string, connType string, proxyUrl st
 		}
 	} else {
 		sess, err = kcp.DialWithOptions(server, nil, 10, 3)
-		conn.SetUdpSession(sess)
-		connection = sess
+		if err == nil {
+			conn.SetUdpSession(sess)
+			connection = sess
+		}
 	}
 	if err != nil {
 		return nil, err

+ 2 - 0
client/health.go

@@ -79,6 +79,7 @@ func check(t *file.Health) {
 				err = errors.New("status code is not match")
 			}
 		}
+		t.Lock()
 		if err != nil {
 			t.HealthMap[v] += 1
 		} else if t.HealthMap[v] >= t.HealthMaxFail {
@@ -91,5 +92,6 @@ func check(t *file.Health) {
 			//send fail remove
 			serverConn.SendHealthInfo(v, "0")
 		}
+		t.Unlock()
 	}
 }

+ 2 - 0
conf/nps.conf

@@ -46,3 +46,5 @@ auth_key=test
 auth_crypt_key =1234567812345678
 
 #allow_ports=9001-9009,10001,11000-12000
+
+#allow_user_login=true

+ 5 - 5
lib/conn/conn.go

@@ -440,16 +440,16 @@ func CopyWaitGroup(conn1, conn2 net.Conn, crypt bool, snappy bool, rate *rate.Ra
 }
 
 //get crypt or snappy conn
-func GetConn(conn net.Conn, cpt, snappy bool, rate *rate.Rate, isServer bool) (io.ReadWriteCloser) {
+func GetConn(conn net.Conn, cpt, snappy bool, rt *rate.Rate, isServer bool) (io.ReadWriteCloser) {
 	if cpt {
 		if isServer {
-			return crypt.NewTlsServerConn(conn)
+			return rate.NewRateConn(crypt.NewTlsServerConn(conn), rt)
 		}
-		return crypt.NewTlsClientConn(conn)
+		return rate.NewRateConn(crypt.NewTlsClientConn(conn), rt)
 	} else if snappy {
-		return NewSnappyConn(conn, cpt, rate)
+		return NewSnappyConn(conn, cpt, rt)
 	}
-	return conn
+	return rate.NewRateConn(conn, rt)
 }
 
 //read length or id (content length=4)

+ 11 - 0
lib/file/file.go

@@ -256,6 +256,9 @@ func (s *Csv) LoadClientFromCsv() {
 		if post.RateLimit > 0 {
 			post.Rate = rate.NewRate(int64(post.RateLimit * 1024))
 			post.Rate.Start()
+		} else {
+			post.Rate = rate.NewRate(int64(2 << 23))
+			post.Rate.Start()
 		}
 		post.Flow = new(Flow)
 		post.Flow.FlowLimit = int64(common.GetIntNoErrByStr(item[9]))
@@ -382,6 +385,10 @@ reset:
 		isNotSet = true
 		c.VerifyKey = crypt.GetRandomString(16)
 	}
+	if c.RateLimit == 0 {
+		c.Rate = rate.NewRate(int64(2 << 23))
+		c.Rate.Start()
+	}
 	if !s.VerifyVkey(c.VerifyKey, c.id) {
 		if isNotSet {
 			goto reset
@@ -426,6 +433,10 @@ func (s *Csv) GetHostId() int32 {
 
 func (s *Csv) UpdateClient(t *Client) error {
 	s.Clients.Store(t.Id, t)
+	if t.RateLimit == 0 {
+		t.Rate = rate.NewRate(int64(2 << 23))
+		t.Rate.Start()
+	}
 	return nil
 }
 

+ 1 - 0
lib/file/obj.go

@@ -142,6 +142,7 @@ type Health struct {
 	HealthRemoveArr     []string
 	HealthCheckType     string
 	HealthCheckTarget   string
+	sync.RWMutex
 }
 
 func (s *Tunnel) GetRandomTarget() (string, error) {

+ 38 - 0
lib/rate/conn.go

@@ -0,0 +1,38 @@
+package rate
+
+import (
+	"io"
+	"net"
+)
+
+type rateConn struct {
+	conn net.Conn
+	rate *Rate
+}
+
+func NewRateConn(conn net.Conn, rate *Rate) io.ReadWriteCloser {
+	return &rateConn{
+		conn: conn,
+		rate: rate,
+	}
+}
+
+func (s *rateConn) Read(b []byte) (n int, err error) {
+	n, err = s.conn.Read(b)
+	if s.rate != nil {
+		s.rate.Get(int64(n))
+	}
+	return
+}
+
+func (s *rateConn) Write(b []byte) (n int, err error) {
+	n, err = s.conn.Write(b)
+	if s.rate != nil {
+		s.rate.Get(int64(n))
+	}
+	return
+}
+
+func (s *rateConn) Close() error {
+	return s.conn.Close()
+}

+ 8 - 1
lib/rate/rate.go

@@ -10,6 +10,7 @@ type Rate struct {
 	bucketSurplusSize int64     //当前桶中体积
 	bucketAddSize     int64     //每次加水大小
 	stopChan          chan bool //停止
+	NowRate           int64
 }
 
 func NewRate(addSize int64) *Rate {
@@ -26,7 +27,8 @@ func (s *Rate) Start() {
 }
 
 func (s *Rate) add(size int64) {
-	if (s.bucketSize - s.bucketSurplusSize) < s.bucketAddSize {
+	if res := s.bucketSize - s.bucketSurplusSize; res < s.bucketAddSize {
+		atomic.AddInt64(&s.bucketSurplusSize, res)
 		return
 	}
 	atomic.AddInt64(&s.bucketSurplusSize, size)
@@ -65,6 +67,11 @@ func (s *Rate) session() {
 	for {
 		select {
 		case <-ticker.C:
+			if rs := s.bucketAddSize - s.bucketSurplusSize; rs > 0 {
+				s.NowRate = rs
+			} else {
+				s.NowRate = s.bucketSize - s.bucketSurplusSize
+			}
 			s.add(s.bucketAddSize)
 		case <-s.stopChan:
 			ticker.Stop()

+ 1 - 1
server/server.go

@@ -227,7 +227,7 @@ func GetTunnel(start, length int, typeVal string, clientId int, search string) (
 	for _, key := range keys {
 		if value, ok := file.GetCsvDb().Tasks.Load(key); ok {
 			v := value.(*file.Tunnel)
-			if (typeVal != "" && v.Mode != typeVal) || (typeVal == "" && clientId != v.Client.Id) {
+			if (typeVal != "" && v.Mode != typeVal || (clientId != 0 && v.Client.Id != clientId)) || (typeVal == "" && clientId != v.Client.Id) {
 				continue
 			}
 			if search != "" && !(v.Id == common.GetIntNoErrByStr(search) || v.Port == common.GetIntNoErrByStr(search) || strings.Contains(v.Password, search) || strings.Contains(v.Remark, search)) {

+ 36 - 0
web/controllers/base.go

@@ -3,6 +3,7 @@ package controllers
 import (
 	"github.com/cnlh/nps/lib/common"
 	"github.com/cnlh/nps/lib/crypt"
+	"github.com/cnlh/nps/lib/file"
 	"github.com/cnlh/nps/server"
 	"github.com/cnlh/nps/vender/github.com/astaxie/beego"
 	"strconv"
@@ -33,6 +34,14 @@ func (s *BaseController) Prepare() {
 			s.Redirect("/login/index", 302)
 		}
 	}
+	if s.GetSession("isAdmin") != nil && !s.GetSession("isAdmin").(bool) {
+		s.Ctx.Input.SetData("client_id", s.GetSession("clientId").(int))
+		s.Ctx.Input.SetParam("client_id", strconv.Itoa(s.GetSession("clientId").(int)))
+		s.Data["isAdmin"] = false
+		s.CheckUserAuth()
+	} else {
+		s.Data["isAdmin"] = true
+	}
 }
 
 //加载模板
@@ -128,3 +137,30 @@ func (s *BaseController) SetInfo(name string) {
 func (s *BaseController) SetType(name string) {
 	s.Data["type"] = name
 }
+
+func (s *BaseController) CheckUserAuth() {
+	if s.controllerName == "client" {
+		s.StopRun()
+	}
+	if s.controllerName == "index" {
+		if id := s.GetIntNoErr("id"); id != 0 {
+			belong := false
+			if strings.Contains(s.actionName, "H") {
+				if v, ok := file.GetCsvDb().Hosts.Load(id); ok {
+					if v.(*file.Host).Client.Id == s.GetSession("clientId").(int) {
+						belong = true
+					}
+				}
+			} else {
+				if v, ok := file.GetCsvDb().Tasks.Load(id); ok {
+					if v.(*file.Tunnel).Client.Id == s.GetSession("clientId").(int) {
+						belong = true
+					}
+				}
+			}
+			if !belong {
+				s.StopRun()
+			}
+		}
+	}
+}

+ 2 - 1
web/controllers/client.go

@@ -111,7 +111,8 @@ func (s *ClientController) Edit() {
 				c.Rate = rate.NewRate(int64(c.RateLimit * 1024))
 				c.Rate.Start()
 			} else {
-				c.Rate = nil
+				c.Rate = rate.NewRate(int64(2 << 23))
+				c.Rate.Start()
 			}
 			file.GetCsvDb().StoreClientsToCsv()
 		}

+ 21 - 3
web/controllers/login.go

@@ -2,6 +2,7 @@ package controllers
 
 import (
 	"github.com/cnlh/nps/lib/common"
+	"github.com/cnlh/nps/lib/file"
 	"github.com/cnlh/nps/server"
 	"github.com/cnlh/nps/vender/github.com/astaxie/beego"
 	"time"
@@ -15,15 +16,32 @@ func (self *LoginController) Index() {
 	self.TplName = "login/index.html"
 }
 func (self *LoginController) Verify() {
+	var auth bool
 	if self.GetString("password") == beego.AppConfig.String("web_password") && self.GetString("username") == beego.AppConfig.String("web_username") {
+		self.SetSession("isAdmin", true)
+		auth = true
+		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 {
+		file.GetCsvDb().Clients.Range(func(key, value interface{}) bool {
+			v := value.(*file.Client)
+			if v.VerifyKey == self.GetString("password") && v.Status {
+				self.SetSession("isAdmin", false)
+				self.SetSession("clientId", v.Id)
+				auth = true
+				return false
+			}
+			return true
+		})
+	}
+	if auth {
 		self.SetSession("auth", true)
 		self.Data["json"] = map[string]interface{}{"status": 1, "msg": "login success"}
-		server.Bridge.Register.Store(common.GetIpByAddr(self.Ctx.Request.RemoteAddr), time.Now().Add(time.Hour*time.Duration(2)))
-		self.ServeJSON()
 	} else {
 		self.Data["json"] = map[string]interface{}{"status": 0, "msg": "username or password incorrect"}
-		self.ServeJSON()
 	}
+	self.ServeJSON()
 }
 func (self *LoginController) Out() {
 	self.SetSession("auth", false)

+ 28 - 2
web/views/client/list.html

@@ -124,8 +124,7 @@
                     + '<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-export-flow">出口流量</b>:' + change(row.Flow.ExportFlow) + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
-                    + '<b langtag="info-inlet-flow">入口流量</b>:' + change(row.Flow.InletFlow) + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp` + "<br/><br>"
+                    + `&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`
                     + '<b langtag="info-config-conn-allow">是否允许配置文件模式连接</b>:' + row.ConfigConnAllow + `&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp`
@@ -166,6 +165,33 @@
                 visible: true,//false表示不显示
                 sortable: true,//启用排序
             },
+            {
+                field: 'Addr',//域值
+                title: 'in flow',//标题
+                visible: true,//false表示不显示
+                sortable: true,//启用排序
+                formatter: function (value, row, index) {
+                    return change(row.Flow.InletFlow)
+                }
+            },
+            {
+                field: 'Addr',//域值
+                title: 'out flow',//标题
+                visible: true,//false表示不显示
+                sortable: true,//启用排序
+                formatter: function (value, row, index) {
+                    return change(row.Flow.ExportFlow)
+                }
+            },
+            {
+                field: 'IsConnect',//域值
+                title: 'speed',//内容
+                visible: true,//false表示不显示
+                sortable: true,//启用排序
+                formatter: function (value, row, index) {
+                    return change(row.Rate.NowRate) + "/S"
+                }
+            },
             {
                 field: 'Status',//域值
                 title: 'setting status',//内容

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

@@ -43,16 +43,19 @@
                         </a>
                     </div>
                     <div class="logo-element">
-                        IN+
+                        NPS
                     </div>
                 </li>
+            {{if eq true .isAdmin}}
                 <li class="{{if eq "index" .menu}}active{{end}}">
-                    <a href="/"><i class="fa fa-dashboard"></i> <span langtag="menu-dashboard" class="nav-label">仪表盘</span></a>
+                    <a href="/"><i class="fa fa-dashboard"></i> <span langtag="menu-dashboard"
+                                                                      class="nav-label">仪表盘</span></a>
                 </li>
                 <li class="{{if eq "client" .menu}}active{{end}}">
                     <a href="/client/list"><i class="fa fa-clipboard"></i> <span langtag="menu-client"
                                                                                  class="nav-label">客户端</span></a>
                 </li>
+            {{end}}
                 <li class="{{if eq "host" .menu}}active{{end}}">
                     <a href="/index/hostlist"><i class="fa fa-paperclip"></i> <span langtag="menu-host"
                                                                                     class="nav-label">域名解析</span></a>