Skip to content

Feat/sing box 1.14 - #112

Open
yekaipow wants to merge 62 commits into
starifly:mainfrom
yekaipow:feat/sing-box-1.14
Open

yekaipow wants to merge 62 commits into
starifly:mainfrom
yekaipow:feat/sing-box-1.14

Conversation

@yekaipow

@yekaipow yekaipow commented Nov 5, 2026

Copy link
Copy Markdown

概述

将内核从 1.12.19-neko-1 升级到官方 v1.14.0,并完成 Android 端配套适配。

内核

  • 内核源改为官方仓库,通过 patch 脚本保留 nekoutils 定制(srs、callback、geoip 前缀、selector 回调)
  • 平台接口 12 → 34 个方法,重构 platform_box.go
  • 移除 1.14 已删除的 API:conntrack、shadowsocksr 协议、dialer.DoNotSelectInterface 等
  • WireGuard 由 outbound 改为 endpoint
  • 依赖矩阵对齐(gvisor / sing-juicity / quic 等)

客户端配置生成

1.14 移除了 1.12 起废弃的旧配置语法,旧配置会在启动时报 decode config:

  • DNS 服务器改为 type / server / server_port / path 新格式
  • 移除 dns.fakeip 对象,改用 fakeip 类型服务器
  • 移除 rcode:// 服务器,改用 DNS 规则 action=predefined + rcode
  • tun 的 inet4_address / inet6_address 合并为 address
  • inbound 的 sniff / domain_strategy 移除,改用路由动作 sniff / resolve
  • route.concurrent_dial 移除
  • TLS 分片改用 route-options + tls_fragment

新增功能

  • 节点卡片单节点 URL Test 按钮(单列 / 双列布局各一个)
  • 节点长按菜单,支持多选移动分组
  • 分组标签长按改名 / 删除
  • 节点卡片显示国家国旗(本地 geoip 查询,持久化)

修复

  • 每次提交触发两个相同 workflow 的重复构建

破坏性变更

  • SSR 协议不再支持(上游 1.14 已删除)
  • TLS 分片只剩开关,length / interval 参数不再生效(上游移除)

验证

用官方 v1.14.0 二进制对本项目生成的完整配置执行 sing-box check,通过(无 FATAL / WARN)。
实机验证:订阅更新、各协议连接、URL Test、分流规则。

