update
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
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 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m47s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 57s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 49s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m5s
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 45s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 51s
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
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 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m47s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 57s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 49s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m5s
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 45s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 51s
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 核心接口定义 - 按职责分离
|
||||
// =============================================================================
|
||||
|
||||
// Dialer 网络拨号接口(已在 types.go 中定义,此处为文档说明)
|
||||
// type Dialer interface {
|
||||
// Dial(network, address string) (net.Conn, error)
|
||||
// }
|
||||
|
||||
// PortManager 端口管理接口(仅服务端可用)
|
||||
type PortManager interface {
|
||||
// ReservePort 预留端口,返回错误如果端口已被占用
|
||||
ReservePort(port int) error
|
||||
// ReleasePort 释放端口
|
||||
ReleasePort(port int)
|
||||
// IsPortAvailable 检查端口是否可用
|
||||
IsPortAvailable(port int) bool
|
||||
}
|
||||
|
||||
// RuleManager 代理规则管理接口(仅服务端可用)
|
||||
type RuleManager interface {
|
||||
// CreateRule 创建代理规则
|
||||
CreateRule(rule *RuleConfig) error
|
||||
// DeleteRule 删除代理规则
|
||||
DeleteRule(clientID, ruleName string) error
|
||||
// GetRules 获取客户端的代理规则
|
||||
GetRules(clientID string) ([]RuleConfig, error)
|
||||
// UpdateRule 更新代理规则
|
||||
UpdateRule(clientID string, rule *RuleConfig) error
|
||||
}
|
||||
|
||||
// ClientManager 客户端管理接口(仅服务端可用)
|
||||
type ClientManager interface {
|
||||
// GetClientList 获取所有客户端列表
|
||||
GetClientList() ([]ClientInfo, error)
|
||||
// IsClientOnline 检查客户端是否在线
|
||||
IsClientOnline(clientID string) bool
|
||||
}
|
||||
|
||||
// Logger 日志接口
|
||||
type Logger interface {
|
||||
// Log 记录日志
|
||||
Log(level LogLevel, format string, args ...interface{})
|
||||
}
|
||||
|
||||
// ConfigStore 配置存储接口
|
||||
type ConfigStore interface {
|
||||
// GetConfig 获取配置值
|
||||
GetConfig(key string) string
|
||||
// SetConfig 设置配置值
|
||||
SetConfig(key, value string)
|
||||
}
|
||||
|
||||
// EventBus 事件总线接口
|
||||
type EventBus interface {
|
||||
// OnEvent 订阅事件
|
||||
OnEvent(eventType EventType, handler EventHandler)
|
||||
// EmitEvent 发送事件
|
||||
EmitEvent(event *Event)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 组合接口
|
||||
// =============================================================================
|
||||
|
||||
// PluginAPI 插件 API 主接口,组合所有子接口
|
||||
// 插件可以通过此接口访问 GoTunnel 的功能
|
||||
type PluginAPI interface {
|
||||
// 网络操作
|
||||
Dial(network, address string) (net.Conn, error)
|
||||
DialTimeout(network, address string, timeout time.Duration) (net.Conn, error)
|
||||
Listen(network, address string) (net.Listener, error)
|
||||
|
||||
// 端口管理(服务端)
|
||||
PortManager
|
||||
|
||||
// 规则管理(服务端)
|
||||
RuleManager
|
||||
|
||||
// 客户端管理(服务端)
|
||||
ClientManager
|
||||
|
||||
// 日志
|
||||
Logger
|
||||
|
||||
// 配置
|
||||
ConfigStore
|
||||
|
||||
// 事件
|
||||
EventBus
|
||||
|
||||
// 上下文
|
||||
GetContext() *Context
|
||||
GetClientID() string
|
||||
GetServerInfo() *ServerInfo
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 基础实现 - 提取公共代码
|
||||
// =============================================================================
|
||||
|
||||
// baseAPI 包含服务端和客户端共享的基础功能
|
||||
type baseAPI struct {
|
||||
pluginName string
|
||||
config map[string]string
|
||||
configMu sync.RWMutex
|
||||
|
||||
eventHandlers map[EventType][]EventHandler
|
||||
eventMu sync.RWMutex
|
||||
}
|
||||
|
||||
// newBaseAPI 创建基础 API
|
||||
func newBaseAPI(pluginName string, config map[string]string) *baseAPI {
|
||||
cfg := config
|
||||
if cfg == nil {
|
||||
cfg = make(map[string]string)
|
||||
}
|
||||
return &baseAPI{
|
||||
pluginName: pluginName,
|
||||
config: cfg,
|
||||
eventHandlers: make(map[EventType][]EventHandler),
|
||||
}
|
||||
}
|
||||
|
||||
// Log 记录日志
|
||||
func (b *baseAPI) Log(level LogLevel, format string, args ...interface{}) {
|
||||
prefix := fmt.Sprintf("[Plugin:%s] ", b.pluginName)
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
log.Printf("%s%s", prefix, msg)
|
||||
}
|
||||
|
||||
// GetConfig 获取配置值
|
||||
func (b *baseAPI) GetConfig(key string) string {
|
||||
b.configMu.RLock()
|
||||
defer b.configMu.RUnlock()
|
||||
return b.config[key]
|
||||
}
|
||||
|
||||
// SetConfig 设置配置值
|
||||
func (b *baseAPI) SetConfig(key, value string) {
|
||||
b.configMu.Lock()
|
||||
defer b.configMu.Unlock()
|
||||
b.config[key] = value
|
||||
}
|
||||
|
||||
// OnEvent 订阅事件
|
||||
func (b *baseAPI) OnEvent(eventType EventType, handler EventHandler) {
|
||||
b.eventMu.Lock()
|
||||
defer b.eventMu.Unlock()
|
||||
b.eventHandlers[eventType] = append(b.eventHandlers[eventType], handler)
|
||||
}
|
||||
|
||||
// EmitEvent 发送事件(复制切片避免竞态条件)
|
||||
func (b *baseAPI) EmitEvent(event *Event) {
|
||||
b.eventMu.RLock()
|
||||
handlers := make([]EventHandler, len(b.eventHandlers[event.Type]))
|
||||
copy(handlers, b.eventHandlers[event.Type])
|
||||
b.eventMu.RUnlock()
|
||||
|
||||
for _, handler := range handlers {
|
||||
go handler(event)
|
||||
}
|
||||
}
|
||||
|
||||
// getPluginName 获取插件名称
|
||||
func (b *baseAPI) getPluginName() string {
|
||||
return b.pluginName
|
||||
}
|
||||
|
||||
// getConfigMap 获取配置副本
|
||||
func (b *baseAPI) getConfigMap() map[string]string {
|
||||
b.configMu.RLock()
|
||||
defer b.configMu.RUnlock()
|
||||
result := make(map[string]string, len(b.config))
|
||||
for k, v := range b.config {
|
||||
result[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package plugin
|
||||
|
||||
// RegisterBuiltins 注册所有内置 plugins
|
||||
func RegisterBuiltins(registry *Registry, handlers ...ProxyHandler) error {
|
||||
for _, handler := range handlers {
|
||||
if err := registry.RegisterBuiltin(handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterClientPlugin(NewEchoPlugin())
|
||||
RegisterClient(NewEchoPlugin())
|
||||
}
|
||||
|
||||
// EchoPlugin 回显插件 - 客户端插件示例
|
||||
@@ -27,8 +27,8 @@ func NewEchoPlugin() *EchoPlugin {
|
||||
}
|
||||
|
||||
// Metadata 返回插件信息
|
||||
func (p *EchoPlugin) Metadata() plugin.PluginMetadata {
|
||||
return plugin.PluginMetadata{
|
||||
func (p *EchoPlugin) Metadata() plugin.Metadata {
|
||||
return plugin.Metadata{
|
||||
Name: "echo",
|
||||
Version: "1.0.0",
|
||||
Type: plugin.PluginTypeApp,
|
||||
|
||||
@@ -2,28 +2,27 @@ package builtin
|
||||
|
||||
import "github.com/gotunnel/pkg/plugin"
|
||||
|
||||
// 全局插件注册表
|
||||
var (
|
||||
serverPlugins []plugin.ProxyHandler
|
||||
clientPlugins []plugin.ClientHandler
|
||||
serverPlugins []plugin.ServerPlugin
|
||||
clientPlugins []plugin.ClientPlugin
|
||||
)
|
||||
|
||||
// Register 注册服务端插件
|
||||
func Register(handler plugin.ProxyHandler) {
|
||||
// RegisterServer 注册服务端插件
|
||||
func RegisterServer(handler plugin.ServerPlugin) {
|
||||
serverPlugins = append(serverPlugins, handler)
|
||||
}
|
||||
|
||||
// RegisterClientPlugin 注册客户端插件
|
||||
func RegisterClientPlugin(handler plugin.ClientHandler) {
|
||||
// RegisterClient 注册客户端插件
|
||||
func RegisterClient(handler plugin.ClientPlugin) {
|
||||
clientPlugins = append(clientPlugins, handler)
|
||||
}
|
||||
|
||||
// GetAll 返回所有服务端插件
|
||||
func GetAll() []plugin.ProxyHandler {
|
||||
// GetServerPlugins 返回所有服务端插件
|
||||
func GetServerPlugins() []plugin.ServerPlugin {
|
||||
return serverPlugins
|
||||
}
|
||||
|
||||
// GetAllClientPlugins 返回所有客户端插件
|
||||
func GetAllClientPlugins() []plugin.ClientHandler {
|
||||
// GetClientPlugins 返回所有客户端插件
|
||||
func GetClientPlugins() []plugin.ClientPlugin {
|
||||
return clientPlugins
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register(NewSOCKS5Plugin())
|
||||
RegisterServer(NewSOCKS5Plugin())
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -39,8 +39,8 @@ func NewSOCKS5Plugin() *SOCKS5Plugin {
|
||||
}
|
||||
|
||||
// Metadata 返回 plugin 信息
|
||||
func (p *SOCKS5Plugin) Metadata() plugin.PluginMetadata {
|
||||
return plugin.PluginMetadata{
|
||||
func (p *SOCKS5Plugin) Metadata() plugin.Metadata {
|
||||
return plugin.Metadata{
|
||||
Name: "socks5",
|
||||
Version: "1.0.0",
|
||||
Type: plugin.PluginTypeProxy,
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register(NewVNCPlugin())
|
||||
RegisterServer(NewVNCPlugin())
|
||||
}
|
||||
|
||||
// VNCPlugin VNC 远程桌面插件
|
||||
@@ -23,13 +23,13 @@ func NewVNCPlugin() *VNCPlugin {
|
||||
}
|
||||
|
||||
// Metadata 返回 plugin 信息
|
||||
func (p *VNCPlugin) Metadata() plugin.PluginMetadata {
|
||||
return plugin.PluginMetadata{
|
||||
func (p *VNCPlugin) Metadata() plugin.Metadata {
|
||||
return plugin.Metadata{
|
||||
Name: "vnc",
|
||||
Version: "1.0.0",
|
||||
Type: plugin.PluginTypeApp,
|
||||
Source: plugin.PluginSourceBuiltin,
|
||||
RunAt: plugin.SideServer, // 当前为服务端中继模式
|
||||
RunAt: plugin.SideServer,
|
||||
Description: "VNC remote desktop relay",
|
||||
Author: "GoTunnel",
|
||||
RuleSchema: &plugin.RuleSchema{
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 客户端 API 实现
|
||||
// =============================================================================
|
||||
|
||||
// ClientAPI 客户端 PluginAPI 实现
|
||||
type ClientAPI struct {
|
||||
*baseAPI
|
||||
clientID string
|
||||
dialer Dialer
|
||||
}
|
||||
|
||||
// ClientAPIOption 客户端 API 配置选项
|
||||
type ClientAPIOption struct {
|
||||
PluginName string
|
||||
ClientID string
|
||||
Config map[string]string
|
||||
Dialer Dialer
|
||||
}
|
||||
|
||||
// NewClientAPI 创建客户端 API
|
||||
func NewClientAPI(opt ClientAPIOption) *ClientAPI {
|
||||
return &ClientAPI{
|
||||
baseAPI: newBaseAPI(opt.PluginName, opt.Config),
|
||||
clientID: opt.ClientID,
|
||||
dialer: opt.Dialer,
|
||||
}
|
||||
}
|
||||
|
||||
// --- 网络操作 ---
|
||||
|
||||
// Dial 通过隧道建立连接
|
||||
func (c *ClientAPI) Dial(network, address string) (net.Conn, error) {
|
||||
if c.dialer == nil {
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
return c.dialer.Dial(network, address)
|
||||
}
|
||||
|
||||
// DialTimeout 带超时的连接(使用 context 避免 goroutine 泄漏)
|
||||
func (c *ClientAPI) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||
if c.dialer == nil {
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
type result struct {
|
||||
conn net.Conn
|
||||
err error
|
||||
}
|
||||
ch := make(chan result, 1)
|
||||
|
||||
go func() {
|
||||
conn, err := c.dialer.Dial(network, address)
|
||||
select {
|
||||
case ch <- result{conn, err}:
|
||||
case <-ctx.Done():
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case r := <-ch:
|
||||
return r.conn, r.err
|
||||
case <-ctx.Done():
|
||||
return nil, fmt.Errorf("dial timeout")
|
||||
}
|
||||
}
|
||||
|
||||
// Listen 客户端不支持监听
|
||||
func (c *ClientAPI) Listen(network, address string) (net.Listener, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// --- 端口管理(客户端不支持)---
|
||||
|
||||
// ReservePort 客户端不支持
|
||||
func (c *ClientAPI) ReservePort(port int) error {
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
// ReleasePort 客户端不支持
|
||||
func (c *ClientAPI) ReleasePort(port int) {}
|
||||
|
||||
// IsPortAvailable 检查本地端口是否可用
|
||||
func (c *ClientAPI) IsPortAvailable(port int) bool {
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ln.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
// --- 规则管理(客户端不支持)---
|
||||
|
||||
// CreateRule 客户端不支持
|
||||
func (c *ClientAPI) CreateRule(rule *RuleConfig) error {
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
// DeleteRule 客户端不支持
|
||||
func (c *ClientAPI) DeleteRule(clientID, ruleName string) error {
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
// GetRules 客户端不支持
|
||||
func (c *ClientAPI) GetRules(clientID string) ([]RuleConfig, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// UpdateRule 客户端不支持
|
||||
func (c *ClientAPI) UpdateRule(clientID string, rule *RuleConfig) error {
|
||||
return ErrNotSupported
|
||||
}
|
||||
|
||||
// --- 客户端管理 ---
|
||||
|
||||
// GetClientID 获取当前客户端 ID
|
||||
func (c *ClientAPI) GetClientID() string {
|
||||
return c.clientID
|
||||
}
|
||||
|
||||
// GetClientList 客户端不支持
|
||||
func (c *ClientAPI) GetClientList() ([]ClientInfo, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// IsClientOnline 客户端不支持
|
||||
func (c *ClientAPI) IsClientOnline(clientID string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// --- 上下文 ---
|
||||
|
||||
// GetContext 获取当前上下文
|
||||
func (c *ClientAPI) GetContext() *Context {
|
||||
return &Context{
|
||||
PluginName: c.getPluginName(),
|
||||
Side: SideClient,
|
||||
ClientID: c.clientID,
|
||||
Config: c.getConfigMap(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetServerInfo 客户端不支持
|
||||
func (c *ClientAPI) GetServerInfo() *ServerInfo {
|
||||
return nil
|
||||
}
|
||||
@@ -8,23 +8,23 @@ import (
|
||||
|
||||
// Registry 管理可用的 plugins
|
||||
type Registry struct {
|
||||
serverPlugins map[string]ProxyHandler // 服务端插件
|
||||
clientPlugins map[string]ClientHandler // 客户端插件
|
||||
enabled map[string]bool // 启用状态
|
||||
serverPlugins map[string]ServerPlugin // 服务端插件
|
||||
clientPlugins map[string]ClientPlugin // 客户端插件
|
||||
enabled map[string]bool // 启用状态
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewRegistry 创建 plugin 注册表
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
serverPlugins: make(map[string]ProxyHandler),
|
||||
clientPlugins: make(map[string]ClientHandler),
|
||||
serverPlugins: make(map[string]ServerPlugin),
|
||||
clientPlugins: make(map[string]ClientPlugin),
|
||||
enabled: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterBuiltin 注册服务端插件
|
||||
func (r *Registry) RegisterBuiltin(handler ProxyHandler) error {
|
||||
// RegisterServer 注册服务端插件
|
||||
func (r *Registry) RegisterServer(handler ServerPlugin) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
@@ -42,8 +42,8 @@ func (r *Registry) RegisterBuiltin(handler ProxyHandler) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterClientPlugin 注册客户端插件
|
||||
func (r *Registry) RegisterClientPlugin(handler ClientHandler) error {
|
||||
// RegisterClient 注册客户端插件
|
||||
func (r *Registry) RegisterClient(handler ClientPlugin) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
@@ -61,23 +61,22 @@ func (r *Registry) RegisterClientPlugin(handler ClientHandler) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 返回指定代理类型的服务端 handler
|
||||
func (r *Registry) Get(proxyType string) (ProxyHandler, error) {
|
||||
// GetServer 返回服务端插件
|
||||
func (r *Registry) GetServer(name string) (ServerPlugin, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if handler, ok := r.serverPlugins[proxyType]; ok {
|
||||
if !r.enabled[proxyType] {
|
||||
return nil, fmt.Errorf("plugin %s is disabled", proxyType)
|
||||
if handler, ok := r.serverPlugins[name]; ok {
|
||||
if !r.enabled[name] {
|
||||
return nil, fmt.Errorf("plugin %s is disabled", name)
|
||||
}
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("plugin %s not found", proxyType)
|
||||
return nil, fmt.Errorf("plugin %s not found", name)
|
||||
}
|
||||
|
||||
// GetClientPlugin 返回指定类型的客户端 handler
|
||||
func (r *Registry) GetClientPlugin(name string) (ClientHandler, error) {
|
||||
// GetClient 返回客户端插件
|
||||
func (r *Registry) GetClient(name string) (ClientPlugin, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
@@ -87,29 +86,26 @@ func (r *Registry) GetClientPlugin(name string) (ClientHandler, error) {
|
||||
}
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("client plugin %s not found", name)
|
||||
}
|
||||
|
||||
// List 返回所有可用的 plugins
|
||||
func (r *Registry) List() []PluginInfo {
|
||||
func (r *Registry) List() []Info {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
var plugins []PluginInfo
|
||||
var plugins []Info
|
||||
|
||||
// 服务端插件
|
||||
for name, handler := range r.serverPlugins {
|
||||
plugins = append(plugins, PluginInfo{
|
||||
plugins = append(plugins, Info{
|
||||
Metadata: handler.Metadata(),
|
||||
Loaded: true,
|
||||
Enabled: r.enabled[name],
|
||||
})
|
||||
}
|
||||
|
||||
// 客户端插件
|
||||
for name, handler := range r.clientPlugins {
|
||||
plugins = append(plugins, PluginInfo{
|
||||
plugins = append(plugins, Info{
|
||||
Metadata: handler.Metadata(),
|
||||
Loaded: true,
|
||||
Enabled: r.enabled[name],
|
||||
@@ -187,10 +183,10 @@ func (r *Registry) IsEnabled(name string) bool {
|
||||
return r.enabled[name]
|
||||
}
|
||||
|
||||
// RegisterAll 批量注册插件
|
||||
func (r *Registry) RegisterAll(handlers []ProxyHandler) error {
|
||||
// RegisterAllServer 批量注册服务端插件
|
||||
func (r *Registry) RegisterAllServer(handlers []ServerPlugin) error {
|
||||
for _, handler := range handlers {
|
||||
if err := r.RegisterBuiltin(handler); err != nil {
|
||||
if err := r.RegisterServer(handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
412
pkg/plugin/script/js.go
Normal file
412
pkg/plugin/script/js.go
Normal file
@@ -0,0 +1,412 @@
|
||||
package script
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/gotunnel/pkg/plugin"
|
||||
)
|
||||
|
||||
// JSPlugin JavaScript 脚本插件
|
||||
type JSPlugin struct {
|
||||
name string
|
||||
source string
|
||||
vm *goja.Runtime
|
||||
metadata plugin.Metadata
|
||||
config map[string]string
|
||||
running bool
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewJSPlugin 从 JS 源码创建插件
|
||||
func NewJSPlugin(name, source string) (*JSPlugin, error) {
|
||||
p := &JSPlugin{
|
||||
name: name,
|
||||
source: source,
|
||||
vm: goja.New(),
|
||||
}
|
||||
|
||||
if err := p.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// init 初始化 JS 运行时
|
||||
func (p *JSPlugin) init() error {
|
||||
// 注入基础 API
|
||||
p.vm.Set("log", p.jsLog)
|
||||
p.vm.Set("config", p.jsGetConfig)
|
||||
|
||||
// 注入文件 API
|
||||
p.vm.Set("fs", p.createFsAPI())
|
||||
|
||||
// 注入 HTTP API
|
||||
p.vm.Set("http", p.createHttpAPI())
|
||||
|
||||
// 执行脚本
|
||||
_, err := p.vm.RunString(p.source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("run script: %w", err)
|
||||
}
|
||||
|
||||
// 获取元数据
|
||||
if err := p.loadMetadata(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadMetadata 从 JS 获取元数据
|
||||
func (p *JSPlugin) loadMetadata() error {
|
||||
fn, ok := goja.AssertFunction(p.vm.Get("metadata"))
|
||||
if !ok {
|
||||
// 使用默认元数据
|
||||
p.metadata = plugin.Metadata{
|
||||
Name: p.name,
|
||||
Type: plugin.PluginTypeApp,
|
||||
Source: plugin.PluginSourceScript,
|
||||
RunAt: plugin.SideClient,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := fn(goja.Undefined())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj := result.ToObject(p.vm)
|
||||
p.metadata = plugin.Metadata{
|
||||
Name: getString(obj, "name", p.name),
|
||||
Version: getString(obj, "version", "1.0.0"),
|
||||
Type: plugin.PluginType(getString(obj, "type", "app")),
|
||||
Source: plugin.PluginSourceScript,
|
||||
RunAt: plugin.Side(getString(obj, "run_at", "client")),
|
||||
Description: getString(obj, "description", ""),
|
||||
Author: getString(obj, "author", ""),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Metadata 返回插件元数据
|
||||
func (p *JSPlugin) Metadata() plugin.Metadata {
|
||||
return p.metadata
|
||||
}
|
||||
|
||||
// Init 初始化插件配置
|
||||
func (p *JSPlugin) Init(config map[string]string) error {
|
||||
p.config = config
|
||||
p.vm.Set("config", config)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start 启动插件
|
||||
func (p *JSPlugin) Start() (string, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.running {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
fn, ok := goja.AssertFunction(p.vm.Get("start"))
|
||||
if ok {
|
||||
_, err := fn(goja.Undefined())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
p.running = true
|
||||
return "script-plugin", nil
|
||||
}
|
||||
|
||||
// HandleConn 处理连接
|
||||
func (p *JSPlugin) HandleConn(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
|
||||
// 创建连接包装器
|
||||
jsConn := newJSConn(conn)
|
||||
p.vm.Set("conn", jsConn)
|
||||
|
||||
fn, ok := goja.AssertFunction(p.vm.Get("handleConn"))
|
||||
if !ok {
|
||||
return fmt.Errorf("handleConn not defined")
|
||||
}
|
||||
|
||||
_, err := fn(goja.Undefined(), p.vm.ToValue(jsConn))
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop 停止插件
|
||||
func (p *JSPlugin) Stop() error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if !p.running {
|
||||
return nil
|
||||
}
|
||||
|
||||
fn, ok := goja.AssertFunction(p.vm.Get("stop"))
|
||||
if ok {
|
||||
fn(goja.Undefined())
|
||||
}
|
||||
|
||||
p.running = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// jsLog JS 日志函数
|
||||
func (p *JSPlugin) jsLog(msg string) {
|
||||
fmt.Printf("[JS:%s] %s\n", p.name, msg)
|
||||
}
|
||||
|
||||
// jsGetConfig 获取配置
|
||||
func (p *JSPlugin) jsGetConfig(key string) string {
|
||||
if p.config == nil {
|
||||
return ""
|
||||
}
|
||||
return p.config[key]
|
||||
}
|
||||
|
||||
// getString 从 JS 对象获取字符串
|
||||
func getString(obj *goja.Object, key, def string) string {
|
||||
v := obj.Get(key)
|
||||
if v == nil || goja.IsUndefined(v) {
|
||||
return def
|
||||
}
|
||||
return v.String()
|
||||
}
|
||||
|
||||
// jsConn JS 连接包装器
|
||||
type jsConn struct {
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
func newJSConn(conn net.Conn) *jsConn {
|
||||
return &jsConn{conn: conn}
|
||||
}
|
||||
|
||||
func (c *jsConn) Read(size int) []byte {
|
||||
buf := make([]byte, size)
|
||||
n, err := c.conn.Read(buf)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return buf[:n]
|
||||
}
|
||||
|
||||
func (c *jsConn) Write(data []byte) int {
|
||||
n, _ := c.conn.Write(data)
|
||||
return n
|
||||
}
|
||||
|
||||
func (c *jsConn) Close() {
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 文件系统 API
|
||||
// =============================================================================
|
||||
|
||||
// createFsAPI 创建文件系统 API
|
||||
func (p *JSPlugin) createFsAPI() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"readFile": p.fsReadFile,
|
||||
"writeFile": p.fsWriteFile,
|
||||
"readDir": p.fsReadDir,
|
||||
"stat": p.fsStat,
|
||||
"exists": p.fsExists,
|
||||
"mkdir": p.fsMkdir,
|
||||
"remove": p.fsRemove,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *JSPlugin) fsReadFile(path string) string {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (p *JSPlugin) fsWriteFile(path, content string) bool {
|
||||
return os.WriteFile(path, []byte(content), 0644) == nil
|
||||
}
|
||||
|
||||
func (p *JSPlugin) fsReadDir(path string) []map[string]interface{} {
|
||||
entries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var result []map[string]interface{}
|
||||
for _, e := range entries {
|
||||
info, _ := e.Info()
|
||||
result = append(result, map[string]interface{}{
|
||||
"name": e.Name(),
|
||||
"isDir": e.IsDir(),
|
||||
"size": info.Size(),
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *JSPlugin) fsStat(path string) map[string]interface{} {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"name": info.Name(),
|
||||
"size": info.Size(),
|
||||
"isDir": info.IsDir(),
|
||||
"modTime": info.ModTime().Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *JSPlugin) fsExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (p *JSPlugin) fsMkdir(path string) bool {
|
||||
return os.MkdirAll(path, 0755) == nil
|
||||
}
|
||||
|
||||
func (p *JSPlugin) fsRemove(path string) bool {
|
||||
return os.RemoveAll(path) == nil
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HTTP 服务 API
|
||||
// =============================================================================
|
||||
|
||||
// createHttpAPI 创建 HTTP API
|
||||
func (p *JSPlugin) createHttpAPI() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"serve": p.httpServe,
|
||||
"json": p.httpJSON,
|
||||
"sendFile": p.httpSendFile,
|
||||
}
|
||||
}
|
||||
|
||||
// httpServe 启动 HTTP 服务处理连接
|
||||
func (p *JSPlugin) httpServe(conn net.Conn, handler func(map[string]interface{}) map[string]interface{}) {
|
||||
defer conn.Close()
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req := parseHTTPRequest(buf[:n])
|
||||
resp := handler(req)
|
||||
writeHTTPResponse(conn, resp)
|
||||
}
|
||||
|
||||
func (p *JSPlugin) httpJSON(data interface{}) string {
|
||||
b, _ := json.Marshal(data)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (p *JSPlugin) httpSendFile(conn net.Conn, filePath string) {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
conn.Write([]byte("HTTP/1.1 404 Not Found\r\n\r\n"))
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
info, _ := f.Stat()
|
||||
contentType := getContentType(filePath)
|
||||
|
||||
header := fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
|
||||
contentType, info.Size())
|
||||
conn.Write([]byte(header))
|
||||
io.Copy(conn, f)
|
||||
}
|
||||
|
||||
// parseHTTPRequest 解析 HTTP 请求
|
||||
func parseHTTPRequest(data []byte) map[string]interface{} {
|
||||
lines := string(data)
|
||||
req := map[string]interface{}{
|
||||
"method": "GET",
|
||||
"path": "/",
|
||||
"body": "",
|
||||
}
|
||||
|
||||
// 解析请求行
|
||||
if idx := indexOf(lines, " "); idx > 0 {
|
||||
req["method"] = lines[:idx]
|
||||
rest := lines[idx+1:]
|
||||
if idx2 := indexOf(rest, " "); idx2 > 0 {
|
||||
req["path"] = rest[:idx2]
|
||||
}
|
||||
}
|
||||
|
||||
// 解析 body
|
||||
if idx := indexOf(lines, "\r\n\r\n"); idx > 0 {
|
||||
req["body"] = lines[idx+4:]
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
// writeHTTPResponse 写入 HTTP 响应
|
||||
func writeHTTPResponse(conn net.Conn, resp map[string]interface{}) {
|
||||
status := 200
|
||||
if s, ok := resp["status"].(int); ok {
|
||||
status = s
|
||||
}
|
||||
|
||||
body := ""
|
||||
if b, ok := resp["body"].(string); ok {
|
||||
body = b
|
||||
}
|
||||
|
||||
contentType := "application/json"
|
||||
if ct, ok := resp["contentType"].(string); ok {
|
||||
contentType = ct
|
||||
}
|
||||
|
||||
header := fmt.Sprintf("HTTP/1.1 %d OK\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
|
||||
status, contentType, len(body))
|
||||
conn.Write([]byte(header + body))
|
||||
}
|
||||
|
||||
func indexOf(s, substr string) int {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func getContentType(path string) string {
|
||||
ext := filepath.Ext(path)
|
||||
types := map[string]string{
|
||||
".html": "text/html",
|
||||
".css": "text/css",
|
||||
".js": "application/javascript",
|
||||
".json": "application/json",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".txt": "text/plain",
|
||||
}
|
||||
if ct, ok := types[ext]; ok {
|
||||
return ct
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 服务端依赖接口(依赖注入)
|
||||
// =============================================================================
|
||||
|
||||
// PortStore 端口存储接口
|
||||
type PortStore interface {
|
||||
Reserve(port int, owner string) error
|
||||
Release(port int)
|
||||
IsAvailable(port int) bool
|
||||
}
|
||||
|
||||
// RuleStore 规则存储接口
|
||||
type RuleStore interface {
|
||||
GetAll(clientID string) ([]RuleConfig, error)
|
||||
Create(clientID string, rule *RuleConfig) error
|
||||
Update(clientID string, rule *RuleConfig) error
|
||||
Delete(clientID, ruleName string) error
|
||||
}
|
||||
|
||||
// ClientStore 客户端存储接口
|
||||
type ClientStore interface {
|
||||
GetAll() ([]ClientInfo, error)
|
||||
IsOnline(clientID string) bool
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 服务端 API 实现
|
||||
// =============================================================================
|
||||
|
||||
// ServerAPI 服务端 PluginAPI 实现
|
||||
type ServerAPI struct {
|
||||
*baseAPI
|
||||
portStore PortStore
|
||||
ruleStore RuleStore
|
||||
clientStore ClientStore
|
||||
serverInfo *ServerInfo
|
||||
}
|
||||
|
||||
// ServerAPIOption 服务端 API 配置选项
|
||||
type ServerAPIOption struct {
|
||||
PluginName string
|
||||
Config map[string]string
|
||||
PortStore PortStore
|
||||
RuleStore RuleStore
|
||||
ClientStore ClientStore
|
||||
ServerInfo *ServerInfo
|
||||
}
|
||||
|
||||
// NewServerAPI 创建服务端 API
|
||||
func NewServerAPI(opt ServerAPIOption) *ServerAPI {
|
||||
return &ServerAPI{
|
||||
baseAPI: newBaseAPI(opt.PluginName, opt.Config),
|
||||
portStore: opt.PortStore,
|
||||
ruleStore: opt.RuleStore,
|
||||
clientStore: opt.ClientStore,
|
||||
serverInfo: opt.ServerInfo,
|
||||
}
|
||||
}
|
||||
|
||||
// --- 网络操作 ---
|
||||
|
||||
// Dial 服务端不支持隧道拨号
|
||||
func (s *ServerAPI) Dial(network, address string) (net.Conn, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// DialTimeout 服务端不支持隧道拨号
|
||||
func (s *ServerAPI) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// Listen 在指定地址监听
|
||||
func (s *ServerAPI) Listen(network, address string) (net.Listener, error) {
|
||||
return net.Listen(network, address)
|
||||
}
|
||||
|
||||
// --- 端口管理 ---
|
||||
|
||||
// ReservePort 预留端口
|
||||
func (s *ServerAPI) ReservePort(port int) error {
|
||||
if s.portStore == nil {
|
||||
return ErrNotSupported
|
||||
}
|
||||
return s.portStore.Reserve(port, s.getPluginName())
|
||||
}
|
||||
|
||||
// ReleasePort 释放端口
|
||||
func (s *ServerAPI) ReleasePort(port int) {
|
||||
if s.portStore != nil {
|
||||
s.portStore.Release(port)
|
||||
}
|
||||
}
|
||||
|
||||
// IsPortAvailable 检查端口是否可用
|
||||
func (s *ServerAPI) IsPortAvailable(port int) bool {
|
||||
if s.portStore == nil {
|
||||
return false
|
||||
}
|
||||
return s.portStore.IsAvailable(port)
|
||||
}
|
||||
|
||||
// --- 规则管理 ---
|
||||
|
||||
// CreateRule 创建代理规则
|
||||
func (s *ServerAPI) CreateRule(rule *RuleConfig) error {
|
||||
if s.ruleStore == nil {
|
||||
return ErrNotSupported
|
||||
}
|
||||
return s.ruleStore.Create(rule.ClientID, rule)
|
||||
}
|
||||
|
||||
// DeleteRule 删除代理规则
|
||||
func (s *ServerAPI) DeleteRule(clientID, ruleName string) error {
|
||||
if s.ruleStore == nil {
|
||||
return ErrNotSupported
|
||||
}
|
||||
return s.ruleStore.Delete(clientID, ruleName)
|
||||
}
|
||||
|
||||
// GetRules 获取客户端的代理规则
|
||||
func (s *ServerAPI) GetRules(clientID string) ([]RuleConfig, error) {
|
||||
if s.ruleStore == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.ruleStore.GetAll(clientID)
|
||||
}
|
||||
|
||||
// UpdateRule 更新代理规则
|
||||
func (s *ServerAPI) UpdateRule(clientID string, rule *RuleConfig) error {
|
||||
if s.ruleStore == nil {
|
||||
return ErrNotSupported
|
||||
}
|
||||
return s.ruleStore.Update(clientID, rule)
|
||||
}
|
||||
|
||||
// --- 客户端管理 ---
|
||||
|
||||
// GetClientID 服务端返回空
|
||||
func (s *ServerAPI) GetClientID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetClientList 获取所有客户端列表
|
||||
func (s *ServerAPI) GetClientList() ([]ClientInfo, error) {
|
||||
if s.clientStore == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.clientStore.GetAll()
|
||||
}
|
||||
|
||||
// IsClientOnline 检查客户端是否在线
|
||||
func (s *ServerAPI) IsClientOnline(clientID string) bool {
|
||||
if s.clientStore == nil {
|
||||
return false
|
||||
}
|
||||
return s.clientStore.IsOnline(clientID)
|
||||
}
|
||||
|
||||
// --- 上下文 ---
|
||||
|
||||
// GetContext 获取当前上下文
|
||||
func (s *ServerAPI) GetContext() *Context {
|
||||
return &Context{
|
||||
PluginName: s.getPluginName(),
|
||||
Side: SideServer,
|
||||
Config: s.getConfigMap(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetServerInfo 获取服务端信息
|
||||
func (s *ServerAPI) GetServerInfo() *ServerInfo {
|
||||
return s.serverInfo
|
||||
}
|
||||
@@ -5,138 +5,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// PluginType 定义 plugin 类别
|
||||
type PluginType string
|
||||
|
||||
const (
|
||||
PluginTypeProxy PluginType = "proxy" // 代理协议插件 (SOCKS5 等)
|
||||
PluginTypeApp PluginType = "app" // 应用插件 (VNC, 文件管理等)
|
||||
PluginTypeService PluginType = "service" // 服务插件 (Web服务等)
|
||||
PluginTypeTool PluginType = "tool" // 工具插件 (监控、日志等)
|
||||
)
|
||||
|
||||
// PluginSource 表示 plugin 来源
|
||||
type PluginSource string
|
||||
|
||||
const (
|
||||
PluginSourceBuiltin PluginSource = "builtin" // 内置编译
|
||||
)
|
||||
|
||||
// ConfigFieldType 配置字段类型
|
||||
type ConfigFieldType string
|
||||
|
||||
const (
|
||||
ConfigFieldString ConfigFieldType = "string"
|
||||
ConfigFieldNumber ConfigFieldType = "number"
|
||||
ConfigFieldBool ConfigFieldType = "bool"
|
||||
ConfigFieldSelect ConfigFieldType = "select" // 下拉选择
|
||||
ConfigFieldPassword ConfigFieldType = "password" // 密码输入
|
||||
)
|
||||
|
||||
// ConfigField 配置字段定义
|
||||
type ConfigField struct {
|
||||
Key string `json:"key"` // 配置键名
|
||||
Label string `json:"label"` // 显示标签
|
||||
Type ConfigFieldType `json:"type"` // 字段类型
|
||||
Default string `json:"default,omitempty"` // 默认值
|
||||
Required bool `json:"required,omitempty"` // 是否必填
|
||||
Options []string `json:"options,omitempty"` // select 类型的选项
|
||||
Description string `json:"description,omitempty"` // 字段描述
|
||||
}
|
||||
|
||||
// RuleSchema 规则表单模式定义
|
||||
type RuleSchema struct {
|
||||
NeedsLocalAddr bool `json:"needs_local_addr"` // 是否需要本地地址
|
||||
ExtraFields []ConfigField `json:"extra_fields,omitempty"` // 额外字段
|
||||
}
|
||||
|
||||
// PluginMetadata 描述一个 plugin
|
||||
type PluginMetadata struct {
|
||||
Name string `json:"name"` // 唯一标识符
|
||||
Version string `json:"version"` // 语义化版本
|
||||
Type PluginType `json:"type"` // Plugin 类别
|
||||
Source PluginSource `json:"source"` // builtin
|
||||
RunAt Side `json:"run_at"` // 运行位置: server 或 client
|
||||
Description string `json:"description"` // 人类可读描述
|
||||
Author string `json:"author"` // Plugin 作者
|
||||
Icon string `json:"icon,omitempty"` // 图标文件名
|
||||
Capabilities []string `json:"capabilities,omitempty"` // 所需能力
|
||||
ConfigSchema []ConfigField `json:"config_schema,omitempty"` // 插件配置模式
|
||||
RuleSchema *RuleSchema `json:"rule_schema,omitempty"` // 规则表单模式
|
||||
}
|
||||
|
||||
// PluginInfo 组合元数据和运行时状态
|
||||
type PluginInfo struct {
|
||||
Metadata PluginMetadata `json:"metadata"`
|
||||
Loaded bool `json:"loaded"`
|
||||
Enabled bool `json:"enabled"`
|
||||
LoadedAt time.Time `json:"loaded_at,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Dialer 用于建立连接的接口
|
||||
type Dialer interface {
|
||||
Dial(network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
// ProxyHandler 是所有 proxy plugin 必须实现的接口
|
||||
// 运行在服务端,处理外部连接并通过隧道转发
|
||||
type ProxyHandler interface {
|
||||
// Metadata 返回 plugin 信息
|
||||
Metadata() PluginMetadata
|
||||
|
||||
// Init 使用配置初始化 plugin
|
||||
Init(config map[string]string) error
|
||||
|
||||
// HandleConn 处理传入连接
|
||||
// dialer 用于通过隧道建立连接
|
||||
HandleConn(conn net.Conn, dialer Dialer) error
|
||||
|
||||
// Close 释放 plugin 资源
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ClientHandler 客户端插件接口
|
||||
// 运行在客户端,提供本地服务(如 VNC 服务器、文件管理等)
|
||||
type ClientHandler interface {
|
||||
// Metadata 返回 plugin 信息
|
||||
Metadata() PluginMetadata
|
||||
|
||||
// Init 使用配置初始化 plugin
|
||||
Init(config map[string]string) error
|
||||
|
||||
// Start 启动客户端服务
|
||||
// 返回服务监听的本地地址(如 "127.0.0.1:5900")
|
||||
Start() (localAddr string, err error)
|
||||
|
||||
// HandleConn 处理来自隧道的连接
|
||||
HandleConn(conn net.Conn) error
|
||||
|
||||
// Stop 停止客户端服务
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// ExtendedProxyHandler 扩展的代理处理器接口
|
||||
// 支持 PluginAPI 的插件应实现此接口
|
||||
type ExtendedProxyHandler interface {
|
||||
ProxyHandler
|
||||
|
||||
// SetAPI 设置 PluginAPI,允许插件调用系统功能
|
||||
SetAPI(api PluginAPI)
|
||||
}
|
||||
|
||||
// LogLevel 日志级别
|
||||
type LogLevel uint8
|
||||
|
||||
const (
|
||||
LogDebug LogLevel = iota
|
||||
LogInfo
|
||||
LogWarn
|
||||
LogError
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// API 相关类型
|
||||
// 基础类型
|
||||
// =============================================================================
|
||||
|
||||
// Side 运行侧
|
||||
@@ -147,86 +17,104 @@ const (
|
||||
SideClient Side = "client"
|
||||
)
|
||||
|
||||
// Context 插件运行上下文
|
||||
type Context struct {
|
||||
PluginName string
|
||||
Side Side
|
||||
ClientID string
|
||||
Config map[string]string
|
||||
}
|
||||
|
||||
// ServerInfo 服务端信息
|
||||
type ServerInfo struct {
|
||||
BindAddr string
|
||||
BindPort int
|
||||
Version string
|
||||
}
|
||||
|
||||
// RuleConfig 代理规则配置
|
||||
type RuleConfig struct {
|
||||
ClientID string `json:"client_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
LocalIP string `json:"local_ip"`
|
||||
LocalPort int `json:"local_port"`
|
||||
RemotePort int `json:"remote_port"`
|
||||
Enabled bool `json:"enabled"`
|
||||
PluginName string `json:"plugin_name,omitempty"`
|
||||
PluginConfig map[string]string `json:"plugin_config,omitempty"`
|
||||
}
|
||||
|
||||
// ClientInfo 客户端信息
|
||||
type ClientInfo struct {
|
||||
ID string `json:"id"`
|
||||
Nickname string `json:"nickname"`
|
||||
Online bool `json:"online"`
|
||||
LastPing string `json:"last_ping,omitempty"`
|
||||
}
|
||||
|
||||
// EventType 事件类型
|
||||
type EventType string
|
||||
// PluginType 插件类别
|
||||
type PluginType string
|
||||
|
||||
const (
|
||||
EventClientConnect EventType = "client_connect"
|
||||
EventClientDisconnect EventType = "client_disconnect"
|
||||
EventRuleCreated EventType = "rule_created"
|
||||
EventRuleDeleted EventType = "rule_deleted"
|
||||
EventProxyConnect EventType = "proxy_connect"
|
||||
EventProxyDisconnect EventType = "proxy_disconnect"
|
||||
PluginTypeProxy PluginType = "proxy" // 代理协议 (SOCKS5 等)
|
||||
PluginTypeApp PluginType = "app" // 应用服务 (VNC, Echo 等)
|
||||
)
|
||||
|
||||
// Event 事件
|
||||
type Event struct {
|
||||
Type EventType `json:"type"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
// PluginSource 插件来源
|
||||
type PluginSource string
|
||||
|
||||
// EventHandler 事件处理函数
|
||||
type EventHandler func(event *Event)
|
||||
|
||||
// =============================================================================
|
||||
// 错误定义
|
||||
// =============================================================================
|
||||
|
||||
// APIError API 错误
|
||||
type APIError struct {
|
||||
Code int
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *APIError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// 常见 API 错误
|
||||
var (
|
||||
ErrNotSupported = &APIError{Code: 1, Message: "operation not supported"}
|
||||
ErrClientNotFound = &APIError{Code: 2, Message: "client not found"}
|
||||
ErrPortOccupied = &APIError{Code: 3, Message: "port already occupied"}
|
||||
ErrRuleNotFound = &APIError{Code: 4, Message: "rule not found"}
|
||||
ErrRuleExists = &APIError{Code: 5, Message: "rule already exists"}
|
||||
ErrNotConnected = &APIError{Code: 6, Message: "not connected"}
|
||||
ErrInvalidConfig = &APIError{Code: 7, Message: "invalid configuration"}
|
||||
const (
|
||||
PluginSourceBuiltin PluginSource = "builtin" // 内置编译
|
||||
PluginSourceScript PluginSource = "script" // 脚本插件
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// 配置相关
|
||||
// =============================================================================
|
||||
|
||||
// ConfigFieldType 配置字段类型
|
||||
type ConfigFieldType string
|
||||
|
||||
const (
|
||||
ConfigFieldString ConfigFieldType = "string"
|
||||
ConfigFieldNumber ConfigFieldType = "number"
|
||||
ConfigFieldBool ConfigFieldType = "bool"
|
||||
ConfigFieldSelect ConfigFieldType = "select"
|
||||
ConfigFieldPassword ConfigFieldType = "password"
|
||||
)
|
||||
|
||||
// ConfigField 配置字段定义
|
||||
type ConfigField struct {
|
||||
Key string `json:"key"`
|
||||
Label string `json:"label"`
|
||||
Type ConfigFieldType `json:"type"`
|
||||
Default string `json:"default,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Options []string `json:"options,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// RuleSchema 规则表单模式
|
||||
type RuleSchema struct {
|
||||
NeedsLocalAddr bool `json:"needs_local_addr"`
|
||||
ExtraFields []ConfigField `json:"extra_fields,omitempty"`
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 元数据
|
||||
// =============================================================================
|
||||
|
||||
// Metadata 插件元数据
|
||||
type Metadata struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type PluginType `json:"type"`
|
||||
Source PluginSource `json:"source"`
|
||||
RunAt Side `json:"run_at"`
|
||||
Description string `json:"description"`
|
||||
Author string `json:"author,omitempty"`
|
||||
ConfigSchema []ConfigField `json:"config_schema,omitempty"`
|
||||
RuleSchema *RuleSchema `json:"rule_schema,omitempty"`
|
||||
}
|
||||
|
||||
// Info 插件运行时信息
|
||||
type Info struct {
|
||||
Metadata Metadata `json:"metadata"`
|
||||
Loaded bool `json:"loaded"`
|
||||
Enabled bool `json:"enabled"`
|
||||
LoadedAt time.Time `json:"loaded_at,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 核心接口
|
||||
// =============================================================================
|
||||
|
||||
// Dialer 网络拨号接口
|
||||
type Dialer interface {
|
||||
Dial(network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
// ServerPlugin 服务端插件接口
|
||||
// 运行在服务端,处理外部连接并通过隧道转发到客户端
|
||||
type ServerPlugin interface {
|
||||
Metadata() Metadata
|
||||
Init(config map[string]string) error
|
||||
HandleConn(conn net.Conn, dialer Dialer) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ClientPlugin 客户端插件接口
|
||||
// 运行在客户端,提供本地服务
|
||||
type ClientPlugin interface {
|
||||
Metadata() Metadata
|
||||
Init(config map[string]string) error
|
||||
Start() (localAddr string, err error)
|
||||
HandleConn(conn net.Conn) error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ const (
|
||||
MsgTypeClientPluginStop uint8 = 41 // 停止客户端插件
|
||||
MsgTypeClientPluginStatus uint8 = 42 // 客户端插件状态
|
||||
MsgTypeClientPluginConn uint8 = 43 // 客户端插件连接请求
|
||||
|
||||
// JS 插件动态安装
|
||||
MsgTypeJSPluginInstall uint8 = 50 // 安装 JS 插件
|
||||
MsgTypeJSPluginResult uint8 = 51 // 安装结果
|
||||
)
|
||||
|
||||
// Message 基础消息结构
|
||||
@@ -204,6 +208,23 @@ type ClientPluginConnRequest struct {
|
||||
RuleName string `json:"rule_name"` // 规则名称
|
||||
}
|
||||
|
||||
// JSPluginInstallRequest JS 插件安装请求
|
||||
type JSPluginInstallRequest struct {
|
||||
PluginName string `json:"plugin_name"` // 插件名称
|
||||
Source string `json:"source"` // JS 源码
|
||||
RuleName string `json:"rule_name"` // 规则名称
|
||||
RemotePort int `json:"remote_port"` // 服务端监听端口
|
||||
Config map[string]string `json:"config"` // 插件配置
|
||||
AutoStart bool `json:"auto_start"` // 是否自动启动
|
||||
}
|
||||
|
||||
// JSPluginInstallResult JS 插件安装结果
|
||||
type JSPluginInstallResult struct {
|
||||
PluginName string `json:"plugin_name"`
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// WriteMessage 写入消息到 writer
|
||||
func WriteMessage(w io.Writer, msg *Message) error {
|
||||
header := make([]byte, HeaderSize)
|
||||
|
||||
Reference in New Issue
Block a user