Skip to content

Commit 276fa5f

Browse files
committed
Improve the DNS address processing logic to ensure that remote DNS is not incorrectly normalized to the local address, and add user prompts to update settings.
1 parent 5853e0b commit 276fa5f

3 files changed

Lines changed: 29 additions & 3 deletions

File tree

REPO_SCHEMA.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Android 端的代码主要分为两个核心包:
2929
- [`io/nekohasekai/sagernet/database/`](app/src/main/java/io/nekohasekai/sagernet/database): 数据库与偏好设置模块。
3030
- `SagerDatabase.kt`: Room 数据库定义,存储代理配置、分组、规则等。
3131
- `ProfileManager.kt` / `GroupManager.kt`: 配置和分组管理器。
32-
- [`io/nekohasekai/sagernet/fmt/`](app/src/main/java/io/nekohasekai/sagernet/fmt): 各种代理协议的配置格式化与解析。`ConfigBuilder.kt` 生成 sing-box 配置,已对齐官方 v1.13 schema:入站 sniff/解析目标地址迁移为路由规则动作(`sniff`/`resolve`,置于规则最前);tun 地址用合并字段 `address``inet4_address`/`endpoint_independent_nat` 等 legacy 字段已弃用);`concurrent_dial` 映射为 `default_network_strategy: "hybrid"`;DNS 地址 `hosts` 归一化为 `local`
32+
- [`io/nekohasekai/sagernet/fmt/`](app/src/main/java/io/nekohasekai/sagernet/fmt): 各种代理协议的配置格式化与解析。`ConfigBuilder.kt` 生成 sing-box 配置,已对齐官方 v1.13 schema:入站 sniff/解析目标地址迁移为路由规则动作(`sniff`/`resolve`,置于规则最前);tun 地址用合并字段 `address``inet4_address`/`endpoint_independent_nat` 等 legacy 字段已弃用);`concurrent_dial` 映射为 `default_network_strategy: "hybrid"`;DNS 地址归一化:dns-direct/订阅 resolver 的 `hosts` 视为 `local`(本机直解,语义正确),远程 DNS 的 `hosts`/`local`/`localhost`/`fakeip` 占位符一律回退为 `https://8.8.8.8/dns-query` 并 Toast 提示用户修改设置(本机直解用作远程即 DNS 泄露),且 dns-remote 显式 `detour`=当前节点(对齐 Throne 桌面端 `detour:"proxy"`
3333
- 支持 Shadowsocks, VMess, Trojan, Hysteria, Juicity, Naive, WireGuard 等协议的配置解析与转换。
3434
- `SingBoxOutboundParser.kt`: 将 sing-box 配置中的单个 outbound JSON 还原为原生协议 Bean(支持 shadowsocks/vmess/vless/trojan/hysteria/hysteria2/tuic/socks/http/wireguard/anytls,含 TLS/transport/multiplex 子块解析)。用于订阅返回完整 sing-box 配置(含 `outbounds`)的场景:`RawUpdater.parseJSON``outbounds` 分支对每个 outbound 优先调用 `parseSingBoxOutbound()` 还原原生节点,不支持的类型或解析失败时回退为 `ConfigBean`(自定义 JSON);`dns`/`block`/`direct`/`selector`/`urltest` 类型的 outbound 始终跳过。
3535
- [`io/nekohasekai/sagernet/ui/`](app/src/main/java/io/nekohasekai/sagernet/ui): 各种 Activity 和 Fragment 界面。

ROO_KERNEL_TODO.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
- [x] 首轮真机运行修复(2026-08-01,配置 schema 对齐官方 v1.13,[`ConfigBuilder.kt`](app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt) 等):
3030
- `route.concurrent_dial` 1.13 已删除(`box.New` 报 unknown field 直接崩,内核完全起不来)→ 映射为官方替代 `default_network_strategy: "hybrid"`(WiFi/移动数据并发竞速拨号);设置项保留,7 语言摘要改为明确说明(需 WiFi+移动数据同开、更耗电),防小白盲开
3131
- 入站 `sniff`/`sniff_override_destination`/`domain_strategy` 字段 1.13 硬错误(legacy inbound fields removed,tun/mixed 均中招)→ 迁移为路由规则动作 `{"action":"sniff"}` / `{"action":"resolve","strategy":...}`(置于规则最前);`sniff_override_destination` 官方无替代(`OverrideDestination` 已成死代码),`trafficSniffing` 设置退化为 关/开 两档(`traffic_sniffing_values` 数组缩减,存量值 "2" 按开启处理)
32-
- DNS 地址 `hosts`(旧 fork=系统解析器):官方 legacy 升级把裸 `hosts` 静默误判为 UDP 服务器域名(远程 DNS 全灭且不报错)→ `normalizeDnsAddress()` 归一化为 `local`(direct/remote/订阅 resolver 三处)
32+
- DNS 地址 `hosts`(旧 fork=系统解析器):官方 legacy 升级把裸 `hosts` 静默误判为 UDP 服务器域名(远程 DNS 全灭且不报错)→ `normalizeDnsAddress()` 归一化为 `local`(仅 dns-direct/订阅 resolver 等本机直解场景)
33+
- 远程 DNS 防泄露(2026-08-01 二轮修复):首版把远程 `hosts` 也归一化为 `local`,实测 DNS 泄露(官方内核 `local` 在 Android 走平台接口经物理网卡直连系统 DNS,与 fork `hosts` "节点代访问"语义不对齐)→ 远程 DNS 遇 `hosts`/`local`/`localhost`/`fakeip` 占位符一律回退 `https://8.8.8.8/dns-query` + Toast 提示(`normalizeRemoteDnsAddress()`);dns-remote 补 `detour = mainProxyTag`(当前节点,对齐 Throne 桌面端 `detour:"proxy"`/husi `detour=mainTag`;legacy 无 detour 时虽兜底走默认出站,显式指定才无歧义)。注:官方 legacy DNS 格式 1.14 将移除,且新结构化格式下无 detour 的 DNS server 默认**直连**(不再兜底默认出站),届时迁移必须保留显式 detour
3334
- tun 入站 `inet4_address`/`inet6_address`:字段虽仍在结构体(可解析),但构造函数硬报错(1.12 已移除,`initialize inbound[0]` 失败)→ 迁移为合并字段 `address``endpoint_independent_nat` 同步停止发射(官方已移除语义,当前无硬检查但迟早硬化)。Kotlin 侧 `startVpn` 不解析 tun JSON(地址/路由/MTU 用 DataStore),零影响
3435
- **接口监视器补齐**(`no available network interface` 全部拨号失败,两半缺一不可):① 官方 v1.13 只要注册了 PlatformInterface 就强制走平台 `CreateDefaultInterfaceMonitor`(`route/network.go`),旧 `interfaceMonitorStub` 空实现 `DefaultInterface()=nil` → [`interface_monitor.go`](libcore/interface_monitor.go) 重写为完整实现(参考 husi `tun.go`,含 `InterfaceUpdateListener` JNI 回调接口),`BoxPlatformInterface` 新增 `StartDefaultInterfaceMonitor`/`CloseDefaultInterfaceMonitor`;Kotlin 侧 [`NativeInterface.kt`](app/src/main/java/moe/matsuri/nb4a/NativeInterface.kt) 复用现成 `DefaultNetworkListener`(`registerBestMatchingNetworkCallback` 天然避开 VPN 接口,报告物理默认网络)。② **平台接口枚举同为硬性要求**:注册 PlatformInterface 后拨号器恒走并行接口选择(`common/dialer` strategy 默认非 nil),而 `NetworkManager.UpdateInterfaces()` 只在 `UsePlatformNetworkInterfaces()=true` 的平台分支缓存接口列表——为 false 时列表恒空,`selectInterfaces` 选不出接口报同款错误 → `UsePlatformNetworkInterfaces()` 改 true,`NetworkInterfaces()` 经 JNI `GetInterfaces()` 实现(husi `platform_box.go` 转换逻辑);新增 `iterator.go`(StringIterator)、`network_interface.go`(NetworkInterface/迭代器/类型常量)、`link_flags_unix.go`(IFF_* → net.Flags);Kotlin 侧 `getInterfaces()` 枚举 allNetworks+LinkProperties+NetworkCapabilities(husi 同款),`interfaceMonitor.UpdateDefaultInterface` 回调时先 `networkManager.UpdateInterfaces()` 刷新缓存(Initialize 晚于 CreateDefaultInterfaceMonitor 调用,wrapper 指针延迟访问)。T1-6 的 interfaceMonitor 部分就此完成
3536

app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean
3232
import io.nekohasekai.sagernet.fmt.wireguard.buildSingBoxOutboundWireguardBean
3333
import io.nekohasekai.sagernet.ktx.isIpAddress
3434
import io.nekohasekai.sagernet.ktx.mkPort
35+
import io.nekohasekai.sagernet.ktx.runOnMainDispatcher
3536
import io.nekohasekai.sagernet.utils.PackageCache
3637
import moe.matsuri.nb4a.*
3738
import moe.matsuri.nb4a.SingBoxOptions.*
@@ -205,8 +206,30 @@ fun buildConfig(
205206

206207
// 旧 fork 的 "hosts" DNS 地址 = 系统解析器;官方内核无此 scheme,对应 "local"
207208
// (官方 legacy 升级会把裸 "hosts" 误判为 UDP 服务器域名,静默失败)。
209+
// 仅用于 dns-direct / 订阅 resolver 等"本就应本机直解"的场景,远程 DNS 禁止走此函数。
208210
fun normalizeDnsAddress(address: String): String = if (address == "hosts") "local" else address
209211

212+
// 远程 DNS 必须由节点代访问(配合下方 detour=当前节点),绝不能归一化为本机直解的 local:
213+
// 官方内核下 local/hosts/fakeip 都是本机解析占位符(Android 上 local 走平台接口经物理网卡
214+
// 直连系统 DNS),用作远程即 DNS 泄露,与 fork 时代 hosts 语义不对齐。
215+
// 统一回退为公共 DoH 并 Toast 提示用户修改设置。
216+
fun normalizeRemoteDnsAddress(address: String): String {
217+
return when (address) {
218+
"hosts", "local", "localhost", "fakeip" -> {
219+
runOnMainDispatcher {
220+
Toast.makeText(
221+
SagerNet.application,
222+
"Warning: \"$address\" is not supported as remote DNS and has been replaced with https://8.8.8.8/dns-query. Please update your remote DNS setting.",
223+
Toast.LENGTH_LONG
224+
).show()
225+
}
226+
"https://8.8.8.8/dns-query"
227+
}
228+
229+
else -> address
230+
}
231+
}
232+
210233
return MyOptions().apply {
211234
if (!forTest) {
212235
experimental = ExperimentalOptions().apply {
@@ -935,8 +958,10 @@ fun buildConfig(
935958
remoteDns.firstOrNull().let {
936959
// Always use direct DNS for urlTest
937960
if (!forTest) dns.servers.add(DNSServerOptions().apply {
938-
address = normalizeDnsAddress(it ?: throw Exception("No remote DNS, check your settings!"))
961+
address = normalizeRemoteDnsAddress(it ?: throw Exception("No remote DNS, check your settings!"))
939962
tag = "dns-remote"
963+
// 远程 DNS 交给当前节点代访问(对齐 Throne 桌面端 detour=proxy),本机直出即泄露。
964+
detour = mainProxyTag
940965
address_resolver = "dns-direct"
941966
strategy = autoDnsDomainStrategy(SingBoxOptionsUtil.domainStrategy(tag))
942967
})

0 commit comments

Comments
 (0)