native job 编译 libcore.aar(Go/gomobile)与 hev-tun .so(NDK 25.0.8775105),build job 下载 geo 资源并用 Gradle 打包,支持 oss/fdroid/play/preview 与 release/debug 切换。
- build-apk.yml 增加 abi 输入(默认 arm64-v8a),用 android.injected.build.abi 只出目标 ABI 的 APK,只上传匹配的那一个
- libcore/build.sh 支持 GOMOBILE_TARGET 限定 gomobile 编译 ABI
- compile-hevtun.sh 支持 HEV_ABIS 覆盖,CI 只编译目标 ABI
GitHub 在 secret 未配置时注入空字符串,Helpers.kt 的 ?: 判断不到 null,会创建密码/别名为空的 signingConfig,导致 debug 构建 packageOssDebug 签名失败(Failed to read key ... password was incorrect)。改为只要三项不全就 unset,release 顺带回退 debug,并在 Prepare 步骤打印各 secret 是否为空。
在节点卡片 edit 按钮旁新增测速按钮,点击直接用 UrlTest().doTest() 测当前节点延迟,状态码与全量 URL Test 一致(1 成功/2 插件缺失/3 失败),完成后只刷新该行;双列与选择模式下跟随 edit 隐藏。
把设置页里的「分应用代理」提到侧边栏,位置在 路由 与 设置 之间;点击直接进入 AppManagerActivity 选择应用,菜单项放在 checkableBehavior=none 的独立 group 里,避免点击后改变导航选中态。
开始 URL Test 前先把当前分组所有节点的 status/ping/error 归零并写库、同步清空 adapter,避免测试中途取消时旧结果与新结果混在一起显示。
ci.yml 原本 push 到任意分支就触发,与 build-apk.yml 重复;改为仅 pull_request + 手动。build.yml 的 nb4a.properties 路径触发也与 build-apk.yml 重叠,改为仅手动。现在 push main 只会跑 build-apk.yml 一个。
- -Pandroid.injected.build.abi 在 AGP 8.8 上导致 assemble 成功但不产出 APK(outputs/apk 目录不存在)
- 改为 buildSrc 读环境变量 NB4A_ABI 限制 splits.abi,默认不设置时仍编译全部 ABI
- Collect APKs 改为全量搜索 outputs 并打印诊断信息,不再死绑 flavor 目录
以前每次构建都用 AGP 自动生成的 debug key,签名不同导致新包无法覆盖安装。改为使用仓库内固定的 debug.keystore(alias/pass 均为 nekobox,SHA-256 6A:B1:8C:F5...),且 release 密码为空字符串时也视为未配置。
长按节点卡片弹出菜单(移动分组/多选/调整顺序/编辑/分享/删除)。进入多选后工具栏变为「移动到分组 + 全选 + 关闭」,点击卡片切换选中(绿描边),支持批量移动到其它分组并刷新各个已创建分组页面。长按拖拽排序改为菜单项触发,避免与长按菜单冲突。
配置页顶部分组标签长按弹出菜单:修改分组名(对话框输入,走 GroupManager.updateGroup 自动刷新标签)、删除分组(二次确认,删除后重建分组列表并回退到可用分组)。未分组系统分组不提供菜单。
- profile_multi_select_menu 引用的 @string/cancel 不存在,改为语义化的 exit_selection(退出多选)
- 顺带修复原有 bug:mal_close 只在俄语里定义,非俄语环境点 yacd 面板的关闭按钮会崩,补上默认与中文值
- 没有正式证书时 release 也用仓库内 debug.keystore 签名(原来只给 debug,release 会产出未签名包)
- 工作流默认构建类型改为 release,只出 release 产物
- secrets 不完整时不再把 release 降级成 debug,固定证书足够签名
- apksigner 校验改为无条件执行
双列布局下 edit 隐藏、改用三点菜单,原先单列的测速按钮也被隐藏。在三点菜单旁新增专用测速按钮:双列显示、单列隐藏,多选模式与选择模式下跟随隐藏,测试中与单列按钮同步禁用。
- libcore 新增 QueryGeoIPCountry(ip),用已打包的 geoip.db(MaxMind)离线查 ISO 国家代码,单例 reader 复用
- Kotlin 侧 CountryFlags:URL Test 时解析服务器地址->IP->国家码,按 profileId 与 host 双键缓存,避免重复解析
- 卡片底部协议标识右侧显示国旗 emoji(不显示文字),仅在测试过且查到归属时出现
1) sing-geoip 的 geoip.db 记录值是两位国家码字符串(同 Rules() 里的 networks.Network(&code)),按标准 MaxMind 结构解码必然失败;改为先按 string 解码,失败再按 country.iso_code 结构解码
2) 全量 URL Test 每测完一个节点即刷新该行(notifyProfileChanged),国旗不再等整轮结束
3) 增加 CountryFlags 日志(host -> ip -> code),便于在日志页定位
- 失败地址原来进永久黑名单,一次失败后续 URL Test 全部静默跳过(日志里一行都没有),改为 60 秒冷却重试
- 每个提前返回分支都补日志,便于在日志页定位卡在哪一步
- 卡片首次绑定即异步补查(inFlight 去重 + 同域名结果复用),不必等到下次 URL Test
- 查询结果按服务器地址持久化到 SharedPreferences(country_flags),重启后直接回填;profileId 映射每次启动按地址重建
- 卡片绑定优先用 resolve()(profileId -> host 兜底),持久化数据无需等待异步回调
- 失败地址改为 60 秒冷却重试,不再永久拉黑(原来一次失败后续测试全部静默跳过)
- 移除 CountryFlags 全部日志输出,不污染运行日志
- 卡片首次绑定异步补查,inFlight 去重,同域名多节点复用结果
- 清理测试结果时同时清除该分组所有节点的图标缓存(内存 + SharedPreferences),图标与延迟一起消失
- 移除卡片绑定时的自动补查逻辑:图标只在 URL Test 后产生,清理后不会被自动查回来
- 持久化数据保留:重启后仍显示上次测速产生的图标(清理过的除外)
URL Test 开始前的清理只重置了 status/ping/error,没有清图标缓存,导致测试前残留上一轮的国旗。补上与「清理测试结果」相同的 forgetProfiles 调用。
原来每测完一个节点只做 notifyItemChanged,adapter 缓存的还是旧 ProxyEntity(status=0),所以延迟不刷新、只有国旗出现(国旗读独立缓存)。改为测完立即 ProfileManager.updateProfile,走 onUpdated 链路把新对象写回列表并重绘,延迟与国旗同时出现。
get_source.sh 改为默认从 SagerNet/sing-box 克隆(SING_BOX_REPO 可覆盖),checkout 后调用 patch-singbox.py 植入 neo-geoip/geosite 钩子与选择器回调,否则 libcore 无法对接官方内核。
sing-box v1.14.0 的 go.mod 要求 go 1.25.5,而 libcore 声明 go 1.24.7 + toolchain go1.24.9,主模块版本低于依赖要求,gomobile 生成临时模块后 go mod tidy 直接失败(missing module declaration)。同步升级 go 指令并移除旧 toolchain。
gomobile 在包加载失败时只会报 no exported names in command-line-arguments,把根因吞掉。绑定前先跑一次 go build(与 bind 相同的 build tags),让 API 不兼容等真实错误直接暴露在日志里。
… API)

