From f2f41e67c3368dced6867b6cc62a45651a197245 Mon Sep 17 00:00:00 2001 From: gongdao123 Date: Thu, 20 Aug 2026 11:56:36 +0800 Subject: [PATCH] luci-app-ssr-plus: set a nameserver for mihomo's built-in DNS When the DNS resolution mode is mihomo's built-in DNS (pdnsd_enable=7), the generated dns section enables DNS but never sets any nameserver. mihomo applies its defaults before patching them with the YAML, so the absent key leaves the built-in nameserver list in place: doh.pub and AliDNS over TLS. Both are encrypted, but both are domestic resolvers and cannot resolve blocked domains correctly, so those domains end up pointing at forged addresses and surface as intermittent TLS handshake failures. Nothing is logged, because the "NameServer cannot be empty" check never fires against a non-empty default. Two config paths never set a nameserver at all: the aggregate node built from a subscription (buildMihomoSubscribeYaml in subscribe.lua) and the single-node runtime documents built here. A Clash subscription that ships its own dns section is unaffected. Every other DNS mode already honours the existing "Anti-pollution DNS Server" option (tunnel_forward): dns2tcp, dns2socks, chinadns-ng and mosdns all read it. Only the mihomo path ignored it. What makes those modes work is not encryption but that the query is resolved outside the censored network, which is what dns2tcp/dns2socks provide. Reuse tunnel_forward as dns.nameserver and set respect-rules so the query follows the routing rules into the tunnel. respect-rules requires proxy-server-nameserver, so bootstrap it from the WAN-provided resolver and fall back to a public resolver when none is available; mihomo parses that list with respect-rules disabled, so the bootstrap cannot deadlock. The merge path only fills what is missing, so a subscription that already carries dns.nameserver keeps its own configuration untouched. Co-Authored-By: Claude Opus 5 --- .../usr/share/shadowsocksr/clash_yaml.lua | 96 +++++++++++++++---- 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua index 8d439f3e28d..1275a3e3a98 100644 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua @@ -306,6 +306,63 @@ local function prepare(input_path, output_path) return true end +local function get_wan_nameserver() + local f = io.open("/tmp/resolv.conf.d/resolv.conf.auto", "r") + if not f then + return nil + end + + local ip + for line in f:lines() do + ip = line:match("^nameserver%s+(%d+%.%d+%.%d+%.%d+)%s*$") + if ip then + break + end + end + f:close() + + return ip +end + +-- With no nameserver configured, mihomo falls back to plaintext public +-- resolvers, which are poisoned in the environment this package targets. +-- Reuse the existing "Anti-pollution DNS Server" option and let the query +-- follow the routing rules into the tunnel, which is the same semantic +-- dns2tcp/dns2socks provide for the other DNS modes. respect-rules requires +-- proxy-server-nameserver, so bootstrap it from the WAN-provided resolver. +local function build_dns_upstreams() + local forward = uci:get_first("shadowsocksr", "global", "tunnel_forward", "8.8.4.4:53") + if forward == nil or forward == "" then + forward = "8.8.4.4:53" + end + + return { + ["respect-rules"] = true, + ["proxy-server-nameserver"] = { "udp://" .. (get_wan_nameserver() or "223.5.5.5") .. ":53" }, + nameserver = { "udp://" .. forward } + } +end + +-- Only fills what the merged document is missing, so a subscription that +-- ships its own dns.nameserver keeps it. +local function fill_missing_dns_upstreams(doc) + if type(doc.dns) ~= "table" or not doc.dns.enable then + return 0 + end + + if has_nonempty_sequence(doc.dns.nameserver) then + return 0 + end + + for k, v in pairs(build_dns_upstreams()) do + if doc.dns[k] == nil then + doc.dns[k] = v + end + end + + return 1 +end + local function merge(raw_path, overlay_path, output_path) local raw_doc, raw_err = load_yaml(raw_path) if not raw_doc then @@ -323,6 +380,7 @@ local function merge(raw_path, overlay_path, output_path) local filled_groups = fill_empty_proxy_groups(raw_doc) local stripped_rules = strip_incompatible_script_rules(raw_doc) local merged = deep_merge(raw_doc, overlay_doc) + local filled_dns = fill_missing_dns_upstreams(merged) local ok, rendered = pcall(lyaml.dump, { merged }) if not ok or not rendered then io.stderr:write("dump_failed\n") @@ -330,7 +388,7 @@ local function merge(raw_path, overlay_path, output_path) end write_file(output_path, rendered) - io.stdout:write(string.format("filled_groups=%d stripped_script_rules=%d\n", filled_groups, stripped_rules)) + io.stdout:write(string.format("filled_groups=%d stripped_script_rules=%d filled_dns=%d\n", filled_groups, stripped_rules, filled_dns)) return true end @@ -427,6 +485,21 @@ local function get_filter_aaaa() return value end +local function build_dns_section(dns_mode) + local dns = { + enable = dns_mode == "7", + ["enhanced-mode"] = "redir-host", + listen = "127.0.0.1:5335", + ipv6 = get_filter_aaaa() ~= "1" + } + + for k, v in pairs(build_dns_upstreams()) do + dns[k] = v + end + + return dns +end + local function build_tuic_runtime_doc(sid, local_port, socks_port, mode) local server = get_server_field(sid, "server", "") local server_port = tonumber(get_server_field(sid, "server_port", "0")) or 0 @@ -503,12 +576,7 @@ local function build_tuic_runtime_doc(sid, local_port, socks_port, mode) rules = { "MATCH,PROXY" }, tun = { enable = false }, profile = { ["store-selected"] = true }, - dns = { - enable = dns_mode == "7", - ["enhanced-mode"] = "redir-host", - listen = "127.0.0.1:5335", - ipv6 = get_filter_aaaa() ~= "1" - } + dns = build_dns_section(dns_mode) } if mode == "socks" then @@ -944,12 +1012,7 @@ local function build_single_proxy_runtime_doc(proxy, local_port, socks_port, mod rules = { "MATCH,PROXY" }, tun = { enable = false }, profile = { ["store-selected"] = true }, - dns = { - enable = dns_mode == "7", - ["enhanced-mode"] = "redir-host", - listen = "127.0.0.1:5335", - ipv6 = get_filter_aaaa() ~= "1" - } + dns = build_dns_section(dns_mode) } if mode == "socks" then @@ -1219,12 +1282,7 @@ local function build_shadowsocks_runtime_doc(sid, local_port, socks_port, mode) rules = { "MATCH,PROXY" }, tun = { enable = false }, profile = { ["store-selected"] = true }, - dns = { - enable = dns_mode == "7", - ["enhanced-mode"] = "redir-host", - listen = "127.0.0.1:5335", - ipv6 = get_filter_aaaa() ~= "1" - } + dns = build_dns_section(dns_mode) } local listen_port = tonumber(local_port)