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

This commit is contained in:
Flik
2025-12-29 14:24:46 +08:00
parent c728cc3bb7
commit e10736e05e
23 changed files with 591 additions and 910 deletions

View File

@@ -0,0 +1,95 @@
package builtin
import (
"io"
"log"
"net"
"sync"
"github.com/gotunnel/pkg/plugin"
)
func init() {
RegisterClientPlugin(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.PluginMetadata {
return plugin.PluginMetadata{
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
}

View File

@@ -1,116 +0,0 @@
package builtin
import (
"bufio"
"io"
"net"
"net/http"
"strings"
"github.com/gotunnel/pkg/plugin"
)
// HTTPPlugin 将现有 HTTP 代理实现封装为 plugin
type HTTPPlugin struct {
config map[string]string
}
// NewHTTPPlugin 创建 HTTP plugin
func NewHTTPPlugin() *HTTPPlugin {
return &HTTPPlugin{}
}
// Metadata 返回 plugin 信息
func (p *HTTPPlugin) Metadata() plugin.PluginMetadata {
return plugin.PluginMetadata{
Name: "http",
Version: "1.0.0",
Type: plugin.PluginTypeProxy,
Source: plugin.PluginSourceBuiltin,
Description: "HTTP/HTTPS proxy protocol handler",
Author: "GoTunnel",
Capabilities: []string{
"dial", "read", "write", "close",
},
}
}
// Init 初始化 plugin
func (p *HTTPPlugin) Init(config map[string]string) error {
p.config = config
return nil
}
// HandleConn 处理 HTTP 代理连接
func (p *HTTPPlugin) HandleConn(conn net.Conn, dialer plugin.Dialer) error {
defer conn.Close()
reader := bufio.NewReader(conn)
req, err := http.ReadRequest(reader)
if err != nil {
return err
}
if req.Method == http.MethodConnect {
return p.handleConnect(conn, req, dialer)
}
return p.handleHTTP(conn, req, dialer)
}
// Close 释放资源
func (p *HTTPPlugin) Close() error {
return nil
}
// handleConnect 处理 CONNECT 方法 (HTTPS)
func (p *HTTPPlugin) handleConnect(conn net.Conn, req *http.Request, dialer plugin.Dialer) error {
target := req.Host
if !strings.Contains(target, ":") {
target = target + ":443"
}
remote, err := dialer.Dial("tcp", target)
if err != nil {
conn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
return err
}
defer remote.Close()
conn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
go io.Copy(remote, conn)
io.Copy(conn, remote)
return nil
}
// handleHTTP 处理普通 HTTP 请求
func (p *HTTPPlugin) handleHTTP(conn net.Conn, req *http.Request, dialer plugin.Dialer) error {
target := req.Host
if !strings.Contains(target, ":") {
target = target + ":80"
}
remote, err := dialer.Dial("tcp", target)
if err != nil {
conn.Write([]byte("HTTP/1.1 502 Bad Gateway\r\n\r\n"))
return err
}
defer remote.Close()
// 修改请求路径为相对路径
req.URL.Scheme = ""
req.URL.Host = ""
req.RequestURI = req.URL.Path
if req.URL.RawQuery != "" {
req.RequestURI += "?" + req.URL.RawQuery
}
// 发送请求到目标
if err := req.Write(remote); err != nil {
return err
}
// 转发响应
_, err = io.Copy(conn, remote)
return err
}

View File

@@ -3,14 +3,27 @@ package builtin
import "github.com/gotunnel/pkg/plugin"
// 全局插件注册表
var registry []plugin.ProxyHandler
var (
serverPlugins []plugin.ProxyHandler
clientPlugins []plugin.ClientHandler
)
// Register 插件自注册函数,由各插件的 init() 调用
// Register 注册服务端插件
func Register(handler plugin.ProxyHandler) {
registry = append(registry, handler)
serverPlugins = append(serverPlugins, handler)
}
// GetAll 返回所有已注册的内置插件
func GetAll() []plugin.ProxyHandler {
return registry
// RegisterClientPlugin 注册客户端插件
func RegisterClientPlugin(handler plugin.ClientHandler) {
clientPlugins = append(clientPlugins, handler)
}
// GetAll 返回所有服务端插件
func GetAll() []plugin.ProxyHandler {
return serverPlugins
}
// GetAllClientPlugins 返回所有客户端插件
func GetAllClientPlugins() []plugin.ClientHandler {
return clientPlugins
}

View File

@@ -45,34 +45,29 @@ func (p *SOCKS5Plugin) Metadata() plugin.PluginMetadata {
Version: "1.0.0",
Type: plugin.PluginTypeProxy,
Source: plugin.PluginSourceBuiltin,
Description: "SOCKS5 proxy protocol handler (official plugin)",
RunAt: plugin.SideServer,
Description: "SOCKS5 proxy protocol handler",
Author: "GoTunnel",
Capabilities: []string{
"dial", "read", "write", "close",
},
RuleSchema: &plugin.RuleSchema{
NeedsLocalAddr: false, // SOCKS5 不需要本地地址
NeedsLocalAddr: false,
},
ConfigSchema: []plugin.ConfigField{
{
Key: "auth",
Label: "认证方式",
Type: plugin.ConfigFieldSelect,
Default: "none",
Options: []string{"none", "password"},
Description: "SOCKS5 认证方式",
Key: "auth",
Label: "认证方式",
Type: plugin.ConfigFieldSelect,
Default: "none",
Options: []string{"none", "password"},
},
{
Key: "username",
Label: "用户名",
Type: plugin.ConfigFieldString,
Description: "认证用户名(仅 password 认证时需要)",
Key: "username",
Label: "用户名",
Type: plugin.ConfigFieldString,
},
{
Key: "password",
Label: "密码",
Type: plugin.ConfigFieldPassword,
Description: "认证密码(仅 password 认证时需要)",
Key: "password",
Label: "密码",
Type: plugin.ConfigFieldPassword,
},
},
}

View File

@@ -29,20 +29,17 @@ func (p *VNCPlugin) Metadata() plugin.PluginMetadata {
Version: "1.0.0",
Type: plugin.PluginTypeApp,
Source: plugin.PluginSourceBuiltin,
Description: "VNC remote desktop relay (connects to client's local VNC server)",
RunAt: plugin.SideServer, // 当前为服务端中继模式
Description: "VNC remote desktop relay",
Author: "GoTunnel",
Capabilities: []string{
"dial", "read", "write", "close",
},
RuleSchema: &plugin.RuleSchema{
NeedsLocalAddr: false,
ExtraFields: []plugin.ConfigField{
{
Key: "vnc_addr",
Label: "VNC 地址",
Type: plugin.ConfigFieldString,
Default: "127.0.0.1:5900",
Description: "客户端本地 VNC 服务地址",
Key: "vnc_addr",
Label: "VNC 地址",
Type: plugin.ConfigFieldString,
Default: "127.0.0.1:5900",
},
},
},

View File

@@ -8,20 +8,22 @@ import (
// Registry 管理可用的 plugins
type Registry struct {
builtin map[string]ProxyHandler // 内置 Go 实现
enabled map[string]bool // 启用状态
mu sync.RWMutex
serverPlugins map[string]ProxyHandler // 服务端插件
clientPlugins map[string]ClientHandler // 客户端插件
enabled map[string]bool // 启用状态
mu sync.RWMutex
}
// NewRegistry 创建 plugin 注册表
func NewRegistry() *Registry {
return &Registry{
builtin: make(map[string]ProxyHandler),
enabled: make(map[string]bool),
serverPlugins: make(map[string]ProxyHandler),
clientPlugins: make(map[string]ClientHandler),
enabled: make(map[string]bool),
}
}
// RegisterBuiltin 注册内置 plugin
// RegisterBuiltin 注册服务端插件
func (r *Registry) RegisterBuiltin(handler ProxyHandler) error {
r.mu.Lock()
defer r.mu.Unlock()
@@ -31,22 +33,40 @@ func (r *Registry) RegisterBuiltin(handler ProxyHandler) error {
return fmt.Errorf("plugin name cannot be empty")
}
if _, exists := r.builtin[meta.Name]; exists {
if _, exists := r.serverPlugins[meta.Name]; exists {
return fmt.Errorf("plugin %s already registered", meta.Name)
}
r.builtin[meta.Name] = handler
r.enabled[meta.Name] = true // 默认启用
r.serverPlugins[meta.Name] = handler
r.enabled[meta.Name] = true
return nil
}
// Get 返回指定代理类型的 handler
// RegisterClientPlugin 注册客户端插件
func (r *Registry) RegisterClientPlugin(handler ClientHandler) error {
r.mu.Lock()
defer r.mu.Unlock()
meta := handler.Metadata()
if meta.Name == "" {
return fmt.Errorf("plugin name cannot be empty")
}
if _, exists := r.clientPlugins[meta.Name]; exists {
return fmt.Errorf("client plugin %s already registered", meta.Name)
}
r.clientPlugins[meta.Name] = handler
r.enabled[meta.Name] = true
return nil
}
// Get 返回指定代理类型的服务端 handler
func (r *Registry) Get(proxyType string) (ProxyHandler, error) {
r.mu.RLock()
defer r.mu.RUnlock()
// 先查找内置 plugin
if handler, ok := r.builtin[proxyType]; ok {
if handler, ok := r.serverPlugins[proxyType]; ok {
if !r.enabled[proxyType] {
return nil, fmt.Errorf("plugin %s is disabled", proxyType)
}
@@ -56,6 +76,21 @@ func (r *Registry) Get(proxyType string) (ProxyHandler, error) {
return nil, fmt.Errorf("plugin %s not found", proxyType)
}
// GetClientPlugin 返回指定类型的客户端 handler
func (r *Registry) GetClientPlugin(name string) (ClientHandler, error) {
r.mu.RLock()
defer r.mu.RUnlock()
if handler, ok := r.clientPlugins[name]; ok {
if !r.enabled[name] {
return nil, fmt.Errorf("client plugin %s is disabled", name)
}
return handler, nil
}
return nil, fmt.Errorf("client plugin %s not found", name)
}
// List 返回所有可用的 plugins
func (r *Registry) List() []PluginInfo {
r.mu.RLock()
@@ -63,8 +98,17 @@ func (r *Registry) List() []PluginInfo {
var plugins []PluginInfo
// 内置 plugins
for name, handler := range r.builtin {
// 服务端插件
for name, handler := range r.serverPlugins {
plugins = append(plugins, PluginInfo{
Metadata: handler.Metadata(),
Loaded: true,
Enabled: r.enabled[name],
})
}
// 客户端插件
for name, handler := range r.clientPlugins {
plugins = append(plugins, PluginInfo{
Metadata: handler.Metadata(),
Loaded: true,
@@ -80,8 +124,9 @@ func (r *Registry) Has(name string) bool {
r.mu.RLock()
defer r.mu.RUnlock()
_, ok := r.builtin[name]
return ok
_, ok1 := r.serverPlugins[name]
_, ok2 := r.clientPlugins[name]
return ok1 || ok2
}
// Close 关闭所有 plugins
@@ -90,11 +135,16 @@ func (r *Registry) Close(ctx context.Context) error {
defer r.mu.Unlock()
var lastErr error
for name, handler := range r.builtin {
for name, handler := range r.serverPlugins {
if err := handler.Close(); err != nil {
lastErr = fmt.Errorf("failed to close plugin %s: %w", name, err)
}
}
for name, handler := range r.clientPlugins {
if err := handler.Stop(); err != nil {
lastErr = fmt.Errorf("failed to stop client plugin %s: %w", name, err)
}
}
return lastErr
}
@@ -104,7 +154,7 @@ func (r *Registry) Enable(name string) error {
r.mu.Lock()
defer r.mu.Unlock()
if _, ok := r.builtin[name]; !ok {
if !r.has(name) {
return fmt.Errorf("plugin %s not found", name)
}
r.enabled[name] = true
@@ -116,13 +166,20 @@ func (r *Registry) Disable(name string) error {
r.mu.Lock()
defer r.mu.Unlock()
if _, ok := r.builtin[name]; !ok {
if !r.has(name) {
return fmt.Errorf("plugin %s not found", name)
}
r.enabled[name] = false
return nil
}
// has 内部检查(无锁)
func (r *Registry) has(name string) bool {
_, ok1 := r.serverPlugins[name]
_, ok2 := r.clientPlugins[name]
return ok1 || ok2
}
// IsEnabled 检查插件是否启用
func (r *Registry) IsEnabled(name string) bool {
r.mu.RLock()

View File

@@ -20,7 +20,6 @@ type PluginSource string
const (
PluginSourceBuiltin PluginSource = "builtin" // 内置编译
PluginSourceWASM PluginSource = "wasm" // WASM 模块
)
// ConfigFieldType 配置字段类型
@@ -53,18 +52,17 @@ type RuleSchema struct {
// PluginMetadata 描述一个 plugin
type PluginMetadata struct {
Name string `json:"name"` // 唯一标识符 (如 "socks5")
Version string `json:"version"` // 语义化版本
Type PluginType `json:"type"` // Plugin 类别
Source PluginSource `json:"source"` // builtin 或 wasm
Description string `json:"description"` // 人类可读描述
Author string `json:"author"` // Plugin 作者
Icon string `json:"icon,omitempty"` // 图标文件名 (如 "socks5.png")
Checksum string `json:"checksum,omitempty"` // WASM 二进制的 SHA256
Size int64 `json:"size,omitempty"` // WASM 二进制大小
Capabilities []string `json:"capabilities,omitempty"` // 所需 host functions
ConfigSchema []ConfigField `json:"config_schema,omitempty"`// 插件配置模式
RuleSchema *RuleSchema `json:"rule_schema,omitempty"` // 规则表单模式
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 组合元数据和运行时状态
@@ -82,6 +80,7 @@ type Dialer interface {
}
// ProxyHandler 是所有 proxy plugin 必须实现的接口
// 运行在服务端,处理外部连接并通过隧道转发
type ProxyHandler interface {
// Metadata 返回 plugin 信息
Metadata() PluginMetadata
@@ -97,6 +96,26 @@ type ProxyHandler interface {
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 {
@@ -211,27 +230,3 @@ var (
ErrInvalidConfig = &APIError{Code: 7, Message: "invalid configuration"}
)
// ConnHandle WASM 连接句柄
type ConnHandle uint32
// HostContext 提供给 WASM plugin 的 host functions
type HostContext interface {
// 网络操作
Dial(network, address string) (ConnHandle, error)
Read(handle ConnHandle, buf []byte) (int, error)
Write(handle ConnHandle, buf []byte) (int, error)
CloseConn(handle ConnHandle) error
// 客户端连接操作
ClientRead(buf []byte) (int, error)
ClientWrite(buf []byte) (int, error)
// 日志
Log(level LogLevel, message string)
// 时间
Now() int64
// 配置
GetConfig(key string) string
}

View File

@@ -1,146 +0,0 @@
package wasm
import (
"errors"
"log"
"net"
"sync"
"time"
"github.com/gotunnel/pkg/plugin"
)
// ErrInvalidHandle 无效的连接句柄
var ErrInvalidHandle = errors.New("invalid connection handle")
// HostContextImpl 实现 HostContext 接口
type HostContextImpl struct {
dialer plugin.Dialer
clientConn net.Conn
config map[string]string
// 连接管理
conns map[plugin.ConnHandle]net.Conn
nextHandle plugin.ConnHandle
mu sync.Mutex
}
// NewHostContext 创建 host context
func NewHostContext(dialer plugin.Dialer, clientConn net.Conn, config map[string]string) *HostContextImpl {
return &HostContextImpl{
dialer: dialer,
clientConn: clientConn,
config: config,
conns: make(map[plugin.ConnHandle]net.Conn),
nextHandle: 1,
}
}
// Dial 通过隧道建立连接
func (h *HostContextImpl) Dial(network, address string) (plugin.ConnHandle, error) {
conn, err := h.dialer.Dial(network, address)
if err != nil {
return 0, err
}
h.mu.Lock()
handle := h.nextHandle
h.nextHandle++
h.conns[handle] = conn
h.mu.Unlock()
return handle, nil
}
// Read 从连接读取数据
func (h *HostContextImpl) Read(handle plugin.ConnHandle, buf []byte) (int, error) {
h.mu.Lock()
conn, ok := h.conns[handle]
h.mu.Unlock()
if !ok {
return 0, ErrInvalidHandle
}
return conn.Read(buf)
}
// Write 向连接写入数据
func (h *HostContextImpl) Write(handle plugin.ConnHandle, buf []byte) (int, error) {
h.mu.Lock()
conn, ok := h.conns[handle]
h.mu.Unlock()
if !ok {
return 0, ErrInvalidHandle
}
return conn.Write(buf)
}
// CloseConn 关闭连接
func (h *HostContextImpl) CloseConn(handle plugin.ConnHandle) error {
h.mu.Lock()
conn, ok := h.conns[handle]
if ok {
delete(h.conns, handle)
}
h.mu.Unlock()
if !ok {
return ErrInvalidHandle
}
return conn.Close()
}
// ClientRead 从客户端连接读取数据
func (h *HostContextImpl) ClientRead(buf []byte) (int, error) {
return h.clientConn.Read(buf)
}
// ClientWrite 向客户端连接写入数据
func (h *HostContextImpl) ClientWrite(buf []byte) (int, error) {
return h.clientConn.Write(buf)
}
// Log 记录日志
func (h *HostContextImpl) Log(level plugin.LogLevel, message string) {
prefix := "[WASM]"
switch level {
case plugin.LogDebug:
prefix = "[WASM DEBUG]"
case plugin.LogInfo:
prefix = "[WASM INFO]"
case plugin.LogWarn:
prefix = "[WASM WARN]"
case plugin.LogError:
prefix = "[WASM ERROR]"
}
log.Printf("%s %s", prefix, message)
}
// Now 返回当前 Unix 时间戳
func (h *HostContextImpl) Now() int64 {
return time.Now().Unix()
}
// GetConfig 获取配置值
func (h *HostContextImpl) GetConfig(key string) string {
if h.config == nil {
return ""
}
return h.config[key]
}
// Close 关闭所有连接
func (h *HostContextImpl) Close() error {
h.mu.Lock()
defer h.mu.Unlock()
for handle, conn := range h.conns {
conn.Close()
delete(h.conns, handle)
}
return nil
}

View File

@@ -1,29 +0,0 @@
package wasm
import (
"github.com/tetratelabs/wazero/api"
)
// ReadString 从 WASM 内存读取字符串
func ReadString(mem api.Memory, ptr, len uint32) (string, bool) {
data, ok := mem.Read(ptr, len)
if !ok {
return "", false
}
return string(data), true
}
// WriteString 向 WASM 内存写入字符串
func WriteString(mem api.Memory, ptr uint32, s string) bool {
return mem.Write(ptr, []byte(s))
}
// ReadBytes 从 WASM 内存读取字节
func ReadBytes(mem api.Memory, ptr, len uint32) ([]byte, bool) {
return mem.Read(ptr, len)
}
// WriteBytes 向 WASM 内存写入字节
func WriteBytes(mem api.Memory, ptr uint32, data []byte) bool {
return mem.Write(ptr, data)
}

View File

@@ -1,148 +0,0 @@
package wasm
import (
"context"
"encoding/json"
"fmt"
"github.com/gotunnel/pkg/plugin"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
// WASMPlugin 封装 WASM 模块作为 ProxyHandler
type WASMPlugin struct {
name string
metadata plugin.PluginMetadata
runtime *Runtime
compiled wazero.CompiledModule
config map[string]string
}
// NewWASMPlugin 从 WASM 字节创建 plugin
func NewWASMPlugin(ctx context.Context, rt *Runtime, name string, wasmBytes []byte) (*WASMPlugin, error) {
compiled, err := rt.runtime.CompileModule(ctx, wasmBytes)
if err != nil {
return nil, fmt.Errorf("compile module: %w", err)
}
p := &WASMPlugin{
name: name,
runtime: rt,
compiled: compiled,
}
// 尝试获取元数据
if err := p.loadMetadata(ctx); err != nil {
// 使用默认元数据
p.metadata = plugin.PluginMetadata{
Name: name,
Type: plugin.PluginTypeProxy,
Source: plugin.PluginSourceWASM,
}
}
return p, nil
}
// loadMetadata 从 WASM 模块加载元数据
func (p *WASMPlugin) loadMetadata(ctx context.Context) error {
// 创建临时实例获取元数据
inst, err := p.runtime.runtime.InstantiateModule(ctx, p.compiled, wazero.NewModuleConfig())
if err != nil {
return err
}
defer inst.Close(ctx)
metadataFn := inst.ExportedFunction("metadata")
if metadataFn == nil {
return fmt.Errorf("metadata function not exported")
}
allocFn := inst.ExportedFunction("alloc")
if allocFn == nil {
return fmt.Errorf("alloc function not exported")
}
// 分配缓冲区
results, err := allocFn.Call(ctx, 1024)
if err != nil {
return err
}
bufPtr := uint32(results[0])
// 调用 metadata 函数
results, err = metadataFn.Call(ctx, uint64(bufPtr), 1024)
if err != nil {
return err
}
actualLen := uint32(results[0])
// 读取元数据
mem := inst.Memory()
data, ok := mem.Read(bufPtr, actualLen)
if !ok {
return fmt.Errorf("failed to read metadata")
}
return json.Unmarshal(data, &p.metadata)
}
// Metadata 返回 plugin 信息
func (p *WASMPlugin) Metadata() plugin.PluginMetadata {
return p.metadata
}
// Init 初始化 plugin
func (p *WASMPlugin) Init(config map[string]string) error {
p.config = config
return nil
}
// HandleConn 处理连接
func (p *WASMPlugin) HandleConn(conn interface{}, dialer plugin.Dialer) error {
// WASM plugin 的连接处理需要更复杂的实现
// 这里提供基础框架,实际实现需要注册 host functions
return fmt.Errorf("WASM plugin HandleConn not fully implemented")
}
// Close 关闭 plugin
func (p *WASMPlugin) Close() error {
return p.compiled.Close(context.Background())
}
// RegisterHostFunctions 注册 host functions 到 wazero 运行时
func RegisterHostFunctions(ctx context.Context, r wazero.Runtime) (wazero.CompiledModule, error) {
return r.NewHostModuleBuilder("env").
NewFunctionBuilder().
WithFunc(hostLog).
Export("log").
NewFunctionBuilder().
WithFunc(hostNow).
Export("now").
Compile(ctx)
}
// host function 实现
func hostLog(ctx context.Context, m api.Module, level uint32, msgPtr, msgLen uint32) {
data, ok := m.Memory().Read(msgPtr, msgLen)
if !ok {
return
}
prefix := "[WASM]"
switch plugin.LogLevel(level) {
case plugin.LogDebug:
prefix = "[WASM DEBUG]"
case plugin.LogInfo:
prefix = "[WASM INFO]"
case plugin.LogWarn:
prefix = "[WASM WARN]"
case plugin.LogError:
prefix = "[WASM ERROR]"
}
fmt.Printf("%s %s\n", prefix, string(data))
}
func hostNow(ctx context.Context) int64 {
return ctx.Value("now").(func() int64)()
}

View File

@@ -1,116 +0,0 @@
package wasm
import (
"context"
"fmt"
"sync"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
// Runtime 管理 wazero WASM 运行时
type Runtime struct {
runtime wazero.Runtime
modules map[string]*Module
mu sync.RWMutex
}
// NewRuntime 创建新的 WASM 运行时
func NewRuntime(ctx context.Context) (*Runtime, error) {
r := wazero.NewRuntime(ctx)
return &Runtime{
runtime: r,
modules: make(map[string]*Module),
}, nil
}
// GetWazeroRuntime 返回底层 wazero 运行时
func (r *Runtime) GetWazeroRuntime() wazero.Runtime {
return r.runtime
}
// LoadModule 从字节加载 WASM 模块
func (r *Runtime) LoadModule(ctx context.Context, name string, wasmBytes []byte) (*Module, error) {
r.mu.Lock()
defer r.mu.Unlock()
if _, exists := r.modules[name]; exists {
return nil, fmt.Errorf("module %s already loaded", name)
}
compiled, err := r.runtime.CompileModule(ctx, wasmBytes)
if err != nil {
return nil, fmt.Errorf("failed to compile module: %w", err)
}
module := &Module{
name: name,
compiled: compiled,
}
r.modules[name] = module
return module, nil
}
// GetModule 获取已加载的模块
func (r *Runtime) GetModule(name string) (*Module, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
m, ok := r.modules[name]
return m, ok
}
// UnloadModule 卸载 WASM 模块
func (r *Runtime) UnloadModule(ctx context.Context, name string) error {
r.mu.Lock()
defer r.mu.Unlock()
module, exists := r.modules[name]
if !exists {
return fmt.Errorf("module %s not found", name)
}
if err := module.Close(ctx); err != nil {
return err
}
delete(r.modules, name)
return nil
}
// Close 关闭运行时
func (r *Runtime) Close(ctx context.Context) error {
r.mu.Lock()
defer r.mu.Unlock()
for name, module := range r.modules {
if err := module.Close(ctx); err != nil {
return fmt.Errorf("failed to close module %s: %w", name, err)
}
}
return r.runtime.Close(ctx)
}
// Module WASM 模块封装
type Module struct {
name string
compiled wazero.CompiledModule
instance api.Module
}
// Name 返回模块名称
func (m *Module) Name() string {
return m.name
}
// Close 关闭模块
func (m *Module) Close(ctx context.Context) error {
if m.instance != nil {
if err := m.instance.Close(ctx); err != nil {
return err
}
}
return m.compiled.Close(ctx)
}