├── app ├── handles │ ├── clients.go │ ├── download_test.go │ └── download.go └── router.go ├── proxy ├── backend │ ├── base.go │ ├── server.go │ └── pool.go ├── wxPusher │ └── pusher.go ├── server │ ├── client.go │ ├── status.go │ └── server.go ├── protocol │ └── protocol.go └── client │ └── client.go ├── .gitignore ├── images ├── logo.png └── qq.svg ├── start ├── genkey.sh ├── win_client_startup.bat ├── linux_server_start_nohup.sh └── linux_server_start_server.sh ├── docker-entrypoint ├── pkg ├── cache │ └── cache.go ├── input.go ├── recover.go ├── ip.go ├── hello.go ├── middleware │ └── cors.go ├── zip.go ├── util_test.go ├── util.go ├── encryption.go └── log.go ├── .github ├── ISSUE_TEMPLATE │ ├── ----.md │ └── -----.md └── workflows │ ├── release.yaml │ └── codeql-analysis.yml ├── docker-compose.yml ├── Dockerfile ├── LICENSE ├── go.mod ├── README.md ├── go.sum └── cmd └── miner-proxy ├── main.go └── web └── index.html /app/handles/clients.go: -------------------------------------------------------------------------------- 1 | package handles 2 | -------------------------------------------------------------------------------- /proxy/backend/base.go: -------------------------------------------------------------------------------- 1 | package backend 2 | -------------------------------------------------------------------------------- /proxy/backend/server.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | .idea 3 | *.log 4 | *.txt 5 | local 6 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrornet/miner-proxy/HEAD/images/logo.png -------------------------------------------------------------------------------- /start/genkey.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n1 > secretkey.txt 3 | -------------------------------------------------------------------------------- /start/win_client_startup.bat: -------------------------------------------------------------------------------- 1 | miner-proxy_windows_amd64.exe -l :12345 -r xxxxx:123456 -sc -client -secret_key 123456789 -debug -------------------------------------------------------------------------------- /docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # first arg is `-f` or `--some-option` 5 | if [ "${1#-}" != "$1" ]; then 6 | set -- miner-proxy "$@" 7 | fi 8 | 9 | exec "$@" -------------------------------------------------------------------------------- /start/linux_server_start_nohup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | secretkey=$(cat secretkey.txt) 3 | nohup ./miner-proxy -l :12345 -r xxxxx:123456 -sc -k $secretkey -debug >> miner.log 2>& 1 & 4 | -------------------------------------------------------------------------------- /start/linux_server_start_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | secretkey=$(cat secretkey.txt) 3 | ./miner-proxy -install -l :123 -r baidu.com:123456 -sc -k $secretkey -d && ./miner-proxy -start && ./miner-proxy -stat 4 | -------------------------------------------------------------------------------- /pkg/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/patrickmn/go-cache" 7 | ) 8 | 9 | var Client *cache.Cache 10 | 11 | func init() { 12 | Client = cache.New(time.Second*60, time.Second*20) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/input.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "fmt" 4 | 5 | func Input(msg string, f func(s string) bool) string { 6 | for { 7 | fmt.Printf(msg) 8 | var s string 9 | fmt.Scan(&s) 10 | if f(s) { 11 | return s 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能请求 3 | about: 为这个项目提出一个想法 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **您的功能请求是否与问题有关?请描述一下。** 11 | 对问题的清晰、简洁的描述 12 | 13 | **描述你想要的解决方案** 14 | 对你想要发生的事情进行清晰简洁的描述。 15 | 16 | **描述你考虑过的备选方案** 17 | 对您考虑过的任何替代解决方案或功能的清晰简洁的描述。 18 | -------------------------------------------------------------------------------- /pkg/recover.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "log" 5 | "runtime/debug" 6 | ) 7 | 8 | func Recover(showStack bool) { 9 | if err := recover(); err != nil { 10 | log.Println(err) 11 | if showStack { 12 | debug.PrintStack() 13 | } 14 | } 15 | } 16 | 17 | func Error2Null(_ error) { 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /pkg/ip.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | ) 7 | 8 | func LocalIPv4s() string { 9 | conn, err := net.Dial("udp", "8.8.8.8:80") 10 | if err != nil { 11 | return "" 12 | } 13 | defer conn.Close() 14 | res := conn.LocalAddr().String() 15 | res = strings.Split(res, ":")[0] 16 | return res 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '错误报告 ' 3 | about: 创建报告以帮助我们改进 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **描述错误** 11 | 清晰简洁地描述错误是什么。 12 | 13 | **重现** 14 | 重现行为的步骤: 15 | 1. xxx 16 | 2. xxx 17 | 18 | **截图** 19 | 如果适用,请添加屏幕截图以帮助解释您的问题。 20 | 21 | **您的系统信息** 22 | - 系统名称: win 23 | - 系统版本: 10 24 | 25 | 26 | **miner-proxy信息** 27 | - 版本: v0.4.0 或者 填写 启动时输出的 "版本日志:" 28 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | 3 | services: 4 | server: 5 | build: . 6 | image: miner-proxy:latest 7 | ports: 8 | - 9998:9999 9 | command: miner-proxy -l :9999 -r asia2.ethermine.org:5555 -k 12345 10 | 11 | client: 12 | build: . 13 | image: miner-proxy:latest 14 | ports: 15 | - 9999:9999 16 | command: miner-proxy -l :9999 -r server:9999 -k 12345 -c 17 | -------------------------------------------------------------------------------- /pkg/hello.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/common-nighthawk/go-figure" 7 | ) 8 | 9 | func PrintHelp() { 10 | myFigure := figure.NewFigure("Miner Proxy", "", true) 11 | myFigure.Print() 12 | // 免责声明以及项目地址 13 | fmt.Println("项目地址: https://github.com/PerrorOne/miner-proxy") 14 | fmt.Println("免责声明: 本工具只适用于测试与学习使用, 请勿将其使用到挖矿活动上!!") 15 | } 16 | 17 | func StringHelp() string { 18 | myFigure := figure.NewFigure("Miner Proxy", "", true) 19 | return myFigure.String() 20 | } 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17.6-alpine3.15 2 | 3 | ADD . /home/miner-proxy 4 | 5 | ENV GOPROXY=https://goproxy.io,direct 6 | 7 | RUN cd /home/miner-proxy && go mod tidy && cd ./cmd/miner-proxy && go build . 8 | 9 | RUN mv /home/miner-proxy/docker-entrypoint /usr/bin/ && \ 10 | mv /home/miner-proxy/cmd/miner-proxy/miner-proxy /usr/bin/ && \ 11 | rm -rf /home/miner-proxy 12 | 13 | WORKDIR /home 14 | 15 | EXPOSE 9999 16 | 17 | ENTRYPOINT ["docker-entrypoint"] 18 | 19 | CMD ["miner-proxy","-h"] 20 | -------------------------------------------------------------------------------- /app/router.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "miner-proxy/app/handles" 5 | "miner-proxy/proxy/server" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func NewRouter(app *gin.Engine) { 11 | 12 | app.GET("/api/clients/", func(c *gin.Context) { 13 | c.JSON(200, gin.H{"data": server.ClientInfo(), "code": 200}) 14 | }) 15 | 16 | app.GET("/api/server/version/", func(c *gin.Context) { 17 | c.JSON(200, gin.H{"data": c.GetString("tag"), "code": 200}) 18 | }) 19 | 20 | app.POST("/api/client/download/", handles.PackScriptFile) 21 | app.GET("/download/:fileName", handles.File) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /pkg/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Cors() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | method := c.Request.Method 12 | c.Header("Access-Control-Allow-Origin", "*") 13 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") 14 | c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") 15 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type") 16 | c.Header("Access-Control-Allow-Credentials", "true") 17 | if method == "OPTIONS" { 18 | c.AbortWithStatus(http.StatusNoContent) 19 | } 20 | c.Next() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /images/qq.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/zip.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "archive/zip" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | func Zip(srcFile string, writer io.Writer) error { 12 | archive := zip.NewWriter(writer) 13 | defer archive.Close() 14 | 15 | return filepath.Walk(srcFile, func(path string, info os.FileInfo, err error) error { 16 | if err != nil { 17 | return err 18 | } 19 | 20 | header, err := zip.FileInfoHeader(info) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | header.Name = strings.TrimPrefix(path, filepath.Dir(srcFile)+"/") 26 | header.Name = strings.ReplaceAll(header.Name, srcFile, "") 27 | if info.IsDir() { 28 | header.Name += "/" 29 | } else { 30 | header.Method = zip.Deflate 31 | } 32 | 33 | writer, err := archive.CreateHeader(header) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | if !info.IsDir() { 39 | file, err := os.Open(path) 40 | if err != nil { 41 | return err 42 | } 43 | defer file.Close() 44 | _, err = io.Copy(writer, file) 45 | } 46 | return err 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: # 每次 push 的时候触发 3 | 4 | name: Build Release 5 | jobs: 6 | release: 7 | if: startsWith(github.ref, 'refs/tags/') 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@master 11 | - uses: actions/setup-go@v2 12 | with: 13 | go-version: "1.17" 14 | - run: chmod +x ./build.sh &&./build.sh 15 | - name: Run UPX 16 | uses: crazy-max/ghaction-upx@v1 17 | with: 18 | version: latest 19 | files: | 20 | miner-proxy_darwin_amd64 21 | miner-proxy_linux_amd64 22 | miner-proxy_linux_arm 23 | miner-proxy_windows_amd64.exe 24 | args: -fq 25 | - name: Release 26 | uses: softprops/action-gh-release@v1 27 | with: 28 | files: | 29 | miner-proxy_darwin_amd64 30 | miner-proxy_linux_amd64 31 | miner-proxy_linux_arm 32 | miner-proxy_windows_amd64.exe 33 | miner-proxy_windows_arm.exe 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 dev@jpillora.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /proxy/wxPusher/pusher.go: -------------------------------------------------------------------------------- 1 | package wxPusher 2 | 3 | import ( 4 | "github.com/wxpusher/wxpusher-sdk-go" 5 | "github.com/wxpusher/wxpusher-sdk-go/model" 6 | ) 7 | 8 | type WxPusher struct { 9 | ApiKey string `json:"api_key"` 10 | } 11 | 12 | func NewPusher(apiKey string) *WxPusher { 13 | return &WxPusher{ApiKey: apiKey} 14 | } 15 | 16 | func (w *WxPusher) SendMessage(text string, uid ...string) error { 17 | m := model.NewMessage(w.ApiKey) 18 | m.UIds = uid 19 | _, err := wxpusher.SendMessage(m.SetContent(text)) 20 | return err 21 | } 22 | 23 | func (w *WxPusher) ShowQrCode() (string, error) { 24 | resp, err := wxpusher.CreateQrcode(&model.Qrcode{ 25 | AppToken: w.ApiKey, 26 | Extra: "miner-proxy", 27 | }) 28 | return resp.Url, err 29 | } 30 | 31 | func (w *WxPusher) GetAllUser() ([]model.WxUser, error) { 32 | var ( 33 | page = 1 34 | limit = 20 35 | ) 36 | var result []model.WxUser 37 | for { 38 | resp, err := wxpusher.QueryWxUser(w.ApiKey, page, limit) 39 | if err != nil { 40 | return nil, err 41 | } 42 | if len(resp.Records) == 0 { 43 | break 44 | } 45 | result = append(result, resp.Records...) 46 | page++ 47 | } 48 | return result, nil 49 | } 50 | 51 | func (w *WxPusher) GetToken() string { 52 | return w.ApiKey 53 | } 54 | -------------------------------------------------------------------------------- /pkg/util_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestGetHashRateBySize(t *testing.T) { 9 | type args struct { 10 | size int64 11 | startTime time.Duration 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want float64 17 | }{ 18 | {"1M", args{size: 1000, startTime: time.Second}, 746.2686567164179}, 19 | } 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | if got := GetHashRateBySize(tt.args.size, tt.args.startTime); got != tt.want { 23 | t.Errorf("GetHashRateBySize() = %v, want %v", got, tt.want) 24 | } 25 | }) 26 | } 27 | } 28 | 29 | func TestGetHumanizeHashRateBySize(t *testing.T) { 30 | type args struct { 31 | hashRate float64 32 | } 33 | tests := []struct { 34 | name string 35 | args args 36 | want string 37 | }{ 38 | {"1M", args{hashRate: 1}, "1.00 MH/S"}, 39 | {"100M", args{hashRate: 100}, "100.00 MH/S"}, 40 | {"1G", args{hashRate: 1000}, "1.00 G/S"}, 41 | {"10G", args{hashRate: 10000}, "10.00 G/S"}, 42 | {"100G", args{hashRate: 100000}, "100.00 G/S"}, 43 | {"1T", args{hashRate: 1000000}, "1.00 T/S"}, 44 | {"100T", args{hashRate: 100000000}, "100.00 T/S"}, 45 | } 46 | for _, tt := range tests { 47 | t.Run(tt.name, func(t *testing.T) { 48 | if got := GetHumanizeHashRateBySize(tt.args.hashRate); got != tt.want { 49 | t.Errorf("GetHumanizeHashRateBySize() = %v, want %v", got, tt.want) 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pkg/util.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "hash/crc32" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/spf13/cast" 12 | ) 13 | 14 | func Try(f func() bool, maxTry int) error { 15 | var n int 16 | for n < maxTry { 17 | if f() { 18 | return nil 19 | } 20 | n++ 21 | } 22 | return errors.New("try run function error") 23 | } 24 | 25 | func GetHashRateBySize(size int64, parseTime time.Duration) float64 { 26 | return float64(size) / parseTime.Seconds() / 1.34 27 | } 28 | 29 | func GetHumanizeHashRateBySize(hashRate float64) string { 30 | var result string 31 | switch { 32 | case hashRate < 1000: 33 | result = fmt.Sprintf("%.2f MH/S", hashRate) 34 | case hashRate < 1000000: 35 | result = fmt.Sprintf("%.2f G/S", hashRate/1000) 36 | default: 37 | result = fmt.Sprintf("%.2f T/S", hashRate/1000000) 38 | } 39 | return result 40 | } 41 | 42 | func String2Array(text string, seq string) []string { 43 | var result []string 44 | for _, v := range strings.Split(text, seq) { 45 | if v == "" { 46 | continue 47 | } 48 | result = append(result, v) 49 | } 50 | return result 51 | } 52 | 53 | func Interface2Strings(arr []interface{}) []string { 54 | var result []string 55 | for _, v := range arr { 56 | result = append(result, cast.ToString(v)) 57 | } 58 | return result 59 | } 60 | 61 | func Crc32IEEE(data []byte) uint32 { 62 | return crc32.ChecksumIEEE(data) 63 | } 64 | 65 | func Crc32IEEEString(data []byte) string { 66 | return strconv.Itoa(int(Crc32IEEE(data))) 67 | } 68 | 69 | func Crc32IEEEStr(data string) string { 70 | return Crc32IEEEString([]byte(data)) 71 | } 72 | -------------------------------------------------------------------------------- /pkg/encryption.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "fmt" 8 | "log" 9 | ) 10 | 11 | // code from https://www.jianshu.com/p/b5959b2defdb 12 | 13 | func PKCS7Padding(ciphertext []byte, blockSize int) []byte { 14 | padding := blockSize - len(ciphertext)%blockSize 15 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 16 | return append(ciphertext, padtext...) 17 | } 18 | 19 | func PKCS7UnPadding(origData []byte) []byte { 20 | length := len(origData) 21 | unpadding := int(origData[length-1]) 22 | return origData[:(length - unpadding)] 23 | } 24 | 25 | // AES加密,CBC 26 | func AesEncrypt(origData, key []byte) (crypted []byte, err error) { 27 | defer func() { 28 | if err := recover(); err != nil { 29 | log.Println("[ERROR]: AesEncrypt error: ", err) 30 | } 31 | }() 32 | block, err := aes.NewCipher(key) 33 | if err != nil { 34 | return nil, err 35 | } 36 | blockSize := block.BlockSize() 37 | origData = PKCS7Padding(origData, blockSize) 38 | blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) 39 | crypted = make([]byte, len(origData)) 40 | blockMode.CryptBlocks(crypted, origData) 41 | return 42 | } 43 | 44 | // AES解密 45 | func AesDecrypt(crypted, key []byte) (origData []byte, err error) { 46 | defer func() { 47 | if recoverErr := recover(); recoverErr != nil { 48 | err = fmt.Errorf("密钥错误") 49 | } 50 | }() 51 | 52 | block, err := aes.NewCipher(key) 53 | if err != nil { 54 | return nil, err 55 | } 56 | blockSize := block.BlockSize() 57 | blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) 58 | origData = make([]byte, len(crypted)) 59 | blockMode.CryptBlocks(origData, crypted) 60 | origData = PKCS7UnPadding(origData) 61 | return 62 | } 63 | -------------------------------------------------------------------------------- /proxy/server/client.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/panjf2000/gnet" 8 | "go.uber.org/atomic" 9 | ) 10 | 11 | type ClientDispatch struct { 12 | m sync.RWMutex 13 | remoteAddr string 14 | pool string 15 | conns sync.Map 16 | connIds []string 17 | index *atomic.Int64 18 | ClientId string 19 | startTime time.Time 20 | } 21 | 22 | type Conn struct { 23 | gnet.Conn 24 | Id string 25 | } 26 | 27 | func NewClientDispatch(clientId, pool, remoteAddr string) *ClientDispatch { 28 | return &ClientDispatch{ 29 | index: atomic.NewInt64(0), 30 | ClientId: clientId, 31 | pool: pool, 32 | remoteAddr: remoteAddr, 33 | startTime: time.Now(), 34 | } 35 | } 36 | 37 | func (c *ClientDispatch) GetConn() *Conn { 38 | c.m.RLock() 39 | defer c.m.RUnlock() 40 | if len(c.connIds) == 0 { 41 | return nil 42 | } 43 | index := c.index.Add(1) % int64(len(c.connIds)) 44 | v, ok := c.conns.Load(c.connIds[index]) 45 | if !ok { 46 | return nil 47 | } 48 | return v.(*Conn) 49 | } 50 | 51 | func (c *ClientDispatch) SetConn(id string, conn gnet.Conn) { 52 | c.conns.Store(id, &Conn{ 53 | Conn: conn, 54 | Id: id, 55 | }) 56 | c.m.Lock() 57 | defer c.m.Unlock() 58 | c.connIds = append(c.connIds, id) 59 | } 60 | 61 | func (c *ClientDispatch) DelConn(id string) { 62 | c.conns.Delete(id) 63 | c.m.Lock() 64 | defer c.m.Unlock() 65 | var conns []string 66 | for index, v := range c.connIds { 67 | if v != id { 68 | conns = append(conns, c.connIds[index]) 69 | continue 70 | } 71 | } 72 | c.connIds = conns 73 | } 74 | 75 | func (c *ClientDispatch) ConnCount() int { 76 | c.m.RLock() 77 | defer c.m.RUnlock() 78 | return len(c.connIds) 79 | } 80 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module miner-proxy 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/BurntSushi/toml v1.0.0 // indirect 7 | github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2 8 | github.com/dustin/go-humanize v1.0.0 9 | github.com/gin-gonic/gin v1.7.7 10 | github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff 11 | github.com/kardianos/service v1.2.0 12 | github.com/liushuochen/gotable v0.0.0-20220106123442-3486f065ca09 13 | github.com/wxpusher/wxpusher-sdk-go v1.0.3 14 | go.uber.org/zap v1.21.0 15 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 16 | ) 17 | 18 | require ( 19 | github.com/denisbrodbeck/machineid v1.0.1 20 | github.com/emirpasic/gods v1.12.0 21 | github.com/panjf2000/gnet v1.6.4 22 | github.com/pkg/errors v0.8.1 23 | github.com/segmentio/ksuid v1.0.4 24 | github.com/smallnest/goframe v1.0.0 25 | github.com/spf13/cast v1.4.1 26 | github.com/urfave/cli v1.22.5 27 | github.com/vmihailenco/msgpack/v5 v5.3.5 28 | go.uber.org/atomic v1.9.0 29 | ) 30 | 31 | require ( 32 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect 33 | github.com/gin-contrib/sse v0.1.0 // indirect 34 | github.com/go-playground/locales v0.13.0 // indirect 35 | github.com/go-playground/universal-translator v0.17.0 // indirect 36 | github.com/go-playground/validator/v10 v10.4.1 // indirect 37 | github.com/golang/protobuf v1.3.3 // indirect 38 | github.com/json-iterator/go v1.1.9 // indirect 39 | github.com/leodido/go-urn v1.2.0 // indirect 40 | github.com/mattn/go-isatty v0.0.12 // indirect 41 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 42 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect 43 | github.com/panjf2000/ants/v2 v2.4.7 // indirect 44 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 45 | github.com/russross/blackfriday/v2 v2.0.1 // indirect 46 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 47 | github.com/ugorji/go/codec v1.1.7 // indirect 48 | github.com/valyala/bytebufferpool v1.0.0 // indirect 49 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 50 | go.uber.org/multierr v1.7.0 // indirect 51 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect 52 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect 53 | gopkg.in/yaml.v2 v2.2.8 // indirect 54 | ) 55 | -------------------------------------------------------------------------------- /proxy/backend/pool.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "io" 5 | "miner-proxy/pkg" 6 | "net" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/pkg/errors" 12 | "github.com/spf13/cast" 13 | "go.uber.org/atomic" 14 | ) 15 | 16 | type PoolConn struct { 17 | stop sync.Once 18 | addr string 19 | conn net.Conn 20 | input <-chan []byte 21 | output chan<- []byte 22 | closed *atomic.Bool 23 | } 24 | 25 | func NewPoolConn(addr string, input <-chan []byte, output chan<- []byte) (*PoolConn, error) { 26 | p := &PoolConn{ 27 | addr: addr, 28 | input: input, 29 | output: output, 30 | closed: atomic.NewBool(false), 31 | } 32 | if err := p.init(); err != nil { 33 | return nil, err 34 | } 35 | return p, nil 36 | } 37 | 38 | func (p *PoolConn) init() error { 39 | if p.conn != nil { 40 | return nil 41 | } 42 | if p.closed.Load() { 43 | return nil 44 | } 45 | if p.input == nil || p.output == nil { 46 | return errors.New("input or output not make") 47 | } 48 | conn, err := net.DialTimeout("tcp", p.addr, time.Second*10) 49 | if err != nil { 50 | return errors.Wrapf(err, "dial mine pool %s error", p.addr) 51 | } 52 | p.conn = conn 53 | return nil 54 | } 55 | 56 | func (p *PoolConn) Close() { 57 | p.stop.Do(func() { 58 | if p.conn != nil { 59 | _ = p.conn.Close() 60 | } 61 | if p.output != nil { 62 | close(p.output) 63 | } 64 | }) 65 | p.closed.Store(true) 66 | } 67 | 68 | func (p *PoolConn) IsClosed() bool { 69 | return p.closed.Load() 70 | } 71 | 72 | func (p *PoolConn) Address() string { 73 | return p.addr 74 | } 75 | 76 | func (p *PoolConn) Start() { 77 | defer p.Close() 78 | 79 | go func() { 80 | defer p.Close() 81 | defer func() { 82 | if err := recover(); err != nil { 83 | if strings.Contains(cast.ToString(err), "send on closed channel") { 84 | return 85 | } 86 | } 87 | }() 88 | for !p.IsClosed() { 89 | data := make([]byte, 1024) 90 | n, err := p.conn.Read(data) 91 | switch err { 92 | case nil: 93 | case io.EOF: 94 | return 95 | default: 96 | pkg.Warn("read data from miner pool error %s", err) 97 | return 98 | } 99 | p.output <- data[:n] 100 | } 101 | }() 102 | 103 | for !p.IsClosed() { 104 | data, isOpen := <-p.input 105 | if !isOpen { 106 | break 107 | } 108 | if _, err := p.conn.Write(data); err != nil { 109 | pkg.Debug("write data to miner pool error: %s", err) 110 | return 111 | } 112 | } 113 | return 114 | } 115 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '19 20 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 14 | **谨慎使用0.5以上版本, 推荐使用[0.4](https://github.com/PerrorOne/miner-proxy/releases/tag/v0.4.0)/[0.35](https://github.com/PerrorOne/miner-proxy/releases/tag/v0.3.5)版本** 15 | 16 | # 📃 简介 17 | * `miner-proxy`底层基于TCP协议传输,支持stratum、openvpn、socks5、http、ssl等协议。 18 | * `miner-proxy`内置加密、数据检验算法,使得他人无法篡改、查看您的原数据。混淆算法改变了您的数据流量特征无惧机器学习检测。 19 | * `miner-proxy`内置数据同步算法,让您在网络波动剧烈情况下依旧能够正常通信,即便网络被断开也能在网络恢复的一瞬间恢复传输进度。 20 | 21 | # 🛠️ 功能 22 | - [x] 加密混淆数据, 破坏数据特征 23 | - [x] 客户端支持随机http请求, 混淆上传下载数据 24 | - [x] 服务端管理页面快捷下载客户端运行脚本 25 | - [x] 单个客户端监听多端口并支持转发多个地址 26 | - [x] [官网](https://perror.dev/miner-proxy/) 可以快速下载服务端运行脚本 27 | - [x] 临时断网自动恢复数据传输, 永不掉线 28 | - [x] 多协议支持 29 | 30 | 31 | 32 | 33 | # 🏛 官网 34 | 你可以访问 [miner-proxy](https://perrorone.github.io/miner-proxy/) 获取服务端的安装方式 35 | 36 | 如果在寻找v0.5.0以下版本的帮助文档,你可以访问 [v0.5.0以下版本文档](https://github.com/PerrorOne/miner-proxy/tree/v0.4.0) 37 | 38 | # ⚠️ 证书 39 | `miner-proxy` 需在遵循 [MIT](https://github.com/PerrorOne/miner-proxy/blob/master/LICENSE) 开源证书的前提下使用。 40 | 41 | # 🎉 JetBrains 开源证书支持 42 | miner-proxy 在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,感谢 JetBrains 公司提供的 free JetBrains Open Source license(s) 正版免费授权,在此表达我的谢意。 43 | 44 | 45 | -------------------------------------------------------------------------------- /pkg/log.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "go.uber.org/zap" 8 | "go.uber.org/zap/zapcore" 9 | "gopkg.in/natefinch/lumberjack.v2" 10 | ) 11 | 12 | var ( 13 | _log *zap.SugaredLogger 14 | ) 15 | 16 | type GormLog struct { 17 | l *zap.SugaredLogger 18 | } 19 | 20 | func InitLog(level zapcore.Level, logfile string, syncs ...io.Writer) { 21 | encoderConfig := &zapcore.EncoderConfig{ 22 | TimeKey: "time", 23 | LevelKey: "level", 24 | NameKey: "logger", 25 | MessageKey: "message", 26 | StacktraceKey: "trace", 27 | LineEnding: zapcore.DefaultLineEnding, 28 | EncodeLevel: zapcore.CapitalLevelEncoder, 29 | EncodeTime: zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05"), 30 | EncodeDuration: zapcore.MillisDurationEncoder, 31 | } 32 | 33 | atomicLevel := zap.NewAtomicLevelAt(level) 34 | var ws []zapcore.WriteSyncer 35 | 36 | ws = append(ws, zapcore.AddSync(os.Stdout)) 37 | if logfile != "" { 38 | hook := &lumberjack.Logger{ 39 | Filename: logfile, // 日志文件路径 40 | MaxSize: 100, 41 | MaxBackups: 5, 42 | MaxAge: 7, 43 | Compress: false, 44 | } 45 | ws = append(ws, zapcore.AddSync(hook)) 46 | } 47 | for _, v := range syncs { 48 | ws = append(ws, zapcore.AddSync(v)) 49 | } 50 | 51 | core := zapcore.NewCore(zapcore.NewJSONEncoder(*encoderConfig), 52 | zapcore.NewMultiWriteSyncer(ws...), atomicLevel) 53 | 54 | var options = []zap.Option{zap.AddCallerSkip(2)} 55 | if level <= zap.DebugLevel { 56 | options = append(options, zap.AddCaller()) 57 | } 58 | _log = zap.New(core, options...).Sugar() 59 | } 60 | 61 | func outByLog(level zapcore.Level, msg string, v ...interface{}) { 62 | if _log == nil { 63 | InitLog(zap.DebugLevel, "") 64 | _log.Warn("log not init, auto init log level debug") 65 | } 66 | var out = _log.Debugf 67 | 68 | switch level { 69 | case zapcore.DebugLevel: 70 | out = _log.Debugf 71 | case zapcore.InfoLevel: 72 | out = _log.Infof 73 | case zapcore.WarnLevel: 74 | out = _log.Warnf 75 | case zapcore.ErrorLevel: 76 | out = _log.Errorf 77 | case zapcore.FatalLevel: 78 | out = _log.Fatalf 79 | case zapcore.PanicLevel: 80 | out = _log.Panicf 81 | case zapcore.DPanicLevel: 82 | out = _log.DPanicf 83 | } 84 | out(msg, v...) 85 | } 86 | 87 | func Debug(msg string, v ...interface{}) { 88 | outByLog(zapcore.DebugLevel, msg, v...) 89 | } 90 | 91 | func Info(msg string, v ...interface{}) { 92 | outByLog(zapcore.InfoLevel, msg, v...) 93 | } 94 | 95 | func Warn(msg string, v ...interface{}) { 96 | outByLog(zapcore.WarnLevel, msg, v...) 97 | } 98 | 99 | func Error(msg string, v ...interface{}) { 100 | outByLog(zapcore.ErrorLevel, msg, v...) 101 | } 102 | 103 | func Panic(msg string, v ...interface{}) { 104 | outByLog(zapcore.PanicLevel, msg, v...) 105 | } 106 | 107 | func Fatal(msg string, v ...interface{}) { 108 | outByLog(zapcore.FatalLevel, msg, v...) 109 | } 110 | -------------------------------------------------------------------------------- /app/handles/download_test.go: -------------------------------------------------------------------------------- 1 | package handles 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestZipParams_build(t *testing.T) { 9 | type fields struct { 10 | ClientSystemType string 11 | ClientSystemStruct string 12 | ClientRunType string 13 | Forward []Forward 14 | } 15 | type args struct { 16 | filename string 17 | secretKey string 18 | serverPort string 19 | serverHost string 20 | } 21 | tests := []struct { 22 | name string 23 | fields fields 24 | args args 25 | want []byte 26 | }{ 27 | { 28 | name: "windows-amd64-frontend", 29 | fields: fields{ 30 | ClientSystemType: "windows", 31 | ClientSystemStruct: "amd64", 32 | ClientRunType: "frontend", 33 | Forward: []Forward{ 34 | { 35 | Port: "8080", 36 | Pool: "baidu.com:80", 37 | }, 38 | }, 39 | }, 40 | args: args{ 41 | filename: "test.file", 42 | secretKey: "123456789", 43 | serverPort: "2356", 44 | serverHost: "127.0.0.1", 45 | }, 46 | want: []byte(".\\test.file -k 123456789 -r 127.0.0.1:2356 -l :8080 -c -u baidu.com:80\npause"), 47 | }, 48 | { 49 | name: "windows-amd64-backend", 50 | fields: fields{ 51 | ClientSystemType: "windows", 52 | ClientSystemStruct: "amd64", 53 | ClientRunType: "backend", 54 | Forward: []Forward{ 55 | { 56 | Port: "8080", 57 | Pool: "baidu.com:80", 58 | }, 59 | }, 60 | }, 61 | args: args{ 62 | filename: "test.file", 63 | secretKey: "123456789", 64 | serverPort: "2356", 65 | serverHost: "127.0.0.1", 66 | }, 67 | want: []byte{64, 101, 99, 104, 111, 32, 111, 102, 102, 10, 105, 102, 32, 34, 37, 49, 34, 61, 61, 34, 104, 34, 32, 103, 111, 116, 111, 32, 98, 101, 103, 105, 110, 10, 10, 115, 116, 97, 114, 116, 32, 109, 115, 104, 116, 97, 32, 118, 98, 115, 99, 114, 105, 112, 116, 58, 99, 114, 101, 97, 116, 101, 111, 98, 106, 101, 99, 116, 40, 34, 119, 115, 99, 114, 105, 112, 116, 46, 115, 104, 101, 108, 108, 34, 41, 46, 114, 117, 110, 40, 34, 34, 34, 37, 126, 110, 120, 48, 34, 34, 32, 104, 34, 44, 48, 41, 40, 119, 105, 110, 100, 111, 119, 46, 99, 108, 111, 115, 101, 41, 38, 38, 101, 120, 105, 116, 10, 10, 58, 98, 101, 103, 105, 110, 10, 10, 46, 92, 116, 101, 115, 116, 46, 102, 105, 108, 101, 32, 45, 107, 32, 49, 50, 51, 52, 53, 54, 55, 56, 57, 32, 45, 114, 32, 49, 50, 55, 46, 48, 46, 48, 46, 49, 58, 50, 51, 53, 54, 32, 45, 108, 32, 58, 56, 48, 56, 48, 32, 45, 99, 32, 45, 117, 32, 98, 97, 105, 100, 117, 46, 99, 111, 109, 58, 56, 48}, 68 | }, 69 | { 70 | name: "windows-amd64-service", 71 | fields: fields{ 72 | ClientSystemType: "windows", 73 | ClientSystemStruct: "amd64", 74 | ClientRunType: "service", 75 | Forward: []Forward{ 76 | { 77 | Port: "8080", 78 | Pool: "baidu.com:80", 79 | }, 80 | }, 81 | }, 82 | args: args{ 83 | filename: "test.file", 84 | secretKey: "123456789", 85 | serverPort: "2356", 86 | serverHost: "127.0.0.1", 87 | }, 88 | want: []byte(".\\test.file -k 123456789 -r 127.0.0.1:2356 -l :8080 -c -u baidu.com:80 install\npause"), 89 | }, 90 | 91 | // linux 92 | { 93 | name: "linux-amd64-frontend", 94 | fields: fields{ 95 | ClientSystemType: "linux", 96 | ClientSystemStruct: "amd64", 97 | ClientRunType: "frontend", 98 | Forward: []Forward{ 99 | { 100 | Port: "8080", 101 | Pool: "baidu.com:80", 102 | }, 103 | }, 104 | }, 105 | args: args{ 106 | filename: "test.file", 107 | secretKey: "123456789", 108 | serverPort: "2356", 109 | serverHost: "127.0.0.1", 110 | }, 111 | want: []byte("sudo su\nchmod +x ./test.file\n./test.file -k 123456789 -r 127.0.0.1:2356 -l :8080 -c -u baidu.com:80"), 112 | }, 113 | { 114 | name: "linux-amd64-backend", 115 | fields: fields{ 116 | ClientSystemType: "linux", 117 | ClientSystemStruct: "amd64", 118 | ClientRunType: "backend", 119 | Forward: []Forward{ 120 | { 121 | Port: "8080", 122 | Pool: "baidu.com:80", 123 | }, 124 | }, 125 | }, 126 | args: args{ 127 | filename: "test.file", 128 | secretKey: "123456789", 129 | serverPort: "2356", 130 | serverHost: "127.0.0.1", 131 | }, 132 | want: []byte("sudo su\nchmod +x ./test.file\nnohup ./test.file -k 123456789 -r 127.0.0.1:2356 -l :8080 -c -u baidu.com:80 > ./miner-proxy.log 2>& 1&"), 133 | }, 134 | { 135 | name: "linux-amd64-service", 136 | fields: fields{ 137 | ClientSystemType: "linux", 138 | ClientSystemStruct: "amd64", 139 | ClientRunType: "service", 140 | Forward: []Forward{ 141 | { 142 | Port: "8080", 143 | Pool: "baidu.com:80", 144 | }, 145 | }, 146 | }, 147 | args: args{ 148 | filename: "test.file", 149 | secretKey: "123456789", 150 | serverPort: "2356", 151 | serverHost: "127.0.0.1", 152 | }, 153 | want: []byte("sudo su\nchmod +x ./test.file\n./test.file -k 123456789 -r 127.0.0.1:2356 -l :8080 -c -u baidu.com:80 install"), 154 | }, 155 | } 156 | for _, tt := range tests { 157 | t.Run(tt.name, func(t *testing.T) { 158 | z := ZipParams{ 159 | ClientSystemType: tt.fields.ClientSystemType, 160 | ClientSystemStruct: tt.fields.ClientSystemStruct, 161 | ClientRunType: tt.fields.ClientRunType, 162 | Forward: tt.fields.Forward, 163 | } 164 | if got := z.build(tt.args.filename, tt.args.secretKey, tt.args.serverPort, tt.args.serverHost); !reflect.DeepEqual(got, tt.want) { 165 | t.Errorf("build() = %s, want %s", got, tt.want) 166 | } 167 | }) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /app/handles/download.go: -------------------------------------------------------------------------------- 1 | package handles 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "miner-proxy/pkg" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | 14 | "github.com/gin-gonic/gin" 15 | "github.com/segmentio/ksuid" 16 | ) 17 | 18 | var ( 19 | BASEDIR = "./download" 20 | ) 21 | 22 | type ZipParams struct { 23 | ClientVersion string `json:"client_version"` 24 | ClientSystemType string `json:"client_system_type"` 25 | ClientSystemStruct string `json:"client_system_struct"` 26 | ClientRunType string `json:"client_run_type"` 27 | Forward []Forward `json:"forward"` 28 | } 29 | 30 | type Forward struct { 31 | Port string `json:"port"` 32 | Pool string `json:"pool"` 33 | } 34 | 35 | func (z ZipParams) ID() string { 36 | var ports []string 37 | for _, v := range z.Forward { 38 | ports = append(ports, v.Port, v.Pool) 39 | } 40 | name := pkg.Crc32IEEEStr(fmt.Sprintf("%s-%s-%s-%s-%s", 41 | z.ClientSystemType, z.ClientSystemStruct, z.ClientRunType, strings.Join(ports, ","), z.ClientVersion)) 42 | return name 43 | } 44 | 45 | func (z ZipParams) build(filename, secretKey, serverPort, serverHost string) []byte { 46 | var args []string 47 | 48 | if strings.ToLower(z.ClientSystemType) == "windows" { // bat 49 | filename = fmt.Sprintf(".\\%s", filename) 50 | } else { 51 | filename = fmt.Sprintf("./%s", filename) 52 | } 53 | args = append(args, filename) 54 | args = append(args, "-k", secretKey, 55 | "-r", fmt.Sprintf("%s:%s", serverHost, serverPort)) 56 | 57 | var port []string 58 | var pool []string 59 | for _, v := range z.Forward { 60 | port = append(port, fmt.Sprintf(":%s", v.Port)) 61 | pool = append(pool, v.Pool) 62 | } 63 | 64 | args = append(args, "-l", strings.Join(port, ","), "-c") 65 | args = append(args, "-u", strings.Join(pool, ",")) 66 | 67 | switch strings.ToLower(z.ClientRunType) { 68 | case "service": 69 | args = append(args, "install") 70 | if strings.ToLower(z.ClientSystemType) == "windows" { // bat 71 | args = []string{ 72 | fmt.Sprintf("%s --delete\npause", strings.Join(args, " ")), 73 | } 74 | } else { 75 | args = []string{fmt.Sprintf("chmod +x %s\n%s --delete", fmt.Sprintf("%s", filename), strings.Join(args, " "))} 76 | } 77 | case "backend": 78 | if strings.ToLower(z.ClientSystemType) == "windows" { // bat 79 | cmd := "@echo off\nif \"%1\"==\"h\" goto begin\n\nstart mshta vbscript:createobject(\"wscript.shell\").run(\"\"\"%~nx0\"\" h\",0)(window.close)&&exit\n\n:begin" 80 | 81 | args = []string{ 82 | fmt.Sprintf("%s\n\n%s", cmd, strings.Join(args, " ")), 83 | } 84 | } else { 85 | args = []string{ 86 | fmt.Sprintf("chmod +x %s\nnohup %s > ./miner-proxy.log 2>& 1&", filename, strings.Join(args, " ")), 87 | } 88 | } 89 | case "frontend": 90 | if strings.ToLower(z.ClientSystemType) == "windows" { // bat 91 | args = []string{ 92 | fmt.Sprintf("%s\npause", strings.Join(args, " ")), 93 | } 94 | } else { 95 | args = []string{fmt.Sprintf("sudo su\nchmod +x %s\n%s", filename, strings.Join(args, " "))} 96 | } 97 | } 98 | return []byte(strings.Join(args, " ")) 99 | } 100 | 101 | func (z ZipParams) Check() error { 102 | if z.ClientSystemType == "" || z.ClientSystemStruct == "" || z.ClientRunType == "" || len(z.Forward) == 0 { 103 | return fmt.Errorf("参数错误") 104 | } 105 | return nil 106 | } 107 | 108 | func PackScriptFile(c *gin.Context) { 109 | args := new(ZipParams) 110 | if err := c.BindJSON(args); err != nil { 111 | c.JSON(200, gin.H{"code": 400, "msg": "参数错误"}) 112 | return 113 | } 114 | 115 | currentDir := ksuid.New().String() 116 | dir := filepath.Join(BASEDIR, currentDir) 117 | // build download url 118 | 119 | u := "https://github.com/PerrorOne/miner-proxy/releases/download/" + args.ClientVersion 120 | if dgu := c.GetString("download_github_url"); dgu != "" { 121 | if !strings.HasSuffix(dgu, "/") { 122 | dgu = dgu + "/" 123 | } 124 | 125 | if strings.HasPrefix(dgu, "http") { 126 | u = fmt.Sprintf("%s%s", dgu, u) 127 | } 128 | } 129 | 130 | filename := fmt.Sprintf("miner-proxy_%s_%s", args.ClientSystemType, args.ClientSystemStruct) 131 | if err := os.MkdirAll(dir, 0666); err != nil { 132 | c.JSON(200, gin.H{"code": 500, "msg": fmt.Sprintf("创建临时文件失败: %s", err)}) 133 | return 134 | } 135 | if args.ClientSystemType == "windows" { 136 | filename += ".exe" 137 | } 138 | 139 | if _, err := os.Stat(filepath.Join(dir, filename)); err != nil { 140 | resp, err := http.Get(fmt.Sprintf("%s/%s", u, filename)) 141 | if err != nil { 142 | c.JSON(200, gin.H{"code": 500, "msg": fmt.Sprintf("从github中下载脚本失败: %s", err)}) 143 | return 144 | } 145 | defer resp.Body.Close() 146 | if resp.StatusCode != 200 { 147 | c.JSON(200, gin.H{"code": 500, "msg": fmt.Sprintf("没有在 %s 中发现任何脚本内容, 请检查您的设置是否有问题", fmt.Sprintf("%s/%s", u, filename))}) 148 | return 149 | } 150 | 151 | f, err := os.OpenFile(filepath.Join(dir, filename), os.O_CREATE|os.O_WRONLY, 0666) 152 | if err != nil { 153 | c.JSON(200, gin.H{"code": 500, "msg": fmt.Sprintf("创建临时文件失败: %s", err)}) 154 | return 155 | } 156 | if _, err := io.Copy(f, resp.Body); err != nil { 157 | _ = f.Close() 158 | c.JSON(200, gin.H{"code": 500, "msg": fmt.Sprintf("创建临时文件失败: %s", err)}) 159 | return 160 | } 161 | _ = f.Close() 162 | } 163 | // 构建文件 164 | data := args.build(filename, strings.TrimRight(c.GetString("secretKey"), "0"), c.GetString("server_port"), strings.Split(c.Request.Host, ":")[0]) 165 | if err := os.MkdirAll(dir, 0666); err != nil { 166 | c.JSON(200, gin.H{"code": 500, "msg": fmt.Sprintf("创建临时文件失败: %s", err)}) 167 | return 168 | } 169 | var runName = "run.bat" 170 | if args.ClientSystemType != "windows" { 171 | runName = "run.sh" 172 | } 173 | if err := ioutil.WriteFile(filepath.Join(dir, runName), data, 0666); err != nil { 174 | c.JSON(200, gin.H{"code": 500, "msg": fmt.Sprintf("写入文件失败: %s", err)}) 175 | return 176 | } 177 | // 压缩 178 | zipName := fmt.Sprintf("miner-proxy-%s.zip", args.ID()) 179 | zapF, err := os.OpenFile(filepath.Join(BASEDIR, zipName), os.O_CREATE|os.O_WRONLY, 0666) 180 | if err != nil { 181 | c.JSON(200, gin.H{"code": 500, "msg": fmt.Sprintf("创建zip文件失败: %s", err)}) 182 | return 183 | } 184 | defer zapF.Close() 185 | if err := pkg.Zip(dir, zapF); err != nil { 186 | c.JSON(200, gin.H{"code": 500, "msg": fmt.Sprintf("压缩文件失败: %s", err)}) 187 | return 188 | } 189 | downloadPath := fmt.Sprintf("/download/%s?name=%s", zipName, currentDir) 190 | downloadPath = strings.ReplaceAll(downloadPath, "\\", "/") 191 | c.JSON(200, gin.H{"code": 200, "msg": "ok", "data": downloadPath}) 192 | return 193 | } 194 | 195 | func File(c *gin.Context) { 196 | info, err := os.Stat(filepath.Join(BASEDIR, c.Param("fileName"))) 197 | if err != nil { 198 | c.AbortWithStatus(404) 199 | return 200 | } 201 | if info.IsDir() || info.Size() >= 50*1024*1024 { 202 | c.AbortWithStatus(404) 203 | return 204 | } 205 | 206 | info, err = os.Stat(filepath.Join(BASEDIR, c.Query("name"))) 207 | if err != nil { 208 | c.AbortWithStatus(404) 209 | return 210 | } 211 | 212 | if !info.IsDir() { 213 | c.AbortWithStatus(404) 214 | return 215 | } 216 | 217 | data, err := ioutil.ReadFile(filepath.Join(BASEDIR, c.Param("fileName"))) 218 | if err != nil { 219 | c.AbortWithStatus(404) 220 | return 221 | } 222 | 223 | c.DataFromReader(200, int64(len(data)), "application/x-zip-compressed", bytes.NewReader(data), map[string]string{ 224 | "Content-Disposition": fmt.Sprintf(`attachment; filename="%s"`, c.Param("fileName")), 225 | }) 226 | return 227 | } 228 | -------------------------------------------------------------------------------- /proxy/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "miner-proxy/pkg" 7 | "net" 8 | "strings" 9 | 10 | "github.com/panjf2000/gnet" 11 | "github.com/smallnest/goframe" 12 | "github.com/vmihailenco/msgpack/v5" 13 | "go.uber.org/atomic" 14 | ) 15 | 16 | type RequestType int 17 | 18 | const ( 19 | LOGIN RequestType = iota 20 | INIT 21 | DATA 22 | PING 23 | PONG 24 | ACK 25 | ERROR 26 | // CLOSE 关闭矿机的连接 27 | CLOSE 28 | ) 29 | 30 | var ( 31 | confusionCount = atomic.NewInt64(1) 32 | ) 33 | 34 | func (r RequestType) String() string { 35 | switch r { 36 | case LOGIN: 37 | return "login" 38 | case DATA: 39 | return "data" 40 | case CLOSE: 41 | return "close" 42 | case ERROR: 43 | return "error" 44 | case PING: 45 | return "ping" 46 | case PONG: 47 | return "pong" 48 | case INIT: 49 | return "init" 50 | case ACK: 51 | return "ack" 52 | } 53 | return "" 54 | } 55 | 56 | type EncryptionProtocol struct { 57 | secretKey string 58 | useSendConfusionData bool 59 | } 60 | 61 | // separateConfusionData 分离混淆的数据 62 | func (p *EncryptionProtocol) separateConfusionData(data []byte) []byte { 63 | if len(data) == 0 { 64 | return data 65 | } 66 | if !p.useSendConfusionData { 67 | return data 68 | } 69 | var result = make([]byte, 0, len(data)/2) 70 | for index, v := range data { 71 | if index%2 == 0 { 72 | continue 73 | } 74 | result = append(result, v) 75 | } 76 | return result 77 | } 78 | 79 | // buildConfusionData 构建混淆数据 80 | // 从 10 - 135中随机一个数字作为本次随机数据的长度 N 81 | // 循环 N 次, 每次从 1 - 255 中随机一个数字作为本次随机数据 82 | func (p *EncryptionProtocol) buildConfusionData() []byte { 83 | count := int(confusionCount.Inc()) 84 | number := count % 356 85 | if number < 10 { 86 | number = 10 87 | } 88 | var data = make([]byte, number) 89 | for i := 0; i < number; i++ { 90 | count = int(confusionCount.Inc()) 91 | data[i] = uint8((count % 254) + 1) 92 | } 93 | return data 94 | } 95 | 96 | // EncryptionData 构建需要发送的加密数据 97 | // 先使用 SecretKey aes 加密 data 如果 UseSendConfusionData 等于 true 98 | // 那么将会每25个字符插入 buildConfusionData 生成的随机字符 99 | func (p *EncryptionProtocol) EncryptionData(data []byte) ([]byte, error) { 100 | if p.useSendConfusionData { // 插入随机混淆数据 101 | confusionData := p.buildConfusionData() 102 | var result []byte 103 | for _, v := range data { 104 | result = append(result, confusionData[0]) 105 | confusionData = append(confusionData[1:], confusionData[0]) 106 | result = append(result, v) 107 | } 108 | data = result 109 | } 110 | if p.secretKey != "" { 111 | return pkg.AesEncrypt(data, []byte(p.secretKey)) 112 | } 113 | return data, nil 114 | } 115 | 116 | func (cc *EncryptionProtocol) DecryptData(data []byte) (result []byte, err error) { 117 | if cc.secretKey != "" { 118 | data, err = pkg.AesDecrypt(data, []byte(cc.secretKey)) 119 | if err != nil { 120 | return nil, err 121 | } 122 | } 123 | 124 | if cc.useSendConfusionData { // 去除随机混淆数据 125 | data = cc.separateConfusionData(data) 126 | } 127 | return data, nil 128 | } 129 | 130 | type Request struct { 131 | ClientId string `msgpack:"client_id"` 132 | MinerId string `msgpack:"miner_id"` 133 | Hash string `msgpack:"hash"` 134 | Type RequestType `msgpack:"type"` 135 | Data []byte `msgpack:"data"` 136 | Seq int64 `msgpack:"seq"` 137 | } 138 | 139 | func CopyRequest(req Request) Request { 140 | return Request{ 141 | ClientId: req.ClientId, 142 | MinerId: req.MinerId, 143 | Type: req.Type, 144 | } 145 | } 146 | 147 | func (r *Request) SetClientId(clientId string) *Request { 148 | r.ClientId = clientId 149 | return r 150 | } 151 | 152 | func (r *Request) SetMinerId(MinerId string) *Request { 153 | r.MinerId = MinerId 154 | return r 155 | } 156 | 157 | func (r *Request) SetData(data []byte) *Request { 158 | r.Data = data 159 | return r 160 | } 161 | 162 | func (r *Request) SetType(Type RequestType) *Request { 163 | r.Type = Type 164 | return r 165 | } 166 | 167 | func (r *Request) End() Request { 168 | return *r 169 | } 170 | 171 | func (r Request) String() string { 172 | var msg = []string{ 173 | fmt.Sprintf("clientId=%s", r.ClientId), 174 | } 175 | if r.Seq != 0 { 176 | msg = append(msg, fmt.Sprintf("seq=%d", r.Seq)) 177 | } 178 | if r.Hash != "" { 179 | msg = append(msg, fmt.Sprintf("hash=%s", r.Hash)) 180 | } 181 | msg = append(msg, fmt.Sprintf("miner_id=%s,type=%s,data_size=%d", r.MinerId, r.Type, len(r.Data))) 182 | return strings.Join(msg, ",") 183 | } 184 | 185 | type LoginRequest struct { 186 | PoolAddress string `msgpack:"pool_address"` 187 | MinerIp string `msgpack:"miner_ip"` 188 | } 189 | 190 | func Encode2Request(data []byte) (Request, error) { 191 | var result = new(Request) 192 | err := msgpack.Unmarshal(data, result) 193 | if err != nil { 194 | return *result, err 195 | } 196 | want := pkg.Crc32IEEEString(result.Data) 197 | if result.Hash != want && result.Hash != "" { 198 | return Request{}, fmt.Errorf("data hash must equal, got=%s want=%s", result.Hash, want) 199 | } 200 | return *result, err 201 | } 202 | 203 | func Decode2Byte(req Request) ([]byte, error) { 204 | req.Hash = pkg.Crc32IEEEString(req.Data) 205 | return msgpack.Marshal(req) 206 | } 207 | 208 | func Encode2LoginRequest(data []byte) (LoginRequest, error) { 209 | var result = new(LoginRequest) 210 | err := msgpack.Unmarshal(data, result) 211 | return *result, err 212 | } 213 | 214 | func DecodeLoginRequest2Byte(req LoginRequest) []byte { 215 | data, _ := msgpack.Marshal(req) 216 | return data 217 | } 218 | 219 | type GoframeProtocol struct { 220 | frame goframe.FrameConn 221 | *EncryptionProtocol 222 | } 223 | 224 | func NewGoframeProtocol(secretKey string, useSendConfusionData bool, c net.Conn) goframe.FrameConn { 225 | encoderConfig := goframe.EncoderConfig{ 226 | ByteOrder: binary.BigEndian, 227 | LengthFieldLength: 4, 228 | LengthAdjustment: 0, 229 | LengthIncludesLengthFieldLength: false, 230 | } 231 | decoderConfig := goframe.DecoderConfig{ 232 | ByteOrder: binary.BigEndian, 233 | LengthFieldOffset: 0, 234 | LengthFieldLength: 4, 235 | LengthAdjustment: 0, 236 | InitialBytesToStrip: 4, 237 | } 238 | return &GoframeProtocol{ 239 | frame: goframe.NewLengthFieldBasedFrameConn(encoderConfig, decoderConfig, c), 240 | EncryptionProtocol: &EncryptionProtocol{ 241 | secretKey: secretKey, 242 | useSendConfusionData: useSendConfusionData, 243 | }, 244 | } 245 | } 246 | 247 | func (g *GoframeProtocol) ReadFrame() ([]byte, error) { 248 | data, err := g.frame.ReadFrame() 249 | if err != nil { 250 | return nil, err 251 | } 252 | return g.DecryptData(data) 253 | } 254 | 255 | func (g *GoframeProtocol) WriteFrame(p []byte) error { 256 | p, err := g.EncryptionData(p) 257 | if err != nil { 258 | return err 259 | } 260 | return g.frame.WriteFrame(p) 261 | } 262 | 263 | func (g *GoframeProtocol) Close() error { 264 | return g.frame.Close() 265 | } 266 | 267 | func (g *GoframeProtocol) Conn() net.Conn { 268 | return g.frame.Conn() 269 | } 270 | 271 | type Protocol struct { 272 | *gnet.LengthFieldBasedFrameCodec 273 | *EncryptionProtocol 274 | } 275 | 276 | func NewProtocol(secretKey string, useSendConfusionData bool) *Protocol { 277 | encoderConfig := gnet.EncoderConfig{ 278 | ByteOrder: binary.BigEndian, 279 | LengthFieldLength: 4, 280 | LengthAdjustment: 0, 281 | LengthIncludesLengthFieldLength: false, 282 | } 283 | decoderConfig := gnet.DecoderConfig{ 284 | ByteOrder: binary.BigEndian, 285 | LengthFieldOffset: 0, 286 | LengthFieldLength: 4, 287 | LengthAdjustment: 0, 288 | InitialBytesToStrip: 4, 289 | } 290 | return &Protocol{ 291 | LengthFieldBasedFrameCodec: gnet.NewLengthFieldBasedFrameCodec(encoderConfig, decoderConfig), 292 | EncryptionProtocol: &EncryptionProtocol{ 293 | secretKey: secretKey, 294 | useSendConfusionData: useSendConfusionData, 295 | }, 296 | } 297 | } 298 | 299 | // Encode ... 300 | func (cc *Protocol) Encode(c gnet.Conn, buf []byte) ([]byte, error) { 301 | buf, err := cc.EncryptionData(buf) 302 | if err != nil { 303 | return nil, err 304 | } 305 | return cc.LengthFieldBasedFrameCodec.Encode(c, buf) 306 | } 307 | 308 | // Decode ... 309 | func (cc *Protocol) Decode(c gnet.Conn) ([]byte, error) { 310 | data, err := cc.LengthFieldBasedFrameCodec.Decode(c) 311 | if err != nil { 312 | return nil, err 313 | } 314 | return cc.DecryptData(data) 315 | } 316 | -------------------------------------------------------------------------------- /proxy/server/status.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "miner-proxy/pkg" 6 | "sort" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/dustin/go-humanize" 12 | "github.com/emirpasic/gods/sets/hashset" 13 | "github.com/liushuochen/gotable" 14 | "github.com/spf13/cast" 15 | "github.com/wxpusher/wxpusher-sdk-go/model" 16 | ) 17 | 18 | var ( 19 | pushers sync.Map 20 | m sync.Mutex 21 | ) 22 | 23 | type ClientSize struct { 24 | lastTime time.Time 25 | size int64 26 | } 27 | 28 | func init() { 29 | go func() { 30 | for { 31 | pushers.Range(func(key, value interface{}) bool { 32 | p, ok := value.(*pusher) 33 | if !ok { 34 | return true 35 | } 36 | if err := p.UpdateUsers(); err != nil { 37 | pkg.Error("更新订阅用户失败: %s", err) 38 | return true 39 | } 40 | return true 41 | }) 42 | time.Sleep(time.Second*5*60 + 30) // 每5分30秒执行一次 43 | } 44 | }() 45 | } 46 | 47 | func Show(offlineTime time.Duration) { 48 | var offlineClient = hashset.New() 49 | table, _ := gotable.Create("客户端id", "矿工id", "Ip", "传输数据大小", "连接时长", "是否在线", "客户端-服务端延迟", "矿池连接", "预估算力(仅通过流量大小判断)") 50 | for _, v := range ClientInfo() { 51 | for _, v1 := range v.Miners { 52 | if !v1.IsOnline && !v1.stopTime.IsZero() && time.Since(v1.stopTime).Seconds() >= offlineTime.Seconds() { 53 | offlineClient.Add(fmt.Sprintf("ip: %s; 池: %s; 停止时间: %s", v1.Ip, v1.Pool, v1.StopTime)) 54 | clients.Delete(v1.Id) 55 | } 56 | 57 | _ = table.AddRow(map[string]string{ 58 | "客户端id": v.ClientId, 59 | "矿工id": v1.Id, 60 | "Ip": v1.Ip, 61 | "传输数据大小": v1.Size, 62 | "连接时长": v1.ConnTime, 63 | "矿池连接": v1.Pool, 64 | "是否在线": cast.ToString(v1.IsOnline), 65 | "客户端-服务端延迟": v.Delay, 66 | }) 67 | } 68 | } 69 | fmt.Println(table.String()) 70 | if offlineClient.Size() != 0 { // 发送掉线通知 71 | SendOfflineIps(pkg.Interface2Strings(offlineClient.Values())) 72 | } 73 | } 74 | 75 | type Pusher interface { 76 | SendMessage(text string, uid ...string) error 77 | GetAllUser() ([]model.WxUser, error) 78 | GetToken() string 79 | } 80 | 81 | type pusher struct { 82 | Pusher Pusher 83 | Users []model.WxUser 84 | m sync.Mutex 85 | lastUpdateUser time.Time 86 | } 87 | 88 | func (p *pusher) UpdateUsers() error { 89 | if !p.lastUpdateUser.IsZero() && time.Since(p.lastUpdateUser).Minutes() < 5 { 90 | return nil 91 | } 92 | users, _ := p.Pusher.GetAllUser() 93 | if len(users) == 0 { 94 | return nil 95 | } 96 | m.Lock() 97 | defer m.Unlock() 98 | p.Users = users 99 | p.lastUpdateUser = time.Now() 100 | return nil 101 | } 102 | 103 | func (p *pusher) SendMessage2All(msg string) error { 104 | var uids []string 105 | for _, v := range p.Users { 106 | uids = append(uids, v.UId) 107 | } 108 | return p.Pusher.SendMessage(msg, uids...) 109 | } 110 | 111 | func AddConnectErrorCallback(p Pusher) error { 112 | obj := &pusher{ 113 | Pusher: p, 114 | } 115 | if err := obj.UpdateUsers(); err != nil { 116 | return err 117 | } 118 | pushers.Store(obj.Pusher.GetToken(), obj) 119 | return nil 120 | } 121 | 122 | func SendOfflineIps(offlineIps []string) { 123 | if len(offlineIps) <= 0 { 124 | return 125 | } 126 | var ips = strings.Join(offlineIps, "\n") 127 | if len(offlineIps) > 10 { 128 | ips = fmt.Sprintf("%s 等 %d个ip", strings.Join(offlineIps[:10], "\n"), len(offlineIps)) 129 | } 130 | ips = fmt.Sprintf("您有掉线的机器:\n%s", ips) 131 | pushers.Range(func(key, value interface{}) bool { 132 | p := value.(*pusher) 133 | pkg.Info("发送掉线通知: %+v", p.Users) 134 | if err := p.SendMessage2All(ips); err != nil { 135 | pkg.Error("发送通知失败: %s", err) 136 | } 137 | return true 138 | }) 139 | } 140 | 141 | type ClientStatus struct { 142 | Id string `json:"id"` 143 | ClientId string `json:"client_id"` 144 | Ip string `json:"ip"` 145 | Size string `json:"size"` 146 | ConnectDuration string `json:"connect_duration"` 147 | RemoteAddr string `json:"remote_addr"` 148 | IsOnline bool `json:"is_online"` 149 | // 根据传输数据大小判断预估算力 150 | HashRate string `json:"hash_rate"` 151 | Delay string `json:"delay"` 152 | connectDuration time.Duration 153 | } 154 | 155 | type ClientStatusArray []ClientStatus 156 | 157 | func (c ClientStatusArray) Len() int { 158 | return len(c) 159 | } 160 | 161 | func (c ClientStatusArray) Less(i, j int) bool { 162 | return c[i].connectDuration.Nanoseconds() > c[j].connectDuration.Nanoseconds() 163 | } 164 | 165 | // Swap swaps the elements with indexes i and j. 166 | func (c ClientStatusArray) Swap(i, j int) { 167 | c[i], c[j] = c[j], c[i] 168 | 169 | } 170 | 171 | type ClientRemoteAddr struct { 172 | Delay string `json:"delay"` 173 | ClientId string `json:"client_id"` 174 | ConnSize int `json:"conn_size"` 175 | dataSize int64 176 | DataSize string `json:"data_size"` 177 | RemoteAddr string `json:"remote_address"` 178 | Pool string `json:"pool"` 179 | SendDataCount int `json:"send_data_count"` 180 | Miners []Miner `json:"miners"` 181 | OnlineTime string `json:"online_time"` 182 | } 183 | 184 | type Miner struct { 185 | dataSize int64 186 | Id string `json:"id"` 187 | Ip string `json:"ip"` 188 | ConnTime string `json:"conn_time"` 189 | Pool string `json:"pool"` 190 | Size string `json:"size"` 191 | StopTime string `json:"stop_time"` 192 | stopTime time.Time 193 | IsOnline bool `json:"is_online"` 194 | } 195 | 196 | type ClientRemoteAddrs []*ClientRemoteAddr 197 | 198 | func (c ClientRemoteAddrs) Len() int { 199 | return len(c) 200 | } 201 | 202 | func (c ClientRemoteAddrs) Less(i, j int) bool { 203 | var iTotal int 204 | var jTotal int 205 | iTotal += c[i].ConnSize + len(c[i].Miners) + int(c[i].dataSize) 206 | jTotal += c[j].ConnSize + len(c[j].Miners) + int(c[j].dataSize) 207 | 208 | return iTotal > jTotal 209 | } 210 | 211 | // Swap swaps the elements with indexes i and j. 212 | func (c ClientRemoteAddrs) Swap(i, j int) { 213 | c[i], c[j] = c[j], c[i] 214 | 215 | } 216 | 217 | func ClientInfo() []*ClientRemoteAddr { 218 | var clientMap = make(map[string][]Miner) 219 | var clientPools = make(map[string]*hashset.Set) 220 | var clientSizeMap = make(map[string]int64) 221 | var existIpMiner = make(map[string]struct{}) 222 | clients.Range(func(key, value interface{}) bool { 223 | c := value.(*Client) 224 | if _, ok := clientPools[c.clientId]; !ok { 225 | clientPools[c.clientId] = hashset.New() 226 | } 227 | 228 | if _, ok := existIpMiner[c.ip]; ok && c.closed.Load() { 229 | pkg.Debug("删除旧的miner连接, 使用新的miner连接") 230 | clients.Delete(key) 231 | return true 232 | } 233 | 234 | if time.Since(c.startTime).Seconds() >= 30 && c.dataSize.Load() <= 0 { 235 | pkg.Debug("删除未使用的连接") 236 | clients.Delete(key) 237 | return true 238 | } 239 | 240 | clientPools[c.clientId].Add(c.address) 241 | m := &Miner{ 242 | Id: c.id, 243 | Ip: c.ip, 244 | Pool: c.pool.Address(), 245 | ConnTime: time.Since(c.startTime).String(), 246 | Size: humanize.Bytes(uint64(c.dataSize.Load())), 247 | IsOnline: !c.closed.Load(), 248 | } 249 | if !m.IsOnline && !c.stopTime.IsZero() { 250 | m.StopTime = time.Since(c.stopTime).String() 251 | m.stopTime = c.stopTime 252 | } 253 | if m.IsOnline { 254 | existIpMiner[c.ip] = struct{}{} 255 | } 256 | clientSizeMap[c.clientId] += c.dataSize.Load() 257 | clientMap[c.clientId] = append(clientMap[c.clientId], *m) 258 | return true 259 | }) 260 | 261 | var result ClientRemoteAddrs 262 | conns.Range(func(key, value interface{}) bool { 263 | cd := value.(*ClientDispatch) 264 | c := &ClientRemoteAddr{ 265 | ClientId: cast.ToString(key), 266 | ConnSize: cd.ConnCount(), 267 | Pool: cd.pool, 268 | OnlineTime: time.Since(cd.startTime).String(), 269 | RemoteAddr: cd.remoteAddr, 270 | } 271 | if _, ok := clientMap[cast.ToString(key)]; ok { 272 | c.Miners = clientMap[cast.ToString(key)] 273 | c.dataSize = clientSizeMap[cast.ToString(key)] 274 | c.DataSize = humanize.Bytes(uint64(clientSizeMap[cast.ToString(key)])) 275 | c.Pool = strings.Join(pkg.Interface2Strings(clientPools[cast.ToString(key)].Values()), ",") 276 | } 277 | v, _ := connDelay.Load(c.ClientId) 278 | if v == nil { 279 | v = Delay{} 280 | } 281 | 282 | d := v.(Delay) 283 | c.Delay = "等待检测" 284 | if d.delay.Seconds() <= 120 { 285 | c.Delay = d.delay.String() 286 | } 287 | 288 | result = append(result, c) 289 | 290 | return true 291 | }) 292 | 293 | sort.Sort(result) 294 | return result 295 | } 296 | -------------------------------------------------------------------------------- /proxy/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "miner-proxy/pkg" 7 | "miner-proxy/pkg/cache" 8 | "miner-proxy/proxy/backend" 9 | "miner-proxy/proxy/protocol" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/panjf2000/gnet" 15 | "github.com/panjf2000/gnet/pkg/pool/goroutine" 16 | "github.com/spf13/cast" 17 | "go.uber.org/atomic" 18 | ) 19 | 20 | var ( 21 | clients sync.Map 22 | conns sync.Map 23 | connId2Id sync.Map 24 | connDelay sync.Map 25 | p = goroutine.Default() 26 | ) 27 | 28 | type Delay struct { 29 | startTime time.Time 30 | delay time.Duration 31 | } 32 | 33 | type Server struct { 34 | *gnet.EventServer 35 | pool *goroutine.Pool 36 | PoolAddress string 37 | } 38 | 39 | type Client struct { 40 | id, address, ip, clientId string 41 | pool *backend.PoolConn 42 | input chan []byte 43 | output chan []byte 44 | closed *atomic.Bool 45 | stop sync.Once 46 | startTime time.Time 47 | dataSize *atomic.Int64 48 | seq *atomic.Int64 49 | stopTime time.Time 50 | ready *atomic.Bool 51 | readyChan chan struct{} 52 | lastSendReq protocol.Request 53 | } 54 | 55 | func (c *Client) IsSend(req protocol.Request) bool { 56 | key := fmt.Sprintf("send_req:%d:%s:%s", req.Seq, req.Hash, req.ClientId) 57 | if _, ok := cache.Client.Get(key); ok { 58 | return true 59 | } 60 | return false 61 | } 62 | 63 | func (c *Client) SetSend(req protocol.Request) { 64 | key := fmt.Sprintf("send_req:%d:%s:%s", req.Seq, req.Hash, req.ClientId) 65 | cache.Client.SetDefault(key, "") 66 | } 67 | 68 | func (c *Client) SetReady() { 69 | if c.ready.Load() { 70 | return 71 | } 72 | c.lastSendReq = protocol.Request{} 73 | pkg.Debug("设置 %s ready", c.id) 74 | c.ready.Store(true) 75 | c.readyChan <- struct{}{} 76 | } 77 | 78 | func (c *Client) SetWait(req protocol.Request) { 79 | pkg.Debug("设置 %s wait", c.id) 80 | c.lastSendReq = req 81 | c.ready.Store(false) 82 | } 83 | 84 | func (c *Client) Wait(timeout time.Duration) bool { 85 | if c.ready.Load() { 86 | return true 87 | } 88 | t := time.NewTicker(timeout) 89 | defer t.Stop() 90 | select { 91 | case <-c.readyChan: 92 | return true 93 | case <-t.C: 94 | return false 95 | } 96 | } 97 | 98 | func (c *Client) Init(req protocol.Request, defaultPoolAddress, clientId string) error { 99 | c.id = req.MinerId 100 | c.ready = atomic.NewBool(true) 101 | c.readyChan = make(chan struct{}) 102 | lr, err := protocol.Encode2LoginRequest(req.Data) 103 | if err != nil { 104 | return err 105 | } 106 | c.ip = lr.MinerIp 107 | c.clientId = clientId 108 | c.address = lr.PoolAddress 109 | if c.address == "" { 110 | c.address = defaultPoolAddress 111 | } 112 | c.input = make(chan []byte) 113 | c.output = make(chan []byte) 114 | c.startTime = time.Now() 115 | c.stopTime = time.Time{} 116 | c.dataSize = atomic.NewInt64(0) 117 | c.seq = atomic.NewInt64(0) 118 | c.closed = atomic.NewBool(false) 119 | p, err := backend.NewPoolConn(c.address, c.input, c.output) 120 | if err != nil { 121 | return err 122 | } 123 | c.pool = p 124 | return nil 125 | } 126 | 127 | func (c *Client) Close() { 128 | c.stop.Do(func() { 129 | c.closed.Store(true) 130 | if c.input != nil { 131 | close(c.input) 132 | } 133 | 134 | if c.pool != nil { 135 | c.pool.Close() 136 | } 137 | c.stopTime = time.Now() 138 | }) 139 | } 140 | 141 | func NewServer(address, secretKey, PoolAddress string) error { 142 | s := &Server{pool: p, PoolAddress: PoolAddress} 143 | return gnet.Serve(s, "tcp://"+address, 144 | gnet.WithReusePort(true), 145 | gnet.WithReuseAddr(true), 146 | gnet.WithCodec(protocol.NewProtocol(secretKey, true)), 147 | gnet.WithTicker(true), 148 | ) 149 | } 150 | 151 | func (ps *Server) OnClosed(c gnet.Conn, _ error) (action gnet.Action) { 152 | if c == nil { 153 | return gnet.None 154 | } 155 | clientId, ok := connId2Id.Load(c.RemoteAddr().String()) 156 | if !ok { 157 | return gnet.None 158 | } 159 | v, ok := conns.Load(clientId) 160 | if !ok { 161 | return gnet.None 162 | } 163 | cd := v.(*ClientDispatch) 164 | cd.DelConn(ps.getConnId(cast.ToString(clientId), c)) 165 | if cd.ConnCount() == 0 { 166 | conns.Delete(clientId) 167 | } 168 | connId2Id.Delete(c.RemoteAddr().String()) 169 | return gnet.None 170 | } 171 | 172 | func (ps *Server) Tick() (delay time.Duration, action gnet.Action) { 173 | var exist = make(map[string]struct{}) 174 | var clientMap = make(map[string][]string) 175 | clients.Range(func(key, value interface{}) bool { 176 | c := value.(*Client) 177 | if c.closed.Load() { 178 | return true 179 | } 180 | clientMap[c.clientId] = append(clientMap[c.clientId], cast.ToString(key)) 181 | return true 182 | }) 183 | 184 | connId2Id.Range(func(key, value interface{}) bool { 185 | v, ok := conns.Load(cast.ToString(value)) 186 | if !ok { 187 | return true 188 | } 189 | cd := v.(*ClientDispatch) 190 | if cd.ConnCount() == 0 { 191 | conns.Delete(value) 192 | connId2Id.Delete(key) 193 | return true 194 | } 195 | cd.conns.Range(func(key, value1 interface{}) bool { 196 | req := new(protocol.Request).SetType(protocol.PING). 197 | SetClientId(strings.Split(cast.ToString(key), "-")[0]) 198 | if _, ok := exist[cast.ToString(value)]; !ok { 199 | v, _ := connDelay.Load(value) 200 | if v == nil { 201 | v = Delay{} 202 | } 203 | connDelay.Store(value, Delay{ 204 | startTime: time.Now(), 205 | delay: v.(Delay).delay, 206 | }) 207 | if _, ok = clientMap[cast.ToString(value)]; ok { 208 | req.SetData([]byte(strings.Join(clientMap[cast.ToString(value)], ","))) 209 | } 210 | } 211 | 212 | data, _ := protocol.Decode2Byte(req.End()) 213 | if err := value1.(*Conn).Conn.AsyncWrite(data); err != nil { 214 | cd.DelConn(cast.ToString(key)) 215 | return true 216 | } 217 | exist[cast.ToString(value)] = struct{}{} 218 | return false 219 | }) 220 | return true 221 | }) 222 | delay = time.Second * 20 223 | return 224 | } 225 | 226 | func (ps *Server) SendToClient(req protocol.Request, maxTry int, clientId, _ string) error { 227 | return pkg.Try(func() bool { 228 | v, ok := conns.Load(clientId) 229 | if !ok { 230 | time.Sleep(time.Second) 231 | return false 232 | } 233 | cd := v.(*ClientDispatch) 234 | 235 | conn := cd.GetConn() 236 | if conn == nil { 237 | pkg.Warn("%s 没有可用的连接", clientId) 238 | time.Sleep(time.Second) 239 | return false 240 | } 241 | data, _ := protocol.Decode2Byte(req) 242 | pkg.Debug("server -> client %s", req) 243 | if err := conn.AsyncWrite(data); err != nil { 244 | pkg.Warn("server data to client error: %v", err) 245 | return false 246 | } 247 | return true 248 | }, maxTry) 249 | } 250 | 251 | func (ps *Server) getOrCreateClient(req protocol.Request) (*Client, error) { 252 | client, ok := clients.Load(req.MinerId) 253 | if ok { 254 | if !client.(*Client).closed.Load() { 255 | return client.(*Client), nil 256 | } 257 | } 258 | if req.Type != protocol.LOGIN { 259 | return nil, errors.New("need login") 260 | } 261 | c := new(Client) 262 | if err := c.Init(req, ps.PoolAddress, req.ClientId); err != nil { 263 | return nil, err 264 | } 265 | clients.Store(req.MinerId, c) 266 | _ = ps.pool.Submit(c.pool.Start) 267 | _ = ps.pool.Submit(func() { 268 | defer c.Close() 269 | t := time.NewTicker(time.Second * 5) 270 | defer t.Stop() 271 | var count int 272 | for !c.closed.Load() { 273 | if !c.Wait(time.Second * 3) { 274 | if count < 3 && len(c.lastSendReq.Data) != 0 { 275 | if err := ps.SendToClient(c.lastSendReq, 1, c.clientId, c.id); err != nil { 276 | pkg.Warn("try 10 times write to client failed") 277 | return 278 | } 279 | continue 280 | } 281 | pkg.Warn("等待 client %s ack 超时", c.id) 282 | return 283 | } 284 | select { 285 | case data, ok := <-c.output: 286 | if !ok { 287 | _ = ps.SendToClient(protocol.Request{ 288 | ClientId: c.clientId, 289 | MinerId: c.id, 290 | Type: protocol.CLOSE, 291 | }, 1, c.clientId, c.id) 292 | return 293 | } 294 | req := protocol.Request{Type: protocol.DATA, MinerId: c.id, Data: data, 295 | ClientId: c.clientId, Seq: c.seq.Inc()} 296 | data, _ = protocol.Decode2Byte(req) 297 | if err := ps.SendToClient(req, 10, c.clientId, c.id); err != nil { 298 | pkg.Warn("try 10 times write to client failed") 299 | return 300 | } 301 | c.SetWait(req) 302 | c.dataSize.Add(int64(len(data))) 303 | case <-t.C: 304 | } 305 | } 306 | }) 307 | 308 | return c, nil 309 | } 310 | 311 | func (ps *Server) getClient(MinerId string) (*Client, bool) { 312 | client, ok := clients.Load(MinerId) 313 | if !ok { 314 | return nil, false 315 | } 316 | c := client.(*Client) 317 | if c.closed.Load() { 318 | return nil, false 319 | } 320 | return c, true 321 | } 322 | 323 | func (ps *Server) ping(req protocol.Request, _ gnet.Conn) (out []byte, action gnet.Action) { 324 | v, ok := connDelay.Load(req.ClientId) 325 | if !ok { 326 | return nil, gnet.None 327 | } 328 | if v.(Delay).delay.Seconds() <= 0 { 329 | connDelay.Store(req.ClientId, Delay{ 330 | delay: time.Since(v.(Delay).startTime), 331 | }) 332 | } 333 | for _, v := range strings.Split(string(req.Data), ",") { 334 | if v == "" { 335 | continue 336 | } 337 | pkg.Debug("删除过时的矿机id: %s", v) 338 | client, ok := ps.getClient(v) 339 | if !ok { 340 | return nil, gnet.None 341 | } 342 | client.Close() 343 | } 344 | return nil, gnet.None 345 | } 346 | 347 | func (ps *Server) login(req protocol.Request, _ gnet.Conn) (out []byte, action gnet.Action) { 348 | _, err := ps.getOrCreateClient(req) 349 | if err != nil { 350 | data, _ := protocol.Decode2Byte(protocol.Request{Type: protocol.ERROR, MinerId: req.MinerId, Data: []byte(err.Error())}) 351 | return data, gnet.None 352 | } 353 | req = protocol.CopyRequest(req) 354 | pkg.Debug("server -> client %s", req) 355 | data, _ := protocol.Decode2Byte(req) 356 | return data, gnet.None 357 | } 358 | 359 | func (ps *Server) proxy(req protocol.Request, _ gnet.Conn) (out []byte, action gnet.Action) { 360 | defer func() { 361 | if err := recover(); err != nil { 362 | if strings.Contains(cast.ToString(err), "send on closed channel") { 363 | return 364 | } 365 | out, _ = protocol.Decode2Byte(protocol.Request{Type: protocol.ACK, 366 | MinerId: req.MinerId}) 367 | } 368 | }() 369 | client, ok := ps.getClient(req.MinerId) 370 | if !ok { 371 | data, _ := protocol.Decode2Byte(protocol.Request{Type: protocol.ERROR, 372 | MinerId: req.MinerId, Data: []byte("need login")}) 373 | return data, gnet.None 374 | } 375 | if !client.IsSend(req) { 376 | client.input <- req.Data 377 | client.SetSend(req) 378 | } 379 | 380 | client.dataSize.Add(int64(len(req.Data))) 381 | req = protocol.Request{Type: protocol.ACK, 382 | MinerId: req.MinerId, ClientId: req.ClientId} 383 | data, _ := protocol.Decode2Byte(req) 384 | pkg.Debug("server -> client %s", req) 385 | return data, gnet.None 386 | } 387 | 388 | func (ps *Server) getConnId(clientId string, c gnet.Conn) string { 389 | return fmt.Sprintf("%s_%s", clientId, c.RemoteAddr().String()) 390 | } 391 | 392 | func (ps *Server) init(req protocol.Request, c gnet.Conn) (out []byte, action gnet.Action) { 393 | info := strings.Split(string(req.Data), "|") 394 | if len(info) < 3 { 395 | return nil, gnet.Close 396 | } 397 | v, _ := conns.LoadOrStore(req.ClientId, NewClientDispatch(req.ClientId, info[0], info[2])) 398 | cd := v.(*ClientDispatch) 399 | 400 | cd.SetConn(ps.getConnId(req.ClientId, c), c) 401 | connId2Id.Store(c.RemoteAddr().String(), req.ClientId) 402 | var closeMiner []string 403 | for _, miner := range pkg.String2Array(info[1], ",") { 404 | if _, ok := clients.Load(miner); !ok { 405 | closeMiner = append(closeMiner, miner) 406 | } 407 | } 408 | if len(closeMiner) != 0 { 409 | data, _ := protocol.Decode2Byte(protocol.Request{ 410 | ClientId: cd.ClientId, 411 | Type: protocol.CLOSE, 412 | Data: []byte(strings.Join(closeMiner, ",")), 413 | }) 414 | return data, gnet.None 415 | } 416 | data, _ := protocol.Decode2Byte(protocol.Request{ 417 | ClientId: cd.ClientId, 418 | Type: req.Type, 419 | }) 420 | pkg.Debug("server -> client %s", req) 421 | cd.conns.Range(func(key, value interface{}) bool { 422 | return true 423 | }) 424 | return data, gnet.None 425 | } 426 | 427 | func (ps *Server) React(frame []byte, c gnet.Conn) (out []byte, action gnet.Action) { 428 | defer pkg.Recover(true) 429 | req, err := protocol.Encode2Request(frame) 430 | if err != nil { 431 | pkg.Warn("Encode2Request error %s", err) 432 | return nil, gnet.Close 433 | } 434 | pkg.Debug("server <- client %s", req.String()) 435 | switch req.Type { 436 | case protocol.INIT: 437 | return ps.init(req, c) 438 | case protocol.DATA: 439 | return ps.proxy(req, c) 440 | case protocol.LOGIN: 441 | return ps.login(req, c) 442 | case protocol.PING, protocol.PONG: 443 | return ps.ping(req, c) 444 | case protocol.CLOSE: 445 | client, ok := ps.getClient(req.MinerId) 446 | if !ok { 447 | return nil, gnet.None 448 | } 449 | client.Close() 450 | return nil, gnet.None 451 | case protocol.ACK: 452 | client, ok := ps.getClient(req.MinerId) 453 | if !ok { 454 | return nil, gnet.None 455 | } 456 | client.SetReady() 457 | return nil, gnet.None 458 | } 459 | return nil, gnet.Close 460 | } 461 | -------------------------------------------------------------------------------- /proxy/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "miner-proxy/pkg" 6 | "miner-proxy/pkg/cache" 7 | "miner-proxy/proxy/protocol" 8 | "net" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | "github.com/pkg/errors" 14 | "github.com/segmentio/ksuid" 15 | "github.com/spf13/cast" 16 | "go.uber.org/atomic" 17 | ) 18 | 19 | var ( 20 | clients sync.Map 21 | // key=client id value=*ServerManage 22 | serverManage sync.Map 23 | localIPv4 = pkg.LocalIPv4s() 24 | ) 25 | 26 | func InitServerManage(maxConn int, secretKey, serverAddress, clientId, pool string) error { 27 | s, err := NewServerManage(maxConn, secretKey, serverAddress, clientId, pool) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | go func() { 33 | for { 34 | s.m.RLock() 35 | size := len(s.connIds) 36 | s.m.RUnlock() 37 | for i := 0; i < maxConn-size; i++ { 38 | server := s.NewServer(ksuid.New().String()) 39 | if server == nil { 40 | pkg.Warn("connection to server failed") 41 | } 42 | } 43 | time.Sleep(time.Second * 5) 44 | } 45 | }() 46 | serverManage.Store(clientId, s) 47 | return nil 48 | } 49 | 50 | type ServerManage struct { 51 | secretKey, serverAddress, clientId, pool string 52 | maxConn int 53 | m sync.RWMutex 54 | conns sync.Map 55 | connIds []string 56 | index *atomic.Int64 57 | } 58 | 59 | func NewServerManage(maxConn int, secretKey, serverAddress, clientId, pool string) (*ServerManage, error) { 60 | s := &ServerManage{ 61 | secretKey: secretKey, serverAddress: serverAddress, 62 | maxConn: maxConn, index: atomic.NewInt64(0), 63 | clientId: clientId, 64 | pool: pool, 65 | } 66 | for i := 0; i < maxConn; i++ { 67 | server := s.NewServer(ksuid.New().String()) 68 | if server == nil { 69 | return nil, errors.New("connection to server error") 70 | } 71 | } 72 | return s, nil 73 | } 74 | 75 | func (s *ServerManage) DelServerConn(key string) { 76 | s.m.Lock() 77 | defer s.m.Unlock() 78 | s.conns.Delete(key) 79 | var conns []string 80 | for index, v := range s.connIds { 81 | if v == key { 82 | continue 83 | } 84 | conns = append(conns, s.connIds[index]) 85 | } 86 | s.connIds = conns 87 | return 88 | } 89 | 90 | func (s *ServerManage) SetServerConn(key string, server *Server) { 91 | s.m.Lock() 92 | defer s.m.Unlock() 93 | s.conns.Store(key, server) 94 | s.connIds = append(s.connIds, key) 95 | return 96 | } 97 | 98 | func (s *ServerManage) GetServer() *Server { 99 | s.m.RLock() 100 | connSize := len(s.connIds) 101 | if connSize == 0 { 102 | s.m.RUnlock() 103 | return nil 104 | } 105 | index := s.index.Add(1) % int64(connSize) 106 | key := s.connIds[index] 107 | s.m.RUnlock() 108 | v, ok := s.conns.Load(key) 109 | if !ok { 110 | return nil 111 | } 112 | server := v.(*Server) 113 | if server == nil || server.close.Load() { // 连接 114 | s.DelServerConn(key) 115 | key = ksuid.New().String() 116 | if server = s.NewServer(key); server == nil { 117 | return nil 118 | } 119 | s.SetServerConn(key, server) 120 | } 121 | return server 122 | } 123 | 124 | func (s *ServerManage) NewServer(id string) *Server { 125 | conn, err := net.DialTimeout("tcp", s.serverAddress, time.Second*3) 126 | if err != nil { 127 | return nil 128 | } 129 | server := &Server{ 130 | id: id, 131 | address: s.serverAddress, 132 | conn: conn, 133 | close: atomic.NewBool(false), 134 | } 135 | 136 | fc := protocol.NewGoframeProtocol(s.secretKey, true, server.conn) 137 | var miners []string 138 | clients.Range(func(key, value interface{}) bool { 139 | miners = append(miners, cast.ToString(key)) 140 | return true 141 | }) 142 | req := protocol.Request{ 143 | ClientId: s.clientId, 144 | Type: protocol.INIT, 145 | Data: []byte(fmt.Sprintf("%s|%s|%s", s.pool, strings.Join(miners, ","), localIPv4)), 146 | } 147 | 148 | data, _ := protocol.Decode2Byte(req) 149 | pkg.Debug("client -> server %s", req) 150 | if err := fc.WriteFrame(data); err != nil { 151 | return nil 152 | } 153 | 154 | go func(server *Server) { 155 | defer server.Close() 156 | defer s.DelServerConn(id) 157 | fc := protocol.NewGoframeProtocol(s.secretKey, true, server.conn) 158 | for !server.close.Load() { 159 | data, err := fc.ReadFrame() 160 | if err != nil { 161 | return 162 | } 163 | req, err := protocol.Encode2Request(data) 164 | if err != nil { 165 | return 166 | } 167 | pkg.Debug("client <- server %s", req) 168 | switch req.Type { 169 | case protocol.PING, protocol.PONG: 170 | var needClose []string 171 | for _, minerId := range strings.Split(string(req.Data), ",") { 172 | if minerId == "" { 173 | continue 174 | } 175 | if _, ok := clients.Load(minerId); !ok { // 发送删除 176 | needClose = append(needClose, minerId) 177 | } 178 | } 179 | 180 | req := protocol.Request{ 181 | ClientId: s.clientId, 182 | Type: protocol.PONG, 183 | Data: []byte(strings.Join(needClose, ",")), 184 | } 185 | data, _ := protocol.Decode2Byte(req) 186 | pkg.Debug("client -> server %s", req) 187 | if err := fc.WriteFrame(data); err != nil { 188 | return 189 | } 190 | continue 191 | case protocol.INIT: 192 | continue 193 | case protocol.CLOSE: 194 | for _, v := range pkg.String2Array(string(req.Data), ",") { 195 | value, ok := clients.Load(v) 196 | if !ok { 197 | continue 198 | } 199 | pkg.Debug("server send mandate close connection") 200 | value.(*Client).Close() 201 | } 202 | } 203 | v, ok := clients.Load(req.MinerId) 204 | if !ok { 205 | continue 206 | } 207 | v.(*Client).input <- req 208 | } 209 | }(server) 210 | 211 | s.conns.Store(id, server) 212 | 213 | s.m.RLock() 214 | defer s.m.RUnlock() 215 | s.connIds = append(s.connIds, id) 216 | return server 217 | } 218 | 219 | type Server struct { 220 | conn net.Conn 221 | close *atomic.Bool 222 | stop sync.Once 223 | id, address string 224 | } 225 | 226 | func (s *Server) Close() { 227 | s.stop.Do(func() { 228 | s.close.Store(true) 229 | if s.conn != nil { 230 | _ = s.conn.Close() 231 | } 232 | }) 233 | } 234 | 235 | type Client struct { 236 | ClientId string 237 | // id MinerId 238 | id, ip, serverAddress, secretKey, poolAddress string 239 | lconn net.Conn 240 | input chan protocol.Request 241 | closed *atomic.Bool 242 | lastSendReq protocol.Request 243 | ready *atomic.Bool 244 | readyChan chan struct{} 245 | stop sync.Once 246 | seq *atomic.Int64 247 | } 248 | 249 | func newClient(ip string, serverAddress string, secretKey string, poolAddress string, conn net.Conn, clientId string) { 250 | defer pkg.Recover(true) 251 | if strings.Contains(ip, "127.0.0.1") && localIPv4 != "" { 252 | ip = localIPv4 253 | } 254 | client := &Client{ 255 | secretKey: secretKey, 256 | serverAddress: serverAddress, 257 | ClientId: clientId, 258 | ip: ip, 259 | lconn: conn, 260 | input: make(chan protocol.Request), 261 | ready: atomic.NewBool(true), 262 | readyChan: make(chan struct{}), 263 | closed: atomic.NewBool(false), 264 | id: ksuid.New().String(), 265 | poolAddress: poolAddress, 266 | seq: atomic.NewInt64(0), 267 | } 268 | defer func() { 269 | client.Close() 270 | }() 271 | 272 | clients.Store(client.id, client) 273 | if err := client.Login(); err != nil { 274 | pkg.Warn("login to server failed %s", err) 275 | return 276 | } 277 | client.Run() 278 | return 279 | } 280 | 281 | func (c *Client) IsSend(req protocol.Request) bool { 282 | key := fmt.Sprintf("c_send_req:%d:%s:%s", req.Seq, req.Hash, req.ClientId) 283 | if _, ok := cache.Client.Get(key); ok { 284 | return true 285 | } 286 | return false 287 | } 288 | 289 | func (c *Client) SetSend(req protocol.Request) { 290 | key := fmt.Sprintf("c_send_req:%d:%s:%s", req.Seq, req.Hash, req.ClientId) 291 | cache.Client.SetDefault(key, "") 292 | } 293 | 294 | func (c *Client) Close() { 295 | c.stop.Do(func() { 296 | c.closed.Store(true) 297 | if c.lconn != nil { 298 | _ = c.lconn.Close() 299 | } 300 | clients.Delete(c.id) 301 | }) 302 | } 303 | 304 | func (c *Client) SendToServer(req protocol.Request, maxTry int, secretKey string) error { 305 | value, ok := serverManage.Load(c.ClientId) 306 | if !ok { 307 | return errors.Errorf("not found %s server connection", c.ClientId) 308 | } 309 | sm := value.(*ServerManage) 310 | return pkg.Try(func() bool { 311 | s := sm.GetServer() 312 | if s == nil { 313 | s = sm.NewServer(ksuid.New().String()) 314 | } 315 | if s == nil { 316 | pkg.Warn("没有server连接可用!也无法新建连接到server端, 检查网络是否畅通, 1S 后重试") 317 | time.Sleep(time.Second) 318 | return false 319 | } 320 | fc := protocol.NewGoframeProtocol(secretKey, true, s.conn) 321 | sendData, err := protocol.Decode2Byte(req) 322 | if err != nil { 323 | time.Sleep(time.Second) 324 | return false 325 | } 326 | pkg.Debug("client -> server %s", req) 327 | if err := fc.WriteFrame(sendData); err != nil { 328 | return false 329 | } 330 | return true 331 | }, maxTry) 332 | } 333 | 334 | func (c *Client) SendCloseToServer(secretKey string) { 335 | req := &protocol.Request{ 336 | ClientId: c.ClientId, 337 | MinerId: c.id, 338 | Type: protocol.CLOSE, 339 | } 340 | _ = c.SendToServer(req.End(), 1, secretKey) 341 | pkg.Debug("client -> server %s", req) 342 | } 343 | 344 | func (c *Client) SendDataToServer(data []byte, secretKey string) error { 345 | req := protocol.Request{ 346 | MinerId: c.id, 347 | ClientId: c.ClientId, 348 | Type: protocol.DATA, 349 | Data: data, 350 | Seq: c.seq.Inc(), 351 | } 352 | 353 | c.SetWait(req) 354 | return c.SendToServer(req, 10, secretKey) 355 | } 356 | 357 | func (c *Client) Login() error { 358 | req := protocol.Request{ 359 | ClientId: c.ClientId, 360 | MinerId: c.id, 361 | Type: protocol.LOGIN, 362 | Data: protocol.DecodeLoginRequest2Byte(protocol.LoginRequest{ 363 | PoolAddress: c.poolAddress, 364 | MinerIp: c.ip, 365 | }), 366 | } 367 | c.SetWait(req) 368 | return c.SendToServer(req, 3, c.secretKey) 369 | } 370 | 371 | func (c *Client) readServerData() { 372 | defer c.Close() 373 | t := time.NewTicker(time.Second * 5) 374 | defer t.Stop() 375 | for !c.closed.Load() { 376 | select { 377 | case req, ok := <-c.input: 378 | if !ok { 379 | return 380 | } 381 | switch req.Type { 382 | case protocol.ERROR, protocol.CLOSE: 383 | pkg.Debug("server send mandate close connection") 384 | return 385 | case protocol.LOGIN, protocol.ACK: 386 | c.SetReady() 387 | continue 388 | } 389 | if !c.IsSend(req) { 390 | if _, err := c.lconn.Write(req.Data); err != nil { 391 | pkg.Warn("write miner error: %s. close connection", err) 392 | return 393 | } 394 | c.SetSend(req) 395 | } 396 | 397 | if err := c.SendToServer(protocol.Request{ 398 | ClientId: c.ClientId, 399 | MinerId: c.id, 400 | Type: protocol.ACK, 401 | }, 2, c.secretKey); err != nil { 402 | pkg.Error("send ACK to server error: %v close connection", err) 403 | return 404 | } 405 | case <-t.C: 406 | continue 407 | } 408 | } 409 | } 410 | 411 | func (c *Client) SendTryLastRequest() { 412 | if len(c.lastSendReq.Data) != 0 { 413 | pkg.Debug("client -> server try %s", c.lastSendReq) 414 | _ = c.SendToServer(c.lastSendReq, 1, c.secretKey) 415 | } 416 | } 417 | 418 | func (c *Client) Run() { 419 | defer c.Close() 420 | go c.readServerData() 421 | 422 | var count int 423 | for !c.closed.Load() { // 从矿机从读取数据 424 | if !c.Wait(3 * time.Second) { 425 | if count < 3 { 426 | c.SendTryLastRequest() 427 | continue 428 | } 429 | pkg.Warn("%s %s 等待ack超时. close connection", c.ip, c.id) 430 | return 431 | } 432 | count = 0 433 | data := make([]byte, 1024) 434 | n, err := c.lconn.Read(data) 435 | if err != nil { 436 | pkg.Warn("miner close connection error: %v. close connection", err) 437 | c.SendCloseToServer(c.secretKey) 438 | return 439 | } 440 | 441 | if err := c.SendDataToServer(data[:n], c.secretKey); err != nil { 442 | pkg.Error("send data to server error: %s try 10 times. close connection", err) 443 | return 444 | } 445 | } 446 | } 447 | 448 | func (c *Client) SetReady() { 449 | if c.ready.Load() { 450 | return 451 | } 452 | c.lastSendReq = protocol.Request{} 453 | c.ready.Store(true) 454 | c.readyChan <- struct{}{} 455 | pkg.Debug("设置 %s ready", c.id) 456 | } 457 | 458 | func (c *Client) SetWait(req protocol.Request) { 459 | pkg.Debug("设置 %s wait", c.id) 460 | c.ready.Store(false) 461 | c.lastSendReq = req 462 | } 463 | 464 | func (c *Client) Wait(timeout time.Duration) bool { 465 | if c.ready.Load() { 466 | return true 467 | } 468 | t := time.NewTicker(timeout) 469 | defer t.Stop() 470 | select { 471 | case <-c.readyChan: 472 | return true 473 | case <-t.C: 474 | return false 475 | } 476 | } 477 | 478 | func RunClient(address, secretKey, serverAddress, poolAddress, clientId string) error { 479 | s, err := net.Listen("tcp", address) 480 | if err != nil { 481 | return err 482 | } 483 | for { 484 | conn, err := s.Accept() 485 | if err != nil { 486 | continue 487 | } 488 | pkg.Debug("nwe connect from mine %s", conn.RemoteAddr().String()) 489 | go newClient( 490 | strings.Split(conn.RemoteAddr().String(), ":")[0], 491 | serverAddress, secretKey, poolAddress, conn, clientId) 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= 3 | github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 4 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 5 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 6 | github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2 h1:tjT4Jp4gxECvsJcYpAMtW2I3YqzBTPuB67OejxXs86s= 7 | github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= 8 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 9 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= 14 | github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= 15 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 16 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 17 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 18 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 19 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 20 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 21 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 22 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 23 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 24 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 25 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 26 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 27 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 28 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 29 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 30 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 31 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 32 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 33 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 34 | github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff h1:6NvhExg4omUC9NfA+l4Oq3ibNNeJUdiAF3iBVB0PlDk= 35 | github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff/go.mod h1:ddfPX8Z28YMjiqoaJhNBzWHapTHXejnB5cDCUWDwriw= 36 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 37 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 38 | github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g= 39 | github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= 40 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 41 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 42 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 43 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 44 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 45 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 46 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 47 | github.com/liushuochen/gotable v0.0.0-20220106123442-3486f065ca09 h1:iZ0bdoqkDu/UZozph5/rGMm6pzvEPts/Mwqa0MOg7CI= 48 | github.com/liushuochen/gotable v0.0.0-20220106123442-3486f065ca09/go.mod h1:CxUy8nDvutaC1pOfaG9TRoYwdHHqoNstSPPKhomC9k8= 49 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 50 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 51 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 52 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 53 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 54 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 55 | github.com/panjf2000/ants/v2 v2.4.7 h1:MZnw2JRyTJxFwtaMtUJcwE618wKD04POWk2gwwP4E2M= 56 | github.com/panjf2000/ants/v2 v2.4.7/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= 57 | github.com/panjf2000/gnet v1.6.4 h1:GJyx3z7UvjZtPkNN4rpsW/n6tgp1TBZUHcH2xbzYJ7U= 58 | github.com/panjf2000/gnet v1.6.4/go.mod h1:KcOU7QsCaCBjeD5kyshBIamG3d9kAQtlob4Y0v0E+sc= 59 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 60 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 61 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 62 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 63 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 64 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 65 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 66 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 67 | github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= 68 | github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= 69 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 70 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 71 | github.com/smallnest/goframe v1.0.0 h1:ywsSz9P5BFiqn39w8iFDENTdqN44v+B5bp1PbCH+PVw= 72 | github.com/smallnest/goframe v1.0.0/go.mod h1:Dy8560GXrB6w5OJnVBU71dJtSyINdnqHHe6atDaZX00= 73 | github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= 74 | github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 75 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 76 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 77 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 78 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 79 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 80 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 81 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 82 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 83 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 84 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 85 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 86 | github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= 87 | github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 88 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 89 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 90 | github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= 91 | github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= 92 | github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= 93 | github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 94 | github.com/wxpusher/wxpusher-sdk-go v1.0.3 h1:KMI7yYhPps5AbiI5X2d24v0l+D/0Kzm4iiCyWBlWSKE= 95 | github.com/wxpusher/wxpusher-sdk-go v1.0.3/go.mod h1:OfMYzFcCUNECO0ycmjCUciKD1PG67LBWeMC9B1KtBnE= 96 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 97 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 98 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 99 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 100 | go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 101 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 102 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 103 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 104 | go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= 105 | go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 106 | go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 107 | go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= 108 | go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= 109 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 110 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 111 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 112 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 113 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 114 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 115 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 116 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 117 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 118 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 119 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 120 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 121 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 122 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 123 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 124 | golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 125 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 126 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 127 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 128 | golang.org/x/sys v0.0.0-20211204120058-94396e421777/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 129 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= 130 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 131 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 132 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 133 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 134 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 135 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 136 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 137 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 138 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 139 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 140 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 141 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 142 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 143 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 144 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 145 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= 146 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 147 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 148 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 149 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 150 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 151 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 152 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 153 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 154 | -------------------------------------------------------------------------------- /cmd/miner-proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | "fmt" 6 | app2 "miner-proxy/app" 7 | "miner-proxy/pkg" 8 | "miner-proxy/pkg/middleware" 9 | "miner-proxy/proxy/client" 10 | "miner-proxy/proxy/server" 11 | "miner-proxy/proxy/wxPusher" 12 | "net/http" 13 | "os" 14 | "strings" 15 | "time" 16 | 17 | "github.com/denisbrodbeck/machineid" 18 | "github.com/gin-gonic/gin" 19 | "github.com/jmcvetta/randutil" 20 | "github.com/kardianos/service" 21 | "github.com/liushuochen/gotable" 22 | "github.com/pkg/errors" 23 | "github.com/urfave/cli" 24 | "go.uber.org/zap/zapcore" 25 | ) 26 | 27 | var ( 28 | // build 时加入 29 | gitCommit string 30 | version string 31 | //go:embed web/index.html 32 | indexHtml []byte 33 | ) 34 | var ( 35 | reqeustUrls = []string{ 36 | "https://www.baidu.com/", 37 | "https://m.baidu.com/", 38 | "https://www.jianshu.com/", 39 | "https://www.jianshu.com/p/4fbdab9fb44c", 40 | "https://www.jianshu.com/p/5d25218fb22d", 41 | "https://www.tencent.com/", 42 | "https://tieba.baidu.com/", 43 | } 44 | ) 45 | 46 | type proxyService struct { 47 | args *cli.Context 48 | } 49 | 50 | func (p *proxyService) checkWxPusher(wxPusherToken string, newWxPusherUser bool) error { 51 | if len(wxPusherToken) <= 10 { 52 | pkg.Fatal("您输入的微信通知token无效, 请在 https://wxpusher.zjiecode.com/admin/main/app/appToken 中获取") 53 | } 54 | w := wxPusher.NewPusher(wxPusherToken) 55 | if newWxPusherUser { 56 | qrUrl, err := w.ShowQrCode() 57 | if err != nil { 58 | pkg.Fatal("获取二维码url失败: %s", err.Error()) 59 | } 60 | fmt.Printf("请复制网址, 在浏览器打开, 并使用微信进行扫码登陆: %s\n", qrUrl) 61 | pkg.Input("您是否扫描完成?(y/n):", func(s string) bool { 62 | if strings.ToLower(s) == "y" { 63 | return true 64 | } 65 | return false 66 | }) 67 | } 68 | 69 | users, err := w.GetAllUser() 70 | if err != nil { 71 | pkg.Fatal("获取所有的user失败: %s", err.Error()) 72 | } 73 | table, _ := gotable.Create("uid", "微信昵称") 74 | for _, v := range users { 75 | _ = table.AddRow(map[string]string{ 76 | "uid": v.UId, 77 | "微信昵称": v.NickName, 78 | }) 79 | } 80 | fmt.Println("您已经注册的微信通知用户, 如果您还需要增加用户, 请再次运行 ./miner-proxy -add_wx_user -wx tokne, 增加用户, 已经运行的程序将会在5分钟内更新订阅的用户:") 81 | fmt.Println(table.String()) 82 | if !p.args.Bool("c") && (p.args.String("l") != "" && p.args.String("k") != "") { 83 | // 不是客户端并且不是只想要增加新的用户, 就直接将wxpusher obj 注册回调 84 | if err := server.AddConnectErrorCallback(w); err != nil { 85 | pkg.Fatal("注册失败通知callback失败: %s", err.Error()) 86 | } 87 | } 88 | return nil 89 | } 90 | 91 | func (p *proxyService) startHttpServer() { 92 | gin.SetMode(gin.ReleaseMode) 93 | app := gin.New() 94 | app.Use(gin.Recovery(), middleware.Cors()) 95 | 96 | skipAuthPaths := []string{ 97 | "/download/", 98 | } 99 | 100 | if p.args.String("p") != "" { 101 | middlewareFunc := gin.BasicAuth(gin.Accounts{ 102 | "admin": p.args.String("p"), 103 | }) 104 | app.Use(func(ctx *gin.Context) { 105 | for _, v := range skipAuthPaths { 106 | if strings.HasPrefix(ctx.Request.URL.Path, v) { 107 | return 108 | } 109 | } 110 | middlewareFunc(ctx) 111 | return 112 | }) 113 | } 114 | 115 | port := strings.Split(p.args.String("l"), ":")[1] 116 | 117 | app.Use(func(ctx *gin.Context) { 118 | ctx.Set("tag", version) 119 | ctx.Set("secretKey", p.args.String("k")) 120 | ctx.Set("server_port", port) 121 | ctx.Set("download_github_url", p.args.String("g")) 122 | }) 123 | 124 | app2.NewRouter(app) 125 | 126 | app.NoRoute(func(c *gin.Context) { 127 | c.Data(http.StatusOK, "text/html", indexHtml) 128 | }) 129 | 130 | pkg.Info("web server address: %s", p.args.String("a")) 131 | if err := app.Run(p.args.String("a")); err != nil { 132 | pkg.Panic(err.Error()) 133 | } 134 | } 135 | 136 | func (p *proxyService) Start(_ service.Service) error { 137 | go p.run() 138 | return nil 139 | } 140 | 141 | func (p *proxyService) randomRequestHttp() { 142 | defer func() { 143 | sleepTime, _ := randutil.IntRange(10, 60) 144 | time.AfterFunc(time.Duration(sleepTime)*time.Second, p.randomRequestHttp) 145 | }() 146 | 147 | index, _ := randutil.IntRange(0, len(reqeustUrls)) 148 | pkg.Debug("request: %s", reqeustUrls[index]) 149 | resp, _ := (&http.Client{Timeout: time.Second * 10}).Get(reqeustUrls[index]) 150 | if resp == nil { 151 | return 152 | } 153 | _ = resp.Body.Close() 154 | } 155 | 156 | func (p *proxyService) run() { 157 | defer func() { 158 | if err := recover(); err != nil { 159 | pkg.Error("程序崩溃: %v, 重启中", err) 160 | p.run() 161 | } 162 | }() 163 | 164 | if !p.args.Bool("c") { 165 | fmt.Printf("监听端口 '%s', 默认矿池地址: '%s'\n", p.args.String("l"), p.args.String("r")) 166 | } 167 | 168 | if p.args.Bool("d") { 169 | pkg.Warn("你开启了-debug 参数, 该参数建议只有在测试时开启") 170 | } 171 | 172 | if len(p.args.String("k")) > 32 { 173 | pkg.Error("密钥必须小于等于32位!") 174 | os.Exit(1) 175 | } 176 | secretKey := p.args.String("k") 177 | for len(secretKey)%16 != 0 { 178 | secretKey += "0" 179 | } 180 | _ = p.args.Set("k", secretKey) 181 | 182 | if p.args.Bool("c") { 183 | go p.randomRequestHttp() 184 | 185 | if err := p.runClient(); err != nil { 186 | pkg.Fatal("run client failed %s", err) 187 | } 188 | 189 | select {} 190 | } 191 | 192 | if !p.args.Bool("c") { 193 | go func() { 194 | for range time.Tick(time.Second * 60) { 195 | server.Show(time.Duration(p.args.Int64("offline")) * time.Second) 196 | } 197 | }() 198 | if p.args.String("a") != "" { 199 | go p.startHttpServer() 200 | } 201 | 202 | if err := p.runServer(); err != nil { 203 | pkg.Fatal("run server failed %s", err) 204 | } 205 | } 206 | 207 | } 208 | 209 | func (p *proxyService) runClient() error { 210 | id, _ := machineid.ID() 211 | pools := strings.Split(p.args.String("u"), ",") 212 | for index, port := range strings.Split(p.args.String("l"), ",") { 213 | port = strings.ReplaceAll(port, " ", "") 214 | if port == "" { 215 | continue 216 | } 217 | if len(pools) < index { 218 | return errors.Errorf("-l参数: %s, --pool参数:%s; 必须一一对应", p.args.String("l"), p.args.String("u")) 219 | } 220 | pools[index] = strings.ReplaceAll(pools[index], " ", "") 221 | clientId := pkg.Crc32IEEEStr(fmt.Sprintf("%s-%s-%s-%s-%s", id, 222 | p.args.String("k"), p.args.String("r"), port, pools[index])) 223 | 224 | if err := pkg.Try(func() bool { 225 | if err := client.InitServerManage(p.args.Int("n"), p.args.String("k"), p.args.String("r"), clientId, pools[index]); err != nil { 226 | pkg.Error("连接到 %s 失败, 请检查到服务端的防火墙是否开放该端口, 或者检查服务端是否启动! 错误信息: %s", p.args.String("r"), err) 227 | time.Sleep(time.Second) 228 | return false 229 | } 230 | return true 231 | }, 1000); err != nil { 232 | pkg.Fatal("连接到服务器失败!") 233 | } 234 | 235 | fmt.Printf("监听端口 '%s', 矿池地址: '%s'\n", port, pools[index]) 236 | go func(pool, clientId, port string) { 237 | if err := client.RunClient(port, p.args.String("k"), p.args.String("r"), pool, clientId); err != nil { 238 | pkg.Panic("初始化%s客户端失败: %s", clientId, err) 239 | } 240 | }(pools[index], clientId, port) 241 | } 242 | return nil 243 | } 244 | 245 | func (p *proxyService) runServer() error { 246 | return server.NewServer(p.args.String("l"), p.args.String("k"), p.args.String("r")) 247 | } 248 | 249 | func (p *proxyService) Stop(_ service.Service) error { 250 | return nil 251 | } 252 | 253 | func getArgs() []string { 254 | var result []string 255 | cmds := []string{ 256 | "install", "remove", "stop", "restart", "start", "stat", "--delete", 257 | } 258 | A: 259 | for _, v := range os.Args[1:] { 260 | for _, c := range cmds { 261 | if strings.Contains(v, c) { 262 | continue A 263 | } 264 | } 265 | result = append(result, v) 266 | } 267 | return result 268 | } 269 | 270 | func Install(c *cli.Context) error { 271 | s, err := NewService(c) 272 | if err != nil { 273 | return err 274 | } 275 | status, _ := s.Status() 276 | switch status { 277 | case service.StatusStopped, service.StatusRunning: 278 | if !c.Bool("delete") { 279 | pkg.Warn("已经存在一个服务!如果你需要重新安装请在本次参数尾部加上 --delete") 280 | return nil 281 | } 282 | if status == service.StatusRunning { 283 | _ = s.Stop() 284 | } 285 | if err := s.Uninstall(); err != nil { 286 | return errors.Wrap(err, "卸载存在的服务失败") 287 | } 288 | pkg.Info("成功卸载已经存在的服务") 289 | } 290 | if err := s.Install(); err != nil { 291 | return errors.Wrap(err, "安装服务失败") 292 | } 293 | return Start(c) 294 | } 295 | 296 | func Remove(c *cli.Context) error { 297 | s, err := NewService(c) 298 | if err != nil { 299 | return err 300 | } 301 | status, _ := s.Status() 302 | switch status { 303 | case service.StatusStopped, service.StatusRunning, service.StatusUnknown: 304 | if status == service.StatusRunning { 305 | _ = s.Stop() 306 | } 307 | if err := s.Uninstall(); err != nil { 308 | return errors.Wrap(err, "卸载服务失败") 309 | } 310 | pkg.Info("成功卸载服务") 311 | } 312 | return nil 313 | } 314 | 315 | func Restart(c *cli.Context) error { 316 | s, err := NewService(c) 317 | if err != nil { 318 | return err 319 | } 320 | status, _ := s.Status() 321 | switch status { 322 | case service.StatusStopped, service.StatusRunning, service.StatusUnknown: 323 | if err := s.Restart(); err != nil { 324 | return errors.Wrap(err, "重新启动服务失败") 325 | } 326 | status, _ := s.Status() 327 | if status != service.StatusRunning { 328 | return errors.New("该服务没有正常启动, 请查看日志!") 329 | } 330 | pkg.Info("重新启动服务成功") 331 | } 332 | return nil 333 | } 334 | 335 | func Start(c *cli.Context) error { 336 | s, err := NewService(c) 337 | if err != nil { 338 | return err 339 | } 340 | status, _ := s.Status() 341 | switch status { 342 | case service.StatusRunning: 343 | pkg.Info("服务已经在运行了") 344 | return nil 345 | case service.StatusStopped, service.StatusUnknown: 346 | if err := s.Start(); err != nil { 347 | return errors.Wrap(err, "启动服务失败") 348 | } 349 | pkg.Info("启动服务成功") 350 | return nil 351 | } 352 | return errors.New("服务还没有使用install安装!") 353 | } 354 | 355 | func Stop(c *cli.Context) error { 356 | s, err := NewService(c) 357 | if err != nil { 358 | return err 359 | } 360 | status, _ := s.Status() 361 | switch status { 362 | case service.StatusRunning: 363 | if err := s.Stop(); err != nil { 364 | return errors.Wrap(err, "停止服务失败") 365 | } 366 | return nil 367 | } 368 | pkg.Info("停止服务成功") 369 | return nil 370 | } 371 | 372 | func NewService(c *cli.Context) (service.Service, error) { 373 | svcConfig := &service.Config{ 374 | Name: "miner-proxy", 375 | DisplayName: "miner-proxy", 376 | Description: "miner encryption proxy service", 377 | Arguments: getArgs(), 378 | } 379 | return service.New(&proxyService{args: c}, svcConfig) 380 | } 381 | 382 | var ( 383 | Usages = []string{ 384 | "以服务的方式安装客户端: ./miner-proxy install -c -d -l :9999 -r 服务端ip:服务端端口 -k 密钥 -u 客户端指定的矿池域名:矿池端口", 385 | "\t 以服务的方式安装服务端: ./miner-proxy install -d -l :9998 -r 默认矿池域名:默认矿池端口 -k 密钥", 386 | "\t 更新以服务的方式安装的客户端/服务端: ./miner-proxy restart", 387 | "\t 在客户端/服务端添加微信掉线通知的订阅用户: ./miner-proxy add_wx_user -w appToken", 388 | "\t 服务端增加掉线通知: ./miner-proxy install -d -l :9998 -r 默认矿池域名:默认矿池端口 -k 密钥 --w appToken", 389 | "\t linux查看以服务的方式安装的日志: journalctl -f -u miner-proxy", 390 | "\t 客户端监听多个端口并且每个端口转发不同的矿池: ./miner-proxy -l :监听端口1,:监听端口2,:监听端口3 -r 服务端ip:服务端端口 -u 矿池链接1,矿池链接2,矿池链接3 -k 密钥 -d", 391 | } 392 | ) 393 | 394 | func main() { 395 | flags := []cli.Flag{ 396 | cli.BoolFlag{ 397 | Name: "c", 398 | Usage: "标记当前运行的是客户端", 399 | }, 400 | cli.BoolFlag{ 401 | Name: "d", 402 | Usage: "是否开启debug, 如果开启了debug参数将会打印更多的日志", 403 | }, 404 | cli.StringFlag{ 405 | Name: "l", 406 | Usage: "当前程序监听的地址", 407 | Value: ":9999", 408 | }, 409 | cli.StringFlag{ 410 | Name: "r", 411 | Usage: "远程矿池地址或者远程本程序的监听地址 (default \"localhost:80\")", 412 | Value: "127.0.0.1:80", 413 | }, 414 | cli.StringFlag{ 415 | Name: "f", 416 | Usage: "将日志写入到指定的文件中", 417 | }, 418 | cli.StringFlag{ 419 | Name: "k", 420 | Usage: "数据包加密密钥, 长度小于等于32位", 421 | }, 422 | cli.StringFlag{ 423 | Name: "a", 424 | Usage: "网页查看状态端口", 425 | }, 426 | cli.StringFlag{ 427 | Name: "u", 428 | Usage: "客户端如果设置了这个参数, 那么服务端将会直接使用客户端的参数连接, 如果需要多个矿池, 请使用 -l :端口1,端口2,端口3 -P 矿池1,矿池2,矿池3", 429 | }, 430 | cli.StringFlag{ 431 | Name: "w", 432 | Usage: "掉线微信通知token, 该参数只有在服务端生效, ,请在 https://wxpusher.zjiecode.com/admin/main/app/appToken 注册获取appToken", 433 | }, 434 | cli.IntFlag{ 435 | Name: "o", 436 | Usage: "掉线多少秒之后就发送微信通知,默认4分钟", 437 | Value: 360, 438 | }, 439 | cli.StringFlag{ 440 | Name: "p", 441 | Usage: "访问网页端时的密码, 如果没有设置, 那么网页端将不需要密码即可查看!固定的用户名为:admin", 442 | }, 443 | cli.StringFlag{ 444 | Name: "g", 445 | Usage: "服务端参数, 使用指定的网址加速github下载, 示例: -g https://gh.api.99988866.xyz/ 将会使用 https://gh.api.99988866.xyz/https://github.com/PerrorOne/miner-proxy/releases/download/{tag}/miner-proxy下载", 446 | }, 447 | cli.IntFlag{ 448 | Name: "n", 449 | Value: 10, 450 | Usage: "客户端参数, 指定客户端启动时对于每一个转发端口通过多少tcp隧道连接服务端, 如果不清楚请保持默认, 不要设置小于2", 451 | }, 452 | } 453 | 454 | app := &cli.App{ 455 | Name: "miner-proxy", 456 | UsageText: strings.Join(Usages, "\n"), 457 | Commands: []cli.Command{ 458 | { 459 | Name: "install", 460 | Usage: "./miner-proxy install: 将代理安装到系统服务中, 开机自启动, 必须使用root或者管理员权限运行", 461 | Action: Install, 462 | Flags: []cli.Flag{ 463 | cli.BoolFlag{ 464 | Name: "delete", 465 | Usage: "如果已经存在一个服务, 那么直接删除后,再安装", 466 | }, 467 | }, 468 | }, 469 | { 470 | Name: "remove", 471 | Usage: "./miner-proxy remove: 将代理从系统服务中移除", 472 | Action: Remove, 473 | }, 474 | { 475 | Name: "restart", 476 | Usage: "./miner-proxy restart: 重新启动已经安装到系统服务的代理", 477 | Action: Restart, 478 | }, 479 | { 480 | Name: "start", 481 | Usage: "./miner-proxy start: 启动已经安装到系统服务的代理", 482 | Action: Start, 483 | }, 484 | { 485 | Name: "stop", 486 | Usage: "./miner-proxy start: 停止已经安装到系统服务的代理", 487 | Action: Stop, 488 | }, 489 | { 490 | Name: "add_wx_user", 491 | Usage: "./miner-proxy add_wx_user: 添加微信用户到掉线通知中", 492 | Flags: []cli.Flag{ 493 | cli.StringFlag{ 494 | Name: "w", 495 | Required: true, 496 | Usage: "掉线微信通知token, 该参数只有在服务端生效, ,请在 https://wxpusher.zjiecode.com/admin/main/app/appToken 注册获取appToken", 497 | }, 498 | }, 499 | Action: func(c *cli.Context) error { 500 | return (&proxyService{args: c}).checkWxPusher(c.String("w"), true) 501 | }, 502 | }, 503 | }, 504 | Flags: flags, 505 | Action: func(c *cli.Context) error { 506 | var logLevel = zapcore.InfoLevel 507 | if c.Bool("d") { 508 | logLevel = zapcore.DebugLevel 509 | } 510 | pkg.InitLog(logLevel, c.String("f")) 511 | if c.String("w") != "" { 512 | if err := (&proxyService{args: c}).checkWxPusher(c.String("w"), false); err != nil { 513 | pkg.Fatal(err.Error()) 514 | } 515 | } 516 | 517 | s, err := NewService(c) 518 | if err != nil { 519 | return err 520 | } 521 | return s.Run() 522 | }, 523 | } 524 | 525 | pkg.PrintHelp() 526 | fmt.Printf("版本:%s\n更新日志:%s\n", version, gitCommit) 527 | if err := app.Run(os.Args); err != nil { 528 | pkg.Fatal("启动代理失败: %s", err) 529 | } 530 | } 531 | -------------------------------------------------------------------------------- /cmd/miner-proxy/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Miner Proxy 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 自动刷新时间设置 33 | 34 | 下载客户端 35 |
36 |
37 | 38 |
39 | 本项目属于学习测试项目, 请勿用于非法活动 40 | 项目地址: miner-proxy- 41 |
42 | 43 | 44 | 107 | 108 | 109 | 123 | 124 | 125 | 417 | 418 | 419 | 420 | 421 | 422 | --------------------------------------------------------------------------------