☰
Madeira TCP状态编号错位根因与修复:一个Linux/BSD兼容bug全记录
2026/10/4 7:20:15 网站建设 项目流程

Madeira TCP状态编号错位根因与修复:一个Linux/BSD兼容bug全记录

【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu + Wine + DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira

Madeira 是一个在越狱 iOS 上通过 FEX-Emu + Wine + DXMT 运行 x86-64 Windows 游戏的开源项目。本次排障记录围绕它的 Steam 客户端传输层报错展开:Steam 反复弹出 "Unexpected Transport Error",根因是TCP 连接状态编号错位——一个典型的 Linux/BSD 与 Windows 枚举值不一致的兼容 bug。

一、症状:Steam 的"拒绝连接死循环"

在 iOS 上运行 Steam 时,现象非常典型:

  • SteamUI 的 CEF(Chromium)组件发起 loopback 连接后,不断收到Rejecting connection attempt from unknown source;
  • 界面弹出 VGUI "Unexpected Transport Error" 对话框;
  • 连接出现 accept/reject 循环,登录流程无法推进。

Steam 的安全机制是:它通过GetExtendedTcpTable查询 TCP 连接表,找到对端远端端口对应的行,比对dwOwningPid(拥有该 socket 的进程 ID)是否属于自己或它跟踪的子进程。只要这张表查不到、或状态不对,就一律当作"未知来源"拒绝。

二、根因:两张 TCP 状态编号表

问题出在底层的一个关键差异上——BSD(Linux/macOS/iOS)和 Windows 对 TCP 状态的编号完全不同:

状态BSD / Linuxtcp_statesWindowsMIB_TCP_STATE
LISTEN102
ESTABLISHED13

这就是"错位"的含义:

  1. iOS 上没有nsiproxy.sys(没有 winedevice/驱动栈),PE 版nsi.dll打开\\.\Nsi设备失败,所有 NSI 表读取全部报错;
  2. 于是GetExtendedTcpTable直接失败,Steam 的安全检查在 pid 比对之前就退出了——表不可用,连接必然被拒;
  3. 修复思路是补上这张表,但补表时又踩到第二层坑:wineserver 内部记录的状态沿用BSD 编号,而 NSI 接口要求的是Windows MIB 编号。直接把 BSD 值原样透传,Steam 读到的dwState就是错的(ESTAB 表里出现 state=1 而不是 3)。

这是跨系统移植的经典盲区:枚举值不是 API 的一部分,文档里很少强调,但语义全在编号里。

三、修复:从 wineserver 直供 TCP 连接表

修复位置在 NSI 的 unixlib 实现 nsi_unixlib_ios.c:

  1. 按模块 ID 分流:TCP 模块(NPI_MS_TCP_MODULEID)的请求由本文件服务,其他模块(网卡、地址、路由)交给 nsi_network_ios.c 走 Wine 的 BSD provider;

  2. 直接复用 wineserver 的连接数据:wineserver 本来就为每个 TCP socket 跟踪了"诚实的" owner pid(server/sock.c的get_tcp_connections),所以三个 TCP 连接表(ALL / ESTAB / LISTEN)直接从 server 应答,无需伪造;

  3. 状态过滤与编号对齐:表类型到过滤条件的映射保证了语义一致——

    case NSI_TCP_ALL_TABLE: filter = 0; break; case NSI_TCP_ESTAB_TABLE: filter = MIB_TCP_STATE_ESTAB; break; case NSI_TCP_LISTEN_TABLE: filter = MIB_TCP_STATE_LISTEN; break;

    即 nsi_unixlib_ios.c 中用MIB 编号而非 BSD 编号参与过滤,杜绝错位;

  4. 行数据逐字段转换:每条连接输出 key(本地/远端地址端口)、dynamic(state取 MIB 值)、static(pid取 wineserver 记录的 owner),见 ios_tcp_conns_enumerate_all;

  5. 兼容 32 位调用:WoW64 下 32 位nsi.dll传的是 4 字节指针的变体结构体,ios_wow64_nsi_enumerate_all_ex 负责把内嵌指针逐个从 guest 地址转换到 host 地址;

  6. 留了排障日志:修复过程中(rev=ml473/ml474)每行连接都会 dump 实际交付给 Steam 的state和pid,正是靠这份"我们到底给了它什么"的日志确认了状态编号已对齐。

四、经验总结:跨平台移植的 3 条教训

  • 📌枚举值是隐性 ABI:TCP_ESTABLISHED在 BSD 是 1、在 Windows 是 3,任何一层透传都必须显式转换,不能假设"两边都一样";
  • 📌先让接口可用,再谈数据正确:本例分两步——先补上 NSI 表(解决"表不可用"),再核对状态编号(解决"表数据错位"),两步各修一个独立 bug;
  • 📌给排障留日志:修复代码中把"实际交付值"打出来([nsi-row] ... state=%u pid=%u),比任何推理都直接。

五、延伸阅读

  • 修复核心文件:nsi_unixlib_ios.c
  • 网络表 provider:nsi_network_ios.c、nsi_ip_ios.c
  • 32 位 WoW64 指针转换:ios_wow64_nsi_enumerate_all_ex
  • 项目网络相关测试:tests/host/check-nsi-network.py
  • 构建说明:docs/BUILDING.md

【免费下载链接】MadeiraRun x86-64 Windows PC games on jailed iOS via FEX-Emu + Wine + DXMT项目地址: https://gitcode.com/GitHub_Trending/mad/Madeira

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询