浏览代码

Update v0.5.1 for anylink

Stille 4 年之前
父节点
当前提交
9104441269
共有 41 个文件被更改,包括 814 次插入592 次删除
  1. 2 2
      anylink/Dockerfile
  2. 21 0
      anylink/LICENSE
  3. 1 0
      anylink/README.md
  4. 4 1
      anylink/build.sh
  5. 42 0
      anylink/question.md
  6. 1 1
      anylink/server/admin/api_group.go
  7. 2 10
      anylink/server/admin/api_ip_map.go
  8. 2 3
      anylink/server/admin/api_set.go
  9. 5 4
      anylink/server/admin/api_user.go
  10. 1 1
      anylink/server/admin/server.go
  11. 2 2
      anylink/server/base/app_ver.go
  12. 11 10
      anylink/server/base/cfg.go
  13. 47 33
      anylink/server/base/cmd.go
  14. 7 3
      anylink/server/base/config.go
  15. 8 7
      anylink/server/conf/server-sample.toml
  16. 65 23
      anylink/server/dbdata/db.go
  17. 56 38
      anylink/server/dbdata/db_orm.go
  18. 4 3
      anylink/server/dbdata/db_test.go
  19. 20 16
      anylink/server/dbdata/group.go
  20. 27 11
      anylink/server/dbdata/ip_map.go
  21. 40 23
      anylink/server/dbdata/setting.go
  22. 1 1
      anylink/server/dbdata/start.go
  23. 56 0
      anylink/server/dbdata/tables.go
  24. 20 16
      anylink/server/dbdata/user.go
  25. 6 2
      anylink/server/go.mod
  26. 86 16
      anylink/server/go.sum
  27. 1 1
      anylink/server/handler/link_base.go
  28. 34 21
      anylink/server/handler/link_cstp.go
  29. 31 19
      anylink/server/handler/link_dtls.go
  30. 42 28
      anylink/server/handler/link_tap.go
  31. 15 13
      anylink/server/handler/link_tun.go
  32. 16 32
      anylink/server/handler/payload.go
  33. 27 13
      anylink/server/handler/pool.go
  34. 44 0
      anylink/server/handler/pool_test.go
  35. 15 15
      anylink/server/sessdata/ip_pool.go
  36. 3 2
      anylink/server/sessdata/ip_pool_test.go
  37. 1 1
      anylink/server/sessdata/protocol.go
  38. 5 5
      anylink/server/sessdata/session.go
  39. 11 196
      anylink/web/package-lock.json
  40. 1 0
      anylink/web/package.json
  41. 31 20
      anylink/web/src/pages/group/List.vue

+ 2 - 2
anylink/Dockerfile

@@ -1,7 +1,7 @@
 # web
 FROM node:lts-alpine as builder_node
+ENV VERSION 0.5.1
 WORKDIR /web
-ENV VERSION 0.4.2
 COPY ./web /web
 RUN npx browserslist@latest --update-db \
     && npm install \
@@ -19,7 +19,7 @@ COPY --from=builder_node /web/ui  /anylink/server/ui
 
 #TODO 本地打包时使用镜像
 #RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
-RUN apk add --no-cache git
+RUN apk add --no-cache git gcc musl-dev
 RUN cd /anylink/server;go build -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)" \
     && /anylink/server/anylink tool -v
 

+ 21 - 0
anylink/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 bjdgyc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 1 - 0
anylink/README.md

