├── .gitignore
├── assets
├── assets.go
└── templates
│ ├── pwd.tmpl
│ ├── msg.tmpl
│ └── index.tmpl
├── conf
└── conf.go
├── readme.md
├── get.sh
├── utils
└── utils.go
├── main.go
├── go.mod
├── .github
└── workflows
│ └── go-build.yml
├── web
└── web.go
├── sql
└── sql.go
├── forward
└── forward.go
└── go.sum
/.gitignore:
--------------------------------------------------------------------------------
1 | /conf.json
2 | /data.db
3 | /goForward.db
4 | /main
5 | /goForward
6 | /script.py
7 | /visit.py
8 | /main.exe
--------------------------------------------------------------------------------
/assets/assets.go:
--------------------------------------------------------------------------------
1 | package assets
2 |
3 | import "embed"
4 |
5 | var (
6 | //go:embed templates
7 | Templates embed.FS
8 | )
9 |
--------------------------------------------------------------------------------
/conf/conf.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | // ConnectionStats 结构体用于保存多个连接信息
9 | type ConnectionStats struct {
10 | Id int `gorm:"primaryKey;autoIncrement"`
11 | Ps string
12 | LocalPort string
13 | RemoteAddr string
14 | RemotePort string
15 | Protocol string
16 | Status int
17 | TotalBytes uint64
18 | TotalGigabyte uint64
19 | }
20 |
21 | type IpBan struct {
22 | Id int `gorm:"primaryKey;autoIncrement"`
23 | Ip string
24 | TimeStamp int64
25 | }
26 |
27 | // 全局转发协程等待组
28 | var Wg sync.WaitGroup
29 |
30 | // 全局协程通道 未初始化默认为nil
31 | var Ch chan string
32 |
33 | // Web管理面板端口
34 | var WebPort string
35 |
36 | // Web IP绑定
37 | var WebIP string
38 |
39 | // Web管理面板密码
40 | var WebPass string
41 |
42 | // TCP超时
43 | var TcpTimeout int
44 |
45 | // 版本号
46 | var version string
47 |
48 | // 数据库位置
49 | var Db string
50 |
51 | func init() {
52 | if version != "" {
53 | fmt.Println("goForward Version " + version)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | 使用 golang 实现的 tcp udp 端口转发
2 |
3 | 目前已实现:
4 |
5 | - 规则热加载
6 | - web 管理面板
7 | - 流量统计
8 |
9 | 支持:Linux、Windows、MacOS(MacOS 需要自行编译)
10 |
11 | **截图**
12 |
13 | 
14 |
15 | **使用**
16 |
17 | Linux 下载
18 |
19 | ```
20 | sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/csznet/goForward/main/get.sh)"
21 | ```
22 |
23 | 运行
24 |
25 | ```
26 | ./goForward
27 | ```
28 |
29 | **参数**
30 |
31 | TCP 无传输超时关闭
32 | 默认 60,单位秒
33 |
34 | ```
35 | ./goForward -tt 18000
36 | ```
37 |
38 | 自定义 web 管理端口
39 |
40 | ```
41 | ./goForward -port 8899
42 | ```
43 |
44 | 指定 IP 绑定
45 |
46 | ```
47 | ./goForward -ip 1.1.1.1
48 | ```
49 |
50 | 指定数据库位置
51 |
52 | ```
53 | ./goForward -db /root/data.db
54 | ```
55 |
56 | 设置 web 管理访问密码
57 |
58 | ```
59 | ./goForward -pass 666
60 | ```
61 |
62 | 当 24H 内同一 IP 密码试错超过 3 次将会 ban 掉
63 |
64 | ## 开机自启
65 |
66 | **创建 Systemd 服务**
67 |
68 | ```
69 | sudo nano /etc/systemd/system/goForward.service
70 | ```
71 |
72 | **输入内容**
73 |
74 | ```
75 | [Unit]
76 | Description=Start goForward on boot
77 |
78 | [Service]
79 | ExecStart=/full/path/to/your/goForward -pass 666
80 |
81 | [Install]
82 | WantedBy=default.target
83 | ```
84 |
85 | 其中的`/full/path/to/your/goForward`改为二进制文件地址,后面可接参数
86 |
87 | **重新加载 Systemd 配置**
88 |
89 | ```
90 | sudo systemctl daemon-reload
91 | ```
92 |
93 | **启用服务**
94 |
95 | ```
96 | sudo systemctl enable goForward
97 | ```
98 |
99 | **启动服务**
100 |
101 | ```
102 | sudo systemctl start goForward
103 | ```
104 |
105 | **检查状态**
106 |
107 | ```
108 | sudo systemctl status goForward.service
109 | ```
110 |
--------------------------------------------------------------------------------
/get.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ARCH=$(uname -m)
4 |
5 | if [ "$ARCH" == "x86_64" ]; then
6 | FILE="goForward.zip"
7 | elif [ "$ARCH" == "arm64" ]; then
8 | FILE="goForward_arm64.zip"
9 | else
10 | echo -e "\033[41mError\033[0m: Unsupported architecture: $ARCH"
11 | exit 1
12 | fi
13 |
14 | # Check if unzip is installed
15 | if ! command -v unzip &> /dev/null; then
16 | echo -e "\033[41mError\033[0m: unzip is not installed. Installing..."
17 |
18 | # Install unzip based on the package manager
19 | if command -v apt-get &> /dev/null; then
20 | sudo apt-get install -y unzip
21 | elif command -v yum &> /dev/null; then
22 | sudo yum install -y unzip
23 | else
24 | echo -e "\033[41mError\033[0m: Unsupported package manager. Please install unzip manually."
25 | exit 1
26 | fi
27 | fi
28 |
29 | # 获取百度的平均延迟(ping 5次并取平均值)
30 | ping_result=$(ping -c 5 -q baidu.com | awk -F'/' 'END{print $5}')
31 |
32 | # 判断延迟是否在100以内
33 | if awk -v ping="$ping_result" 'BEGIN{exit !(ping < 100)}'; then
34 | echo "服务器位于中国国内,使用代理下载"
35 | url="https://mirror.ghproxy.com/https://github.com/csznet/goForward/releases/latest/download/${FILE}"
36 | else
37 | echo "服务器位于国外,不使用代理下载"
38 | url="https://github.com/csznet/goForward/releases/latest/download/${FILE}"
39 | fi
40 |
41 | # Download and unzip
42 | if ! curl -L -O $url; then
43 | echo -e "\033[41mError\033[0m: Failed to download $FILE. Please check your internet connection or try again later."
44 | exit 1
45 | fi
46 |
47 | if ! unzip "$FILE"; then
48 | echo -e "\033[41mError\033[0m: Failed to unzip $FILE."
49 | exit 1
50 | fi
51 |
52 | rm "$FILE"
53 |
54 | # Set permissions
55 | chmod +x goForward
56 |
57 | # Output success message
58 | echo -e "\033[44mSuccess\033[0m The 'goForward' executable is ready for use."
--------------------------------------------------------------------------------
/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "sync"
5 |
6 | "csz.net/goForward/conf"
7 | "csz.net/goForward/forward"
8 | "csz.net/goForward/sql"
9 | )
10 |
11 | // 增加转发并开启
12 | func AddForward(newF conf.ConnectionStats) bool {
13 | if newF.LocalPort == conf.WebPort && newF.Protocol == "tcp" {
14 | return false
15 | }
16 | id := sql.AddForward(newF)
17 | if id > 0 {
18 | stats := &forward.ConnectionStats{
19 | ConnectionStats: conf.ConnectionStats{
20 | Id: id,
21 | LocalPort: newF.LocalPort,
22 | RemotePort: newF.RemotePort,
23 | RemoteAddr: newF.RemoteAddr,
24 | Protocol: newF.Protocol,
25 | TotalBytes: 0,
26 | },
27 | TotalBytesOld: 0,
28 | TotalBytesLock: sync.Mutex{},
29 | }
30 | conf.Wg.Add(1)
31 | go func() {
32 | forward.Run(stats)
33 | conf.Wg.Done()
34 | }()
35 | return true
36 | }
37 | return false
38 | }
39 |
40 | // 删除并关闭指定转发
41 | func DelForward(f conf.ConnectionStats) bool {
42 | sql.DelForward(f.Id)
43 | conf.Ch <- f.LocalPort + f.Protocol
44 | return true
45 | }
46 |
47 | // 改变转发状态
48 | func ExStatus(f conf.ConnectionStats) bool {
49 | if sql.FreeForward(f.LocalPort, f.Protocol) {
50 | return false
51 | }
52 | if sql.UpdateForwardStatus(f.Id, f.Status) {
53 | // 启用转发
54 | if f.Status == 0 {
55 | stats := &forward.ConnectionStats{
56 | ConnectionStats: conf.ConnectionStats{
57 | Id: f.Id,
58 | LocalPort: f.LocalPort,
59 | RemotePort: f.RemotePort,
60 | RemoteAddr: f.RemoteAddr,
61 | Protocol: f.Protocol,
62 | TotalBytes: f.TotalBytes,
63 | },
64 | TotalBytesOld: f.TotalBytes,
65 | TotalBytesLock: sync.Mutex{},
66 | }
67 | conf.Wg.Add(1)
68 | go func() {
69 | forward.Run(stats)
70 | conf.Wg.Done()
71 | }()
72 | return true
73 | } else {
74 | conf.Ch <- f.LocalPort + f.Protocol
75 | return true
76 | }
77 | }
78 |
79 | return false
80 | }
81 |
--------------------------------------------------------------------------------
/assets/templates/pwd.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | goForward
7 |
52 |
53 |
54 |
61 |
62 |
--------------------------------------------------------------------------------
/assets/templates/msg.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 提示
8 |
42 |
43 |
44 | Message
45 | {{if .suc}}
46 |
47 |
{{.msg}}
48 |
3秒后跳转
49 |
50 | {{else}}
51 |
52 |
{{.msg}}
53 |
3秒后跳转
54 |
55 | {{end}}
56 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "strings"
6 | "sync"
7 |
8 | "csz.net/goForward/conf"
9 | "csz.net/goForward/forward"
10 | "csz.net/goForward/sql"
11 | "csz.net/goForward/web"
12 | )
13 |
14 | func main() {
15 | go web.Run()
16 | if conf.TcpTimeout < 5 {
17 | conf.TcpTimeout = 5
18 | }
19 | // 初始化通道
20 | conf.Ch = make(chan string)
21 | forwardList := sql.GetAction()
22 | if len(forwardList) == 0 {
23 | //添加测试数据
24 | testData := conf.ConnectionStats{
25 | LocalPort: conf.WebPort,
26 | RemotePort: conf.WebPort,
27 | RemoteAddr: "127.0.0.1",
28 | Protocol: "udp",
29 | }
30 | sql.AddForward(testData)
31 | forwardList = sql.GetForwardList()
32 | }
33 | var largeStats forward.LargeConnectionStats
34 | largeStats.Connections = make([]*forward.ConnectionStats, len(forwardList))
35 | for i := range forwardList {
36 | connectionStats := &forward.ConnectionStats{
37 | ConnectionStats: conf.ConnectionStats{
38 | Id: forwardList[i].Id,
39 | Protocol: forwardList[i].Protocol,
40 | LocalPort: forwardList[i].LocalPort,
41 | RemotePort: forwardList[i].RemotePort,
42 | RemoteAddr: forwardList[i].RemoteAddr,
43 | TotalBytes: forwardList[i].TotalBytes,
44 | },
45 | TotalBytesOld: forwardList[i].TotalBytes,
46 | TotalBytesLock: sync.Mutex{},
47 | }
48 |
49 | largeStats.Connections[i] = connectionStats
50 | }
51 | // 设置 WaitGroup 计数为连接数
52 | conf.Wg.Add(len(largeStats.Connections))
53 | // 并发执行多个转发
54 | for _, stats := range largeStats.Connections {
55 | go func(s *forward.ConnectionStats) {
56 | forward.Run(s)
57 | conf.Wg.Done()
58 | }(stats)
59 | }
60 | conf.Wg.Wait()
61 | defer close(conf.Ch)
62 | }
63 | func init() {
64 | flag.StringVar(&conf.WebPort, "port", "8889", "Web Port")
65 | flag.StringVar(&conf.Db, "db", "goForward.db", "Db Path")
66 | flag.StringVar(&conf.WebIP, "ip", "0.0.0.0", "Web IP")
67 | flag.StringVar(&conf.WebPass, "pass", "", "Web Password")
68 | flag.IntVar(&conf.TcpTimeout, "tt", 60, "Tcp Timeout")
69 | flag.Parse()
70 | if !strings.HasSuffix(conf.Db, ".db") {
71 | conf.Db += ".db"
72 | }
73 | sql.Once()
74 | }
75 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module csz.net/goForward
2 |
3 | go 1.20
4 |
5 | require gorm.io/gorm v1.25.5
6 |
7 | require (
8 | github.com/bytedance/sonic v1.9.1 // indirect
9 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
10 | github.com/dustin/go-humanize v1.0.1 // indirect
11 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
12 | github.com/gin-contrib/sse v0.1.0 // indirect
13 | github.com/glebarez/go-sqlite v1.21.2 // indirect
14 | github.com/go-playground/locales v0.14.1 // indirect
15 | github.com/go-playground/universal-translator v0.18.1 // indirect
16 | github.com/go-playground/validator/v10 v10.14.0 // indirect
17 | github.com/goccy/go-json v0.10.2 // indirect
18 | github.com/google/uuid v1.3.0 // indirect
19 | github.com/gorilla/context v1.1.1 // indirect
20 | github.com/gorilla/securecookie v1.1.1 // indirect
21 | github.com/gorilla/sessions v1.2.1 // indirect
22 | github.com/json-iterator/go v1.1.12 // indirect
23 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
24 | github.com/leodido/go-urn v1.2.4 // indirect
25 | github.com/mattn/go-isatty v0.0.19 // indirect
26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
27 | github.com/modern-go/reflect2 v1.0.2 // indirect
28 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
29 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
30 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
31 | github.com/ugorji/go/codec v1.2.11 // indirect
32 | golang.org/x/arch v0.3.0 // indirect
33 | golang.org/x/crypto v0.9.0 // indirect
34 | golang.org/x/net v0.10.0 // indirect
35 | golang.org/x/sys v0.8.0 // indirect
36 | golang.org/x/text v0.9.0 // indirect
37 | google.golang.org/protobuf v1.30.0 // indirect
38 | gopkg.in/yaml.v3 v3.0.1 // indirect
39 | modernc.org/libc v1.22.5 // indirect
40 | modernc.org/mathutil v1.5.0 // indirect
41 | modernc.org/memory v1.5.0 // indirect
42 | modernc.org/sqlite v1.23.1 // indirect
43 | )
44 |
45 | require (
46 | github.com/gin-contrib/sessions v0.0.5
47 | github.com/gin-gonic/gin v1.9.1
48 | github.com/glebarez/sqlite v1.10.0
49 | github.com/jinzhu/inflection v1.0.0 // indirect
50 | github.com/jinzhu/now v1.1.5 // indirect
51 | )
52 |
--------------------------------------------------------------------------------
/.github/workflows/go-build.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 |
3 | on:
4 | release:
5 | types: [ "created" ]
6 |
7 | jobs:
8 |
9 | build_and_upload_assets:
10 | permissions: write-all
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | - name: Set up Go
16 | uses: actions/setup-go@v4
17 | with:
18 | go-version: '1.20'
19 |
20 | - name: Build Linux arm64
21 | run: |
22 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-X csz.net/goForward/conf.version=${GITHUB_REF/refs\/tags\//}" -o goForward main.go
23 |
24 | - name: Zip Linux arm64
25 | run: |
26 | sudo apt-get install -y zip
27 | zip goForward_arm64.zip goForward
28 |
29 | - name: Delete goForward arm64
30 | run: |
31 | rm goForward
32 |
33 | - name: Build Linux amd64
34 | run: |
35 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X csz.net/goForward/conf.version=${GITHUB_REF/refs\/tags\//}" -o goForward main.go
36 |
37 | - name: Zip Linux amd64
38 | run: |
39 | zip goForward.zip goForward
40 |
41 | - name: Build Windows amd64
42 | run: |
43 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X csz.net/goForward/conf.version=${GITHUB_REF/refs\/tags\//}" -o goForward.exe main.go
44 |
45 | - name: Zip Windows amd64
46 | run: |
47 | zip goForward_win.zip goForward.exe
48 |
49 | - name: Upload server asset
50 | uses: actions/upload-release-asset@v1
51 | env:
52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53 | with:
54 | upload_url: ${{ github.event.release.upload_url }}
55 | asset_path: ./goForward.zip
56 | asset_name: goForward.zip
57 | asset_content_type: application/zip
58 |
59 | - name: Upload server asset
60 | uses: actions/upload-release-asset@v1
61 | env:
62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63 | with:
64 | upload_url: ${{ github.event.release.upload_url }}
65 | asset_path: ./goForward_arm64.zip
66 | asset_name: goForward_arm64.zip
67 | asset_content_type: application/zip
68 |
69 | - name: Upload server asset
70 | uses: actions/upload-release-asset@v1
71 | env:
72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
73 | with:
74 | upload_url: ${{ github.event.release.upload_url }}
75 | asset_path: ./goForward_win.zip
76 | asset_name: goForward_win.zip
77 | asset_content_type: application/zip
--------------------------------------------------------------------------------
/web/web.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | import (
4 | "fmt"
5 | "html/template"
6 | "log"
7 | "net/http"
8 | "strconv"
9 | "time"
10 |
11 | "csz.net/goForward/assets"
12 | "csz.net/goForward/conf"
13 | "csz.net/goForward/sql"
14 | "csz.net/goForward/utils"
15 | "github.com/gin-contrib/sessions"
16 | "github.com/gin-contrib/sessions/cookie"
17 | "github.com/gin-gonic/gin"
18 | )
19 |
20 | func Run() {
21 | gin.SetMode(gin.ReleaseMode)
22 | r := gin.Default()
23 | store := cookie.NewStore([]byte("secret"))
24 | r.Use(sessions.Sessions("goForward", store))
25 | r.Use(checkCookieMiddleware)
26 | r.SetHTMLTemplate(template.Must(template.New("").Funcs(r.FuncMap).ParseFS(assets.Templates, "templates/*")))
27 | r.GET("/", func(c *gin.Context) {
28 | c.HTML(http.StatusOK, "index.tmpl", gin.H{
29 | "forwardList": sql.GetForwardList(),
30 | })
31 | })
32 | r.GET("/ban", func(c *gin.Context) {
33 | c.JSON(200, sql.GetIpBan())
34 | })
35 | r.POST("/add", func(c *gin.Context) {
36 | if c.PostForm("localPort") != "" && c.PostForm("remoteAddr") != "" && c.PostForm("remotePort") != "" && c.PostForm("protocol") != "" {
37 | f := conf.ConnectionStats{
38 | LocalPort: c.PostForm("localPort"),
39 | RemotePort: c.PostForm("remotePort"),
40 | RemoteAddr: c.PostForm("remoteAddr"),
41 | Protocol: c.PostForm("protocol"),
42 | Ps: c.PostForm("ps"),
43 | }
44 | if utils.AddForward(f) {
45 | c.HTML(200, "msg.tmpl", gin.H{
46 | "msg": "添加成功",
47 | "suc": true,
48 | })
49 | } else {
50 | c.HTML(200, "msg.tmpl", gin.H{
51 | "msg": "添加失败,端口已占用",
52 | "suc": false,
53 | })
54 | }
55 | } else {
56 | c.HTML(200, "msg.tmpl", gin.H{
57 | "msg": "添加失败,表单信息不完整",
58 | "suc": false,
59 | })
60 | }
61 | })
62 | r.GET("/do/:id", func(c *gin.Context) {
63 | id := c.Param("id")
64 | intID, err := strconv.Atoi(id)
65 | f := sql.GetForward(intID)
66 | status := false
67 | if err == nil {
68 | if f.Status == 0 {
69 | f.Status = 1
70 | if len(sql.GetAction()) == 1 {
71 | c.HTML(200, "msg.tmpl", gin.H{
72 | "msg": "停止失败,请确保有至少一个转发在运行",
73 | "suc": false,
74 | })
75 | return
76 | }
77 | } else {
78 | f.Status = 0
79 | }
80 | status = utils.ExStatus(f)
81 | }
82 | if status {
83 | c.HTML(200, "msg.tmpl", gin.H{
84 | "msg": "操作成功",
85 | "suc": true,
86 | })
87 | return
88 | } else {
89 | c.HTML(200, "msg.tmpl", gin.H{
90 | "msg": "操作失败",
91 | "suc": false,
92 | })
93 | return
94 | }
95 | })
96 | r.GET("/del/:id", func(c *gin.Context) {
97 | id := c.Param("id")
98 | intID, err := strconv.Atoi(id)
99 | f := sql.GetForward(intID)
100 | if err != nil {
101 | c.HTML(200, "msg.tmpl", gin.H{
102 | "msg": "删除失败,ID错误",
103 | "suc": false,
104 | })
105 | return
106 | }
107 | if len(sql.GetForwardList()) == 1 {
108 | c.HTML(200, "msg.tmpl", gin.H{
109 | "msg": "删除失败,请确保有至少一个转发在运行",
110 | "suc": false,
111 | })
112 | return
113 | }
114 | if f.Id != 0 && utils.DelForward(f) {
115 | c.HTML(200, "msg.tmpl", gin.H{
116 | "msg": "删除成功",
117 | "suc": true,
118 | })
119 | } else {
120 | c.HTML(200, "msg.tmpl", gin.H{
121 | "msg": "删除失败",
122 | "suc": false,
123 | })
124 | }
125 | })
126 | r.GET("/pwd", func(c *gin.Context) {
127 | c.HTML(200, "pwd.tmpl", nil)
128 | })
129 | r.POST("/pwd", func(c *gin.Context) {
130 | if !sql.IpFree(c.ClientIP()) {
131 | c.HTML(200, "msg.tmpl", gin.H{
132 | "msg": "IP is Ban",
133 | "suc": false,
134 | })
135 | return
136 | }
137 | session := sessions.Default(c)
138 | session.Set("p", c.PostForm("p"))
139 | // 设置session的过期时间为1天
140 | session.Options(sessions.Options{MaxAge: 86400})
141 | session.Save()
142 | if c.PostForm("p") != conf.WebPass {
143 | ban := conf.IpBan{
144 | Ip: c.ClientIP(),
145 | TimeStamp: time.Now().Unix(),
146 | }
147 | sql.AddBan(ban)
148 | }
149 | c.Redirect(302, "/")
150 | })
151 | fmt.Println("Web管理面板端口:" + conf.WebPort)
152 | err := r.Run(conf.WebIP + ":" + conf.WebPort)
153 | if err != nil {
154 | log.Panicln(err)
155 | }
156 | }
157 |
158 | // 密码验证中间件
159 | func checkCookieMiddleware(c *gin.Context) {
160 | currenPath := c.Request.URL.Path
161 | if conf.WebPass != "" && currenPath != "/pwd" {
162 | session := sessions.Default(c)
163 | pass := session.Get("p")
164 | if pass != conf.WebPass {
165 | c.Redirect(http.StatusFound, "/pwd")
166 | c.Abort()
167 | return
168 | }
169 | }
170 | // 继续处理请求
171 | c.Next()
172 | }
173 |
--------------------------------------------------------------------------------
/sql/sql.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 | "time"
10 |
11 | "csz.net/goForward/conf"
12 | "github.com/glebarez/sqlite"
13 | "gorm.io/gorm"
14 | )
15 |
16 | // 定义数据库指针
17 | var db *gorm.DB
18 |
19 | func Once() {
20 | var err error
21 | var dbPath string
22 | executablePath, err := os.Executable()
23 | if conf.Db == "goForward.db" {
24 | if err != nil {
25 | log.Println("获取可执行文件路径失败:", err)
26 | log.Println("使用默认获取的路径")
27 | dbPath = "goForward.db"
28 | } else {
29 | dbPath = filepath.Join(filepath.Dir(executablePath), "goForward.db")
30 | }
31 | } else {
32 | dbPath = conf.Db
33 | }
34 | fmt.Println("Data:", dbPath)
35 | db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
36 | if err != nil {
37 | log.Println("连接数据库失败")
38 | return
39 | }
40 | db.AutoMigrate(&conf.ConnectionStats{})
41 | db.AutoMigrate(&conf.IpBan{})
42 | }
43 |
44 | // 获取转发列表
45 | func GetForwardList() []conf.ConnectionStats {
46 | var res []conf.ConnectionStats
47 | db.Model(&conf.ConnectionStats{}).Find(&res)
48 | return res
49 | }
50 |
51 | // 获取启用的转发列表
52 | func GetAction() []conf.ConnectionStats {
53 | var res []conf.ConnectionStats
54 | db.Model(&conf.ConnectionStats{}).Where("status = ?", 0).Find(&res)
55 | return res
56 | }
57 |
58 | // 获取ipban列表
59 | func GetIpBan() []conf.IpBan {
60 | var res []conf.IpBan
61 | db.Model(&conf.IpBan{}).Find(&res)
62 | return res
63 | }
64 |
65 | // 修改指定转发统计流量(byte)
66 | func UpdateForwardBytes(id int, bytes uint64) bool {
67 | res := db.Model(&conf.ConnectionStats{}).Where("id = ?", id).Update("total_bytes", bytes)
68 | if res.Error != nil {
69 | fmt.Println(res.Error)
70 | return false
71 | }
72 | return true
73 | }
74 |
75 | // 修改指定转发统计流量(byte)
76 | func UpdateForwardGb(id int, gb uint64) bool {
77 | res := db.Model(&conf.ConnectionStats{}).Where("id = ?", id).Update("total_gigabyte", gb)
78 | if res.Error != nil {
79 | fmt.Println(res.Error)
80 | return false
81 | }
82 | return true
83 | }
84 |
85 | // 修改指定转发状态
86 | func UpdateForwardStatus(id int, status int) bool {
87 | res := db.Model(&conf.ConnectionStats{}).Where("id = ?", id).Update("status", status)
88 | if res.Error != nil {
89 | fmt.Println(res.Error)
90 | return false
91 | }
92 | return true
93 | }
94 |
95 | // 获取指定转发内容
96 | func GetForward(id int) conf.ConnectionStats {
97 | var get conf.ConnectionStats
98 | db.Model(&conf.ConnectionStats{}).Where("id = ?", id).Find(&get)
99 | return get
100 | }
101 |
102 | // 判断指定端口转发是否可添加
103 | func FreeForward(localPort, protocol string) bool {
104 | var get conf.ConnectionStats
105 | res := db.Model(&conf.ConnectionStats{}).Where("local_port = ? And protocol = ?", localPort, protocol).Find(&get)
106 | if res.Error == nil {
107 | if get.Id == 0 {
108 | return true
109 | } else {
110 | return false
111 | }
112 | }
113 | return false
114 | }
115 |
116 | // 去掉所有空格
117 | func rmSpaces(input string) string {
118 | return strings.ReplaceAll(input, " ", "")
119 | }
120 |
121 | // 增加转发
122 | func AddForward(newForward conf.ConnectionStats) int {
123 | //预处理
124 | newForward.RemoteAddr = rmSpaces(newForward.RemoteAddr)
125 | newForward.RemotePort = rmSpaces(newForward.RemotePort)
126 | newForward.LocalPort = rmSpaces(newForward.LocalPort)
127 | if newForward.Protocol != "udp" {
128 | newForward.Protocol = "tcp"
129 | }
130 | if !FreeForward(newForward.LocalPort, newForward.Protocol) {
131 | return 0
132 | }
133 | //开启事务
134 | tx := db.Begin()
135 | if tx.Error != nil {
136 | log.Println("开启事务失败")
137 | return 0
138 | }
139 | // 在事务中执行插入操作
140 | if err := tx.Create(&newForward).Error; err != nil {
141 | log.Println("插入新转发失败")
142 | log.Println(err)
143 | tx.Rollback() // 回滚事务
144 | return 0
145 | }
146 | // 提交事务
147 | tx.Commit()
148 | return newForward.Id
149 | }
150 |
151 | // 删除转发
152 | func DelForward(id int) bool {
153 | if err := db.Where("id = ?", id).Delete(&conf.ConnectionStats{}).Error; err != nil {
154 | log.Println(err)
155 | return false
156 | }
157 | return true
158 | }
159 |
160 | // 增加错误登录
161 | func AddBan(ip conf.IpBan) bool {
162 | //开启事务
163 | tx := db.Begin()
164 | if tx.Error != nil {
165 | return false
166 | }
167 | // 在事务中执行插入操作
168 | if err := tx.Create(&ip).Error; err != nil {
169 | log.Println(err)
170 | tx.Rollback() // 回滚事务
171 | return false
172 | }
173 | // 提交事务
174 | tx.Commit()
175 | return true
176 | }
177 |
178 | // 检查过去一天内指定IP地址的记录条数是否超过三条
179 | func IpFree(ip string) bool {
180 | // 获取过去一天的时间戳
181 | oneDayAgo := time.Now().Add(-24 * time.Hour).Unix()
182 |
183 | // 查询过去一天内指定IP地址的记录条数
184 | var count int64
185 | if err := db.Model(&conf.IpBan{}).Where("ip = ? AND time_stamp > ?", ip, oneDayAgo).Count(&count).Error; err != nil {
186 | log.Println(err)
187 | return false
188 | }
189 |
190 | // 如果记录条数超过三条,则返回false;否则返回true
191 | return count < 3
192 | }
193 |
--------------------------------------------------------------------------------
/assets/templates/index.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | goForward
9 |
97 |
98 |
99 |
100 | 转发列表
101 |
102 |
103 |
104 |
105 |
106 | | 本地端口 |
107 | 远程地址 |
108 | 远程端口 |
109 | 类型 |
110 | 流量 |
111 | 操作 |
112 | 备注 |
113 |
114 |
115 |
116 | {{range .forwardList}}
117 |
118 |
119 | | {{.LocalPort}} |
120 | {{.RemoteAddr}} |
121 | {{.RemotePort}} |
122 | {{.Protocol}} |
123 | {{if gt .TotalGigabyte 0}}{{.TotalGigabyte}}G{{end}}{{.TotalBytes}} |
124 |
125 |
129 | |
130 | {{.Ps}} |
131 |
132 | {{end}}
133 |
134 |
135 |
136 |
137 | 添加转发
138 |
150 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/forward/forward.go:
--------------------------------------------------------------------------------
1 | package forward
2 |
3 | import (
4 | "context"
5 | "encoding/binary"
6 | "fmt"
7 | "io"
8 | "log"
9 | "net"
10 | "strconv"
11 | "sync"
12 | "time"
13 |
14 | "csz.net/goForward/conf"
15 | "csz.net/goForward/sql"
16 | )
17 |
18 | type ConnectionStats struct {
19 | conf.ConnectionStats
20 | TotalBytesOld uint64 `gorm:"-"`
21 | TotalBytesLock sync.Mutex `gorm:"-"`
22 | TCPConnections []net.Conn `gorm:"-"` // 用于存储 TCP 连接
23 | TcpTime int `gorm:"-"` // TCP无传输时间
24 | }
25 |
26 | // 保存多个连接信息
27 | type LargeConnectionStats struct {
28 | Connections []*ConnectionStats `json:"connections"`
29 | }
30 |
31 | // 复用缓冲区
32 | var bufPool = sync.Pool{
33 | New: func() interface{} {
34 | return make([]byte, 4096)
35 | },
36 | }
37 |
38 | // 开启转发,负责分发具体转发
39 | func Run(stats *ConnectionStats) {
40 | defer releaseResources(stats) // 在函数返回时释放资源
41 | var ctx, cancel = context.WithCancel(context.Background())
42 | var innerWg sync.WaitGroup
43 | defer cancel()
44 | innerWg.Add(1)
45 | go func() {
46 | stats.printStats(ctx)
47 | innerWg.Done()
48 | }()
49 | fmt.Printf("【%s】监听端口 %s 转发至 %s:%s\n", stats.Protocol, stats.LocalPort, stats.RemoteAddr, stats.RemotePort)
50 | if stats.Protocol == "udp" {
51 | // UDP转发
52 | localAddr, err := net.ResolveUDPAddr("udp", ":"+stats.LocalPort)
53 | if err != nil {
54 | log.Fatalln("解析本地地址时发生错误:", err)
55 | }
56 | remoteAddr, err := net.ResolveUDPAddr("udp", stats.RemoteAddr+":"+stats.RemotePort)
57 | if err != nil {
58 | log.Fatalln("解析远程地址时发生错误:", err)
59 | }
60 | conn, err := net.ListenUDP("udp", localAddr)
61 | if err != nil {
62 | log.Fatalln("监听时发生错误:", err)
63 | }
64 | defer conn.Close()
65 | go func() {
66 | for {
67 | select {
68 | case stopPort := <-conf.Ch:
69 | if stopPort == stats.LocalPort+stats.Protocol {
70 | fmt.Printf("【%s】停止监听端口 %s\n", stats.Protocol, stats.LocalPort)
71 | conn.Close()
72 | cancel()
73 | return
74 | } else {
75 | conf.Ch <- stopPort
76 | time.Sleep(3 * time.Second)
77 | }
78 | default:
79 | time.Sleep(1 * time.Second)
80 | }
81 | }
82 | }()
83 | innerWg.Add(1)
84 | go func() {
85 | stats.handleUDPConnection(conn, remoteAddr, ctx)
86 | innerWg.Done()
87 | }()
88 | } else {
89 | // TCP转发
90 | listener, err := net.Listen("tcp", ":"+stats.LocalPort)
91 | if err != nil {
92 | log.Fatalln("监听时发生错误:", err)
93 | }
94 | defer listener.Close()
95 | go func() {
96 | for {
97 | select {
98 | case stopPort := <-conf.Ch:
99 | fmt.Println("通道信息:" + stopPort)
100 | fmt.Println("当前端口:" + stats.LocalPort)
101 | if stopPort == stats.LocalPort+stats.Protocol {
102 | fmt.Printf("【%s】停止监听端口 %s\n", stats.Protocol, stats.LocalPort)
103 | listener.Close()
104 | cancel()
105 | // 遍历并关闭所有 TCP 连接
106 | for _, conn := range stats.TCPConnections {
107 | conn.Close()
108 | }
109 | return
110 | } else {
111 | conf.Ch <- stopPort
112 | time.Sleep(3 * time.Second)
113 | }
114 | default:
115 | time.Sleep(1 * time.Second)
116 | }
117 | }
118 | }()
119 | for {
120 | clientConn, err := listener.Accept()
121 | if err != nil {
122 | log.Println("【"+stats.LocalPort+"】接受连接时发生错误:", err)
123 | cancel()
124 | break
125 | }
126 | innerWg.Add(1)
127 | go func() {
128 | stats.handleTCPConnection(clientConn, ctx, cancel)
129 | innerWg.Done()
130 | }()
131 | }
132 | }
133 | innerWg.Wait()
134 | }
135 |
136 | // TCP转发
137 | func (cs *ConnectionStats) handleTCPConnection(clientConn net.Conn, ctx context.Context, cancel context.CancelFunc) {
138 | defer clientConn.Close()
139 | remoteConn, err := net.Dial("tcp", cs.RemoteAddr+":"+cs.RemotePort)
140 | if err != nil {
141 | log.Println("【"+cs.LocalPort+"】连接远程地址时发生错误:", err)
142 | return
143 | }
144 | defer remoteConn.Close()
145 | cs.TCPConnections = append(cs.TCPConnections, clientConn, remoteConn) // 添加连接到列表
146 | var copyWG sync.WaitGroup
147 | copyWG.Add(2)
148 | go func() {
149 | defer copyWG.Done()
150 | if err := cs.copyBytes(clientConn, remoteConn); err != nil {
151 | log.Println("复制字节时发生错误:", err)
152 | cancel() // Assuming `cancel` is the cancel function from the context
153 | }
154 | }()
155 | go func() {
156 | defer copyWG.Done()
157 | if err := cs.copyBytes(remoteConn, clientConn); err != nil {
158 | log.Println("复制字节时发生错误:", err)
159 | cancel() // Assuming `cancel` is the cancel function from the context
160 | }
161 | }()
162 | for {
163 | select {
164 | case <-ctx.Done():
165 | // 如果上级 context 被取消,停止接收新连接
166 | return
167 | default:
168 | copyWG.Wait()
169 | return
170 | }
171 | }
172 | }
173 |
174 | // UDP转发
175 | func (cs *ConnectionStats) handleUDPConnection(localConn *net.UDPConn, remoteAddr *net.UDPAddr, ctx context.Context) {
176 | for {
177 | select {
178 | case <-ctx.Done():
179 | return
180 | default:
181 | buf := bufPool.Get().([]byte)
182 | n, _, err := localConn.ReadFromUDP(buf)
183 | if err != nil {
184 | log.Println("【"+cs.LocalPort+"】从源读取时发生错误:", err)
185 | return
186 | }
187 | fmt.Printf("收到长度为 %d 的UDP数据包\n", n)
188 | cs.TotalBytesLock.Lock()
189 | cs.TotalBytes += uint64(n)
190 | cs.TotalBytesLock.Unlock()
191 |
192 | // 处理消息的边界和错误情况
193 | go func() {
194 | cs.forwardUDPMessage(localConn, remoteAddr, buf[:n])
195 | bufPool.Put(buf)
196 | }()
197 | }
198 | }
199 | }
200 |
201 | func (cs *ConnectionStats) forwardUDPMessage(localConn *net.UDPConn, remoteAddr *net.UDPAddr, message []byte) {
202 | // 在消息前面添加消息长度信息
203 | length := make([]byte, 2)
204 | binary.BigEndian.PutUint16(length, uint16(len(message)))
205 | // 组合消息长度和实际消息
206 | data := append(length, message...)
207 | _, err := localConn.WriteToUDP(data, remoteAddr)
208 | if err != nil {
209 | log.Println("【"+cs.LocalPort+"】写入目标时发生错误:", err)
210 | }
211 |
212 | }
213 |
214 | func (cs *ConnectionStats) copyBytes(dst, src net.Conn) error {
215 | buf := bufPool.Get().([]byte)
216 | defer bufPool.Put(buf)
217 | for {
218 | n, err := src.Read(buf)
219 | if n > 0 {
220 | cs.TotalBytesLock.Lock()
221 | cs.TotalBytes += uint64(n)
222 | cs.TotalBytesLock.Unlock()
223 | _, err := dst.Write(buf[:n])
224 | if err != nil {
225 | log.Println("【"+cs.LocalPort+"】写入目标时发生错误:", err)
226 | return err
227 | }
228 | }
229 | if err == io.EOF {
230 | break
231 | }
232 | if err != nil {
233 | log.Println("【"+cs.LocalPort+"】从源读取时发生错误:", err)
234 | break
235 | }
236 | }
237 | // 关闭连接
238 | dst.Close()
239 | src.Close()
240 | return nil
241 | }
242 |
243 | // 定时打印和处理流量变化
244 | func (cs *ConnectionStats) printStats(ctx context.Context) {
245 | ticker := time.NewTicker(10 * time.Second)
246 | defer ticker.Stop() // 在函数结束时停止定时器
247 | for {
248 | select {
249 | case <-ticker.C:
250 | cs.TotalBytesLock.Lock()
251 | if cs.TotalBytes > cs.TotalBytesOld {
252 | if cs.Protocol == "tcp" {
253 | cs.TcpTime = 0
254 | }
255 | var total string
256 | if cs.TotalBytes > 0 && float64(cs.TotalBytes)/(1024*1024) < 0.5 {
257 | total = strconv.FormatFloat(float64(cs.TotalBytes)/(1024), 'f', 2, 64) + "KB"
258 | } else {
259 | total = strconv.FormatFloat(float64(cs.TotalBytes)/(1024*1024), 'f', 2, 64) + "MB"
260 | }
261 | fmt.Printf("【%s】端口 %s 统计流量: %s\n", cs.Protocol, cs.LocalPort, total)
262 | //统计更换单位
263 | var gb uint64 = 1073741824
264 | if cs.TotalBytes >= gb {
265 | cs.TotalGigabyte = cs.TotalGigabyte + 1
266 | sql.UpdateForwardGb(cs.Id, cs.TotalGigabyte)
267 | cs.TotalBytes = cs.TotalBytes - gb
268 | }
269 | cs.TotalBytesOld = cs.TotalBytes
270 | sql.UpdateForwardBytes(cs.Id, cs.TotalBytes)
271 | fmt.Printf("【%s】端口 %s 当前连接数: %d\n", cs.Protocol, cs.LocalPort, len(cs.TCPConnections))
272 | } else {
273 | if cs.Protocol == "tcp" {
274 | // fmt.Printf("【%s】端口 %s 当前超时秒: %d\n", cs.Protocol, cs.LocalPort, cs.TcpTime)
275 | if cs.TcpTime >= conf.TcpTimeout {
276 | // fmt.Printf("【%s】端口 %s 超时关闭\n", cs.Protocol, cs.LocalPort)
277 | for i := len(cs.TCPConnections) - 1; i >= 0; i-- {
278 | conn := cs.TCPConnections[i]
279 | conn.Close()
280 | // 从连接列表中移除关闭的连接
281 | cs.TCPConnections = append(cs.TCPConnections[:i], cs.TCPConnections[i+1:]...)
282 | }
283 | } else {
284 | cs.TcpTime = cs.TcpTime + 5
285 | }
286 | }
287 | }
288 | cs.TotalBytesLock.Unlock()
289 | //当协程退出时执行
290 | case <-ctx.Done():
291 | return
292 | }
293 | }
294 | }
295 |
296 | // 关闭 TCP 连接并从切片中移除
297 | func closeTCPConnections(stats *ConnectionStats) {
298 | stats.TotalBytesLock.Lock()
299 | defer stats.TotalBytesLock.Unlock()
300 | for i, conn := range stats.TCPConnections {
301 | conn.Close()
302 | stats.TCPConnections[i] = nil
303 | }
304 | stats.TCPConnections = nil // 清空切片
305 | }
306 |
307 | // 释放资源
308 | func releaseResources(stats *ConnectionStats) {
309 | closeTCPConnections(stats)
310 | }
311 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
2 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
3 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
10 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
11 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
12 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
13 | github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
14 | github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
15 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
16 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
17 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
18 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
19 | github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
20 | github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
21 | github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
22 | github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
23 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
24 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
25 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
26 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
27 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
28 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
29 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
30 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
31 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
32 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
33 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
34 | github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
35 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
36 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
37 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
38 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
39 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
40 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
41 | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
42 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
43 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
44 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
45 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
46 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
47 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
48 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
49 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
50 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
51 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
52 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
53 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
54 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
55 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
56 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
57 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
58 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
59 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
60 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
61 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
62 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
63 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
64 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
65 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
66 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
67 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
68 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
69 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
70 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
71 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
72 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
73 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
74 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
75 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
76 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
77 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
78 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
79 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
80 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
81 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
82 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
83 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
84 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
85 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
86 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
87 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
88 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
89 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
90 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
91 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
92 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
93 | golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
94 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
95 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
96 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
97 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
98 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
99 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
100 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
101 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
102 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
103 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
104 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
105 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
106 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
107 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
108 | gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
109 | gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
110 | modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
111 | modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
112 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
113 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
114 | modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
115 | modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
116 | modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
117 | modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
118 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
119 |
--------------------------------------------------------------------------------