feat: implement initial client-server tunnel with WebSocket, custom protocol, and comprehensive proxy handling.
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 56s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 1m40s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 1m48s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m24s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m47s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 1m16s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 1m42s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m38s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m53s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 1m25s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m55s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m18s

This commit is contained in:
2026-01-19 21:11:10 +08:00
parent 27f958b981
commit 294718dd7e
10 changed files with 396 additions and 57 deletions

View File

@@ -0,0 +1,30 @@
package config
import (
"os"
"gopkg.in/yaml.v3"
)
// ClientConfig 客户端配置
type ClientConfig struct {
Server string `yaml:"server"` // 服务器地址
Token string `yaml:"token"` // 认证 Token
ID string `yaml:"id"` // 客户端 ID
NoTLS bool `yaml:"no_tls"` // 禁用 TLS
}
// LoadClientConfig 加载客户端配置
func LoadClientConfig(path string) (*ClientConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cfg ClientConfig
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}

View File

@@ -30,37 +30,42 @@ const (
reconnectDelay = 5 * time.Second
disconnectDelay = 3 * time.Second
udpBufferSize = 65535
idFileName = ".gotunnel_id"
idFileName = "id"
)
// Client 隧道客户端
type Client struct {
ServerAddr string
Token string
ID string
TLSEnabled bool
TLSConfig *tls.Config
DataDir string // 数据目录
session *yamux.Session
rules []protocol.ProxyRule
mu sync.RWMutex
pluginRegistry *plugin.Registry
runningPlugins map[string]plugin.ClientPlugin
versionStore *PluginVersionStore
pluginMu sync.RWMutex
logger *Logger // 日志收集器
ServerAddr string
Token string
ID string
TLSEnabled bool
TLSConfig *tls.Config
DataDir string // 数据目录
session *yamux.Session
rules []protocol.ProxyRule
mu sync.RWMutex
pluginRegistry *plugin.Registry
runningPlugins map[string]plugin.ClientPlugin
versionStore *PluginVersionStore
pluginMu sync.RWMutex
logger *Logger // 日志收集器
}
// NewClient 创建客户端
func NewClient(serverAddr, token, id string) *Client {
if id == "" {
id = loadClientID()
}
// 默认数据目录
home, _ := os.UserHomeDir()
dataDir := filepath.Join(home, ".gotunnel")
// 确保数据目录存在
if err := os.MkdirAll(dataDir, 0755); err != nil {
log.Printf("[Client] Failed to create data dir: %v", err)
}
if id == "" {
id = loadClientID(dataDir)
}
// 初始化日志收集器
logger, err := NewLogger(dataDir)
if err != nil {
@@ -88,17 +93,13 @@ func (c *Client) InitVersionStore() error {
}
// getIDFilePath 获取 ID 文件路径
func getIDFilePath() string {
home, err := os.UserHomeDir()
if err != nil {
return idFileName
}
return filepath.Join(home, idFileName)
func getIDFilePath(dataDir string) string {
return filepath.Join(dataDir, idFileName)
}
// loadClientID 从本地文件加载客户端 ID
func loadClientID() string {
data, err := os.ReadFile(getIDFilePath())
func loadClientID(dataDir string) string {
data, err := os.ReadFile(getIDFilePath(dataDir))
if err != nil {
return ""
}
@@ -106,8 +107,8 @@ func loadClientID() string {
}
// saveClientID 保存客户端 ID 到本地文件
func saveClientID(id string) {
if err := os.WriteFile(getIDFilePath(), []byte(id), 0600); err != nil {
func saveClientID(dataDir, id string) {
if err := os.WriteFile(getIDFilePath(dataDir), []byte(id), 0600); err != nil {
log.Printf("[Client] Failed to save client ID: %v", err)
}
}
@@ -201,7 +202,7 @@ func (c *Client) connect() error {
// 如果服务端分配了新 ID则更新并保存
if authResp.ClientID != "" && authResp.ClientID != c.ID {
c.ID = authResp.ClientID
saveClientID(c.ID)
saveClientID(c.DataDir, c.ID)
c.logf("[Client] New ID assigned and saved: %s", c.ID)
}