From 8a95857cad87f767b9c86b1b590f2f8b812491f1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 14 Oct 2026 11:17:33 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A6=84=20Sync=202026-10-14=2011:17:33?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-passwall/Makefile | 2 +- .../model/cbi/passwall/client/global.lua | 9 + luci-app-passwall/po/zh-cn/passwall.po | 6 +- .../root/usr/share/passwall/app.sh | 3 +- .../share/passwall/helper_smartdns_add.lua | 162 +++++++++++++++--- 5 files changed, 150 insertions(+), 32 deletions(-) diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index 5bf6fa3df..a14a498b2 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall PKG_VERSION:=26.10.4 -PKG_RELEASE:=332 +PKG_RELEASE:=333 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua index 9b3530aaf..84351ccc1 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua @@ -488,7 +488,16 @@ o:value("remote", translate("Remote DNS")) o:value("direct", translate("Direct DNS")) o.description = desc .. "" o:depends({dns_shunt = "dnsmasq", tcp_proxy_mode = "proxy", chn_list = "direct"}) + if has_smartdns then + o = s:taboption("DNS", ListValue, "smartdns_default_dns", translate("Default DNS")) + o.default = "direct" + o:value("remote", translate("Remote DNS")) + o:value("direct", translate("Direct DNS")) + o:value("auto", translate("Auto")) + o.description = desc + .. "
  • " .. translate("Auto: Prefer mainland China IPs returned by direct DNS and fall back to remote DNS when no usable result is available.") .. "
  • " + .. "" o:depends({dns_shunt = "smartdns", tcp_proxy_mode = "proxy", chn_list = "direct"}) end diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po index 6198ffff4..55a71c08e 100644 --- a/luci-app-passwall/po/zh-cn/passwall.po +++ b/luci-app-passwall/po/zh-cn/passwall.po @@ -226,9 +226,6 @@ msgstr "用于 DNS 查询时通知 DNS 服务器,客户端所在的地理位 msgid "This feature requires the DNS server to support the Edns Client Subnet (RFC7871)." msgstr "此功能需要 DNS 服务器支持 EDNS Client Subnet(RFC7871)。" -msgid "The effect is better, recommend." -msgstr "效果更好,推荐使用。" - msgid "ChinaDNS-NG (recommended)" msgstr "ChinaDNS-NG (推荐)" @@ -253,6 +250,9 @@ msgstr "智能,接受直连 DNS 空响应" msgid "Smart: Forward to both direct and remote DNS, if the direct DNS resolution result is a mainland China IP, then use the direct result, otherwise use the remote result." msgstr "智能:同时转发给直连和远程 DNS,如果直连 DNS 解析结果是大陆 IP,则使用直连结果,否则使用远程结果。" +msgid "Auto: Prefer mainland China IPs returned by direct DNS and fall back to remote DNS when no usable result is available." +msgstr "自动:优先使用直连 DNS 返回的大陆 IP,无可用结果时回退到远程 DNS。" + msgid "In smart mode, no-ip reply from Direct DNS:" msgstr "使用智能模式,直连 DNS 返回空响应时:" diff --git a/luci-app-passwall/root/usr/share/passwall/app.sh b/luci-app-passwall/root/usr/share/passwall/app.sh index b51ab8baa..d0106a220 100755 --- a/luci-app-passwall/root/usr/share/passwall/app.sh +++ b/luci-app-passwall/root/usr/share/passwall/app.sh @@ -1283,6 +1283,7 @@ start_dns() { else smartdns_remote_dns="tcp://1.1.1.1" fi + local smartdns_default_dns=$(config_n_get @global[0] smartdns_default_dns direct) echolog " - 域名解析:使用SmartDNS,请确保配置正常。" china_ng_listen="127.0.0.1#${SMARTDNS_LISTEN_PORT}" @@ -1296,7 +1297,7 @@ start_dns() { local subnet_ip=$(config_n_get @global[0] remote_dns_client_ip) lua $APP_PATH/helper_smartdns_add.lua -FLAG "default" -SMARTDNS_CONF "/tmp/etc/smartdns/$CONFIG.conf" \ -LISTEN_PORT ${SMARTDNS_LISTEN_PORT} -LOCAL_PORT ${SMARTDNS_LOCAL_PORT} -DNS_REDIRECT ${DNS_REDIRECT} \ - -LOCAL_GROUP ${group_domestic:-null} -REMOTE_GROUP "passwall_proxy" -REMOTE_PROXY_SERVER ${GLOBAL_SOCKS_server} -USE_DEFAULT_DNS "${USE_DEFAULT_DNS:-direct}" \ + -LOCAL_GROUP ${group_domestic:-null} -REMOTE_GROUP "passwall_proxy" -REMOTE_PROXY_SERVER ${GLOBAL_SOCKS_server} -USE_DEFAULT_DNS "${smartdns_default_dns}" \ -REMOTE_DNS ${smartdns_remote_dns} -DNS_MODE ${DNS_MODE:-socks} -TUN_DNS ${TUN_DNS} \ -USE_DIRECT_LIST "${USE_DIRECT_LIST}" -USE_PROXY_LIST "${USE_PROXY_LIST}" -USE_BLOCK_LIST "${USE_BLOCK_LIST}" -USE_GFW_LIST "${USE_GFW_LIST}" -CHN_LIST "${CHN_LIST}" \ -NODE ${NODE} -DEFAULT_PROXY_MODE "${TCP_PROXY_MODE}" -NO_PROXY_IPV6 ${FILTER_PROXY_IPV6:-0} -NFTFLAG ${nftflag:-0} \ diff --git a/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua b/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua index 8748a9894..d87701ad9 100644 --- a/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua +++ b/luci-app-passwall/root/usr/share/passwall/helper_smartdns_add.lua @@ -60,6 +60,11 @@ local function is_file_nonzero(path) return nil end +local function get_new_port(start_port, protocol) + local cmd_format = ". /usr/share/passwall/utils.sh ; echo -n $(get_new_port %s %s)" + return tonumber(sys.exec(string.format(cmd_format, api.util.shellquote(tostring(start_port or "auto")), api.util.shellquote(protocol or "tcp,udp")))) +end + local function insert_unique(dest_table, value, lookup_table) if not lookup_table[value] then table.insert(dest_table, value) @@ -113,10 +118,24 @@ sys.call("mkdir -p %s" % FLAG_PATH) sys.call("mkdir -p %s" % CACHE_RULES_PATH) local LOCAL_EXTEND_ARG = "" -if LOCAL_GROUP == "null" or LOCAL_GROUP == "" then +local LOCAL_SERVERS = {} +local SERVER_TYPES = { + udp = "server", tcp = "server-tcp", tls = "server-tls", + https = "server-https", quic = "server-quic", h3 = "server-h3" +} +if not LOCAL_GROUP or LOCAL_GROUP == "null" or LOCAL_GROUP == "" then log(" * 注意:SmartDNS 国内分组名未设置,DNS 将无法正常工作!!!") os.exit(1) else + api.uci:foreach("smartdns", "server", function(s) + if s.enabled ~= "0" and s.server_group == LOCAL_GROUP and s.ip and s.ip ~= "" and SERVER_TYPES[s.type or "udp"] then + table.insert(LOCAL_SERVERS, s) + end + end) + if #LOCAL_SERVERS == 0 then + log(" * 注意:SmartDNS 国内分组 " .. LOCAL_GROUP .. " 没有可用的 DNS 服务器,DNS 将无法正常工作!!!") + os.exit(1) + end --从smartdns配置中读取参数 local custom_conf_path = "/etc/smartdns/custom.conf" local options = { @@ -163,15 +182,14 @@ else val = custom_config[opt.config_key] or api.uci_get("smartdns", "@smartdns[0]", opt.key) or opt.default end if val == "yes" then val = "1" elseif val == "no" then val = "0" end + local arg if opt.yes_no then - local arg = (val == "1" and opt.arg_yes or opt.arg_no) - if arg and arg ~= "" then - LOCAL_EXTEND_ARG = LOCAL_EXTEND_ARG .. (LOCAL_EXTEND_ARG ~= "" and " " or "") .. arg - end - else - if val and (not opt.value or (opt.invert and val ~= opt.value) or (not opt.invert and val == opt.value)) then - LOCAL_EXTEND_ARG = LOCAL_EXTEND_ARG .. (LOCAL_EXTEND_ARG ~= "" and " " or "") .. (opt.prefix or "") .. (opt.arg or val) - end + arg = (val == "1" and opt.arg_yes or opt.arg_no) + elseif val and (not opt.value or (opt.invert and val ~= opt.value) or (not opt.invert and val == opt.value)) then + arg = (opt.prefix or "") .. (opt.arg or val) + end + if arg and arg ~= "" then + LOCAL_EXTEND_ARG = LOCAL_EXTEND_ARG .. (LOCAL_EXTEND_ARG ~= "" and " " or "") .. arg end end end @@ -182,10 +200,29 @@ end if REMOTE_DNS then REMOTE_DNS = REMOTE_DNS:gsub("#", ":") end -sys.call('sed -i "/passwall/d" /etc/smartdns/custom.conf >/dev/null 2>&1') - local SANDBOX_GROUP = "passwall_" .. FLAG +local AUTO_GROUP = "passwall_auto" +local AUTO_ENTRY_GROUP = AUTO_GROUP .. "_entry" +local auto_config_lines = {} +--设置默认 DNS 分组(兜底组),全局代理模式仍使用远程组。 +local DEFAULT_DNS_GROUP = (USE_DEFAULT_DNS == "direct" and LOCAL_GROUP) or + (USE_DEFAULT_DNS == "remote" and REMOTE_GROUP) or + (USE_DEFAULT_DNS == "auto" and AUTO_GROUP) +local only_global = (DEFAULT_PROXY_MODE == "proxy" and CHN_LIST == "0" and USE_GFW_LIST == "0") and 1 +if only_global == 1 then + DEFAULT_DNS_GROUP = REMOTE_GROUP +end +local AUTO_PORT +if DEFAULT_DNS_GROUP == AUTO_GROUP then + AUTO_PORT = get_new_port(15453, "udp") + if not AUTO_PORT then + log(" * 注意:SmartDNS 自动模式无法获取可用的 UDP 回环端口!") + os.exit(1) + end +end +sys.call('sed -i "/passwall/d" /etc/smartdns/custom.conf >/dev/null 2>&1') local force_https_soa = api.uci_get_c("@global[0]", "force_https_soa") or 0 +local force_qtype_soa = tonumber(force_https_soa) == 1 and "force-qtype-SOA 65" or "force-qtype-SOA -,65" local proxy_server_name = "psw-proxy-server" local listen_address = "[::]:" .. LISTEN_PORT .. (DNS_REDIRECT and "" or "@lo") local local_domain = DNS_REDIRECT and api.uci_get("dhcp", "@dnsmasq[0]", "domain") @@ -194,7 +231,6 @@ config_lines = { tonumber(LISTEN_PORT) ~= 0 and ("bind-tcp " .. listen_address .. " -group " .. SANDBOX_GROUP) or "", (tonumber(LOCAL_PORT) ~= 0 and LOCAL_GROUP) and "bind [::]:" .. LOCAL_PORT .. "@lo -group " .. LOCAL_GROUP or "", (tonumber(LOCAL_PORT) ~= 0 and LOCAL_GROUP) and "bind-tcp [::]:" .. LOCAL_PORT .. "@lo -group " .. LOCAL_GROUP or "", - tonumber(force_https_soa) == 1 and "force-qtype-SOA 65" or "force-qtype-SOA -,65", "server 223.5.5.5 -bootstrap-dns", -- Set the suffix before loading hosts/leases so initial records are expanded too. (local_domain and local_domain ~= "") and "local-domain " .. local_domain or "", @@ -224,6 +260,53 @@ if DNS_REDIRECT then table.insert(config_lines, string.format("odhcpd-lease-file %q", odhcpd_lease)) end end +if DEFAULT_DNS_GROUP == AUTO_GROUP then + -- 复制国内组中启用的 UCI DNS 服务器,原组不添加白名单过滤。 + for _, s in ipairs(LOCAL_SERVERS) do + local server_type = SERVER_TYPES[s.type or "udp"] + local address = s.ip + if not address:find("://", 1, true) then + if address:find(":", 1, true) and not address:find("[", 1, true) then + address = "[" .. address .. "]" + end + if s.port and s.port ~= "" then address = address .. ":" .. s.port end + end + local args = {server_type, address} + for _, key in ipairs({"tls_host_verify", "host_name", "http_host", "spki_pin", "set_mark"}) do + if s[key] and s[key] ~= "" then + table.insert(args, "-" .. key:gsub("_", "-")) + table.insert(args, s[key]) + end + end + for _, key in ipairs({"no_check_certificate", "blacklist_ip", "check_edns"}) do + if s[key] == "1" and not (key == "blacklist_ip" and NO_PROXY_IPV6 == "1") then + table.insert(args, "-" .. key:gsub("_", "-")) + end + end + if s.use_proxy == "1" then table.insert(args, "-proxy default-proxy") end + -- 国内副本作为主用服务器,不继承后备标记。 + local addition_arg = api.trim((" " .. (s.addition_arg or "")):gsub("(%s)%-%-?fallback%f[%s%z]", "%1")) + if NO_PROXY_IPV6 == "1" then + -- 国内副本只使用白名单,避免远程 IPv6 黑名单同时过滤国内结果。 + addition_arg = api.trim((" " .. addition_arg):gsub("(%s)%-%-?blacklist%-ip%f[%s%z]", "%1")) + end + if addition_arg ~= "" then table.insert(args, addition_arg) end + table.insert(args, "-exclude-default-group -whitelist-ip") + table.insert(auto_config_lines, table.concat(args, " ")) + end +end + +-- 由下方 DNS_MODE 分支调用,服务器参数来自 PassWall 的 REMOTE_DNS 或 TUN_DNS。 +local function add_remote_server(server_param) + table.insert(config_lines, server_param .. " -group " .. REMOTE_GROUP .. " -exclude-default-group") + if DEFAULT_DNS_GROUP == AUTO_GROUP then + -- 远程副本作为自动组的后备服务器,保留代理、ECS 和传输协议参数。 + local auto_param = server_param .. " -exclude-default-group -fallback" + if NO_PROXY_IPV6 == "1" then auto_param = auto_param .. " -blacklist-ip" end + table.insert(auto_config_lines, auto_param) + end +end + if DNS_MODE == "socks" then for w in string.gmatch(REMOTE_DNS, '[^|]+') do local server_dns = api.trim(w) @@ -274,35 +357,60 @@ if DNS_MODE == "socks" then server_param = server_param .. " -proxy " .. proxy_server_name end - server_param = server_param .. " -group " .. REMOTE_GROUP .. " -exclude-default-group" if SUBNET and SUBNET ~= "" and SUBNET ~= "0" then server_param = server_param .. " -subnet " .. SUBNET end - table.insert(config_lines, server_param) + add_remote_server(server_param) end else - local server_param = string.format("server %s -group %s -exclude-default-group", TUN_DNS:gsub("#", ":"), REMOTE_GROUP) - table.insert(config_lines, server_param) + add_remote_server("server " .. TUN_DNS:gsub("#", ":")) log(" - " .. DNS_MODE:gsub("^%l",string.upper) .. " " .. TUN_DNS .. " -> " .. REMOTE_GROUP) end --- Isolate PassWall domain rules within dedicated group to avoid polluting SmartDNS default group -table.insert(config_lines, "group-begin " .. SANDBOX_GROUP) - ---设置默认 DNS 分组(托底组) -local DEFAULT_DNS_GROUP = (USE_DEFAULT_DNS == "direct" and LOCAL_GROUP) or - (USE_DEFAULT_DNS == "remote" and REMOTE_GROUP) -local only_global = (DEFAULT_PROXY_MODE == "proxy" and CHN_LIST == "0" and USE_GFW_LIST == "0") and 1 --没有启用中国列表和GFW列表时(全局) -if only_global == 1 then - DEFAULT_DNS_GROUP = REMOTE_GROUP +if DEFAULT_DNS_GROUP == AUTO_GROUP then + -- IP 集合和过滤规则集中在自动规则组内。 + if NO_PROXY_IPV6 == "1" then + table.insert(auto_config_lines, "ip-rules ::/0 -blacklist-ip") + end + for _, name in ipairs({"chnroute", "chnroute6"}) do + local path = RULES_PATH .. "/" .. name + if is_file_nonzero(path) then + local set_name = AUTO_GROUP .. "-" .. name + table.insert(auto_config_lines, string.format("ip-set -name %s -file %s", set_name, path)) + local ip_rules = "ip-rules ip-set:" .. set_name .. " -whitelist-ip" + if name == "chnroute6" and NO_PROXY_IPV6 == "1" then + -- 最长前缀匹配时,大陆 IPv6 网段也需要黑名单标记才能过滤远程结果。 + ip_rules = ip_rules .. " -blacklist-ip" + end + table.insert(auto_config_lines, ip_rules) + else + log(" * 注意:SmartDNS 智能模式大陆 IP 列表不存在或为空:" .. path) + end + end + -- 兜底通过 UDP 回环入口进入自动规则组,避免 IP 过滤影响其他查询。 + table.insert(config_lines, "bind [::]:" .. AUTO_PORT .. "@lo -group " .. AUTO_GROUP) + table.insert(config_lines, "server [::1]:" .. AUTO_PORT .. " -group " .. AUTO_ENTRY_GROUP .. " -exclude-default-group") + table.insert(config_lines, "group-begin " .. AUTO_GROUP .. " -inherit none") + table.insert(config_lines, force_qtype_soa) + merge_array(config_lines, auto_config_lines) + table.insert(config_lines, "domain-rules /./ -nameserver " .. AUTO_GROUP .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")) + table.insert(config_lines, "group-end") end + +-- PassWall 规则组不继承外部规则。 +table.insert(config_lines, "group-begin " .. SANDBOX_GROUP .. " -inherit none") +table.insert(config_lines, force_qtype_soa) + if DEFAULT_DNS_GROUP then - local domain_rules_str = "domain-rules /./ -nameserver " .. DEFAULT_DNS_GROUP + local domain_rules_str = "domain-rules /./ -nameserver " .. (DEFAULT_DNS_GROUP == AUTO_GROUP and AUTO_ENTRY_GROUP or DEFAULT_DNS_GROUP) if DEFAULT_DNS_GROUP == REMOTE_GROUP then domain_rules_str = domain_rules_str .. " -speed-check-mode none -d no -no-serve-expired" domain_rules_str = domain_rules_str .. " -address " .. ((only_global and IS_SHUNT_NODE) and "-6" or (NO_PROXY_IPV6 == "1" and "#6" or "-6")) elseif DEFAULT_DNS_GROUP == LOCAL_GROUP then domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "") + elseif DEFAULT_DNS_GROUP == AUTO_GROUP then + -- 转接层只转发请求,测速、双栈优选及缓存交给自动规则组处理。 + domain_rules_str = domain_rules_str .. " -speed-check-mode none -d no -no-cache" end table.insert(config_lines, domain_rules_str) end @@ -803,7 +911,7 @@ if #config_lines > 0 then end if DEFAULT_DNS_GROUP then - log(string.format(" - 默认 DNS 分组:%s", DEFAULT_DNS_GROUP)) + log(string.format(" - 默认 DNS 分组:%s", DEFAULT_DNS_GROUP == AUTO_GROUP and "自动" or DEFAULT_DNS_GROUP)) end fs.symlink(TMP_CONF_FILE, SMARTDNS_CONF)