├── .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 | ![image](https://github.com/csznet/goForward/assets/127601663/2f7840ff-9b34-4f69-a7c1-41feb35e726b) 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 |
55 |
56 | 57 | 58 |
59 |

Powered by goForward

60 |
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 | 120 | 121 | 122 | 123 | 124 | 130 | 131 | 132 | {{end}} 133 | 134 |
本地端口远程地址远程端口类型流量操作备注
{{.LocalPort}}{{.RemoteAddr}}{{.RemotePort}}{{.Protocol}}{{if gt .TotalGigabyte 0}}{{.TotalGigabyte}}G{{end}}{{.TotalBytes}} 125 | 129 | {{.Ps}}
135 |
136 | 137 |

添加转发

138 |
139 | 140 | 141 | 142 | 143 | 144 | 148 | 149 |
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 | --------------------------------------------------------------------------------