完善客户端与服务端注册,端口分配和协议交互逻辑
This commit is contained in:
@@ -57,9 +57,9 @@ ERR: 除非有必要,否则全部 error 都使用 `errors.Wrap()` 包裹(如
|
||||
|
||||
客户端:
|
||||
|
||||
| version(1) | name_len(1) | name_buf(n) |
|
||||
|------------|-------------|-------------|
|
||||
| 版本号 | 名称长度 | 名称 |
|
||||
| id(4) |
|
||||
|--------|
|
||||
| 客户端 ID |
|
||||
|
||||
服务端:
|
||||
|
||||
|
||||
@@ -2,42 +2,43 @@ package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"proxy-server/client/core"
|
||||
"proxy-server/client/env"
|
||||
"proxy-server/client/geo"
|
||||
"proxy-server/client/report"
|
||||
"proxy-server/pkg/utils"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
"github.com/joho/godotenv"
|
||||
|
||||
_ "net/http/pprof"
|
||||
)
|
||||
|
||||
var Geo geo.Func = geo.Ipapi
|
||||
|
||||
func Start() error {
|
||||
|
||||
// 初始化环境变量
|
||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
|
||||
err := godotenv.Load()
|
||||
slog.Debug("初始化环境变量...")
|
||||
err := env.Init()
|
||||
if err != nil {
|
||||
slog.Debug("没有本地环境变量文件")
|
||||
} else {
|
||||
online := os.Getenv("ENDPOINT_ONLINE")
|
||||
if online != "" {
|
||||
core.EndpointOnline = online
|
||||
}
|
||||
offline := os.Getenv("ENDPOINT_OFFLINE")
|
||||
if offline != "" {
|
||||
core.EndpointOffline = offline
|
||||
}
|
||||
return fmt.Errorf("初始化环境变量失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取归属地
|
||||
slog.Debug("获取节点归属地...")
|
||||
err = geo.Query()
|
||||
if err != nil {
|
||||
slog.Error("获取归属地失败", "err", err)
|
||||
}
|
||||
|
||||
// 注册节点
|
||||
slog.Debug("注册节点...")
|
||||
id, host, err := report.Online(geo.Prov, geo.City, geo.Isp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("注册节点失败: %w", err)
|
||||
}
|
||||
|
||||
// 性能监控
|
||||
@@ -49,24 +50,9 @@ func Start() error {
|
||||
// }
|
||||
// }()
|
||||
|
||||
// 获取归属地
|
||||
slog.Debug("获取节点归属地...")
|
||||
prov, city, isp, err := Geo()
|
||||
if err != nil {
|
||||
slog.Error("获取归属地失败", "err", err)
|
||||
}
|
||||
|
||||
// 注册节点
|
||||
slog.Debug("注册节点...")
|
||||
host, err := report.Online(prov, city, isp)
|
||||
if err != nil {
|
||||
slog.Error("节点注册失败", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 建立控制通道
|
||||
for {
|
||||
err := ctrl(host)
|
||||
err := ctrl(id, host)
|
||||
if err != nil {
|
||||
slog.Error("建立控制通道失败", "err", err)
|
||||
slog.Info(fmt.Sprintf("%d 秒后重试", core.RetryInterval))
|
||||
@@ -75,7 +61,7 @@ func Start() error {
|
||||
}
|
||||
}
|
||||
|
||||
func ctrl(host string) error {
|
||||
func ctrl(id int32, host string) error {
|
||||
ctrlAddr := net.JoinHostPort(host, fmt.Sprintf("%d", core.FwdCtrlPort))
|
||||
dataAddr := net.JoinHostPort(host, fmt.Sprintf("%d", core.FwdDataPort))
|
||||
|
||||
@@ -86,25 +72,19 @@ func ctrl(host string) error {
|
||||
}
|
||||
defer utils.Close(conn)
|
||||
|
||||
reader := bufio.NewReader(conn)
|
||||
|
||||
// 请求转发端口
|
||||
_, err = conn.Write([]byte{core.Version})
|
||||
// 发送客户端信息
|
||||
var buf = make([]byte, 4)
|
||||
_, err = binary.Encode(buf, binary.BigEndian, id)
|
||||
if err != nil {
|
||||
return errors.New("发送版本号失败")
|
||||
return fmt.Errorf("编码客户端 ID 失败: %w", err)
|
||||
}
|
||||
|
||||
// 发送客户端名称
|
||||
nameLen := byte(len(core.Name))
|
||||
nameBuf := make([]byte, 1+nameLen)
|
||||
nameBuf[0] = nameLen
|
||||
copy(nameBuf[1:], core.Name)
|
||||
_, err = conn.Write(nameBuf)
|
||||
_, err = conn.Write(buf)
|
||||
if err != nil {
|
||||
return errors.New("发送 name 失败")
|
||||
return fmt.Errorf("发送客户端 ID 失败: %w", err)
|
||||
}
|
||||
|
||||
// 等待服务端响应
|
||||
reader := bufio.NewReader(conn)
|
||||
respBuf, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return errors.New("接收响应失败")
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
package core
|
||||
|
||||
const Version byte = 1
|
||||
const Name = "test-edge"
|
||||
|
||||
var FwdCtrlPort uint = 18080
|
||||
var FwdDataPort uint = 18081
|
||||
var RetryInterval uint = 5
|
||||
const FwdCtrlPort uint = 18080
|
||||
const FwdDataPort uint = 18081
|
||||
|
||||
var EndpointOnline = "https://api.lanhuip.com/api/edge/online"
|
||||
var EndpointOffline = "https://api.lanhuip.com/api/edge/offline"
|
||||
var EndpointGeo = "http://cip.cc"
|
||||
const RetryInterval uint = 5
|
||||
|
||||
53
client/env/env.go
vendored
Normal file
53
client/env/env.go
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
var Mode = "dev"
|
||||
var Name = "dev-edge"
|
||||
|
||||
var EndpointOnline = "https://api.lanhuip.com/api/edge/online"
|
||||
var EndpointOffline = "https://api.lanhuip.com/api/edge/offline"
|
||||
|
||||
func Init() error {
|
||||
|
||||
var env = flag.String("e", "dev", "环境变量,可选值 dev 或 prod")
|
||||
var name = flag.String("n", "", "客户端唯一标识")
|
||||
var online = flag.String("online", "", "服务注册地址")
|
||||
var offline = flag.String("offline", "", "服务注销地址")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if env != nil && *env != "" {
|
||||
if *env == "dev" || *env == "prod" {
|
||||
Mode = *env
|
||||
} else {
|
||||
return errors.New("环境变量只能为 dev 或 prod")
|
||||
}
|
||||
}
|
||||
|
||||
if name != nil && *name != "" {
|
||||
Name = *name
|
||||
} else {
|
||||
return errors.New("客户端唯一标识不能为空")
|
||||
}
|
||||
|
||||
if online != nil && *online != "" {
|
||||
EndpointOnline = *online
|
||||
}
|
||||
|
||||
if offline != nil && *offline != "" {
|
||||
EndpointOffline = *offline
|
||||
}
|
||||
|
||||
if Mode == "dev" {
|
||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
} else {
|
||||
slog.SetLogLoggerLevel(slog.LevelWarn)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package geo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/pkg/errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Cip() (prov, city, isp string, err error) {
|
||||
const endpoint = "http://cip.cc"
|
||||
|
||||
req, err := http.NewRequest("GET", endpoint, nil)
|
||||
if err != nil {
|
||||
return "", "", "", errors.Wrap(err, "创建请求失败")
|
||||
}
|
||||
req.Header.Set("User-Agent", "curl/8.9.1")
|
||||
req.Header.Set("Accept", "*/*")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", "", "", errors.Wrap(err, "请求失败")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", "", "", errors.New("请求失败,状态码: " + resp.Status)
|
||||
}
|
||||
|
||||
reader := textproto.NewReader(bufio.NewReader(resp.Body))
|
||||
_, err = reader.ReadLine()
|
||||
if err != nil {
|
||||
return "", "", "", errors.Wrap(err, "读取响应失败")
|
||||
}
|
||||
|
||||
addrLine, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return "", "", "", errors.Wrap(err, "读取响应失败")
|
||||
}
|
||||
addr := strings.Split(strings.Split(addrLine, ":")[1], " ")
|
||||
prov = strings.TrimSpace(addr[1])
|
||||
city = strings.TrimSpace(addr[2])
|
||||
|
||||
ispLine, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return "", "", "", errors.Wrap(err, "读取响应失败")
|
||||
}
|
||||
isp = strings.TrimSpace(strings.Split(ispLine, ":")[1])
|
||||
|
||||
if prov == "" || city == "" || isp == "" {
|
||||
return "", "", "", errors.New("解析数据为空")
|
||||
}
|
||||
|
||||
slog.Debug("获取归属地", "prov", prov, "city", city, "isp", isp)
|
||||
return prov, city, isp, nil
|
||||
}
|
||||
@@ -1,3 +1,157 @@
|
||||
package geo
|
||||
|
||||
type Func func() (prov, city, isp string, err error)
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"proxy-server/client/env"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
Ip string
|
||||
Prov string
|
||||
City string
|
||||
Isp string
|
||||
)
|
||||
|
||||
func Query() (err error) {
|
||||
|
||||
switch env.Mode {
|
||||
case "dev":
|
||||
err = dev()
|
||||
default:
|
||||
err = ipapi()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func dev() (err error) {
|
||||
Prov = "河南省"
|
||||
City = "郑州市"
|
||||
Isp = "电信"
|
||||
return nil
|
||||
}
|
||||
|
||||
func cip() (err error) {
|
||||
const endpoint = "http://cip.cc"
|
||||
|
||||
req, err := http.NewRequest("GET", endpoint, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建请求失败: %w", err)
|
||||
}
|
||||
req.Header.Set("User-Agent", "curl/8.9.1")
|
||||
req.Header.Set("Accept", "*/*")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("执行请求失败: %w", err)
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
_ = Body.Close()
|
||||
}(resp.Body)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("状态码: %s", resp.Status)
|
||||
}
|
||||
|
||||
reader := textproto.NewReader(bufio.NewReader(resp.Body))
|
||||
ipLine, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
Ip = strings.TrimSpace(strings.Split(ipLine, ":")[1])
|
||||
|
||||
addrLine, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
addr := strings.Split(strings.Split(addrLine, ":")[1], " ")
|
||||
Prov = strings.TrimSpace(addr[1])
|
||||
City = strings.TrimSpace(addr[2])
|
||||
|
||||
ispLine, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
Isp = strings.TrimSpace(strings.Split(ispLine, ":")[1])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ipapi() (err error) {
|
||||
const endpoint = "http://ip-api.com/json/?fields=regionName,city,as,query&lang=zh-CN"
|
||||
|
||||
resp, err := http.Get(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("执行请求失败: %w", err)
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
_ = Body.Close()
|
||||
}(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("状态码: %s", resp.Status)
|
||||
}
|
||||
|
||||
var data struct {
|
||||
RegionName string `json:"regionName"`
|
||||
City string `json:"city"`
|
||||
As string `json:"as"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
err = json.NewDecoder(resp.Body).Decode(&data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析响应失败: %w", err)
|
||||
}
|
||||
|
||||
Ip = data.Query
|
||||
Prov = data.RegionName
|
||||
City = data.City
|
||||
|
||||
var telecom = []string{"AS4134", "AS4812", "AS134419", "AS140292"}
|
||||
var unicom = []string{"AS4837", "AS17621", "AS17816"}
|
||||
var mobile = []string{
|
||||
"AS9808", "AS24444", "AS24445", "AS24547", "AS38019",
|
||||
"AS56040", "AS56041", "AS56042", "AS56044", "AS56046", "AS56047",
|
||||
"AS132525", "AS134810",
|
||||
}
|
||||
var foreign = []string{
|
||||
"AS9299",
|
||||
}
|
||||
|
||||
for _, telecomAsn := range telecom {
|
||||
if strings.HasPrefix(data.As, telecomAsn) {
|
||||
Isp = "电信"
|
||||
break
|
||||
}
|
||||
}
|
||||
if Isp == "" {
|
||||
for _, unicomAsn := range unicom {
|
||||
if strings.HasPrefix(data.As, unicomAsn) {
|
||||
Isp = "联通"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if Isp == "" {
|
||||
for _, mobileAsn := range mobile {
|
||||
if strings.HasPrefix(data.As, mobileAsn) {
|
||||
Isp = "移动"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if Isp == "" {
|
||||
for _, foreignAsn := range foreign {
|
||||
if strings.HasPrefix(data.As, foreignAsn) {
|
||||
Isp = "国外"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package geo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Ipapi() (prov, city, isp string, err error) {
|
||||
const endpoint = "http://ip-api.com/json/?fields=regionName,city,as&lang=zh-CN"
|
||||
|
||||
resp, err := http.Get(endpoint)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
var data struct {
|
||||
RegionName string `json:"regionName"`
|
||||
City string `json:"city"`
|
||||
As string `json:"as"`
|
||||
}
|
||||
err = json.NewDecoder(resp.Body).Decode(&data)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
prov = data.RegionName
|
||||
city = data.City
|
||||
|
||||
var telecom = []string{"AS4134", "AS4812", "AS134419", "AS140292"}
|
||||
var unicom = []string{"AS4837", "AS17621", "AS17816"}
|
||||
var mobile = []string{
|
||||
"AS9808", "AS24444", "AS24445", "AS24547", "AS38019",
|
||||
"AS56040", "AS56041", "AS56042", "AS56044", "AS56046", "AS56047",
|
||||
"AS132525", "AS134810",
|
||||
}
|
||||
var foreign = []string{
|
||||
"AS9299",
|
||||
}
|
||||
|
||||
for _, telecomAsn := range telecom {
|
||||
if strings.HasPrefix(data.As, telecomAsn) {
|
||||
isp = "电信"
|
||||
break
|
||||
}
|
||||
}
|
||||
if isp == "" {
|
||||
for _, unicomAsn := range unicom {
|
||||
if strings.HasPrefix(data.As, unicomAsn) {
|
||||
isp = "联通"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if isp == "" {
|
||||
for _, mobileAsn := range mobile {
|
||||
if strings.HasPrefix(data.As, mobileAsn) {
|
||||
isp = "移动"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if isp == "" {
|
||||
for _, foreignAsn := range foreign {
|
||||
if strings.HasPrefix(data.As, foreignAsn) {
|
||||
isp = "国外"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prov == "" || city == "" || isp == "" {
|
||||
return "", "", "", errors.New("解析数据为空")
|
||||
}
|
||||
|
||||
return prov, city, isp, nil
|
||||
}
|
||||
@@ -3,13 +3,15 @@ package report
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"proxy-server/client/core"
|
||||
"proxy-server/client/env"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Online(prov, city, isp string) (host string, err error) {
|
||||
func Online(prov, city, isp string) (id int32, host string, err error) {
|
||||
|
||||
var ispInt = 0
|
||||
switch isp {
|
||||
@@ -25,42 +27,43 @@ func Online(prov, city, isp string) (host string, err error) {
|
||||
"prov": prov,
|
||||
"city": city,
|
||||
"isp": ispInt,
|
||||
"name": core.Name,
|
||||
"name": env.Name,
|
||||
"version": core.Version,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", core.EndpointOnline, strings.NewReader(string(body)))
|
||||
req, err := http.NewRequest("POST", env.EndpointOnline, strings.NewReader(string(body)))
|
||||
if err != nil {
|
||||
return "", errors.New("创建节点注册请求失败")
|
||||
return 0, "", fmt.Errorf("创建请求失败: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", errors.New("节点注册失败")
|
||||
return 0, "", fmt.Errorf("执行请求失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", errors.New("节点注册失败,状态码: " + resp.Status)
|
||||
return 0, "", errors.New("状态码: " + resp.Status)
|
||||
}
|
||||
|
||||
bytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", errors.New("读取节点注册响应失败")
|
||||
return 0, "", fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
var respBody struct {
|
||||
Id int32 `json:"id"`
|
||||
Host string `json:"host"`
|
||||
}
|
||||
err = json.Unmarshal(bytes, &respBody)
|
||||
if err != nil {
|
||||
return "", errors.New("解析节点注册响应失败")
|
||||
return 0, "", fmt.Errorf("解析响应失败: %w", err)
|
||||
}
|
||||
if respBody.Host == "" {
|
||||
return "", errors.New("节点注册失败,响应体为空")
|
||||
return 0, "", errors.New("响应体为空")
|
||||
}
|
||||
|
||||
return respBody.Host, nil
|
||||
return respBody.Id, respBody.Host, nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ import "proxy-server/client"
|
||||
func main() {
|
||||
err := client.Start()
|
||||
if err != nil {
|
||||
println(err)
|
||||
println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,7 @@ import (
|
||||
func main() {
|
||||
var app = server.New()
|
||||
var err = app.Run()
|
||||
println(err)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,22 @@ package fwd
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"proxy-server/pkg/utils"
|
||||
"proxy-server/server/fwd/core"
|
||||
"proxy-server/server/fwd/dispatcher"
|
||||
"proxy-server/server/fwd/metrics"
|
||||
"proxy-server/server/fwd/repo"
|
||||
"proxy-server/server/pkg/env"
|
||||
"proxy-server/server/pkg/orm"
|
||||
"proxy-server/server/report"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type CtrlCmd struct {
|
||||
@@ -33,7 +35,7 @@ func (s *Service) startCtrlTun() error {
|
||||
// 监听端口
|
||||
ls, err := net.Listen("tcp", ":"+strconv.Itoa(int(ctrlPort)))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "监听控制通道失败")
|
||||
return fmt.Errorf("监听控制通道失败: %w", err)
|
||||
}
|
||||
defer utils.Close(ls)
|
||||
|
||||
@@ -67,58 +69,49 @@ func (s *Service) startCtrlTun() error {
|
||||
func (s *Service) processCtrlConn(conn net.Conn) error {
|
||||
reader := bufio.NewReader(conn)
|
||||
|
||||
// version
|
||||
version, err := reader.ReadByte()
|
||||
var recv = make([]byte, 4)
|
||||
_, err := io.ReadFull(reader, recv)
|
||||
if err != nil {
|
||||
_ = ctrlResp(conn, CtrlFail)
|
||||
return errors.Wrap(err, "获取版本号失败")
|
||||
return fmt.Errorf("读取客户端 ID 失败: %w", err)
|
||||
}
|
||||
var clientId = int32(binary.BigEndian.Uint32(recv))
|
||||
|
||||
// 分配端口
|
||||
var minim uint16 = 20000
|
||||
var maxim uint16 = 60000
|
||||
var fwdPort uint16
|
||||
for i := minim; i < maxim; i++ {
|
||||
var _, ok = s.fwdPortMap[i]
|
||||
if !ok {
|
||||
fwdPort = i
|
||||
s.fwdPortMap[i] = clientId
|
||||
break
|
||||
}
|
||||
}
|
||||
if fwdPort == 0 {
|
||||
return errors.New("没有可用的端口")
|
||||
}
|
||||
|
||||
// name
|
||||
nameLen, err := reader.ReadByte()
|
||||
// 报告端口分配
|
||||
if s.Config.Id == nil || *s.Config.Id == 0 {
|
||||
return errors.New("转发服务未成功注册,无法提供服务")
|
||||
}
|
||||
err = report.Assigned(s.ctx, *s.Config.Id, clientId, fwdPort)
|
||||
if err != nil {
|
||||
_ = ctrlResp(conn, CtrlFail)
|
||||
return errors.Wrap(err, "获取 name 失败")
|
||||
return fmt.Errorf("报告端口分配失败: %w", err)
|
||||
}
|
||||
nameBuf, err := utils.ReadBuffer(reader, int(nameLen))
|
||||
|
||||
// 响应客户端
|
||||
_, err = conn.Write([]byte{1})
|
||||
if err != nil {
|
||||
_ = ctrlResp(conn, CtrlFail)
|
||||
return errors.Wrap(err, "获取 name 失败")
|
||||
return fmt.Errorf("响应客户端失败: %w", err)
|
||||
}
|
||||
name := string(nameBuf)
|
||||
|
||||
if name == "" {
|
||||
_ = ctrlResp(conn, CtrlFail)
|
||||
return errors.New("客户端名称不能为空")
|
||||
}
|
||||
|
||||
// 检查客户端
|
||||
var node repo.Node
|
||||
err = orm.DB.Take(&node, &repo.Node{
|
||||
Name: name,
|
||||
}).Error
|
||||
if err != nil {
|
||||
_ = ctrlResp(conn, CtrlFail)
|
||||
return errors.Wrap(err, "查询客户端失败")
|
||||
}
|
||||
|
||||
if version != node.Version {
|
||||
_ = ctrlResp(conn, CtrlFail)
|
||||
return errors.New("客户端版本不匹配")
|
||||
}
|
||||
|
||||
err = ctrlResp(conn, CtrlDone)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "向客户端发送响应失败")
|
||||
}
|
||||
|
||||
port := node.FwdPort
|
||||
slog.Info("监听转发端口", "port", port, "client", name)
|
||||
|
||||
// 启动转发服务
|
||||
proxy, err := dispatcher.New(port)
|
||||
slog.Info("监听转发端口", "port", fwdPort, "client", clientId)
|
||||
proxy, err := dispatcher.New(fwdPort)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "创建 socks 转发服务失败")
|
||||
return err
|
||||
}
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -168,7 +161,7 @@ func (s *Service) processCtrlConn(conn net.Conn) error {
|
||||
case err == nil:
|
||||
return errors.New("客户端握手失败")
|
||||
default:
|
||||
return errors.Wrap(err, "客户端意外断开连接")
|
||||
return fmt.Errorf("客户端意外断开连接: %w", err)
|
||||
}
|
||||
case user := <-proxy.Conn:
|
||||
metrics.TimerAuth.Store(user.Conn, time.Now())
|
||||
@@ -226,15 +219,3 @@ func (s *Service) processUserConn(user *core.Conn, ctrl net.Conn) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CtrlResult byte
|
||||
|
||||
const (
|
||||
CtrlFail CtrlResult = iota
|
||||
CtrlDone
|
||||
)
|
||||
|
||||
func ctrlResp(conn net.Conn, result CtrlResult) error {
|
||||
_, err := conn.Write([]byte{byte(result)})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Id *int32
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
@@ -22,6 +23,8 @@ type Service struct {
|
||||
ctrlConnWg utils.CountWaitGroup
|
||||
dataConnWg utils.CountWaitGroup
|
||||
userConnWg utils.CountWaitGroup
|
||||
|
||||
fwdPortMap map[uint16]int32 // 转发端口映射,key 为端口号,value 为边缘节点 ID
|
||||
}
|
||||
|
||||
func New(config *Config) *Service {
|
||||
@@ -31,9 +34,10 @@ func New(config *Config) *Service {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &Service{
|
||||
Config: config,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
Config: config,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
fwdPortMap: make(map[uint16]int32),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ func Offline(ctx context.Context, name string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func Assigned(ctx context.Context, id int32, edgeId int32, port int16) (err error) {
|
||||
func Assigned(ctx context.Context, id int32, edgeId int32, port uint16) (err error) {
|
||||
_, err = repeat(ctx, env.EndpointAssigned, map[string]any{
|
||||
"proxy": id,
|
||||
"edge": edgeId,
|
||||
@@ -85,7 +85,7 @@ func repeat(ctx context.Context, endpoint string, body any) (string, error) {
|
||||
default:
|
||||
}
|
||||
|
||||
slog.Warn("服务注册失败,五秒后重试", "err", err)
|
||||
slog.Warn("服务调用失败,五秒后重试", "err", err)
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ func (s *server) Run() (err error) {
|
||||
id, err := report.Online(ctx, s.name)
|
||||
if err != nil {
|
||||
reportErrCh <- err
|
||||
return
|
||||
}
|
||||
s.id = id
|
||||
}()
|
||||
@@ -190,7 +191,9 @@ func (s *server) restore() error {
|
||||
}
|
||||
|
||||
func (s *server) startFwd(ctx context.Context) error {
|
||||
server := fwd.New(nil)
|
||||
server := fwd.New(&fwd.Config{
|
||||
Id: &s.id,
|
||||
})
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
server.Stop()
|
||||
|
||||
Reference in New Issue
Block a user