add plugins
All checks were successful
Build Multi-Platform Binaries / build (push) Successful in 11m9s

This commit is contained in:
Flik
2025-12-26 11:24:23 +08:00
parent d56fdafc1e
commit 4623a7f031
27 changed files with 2090 additions and 97 deletions

16
pkg/plugin/builtin.go Normal file
View File

@@ -0,0 +1,16 @@
package plugin
// RegisterBuiltins 注册所有内置 plugins
// 注意:此函数需要在调用方导入 builtin 包并手动注册
// 示例:
// registry := plugin.NewRegistry()
// registry.RegisterBuiltin(builtin.NewSOCKS5Plugin())
// registry.RegisterBuiltin(builtin.NewHTTPPlugin())
func RegisterBuiltins(registry *Registry, handlers ...ProxyHandler) error {
for _, handler := range handlers {
if err := registry.RegisterBuiltin(handler); err != nil {
return err
}
}
return nil
}

116
pkg/plugin/builtin/http.go Normal file
View File

@@ -0,0 +1,116 @@
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

@@ -0,0 +1,167 @@
package builtin
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"github.com/gotunnel/pkg/plugin"
)
const (
socks5Version = 0x05
noAuth = 0x00
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.PluginMetadata {
return plugin.PluginMetadata{
Name: "socks5",
Version: "1.0.0",
Type: plugin.PluginTypeProxy,
Source: plugin.PluginSourceBuiltin,
Description: "SOCKS5 proxy protocol handler (official plugin)",
Author: "GoTunnel",
Capabilities: []string{
"dial", "read", "write", "close",
},
}
}
// 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
}
// 响应:使用无认证
_, 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
}
// 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
}

93
pkg/plugin/registry.go Normal file
View File

@@ -0,0 +1,93 @@
package plugin
import (
"context"
"fmt"
"sync"
)
// Registry 管理可用的 plugins
type Registry struct {
builtin map[string]ProxyHandler // 内置 Go 实现
mu sync.RWMutex
}
// NewRegistry 创建 plugin 注册表
func NewRegistry() *Registry {
return &Registry{
builtin: make(map[string]ProxyHandler),
}
}
// RegisterBuiltin 注册内置 plugin
func (r *Registry) RegisterBuiltin(handler ProxyHandler) 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.builtin[meta.Name]; exists {
return fmt.Errorf("plugin %s already registered", meta.Name)
}
r.builtin[meta.Name] = handler
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 {
return handler, nil
}
return nil, fmt.Errorf("plugin %s not found", proxyType)
}
// List 返回所有可用的 plugins
func (r *Registry) List() []PluginInfo {
r.mu.RLock()
defer r.mu.RUnlock()
var plugins []PluginInfo
// 内置 plugins
for _, handler := range r.builtin {
plugins = append(plugins, PluginInfo{
Metadata: handler.Metadata(),
Loaded: true,
})
}
return plugins
}
// Has 检查 plugin 是否存在
func (r *Registry) Has(name string) bool {
r.mu.RLock()
defer r.mu.RUnlock()
_, ok := r.builtin[name]
return ok
}
// Close 关闭所有 plugins
func (r *Registry) Close(ctx context.Context) error {
r.mu.Lock()
defer r.mu.Unlock()
var lastErr error
for name, handler := range r.builtin {
if err := handler.Close(); err != nil {
lastErr = fmt.Errorf("failed to close plugin %s: %w", name, err)
}
}
return lastErr
}

View File

@@ -0,0 +1,29 @@
package store
import (
"github.com/gotunnel/pkg/plugin"
)
// PluginStore 管理 plugin 持久化
type PluginStore interface {
// GetAllPlugins 返回所有存储的 plugins
GetAllPlugins() ([]plugin.PluginMetadata, error)
// GetPlugin 返回指定 plugin 的元数据
GetPlugin(name string) (*plugin.PluginMetadata, error)
// GetPluginData 返回 WASM 二进制
GetPluginData(name string) ([]byte, error)
// SavePlugin 存储 plugin
SavePlugin(metadata plugin.PluginMetadata, wasmData []byte) error
// DeletePlugin 删除 plugin
DeletePlugin(name string) error
// PluginExists 检查 plugin 是否存在
PluginExists(name string) (bool, error)
// Close 关闭存储
Close() error
}

