1
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 28s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 46s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m0s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 43s
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 48s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m4s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 49s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 44s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 57s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 50s
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 28s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 46s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m0s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 43s
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 48s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m4s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 49s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 44s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 57s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 50s
This commit is contained in:
@@ -711,6 +711,30 @@ func (h *APIHandler) handleStoreInstall(rw http.ResponseWriter, r *http.Request)
|
|||||||
return
|
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{}{
|
h.jsonResponse(rw, map[string]interface{}{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"plugin": req.PluginName,
|
"plugin": req.PluginName,
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -42,7 +42,8 @@ const builtinTypes = [
|
|||||||
{ label: 'TCP', value: 'tcp' },
|
{ label: 'TCP', value: 'tcp' },
|
||||||
{ label: 'UDP', value: 'udp' },
|
{ label: 'UDP', value: 'udp' },
|
||||||
{ label: 'HTTP', value: 'http' },
|
{ label: 'HTTP', value: 'http' },
|
||||||
{ label: 'HTTPS', value: 'https' }
|
{ label: 'HTTPS', value: 'https' },
|
||||||
|
{ label: 'SOCKS5', value: 'socks5' }
|
||||||
]
|
]
|
||||||
|
|
||||||
// 规则类型选项(内置 + 插件)
|
// 规则类型选项(内置 + 插件)
|
||||||
|
|||||||
Reference in New Issue
Block a user