1.14 的 box.Context 新增第 6 个参数 adapter.CertificateProviderRegistry。libcore 用不到 acme/originca/tailscale,传空注册表。
get_source.sh 里 pushd .. 之后 cwd 已在仓库父目录,相对路径 buildScript/... 不存在,set -e 直接中断,导致后面的 libneko clone 被跳过(go 报 ../../libneko/go.mod 不存在)。改用 REPO_ROOT 绝对路径调用。
v0.3.1 的 go.mod 声明 module 为 github.com/exclavenetwork/sing-juicity(上游迁移组织),require 与 import 同步改新路径;包名仍是 juicity,代码引用不变。
- tls.NewSTDClient 新增 logger 参数;tls.Config.Config() 改为 STDConfig()
- adapter.ConnectionTracker 在 1.14 新增 RoutedFlow 方法,SbStatsService 补空实现(返回 nil)
- dialer.DoNotSelectInterface 已移除,删除对应 init 与 import
- wireguard 在 1.14 改为 endpoint,删除 RegisterOutbound(保留 RegisterEndpoint)
- adapter.DNSTransport 新增 Reset / ExchangeAsync,platformLocalDNSTransport 补实现
- tun.DefaultInterfaceMonitor 新增 RegisterMyInterface / MyInterfaces,stub 补空实现
上一轮补 MyInterfaces 时未先检查,文件里本就有 RegisterMyInterface,导致重复声明。
1.14 移除了 1.12 起废弃的配置写法,旧配置会在启动时报 decode config 失败。
用官方 sing-box v1.14.0 二进制 check 逐项验证并修正:

