Repository Wiki
genshen/wssocks-plugin-ustb

wssocks 客户端启动与插件注册

本页说明项目如何把 GUI 或 C 调用方的启动参数转换为 wssocks 客户端选项,注册 USTB VPN 与版本插件,并完成连接、协商、启动和关闭。

Purpose and Scope

范围是 extra/background.go 中的启动与插件装配,以及 client-ui/main.go 和 extra/go-api/wssocks_client_wrapper.go 中的两个调用入口。VPN 认证、主机加密细节及 UI 表单实现不在此展开;它们属于 VPN 插件及界面相关页面。wssocks 核心 client 包是外部依赖,本文只记录本仓库可见的调用顺序,不推断其内部连接、插件调度和清理语义。

Overview

extra.Options 同时嵌入 client.Options 和 vpn.UstbVpn,并增加字符串 RemoteAddr;TaskHandles 嵌入 client.Handles 并保存供关闭使用的 *sync.Once。StartWssocks 首先装载插件,再校验和解析远端地址,创建连接句柄、建立服务器连接、协商版本,最后启动客户端。GUI 负责开始/停止按钮和异步等待;C 导出函数负责将 C 字符串及布尔值转换为相同的 Go 配置。实现。

Architecture

Loading diagram...

Sources: main.go, wssocks_client_wrapper.go, background.go

图中 loadPlugins 向外部 client 包分别注册 option、request、version 三类插件;StartWssocks 同时直接调用该包的句柄构造及连接/协商/启动方法。入口并不各自注册插件,因此 GUI 与 C 包装共享同一套装配逻辑。注册代码。

插件装配与配置更新

loadPlugins(v vpn.UstbVpn) error 使用包级 vpnPlugin *vpn.UstbVpn 区分首次装配与后续启动。首次调用先保存传入值的地址,再依次注册同一个 VPN 实例的 option 和 request 能力,最后创建并注册版本协商插件。后续调用不重复注册,而是原位覆盖已保存实例的值;这样先前注册的指针仍指向更新后的 VPN 配置。实现。

