Skip to content

fix: improve OpenVPN transport reliability#2824

Merged
wwqgtxx merged 9 commits into
MetaCubeX:Alphafrom
RomanovCaesar:Alpha
May 23, 2026
Merged

fix: improve OpenVPN transport reliability#2824
wwqgtxx merged 9 commits into
MetaCubeX:Alphafrom
RomanovCaesar:Alpha

Conversation

@RomanovCaesar

Copy link
Copy Markdown

变更说明

本 PR 在现有 OpenVPN outbound 支持的基础上,补充了一批稳定性、兼容性和性能改进,主要用于提升 OpenVPN TCP/UDP 节点在真实使用场景下的可用性。

主要改动

OpenVPN 连接稳定性

  • 为 OpenVPN 握手增加超时控制,避免底层连接异常时长时间阻塞。
  • 对 OpenVPN 底层 TCP/UDP socket 设置更大的读写缓冲区,改善高吞吐场景下的收发稳定性。
  • 修复 UDP 模式下控制信道可靠包乱序时可能导致 TLS/control channel 解析失败的问题。
  • 增加 UDP 控制包重传逻辑,提升部分服务端握手阶段的兼容性。
  • 扩大 OpenVPN packet mux 缓冲区,减少高并发收发时控制包/数据包被阻塞的概率。

IPv4-only 隧道处理

  • 记录服务端 push 下发的地址前缀是否包含 IPv6。
  • 当 OpenVPN 隧道没有 IPv6 地址时,DNS 解析和拨号逻辑优先限制为 IPv4。
  • 避免在 IPv4-only OpenVPN 隧道中尝试连接 IPv6 地址,减少 network is unreachable 一类错误。

CBC 数据通道性能优化

  • 优化 AES-CBC 数据通道的加密路径,减少每个数据包的内存分配和拷贝。
  • 优化 AES-CBC 解密路径,支持在接收缓冲上原地解密,减少下载方向的分配压力。
  • 复用 HMAC 对象和 TCP frame 缓冲,降低高吞吐场景下的 GC 压力。
  • 缓存 CBC IV 随机数据读取,减少频繁调用系统随机源带来的开销。

数据包乱序兼容

  • 为 OpenVPN data channel 增加 replay window。
  • 允许一定窗口内的数据包乱序到达,同时继续拒绝重复包和过旧包。
  • 这可以改善 UDP 模式下部分网络环境中由于包乱序导致的数据通道异常。

SoftEther / VPNGate 兼容性

  • 改进 ifconfig push 解析逻辑。
  • 支持 SoftEther / VPNGate 常见的 ifconfig <local> <peer> 点对点格式。
  • 避免将 peer address 错误解析为 netmask,从而生成异常前缀。

测试

已在本地执行:

go test -tags with_gvisor ./transport/openvpn ./adapter/outbound

测试通过。

此外,也使用多个 VPNGate OpenVPN TCP/UDP 节点进行了手动连通性测试,验证了:

  • TCP 节点可正常握手和代理访问。
  • UDP 节点可正常握手和代理访问。
  • AES-CBC / SHA1 配置可正常使用。
  • 常见网页访问、Telegram、分流规则等基础使用场景可正常工作。

备注

本 PR 主要目标是提升当前 OpenVPN outbound 的稳定性和兼容性,并减少数据通道热路径上的明显分配开销。OpenVPN 的整体吞吐仍然会受到 gVisor 用户态网络栈、Go 实现的数据通道处理以及本地代理转发路径影响,因此性能 不能 达到官方 OpenVPN 客户端水平。推翻先前几次PR建立的架构并试图完全移植 OpenVPN 的官方实现架构以追求达成与官方客户端相近的性能并不现实,作为一个简单的PR这么做并不合理,并且目前的架构实现的性能对于测试性质的用途已经够用,故不会考虑推倒重做。

@wwqgtxx

wwqgtxx commented May 22, 2026