- DNS 服务器改为 type/server/server_port/path 新格式,地址字符串自动拆解
- dns.fakeip 对象移除,改用 fakeip 类型服务器;hosts 服务器用 predefined 字段
- rcode://success 服务器移除,改为 DNS 规则 action=predefined rcode=NOERROR
- hosts 规则不再依赖 ip_accept_any(1.14 响应匹配需前置 evaluate),改为按域名精确路由
- tun inbound 的 inet4_address/inet6_address 改为 address(1.12 移除)
- inbound 的 sniff / sniff_override_destination / domain_strategy 移除(1.13 移除),改用路由规则 action=sniff / action=resolve
1.14 移除了 direct outbound 的 fragment 字段,启动时报 unknown field。
- fragment 出站保留为纯 direct 跳板(仍被 detour 引用)
- 分片改用路由动作 action=route-options + tls_fragment=true
- 顺带补 route.default_domain_resolver,消除出站缺少域名解析器的废弃警告(1.16 将变强制)
- 1.14 移除了 route.concurrent_dial
- 1.14 拒绝 detour 到空的 direct 出站,为 DNS 服务器单独建 dns-direct-out(udp_fragment=false)
- version 归一化为 4/6(1.14 只接受这两种)
- 移除 quic_proxy_mode(已从 1.14 删除)
- obfs_mode 按 1.14 枚举传 tls,不再用空串表示 tls
1.14 拒绝 detour 到空的 direct 出站,fragment 出站被简化后触发
"detour to an empty direct outbound makes no sense"。
分片已由 route-options tls_fragment 实现,不再需要 detour 绕行;
fragment 出站保留给 hevtun 使用并补上参数。
constant.Version 默认就是 "unknown",官方靠 -ldflags -X 注入,我们的 gomobile bind 没传,
导致关于页显示 "sing-box: unknown"。补上 SINGBOX_VERSION(默认 v1.14.0)。
- 非法 DNS 地址降级为 local 并打日志,不再让服务起不来
- 国旗持久化缓存设 2000 条上限,超限清空自动重建
- DNS 策略统一到 dns.strategy,出站 domain_strategy 换成 domain_resolver(1.16 将移除的废弃字段)
- 删除 1.12 遗留的 interfaceMonitorStub.MyInterface() 死代码
四个删除场景现在都会同步清理对应节点的国旗缓存:
1. 主动删除节点(ProfileManager.deleteProfile / deleteProfile2,覆盖单删、多选、设置页)
2. 清理不可用配置(复用 deleteProfile2 入口)
3. 更新订阅时删除消失的节点(RawUpdater.toDelete)
4. 删除分组 / 清空分组(GroupManager.deleteGroup / clearGroup)

清理发生在删除动作之前,此时实体仍能取到服务器地址。
与 feat/profile-tools-v2 保持一致:弹出菜单移除,长按直接进入分组详情页(含改名与删除)。
机场的 Clash 订阅带 encryption: mlkem768x25519plus(VLESS Encryption)时,
配置生成会把 encryption 写进 outbound JSON。经实测 sing-box 1.14 的
VLESSOutboundOptions 没有该字段(strict JSON 直接 FATAL unknown field),
整个服务无法启动——比节点超时更严重。

