Files
proxy/client/client.go

297 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package client
import (
"bufio"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"log/slog"
"net"
_ "net/http/pprof"
"os"
"os/signal"
"proxy-server/client/core"
"proxy-server/client/env"
"proxy-server/client/geo"
"proxy-server/client/report"
"proxy-server/pkg/utils"
"time"
)
func Start() error {
// 初始化环境变量
slog.Debug("初始化环境变量...")
err := env.Init()
if err != nil {
return fmt.Errorf("初始化环境变量失败: %w", err)
}
// 获取归属地
slog.Debug("获取节点归属地...")
err = geo.Query()
if err != nil {
return fmt.Errorf("获取节点归属地失败: %w", err)
}
// 注册节点
slog.Debug("注册节点...")
id, host, err := report.Online(geo.Prov, geo.City, geo.Isp)
if err != nil {
return fmt.Errorf("注册节点失败: %w", err)
}
// 连接到网关
var ctx, cancel = signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer cancel()
var errCh = make(chan error)
go func() {
for {
err = ctrl(ctx, id, host)
if err == nil {
errCh <- nil
return
}
select {
case <-ctx.Done():
return
default:
slog.Error("建立控制通道失败", "err", err)
slog.Info(fmt.Sprintf("%d 秒后重试", core.RetryInterval))
}
select {
case <-ctx.Done():
return
case <-time.After(time.Duration(core.RetryInterval) * time.Second):
}
}
}()
// 等待退出
select {
case err := <-errCh:
if err != nil {
slog.Error("控制通道发生错误", "err", err)
}
}
// 下线节点
slog.Debug("下线节点...")
err = report.Offline()
if err != nil {
slog.Error("下线节点失败", "err", err)
}
return ctx.Err()
}
func ctrl(ctx context.Context, id int32, host string) error {
ctrlAddr := net.JoinHostPort(host, fmt.Sprintf("%d", core.FwdCtrlPort))
dataAddr := net.JoinHostPort(host, fmt.Sprintf("%d", core.FwdDataPort))
slog.Info("建立控制通道", "addr", ctrlAddr)
conn, err := net.Dial("tcp", ctrlAddr)
if err != nil {
return errors.New("连接失败")
}
defer utils.Close(conn)
var reader = bufio.NewReader(conn)
// 发送节点连接命令
err = sendOpen(reader, conn, id)
if err != nil {
return fmt.Errorf("发送节点信息失败: %w", err)
}
// 异步定时发送心跳
go func() {
ticker := time.NewTicker(time.Duration(core.HeartbeatInterval) * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case tick := <-ticker.C:
err := sendPing(reader, conn)
if err != nil {
slog.Error("发送心跳失败", "time", tick, "err", err)
}
}
}
}()
// 等待用户连接
// 读写失败后退出重连,防止后续数据读写顺序错位导致卡死控制通道
slog.Info("等待用户连接")
for loop := true; loop; {
select {
case <-ctx.Done():
loop = false
default:
// 接收 dst
tag, addr, err := onConn(reader)
if err != nil {
return fmt.Errorf("接收连接命令失败: %w", err)
}
// 建立数据通道
go func() {
err := data(dataAddr, addr, tag)
if err != nil {
slog.Error("建立数据通道失败", "err", err)
}
}()
}
}
// 发送关闭连接(不 return err否则会重新连接
err = sendClose(reader, conn)
if err != nil {
slog.Error("发送关闭连接失败", "err", err)
}
return nil
}
func data(proxy string, dest string, tag [16]byte) error {
// 向目标地址建立连接
var result = 1
var dstErr error
dst, err := net.Dial("tcp", dest)
if err != nil {
dstErr = fmt.Errorf("连接目标地址失败: %w", dstErr)
result = 0
}
defer utils.Close(dst)
// 向服务端建立连接
src, err := net.Dial("tcp", proxy)
if err != nil {
return errors.New("连接服务端失败")
}
defer utils.Close(src)
// 发送连接状态
var buf = make([]byte, 17)
copy(buf[0:16], tag[:])
buf[16] = byte(result)
_, err = src.Write(buf)
if err != nil {
return errors.New("发送连接状态失败")
}
if result == 0 {
return dstErr
}
go func() {
defer utils.Close(dst)
_, err := io.Copy(dst, src)
if err != nil && !errors.Is(err, net.ErrClosed) {
slog.Error("上行流量代理失败", "err", err)
}
}()
go func() {
defer utils.Close(src)
_, err := io.Copy(src, dst)
if err != nil && !errors.Is(err, net.ErrClosed) {
slog.Error("下行流量代理失败", "err", err)
}
}()
return nil
}
func sendOpen(reader io.Reader, writer io.Writer, id int32) error {
// 发送打开连接
var buf = make([]byte, 5)
buf[0] = 3
binary.BigEndian.PutUint32(buf[1:], uint32(id))
_, err := writer.Write(buf)
if err != nil {
return fmt.Errorf("发送打开连接失败: %w", err)
}
// 等待服务端响应
respBuf := make([]byte, 1)
_, err = io.ReadFull(reader, respBuf)
if err != nil {
return fmt.Errorf("接收服务端响应失败: %w", err)
}
if respBuf[0] != 1 {
return errors.New("服务端响应失败")
}
return nil
}
func sendClose(reader io.Reader, writer io.Writer) error {
// 发送关闭连接
_, err := writer.Write([]byte{4})
if err != nil {
return err
}
// 等待服务端响应
respBuf := make([]byte, 1)
_, err = io.ReadFull(reader, respBuf)
if err != nil {
return fmt.Errorf("接收服务端响应失败: %w", err)
}
if respBuf[0] != 1 {
return errors.New("服务端响应失败")
}
return nil
}
func sendPing(reader io.Reader, writer io.Writer) error {
_, err := writer.Write([]byte{2})
if err != nil {
return err
}
// 等待服务端响应
respBuf := make([]byte, 1)
_, err = io.ReadFull(reader, respBuf)
if err != nil {
return fmt.Errorf("接收服务端响应失败: %w", err)
}
if respBuf[0] != 1 {
return errors.New("服务端响应失败")
}
return nil
}
func onConn(reader io.Reader) (tag [16]byte, addr string, err error) {
var buf = make([]byte, 1+16+2)
_, err = io.ReadFull(reader, buf)
if err != nil {
return [16]byte{}, "", err
}
if buf[0] != 5 {
return [16]byte{}, "", errors.New("命令错误")
}
tag = [16]byte(buf[1:17])
var addrLen = binary.BigEndian.Uint16(buf[17:19])
var addrBuf = make([]byte, addrLen)
_, err = io.ReadFull(reader, addrBuf)
if err != nil {
return [16]byte{}, "", err
}
addr = string(addrBuf)
return tag, addr, nil
}