Copy link
Copy Markdown
Collaborator

请不要过早的进行不必要的“性能优化”,这只会让代码陷入未来无法维护的境地

首先,请去除“IPv4-only 隧道处理”部分,那点error log并没什么实际影响,不值得维持让openvpn的代码和其他的模块有那么大的出入

其次,不要修改tcp底层缓冲区,也不用盲目扩大chan的缓冲容量,这会大幅增加核心的内存占用

最后,我们不反对用pool,但是streamPacketFramePool显然是不必要的,mihomo本来就有自己的全局byte pool,为什么不直接复用

@RomanovCaesar

Copy link
Copy Markdown
Author

请不要过早的进行不必要的“性能优化”,这只会让代码陷入未来无法维护的境地

首先,请去除“IPv4-only 隧道处理”部分,那点error log并没什么实际影响,不值得维持让openvpn的代码和其他的模块有那么大的出入

其次,不要修改tcp底层缓冲区,也不用盲目扩大chan的缓冲容量,这会大幅增加核心的内存占用

最后,我们不反对用pool,但是streamPacketFramePool显然是不必要的,mihomo本来就有自己的全局byte pool,为什么不直接复用

已按照以上这些要求进行修改,测试多个节点后未发现问题

@wwqgtxx

wwqgtxx commented May 23, 2026

Copy link
Copy Markdown
Collaborator

看了一下目前的代码还是有些小问题的,主要在outbound那边:

  1. run函数忽略传入的ctx后,会导致DialContext和ListenPacketContext也无法正常遵守传入的ctx(即必须要等待handshake完成)
  2. 还有一个之前代码就有的问题,代码中有两处o.tunDevice = nil的调用,但是DialContext和ListenPacketContext里并没有检查其是否为nil,会有小概率造成panic

@RomanovCaesar

Copy link
Copy Markdown
Author

看了一下目前的代码还是有些小问题的,主要在outbound那边:

  1. run函数忽略传入的ctx后,会导致DialContext和ListenPacketContext也无法正常遵守传入的ctx(即必须要等待handshake完成)
  2. 还有一个之前代码就有的问题,代码中有两处o.tunDevice = nil的调用,但是DialContext和ListenPacketContext里并没有检查其是否为nil,会有小概率造成panic

好,已处理以上的问题

@wwqgtxx

wwqgtxx commented May 23, 2026

Copy link
Copy Markdown
Collaborator

通过openVPNRunLockRetryInterval来忙等待真的好么,有没有更优雅的方式处理一下

此外,既然每次既要调用.run也要调用.stackDevice不如将这两个函数合并,直接让run函数返回那几个值呗,还能避免两次加锁

@RomanovCaesar

Copy link
Copy Markdown
Author

通过openVPNRunLockRetryInterval来忙等待真的好么,有没有更优雅的方式处理一下

此外,既然每次既要调用.run也要调用.stackDevice不如将这两个函数合并,直接让run函数返回那几个值呗,还能避免两次加锁

已根据建议调整:

  1. 移除了 openVPNRunLockRetryInterval 轮询等待锁的实现,改用可被 context 取消的 semaphore acquire,避免忙等待,同时仍能让 DialContext / ListenPacketContext 在等待 OpenVPN 初始化锁时响应传入的 ctx 和 outbound 关闭状态。

  2. 合并了 run 和 stackDevice 的职责。现在 run(ctx) 会在完成初始化后直接返回 tunDevice 和 resolver 的快照,DialContext / ListenPacketContext 不再需要先 run 再 stackDevice,避免了重复加锁,也减少了 tunDevice 状态变化带来的竞态窗口。

  3. 保留了之前对 tunDevice nil 的保护逻辑:如果设备已关闭或初始化失败,会返回 net.ErrClosed,而不是继续访问导致 panic。

@wwqgtxx wwqgtxx merged commit 5639e93 into MetaCubeX:Alpha May 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants