update
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 29s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 49s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 34s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 59s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 34s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 55s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 37s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m7s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 50s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 33s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 59s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 52s
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 29s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 49s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 34s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 59s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 34s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 55s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 37s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m7s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 50s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 33s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 59s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 52s
This commit is contained in:
@@ -1,114 +0,0 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gotunnel/pkg/plugin"
|
||||
)
|
||||
|
||||
// CachedPlugin 缓存的 plugin 信息
|
||||
type CachedPlugin struct {
|
||||
Metadata plugin.PluginMetadata
|
||||
Path string
|
||||
LoadedAt time.Time
|
||||
}
|
||||
|
||||
// Cache 管理本地 plugin 存储
|
||||
type Cache struct {
|
||||
dir string
|
||||
plugins map[string]*CachedPlugin
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCache 创建 plugin 缓存
|
||||
func NewCache(cacheDir string) (*Cache, error) {
|
||||
if err := os.MkdirAll(cacheDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Cache{
|
||||
dir: cacheDir,
|
||||
plugins: make(map[string]*CachedPlugin),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get 返回缓存的 plugin(如果有效)
|
||||
func (c *Cache) Get(name, version, checksum string) (*CachedPlugin, error) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
cached, ok := c.plugins[name]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 验证版本和 checksum
|
||||
if cached.Metadata.Version != version {
|
||||
return nil, nil
|
||||
}
|
||||
if checksum != "" && cached.Metadata.Checksum != checksum {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return cached, nil
|
||||
}
|
||||
|
||||
// Store 保存 plugin 到缓存
|
||||
func (c *Cache) Store(meta plugin.PluginMetadata, wasmData []byte) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// 验证 checksum
|
||||
hash := sha256.Sum256(wasmData)
|
||||
checksum := hex.EncodeToString(hash[:])
|
||||
if meta.Checksum != "" && meta.Checksum != checksum {
|
||||
return fmt.Errorf("checksum mismatch")
|
||||
}
|
||||
meta.Checksum = checksum
|
||||
|
||||
// 写入文件
|
||||
path := filepath.Join(c.dir, meta.Name+".wasm")
|
||||
if err := os.WriteFile(path, wasmData, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.plugins[meta.Name] = &CachedPlugin{
|
||||
Metadata: meta,
|
||||
Path: path,
|
||||
LoadedAt: time.Now(),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 删除缓存的 plugin
|
||||
func (c *Cache) Remove(name string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
cached, ok := c.plugins[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
os.Remove(cached.Path)
|
||||
delete(c.plugins, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// List 返回所有缓存的 plugins
|
||||
func (c *Cache) List() []plugin.PluginMetadata {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
var result []plugin.PluginMetadata
|
||||
for _, cached := range c.plugins {
|
||||
result = append(result, cached.Metadata)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,43 +1,25 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/gotunnel/pkg/plugin"
|
||||
"github.com/gotunnel/pkg/plugin/builtin"
|
||||
"github.com/gotunnel/pkg/plugin/wasm"
|
||||
)
|
||||
|
||||
// Manager 客户端 plugin 管理器
|
||||
type Manager struct {
|
||||
registry *plugin.Registry
|
||||
cache *Cache
|
||||
runtime *wasm.Runtime
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewManager 创建客户端 plugin 管理器
|
||||
func NewManager(cacheDir string) (*Manager, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
cache, err := NewCache(cacheDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime, err := wasm.NewRuntime(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func NewManager() (*Manager, error) {
|
||||
registry := plugin.NewRegistry()
|
||||
|
||||
m := &Manager{
|
||||
registry: registry,
|
||||
cache: cache,
|
||||
runtime: runtime,
|
||||
}
|
||||
|
||||
// 注册内置 plugins
|
||||
@@ -49,13 +31,19 @@ func NewManager(cacheDir string) (*Manager, error) {
|
||||
}
|
||||
|
||||
// registerBuiltins 注册内置 plugins
|
||||
// 注意: tcp, udp, http, https 是内置类型,直接在 tunnel 中处理
|
||||
func (m *Manager) registerBuiltins() error {
|
||||
// 使用统一的插件注册入口
|
||||
// 注册服务端插件
|
||||
if err := m.registry.RegisterAll(builtin.GetAll()); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("[Plugin] Registered %d builtin plugins", len(builtin.GetAll()))
|
||||
// 注册客户端插件
|
||||
for _, h := range builtin.GetAllClientPlugins() {
|
||||
if err := m.registry.RegisterClientPlugin(h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Printf("[Plugin] Registered %d server plugins, %d client plugins",
|
||||
len(builtin.GetAll()), len(builtin.GetAllClientPlugins()))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -63,8 +51,3 @@ func (m *Manager) registerBuiltins() error {
|
||||
func (m *Manager) GetHandler(proxyType string) (plugin.ProxyHandler, error) {
|
||||
return m.registry.Get(proxyType)
|
||||
}
|
||||
|
||||
// Close 关闭管理器
|
||||
func (m *Manager) Close(ctx context.Context) error {
|
||||
return m.runtime.Close(ctx)
|
||||
}
|
||||
|
||||
@@ -29,27 +29,29 @@ const (
|
||||
|
||||
// Client 隧道客户端
|
||||
type Client struct {
|
||||
ServerAddr string
|
||||
Token string
|
||||
ID string
|
||||
TLSEnabled bool
|
||||
TLSConfig *tls.Config
|
||||
session *yamux.Session
|
||||
rules []protocol.ProxyRule
|
||||
mu sync.RWMutex
|
||||
pluginRegistry *plugin.Registry
|
||||
ServerAddr string
|
||||
Token string
|
||||
ID string
|
||||
TLSEnabled bool
|
||||
TLSConfig *tls.Config
|
||||
session *yamux.Session
|
||||
rules []protocol.ProxyRule
|
||||
mu sync.RWMutex
|
||||
pluginRegistry *plugin.Registry
|
||||
runningPlugins map[string]plugin.ClientHandler // 运行中的客户端插件
|
||||
pluginMu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewClient 创建客户端
|
||||
func NewClient(serverAddr, token, id string) *Client {
|
||||
// 如果未指定 ID,尝试从本地文件加载
|
||||
if id == "" {
|
||||
id = loadClientID()
|
||||
}
|
||||
return &Client{
|
||||
ServerAddr: serverAddr,
|
||||
Token: token,
|
||||
ID: id,
|
||||
ServerAddr: serverAddr,
|
||||
Token: token,
|
||||
ID: id,
|
||||
runningPlugins: make(map[string]plugin.ClientHandler),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +199,10 @@ func (c *Client) handleStream(stream net.Conn) {
|
||||
case protocol.MsgTypePluginConfig:
|
||||
defer stream.Close()
|
||||
c.handlePluginConfig(msg)
|
||||
case protocol.MsgTypeClientPluginStart:
|
||||
c.handleClientPluginStart(stream, msg)
|
||||
case protocol.MsgTypeClientPluginConn:
|
||||
c.handleClientPluginConn(stream, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,3 +380,85 @@ func (c *Client) handlePluginConfig(msg *protocol.Message) {
|
||||
log.Printf("[Client] Plugin %s config applied", cfg.PluginName)
|
||||
}
|
||||
}
|
||||
|
||||
// handleClientPluginStart 处理客户端插件启动请求
|
||||
func (c *Client) handleClientPluginStart(stream net.Conn, msg *protocol.Message) {
|
||||
defer stream.Close()
|
||||
|
||||
var req protocol.ClientPluginStartRequest
|
||||
if err := msg.ParsePayload(&req); err != nil {
|
||||
c.sendPluginStatus(stream, req.PluginName, req.RuleName, false, "", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[Client] Starting plugin %s for rule %s", req.PluginName, req.RuleName)
|
||||
|
||||
// 获取插件
|
||||
if c.pluginRegistry == nil {
|
||||
c.sendPluginStatus(stream, req.PluginName, req.RuleName, false, "", "plugin registry not set")
|
||||
return
|
||||
}
|
||||
|
||||
handler, err := c.pluginRegistry.GetClientPlugin(req.PluginName)
|
||||
if err != nil {
|
||||
c.sendPluginStatus(stream, req.PluginName, req.RuleName, false, "", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化并启动
|
||||
if err := handler.Init(req.Config); err != nil {
|
||||
c.sendPluginStatus(stream, req.PluginName, req.RuleName, false, "", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
localAddr, err := handler.Start()
|
||||
if err != nil {
|
||||
c.sendPluginStatus(stream, req.PluginName, req.RuleName, false, "", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 保存运行中的插件
|
||||
key := req.PluginName + ":" + req.RuleName
|
||||
c.pluginMu.Lock()
|
||||
c.runningPlugins[key] = handler
|
||||
c.pluginMu.Unlock()
|
||||
|
||||
log.Printf("[Client] Plugin %s started at %s", req.PluginName, localAddr)
|
||||
c.sendPluginStatus(stream, req.PluginName, req.RuleName, true, localAddr, "")
|
||||
}
|
||||
|
||||
// sendPluginStatus 发送插件状态响应
|
||||
func (c *Client) sendPluginStatus(stream net.Conn, pluginName, ruleName string, running bool, localAddr, errMsg string) {
|
||||
resp := protocol.ClientPluginStatusResponse{
|
||||
PluginName: pluginName,
|
||||
RuleName: ruleName,
|
||||
Running: running,
|
||||
LocalAddr: localAddr,
|
||||
Error: errMsg,
|
||||
}
|
||||
msg, _ := protocol.NewMessage(protocol.MsgTypeClientPluginStatus, resp)
|
||||
protocol.WriteMessage(stream, msg)
|
||||
}
|
||||
|
||||
// handleClientPluginConn 处理客户端插件连接
|
||||
func (c *Client) handleClientPluginConn(stream net.Conn, msg *protocol.Message) {
|
||||
var req protocol.ClientPluginConnRequest
|
||||
if err := msg.ParsePayload(&req); err != nil {
|
||||
stream.Close()
|
||||
return
|
||||
}
|
||||
|
||||
key := req.PluginName + ":" + req.RuleName
|
||||
c.pluginMu.RLock()
|
||||
handler, ok := c.runningPlugins[key]
|
||||
c.pluginMu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
log.Printf("[Client] Plugin %s not running", key)
|
||||
stream.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// 让插件处理连接
|
||||
handler.HandleConn(stream)
|
||||
}
|
||||
|
||||
@@ -1,40 +1,25 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/gotunnel/internal/server/db"
|
||||
"github.com/gotunnel/pkg/plugin"
|
||||
"github.com/gotunnel/pkg/plugin/builtin"
|
||||
"github.com/gotunnel/pkg/plugin/wasm"
|
||||
)
|
||||
|
||||
// Manager 服务端 plugin 管理器
|
||||
type Manager struct {
|
||||
registry *plugin.Registry
|
||||
store db.PluginStore
|
||||
runtime *wasm.Runtime
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewManager 创建 plugin 管理器
|
||||
func NewManager(pluginStore db.PluginStore) (*Manager, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
runtime, err := wasm.NewRuntime(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create wasm runtime: %w", err)
|
||||
}
|
||||
|
||||
func NewManager() (*Manager, error) {
|
||||
registry := plugin.NewRegistry()
|
||||
|
||||
m := &Manager{
|
||||
registry: registry,
|
||||
store: pluginStore,
|
||||
runtime: runtime,
|
||||
}
|
||||
|
||||
// 注册内置 plugins
|
||||
@@ -46,67 +31,20 @@ func NewManager(pluginStore db.PluginStore) (*Manager, error) {
|
||||
}
|
||||
|
||||
// registerBuiltins 注册内置 plugins
|
||||
// 注意: tcp, udp, http, https 是内置类型,直接在 tunnel 中处理
|
||||
// 这里只注册需要通过 plugin 系统提供的协议
|
||||
func (m *Manager) registerBuiltins() error {
|
||||
// 使用统一的插件注册入口
|
||||
// 注册服务端插件
|
||||
if err := m.registry.RegisterAll(builtin.GetAll()); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("[Plugin] Registered %d builtin plugins", len(builtin.GetAll()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadStoredPlugins 从数据库加载所有 plugins
|
||||
func (m *Manager) LoadStoredPlugins(ctx context.Context) error {
|
||||
if m.store == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
plugins, err := m.store.GetAllPlugins()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range plugins {
|
||||
data, err := m.store.GetPluginWASM(p.Name)
|
||||
if err != nil {
|
||||
log.Printf("[Plugin] Failed to load %s: %v", p.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := m.loadWASMPlugin(ctx, p.Name, data); err != nil {
|
||||
log.Printf("[Plugin] Failed to init %s: %v", p.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadWASMPlugin 加载 WASM plugin
|
||||
func (m *Manager) loadWASMPlugin(ctx context.Context, name string, data []byte) error {
|
||||
_, err := m.runtime.LoadModule(ctx, name, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("[Plugin] WASM plugin loaded: %s", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// InstallPlugin 安装新的 WASM plugin
|
||||
func (m *Manager) InstallPlugin(ctx context.Context, p *db.PluginData) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
// 存储到数据库
|
||||
if m.store != nil {
|
||||
if err := m.store.SavePlugin(p); err != nil {
|
||||
// 注册客户端插件
|
||||
for _, h := range builtin.GetAllClientPlugins() {
|
||||
if err := m.registry.RegisterClientPlugin(h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 加载到运行时
|
||||
return m.loadWASMPlugin(ctx, p.Name, p.WASMData)
|
||||
log.Printf("[Plugin] Registered %d server plugins, %d client plugins",
|
||||
len(builtin.GetAll()), len(builtin.GetAllClientPlugins()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHandler 返回指定代理类型的 handler
|
||||
@@ -119,7 +57,7 @@ func (m *Manager) ListPlugins() []plugin.PluginInfo {
|
||||
return m.registry.List()
|
||||
}
|
||||
|
||||
// Close 关闭管理器
|
||||
func (m *Manager) Close(ctx context.Context) error {
|
||||
return m.runtime.Close(ctx)
|
||||
// GetRegistry 返回插件注册表
|
||||
func (m *Manager) GetRegistry() *plugin.Registry {
|
||||
return m.registry
|
||||
}
|
||||
|
||||
@@ -22,19 +22,21 @@ func validateClientID(id string) bool {
|
||||
|
||||
// ClientStatus 客户端状态
|
||||
type ClientStatus struct {
|
||||
ID string `json:"id"`
|
||||
Nickname string `json:"nickname,omitempty"`
|
||||
Online bool `json:"online"`
|
||||
LastPing string `json:"last_ping,omitempty"`
|
||||
RuleCount int `json:"rule_count"`
|
||||
ID string `json:"id"`
|
||||
Nickname string `json:"nickname,omitempty"`
|
||||
Online bool `json:"online"`
|
||||
LastPing string `json:"last_ping,omitempty"`
|
||||
RemoteAddr string `json:"remote_addr,omitempty"`
|
||||
RuleCount int `json:"rule_count"`
|
||||
}
|
||||
|
||||
// ServerInterface 服务端接口
|
||||
type ServerInterface interface {
|
||||
GetClientStatus(clientID string) (online bool, lastPing string)
|
||||
GetClientStatus(clientID string) (online bool, lastPing string, remoteAddr string)
|
||||
GetAllClientStatus() map[string]struct {
|
||||
Online bool
|
||||
LastPing string
|
||||
Online bool
|
||||
LastPing string
|
||||
RemoteAddr string
|
||||
}
|
||||
ReloadConfig() error
|
||||
GetBindAddr() string
|
||||
@@ -158,6 +160,7 @@ func (h *APIHandler) getClients(rw http.ResponseWriter) {
|
||||
if s, ok := statusMap[c.ID]; ok {
|
||||
cs.Online = s.Online
|
||||
cs.LastPing = s.LastPing
|
||||
cs.RemoteAddr = s.RemoteAddr
|
||||
}
|
||||
result = append(result, cs)
|
||||
}
|
||||
@@ -256,10 +259,11 @@ func (h *APIHandler) getClient(rw http.ResponseWriter, clientID string) {
|
||||
http.Error(rw, "client not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
online, lastPing := h.server.GetClientStatus(clientID)
|
||||
online, lastPing, remoteAddr := h.server.GetClientStatus(clientID)
|
||||
h.jsonResponse(rw, map[string]interface{}{
|
||||
"id": client.ID, "nickname": client.Nickname, "rules": client.Rules,
|
||||
"plugins": client.Plugins, "online": online, "last_ping": lastPing,
|
||||
"remote_addr": remoteAddr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -429,7 +433,7 @@ func (h *APIHandler) pushConfigToClient(rw http.ResponseWriter, r *http.Request,
|
||||
return
|
||||
}
|
||||
|
||||
online, _ := h.server.GetClientStatus(clientID)
|
||||
online, _, _ := h.server.GetClientStatus(clientID)
|
||||
if !online {
|
||||
http.Error(rw, "client not online", http.StatusBadRequest)
|
||||
return
|
||||
@@ -523,7 +527,7 @@ func (h *APIHandler) installPluginsToClient(rw http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
online, _ := h.server.GetClientStatus(clientID)
|
||||
online, _, _ := h.server.GetClientStatus(clientID)
|
||||
if !online {
|
||||
http.Error(rw, "client not online", http.StatusBadRequest)
|
||||
return
|
||||
@@ -734,7 +738,7 @@ func (h *APIHandler) updateClientPluginConfig(rw http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
// 如果客户端在线,同步配置
|
||||
online, _ := h.server.GetClientStatus(clientID)
|
||||
online, _, _ := h.server.GetClientStatus(clientID)
|
||||
if online {
|
||||
if err := h.server.SyncPluginConfigToClient(clientID, pluginName, req.Config); err != nil {
|
||||
// 配置已保存,但同步失败,返回警告
|
||||
|
||||
@@ -52,6 +52,7 @@ type Server struct {
|
||||
// ClientSession 客户端会话
|
||||
type ClientSession struct {
|
||||
ID string
|
||||
RemoteAddr string // 客户端 IP 地址
|
||||
Session *yamux.Session
|
||||
Rules []protocol.ProxyRule
|
||||
Listeners map[int]net.Listener
|
||||
@@ -185,13 +186,20 @@ func (s *Server) setupClientSession(conn net.Conn, clientID string, rules []prot
|
||||
return
|
||||
}
|
||||
|
||||
// 提取客户端 IP(去掉端口)
|
||||
remoteAddr := conn.RemoteAddr().String()
|
||||
if host, _, err := net.SplitHostPort(remoteAddr); err == nil {
|
||||
remoteAddr = host
|
||||
}
|
||||
|
||||
cs := &ClientSession{
|
||||
ID: clientID,
|
||||
Session: session,
|
||||
Rules: rules,
|
||||
Listeners: make(map[int]net.Listener),
|
||||
UDPConns: make(map[int]*net.UDPConn),
|
||||
LastPing: time.Now(),
|
||||
ID: clientID,
|
||||
RemoteAddr: remoteAddr,
|
||||
Session: session,
|
||||
Rules: rules,
|
||||
Listeners: make(map[int]net.Listener),
|
||||
UDPConns: make(map[int]*net.UDPConn),
|
||||
LastPing: time.Now(),
|
||||
}
|
||||
|
||||
s.registerClient(cs)
|
||||
@@ -284,6 +292,10 @@ func (s *Server) stopProxyListeners(cs *ClientSession) {
|
||||
// startProxyListeners 启动代理监听
|
||||
func (s *Server) startProxyListeners(cs *ClientSession) {
|
||||
for _, rule := range cs.Rules {
|
||||
if !rule.IsEnabled() {
|
||||
continue
|
||||
}
|
||||
|
||||
ruleType := rule.Type
|
||||
if ruleType == "" {
|
||||
ruleType = "tcp"
|
||||
@@ -295,6 +307,12 @@ func (s *Server) startProxyListeners(cs *ClientSession) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查是否为客户端插件
|
||||
if s.isClientPlugin(ruleType) {
|
||||
s.startClientPluginListener(cs, rule)
|
||||
continue
|
||||
}
|
||||
|
||||
// TCP 类型
|
||||
if err := s.portManager.Reserve(rule.RemotePort, cs.ID); err != nil {
|
||||
log.Printf("[Server] Port %d error: %v", rule.RemotePort, err)
|
||||
@@ -445,22 +463,23 @@ func (s *Server) sendHeartbeat(cs *ClientSession) bool {
|
||||
}
|
||||
|
||||
// GetClientStatus 获取客户端状态
|
||||
func (s *Server) GetClientStatus(clientID string) (online bool, lastPing string) {
|
||||
func (s *Server) GetClientStatus(clientID string) (online bool, lastPing string, remoteAddr string) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
if cs, ok := s.clients[clientID]; ok {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
return true, cs.LastPing.Format(time.RFC3339)
|
||||
return true, cs.LastPing.Format(time.RFC3339), cs.RemoteAddr
|
||||
}
|
||||
return false, ""
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
// GetAllClientStatus 获取所有客户端状态
|
||||
func (s *Server) GetAllClientStatus() map[string]struct {
|
||||
Online bool
|
||||
LastPing string
|
||||
Online bool
|
||||
LastPing string
|
||||
RemoteAddr string
|
||||
} {
|
||||
// 先复制客户端引用,避免嵌套锁
|
||||
s.mu.RLock()
|
||||
@@ -471,18 +490,21 @@ func (s *Server) GetAllClientStatus() map[string]struct {
|
||||
s.mu.RUnlock()
|
||||
|
||||
result := make(map[string]struct {
|
||||
Online bool
|
||||
LastPing string
|
||||
Online bool
|
||||
LastPing string
|
||||
RemoteAddr string
|
||||
})
|
||||
|
||||
for _, cs := range clients {
|
||||
cs.mu.Lock()
|
||||
result[cs.ID] = struct {
|
||||
Online bool
|
||||
LastPing string
|
||||
Online bool
|
||||
LastPing string
|
||||
RemoteAddr string
|
||||
}{
|
||||
Online: true,
|
||||
LastPing: cs.LastPing.Format(time.RFC3339),
|
||||
Online: true,
|
||||
LastPing: cs.LastPing.Format(time.RFC3339),
|
||||
RemoteAddr: cs.RemoteAddr,
|
||||
}
|
||||
cs.mu.Unlock()
|
||||
}
|
||||
@@ -812,3 +834,119 @@ func (s *Server) sendPluginConfig(session *yamux.Session, pluginName string, con
|
||||
}
|
||||
return protocol.WriteMessage(stream, msg)
|
||||
}
|
||||
|
||||
// isClientPlugin 检查是否为客户端插件
|
||||
func (s *Server) isClientPlugin(pluginType string) bool {
|
||||
if s.pluginRegistry == nil {
|
||||
return false
|
||||
}
|
||||
handler, err := s.pluginRegistry.GetClientPlugin(pluginType)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return handler != nil
|
||||
}
|
||||
|
||||
// startClientPluginListener 启动客户端插件监听
|
||||
func (s *Server) startClientPluginListener(cs *ClientSession, rule protocol.ProxyRule) {
|
||||
if err := s.portManager.Reserve(rule.RemotePort, cs.ID); err != nil {
|
||||
log.Printf("[Server] Port %d error: %v", rule.RemotePort, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 发送启动命令到客户端
|
||||
if err := s.sendClientPluginStart(cs.Session, rule); err != nil {
|
||||
log.Printf("[Server] Failed to start client plugin %s: %v", rule.Type, err)
|
||||
s.portManager.Release(rule.RemotePort)
|
||||
return
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", rule.RemotePort))
|
||||
if err != nil {
|
||||
log.Printf("[Server] Listen %d error: %v", rule.RemotePort, err)
|
||||
s.portManager.Release(rule.RemotePort)
|
||||
return
|
||||
}
|
||||
|
||||
cs.mu.Lock()
|
||||
cs.Listeners[rule.RemotePort] = ln
|
||||
cs.mu.Unlock()
|
||||
|
||||
log.Printf("[Server] Client plugin %s on :%d", rule.Type, rule.RemotePort)
|
||||
go s.acceptClientPluginConns(cs, ln, rule)
|
||||
}
|
||||
|
||||
// sendClientPluginStart 发送客户端插件启动命令
|
||||
func (s *Server) sendClientPluginStart(session *yamux.Session, rule protocol.ProxyRule) error {
|
||||
stream, err := session.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
req := protocol.ClientPluginStartRequest{
|
||||
PluginName: rule.Type,
|
||||
RuleName: rule.Name,
|
||||
RemotePort: rule.RemotePort,
|
||||
Config: rule.PluginConfig,
|
||||
}
|
||||
msg, err := protocol.NewMessage(protocol.MsgTypeClientPluginStart, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := protocol.WriteMessage(stream, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 等待响应
|
||||
resp, err := protocol.ReadMessage(stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Type != protocol.MsgTypeClientPluginStatus {
|
||||
return fmt.Errorf("unexpected response type: %d", resp.Type)
|
||||
}
|
||||
|
||||
var status protocol.ClientPluginStatusResponse
|
||||
if err := resp.ParsePayload(&status); err != nil {
|
||||
return err
|
||||
}
|
||||
if !status.Running {
|
||||
return fmt.Errorf("plugin failed: %s", status.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// acceptClientPluginConns 接受客户端插件连接
|
||||
func (s *Server) acceptClientPluginConns(cs *ClientSession, ln net.Listener, rule protocol.ProxyRule) {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go s.handleClientPluginConn(cs, conn, rule)
|
||||
}
|
||||
}
|
||||
|
||||
// handleClientPluginConn 处理客户端插件连接
|
||||
func (s *Server) handleClientPluginConn(cs *ClientSession, conn net.Conn, rule protocol.ProxyRule) {
|
||||
defer conn.Close()
|
||||
|
||||
stream, err := cs.Session.Open()
|
||||
if err != nil {
|
||||
log.Printf("[Server] Open stream error: %v", err)
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
req := protocol.ClientPluginConnRequest{
|
||||
PluginName: rule.Type,
|
||||
RuleName: rule.Name,
|
||||
}
|
||||
msg, _ := protocol.NewMessage(protocol.MsgTypeClientPluginConn, req)
|
||||
if err := protocol.WriteMessage(stream, msg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
relay.Relay(conn, stream)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user