sing-box 到 1.15.0-alpha.13 都未实现 VLESS Encryption(mihomo/Xray 能力)。
解析(Clash YAML / vless:// 链接 / 手动编辑)、存储、分享链接全部保留,
仅不再把该字段写入内核配置;内核未来支持后恢复一行赋值即可。
机场的 Clash 订阅普遍下发带 encryption 的 VLESS 节点,而 sing-box 官方
(1.14 / 1.15.0-alpha.13 / dev-next)至今未实现该协议层加密。

实现从 qingwan 的 1.15 定制内核移植(约 900 行纯 Go,无第三方依赖):
- protocol/vless/lx_encryption.go      配置解析 + 握手包装(超时/取消保护)
- protocol/vless/encryption/*.go       客户端实现(ML-KEM-768 + X25519)
- option/vless.go                      新增 encryption 字段
- protocol/vless/outbound.go           初始化与两处拨号接线

通过 patch 脚本注入官方源码,保持幂等、锚点失配即报错。
App 侧恢复 outbound encryption 字段输出(之前因内核不认而移除以避免 FATAL)。

已验证:干净 1.14 源码 + patch 脚本 → go build ./protocol/vless/... 通过。
为整组并发测速提供内核侧能力:
- boxapi.DialContextWithTag / CreateProxyHttpClientWithTag:强制经指定出站拨号
- libcore.UrlTestTag(box, tag, link, timeout):在已启动实例内按 tag 测速

已用 1.14 源码 + patch + libneko 在本地完成 go build 验证。
对标 Clash 的 /proxies/{name}/delay:整组节点只构建并启动一次内核,
按 outbound tag 并发拨号,每测完一个立刻回显(延迟+国旗同时出现)。

- ConfigBuilder 新增 testProfiles 参数,一次建出组内全部节点的 outbound
- BatchTestInstance:一次 init/launch,插件节点一并拉起后再开始测速
- urlTest() 改为单实例流程;启动失败时整组标记失败而不是卡在测试中
- 单节点测速按钮仍走原 TestInstance
- ConfigBuildResult 字段名是 profileTagMap,不是 tagMap
- group 是可空的 ProxyGroup,走安全调用并兜底空列表
查服务器 IP 的归属地无法反映真实出口(中转/落地分离、CDN 入口都会错),
整套国旗逻辑(显示、缓存、清理联动、geoip 查询 API)全部移除,由出口实测方案取代。
连接成功后经当前连接请求 Cloudflare trace(cdn-cgi/trace),
解析 ip= 与 loc=,在状态栏右侧显示国旗+国家名+出口 IP。

- libcore.HttpGet:经 mainInstance,按当前连接的出口发起 GET
- EgressInfo:解析与缓存,连接刚建立时自动重试最多 3 次
- 断开/连接中清空显示;换节点后重新连接会刷新
- 分组 selector 切换走 cbSelectorUpdate,服务不重启,延迟 1s 后主动重新探测
- 普通分组换节点会重连:清掉旧出口信息,重连成功后再探测
- StatsBar 新增 refreshEgressInfo(延迟),清缓存 + 最多重试 3 次
之前只走 libcore 默认出站,失败时状态栏一片空白无法判断。
现在优先用本机 mixed inbound(OkHttp,行为可控,支持代理认证),
失败再退回 libcore.httpGet;连接成功后先显示「检测中」,失败显示 --。
整组共用一个内核实例,组内任何一个节点配置非法(如 xhttp 传输内核不支持)
都会让 decode 失败,导致整组节点全部显示不可用。

现在:
- 静态剔除使用了内核不支持传输(xhttp)的节点(含链内节点)
- decode 失败时按 outbounds[N] 索引 + tag 反查定位节点,剔除后重建配置重试
- 被剔除的节点在 UI 上直接标为不可用并显示具体原因
- 单个节点测速失败后隔 300ms 重试一次,避免瞬时抖动误判
父类的 lateinit 属性无法在子类里用 ::x.isInitialized 检查,
改用 runCatching 兜住 UninitializedPropertyAccessException;
processes.close 的 scope 参数改用 CoroutineScope 构造,避免依赖 plus 扩展。
上游 sing-box 没有 Xray 的 XHTTP(split-http)传输,xhttp 节点此前无法使用。

- patch-singbox.py 新增 patch_xhttp:注入 constant/option/transport 三处注册点,
  并写入 option 适配层 transport/v2ray/xhttp.go
- 传输实现使用 github.com/justinwoo280/sing-xhttp v0.1.6(不依赖 sing-box,
  接口与 adapter.V2RayClientTransport / V2RayServerTransport 结构一致)
- go.mod 声明依赖;quic-go 对齐 sing-xhttp 要求的 sing-box-mod.7
- 批量测速移除「xhttp 不支持」的静态剔除(内核已支持),保留 decode 失败的动态剔除

本地已验证:补丁在 1.14/1.15 源码上可重复执行且幂等;
option/transport/v2ray/根包编译与 go vet 通过;
App 侧生成的 xhttp 配置 JSON 能被内核 option 正确解码。
订阅里的 fingerprint=unsafe(Xray 表示禁用 uTLS)会原样写入配置,
sing-box 初始化时直接报 unknown uTLS fingerprint,导致该节点不可用;
批量测速时还会让整组内核起不来。

- 指纹统一小写并只透传内核认识的 10 种(chrome/firefox/edge/safari/360/qq/ios/android/random/randomized)
- 未知值按 unsafe 语义处理:不启用 uTLS,走标准 TLS
- REALITY 必须启用 uTLS,未知指纹退回 chrome
- 批量测速的错误定位正则同时兼容 outbound[N](初始化阶段)与 outbounds[N](decode 阶段)
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.

1 participant