├── 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 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
60 |
61 |
62 |
67 |
68 |
69 |
70 |
74 |
75 |
76 |
77 |
78 |
83 |
84 |
85 |
86 |
96 |
97 |
98 |
104 |
105 |
106 |
107 |
108 |
109 |
123 |
124 |
125 |
417 |
418 |
419 |
420 |
421 |
422 |
--------------------------------------------------------------------------------