168
pkg/plugin/store/sqlite.go Normal file
View File

@@ -0,0 +1,168 @@
package store
import (
"database/sql"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/gotunnel/pkg/plugin"
_ "modernc.org/sqlite"
)
// SQLiteStore SQLite 实现的 PluginStore
type SQLiteStore struct {
db *sql.DB
mu sync.RWMutex
}
// NewSQLiteStore 创建 SQLite plugin 存储
func NewSQLiteStore(dbPath string) (*SQLiteStore, error) {
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, err
}
store := &SQLiteStore{db: db}
if err := store.init(); err != nil {
db.Close()
return nil, err
}
return store, nil
}
// init 初始化数据库表
func (s *SQLiteStore) init() error {
query := `
CREATE TABLE IF NOT EXISTS plugins (
name TEXT PRIMARY KEY,
version TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'proxy',
source TEXT NOT NULL DEFAULT 'wasm',
description TEXT,
author TEXT,
checksum TEXT NOT NULL,
size INTEGER NOT NULL,
capabilities TEXT NOT NULL DEFAULT '[]',
config_schema TEXT NOT NULL DEFAULT '{}',
wasm_data BLOB NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`
_, err := s.db.Exec(query)
return err
}
// GetAllPlugins 返回所有存储的 plugins
func (s *SQLiteStore) GetAllPlugins() ([]plugin.PluginMetadata, error) {
s.mu.RLock()
defer s.mu.RUnlock()
rows, err := s.db.Query(`
SELECT name, version, type, source, description, author,
checksum, size, capabilities, config_schema
FROM plugins`)
if err != nil {
return nil, err
}
defer rows.Close()
var plugins []plugin.PluginMetadata
for rows.Next() {
var m plugin.PluginMetadata
var capJSON, configJSON string
err := rows.Scan(&m.Name, &m.Version, &m.Type, &m.Source,
&m.Description, &m.Author, &m.Checksum, &m.Size,
&capJSON, &configJSON)
if err != nil {
return nil, err
}
json.Unmarshal([]byte(capJSON), &m.Capabilities)
json.Unmarshal([]byte(configJSON), &m.ConfigSchema)
plugins = append(plugins, m)
}
return plugins, rows.Err()
}
// GetPlugin 返回指定 plugin 的元数据
func (s *SQLiteStore) GetPlugin(name string) (*plugin.PluginMetadata, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var m plugin.PluginMetadata
var capJSON, configJSON string
err := s.db.QueryRow(`
SELECT name, version, type, source, description, author,
checksum, size, capabilities, config_schema
FROM plugins WHERE name = ?`, name).Scan(
&m.Name, &m.Version, &m.Type, &m.Source,
&m.Description, &m.Author, &m.Checksum, &m.Size,
&capJSON, &configJSON)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
json.Unmarshal([]byte(capJSON), &m.Capabilities)
json.Unmarshal([]byte(configJSON), &m.ConfigSchema)
return &m, nil
}
// GetPluginData 返回 WASM 二进制
func (s *SQLiteStore) GetPluginData(name string) ([]byte, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var data []byte
err := s.db.QueryRow(`SELECT wasm_data FROM plugins WHERE name = ?`, name).Scan(&data)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("plugin %s not found", name)
}
return data, err
}
// SavePlugin 存储 plugin
func (s *SQLiteStore) SavePlugin(metadata plugin.PluginMetadata, wasmData []byte) error {
s.mu.Lock()
defer s.mu.Unlock()
capJSON, _ := json.Marshal(metadata.Capabilities)
configJSON, _ := json.Marshal(metadata.ConfigSchema)
_, err := s.db.Exec(`
INSERT OR REPLACE INTO plugins
(name, version, type, source, description, author, checksum, size,
capabilities, config_schema, wasm_data, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
metadata.Name, metadata.Version, metadata.Type, metadata.Source,
metadata.Description, metadata.Author, metadata.Checksum, metadata.Size,
string(capJSON), string(configJSON), wasmData, time.Now())
return err
}
// DeletePlugin 删除 plugin
func (s *SQLiteStore) DeletePlugin(name string) error {
s.mu.Lock()
defer s.mu.Unlock()
_, err := s.db.Exec(`DELETE FROM plugins WHERE name = ?`, name)
return err
}
// PluginExists 检查 plugin 是否存在
func (s *SQLiteStore) PluginExists(name string) (bool, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var count int
err := s.db.QueryRow(`SELECT COUNT(*) FROM plugins WHERE name = ?`, name).Scan(&count)
return count > 0, err
}
// Close 关闭存储
func (s *SQLiteStore) Close() error {
return s.db.Close()
}

99
pkg/plugin/types.go Normal file
View File

@@ -0,0 +1,99 @@
package plugin
import (
"net"
"time"
)
// PluginType 定义 plugin 类别
type PluginType string
const (
PluginTypeProxy PluginType = "proxy" // 代理处理器 (SOCKS5, HTTP 等)
)
// PluginSource 表示 plugin 来源
type PluginSource string
const (
PluginSourceBuiltin PluginSource = "builtin" // 内置编译
PluginSourceWASM PluginSource = "wasm" // WASM 模块
)
// 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 作者
Checksum string `json:"checksum,omitempty"` // WASM 二进制的 SHA256
Size int64 `json:"size,omitempty"` // WASM 二进制大小
Capabilities []string `json:"capabilities,omitempty"` // 所需 host functions
ConfigSchema map[string]string `json:"config_schema,omitempty"`
}
// PluginInfo 组合元数据和运行时状态
type PluginInfo struct {
Metadata PluginMetadata `json:"metadata"`
Loaded bool `json:"loaded"`
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
}
// LogLevel 日志级别
type LogLevel uint8
const (
LogDebug LogLevel = iota
LogInfo
LogWarn
LogError
)
// 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
}

146
pkg/plugin/wasm/host.go Normal file
View File

@@ -0,0 +1,146 @@
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
}

29
pkg/plugin/wasm/memory.go Normal file
View File

@@ -0,0 +1,29 @@
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)
}

148
pkg/plugin/wasm/module.go Normal file
View File

@@ -0,0 +1,148 @@
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)()
}

116
pkg/plugin/wasm/runtime.go Normal file
View File

@@ -0,0 +1,116 @@
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)
}

View File

@@ -7,6 +7,12 @@ import (
"io"
)
// 协议常量
const (
MaxMessageSize = 1024 * 1024 // 最大消息大小 1MB
HeaderSize = 5 // 消息头大小
)
// 消息类型定义
const (
MsgTypeAuth uint8 = 1 // 认证请求
@@ -19,6 +25,15 @@ const (
MsgTypeError uint8 = 8 // 错误消息
MsgTypeProxyConnect uint8 = 9 // 代理连接请求 (SOCKS5/HTTP)
MsgTypeProxyResult uint8 = 10 // 代理连接结果
// Plugin 相关消息
MsgTypePluginList uint8 = 20 // 请求/响应可用 plugins
MsgTypePluginDownload uint8 = 21 // 请求下载 plugin
MsgTypePluginData uint8 = 22 // Plugin 二进制数据(分块)
MsgTypePluginReady uint8 = 23 // Plugin 加载确认
// UDP 相关消息
MsgTypeUDPData uint8 = 30 // UDP 数据包
)
// Message 基础消息结构
@@ -42,10 +57,14 @@ type AuthResponse struct {
// ProxyRule 代理规则
type ProxyRule struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"` // tcp, socks5, http
LocalIP string `json:"local_ip" yaml:"local_ip"` // tcp 模式使用
LocalPort int `json:"local_port" yaml:"local_port"` // tcp 模式使用
Type string `json:"type" yaml:"type"` // 内置: tcp, udp, http, https; 插件: socks5 等
LocalIP string `json:"local_ip" yaml:"local_ip"` // tcp/udp 模式使用
LocalPort int `json:"local_port" yaml:"local_port"` // tcp/udp 模式使用
RemotePort int `json:"remote_port" yaml:"remote_port"` // 服务端监听端口
// Plugin 支持字段
PluginName string `json:"plugin_name,omitempty" yaml:"plugin_name"`
PluginVersion string `json:"plugin_version,omitempty" yaml:"plugin_version"`
PluginConfig map[string]string `json:"plugin_config,omitempty" yaml:"plugin_config"`
}
// ProxyConfig 代理配置下发
@@ -75,9 +94,59 @@ type ProxyConnectResult struct {
Message string `json:"message,omitempty"`
}
// PluginMetadata Plugin 元数据(协议层)
type PluginMetadata struct {
Name string `json:"name"`
Version string `json:"version"`
Checksum string `json:"checksum"`
Size int64 `json:"size"`
Description string `json:"description,omitempty"`
}
// PluginListRequest 请求可用 plugins
type PluginListRequest struct {
ClientVersion string `json:"client_version"`
}
// PluginListResponse 返回可用 plugins
type PluginListResponse struct {
Plugins []PluginMetadata `json:"plugins"`
}
// PluginDownloadRequest 请求下载 plugin
type PluginDownloadRequest struct {
Name string `json:"name"`
Version string `json:"version"`
}
// PluginDataChunk Plugin 二进制数据块
type PluginDataChunk struct {
Name string `json:"name"`
Version string `json:"version"`
ChunkIndex int `json:"chunk_index"`
TotalChunks int `json:"total_chunks"`
Data []byte `json:"data"`
Checksum string `json:"checksum,omitempty"`
}
// PluginReadyNotification Plugin 加载确认
type PluginReadyNotification struct {
Name string `json:"name"`
Version string `json:"version"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
}
// UDPPacket UDP 数据包
type UDPPacket struct {
RemotePort int `json:"remote_port"` // 服务端监听端口
ClientAddr string `json:"client_addr"` // 客户端地址 (用于回复)
Data []byte `json:"data"` // UDP 数据
}
// WriteMessage 写入消息到 writer
func WriteMessage(w io.Writer, msg *Message) error {
header := make([]byte, 5)
header := make([]byte, HeaderSize)
header[0] = msg.Type
binary.BigEndian.PutUint32(header[1:], uint32(len(msg.Payload)))
@@ -94,7 +163,7 @@ func WriteMessage(w io.Writer, msg *Message) error {
// ReadMessage 从 reader 读取消息
func ReadMessage(r io.Reader) (*Message, error) {
header := make([]byte, 5)
header := make([]byte, HeaderSize)
if _, err := io.ReadFull(r, header); err != nil {
return nil, err
}
@@ -102,7 +171,7 @@ func ReadMessage(r io.Reader) (*Message, error) {
msgType := header[0]
length := binary.BigEndian.Uint32(header[1:])
if length > 1024*1024 {
if length > MaxMessageSize {
return nil, errors.New("message too large")
}

View File

@@ -45,7 +45,7 @@ func (s *Server) HandleConn(conn net.Conn) {
switch s.typ {
case "socks5":
err = s.socks5.HandleConn(conn)
case "http":
case "http", "https":
err = s.http.HandleConn(conn)
}
if err != nil {

View File

@@ -1,30 +1,29 @@
package relay
import (
"io"
"net"
"sync"
)
const bufferSize = 32 * 1024
// Relay 双向数据转发
func Relay(c1, c2 net.Conn) {
var wg sync.WaitGroup
wg.Add(2)
copy := func(dst, src net.Conn) {
copyConn := func(dst, src net.Conn) {
defer wg.Done()
buf := make([]byte, 32*1024)
for {
n, err := src.Read(buf)
if n > 0 {
dst.Write(buf[:n])
}
if err != nil {
return
}
buf := make([]byte, bufferSize)
_, _ = io.CopyBuffer(dst, src, buf)
// 关闭写端,通知对方数据传输完成
if tc, ok := dst.(*net.TCPConn); ok {
tc.CloseWrite()
}
}
go copy(c1, c2)
go copy(c2, c1)
go copyConn(c1, c2)
go copyConn(c2, c1)
wg.Wait()
}