@@ -8,6 +8,7 @@ Docker [stilleshan/anylink](https://hub.docker.com/r/stilleshan/anylink)
 基于 [bjdgyc/anylink](https://github.com/bjdgyc/anylink) 项目的 docker 镜像.
 
 ## 更新
+- **2021-08-02** 更新`0.5.1`版 docker 镜像.
 - **2021-07-05** 更新`0.4.2`版 docker 镜像.
 - **2021-06-09** 更新`0.3.3`版 docker 镜像,新增同时支持 X86 和 ARM 架构.
 

+ 4 - 1
anylink/build.sh

@@ -15,7 +15,7 @@ cpath=$(pwd)
 echo "编译前端项目"
 cd $cpath/web
 #国内可替换源加快速度
-npx browserslist@latest --update-db
+#npx browserslist@latest --update-db
 npm install --registry=https://registry.npm.taobao.org
 #npm install
 npm run build
@@ -25,6 +25,8 @@ echo "编译二进制文件"
 cd $cpath/server
 rm -rf ui
 cp -rf $cpath/web/ui .
+#国内可替换源加快速度
+export GOPROXY=https://goproxy.io
 go build -v -o anylink -ldflags "-X main.CommitId=$(git rev-parse HEAD)"
 RETVAL $?
 
@@ -37,6 +39,7 @@ mkdir $deploy
 
 cp -r server/anylink $deploy
 cp -r server/bridge-init.sh $deploy
+cp -r server/conf $deploy
 
 cp -r systemd $deploy
 

+ 42 - 0
anylink/question.md

@@ -0,0 +1,42 @@
+# 常见问题
+
+### anyconnect 客户端问题
+> 客户端请使用群共享文件的版本,其他版本没有测试过,不保证使用正常
+> 
+> 添加QQ群: 567510628
+
+### OTP 动态码
+> 请使用手机安装 freeotp ,然后扫描otp二维码,生成的数字即是动态码
+
+### 远程桌面连接
+> 本软件已经支持远程桌面里面连接anyconnect。
+
+### 私有证书问题
+> anylink 默认不支持私有证书
+> 
+> 其他使用私有证书的问题,请自行解决
+
+### dpd timeout 设置问题
+```
+#客户端失效检测时间(秒) dpd > keepalive
+cstp_keepalive = 20
+cstp_dpd = 30
+mobile_keepalive = 40
+mobile_dpd = 50
+```
+> 以上dpd参数为客户端的超时检测时间, 如一段时间内,没有数据传输,防火墙会主动关闭连接
+> 
+> 如经常出现 timeout 的错误信息,应根据当前防火墙的设置,适当减小dpd数值
+
+### 性能问题
+```
+内网环境测试数据
+虚拟服务器:  centos7 4C8G
+anylink:    tun模式 tcp传输
+客户端文件下载速度:240Mb/s
+客户端网卡下载速度:270Mb/s
+服务端网卡上传速度:280Mb/s
+```
+> 客户端tls加密协议、隧道header头都会占用一定带宽
+
+

+ 1 - 1
anylink/server/admin/api_group.go

@@ -22,7 +22,7 @@ func GroupList(w http.ResponseWriter, r *http.Request) {
 	count := dbdata.CountAll(&dbdata.Group{})
 
 	var datas []dbdata.Group
-	err := dbdata.All(&datas, pageSize, page)
+	err := dbdata.Find(&datas, pageSize, page)
 	if err != nil {
 		RespError(w, RespInternalErr, err)
 		return

+ 2 - 10
anylink/server/admin/api_ip_map.go

@@ -5,7 +5,6 @@ import (
 	"io/ioutil"
 	"net/http"
 	"strconv"
-	"time"
 
 	"github.com/bjdgyc/anylink/dbdata"
 )
@@ -23,7 +22,7 @@ func UserIpMapList(w http.ResponseWriter, r *http.Request) {
 	count := dbdata.CountAll(&dbdata.IpMap{})
 
 	var datas []dbdata.IpMap
-	err := dbdata.All(&datas, pageSize, page)
+	err := dbdata.Find(&datas, pageSize, page)
 	if err != nil {
 		RespError(w, RespInternalErr, err)
 		return
@@ -75,14 +74,7 @@ func UserIpMapSet(w http.ResponseWriter, r *http.Request) {
 
 	// fmt.Println(v, len(v.Ip), len(v.MacAddr))
 
-	if len(v.IpAddr) < 4 || len(v.MacAddr) < 6 {
-		RespError(w, RespParamErr, "IP或MAC错误")
-		return
-	}
-
-	v.UpdatedAt = time.Now()
-	err = dbdata.Save(v)
-
+	err = dbdata.SetIpMap(v)
 	if err != nil {
 		RespError(w, RespInternalErr, err)
 		return

+ 2 - 3
anylink/server/admin/api_set.go

@@ -5,11 +5,10 @@ import (
 	"net/http"
 	"runtime"
 
-	"github.com/bjdgyc/anylink/dbdata"
-	"github.com/bjdgyc/anylink/sessdata"
-
 	"github.com/bjdgyc/anylink/base"
+	"github.com/bjdgyc/anylink/dbdata"
 	"github.com/bjdgyc/anylink/pkg/utils"
+	"github.com/bjdgyc/anylink/sessdata"
 	"github.com/shirou/gopsutil/cpu"
 	"github.com/shirou/gopsutil/disk"
 	"github.com/shirou/gopsutil/host"

+ 5 - 4
anylink/server/admin/api_user.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"net/url"
 	"strconv"
 	"strings"
 	"text/template"
@@ -36,11 +37,11 @@ func UserList(w http.ResponseWriter, r *http.Request) {
 
 	// 查询前缀匹配
 	if len(prefix) > 0 {
-		count = pageSize
-		err = dbdata.Prefix("Username", prefix, &datas, pageSize, 1)
+		count = dbdata.CountPrefix("username", prefix, &dbdata.User{})
+		err = dbdata.Prefix("username", prefix, &datas, pageSize, 1)
 	} else {
 		count = dbdata.CountAll(&dbdata.User{})
-		err = dbdata.All(&datas, pageSize, page)
+		err = dbdata.Find(&datas, pageSize, page)
 	}
 
 	if err != nil && !dbdata.CheckErrNotFound(err) {
@@ -141,7 +142,7 @@ func UserOtpQr(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	issuer := base.Cfg.Issuer
+	issuer := url.QueryEscape(base.Cfg.Issuer)
 	qrstr := fmt.Sprintf("otpauth://totp/%s:%s?issuer=%s&secret=%s", issuer, user.Email, issuer, user.OtpSecret)
 	qr, _ := qrcode.New(qrstr, qrcode.High)
 

+ 1 - 1
anylink/server/admin/server.go

@@ -58,7 +58,7 @@ func StartAdmin() {
 		r.HandleFunc("/debug/pprof/profile", pprof.Profile).Name("debug")
 		r.HandleFunc("/debug/pprof/symbol", pprof.Symbol).Name("debug")
 		r.HandleFunc("/debug/pprof/trace", pprof.Trace).Name("debug")
-		r.HandleFunc("/debug/pprof", location("/debug/pprof/"))
+		r.HandleFunc("/debug/pprof", location("/debug/pprof/")).Name("debug")
 		r.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index).Name("debug")
 	}
 

+ 2 - 2
anylink/server/base/app_ver.go

@@ -2,6 +2,6 @@ package base
 
 const (
 	APP_NAME = "AnyLink"
-	// 修复严重bug
-	APP_VER = "0.4.2"
+	// 修改为sql数据库
+	APP_VER = "0.5.1"
 )

+ 11 - 10
anylink/server/base/cfg.go

@@ -5,8 +5,6 @@ import (
 	"os"
 	"path/filepath"
 	"reflect"
-
-	"github.com/spf13/viper"
 )
 
 const (
@@ -31,12 +29,14 @@ var (
 
 type ServerConfig struct {
 	// LinkAddr      string `json:"link_addr"`
+	Conf           string `json:"conf"`
 	ServerAddr     string `json:"server_addr"`
 	ServerDTLSAddr string `json:"server_dtls_addr"`
 	ServerDTLS     bool   `json:"server_dtls"`
 	AdminAddr      string `json:"admin_addr"`
 	ProxyProtocol  bool   `json:"proxy_protocol"`
-	DbFile         string `json:"db_file"`
+	DbType         string `json:"db_type"`
+	DbSource       string `json:"db_source"`
 	CertFile       string `json:"cert_file"`
 	CertKey        string `json:"cert_key"`
 	FilesPath      string `json:"files_path"`
@@ -64,7 +64,7 @@ type ServerConfig struct {
 	MobileDpd       int    `json:"mobile_dpd"`
 
 	SessionTimeout int `json:"session_timeout"` // in seconds
-	AuthTimeout    int `json:"auth_timeout"`    // in seconds
+	// AuthTimeout    int `json:"auth_timeout"`    // in seconds
 }
 
 func initServerCfg() {
@@ -81,6 +81,10 @@ func initServerCfg() {
 	// Cfg.FilesPath = getAbsPath(base, Cfg.FilesPath)
 	// Cfg.LogPath = getAbsPath(base, Cfg.LogPath)
 
+	if Cfg.AdminPass == defaultPwd {
+		fmt.Fprintln(os.Stderr, "=== 使用默认的admin_pass有安全风险,请设置新的admin_pass ===")
+	}
+
 	if Cfg.JwtSecret == defaultJwt {
 		fmt.Fprintln(os.Stderr, "=== 使用默认的jwt_secret有安全风险,请设置新的jwt_secret ===")
 	}
@@ -114,13 +118,13 @@ func initCfg() {
 		for _, v := range configs {
 			if v.Name == tag {
 				if v.Typ == cfgStr {
-					value.SetString(viper.GetString(v.Name))
+					value.SetString(linkViper.GetString(v.Name))
 				}
 				if v.Typ == cfgInt {
-					value.SetInt(int64(viper.GetInt(v.Name)))
+					value.SetInt(int64(linkViper.GetInt(v.Name)))
 				}
 				if v.Typ == cfgBool {
-					value.SetBool(viper.GetBool(v.Name))
+					value.SetBool(linkViper.GetBool(v.Name))
 				}
 			}
 		}
@@ -149,9 +153,6 @@ func ServerCfg2Slice() []SCfg {
 		value := s.Field(i)
 		tag := field.Tag.Get("json")
 		usage, env := getUsageEnv(tag)
-		if usage == "" {
-			continue
-		}
 
 		datas = append(datas, SCfg{Name: tag, Env: env, Info: usage, Data: value.Interface()})
 	}

+ 47 - 33
anylink/server/base/cmd.go

@@ -1,8 +1,10 @@
 package base
 
 import (
+	"errors"
 	"fmt"
 	"os"
+	"reflect"
 	"runtime"
 	"strings"
 
@@ -14,25 +16,26 @@ import (
 var (
 	// 提交id
 	CommitId string
-	// 配置文件
-	cfgFile string
 	// pass明文
 	passwd string
 	// 生成密钥
 	secret bool
 	// 显示版本信息
 	rev bool
-	// 获取env名称
-	env bool
+	// 输出debug信息
+	debug bool
 
 	// Used for flags.
 	runSrv bool
 
-	rootCmd *cobra.Command
+	linkViper *viper.Viper
+	rootCmd   *cobra.Command
 )
 
 // Execute executes the root command.
 func execute() {
+	initCmd()
+
 	err := rootCmd.Execute()
 	if err != nil {
 		fmt.Println(err)
@@ -40,13 +43,25 @@ func execute() {
 	}
 
 	// viper.Debug()
+	ref := reflect.ValueOf(linkViper)
+	s := ref.Elem()
+	ee := s.FieldByName("env")
+	if ee.Kind() != reflect.Map {
+		panic("Viper env is err")
+	}
+	rr := ee.MapRange()
+	for rr.Next() {
+		// fmt.Println(rr.Key(), rr.Value())
+		envs[rr.Key().String()] = rr.Value().String()
+	}
 
 	if !runSrv {
 		os.Exit(0)
 	}
 }
 
-func init() {
+func initCmd() {
+	linkViper = viper.New()
 	rootCmd = &cobra.Command{
 		Use:   "anylink",
 		Short: "AnyLink VPN Server",
@@ -57,43 +72,44 @@ func init() {
 		},
 	}
 
-	cobra.OnInitialize(func() {
-		viper.SetConfigFile(cfgFile)
-		viper.AutomaticEnv()
-
-		if cfgFile == "" {
-			// 没有配置文件,不做处理
-			return
-		}
-
-		err := viper.ReadInConfig()
-		if err != nil {
-			fmt.Println("Using config file:", err)
-		}
-	})
-
-	viper.SetEnvPrefix("link")
+	linkViper.SetEnvPrefix("link")
 
 	// 基础配置
-	rootCmd.Flags().StringVarP(&cfgFile, "conf", "c", "", "config file")
 
 	for _, v := range configs {
 		if v.Typ == cfgStr {
-			rootCmd.Flags().String(v.Name, v.ValStr, v.Usage)
+			rootCmd.Flags().StringP(v.Name, v.Short, v.ValStr, v.Usage)
 		}
 		if v.Typ == cfgInt {
-			rootCmd.Flags().Int(v.Name, v.ValInt, v.Usage)
+			rootCmd.Flags().IntP(v.Name, v.Short, v.ValInt, v.Usage)
 		}
 		if v.Typ == cfgBool {
-			rootCmd.Flags().Bool(v.Name, v.ValBool, v.Usage)
+			rootCmd.Flags().BoolP(v.Name, v.Short, v.ValBool, v.Usage)
 		}
 
-		_ = viper.BindPFlag(v.Name, rootCmd.Flags().Lookup(v.Name))
-		_ = viper.BindEnv(v.Name)
+		_ = linkViper.BindPFlag(v.Name, rootCmd.Flags().Lookup(v.Name))
+		_ = linkViper.BindEnv(v.Name)
 		// viper.SetDefault(v.Name, v.Value)
 	}
 
 	rootCmd.AddCommand(initToolCmd())
+
+	cobra.OnInitialize(func() {
+		linkViper.AutomaticEnv()
+		conf := linkViper.GetString("conf")
+
+		_, err := os.Stat(conf)
+		if errors.Is(err, os.ErrNotExist) {
+			// 没有配置文件,不做处理
+			return
+		}
+
+		linkViper.SetConfigFile(conf)
+		err = linkViper.ReadInConfig()
+		if err != nil {
+			fmt.Println("Using config file:", err)
+		}
+	})
 }
 
 func initToolCmd() *cobra.Command {
@@ -106,7 +122,7 @@ func initToolCmd() *cobra.Command {
 	toolCmd.Flags().BoolVarP(&rev, "version", "v", false, "display version info")
 	toolCmd.Flags().BoolVarP(&secret, "secret", "s", false, "generate a random jwt secret")
 	toolCmd.Flags().StringVarP(&passwd, "passwd", "p", "", "convert the password plaintext")
-	toolCmd.Flags().BoolVarP(&env, "env", "e", false, "list the config name and env key")
+	toolCmd.Flags().BoolVarP(&debug, "debug", "d", false, "list the config viper.Debug() info")
 
 	toolCmd.Run = func(cmd *cobra.Command, args []string) {
 		switch {
@@ -120,10 +136,8 @@ func initToolCmd() *cobra.Command {
 		case passwd != "":
 			pass, _ := utils.PasswordHash(passwd)
 			fmt.Printf("Passwd:%s\n", pass)
-		case env:
-			for k, v := range envs {
-				fmt.Printf("%s => %s\n", k, v)
-			}
+		case debug:
+			linkViper.Debug()
 		default:
 			fmt.Println("Using [anylink tool -h] for help")
 		}

+ 7 - 3
anylink/server/base/config.go

@@ -6,11 +6,13 @@ const (
 	cfgBool
 
 	defaultJwt = "abcdef.0123456789.abcdef"
+	defaultPwd = "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke"
 )
 
 type config struct {
 	Typ     int
 	Name    string
+	Short   string
 	Usage   string
 	ValStr  string
 	ValInt  int
@@ -18,21 +20,23 @@ type config struct {
 }
 
 var configs = []config{
+	{Typ: cfgStr, Name: "conf", Usage: "config file", ValStr: "./conf/server.toml", Short: "c"},
 	{Typ: cfgStr, Name: "server_addr", Usage: "服务监听地址", ValStr: ":443"},
 	{Typ: cfgBool, Name: "server_dtls", Usage: "开启DTLS", ValBool: false},
 	{Typ: cfgStr, Name: "server_dtls_addr", Usage: "DTLS监听地址", ValStr: ":4433"},
 	{Typ: cfgStr, Name: "admin_addr", Usage: "后台服务监听地址", ValStr: ":8800"},
 	{Typ: cfgBool, Name: "proxy_protocol", Usage: "TCP代理协议", ValBool: false},
-	{Typ: cfgStr, Name: "db_file", Usage: "数据库地址", ValStr: "./conf/data.db"},
+	{Typ: cfgStr, Name: "db_type", Usage: "数据库类型 [sqlite3、mysql、postgres]", ValStr: "sqlite3"},
+	{Typ: cfgStr, Name: "db_source", Usage: "数据库source", ValStr: "./conf/anylink.db"},
 	{Typ: cfgStr, Name: "cert_file", Usage: "证书文件", ValStr: "./conf/vpn_cert.pem"},
 	{Typ: cfgStr, Name: "cert_key", Usage: "证书密钥", ValStr: "./conf/vpn_cert.key"},
 	{Typ: cfgStr, Name: "files_path", Usage: "外部下载文件路径", ValStr: "./conf/files"},
 	{Typ: cfgStr, Name: "log_path", Usage: "日志文件路径,默认标准输出", ValStr: ""},
-	{Typ: cfgStr, Name: "log_level", Usage: "日志等级 debug、info、warn、error", ValStr: "info"},
+	{Typ: cfgStr, Name: "log_level", Usage: "日志等级 [debug、info、warn、error]", ValStr: "info"},
 	{Typ: cfgBool, Name: "pprof", Usage: "开启pprof", ValBool: false},
 	{Typ: cfgStr, Name: "issuer", Usage: "系统名称", ValStr: "XX公司VPN"},
 	{Typ: cfgStr, Name: "admin_user", Usage: "管理用户名", ValStr: "admin"},
-	{Typ: cfgStr, Name: "admin_pass", Usage: "管理用户密码", ValStr: "$2a$10$UQ7C.EoPifDeJh6d8.31TeSPQU7hM/NOM2nixmBucJpAuXDQNqNke"},
+	{Typ: cfgStr, Name: "admin_pass", Usage: "管理用户密码", ValStr: defaultPwd},
 	{Typ: cfgStr, Name: "jwt_secret", Usage: "JWT密钥", ValStr: defaultJwt},
 	{Typ: cfgStr, Name: "link_mode", Usage: "虚拟网络类型", ValStr: "tun"},
 	{Typ: cfgStr, Name: "ipv4_cidr", Usage: "ip地址网段", ValStr: "192.168.10.0/24"},

+ 8 - 7
anylink/server/conf/server-sample.toml

@@ -1,16 +1,17 @@
-#服务配置信息
+#示例配置信息
 
 #其他配置文件,可以使用绝对路径
-#或者相对于server.toml的路径
+#或者相对于 anylink 二进制文件的路径
 
 #数据文件
-db_file = "./data.db"
+db_type = "sqlite3"
+db_source = "./conf/anylink.db"
 #证书文件
-cert_file = "./vpn_cert.pem"
-cert_key = "./vpn_cert.key"
-files_path = "./files"
+cert_file = "./conf/vpn_cert.pem"
+cert_key = "./conf/vpn_cert.key"
+files_path = "./conf/files"
 #日志目录,为空写入标准输出
-#log_path = "../log"
+#log_path = "./log"
 log_path = ""
 log_level = "debug"
 pprof = false

+ 65 - 23
anylink/server/dbdata/db.go

@@ -1,70 +1,112 @@
 package dbdata
 
 import (
-	"time"
-
-	"github.com/asdine/storm/v3"
-	"github.com/asdine/storm/v3/codec/json"
 	"github.com/bjdgyc/anylink/base"
-	bolt "go.etcd.io/bbolt"
+	_ "github.com/go-sql-driver/mysql"
+	_ "github.com/lib/pq"
+	_ "github.com/mattn/go-sqlite3"
+	"xorm.io/xorm"
 )
 
 var (
-	sdb *storm.DB
+	xdb *xorm.Engine
 )
 
+func GetXdb() *xorm.Engine {
+	return xdb
+}
+
 func initDb() {
 	var err error
-	sdb, err = storm.Open(base.Cfg.DbFile, storm.Codec(json.Codec),
-		storm.BoltOptions(0600, &bolt.Options{Timeout: 10 * time.Second}))
+	xdb, err = xorm.NewEngine(base.Cfg.DbType, base.Cfg.DbSource)
+	// xdb.ShowSQL(true)
 	if err != nil {
 		base.Fatal(err)
 	}
 
 	// 初始化数据库
-	err = sdb.Init(&User{})
+	err = xdb.Sync2(&User{}, &Setting{}, &Group{}, &IpMap{})
 	if err != nil {
 		base.Fatal(err)
 	}
 
-	// fmt.Println("s1")
+	// fmt.Println("s1=============", err)
 }
 
 func initData() {
 	var (
-		err     error
-		install bool
+		err error
 	)
 
 	// 判断是否初次使用
-	err = Get(SettingBucket, Installed, &install)
-	if err == nil && install {
+	install := &SettingInstall{}
+	err = SettingGet(install)
+
+	if err == nil && install.Installed {
 		// 已经安装过
 		return
 	}
 
-	defer func() {
-		_ = Set(SettingBucket, Installed, true)
-	}()
+	// 发生错误
+	if err != ErrNotFound {
+		base.Fatal(err)
+	}
+
+	err = addInitData()
+	if err != nil {
+		base.Fatal(err)
+	}
+
+}
+
+func addInitData() error {
+	var (
+		err error
+	)
 
+	sess := xdb.NewSession()
+	defer sess.Close()
+
+	err = sess.Begin()
+	if err != nil {
+		return err
+	}
+
+	// SettingSmtp
 	smtp := &SettingSmtp{
-		Host: "127.0.0.1",
-		Port: 25,
-		From: "[email protected]",
+		Host:       "127.0.0.1",
+		Port:       25,
+		From:       "[email protected]",
+		Encryption: "None",
+	}
+	err = SettingSessAdd(sess, smtp)
+	if err != nil {
+		return err
 	}
-	_ = SettingSet(smtp)
 
+	// SettingOther
 	other := &SettingOther{
 		LinkAddr:    "vpn.xx.com",
 		Banner:      "您已接入公司网络,请按照公司规定使用。\n请勿进行非工作下载及视频行为!",
 		AccountMail: accountMail,
 	}
-	_ = SettingSet(other)
+	err = SettingSessAdd(sess, other)
+	if err != nil {
+		return err
+	}
+
+	// Install
+	install := &SettingInstall{Installed: true}
+	err = SettingSessAdd(sess, install)
+	if err != nil {
+		return err
+	}
 
+	return sess.Commit()
 }
 
 func CheckErrNotFound(err error) bool {
-	return err == storm.ErrNotFound
+	return err == ErrNotFound
 }
 
 const accountMail = `<p>您好:</p>

+ 56 - 38
anylink/server/dbdata/db_orm.go

@@ -1,66 +1,84 @@
 package dbdata
 
-import "github.com/asdine/storm/v3/index"
+import (
+	"errors"
+	"reflect"
+)
 
 const PageSize = 10
 
-func Save(data interface{}) error {
-	return sdb.Save(data)
-}
+var ErrNotFound = errors.New("ErrNotFound")
 
-func Update(data interface{}) error {
-	return sdb.Update(data)
+func Add(data interface{}) error {
+	_, err := xdb.InsertOne(data)
+	return err
 }
 
-func UpdateField(data interface{}, fieldName string, value interface{}) error {
-	return sdb.UpdateField(data, fieldName, value)
+func Update(fieldName string, value interface{}, data interface{}) error {
+	_, err := xdb.Where(fieldName+"=?", value).Update(data)
+	return err
 }
 
 func Del(data interface{}) error {
-	return sdb.DeleteStruct(data)
+	_, err := xdb.Delete(data)
+	return err
 }
 
-func Set(bucket, key string, data interface{}) error {
-	return sdb.Set(bucket, key, data)
+func extract(data interface{}, fieldName string) interface{} {
+	ref := reflect.ValueOf(data)
+	r := &ref
+	if r.Kind() == reflect.Ptr {
+		e := r.Elem()
+		r = &e
+	}
+	field := r.FieldByName(fieldName).Interface()
+	return field
 }
 
-func Get(bucket, key string, data interface{}) error {
-	return sdb.Get(bucket, key, data)
+// 更新全部字段
+func Set(data interface{}) error {
+	id := extract(data, "Id")
+	_, err := xdb.ID(id).AllCols().Update(data)
+	return err
 }
 
-func CountAll(data interface{}) int {
-	n, _ := sdb.Count(data)
-	return n
-}
+func One(fieldName string, value interface{}, data interface{}) error {
+	has, err := xdb.Where(fieldName+"=?", value).Get(data)
+	if err != nil {
+		return err
+	}
+	if !has {
+		return ErrNotFound
+	}
 
-func One(fieldName string, value interface{}, to interface{}) error {
-	return sdb.One(fieldName, value, to)
+	return nil
 }
 
-func Find(fieldName string, value interface{}, to interface{}, options ...func(q *index.Options)) error {
-	return sdb.Find(fieldName, value, to, options...)
+func CountAll(data interface{}) int {
+	n, _ := xdb.Count(data)
+	return int(n)
 }
 
-func All(to interface{}, limit, page int) error {
-	opt := getOpt(limit, page)
-	return sdb.All(to, opt)
+func Find(data interface{}, limit, page int) error {
+	if limit == 0 {
+		return xdb.Find(data)
+	}
+
+	start := (page - 1) * limit
+	return xdb.Limit(limit, start).Find(data)
 }
 
-func Prefix(fieldName string, prefix string, to interface{}, limit, page int) error {
-	opt := getOpt(limit, page)
-	return sdb.Prefix(fieldName, prefix, to, opt)
+func CountPrefix(fieldName string, prefix string, data interface{}) int {
+	n, _ := xdb.Where(fieldName+" like ?", prefix+"%").Count(data)
+	return int(n)
 }
 
-func getOpt(limit, page int) func(*index.Options) {
-	skip := (page - 1) * limit
-	opt := func(opt *index.Options) {
-		opt.Reverse = true
-		if limit > 0 {
-			opt.Limit = limit
-		}
-		if skip > 0 {
-			opt.Skip = skip
-		}
+func Prefix(fieldName string, prefix string, data interface{}, limit, page int) error {
+	where := xdb.Where(fieldName+" like ?", prefix+"%")
+	if limit == 0 {
+		return where.Find(data)
 	}
-	return opt
+
+	start := (page - 1) * limit
+	return where.Limit(limit, start).Find(data)
 }

+ 4 - 3
anylink/server/dbdata/db_test.go

@@ -11,12 +11,13 @@ import (
 
 func preIpData() {
 	tmpDb := path.Join(os.TempDir(), "anylink_test.db")
-	base.Cfg.DbFile = tmpDb
+	base.Cfg.DbType = "sqlite3"
+	base.Cfg.DbSource = tmpDb
 	initDb()
 }
 
 func closeIpdata() {
-	sdb.Close()
+	xdb.Close()
 	tmpDb := path.Join(os.TempDir(), "anylink_test.db")
 	os.Remove(tmpDb)
 }
@@ -27,7 +28,7 @@ func TestDb(t *testing.T) {
 	defer closeIpdata()
 
 	u := User{Username: "a"}
-	err := Save(&u)
+	err := Add(&u)
 	ast.Nil(err)
 
 	ast.Equal(u.Id, 1)

+ 20 - 16
anylink/server/dbdata/group.go

@@ -29,24 +29,24 @@ type ValData struct {
 	Note   string `json:"note"`
 }
 
-type Group struct {
-	Id           int            `json:"id" storm:"id,increment"`
-	Name         string         `json:"name" storm:"unique"`
-	Note         string         `json:"note"`
-	AllowLan     bool           `json:"allow_lan"`
-	ClientDns    []ValData      `json:"client_dns"`
-	RouteInclude []ValData      `json:"route_include"`
-	RouteExclude []ValData      `json:"route_exclude"`
-	LinkAcl      []GroupLinkAcl `json:"link_acl"`
-	Bandwidth    int            `json:"bandwidth"` // 带宽限制
-	Status       int8           `json:"status"`    // 1正常
-	CreatedAt    time.Time      `json:"created_at"`
-	UpdatedAt    time.Time      `json:"updated_at"`
-}
+// type Group struct {
+// 	Id           int            `json:"id" xorm:"pk autoincr not null"`
+// 	Name         string         `json:"name" xorm:"not null unique"`
+// 	Note         string         `json:"note"`
+// 	AllowLan     bool           `json:"allow_lan"`
+// 	ClientDns    []ValData      `json:"client_dns"`
+// 	RouteInclude []ValData      `json:"route_include"`
+// 	RouteExclude []ValData      `json:"route_exclude"`
+// 	LinkAcl      []GroupLinkAcl `json:"link_acl"`
+// 	Bandwidth    int            `json:"bandwidth"` // 带宽限制
+// 	Status       int8           `json:"status"`    // 1正常
+// 	CreatedAt    time.Time      `json:"created_at"`
+// 	UpdatedAt    time.Time      `json:"updated_at"`
+// }
 
 func GetGroupNames() []string {
 	var datas []Group
-	err := All(&datas, 0, 0)
+	err := Find(&datas, 0, 0)
 	if err != nil {
 		base.Error(err)
 		return nil
@@ -116,7 +116,11 @@ func SetGroup(g *Group) error {
 	g.LinkAcl = linkAcl
 
 	g.UpdatedAt = time.Now()
-	err = Save(g)
+	if g.Id > 0 {
+		err = Set(g)
+	} else {
+		err = Add(g)
+	}
 
 	return err
 }

+ 27 - 11
anylink/server/dbdata/ip_map.go

@@ -1,18 +1,34 @@
 package dbdata
 
 import (
-	"net"
+	"errors"
 	"time"
 )
 
-type IpMap struct {
-	Id        int       `json:"id" storm:"id,increment"`
-	IpAddr    net.IP    `json:"ip_addr" storm:"unique"`
-	MacAddr   string    `json:"mac_addr" storm:"unique"`
-	Username  string    `json:"username"`
-	Keep      bool      `json:"keep"` // 保留 ip-mac 绑定
-	KeepTime  time.Time `json:"keep_time"`
-	Note      string    `json:"note"` // 备注
-	LastLogin time.Time `json:"last_login"`
-	UpdatedAt time.Time `json:"updated_at"`
+// type IpMap struct {
+// 	Id        int       `json:"id" xorm:"pk autoincr not null"`
+// 	IpAddr    string    `json:"ip_addr" xorm:"not null unique"`
+// 	MacAddr   string    `json:"mac_addr" xorm:"not null unique"`
+// 	Username  string    `json:"username"`
+// 	Keep      bool      `json:"keep"` // 保留 ip-mac 绑定
+// 	KeepTime  time.Time `json:"keep_time"`
+// 	Note      string    `json:"note"` // 备注
+// 	LastLogin time.Time `json:"last_login"`
+// 	UpdatedAt time.Time `json:"updated_at"`
+// }
+
+func SetIpMap(v *IpMap) error {
+	var err error
+
+	if len(v.IpAddr) < 4 || len(v.MacAddr) < 6 {
+		return errors.New("IP或MAC错误")
+	}
+
+	v.UpdatedAt = time.Now()
+	if v.Id > 0 {
+		err = Set(v)
+	} else {
+		err = Add(v)
+	}
+	return err
 }

+ 40 - 23
anylink/server/dbdata/setting.go

@@ -1,13 +1,29 @@
 package dbdata
 
 import (
+	"encoding/json"
 	"reflect"
+	"xorm.io/xorm"
 )
 
-const (
-	SettingBucket = "SettingBucket"
-	Installed     = "Installed"
-)
+type SettingInstall struct {
+	Installed bool `json:"installed"`
+}
+
+type SettingSmtp struct {
+	Host       string `json:"host"`
+	Port       int    `json:"port"`
+	Username   string `json:"username"`
+	Password   string `json:"password"`
+	From       string `json:"from"`
+	Encryption string `json:"encryption"`
+}
+
+type SettingOther struct {
+	LinkAddr    string `json:"link_addr"`
+	Banner      string `json:"banner"`
+	AccountMail string `json:"account_mail"`
+}
 
 func StructName(data interface{}) string {
 	ref := reflect.ValueOf(data)
@@ -20,29 +36,30 @@ func StructName(data interface{}) string {
 	return name
 }
 
-func SettingSet(data interface{}) error {
-	key := StructName(data)
-	err := Set(SettingBucket, key, data)
-	return err
-}
+func SettingSessAdd(sess *xorm.Session, data interface{}) error {
+	name := StructName(data)
+	v, _ := json.Marshal(data)
+	s := &Setting{Name: name, Data: v}
+	_, err := sess.InsertOne(s)
 
-func SettingGet(data interface{}) error {
-	key := StructName(data)
-	err := Get(SettingBucket, key, data)
 	return err
 }
 
-type SettingSmtp struct {
-	Host       string `json:"host"`
-	Port       int    `json:"port"`
-	Username   string `json:"username"`
-	Password   string `json:"password"`
-	From       string `json:"from"`
-	Encryption string `json:"encryption"`
+func SettingSet(data interface{}) error {
+	name := StructName(data)
+	v, _ := json.Marshal(data)
+	s := &Setting{Data: v}
+	err := Update("name", name, s)
+	return err
 }
 
-type SettingOther struct {
-	LinkAddr    string `json:"link_addr"`
-	Banner      string `json:"banner"`
-	AccountMail string `json:"account_mail"`
+func SettingGet(data interface{}) error {
+	name := StructName(data)
+	s := &Setting{Name: name}
+	err := One("name", name, s)
+	if err != nil {
+		return err
+	}
+	err = json.Unmarshal(s.Data, data)
+	return err
 }

+ 1 - 1
anylink/server/dbdata/start.go

@@ -6,5 +6,5 @@ func Start() {
 }
 
 func Stop() error {
-	return sdb.Close()
+	return xdb.Close()
 }

+ 56 - 0
anylink/server/dbdata/tables.go

@@ -0,0 +1,56 @@
+package dbdata
+
+import (
+	"encoding/json"
+	"time"
+)
+
+type Group struct {
+	Id           int            `json:"id" xorm:"pk autoincr not null"`
+	Name         string         `json:"name" xorm:"varchar(60) not null unique"`
+	Note         string         `json:"note" xorm:"varchar(255)"`
+	AllowLan     bool           `json:"allow_lan" xorm:"Bool"`
+	ClientDns    []ValData      `json:"client_dns" xorm:"Text"`
+	RouteInclude []ValData      `json:"route_include" xorm:"Text"`
+	RouteExclude []ValData      `json:"route_exclude" xorm:"Text"`
+	LinkAcl      []GroupLinkAcl `json:"link_acl" xorm:"Text"`
+	Bandwidth    int            `json:"bandwidth" xorm:"Int"` // 带宽限制
+	Status       int8           `json:"status" xorm:"Int"`    // 1正常
+	CreatedAt    time.Time      `json:"created_at" xorm:"DateTime created"`
+	UpdatedAt    time.Time      `json:"updated_at" xorm:"DateTime updated"`
+}
+
+type User struct {
+	Id       int    `json:"id" xorm:"pk autoincr not null"`
+	Username string `json:"username" xorm:"varchar(60) not null unique"`
+	Nickname string `json:"nickname" xorm:"varchar(255)"`
+	Email    string `json:"email" xorm:"varchar(255)"`
+	// Password  string    `json:"password"`
+	PinCode    string    `json:"pin_code" xorm:"varchar(32)"`
+	OtpSecret  string    `json:"otp_secret" xorm:"varchar(255)"`
+	DisableOtp bool      `json:"disable_otp" xorm:"Bool"` // 禁用otp
+	Groups     []string  `json:"groups" xorm:"Text"`
+	Status     int8      `json:"status" xorm:"Int"` // 1正常
+	SendEmail  bool      `json:"send_email" xorm:"Bool"`
+	CreatedAt  time.Time `json:"created_at" xorm:"DateTime created"`
+	UpdatedAt  time.Time `json:"updated_at" xorm:"DateTime updated"`
+}
+
+type IpMap struct {
+	Id        int       `json:"id" xorm:"pk autoincr not null"`
+	IpAddr    string    `json:"ip_addr" xorm:"varchar(32) not null unique"`
+	MacAddr   string    `json:"mac_addr" xorm:"varchar(32) not null unique"`
+	Username  string    `json:"username" xorm:"varchar(60)"`
+	Keep      bool      `json:"keep" xorm:"Bool"` // 保留 ip-mac 绑定
+	KeepTime  time.Time `json:"keep_time" xorm:"DateTime"`
+	Note      string    `json:"note" xorm:"varchar(255)"` // 备注
+	LastLogin time.Time `json:"last_login" xorm:"DateTime updated"`
+	UpdatedAt time.Time `json:"updated_at" xorm:"DateTime updated"`
+}
+
+type Setting struct {
+	Id        int             `json:"id" xorm:"pk autoincr not null"`
+	Name      string          `json:"name" xorm:"varchar(60) not null unique"`
+	Data      json.RawMessage `json:"data" xorm:"Text"`
+	UpdatedAt time.Time       `json:"updated_at" xorm:"DateTime updated"`
+}

+ 20 - 16
anylink/server/dbdata/user.go

@@ -10,21 +10,21 @@ import (
 	"github.com/xlzd/gotp"
 )
 
-type User struct {
-	Id       int    `json:"id" storm:"id,increment"`
-	Username string `json:"username" storm:"unique"`
-	Nickname string `json:"nickname"`
-	Email    string `json:"email"`
-	// Password  string    `json:"password"`
-	PinCode    string    `json:"pin_code"`
-	OtpSecret  string    `json:"otp_secret"`
-	DisableOtp bool      `json:"disable_otp"` // 禁用otp
-	Groups     []string  `json:"groups"`
-	Status     int8      `json:"status"` // 1正常
-	SendEmail  bool      `json:"send_email"`
-	CreatedAt  time.Time `json:"created_at"`
-	UpdatedAt  time.Time `json:"updated_at"`
-}
+// type User struct {
+// 	Id       int    `json:"id"  xorm:"pk autoincr not null"`
+// 	Username string `json:"username" storm:"not null unique"`
+// 	Nickname string `json:"nickname"`
+// 	Email    string `json:"email"`
+// 	// Password  string    `json:"password"`
+// 	PinCode    string    `json:"pin_code"`
+// 	OtpSecret  string    `json:"otp_secret"`
+// 	DisableOtp bool      `json:"disable_otp"` // 禁用otp
+// 	Groups     []string  `json:"groups"`
+// 	Status     int8      `json:"status"` // 1正常
+// 	SendEmail  bool      `json:"send_email"`
+// 	CreatedAt  time.Time `json:"created_at"`
+// 	UpdatedAt  time.Time `json:"updated_at"`
+// }
 
 func SetUser(v *User) error {
 	var err error
@@ -57,7 +57,11 @@ func SetUser(v *User) error {
 	v.Groups = ng
 
 	v.UpdatedAt = time.Now()
-	err = Save(v)
+	if v.Id > 0 {
+		err = Set(v)
+	} else {
+		err = Add(v)
+	}
 
 	return err
 }

+ 6 - 2
anylink/server/go.mod

@@ -4,11 +4,14 @@ go 1.16
 
 require (
 	github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
-	github.com/asdine/storm/v3 v3.2.1
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/go-ole/go-ole v1.2.5 // indirect
+	github.com/go-sql-driver/mysql v1.6.0
+	github.com/golang/snappy v0.0.1 // indirect
 	github.com/google/gopacket v1.1.19
 	github.com/gorilla/mux v1.8.0
+	github.com/lib/pq v1.7.0
+	github.com/mattn/go-sqlite3 v1.14.6
 	github.com/pion/dtls/v2 v2.0.0-00010101000000-000000000000
 	github.com/pion/logging v0.2.2
 	github.com/shirou/gopsutil v3.21.4+incompatible
@@ -21,10 +24,11 @@ require (
 	github.com/tklauser/go-sysconf v0.3.6 // indirect
 	github.com/xhit/go-simple-mail/v2 v2.9.0
 	github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
-	go.etcd.io/bbolt v1.3.5
 	golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
 	golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
 	golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
+	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
+	xorm.io/xorm v1.1.2
 )
 
 replace github.com/pion/dtls/v2 => ../dtls-2.0.9

+ 86 - 16
anylink/server/go.sum

@@ -11,14 +11,12 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
+gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
-github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
-github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
 github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
 github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -26,8 +24,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
-github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
@@ -43,9 +39,11 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -56,9 +54,13 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
 github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -66,14 +68,16 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
 github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@@ -111,14 +115,20 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -128,10 +138,16 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
+github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@@ -143,10 +159,19 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@@ -171,6 +196,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -210,27 +237,27 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4=
 github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
 github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
 github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
-github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
 github.com/xhit/go-simple-mail/v2 v2.9.0 h1:vN4fb1Aw5BDtMeJuV/aTP82ufjdT8q0GmqiBjMKPN6I=
 github.com/xhit/go-simple-mail/v2 v2.9.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
 github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
-go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
-go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -239,6 +266,7 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -266,8 +294,11 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -280,7 +311,7 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -295,9 +326,11 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -309,9 +342,11 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
@@ -344,9 +379,13 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
+golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -358,8 +397,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -378,11 +415,16 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -392,4 +434,32 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0=
+modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878=
+modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA=
+modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY=
+modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
+modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
+modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8=
+modernc.org/libc v1.8.0/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
+modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY=
+modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM=
+modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
+modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
+modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 h1:rgEUzE849tFlHSoeCrKyS9cZAljC+DY7MdMHKq6R6sY=
+modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU=
+modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc=
+modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
+modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc=
+modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
+modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
+modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+xorm.io/builder v0.3.8 h1:P/wPgRqa9kX5uE0aA1/ukJ23u9KH0aSRpHLwDKXigSE=
+xorm.io/builder v0.3.8/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+xorm.io/xorm v1.1.2 h1:bje+1KZvK3m5AHtZNfUDlKEEyuw/IRHT+an0CLIG5TU=
+xorm.io/xorm v1.1.2/go.mod h1:Cb0DKYTHbyECMaSfgRnIZp5aiUgQozxcJJ0vzcLGJSg=

+ 1 - 1
anylink/server/handler/link_base.go

@@ -58,7 +58,7 @@ func execCmd(cmdStrs []string) error {
 		cmd := exec.Command("sh", "-c", cmdStr)
 		b, err := cmd.CombinedOutput()
 		if err != nil {
-			log.Println(string(b), err)
+			log.Println(string(b))
 			return err
 		}
 	}

+ 34 - 21
anylink/server/handler/link_cstp.go

@@ -34,8 +34,8 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
 			return
 		}
 		// hdata := make([]byte, BufferSize)
-		hdata := getByteFull()
-		n, err = conn.Read(hdata)
+		pl := getPayload()
+		n, err = conn.Read(pl.Data)
 		if err != nil {
 			base.Error("read hdata: ", err)
 			return
@@ -47,7 +47,7 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
 			base.Error(err)
 		}
 
-		switch hdata[6] {
+		switch pl.Data[6] {
 		case 0x07: // KEEPALIVE
 			// do nothing
 			// base.Debug("recv keepalive", cSess.IpAddr)
@@ -56,19 +56,24 @@ func LinkCstp(conn net.Conn, cSess *sessdata.ConnSession) {
 			return
 		case 0x03: // DPD-REQ
 			// base.Debug("recv DPD-REQ", cSess.IpAddr)
-			if payloadOutCstp(cSess, sessdata.LTypeIPData, 0x04, nil) {
+			pl.PType = 0x04
+			if payloadOutCstp(cSess, pl) {
 				return
 			}
 		case 0x04:
 			// log.Println("recv DPD-RESP")
 		case 0x00: // DATA
-			dataLen = binary.BigEndian.Uint16(hdata[4:6]) // 4,5
-			if payloadIn(cSess, sessdata.LTypeIPData, 0x00, hdata[8:8+dataLen]) {
+			// 获取数据长度
+			dataLen = binary.BigEndian.Uint16(pl.Data[4:6]) // 4,5
+			// 去除数据头
+			copy(pl.Data, pl.Data[8:8+dataLen])
+			// 更新切片长度
+			pl.Data = pl.Data[:dataLen]
+			// pl.Data = append(pl.Data[:0], pl.Data[8:8+dataLen]...)
+			if payloadIn(cSess, pl) {
 				return
 			}
 		}
-
-		putByte(hdata)
 	}
 }
 
@@ -82,36 +87,44 @@ func cstpWrite(conn net.Conn, cSess *sessdata.ConnSession) {
 	var (
 		err error
 		n   int
-		// header  []byte
-		payload *sessdata.Payload
+		pl  *sessdata.Payload
 	)
 
 	for {
 		select {
-		case payload = <-cSess.PayloadOutCstp:
+		case pl = <-cSess.PayloadOutCstp:
 		case <-cSess.CloseChan:
 			return
 		}
 
-		if payload.LType != sessdata.LTypeIPData {
+		if pl.LType != sessdata.LTypeIPData {
 			continue
 		}
 
-		h := []byte{'S', 'T', 'F', 0x01, 0x00, 0x00, payload.PType, 0x00}
-		header := getByteZero()
-		header = append(header, h...)
-		if payload.PType == 0x00 { // data
-			binary.BigEndian.PutUint16(header[4:6], uint16(len(payload.Data)))
-			header = append(header, payload.Data...)
+		if pl.PType == 0x00 {
+			// 获取数据长度
+			l := len(pl.Data)
+			// 先扩容 +8
+			pl.Data = pl.Data[:l+8]
+			// 数据后移
+			copy(pl.Data[8:], pl.Data)
+			// 添加头信息
+			copy(pl.Data[:8], plHeader)
+			// 更新头长度
+			binary.BigEndian.PutUint16(pl.Data[4:6], uint16(l))
+		} else {
+			pl.Data = append(pl.Data[:0], plHeader...)
+			// 设置头类型
+			pl.Data[6] = pl.PType
 		}
-		n, err = conn.Write(header)
+
+		n, err = conn.Write(pl.Data)
 		if err != nil {
 			base.Error("write err", err)
 			return
 		}
 
-		putByte(header)
-		putPayload(payload)
+		putPayload(pl)
 
 		// 限流设置
 		err = cSess.RateLimit(n, false)

+ 31 - 19
anylink/server/handler/link_dtls.go

@@ -24,21 +24,22 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
 	}()
 
 	var (
+		err  error
+		n    int
 		dead = time.Duration(cSess.CstpDpd+5) * time.Second
 	)
 
 	go dtlsWrite(conn, dSess, cSess)
 
 	for {
-		err := conn.SetReadDeadline(time.Now().Add(dead))
+		err = conn.SetReadDeadline(time.Now().Add(dead))
 		if err != nil {
 			base.Error("SetDeadline: ", err)
 			return
 		}
 
-		// hdata := make([]byte, BufferSize)
-		hdata := getByteFull()
-		n, err := conn.Read(hdata)
+		pl := getPayload()
+		n, err = conn.Read(pl.Data)
 		if err != nil {
 			base.Error("read hdata: ", err)
 			return
@@ -50,7 +51,7 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
 			base.Error(err)
 		}
 
-		switch hdata[0] {
+		switch pl.Data[0] {
 		case 0x07: // KEEPALIVE
 			// do nothing
 			// base.Debug("recv keepalive", cSess.IpAddr)
@@ -59,18 +60,23 @@ func LinkDtls(conn net.Conn, cSess *sessdata.ConnSession) {
 			return
 		case 0x03: // DPD-REQ
 			// base.Debug("recv DPD-REQ", cSess.IpAddr)
-			if payloadOutDtls(cSess, dSess, sessdata.LTypeIPData, 0x04, nil) {
+			pl.PType = 0x04
+			if payloadOutDtls(cSess, dSess, pl) {
 				return
 			}
 		case 0x04:
 			// base.Debug("recv DPD-RESP", cSess.IpAddr)
 		case 0x00: // DATA
-			if payloadIn(cSess, sessdata.LTypeIPData, 0x00, hdata[1:n]) {
+			// 去除数据头
+			// copy(pl.Data, pl.Data[1:n])
+			// 更新切片长度
+			// pl.Data = pl.Data[:n-1]
+			pl.Data = append(pl.Data[:0], pl.Data[1:n]...)
+			if payloadIn(cSess, pl) {
 				return
 			}
 		}
 
-		putByte(hdata)
 	}
 }
 
@@ -82,36 +88,42 @@ func dtlsWrite(conn net.Conn, dSess *sessdata.DtlsSession, cSess *sessdata.ConnS
 	}()
 
 	var (
-		// header  []byte
-		payload *sessdata.Payload
+		pl *sessdata.Payload
 	)
 
 	for {
 		// dtls优先推送数据
 		select {
-		case payload = <-cSess.PayloadOutDtls:
+		case pl = <-cSess.PayloadOutDtls:
 		case <-dSess.CloseChan:
 			return
 		}
 
-		if payload.LType != sessdata.LTypeIPData {
+		if pl.LType != sessdata.LTypeIPData {
 			continue
 		}
 
 		// header = []byte{payload.PType}
-		header := getByteZero()
-		header = append(header, payload.PType)
-		if payload.PType == 0x00 { // data
-			header = append(header, payload.Data...)
+		if pl.PType == 0x00 { // data
+			// 获取数据长度
+			l := len(pl.Data)
+			// 先扩容 +1
+			pl.Data = pl.Data[:l+1]
+			// 数据后移
+			copy(pl.Data[1:], pl.Data)
+			// 添加头信息
+			pl.Data[0] = pl.PType
+		} else {
+			// 设置头类型
+			pl.Data = append(pl.Data[:0], pl.PType)
 		}
-		n, err := conn.Write(header)
+		n, err := conn.Write(pl.Data)
 		if err != nil {
 			base.Error("write err", err)
 			return
 		}
 
-		putByte(header)
-		putPayload(payload)
+		putPayload(pl)
 
 		// 限流设置
 		err = cSess.RateLimit(n, false)

+ 42 - 28
anylink/server/handler/link_tap.go

@@ -64,16 +64,17 @@ func LinkTap(cSess *sessdata.ConnSession) error {
 
 	// arp on
 	cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast on", ifce.Name(), cSess.Mtu)
-	cmdstr2 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
-	cmdstr3 := fmt.Sprintf("ip link set dev %s master %s", ifce.Name(), bridgeName)
-	cmdStrs := []string{cmdstr1, cmdstr2, cmdstr3}
-	err = execCmd(cmdStrs)
+	cmdstr2 := fmt.Sprintf("ip link set dev %s master %s", ifce.Name(), bridgeName)
+	err = execCmd([]string{cmdstr1, cmdstr2})
 	if err != nil {
 		base.Error(err)
 		_ = ifce.Close()
 		return err
 	}
 
+	cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
+	execCmd([]string{cmdstr3})
+
 	go tapRead(ifce, cSess)
 	go tapWrite(ifce, cSess)
 	return nil
@@ -87,31 +88,30 @@ func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
 	}()
 
 	var (
-		err     error
-		payload *sessdata.Payload
-		frame   ethernet.Frame
+		err   error
+		pl    *sessdata.Payload
+		frame ethernet.Frame
 	)
 
 	for {
 		select {
-		case payload = <-cSess.PayloadIn:
+		case pl = <-cSess.PayloadIn:
 		case <-cSess.CloseChan:
 			return
 		}
 
 		// var frame ethernet.Frame
-		frame = getByteFull()
-		switch payload.LType {
+		fb := getByteFull()
+		frame = *fb
+		switch pl.LType {
 		default:
 			// log.Println(payload)
 		case sessdata.LTypeEthernet:
-			copy(frame, payload.Data)
-			frame = frame[:len(payload.Data)]
+			copy(frame, pl.Data)
+			frame = frame[:len(pl.Data)]
 		case sessdata.LTypeIPData: // 需要转换成 Ethernet 数据
-			data := payload.Data
-
-			ip_src := waterutil.IPv4Source(data)
-			if waterutil.IsIPv6(data) || !ip_src.Equal(cSess.IpAddr) {
+			ip_src := waterutil.IPv4Source(pl.Data)
+			if waterutil.IsIPv6(pl.Data) || !ip_src.Equal(cSess.IpAddr) {
 				// 过滤掉IPv6的数据
 				// 非分配给客户端ip,直接丢弃
 				continue
@@ -120,7 +120,7 @@ func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
 			// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
 			// fmt.Println("get:", packet)
 
-			ip_dst := waterutil.IPv4Destination(data)
+			ip_dst := waterutil.IPv4Destination(pl.Data)
 			// fmt.Println("get:", ip_src, ip_dst)
 
 			var dstHw net.HardwareAddr
@@ -140,8 +140,8 @@ func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
 			}
 			// fmt.Println("Gateway", ip_dst, dstAddr.HardwareAddr)
 
-			frame.Prepare(dstHw, cSess.MacHw, ethernet.NotTagged, ethernet.IPv4, len(data))
-			copy(frame[12+2:], data)
+			frame.Prepare(dstHw, cSess.MacHw, ethernet.NotTagged, ethernet.IPv4, len(pl.Data))
+			copy(frame[12+2:], pl.Data)
 		}
 
 		// packet := gopacket.NewPacket(frame, layers.LayerTypeEthernet, gopacket.Default)
@@ -152,8 +152,8 @@ func tapWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
 			return
 		}
 
-		putByte(frame)
-		putPayload(payload)
+		putByte(fb)
+		putPayload(pl)
 	}
 }
 
@@ -166,14 +166,15 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
 	var (
 		err   error
 		n     int
-		buf   []byte
+		data  []byte
 		frame ethernet.Frame
 	)
 
 	for {
 		// var frame ethernet.Frame
 		// frame.Resize(BufferSize)
-		frame = getByteFull()
+		fb := getByteFull()
+		frame = *fb
 		n, err = ifce.Read(frame)
 		if err != nil {
 			base.Error("tap Read err", n, err)
@@ -190,7 +191,7 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
 			continue
 		case ethernet.IPv4:
 			// 发送IP数据
-			data := frame.Payload()
+			data = frame.Payload()
 
 			ip_dst := waterutil.IPv4Destination(data)
 			if !ip_dst.Equal(cSess.IpAddr) {
@@ -202,7 +203,12 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
 			// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
 			// fmt.Println("put:", packet)
 
-			if payloadOut(cSess, sessdata.LTypeIPData, 0x00, data) {
+			pl := getPayload()
+			// 拷贝数据到pl
+			copy(pl.Data, data)
+			// 更新切片长度
+			pl.Data = pl.Data[:len(data)]
+			if payloadOut(cSess, pl) {
 				return
 			}
 
@@ -223,7 +229,7 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
 			// 返回ARP数据
 			src := &arpdis.Addr{IP: cSess.IpAddr, HardwareAddr: cSess.MacHw}
 			dst := &arpdis.Addr{IP: arpReq.SourceProtAddress, HardwareAddr: frame.Source()}
-			buf, err = arpdis.NewARPReply(src, dst)
+			data, err = arpdis.NewARPReply(src, dst)
 			if err != nil {
 				base.Error(err)
 				return
@@ -240,12 +246,20 @@ func tapRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
 			copy(addr.HardwareAddr, frame.Source())
 			arpdis.Add(addr)
 
-			if payloadIn(cSess, sessdata.LTypeEthernet, 0x00, buf) {
+			pl := getPayload()
+			// 设置为二层数据类型
+			pl.LType = sessdata.LTypeEthernet
+			// 拷贝数据到pl
+			copy(pl.Data, data)
+			// 更新切片长度
+			pl.Data = pl.Data[:len(data)]
+
+			if payloadIn(cSess, pl) {
 				return
 			}
 
 		}
 
-		putByte(frame)
+		putByte(fb)
 	}
 }

+ 15 - 13
anylink/server/handler/link_tun.go

@@ -46,15 +46,16 @@ func LinkTun(cSess *sessdata.ConnSession) error {
 	cmdstr1 := fmt.Sprintf("ip link set dev %s up mtu %d multicast off", ifce.Name(), cSess.Mtu)
 	cmdstr2 := fmt.Sprintf("ip addr add dev %s local %s peer %s/32",
 		ifce.Name(), base.Cfg.Ipv4Gateway, cSess.IpAddr)
-	cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
-	cmdStrs := []string{cmdstr1, cmdstr2, cmdstr3}
-	err = execCmd(cmdStrs)
+	err = execCmd([]string{cmdstr1, cmdstr2})
 	if err != nil {
 		base.Error(err)
 		_ = ifce.Close()
 		return err
 	}
 
+	cmdstr3 := fmt.Sprintf("sysctl -w net.ipv6.conf.%s.disable_ipv6=1", ifce.Name())
+	execCmd([]string{cmdstr3})
+
 	go tunRead(ifce, cSess)
 	go tunWrite(ifce, cSess)
 	return nil
@@ -68,24 +69,24 @@ func tunWrite(ifce *water.Interface, cSess *sessdata.ConnSession) {
 	}()
 
 	var (
-		err     error
-		payload *sessdata.Payload
+		err error
+		pl  *sessdata.Payload
 	)
 
 	for {
 		select {
-		case payload = <-cSess.PayloadIn:
+		case pl = <-cSess.PayloadIn:
 		case <-cSess.CloseChan:
 			return
 		}
 
-		_, err = ifce.Write(payload.Data)
+		_, err = ifce.Write(pl.Data)
 		if err != nil {
 			base.Error("tun Write err", err)
 			return
 		}
 
-		putPayload(payload)
+		putPayload(pl)
 	}
 }
 
@@ -101,13 +102,16 @@ func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
 
 	for {
 		// data := make([]byte, BufferSize)
-		data := getByteFull()
-		n, err = ifce.Read(data)
+		pl := getPayload()
+		n, err = ifce.Read(pl.Data)
 		if err != nil {
 			base.Error("tun Read err", n, err)
 			return
 		}
 
+		// 更新数据长度
+		pl.Data = (pl.Data)[:n]
+
 		// data = data[:n]
 		// ip_src := waterutil.IPv4Source(data)
 		// ip_dst := waterutil.IPv4Destination(data)
@@ -116,10 +120,8 @@ func tunRead(ifce *water.Interface, cSess *sessdata.ConnSession) {
 		// packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
 		// fmt.Println("read:", packet)
 
-		if payloadOut(cSess, sessdata.LTypeIPData, 0x00, data[:n]) {
+		if payloadOut(cSess, pl) {
 			return
 		}
-
-		putByte(data)
 	}
 }

+ 16 - 32
anylink/server/handler/payload.go

@@ -6,18 +6,9 @@ import (
 	"github.com/songgao/water/waterutil"
 )
 
-func payloadIn(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
-	pl := getPayload()
-	pl.LType = lType
-	pl.PType = pType
-	pl.Data = append(pl.Data, data...)
-
-	return payloadInData(cSess, pl)
-}
-
-func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool {
+func payloadIn(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
 	// 进行Acl规则判断
-	check := checkLinkAcl(cSess.Group, payload)
+	check := checkLinkAcl(cSess.Group, pl)
 	if !check {
 		// 校验不通过直接丢弃
 		return false
@@ -25,7 +16,7 @@ func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool
 
 	closed := false
 	select {
-	case cSess.PayloadIn <- payload:
+	case cSess.PayloadIn <- pl:
 	case <-cSess.CloseChan:
 		closed = true
 	}
@@ -33,21 +24,16 @@ func payloadInData(cSess *sessdata.ConnSession, payload *sessdata.Payload) bool
 	return closed
 }
 
-func payloadOut(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
+func payloadOut(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
 	dSess := cSess.GetDtlsSession()
 	if dSess == nil {
-		return payloadOutCstp(cSess, lType, pType, data)
+		return payloadOutCstp(cSess, pl)
 	} else {
-		return payloadOutDtls(cSess, dSess, lType, pType, data)
+		return payloadOutDtls(cSess, dSess, pl)
 	}
 }
 
-func payloadOutCstp(cSess *sessdata.ConnSession, lType sessdata.LType, pType byte, data []byte) bool {
-	pl := getPayload()
-	pl.LType = lType
-	pl.PType = pType
-	pl.Data = append(pl.Data, data...)
-
+func payloadOutCstp(cSess *sessdata.ConnSession, pl *sessdata.Payload) bool {
 	closed := false
 
 	select {
@@ -59,12 +45,7 @@ func payloadOutCstp(cSess *sessdata.ConnSession, lType sessdata.LType, pType byt
 	return closed
 }
 
-func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, lType sessdata.LType, pType byte, data []byte) bool {
-	pl := getPayload()
-	pl.LType = lType
-	pl.PType = pType
-	pl.Data = append(pl.Data, data...)
-
+func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, pl *sessdata.Payload) bool {
 	select {
 	case cSess.PayloadOutDtls <- pl:
 	case <-dSess.CloseChan:
@@ -74,14 +55,16 @@ func payloadOutDtls(cSess *sessdata.ConnSession, dSess *sessdata.DtlsSession, lT
 }
 
 // Acl规则校验
-func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
-	if payload.LType == sessdata.LTypeIPData && payload.PType == 0x00 && len(group.LinkAcl) > 0 {
+func checkLinkAcl(group *dbdata.Group, pl *sessdata.Payload) bool {
+	if pl.LType == sessdata.LTypeIPData && pl.PType == 0x00 && len(group.LinkAcl) > 0 {
 	} else {
 		return true
 	}
 
-	ip_dst := waterutil.IPv4Destination(payload.Data)
-	ip_port := waterutil.IPv4DestinationPort(payload.Data)
+	data := pl.Data
+	ip_dst := waterutil.IPv4Destination(data)
+	ip_port := waterutil.IPv4DestinationPort(data)
+	ip_proto := waterutil.IPv4Protocol(data)
 	// fmt.Println("sent:", ip_dst, ip_port)
 
 	// 优先放行dns端口
@@ -94,7 +77,8 @@ func checkLinkAcl(group *dbdata.Group, payload *sessdata.Payload) bool {
 	for _, v := range group.LinkAcl {
 		// 循环判断ip和端口
 		if v.IpNet.Contains(ip_dst) {
-			if v.Port == ip_port || v.Port == 0 {
+			// 放行允许ip的ping
+			if v.Port == ip_port || v.Port == 0 || ip_proto == waterutil.ICMP {
 				if v.Action == dbdata.Allow {
 					return true
 				} else {

+ 27 - 13
anylink/server/handler/pool.go

@@ -3,13 +3,21 @@ package handler
 import (
 	"sync"
 
+	"github.com/bjdgyc/anylink/base"
 	"github.com/bjdgyc/anylink/sessdata"
 )
 
+// 不允许直接修改
+// [6] => PType
+var plHeader = []byte{'S', 'T', 'F', 0x01, 0x00, 0x00, 0x00, 0x00}
+
 var plPool = sync.Pool{
 	New: func() interface{} {
+		b := make([]byte, BufferSize)
 		pl := sessdata.Payload{
-			Data: make([]byte, 0, BufferSize),
+			LType: sessdata.LTypeIPData,
+			PType: 0x00,
+			Data:  b,
 		}
 		// fmt.Println("plPool-init", len(pl.Data), cap(pl.Data))
 		return &pl
@@ -22,31 +30,37 @@ func getPayload() *sessdata.Payload {
 }
 
 func putPayload(pl *sessdata.Payload) {
-	pl.LType = 0
-	pl.PType = 0
-	pl.Data = pl.Data[:0]
+	// 错误数据丢弃
+	if cap(pl.Data) != BufferSize {
+		base.Warn("payload cap is err", cap(pl.Data))
+		return
+	}
+
+	pl.LType = sessdata.LTypeIPData
+	pl.PType = 0x00
+	pl.Data = pl.Data[:BufferSize]
 	plPool.Put(pl)
 }
 
 var bytePool = sync.Pool{
 	New: func() interface{} {
-		b := make([]byte, 0, BufferSize)
+		b := make([]byte, BufferSize)
 		// fmt.Println("bytePool-init")
-		return b
+		return &b
 	},
 }
 
-func getByteZero() []byte {
-	b := bytePool.Get().([]byte)
+func getByteZero() *[]byte {
+	b := bytePool.Get().(*[]byte)
+	*b = (*b)[:0]
 	return b
 }
 
-func getByteFull() []byte {
-	b := bytePool.Get().([]byte)
-	b = b[:BufferSize]
+func getByteFull() *[]byte {
+	b := bytePool.Get().(*[]byte)
 	return b
 }
-func putByte(b []byte) {
-	b = b[:0]
+func putByte(b *[]byte) {
+	*b = (*b)[:BufferSize]
 	bytePool.Put(b)
 }

+ 44 - 0
anylink/server/handler/pool_test.go

@@ -0,0 +1,44 @@
+package handler
+
+import (
+	"testing"
+)
+
+// go test -bench=. -benchmem
+
+// 去除数据头
+func BenchmarkHeaderCopy(b *testing.B) {
+	l := 1500
+	for i := 0; i < b.N; i++ {
+		b.StopTimer()
+		pl := getPayload()
+		// 初始化数据
+		pl.Data = pl.Data[:l]
+
+		b.StartTimer()
+		dataLen := l - 8
+		copy(pl.Data, pl.Data[8:8+dataLen])
+		// 更新切片长度
+		pl.Data = pl.Data[:dataLen]
+		b.StopTimer()
+
+		putPayload(pl)
+	}
+}
+
+func BenchmarkHeaderAppend(b *testing.B) {
+	l := 1500
+	for i := 0; i < b.N; i++ {
+		b.StopTimer()
+		pl := getPayload()
+		// 初始化数据
+		pl.Data = pl.Data[:l]
+
+		b.StartTimer()
+		dataLen := l - 8
+		pl.Data = append(pl.Data[:0], pl.Data[:8+dataLen]...)
+		b.StopTimer()
+
+		putPayload(pl)
+	}
+}

+ 15 - 15
anylink/server/sessdata/ip_pool.go

@@ -67,10 +67,10 @@ func AcquireIp(username, macAddr string) net.IP {
 
 	// 判断已经分配过
 	mi := &dbdata.IpMap{}
-	err := dbdata.One("MacAddr", macAddr, mi)
+	err := dbdata.One("mac_addr", macAddr, mi)
 	if err == nil {
-		ip := mi.IpAddr
-		ipStr := ip.String()
+		ipStr := mi.IpAddr
+		ip := net.ParseIP(ipStr)
 		// 跳过活跃连接
 		_, ok := ipActive[ipStr]
 		// 检测原有ip是否在新的ip池内
@@ -78,7 +78,7 @@ func AcquireIp(username, macAddr string) net.IP {
 			mi.Username = username
 			mi.LastLogin = tNow
 			// 回写db数据
-			_ = dbdata.Save(mi)
+			_ = dbdata.Add(mi)
 			ipActive[ipStr] = true
 			return ip
 		}
@@ -99,12 +99,12 @@ func AcquireIp(username, macAddr string) net.IP {
 		}
 
 		v := &dbdata.IpMap{}
-		err = dbdata.One("IpAddr", ip, v)
+		err = dbdata.One("ip_addr", ipStr, v)
 		if err != nil {
 			if dbdata.CheckErrNotFound(err) {
 				// 该ip没有被使用
-				mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
-				_ = dbdata.Save(mi)
+				mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow}
+				_ = dbdata.Add(mi)
 				ipActive[ipStr] = true
 				return ip
 			}
@@ -120,9 +120,9 @@ func AcquireIp(username, macAddr string) net.IP {
 		// 已经超过租期
 		if tNow.Sub(v.LastLogin) > time.Duration(base.Cfg.IpLease)*time.Second {
 			_ = dbdata.Del(v)
-			mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
+			mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow}
 			// 重写db数据
-			_ = dbdata.Save(mi)
+			_ = dbdata.Add(mi)
 			ipActive[ipStr] = true
 			return ip
 		}
@@ -139,11 +139,11 @@ func AcquireIp(username, macAddr string) net.IP {
 	}
 
 	// 使用最早登陆的mac ip
-	ip := farIp.IpAddr
-	ipStr := ip.String()
-	mi = &dbdata.IpMap{IpAddr: ip, MacAddr: macAddr, Username: username, LastLogin: tNow}
+	ipStr := farIp.IpAddr
+	ip := net.ParseIP(ipStr)
+	mi = &dbdata.IpMap{IpAddr: ipStr, MacAddr: macAddr, Username: username, LastLogin: tNow}
 	// 回写db数据
-	_ = dbdata.Save(mi)
+	_ = dbdata.Add(mi)
 	ipActive[ipStr] = true
 	return ip
 }
@@ -155,9 +155,9 @@ func ReleaseIp(ip net.IP, macAddr string) {
 
 	delete(ipActive, ip.String())
 	mi := &dbdata.IpMap{}
-	err := dbdata.One("IpAddr", ip, mi)
+	err := dbdata.One("ip_addr", ip.String(), mi)
 	if err == nil {
 		mi.LastLogin = time.Now()
-		_ = dbdata.Save(mi)
+		_ = dbdata.Add(mi)
 	}
 }

+ 3 - 2
anylink/server/sessdata/ip_pool_test.go

@@ -15,7 +15,8 @@ import (
 func preData(tmpDir string) {
 	base.Test()
 	tmpDb := path.Join(tmpDir, "test.db")
-	base.Cfg.DbFile = tmpDb
+	base.Cfg.DbType = "sqlite3"
+	base.Cfg.DbSource = tmpDb
 	base.Cfg.Ipv4CIDR = "192.168.3.0/24"
 	base.Cfg.Ipv4Start = "192.168.3.1"
 	base.Cfg.Ipv4End = "192.168.3.199"
@@ -27,7 +28,7 @@ func preData(tmpDir string) {
 		Name:      "group1",
 		Bandwidth: 1000,
 	}
-	_ = dbdata.Save(&group)
+	_ = dbdata.Add(&group)
 	initIpPool()
 }
 

+ 1 - 1
anylink/server/sessdata/protocol.go

@@ -8,8 +8,8 @@ const (
 )
 
 type Payload struct {
-	PType byte  // payload types
 	LType LType // LinkType
+	PType byte  // payload types
 	Data  []byte
 }
 

+ 5 - 5
anylink/server/sessdata/session.go

@@ -55,7 +55,7 @@ type ConnSession struct {
 }
 
 type DtlsSession struct {
-	isActive int32
+	isActive  int32
 	CloseChan chan struct{}
 	closeOnce sync.Once
 	IpAddr    net.IP
@@ -183,9 +183,9 @@ func (s *Session) NewConn() *ConnSession {
 		IpAddr:         ip,
 		closeOnce:      sync.Once{},
 		CloseChan:      make(chan struct{}),
-		PayloadIn:      make(chan *Payload),
-		PayloadOutCstp: make(chan *Payload),
-		PayloadOutDtls: make(chan *Payload),
+		PayloadIn:      make(chan *Payload, 64),
+		PayloadOutCstp: make(chan *Payload, 64),
+		PayloadOutDtls: make(chan *Payload, 64),
 		dSess:          &atomic.Value{},
 	}
 
@@ -236,7 +236,7 @@ func (cs *ConnSession) NewDtlsConn() *DtlsSession {
 	}
 
 	dSess := &DtlsSession{
-		isActive: 1,
+		isActive:  1,
 		CloseChan: make(chan struct{}),
 		closeOnce: sync.Once{},
 		IpAddr:    cs.IpAddr,

+ 11 - 196
anylink/web/package-lock.json

@@ -9,6 +9,7 @@
       "version": "0.1.0",
       "dependencies": {
         "axios": "^0.20.0",
+        "caniuse-lite": "^1.0.30001242",
         "chokidar": "^3.5.2",
         "core-js": "^3.6.5",
         "echarts": "^4.9.0",
@@ -1806,9 +1807,6 @@
       },
       "engines": {
         "node": ">=8"
-      },
-      "optionalDependencies": {
-        "vue-loader-v16": "npm:vue-loader@^16.0.0-beta.7"
       }
     },
     "node_modules/@vue/cli-service/node_modules/acorn": {
@@ -3419,14 +3417,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001240",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001240.tgz",
-      "integrity": "sha512-nb8mDzfMdxBDN7ZKx8chWafAdBp5DAAlpWvNyUGe5tcDWd838zpzDN3Rah9cjCqhfOKkrvx40G2SDtP0qiWX/w==",
-      "dev": true,
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/browserslist"
-      }
+      "version": "1.0.30001242",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001242.tgz",
+      "integrity": "sha512-KvNuZ/duufelMB3w2xtf9gEWCSxJwUgoxOx5b6ScLXC4kPc9xsczUVCPrQU26j5kOsHM4pSUL54tAZt5THQKug=="
     },
     "node_modules/case-sensitive-paths-webpack-plugin": {
       "version": "2.3.0",
@@ -6372,6 +6365,9 @@
       "resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-2.3.2.tgz",
       "integrity": "sha1-ilJveLj99GI7cJ4Ll1xSwkwC/Ro=",
       "optional": true,
+      "os": [
+        "darwin"
+      ],
       "engines": {
         "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
       }
@@ -12880,104 +12876,6 @@
         "vue-style-loader": "^4.1.0"
       }
     },
-    "node_modules/vue-loader-v16": {
-      "name": "vue-loader",
-      "version": "16.1.2",
-      "resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.1.2.tgz",
-      "integrity": "sha1-XAO2xQ0qX5g8fOuhXFDXjKKymPQ=",
-      "dev": true,
-      "optional": true,
-      "dependencies": {
-        "chalk": "^4.1.0",
-        "hash-sum": "^2.0.0",
-        "loader-utils": "^2.0.0"
-      }
-    },
-    "node_modules/vue-loader-v16/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1611325836307&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
-      "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
-      "dev": true,
-      "optional": true,
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/vue-loader-v16/node_modules/chalk": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz?cache=0&sync_timestamp=1594334924907&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-4.1.0.tgz",
-      "integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
-      "dev": true,
-      "optional": true,
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/vue-loader-v16/node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz",
-      "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
-      "dev": true,
-      "optional": true,
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/vue-loader-v16/node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
-      "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
-      "dev": true,
-      "optional": true
-    },
-    "node_modules/vue-loader-v16/node_modules/has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz",
-      "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
-      "dev": true,
-      "optional": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/vue-loader-v16/node_modules/loader-utils": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-2.0.0.tgz",
-      "integrity": "sha1-5MrOW4FtQloWa18JfhDNErNgZLA=",
-      "dev": true,
-      "optional": true,
-      "dependencies": {
-        "big.js": "^5.2.2",
-        "emojis-list": "^3.0.0",
-        "json5": "^2.1.2"
-      },
-      "engines": {
-        "node": ">=8.9.0"
-      }
-    },
-    "node_modules/vue-loader-v16/node_modules/supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz?cache=0&sync_timestamp=1611394023277&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.2.0.tgz",
-      "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
-      "dev": true,
-      "optional": true,
-      "dependencies": {
-        "has-flag": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/vue-loader/node_modules/hash-sum": {
       "version": "1.0.2",
       "resolved": "https://registry.npm.taobao.org/hash-sum/download/hash-sum-1.0.2.tgz",
@@ -13027,11 +12925,11 @@
       "integrity": "sha1-bp2lOzyAuy1lCBiPWyAEEIZs0ws=",
       "dev": true,
       "dependencies": {
+        "chokidar": "^3.4.1",
         "graceful-fs": "^4.1.2",
         "neo-async": "^2.5.0"
       },
       "optionalDependencies": {
-        "chokidar": "^3.4.1",
         "watchpack-chokidar2": "^2.0.0"
       }
     },
@@ -15777,7 +15675,6 @@
         "thread-loader": "^2.1.3",
         "url-loader": "^2.2.0",
         "vue-loader": "^15.9.2",
-        "vue-loader-v16": "npm:vue-loader@^16.0.0-beta.7",
         "vue-style-loader": "^4.1.2",
         "webpack": "^4.0.0",
         "webpack-bundle-analyzer": "^3.8.0",
@@ -17191,10 +17088,9 @@
       }
     },
     "caniuse-lite": {
-      "version": "1.0.30001240",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001240.tgz",
-      "integrity": "sha512-nb8mDzfMdxBDN7ZKx8chWafAdBp5DAAlpWvNyUGe5tcDWd838zpzDN3Rah9cjCqhfOKkrvx40G2SDtP0qiWX/w==",
-      "dev": true
+      "version": "1.0.30001242",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001242.tgz",
+      "integrity": "sha512-KvNuZ/duufelMB3w2xtf9gEWCSxJwUgoxOx5b6ScLXC4kPc9xsczUVCPrQU26j5kOsHM4pSUL54tAZt5THQKug=="
     },
     "case-sensitive-paths-webpack-plugin": {
       "version": "2.3.0",
@@ -25162,87 +25058,6 @@
         }
       }
     },
-    "vue-loader-v16": {
-      "version": "npm:[email protected]",
-      "resolved": "https://registry.npm.taobao.org/vue-loader/download/vue-loader-16.1.2.tgz",
-      "integrity": "sha1-XAO2xQ0qX5g8fOuhXFDXjKKymPQ=",
-      "dev": true,
-      "optional": true,
-      "requires": {
-        "chalk": "^4.1.0",
-        "hash-sum": "^2.0.0",
-        "loader-utils": "^2.0.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-4.3.0.tgz?cache=0&sync_timestamp=1611325836307&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-styles%2Fdownload%2Fansi-styles-4.3.0.tgz",
-          "integrity": "sha1-7dgDYornHATIWuegkG7a00tkiTc=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npm.taobao.org/chalk/download/chalk-4.1.0.tgz?cache=0&sync_timestamp=1594334924907&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchalk%2Fdownload%2Fchalk-4.1.0.tgz",
-          "integrity": "sha1-ThSHCmGNni7dl92DRf2dncMVZGo=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-2.0.1.tgz",
-          "integrity": "sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz",
-          "integrity": "sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=",
-          "dev": true,
-          "optional": true
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz",
-          "integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
-          "dev": true,
-          "optional": true
-        },
-        "loader-utils": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-2.0.0.tgz",
-          "integrity": "sha1-5MrOW4FtQloWa18JfhDNErNgZLA=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "big.js": "^5.2.2",
-            "emojis-list": "^3.0.0",
-            "json5": "^2.1.2"
-          }
-        },
-        "supports-color": {
-          "version": "7.2.0",
-          "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz?cache=0&sync_timestamp=1611394023277&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.2.0.tgz",
-          "integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        }
-      }
-    },
     "vue-router": {
       "version": "3.4.6",
       "resolved": "https://registry.npm.taobao.org/vue-router/download/vue-router-3.4.6.tgz?cache=0&sync_timestamp=1602076636169&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-router%2Fdownload%2Fvue-router-3.4.6.tgz",

+ 1 - 0
anylink/web/package.json

@@ -9,6 +9,7 @@
   },
   "dependencies": {
     "axios": "^0.20.0",
+    "caniuse-lite": "^1.0.30001242",
     "chokidar": "^3.5.2",
     "core-js": "^3.6.5",
     "echarts": "^4.9.0",

+ 31 - 20
anylink/web/src/pages/group/List.vue

@@ -166,7 +166,7 @@
 
         <el-form-item label="带宽限制" prop="bandwidth">
           <el-input v-model.number="ruleForm.bandwidth">
-            <template slot="append">BYTE</template>
+            <template slot="append">BYTE/S</template>
           </el-input>
         </el-form-item>
         <el-form-item label="本地网络" prop="allow_lan">
@@ -181,18 +181,20 @@
             <el-col :span="4">
               <el-button size="mini" type="success" icon="el-icon-plus" circle
                          @click.prevent="addDomain(ruleForm.client_dns)"></el-button>
-              <el-button size="mini" type="danger" icon="el-icon-minus" circle
-                         @click.prevent="removeDomain(ruleForm.client_dns)"></el-button>
             </el-col>
           </el-row>
           <el-row v-for="(item,index) in ruleForm.client_dns"
-                  :key="index" style="margin-bottom: 5px" gutter="10">
+                  :key="index" style="margin-bottom: 5px" :gutter="10">
             <el-col :span="10">
               <el-input v-model="item.val"></el-input>
             </el-col>
-            <el-col :span="14">
+            <el-col :span="12">
               <el-input v-model="item.note" placeholder="备注"></el-input>
             </el-col>
+            <el-col :span="2">
+              <el-button size="mini" type="danger" icon="el-icon-minus" circle
+                         @click.prevent="removeDomain(ruleForm.client_dns,index)"></el-button>
+            </el-col>
           </el-row>
         </el-form-item>
 
@@ -202,18 +204,20 @@
             <el-col :span="4">
               <el-button size="mini" type="success" icon="el-icon-plus" circle
                          @click.prevent="addDomain(ruleForm.route_include)"></el-button>
-              <el-button size="mini" type="danger" icon="el-icon-minus" circle
-                         @click.prevent="removeDomain(ruleForm.route_include)"></el-button>
             </el-col>
           </el-row>
           <el-row v-for="(item,index) in ruleForm.route_include"
-                  :key="index" style="margin-bottom: 5px" gutter="10">
+                  :key="index" style="margin-bottom: 5px" :gutter="10">
             <el-col :span="10">
               <el-input v-model="item.val"></el-input>
             </el-col>
-            <el-col :span="14">
+            <el-col :span="12">
               <el-input v-model="item.note" placeholder="备注"></el-input>
             </el-col>
+            <el-col :span="2">
+              <el-button size="mini" type="danger" icon="el-icon-minus" circle
+                         @click.prevent="removeDomain(ruleForm.route_include,index)"></el-button>
+            </el-col>
           </el-row>
         </el-form-item>
 
@@ -223,18 +227,20 @@
             <el-col :span="4">
               <el-button size="mini" type="success" icon="el-icon-plus" circle
                          @click.prevent="addDomain(ruleForm.route_exclude)"></el-button>
-              <el-button size="mini" type="danger" icon="el-icon-minus" circle
-                         @click.prevent="removeDomain(ruleForm.route_exclude)"></el-button>
             </el-col>
           </el-row>
           <el-row v-for="(item,index) in ruleForm.route_exclude"
-                  :key="index" style="margin-bottom: 5px" gutter="10">
+                  :key="index" style="margin-bottom: 5px" :gutter="10">
             <el-col :span="10">
               <el-input v-model="item.val"></el-input>
             </el-col>
-            <el-col :span="14">
+            <el-col :span="12">
               <el-input v-model="item.note" placeholder="备注"></el-input>
             </el-col>
+            <el-col :span="2">
+              <el-button size="mini" type="danger" icon="el-icon-minus" circle
+                         @click.prevent="removeDomain(ruleForm.route_exclude,index)"></el-button>
+            </el-col>
           </el-row>
         </el-form-item>
 
@@ -244,13 +250,11 @@
             <el-col :span="4">
               <el-button size="mini" type="success" icon="el-icon-plus" circle
                          @click.prevent="addDomain(ruleForm.link_acl)"></el-button>
-              <el-button size="mini" type="danger" icon="el-icon-minus" circle
-                         @click.prevent="removeDomain(ruleForm.link_acl)"></el-button>
             </el-col>
           </el-row>
 
           <el-row v-for="(item,index) in ruleForm.link_acl"
-                  :key="index" style="margin-bottom: 5px" gutter="5">
+                  :key="index" style="margin-bottom: 5px" :gutter="5">
             <el-col :span="11">
               <el-input placeholder="请输入CIDR地址" v-model="item.val">
                 <el-select v-model="item.action" slot="prepend">
@@ -262,9 +266,13 @@
             <el-col :span="3">
               <el-input v-model.number="item.port" placeholder="端口"></el-input>
             </el-col>
-            <el-col :span="10">
+            <el-col :span="8">
               <el-input v-model="item.note" placeholder="备注"></el-input>
             </el-col>
+            <el-col :span="2">
+              <el-button size="mini" type="danger" icon="el-icon-minus" circle
+                         @click.prevent="removeDomain(ruleForm.link_acl,index)"></el-button>
+            </el-col>
           </el-row>
         </el-form-item>
 
@@ -389,13 +397,16 @@ export default {
         console.log(error);
       });
     },
-    removeDomain(arr, item) {
-      console.log(item)
+    removeDomain(arr, index) {
+      console.log(index)
+      if (index >= 0 && index < arr.length) {
+        arr.splice(index, 1)
+      }
       // let index = arr.indexOf(item);
       // if (index !== -1 && arr.length > 1) {
       //   arr.splice(index, 1)
       // }
-      arr.pop()
+      // arr.pop()
     },
     addDomain(arr) {
       arr.push({val: "", action: "allow", port: 0});