go
1var vpnPlugin *vpn.UstbVpn = nil 2 3func loadPlugins(v vpn.UstbVpn) error { 4 if vpnPlugin == nil { 5 vpnPlugin = &v 6 // vpn.UstbVpn has implementations of both option plugin and request plugin 7 if err := client.AddPluginOption(vpnPlugin); err != nil { 8 return err 9 } 10 if err := client.AddPluginRequest(vpnPlugin); err != nil { 11 return err 12 } 13 if err := client.AddPluginVersion(&ver.PluginVersionNeg{}); err != nil { 14 return err 15 } 16 } else { 17 // apply changed vpn config to plugin 18 *vpnPlugin = v 19 } 20 return nil 21}

Source: background.go

这里没有回滚:若任一 AddPlugin* 返回错误,函数立即退出,但 vpnPlugin 已非 nil;下一次调用走更新分支,不会重试未完成的注册。这是依据本文件控制流可确认的部分初始化风险,实际注册表状态取决于外部 client 实现。vpnPlugin 的读写没有可见锁;多个调用方并发启动或运行中更新配置的安全性无法从本仓库保证。实现。

Core Flow:从启动到停止

Loading diagram...

Sources: background.go, main.go, wssocks_client_wrapper.go

启动在远端地址校验之前执行插件装配。空地址产生 empty remote address;非空地址由 url.Parse 转为 RemoteUrl,代码未在此检查 URL scheme 或主机。之后初始化空 http.Header 作为 RemoteHeaders,以新 client.NewClientHandles() 的值替换 h.Handles。用 context.WithTimeout(context.Background(), time.Minute) 创建上下文并 defer cancel(),依次执行 CreateServerConn、NegotiateVersion;只有两者成功才给 h.once 赋值并调用 StartClient。实现。

go
1 _, err = h.CreateServerConn(&options.Options, ctx) 2 if err != nil { 3 return err 4 } 5 // server connect successfully 6 7 if err := h.NegotiateVersion(ctx, options.RemoteAddr); err != nil { 8 return err 9 } 10 11 var once sync.Once 12 h.once = &once 13 h.StartClient(&options.Options, &once) 14 return nil

Source: background.go

连接与协商发生在 StartClient 之前;此处 StartClient 调用没有显式错误返回值检查。超时上下文仅在本文件可见的建立连接及协商调用中传入;无法据此断言启动后的客户端运行期也使用相同的一分钟超时。

Usage Examples:实际入口

GUI 入口

Fyne 按钮在停止状态从表单提取代理地址、HTTP 开关、TLS 校验开关、VPN 配置和远端地址;StartWssocks 返回错误时显示对话框并恢复按钮,成功后启动 goroutine 调用 Wait()。运行状态点击按钮则通过 NotifyCloseWrapper() 通知关闭。实现。

go
1 options := extra.Options{ 2 Options: client.Options{ 3 LocalSocks5Addr: uiLocalAddr.Text, 4 HttpEnabled: uiHttpEnable.Checked, 5 LocalHttpAddr: uiHttpLocalAddr.Text, 6 SkipTLSVerify: uiSkipTSLVerify.Checked, 7 }, 8 UstbVpn: onLoadValue(), 9 RemoteAddr: uiRemoteAddr.Text, 10 } 11 btnStatus = btnStarting 12 btnStart.SetText("Loading") 13 if err := handles.StartWssocks(options); err != nil { 14 // log error 15 dialog.ShowError(err, w) 16 btnStart.SetText("Start") 17 btnStatus = btnStopped 18 return 19 }

Source: main.go

GUI 定义 btnStopped、btnStarting、btnRunning、btnStopping 四个状态,但点击处理只在 btnRunning 或 btnStopped 时进入对应分支。关闭窗口时若状态为 running 也通知关闭;同时保存基础偏好和 VPN UI 偏好。状态与回调、窗口关闭。界面上创建的 uiAuthToken 输入框出现在表单里,但所读的选项组装代码未将其赋给 client.Options;不能将其描述为此启动路径已生效的鉴权配置。表单及选项、构造。

C 导出入口

NewClientHandles() 分配 extra.TaskHandles,把指针转为 uintptr 并保存在包级 map 中,注释明确说明是为避免被 GC 回收。StartClientWrapper 把 C 参数映射到 extra.Options,选择密码认证方法,调用同一 StartWssocks;错误以 C.CString(err.Error()) 返回,成功返回空 C 字符串。WaitClientWrapper 同样把等待错误转换为字符串,StopClientWrapper 调用 NotifyCloseWrapper。实现。

go
1//export NewClientHandles 2func NewClientHandles() uintptr { 3 hd := new(extra.TaskHandles) 4 ptr := uintptr(unsafe.Pointer(hd)) 5 if handleInstances == nil { 6 handleInstances = make(map[uintptr]*extra.TaskHandles) 7 } 8 handleInstances[ptr] = hd 9 return ptr 10}

Source: wssocks_client_wrapper.go

配置选项

下表限于已读取的入口和启动实现;底层 client.Options 其余字段及默认值未在这些源文件中定义。GUI 的字段初值来自创建控件时的 Text 或 checkbox 构造参数,之后还会调用 loadBasicPreference,故表中是控件初值,不保证为最终启动值。GUI 初始化、配置类型。

传递字段类型(可见入口)GUI 初值用途
LocalSocks5Addrclient.Options 字段,由文本赋值127.0.0.1:1080本地 SOCKS5 监听地址
HttpEnabledclient.Options 字段,由布尔值赋值false是否启用 HTTP 代理
LocalHttpAddrclient.Options 字段,由文本赋值127.0.0.1:1086HTTP 监听地址
SkipTLSVerifyclient.Options 字段,由布尔值赋值false传给客户端的 TLS 校验跳过选项
RemoteAddrstring空文本远端地址,启动时不能为空;解析成 RemoteUrl
UstbVpnvpn.UstbVpn由 onLoadValue() 获取插件实例配置,后续启动覆盖已注册实例

C 包装入口另把 vpnEnable、vpnForceLogout、vpnHostEncrypt、vpnHostInput、vpnUsername、vpnPassword 转入 vpn.UstbVpn,并固定 AuthMethod 为 vpn.VpnAuthMethodPasswd;这些参数的业务语义属于 VPN 插件页面。映射。

API Reference

以下签名来自本仓库;client.Handles 的外部方法定义未在已读取的仓库源码中验证,故不补写其内部返回值和异常保证。

函数/方法参数返回本页职责
loadPlugins(v vpn.UstbVpn) errorv:本次 VPN 配置注册错误或 nil首次注册三种插件,之后更新 VPN 配置。定义
(*TaskHandles).StartWssocks(options Options) erroroptions:客户端、VPN、远端地址首个可见错误或 nil注册、校验、连接、协商、启动。定义
(*TaskHandles).NotifyCloseWrapper()无无调用 NotifyClose(h.once, false)。定义
NewClientHandles() uintptr无保存的句柄指针整数分配 C 入口句柄。定义
StartClientWrapper(...) *C.char句柄、地址、代理/TLS/VPN 开关及 VPN 密码认证参数错误文字或空字符串构造配置并启动。完整签名
WaitClientWrapper(handlesPtr uintptr) *C.char句柄指针等待错误文字或空字符串等待客户端。定义
StopClientWrapper(handlesPtr uintptr) *C.char句柄指针空字符串通知关闭。定义

Failure Modes、边界与并发

  • 错误发生顺序:插件注册错误优先于远端地址错误;空地址返回明确错误,url.Parse、CreateServerConn、NegotiateVersion 的错误按发生位置直接向上传播;不进入后续步骤。启动顺序。
  • 部分初始化:注册失败不会重置全局 vpnPlugin;连接或协商失败也没有在本函数中看到显式回滚,已创建的 h.Handles 保留在句柄中。不能推断外部 client 是否自行释放底层资源。实现。
  • 启动和关闭时机:h.once 只在连接和版本协商成功后初始化。NotifyCloseWrapper 直接将它传入 NotifyClose;在未成功启动的句柄上调用该方法会怎样,取决于外部实现。赋值及关闭、启动末尾。
  • GUI 等待和竞争:Wait() 在 goroutine 中执行;主动停止时设置 ignoreWaitErr = true 以不显示预期的等待错误。按钮回调与等待 goroutine 均写 btnStatus、ignoreWaitErr 和按钮文字,本文件中没有可见同步机制;不要把它解读为已验证的线程安全 UI 更新。GUI 控制流。
  • C 指针生命周期:全局 handleInstances map 在读取到的导出函数中只增加条目,未见删除、指针有效性校验或并发保护。StartClientWrapper、WaitClientWrapper、StopClientWrapper 均直接把传入的 uintptr 转为指针,调用方需要避免无效句柄;C 字符串的释放约定在已读取文件中未给出。句柄存储及转换、导出函数。

运行与扩展注意事项

启动路径为连接和版本协商设置一分钟 context 超时(原注释为 fixme),随后 defer cancel();调整超时需考虑这两个阶段的实际耗时,而不能把它当作整个客户端生命周期的运行超时。上下文。插件扩展的本仓库接入点是 loadPlugins 中的 client.AddPluginOption、client.AddPluginRequest、client.AddPluginVersion;新增注册步骤时应考虑首次失败后全局指针已赋值的现状及重复启动时的配置更新分支。插件装配。当前已读取源码没有提供针对该启动协调逻辑的测试证据;不声称存在自动测试保证。

Sources

(3 files)