From 76fde41e48910e63caf95f28d90b1e967ce7aaa5 Mon Sep 17 00:00:00 2001 From: Flik Date: Thu, 1 Jan 2026 01:56:55 +0800 Subject: [PATCH] 1 --- internal/server/router/api.go | 24 ++++ pkg/plugin/builtin/echo.go | 95 ------------ pkg/plugin/builtin/socks5.go | 262 ---------------------------------- pkg/plugin/builtin/vnc.go | 95 ------------ web/src/views/ClientView.vue | 3 +- 5 files changed, 26 insertions(+), 453 deletions(-) delete mode 100644 pkg/plugin/builtin/echo.go delete mode 100644 pkg/plugin/builtin/socks5.go delete mode 100644 pkg/plugin/builtin/vnc.go diff --git a/internal/server/router/api.go b/internal/server/router/api.go index acedf10..ae9f3dc 100644 --- a/internal/server/router/api.go +++ b/internal/server/router/api.go @@ -711,6 +711,30 @@ func (h *APIHandler) handleStoreInstall(rw http.ResponseWriter, r *http.Request) return } + // 将插件信息保存到数据库 + dbClient, err := h.clientStore.GetClient(req.ClientID) + if err == nil { + // 检查插件是否已存在 + exists := false + for i, p := range dbClient.Plugins { + if p.Name == req.PluginName { + // 更新已存在的插件 + dbClient.Plugins[i].Enabled = true + exists = true + break + } + } + if !exists { + // 添加新插件 + dbClient.Plugins = append(dbClient.Plugins, db.ClientPlugin{ + Name: req.PluginName, + Version: "1.0.0", + Enabled: true, + }) + } + _ = h.clientStore.UpdateClient(dbClient) + } + h.jsonResponse(rw, map[string]interface{}{ "status": "ok", "plugin": req.PluginName, diff --git a/pkg/plugin/builtin/echo.go b/pkg/plugin/builtin/echo.go deleted file mode 100644 index 463327c..0000000 --- a/pkg/plugin/builtin/echo.go +++ /dev/null @@ -1,95 +0,0 @@ -package builtin - -import ( - "io" - "log" - "net" - "sync" - - "github.com/gotunnel/pkg/plugin" -) - -func init() { - RegisterClient(NewEchoPlugin()) -} - -// EchoPlugin 回显插件 - 客户端插件示例 -type EchoPlugin struct { - config map[string]string - listener net.Listener - running bool - mu sync.Mutex -} - -// NewEchoPlugin 创建 Echo 插件 -func NewEchoPlugin() *EchoPlugin { - return &EchoPlugin{} -} - -// Metadata 返回插件信息 -func (p *EchoPlugin) Metadata() plugin.Metadata { - return plugin.Metadata{ - Name: "echo", - Version: "1.0.0", - Type: plugin.PluginTypeApp, - Source: plugin.PluginSourceBuiltin, - RunAt: plugin.SideClient, - Description: "Echo server (client plugin example)", - Author: "GoTunnel", - RuleSchema: &plugin.RuleSchema{ - NeedsLocalAddr: false, - }, - } -} - -// Init 初始化插件 -func (p *EchoPlugin) Init(config map[string]string) error { - p.config = config - return nil -} - -// Start 启动服务 -func (p *EchoPlugin) Start() (string, error) { - p.mu.Lock() - defer p.mu.Unlock() - - if p.running { - return "", nil - } - - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return "", err - } - - p.listener = ln - p.running = true - - log.Printf("[Echo] Started on %s", ln.Addr().String()) - return ln.Addr().String(), nil -} - -// HandleConn 处理连接 -func (p *EchoPlugin) HandleConn(conn net.Conn) error { - defer conn.Close() - log.Printf("[Echo] New connection from tunnel") - _, err := io.Copy(conn, conn) - return err -} - -// Stop 停止服务 -func (p *EchoPlugin) Stop() error { - p.mu.Lock() - defer p.mu.Unlock() - - if !p.running { - return nil - } - - if p.listener != nil { - p.listener.Close() - } - p.running = false - log.Printf("[Echo] Stopped") - return nil -} diff --git a/pkg/plugin/builtin/socks5.go b/pkg/plugin/builtin/socks5.go deleted file mode 100644 index 5b0f2cf..0000000 --- a/pkg/plugin/builtin/socks5.go +++ /dev/null @@ -1,262 +0,0 @@ -package builtin - -import ( - "encoding/binary" - "errors" - "fmt" - "io" - "net" - - "github.com/gotunnel/pkg/plugin" -) - -func init() { - RegisterServer(NewSOCKS5Plugin()) -} - -const ( - socks5Version = 0x05 - noAuth = 0x00 - userPassAuth = 0x02 - noAcceptable = 0xFF - userPassAuthVer = 0x01 - authSuccess = 0x00 - authFailure = 0x01 - cmdConnect = 0x01 - atypIPv4 = 0x01 - atypDomain = 0x03 - atypIPv6 = 0x04 -) - -// SOCKS5Plugin 将现有 SOCKS5 实现封装为 plugin -type SOCKS5Plugin struct { - config map[string]string -} - -// NewSOCKS5Plugin 创建 SOCKS5 plugin -func NewSOCKS5Plugin() *SOCKS5Plugin { - return &SOCKS5Plugin{} -} - -// Metadata 返回 plugin 信息 -func (p *SOCKS5Plugin) Metadata() plugin.Metadata { - return plugin.Metadata{ - Name: "socks5", - Version: "1.0.0", - Type: plugin.PluginTypeProxy, - Source: plugin.PluginSourceBuiltin, - RunAt: plugin.SideServer, - Description: "SOCKS5 proxy protocol handler", - Author: "GoTunnel", - RuleSchema: &plugin.RuleSchema{ - NeedsLocalAddr: false, - }, - ConfigSchema: []plugin.ConfigField{ - { - Key: "auth", - Label: "认证方式", - Type: plugin.ConfigFieldSelect, - Default: "none", - Options: []string{"none", "password"}, - }, - { - Key: "username", - Label: "用户名", - Type: plugin.ConfigFieldString, - }, - { - Key: "password", - Label: "密码", - Type: plugin.ConfigFieldPassword, - }, - }, - } -} - -// Init 初始化 plugin -func (p *SOCKS5Plugin) Init(config map[string]string) error { - p.config = config - return nil -} - -// HandleConn 处理 SOCKS5 连接 -func (p *SOCKS5Plugin) HandleConn(conn net.Conn, dialer plugin.Dialer) error { - defer conn.Close() - - // 握手阶段 - if err := p.handshake(conn); err != nil { - return err - } - - // 获取请求 - target, err := p.readRequest(conn) - if err != nil { - return err - } - - // 连接目标 - remote, err := dialer.Dial("tcp", target) - if err != nil { - p.sendReply(conn, 0x05) // Connection refused - return err - } - defer remote.Close() - - // 发送成功响应 - if err := p.sendReply(conn, 0x00); err != nil { - return err - } - - // 双向转发 - go io.Copy(remote, conn) - io.Copy(conn, remote) - - return nil -} - -// Close 释放资源 -func (p *SOCKS5Plugin) Close() error { - return nil -} - -// handshake 处理握手 -func (p *SOCKS5Plugin) handshake(conn net.Conn) error { - buf := make([]byte, 2) - if _, err := io.ReadFull(conn, buf); err != nil { - return err - } - if buf[0] != socks5Version { - return errors.New("unsupported SOCKS version") - } - - nmethods := int(buf[1]) - methods := make([]byte, nmethods) - if _, err := io.ReadFull(conn, methods); err != nil { - return err - } - - // 检查是否需要密码认证 - if p.config["auth"] == "password" { - // 检查客户端是否支持用户名密码认证 - supported := false - for _, m := range methods { - if m == userPassAuth { - supported = true - break - } - } - if !supported { - conn.Write([]byte{socks5Version, noAcceptable}) - return errors.New("client does not support password auth") - } - - // 选择用户名密码认证 - if _, err := conn.Write([]byte{socks5Version, userPassAuth}); err != nil { - return err - } - - // 执行用户名密码认证 - return p.authenticateUserPass(conn) - } - - // 无认证 - _, err := conn.Write([]byte{socks5Version, noAuth}) - return err -} - -// readRequest 读取请求 -func (p *SOCKS5Plugin) readRequest(conn net.Conn) (string, error) { - buf := make([]byte, 4) - if _, err := io.ReadFull(conn, buf); err != nil { - return "", err - } - - if buf[0] != socks5Version || buf[1] != cmdConnect { - return "", errors.New("unsupported command") - } - - var host string - switch buf[3] { - case atypIPv4: - ip := make([]byte, 4) - if _, err := io.ReadFull(conn, ip); err != nil { - return "", err - } - host = net.IP(ip).String() - case atypDomain: - lenBuf := make([]byte, 1) - if _, err := io.ReadFull(conn, lenBuf); err != nil { - return "", err - } - domain := make([]byte, lenBuf[0]) - if _, err := io.ReadFull(conn, domain); err != nil { - return "", err - } - host = string(domain) - case atypIPv6: - ip := make([]byte, 16) - if _, err := io.ReadFull(conn, ip); err != nil { - return "", err - } - host = net.IP(ip).String() - default: - return "", errors.New("unsupported address type") - } - - portBuf := make([]byte, 2) - if _, err := io.ReadFull(conn, portBuf); err != nil { - return "", err - } - port := binary.BigEndian.Uint16(portBuf) - - return fmt.Sprintf("%s:%d", host, port), nil -} - -// authenticateUserPass 用户名密码认证 -func (p *SOCKS5Plugin) authenticateUserPass(conn net.Conn) error { - // 读取认证版本 - buf := make([]byte, 2) - if _, err := io.ReadFull(conn, buf); err != nil { - return err - } - if buf[0] != userPassAuthVer { - return errors.New("unsupported auth version") - } - - // 读取用户名 - ulen := int(buf[1]) - username := make([]byte, ulen) - if _, err := io.ReadFull(conn, username); err != nil { - return err - } - - // 读取密码长度和密码 - plenBuf := make([]byte, 1) - if _, err := io.ReadFull(conn, plenBuf); err != nil { - return err - } - plen := int(plenBuf[0]) - password := make([]byte, plen) - if _, err := io.ReadFull(conn, password); err != nil { - return err - } - - // 验证用户名密码 - expectedUser := p.config["username"] - expectedPass := p.config["password"] - - if string(username) == expectedUser && string(password) == expectedPass { - conn.Write([]byte{userPassAuthVer, authSuccess}) - return nil - } - - conn.Write([]byte{userPassAuthVer, authFailure}) - return errors.New("authentication failed") -} - -// sendReply 发送响应 -func (p *SOCKS5Plugin) sendReply(conn net.Conn, rep byte) error { - reply := []byte{socks5Version, rep, 0x00, atypIPv4, 0, 0, 0, 0, 0, 0} - _, err := conn.Write(reply) - return err -} diff --git a/pkg/plugin/builtin/vnc.go b/pkg/plugin/builtin/vnc.go deleted file mode 100644 index 065fedd..0000000 --- a/pkg/plugin/builtin/vnc.go +++ /dev/null @@ -1,95 +0,0 @@ -package builtin - -import ( - "io" - "log" - "net" - - "github.com/gotunnel/pkg/plugin" -) - -func init() { - RegisterServer(NewVNCPlugin()) -} - -// VNCPlugin VNC 远程桌面插件 -type VNCPlugin struct { - config map[string]string -} - -// NewVNCPlugin 创建 VNC plugin -func NewVNCPlugin() *VNCPlugin { - return &VNCPlugin{} -} - -// Metadata 返回 plugin 信息 -func (p *VNCPlugin) Metadata() plugin.Metadata { - return plugin.Metadata{ - Name: "vnc", - Version: "1.0.0", - Type: plugin.PluginTypeApp, - Source: plugin.PluginSourceBuiltin, - RunAt: plugin.SideServer, - Description: "VNC remote desktop relay", - Author: "GoTunnel", - RuleSchema: &plugin.RuleSchema{ - NeedsLocalAddr: false, - ExtraFields: []plugin.ConfigField{ - { - Key: "vnc_addr", - Label: "VNC 地址", - Type: plugin.ConfigFieldString, - Default: "127.0.0.1:5900", - }, - }, - }, - } -} - -// Init 初始化 plugin -func (p *VNCPlugin) Init(config map[string]string) error { - p.config = config - return nil -} - -// HandleConn 处理 VNC 连接 -// 将外部 VNC 客户端连接转发到客户端本地的 VNC 服务 -func (p *VNCPlugin) HandleConn(conn net.Conn, dialer plugin.Dialer) error { - defer conn.Close() - - // 默认连接客户端本地的 VNC 服务 (5900) - vncAddr := "127.0.0.1:5900" - if addr, ok := p.config["vnc_addr"]; ok && addr != "" { - vncAddr = addr - } - - log.Printf("[VNC] New connection from %s, forwarding to %s", conn.RemoteAddr(), vncAddr) - - // 通过隧道连接到客户端本地的 VNC 服务 - remote, err := dialer.Dial("tcp", vncAddr) - if err != nil { - log.Printf("[VNC] Failed to connect to %s: %v", vncAddr, err) - return err - } - defer remote.Close() - - // 双向转发 VNC 流量 - errCh := make(chan error, 2) - go func() { - _, err := io.Copy(remote, conn) - errCh <- err - }() - go func() { - _, err := io.Copy(conn, remote) - errCh <- err - }() - - // 等待任一方向完成 - <-errCh - return nil -} - -// Close 释放资源 -func (p *VNCPlugin) Close() error { - return nil -} diff --git a/web/src/views/ClientView.vue b/web/src/views/ClientView.vue index 5233b29..b6361d3 100644 --- a/web/src/views/ClientView.vue +++ b/web/src/views/ClientView.vue @@ -42,7 +42,8 @@ const builtinTypes = [ { label: 'TCP', value: 'tcp' }, { label: 'UDP', value: 'udp' }, { label: 'HTTP', value: 'http' }, - { label: 'HTTPS', value: 'https' } + { label: 'HTTPS', value: 'https' }, + { label: 'SOCKS5', value: 'socks5' } ] // 规则类型选项(内置 + 插件)