local api = require "luci.passwall.api" local var = api.get_args(arg) local FLAG = var["-FLAG"] local DIRECT_DNS = var["-DIRECT_DNS"] local LOCAL_PORT = tonumber(var["-LOCAL_PORT"]) or 0 local REMOTE_PROXY_SERVER = var["-REMOTE_PROXY_SERVER"] local USE_DEFAULT_DNS = var["-USE_DEFAULT_DNS"] local REMOTE_DNS = var["-REMOTE_DNS"] local TUN_DNS = var["-TUN_DNS"] local DNS_MODE = var["-DNS_MODE"] local NODE = var["-NODE"] local USE_DIRECT_LIST = var["-USE_DIRECT_LIST"] local USE_PROXY_LIST = var["-USE_PROXY_LIST"] local USE_BLOCK_LIST = var["-USE_BLOCK_LIST"] local USE_GFW_LIST = var["-USE_GFW_LIST"] local CHN_LIST = var["-CHN_LIST"] local DEFAULT_PROXY_MODE = var["-DEFAULT_PROXY_MODE"] local NO_PROXY_IPV6 = var["-NO_PROXY_IPV6"] local NO_LOGIC_LOG = var["-NO_LOGIC_LOG"] local NFTFLAG = var["-NFTFLAG"] local SUBNET = var["-SUBNET"] local LISTEN_PORT = var["-LISTEN_PORT"] local DNS_REDIRECT = var["-DNS_REDIRECT"] == "1" local LOG = var["-LOG"] == "1" local LOG_LEVEL = var["-LOG_LEVEL"] or "error" local LOG_SIZE = tonumber(var["-LOG_SIZE"]) or 1 local UI = var["-UI"] == "1" local UI_PORT = tonumber(var["-UI_PORT"]) or 9880 local UI_DATA_DIR = var["-UI_DATA_DIR"] local UI_LOG_MAX_AGE = tonumber(var["-UI_LOG_MAX_AGE"]) or 7 local sys = api.sys local fs = api.fs local datatypes = api.datatypes local TMP_PATH = api.TMP_PATH local RULES_PATH = "/usr/share/passwall/rules" local USER_RULES_PATH = "/etc/passwall/rules" local CACHE_RULES_PATH = api.CACHE_PATH .. "/user_rules" local FLAG_PATH = TMP_PATH .. "/acl/" .. FLAG local TMP_CONF_FILE = FLAG_PATH .. "/smartdns.conf" local SMARTDNS_DATA_PATH = api.CACHE_PATH .. "/smartdns" local config_lines = {} local tmp_lines = {} local USE_GEOVIEW = api.uci_get_c("@global_rules[0]", "enable_geoview") local IS_SHUNT_NODE = api.uci_get_c(NODE, "protocol") == "_shunt" if not api.is_finded("geoview") then USE_GEOVIEW = "0" end local function log(...) if NO_LOGIC_LOG == "1" then return end api.log(...) end local function is_file_nonzero(path) if path and #path > 1 and (fs.stat(path, "size") or 0) > 0 then return true end return false end local function insert_unique(dest_table, value, lookup_table) if not lookup_table[value] then table.insert(dest_table, value) lookup_table[value] = true end end local function merge_array(array1, array2) for i, line in ipairs(array2) do table.insert(array1, #array1 + 1, line) end end local function insert_array_before(array1, array2, target) --将array2插入到array1的target前面,target不存在则追加 for i, line in ipairs(array1) do if line == target then for j = #array2, 1, -1 do table.insert(array1, i, array2[j]) end return end end merge_array(array1, array2) end local function insert_array_after(array1, array2, target) --将array2插入到array1的target后面,target不存在则追加 for i, line in ipairs(array1) do if line == target then for j = 1, #array2 do table.insert(array1, i + j, array2[j]) end return end end merge_array(array1, array2) end local function get_geosite(list_arg, out_path) local geosite_path = api.uci_get_c("@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/" geosite_path = geosite_path:match("^(.*)/") .. "/geosite.dat" if not is_file_nonzero(geosite_path) then return 1, "File geosite.dat not found" end if not list_arg or list_arg == "" then return 1, "Site list cannot be empty" end if not out_path or out_path == "" then return 1, "Output path cannot be empty" end local bin = api.finded_com("geoview") local cmd = string.format("%q -type geosite -append=true -input %q -list %q -output %q -lowmem=true", bin, geosite_path, list_arg, out_path) return api.exec_call(cmd) end sys.call("mkdir -p %s" % FLAG_PATH) sys.call("mkdir -p %s" % CACHE_RULES_PATH) sys.call("mkdir -p " .. api.util.shellquote(SMARTDNS_DATA_PATH)) local LOG_LEVELS = {off = true, fatal = true, error = true, warn = true, notice = true, info = true, debug = true} if not LOG_LEVELS[LOG_LEVEL] then LOG_LEVEL = "error" end if LOG_SIZE < 1 or LOG_SIZE % 1 ~= 0 then LOG_SIZE = 1 end if UI_PORT < 1 or UI_PORT > 65535 or UI_PORT % 1 ~= 0 then UI_PORT = 9880 end if UI_LOG_MAX_AGE < 1 or UI_LOG_MAX_AGE % 1 ~= 0 then UI_LOG_MAX_AGE = 7 end if not UI_DATA_DIR or UI_DATA_DIR == "" then UI_DATA_DIR = SMARTDNS_DATA_PATH end local UI_PLUGIN = "/usr/lib/smartdns_ui.so" if not fs.access(UI_PLUGIN) then UI_PLUGIN = "/usr/lib/smartdns/smartdns_ui.so" end local ENABLE_UI = UI and fs.access(UI_PLUGIN) local data_dir = ENABLE_UI and UI_DATA_DIR or SMARTDNS_DATA_PATH if data_dir ~= SMARTDNS_DATA_PATH then sys.call("mkdir -p " .. api.util.shellquote(data_dir)) end -- 统一生成直连、远程上游,协议和默认端口由 SmartDNS 根据 URL 识别。 local function make_server_param(endpoint) local dns = api.split(api.trim(endpoint), ",") local address = api.trim(dns[1] or "") if address == "" then return nil end local protocol, host_address = address:match("^(%w+)://(.+)$") protocol = protocol and protocol:lower() address = host_address or address local host, port = address:match("^(.-)#(%d+)$") if host then if host:find(":", 1, true) and not host:find("[", 1, true) then host = "[" .. host .. "]" end address = host .. ":" .. port elseif datatypes.ip6addr(address) and not address:find("[", 1, true) then address = "[" .. address .. "]" end local server_param = "server " .. (protocol and (protocol .. "://") or "") .. address if dns[2] and dns[2] ~= "" then -- 保留 URL 中的域名,固定连接 IP 交给 SmartDNS 处理,避免手动替换影响 SNI 和 HTTP Host。 server_param = server_param .. " -host-ip " .. api.trim(dns[2]):gsub("^%[(.*)%]$", "%1") end return server_param end local DIRECT_SERVERS = {} for endpoint in string.gmatch(DIRECT_DNS or "", "[^|]+") do local server_param = make_server_param(endpoint) if server_param then -- 复用 PassWall 的直连标记,使所有协议及动态域名上游绕过本机透明代理。 table.insert(DIRECT_SERVERS, server_param .. " -set-mark 255") end end if #DIRECT_SERVERS == 0 then log(" * 注意:SmartDNS 没有可用的直连 DNS 服务器,DNS 将无法正常工作!!!") os.exit(1) end local AUTO_GROUP = "auto" local auto_config_lines = {} -- 直连规则显式覆盖远程兜底的缓存、IPv6、测速和双栈优选设置。 local DIRECT_RULE_OPTIONS = " -enable-cache -address -6 -speed-check-mode ping,tcp:80,tcp:443 -response-mode first-ping -d yes" --设置默认 DNS 分组(兜底组),全局代理模式仍使用远程组。 local DEFAULT_DNS_GROUP = (USE_DEFAULT_DNS == "direct" and "direct") or (USE_DEFAULT_DNS == "remote" and "proxy") 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 = "proxy" end local AUTO_DIRECT_PORT = 0 if DEFAULT_DNS_GROUP == AUTO_GROUP then AUTO_DIRECT_PORT = api.get_new_port(15350) -- 避开同一进程尚未启动的主监听和直连监听端口。 for _ = 1, 2 do if not AUTO_DIRECT_PORT or (AUTO_DIRECT_PORT ~= tonumber(LISTEN_PORT) and AUTO_DIRECT_PORT ~= LOCAL_PORT) then break end AUTO_DIRECT_PORT = api.get_new_port(AUTO_DIRECT_PORT + 1) end end 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") config_lines = { tonumber(LISTEN_PORT) ~= 0 and ("bind " .. listen_address) or "", tonumber(LISTEN_PORT) ~= 0 and ("bind-tcp " .. listen_address) or "", LOCAL_PORT > 0 and ("bind [::]:" .. LOCAL_PORT .. "@lo -group direct -no-rules") or "", LOCAL_PORT > 0 and ("bind-tcp [::]:" .. LOCAL_PORT .. "@lo -group direct -no-rules") or "", AUTO_DIRECT_PORT > 0 and ("bind [::]:" .. AUTO_DIRECT_PORT .. "@lo -group auto-direct") or "", "log-level " .. LOG_LEVEL, "log-num " .. (LOG and "1" or "0"), LOG and string.format("log-file %q", FLAG_PATH .. "/smartdns.log") or "", LOG and ("log-size " .. LOG_SIZE .. "M") or "", string.format("cache-file %q", SMARTDNS_DATA_PATH .. "/smartdns.cache"), string.format("data-dir %q", data_dir), "server 223.5.5.5 -bootstrap-dns -set-mark 255", -- Set the suffix before loading hosts/leases so initial records are expanded too. (local_domain and local_domain ~= "") and "local-domain " .. local_domain or "", DNS_REDIRECT and "hosts-file /tmp/hosts/*" or "", is_file_nonzero("/etc/hosts") and "hosts-file /etc/hosts" or "", DNS_MODE == "socks" and string.format("proxy-server socks5://%s -name %s", REMOTE_PROXY_SERVER, proxy_server_name) or "" } if ENABLE_UI then merge_array(config_lines, { "plugin " .. UI_PLUGIN, "smartdns-ui.www-root /usr/share/smartdns/wwwroot", "smartdns-ui.ip http://[::]:" .. UI_PORT, "smartdns-ui.max-query-log-age " .. (UI_LOG_MAX_AGE * 86400) }) end if DNS_REDIRECT then local hosts = {} api.uci:foreach("dhcp", "host", function(s) if datatypes.hostname(s.name or "") and datatypes.ip4addr(s.ip or "") then hosts[#hosts + 1] = s.ip .. " " .. s.name end end) local hosts_file = FLAG_PATH .. "/dhcp-static-hosts" if fs.writefile(hosts_file, table.concat(hosts, "\n") .. "\n") and #hosts > 0 then table.insert(config_lines, string.format("hosts-file %q", hosts_file)) end -- SmartDNS monitors the lease file and updates local records without a restart. local lease_file = api.uci_get("dhcp", "@dnsmasq[0]", "leasefile") if not lease_file or lease_file == "" then lease_file = "/tmp/dhcp.leases" end table.insert(config_lines, string.format("dnsmasq-lease-file %q", lease_file)) local odhcpd_lease = api.uci_get("dhcp", "odhcpd", "leasefile") if odhcpd_lease and odhcpd_lease ~= "" then table.insert(config_lines, string.format("odhcpd-lease-file %q", odhcpd_lease)) end end for _, server_param in ipairs(DIRECT_SERVERS) do table.insert(config_lines, server_param .. " -group direct -exclude-default-group") end if DEFAULT_DNS_GROUP == AUTO_GROUP then -- 自动组先通过独立入口去除 CNAME,再按原逻辑筛选 IP。 table.insert(auto_config_lines, "server 127.0.0.1:" .. AUTO_DIRECT_PORT .. " -group " .. AUTO_GROUP .. " -exclude-default-group -whitelist-ip") table.insert(auto_config_lines, "group-begin auto-direct -inherit none") merge_array(auto_config_lines, DIRECT_SERVERS) merge_array(auto_config_lines, {"force-no-CNAME yes", "group-end"}) end -- 由下方 DNS_MODE 分支调用,服务器参数来自 PassWall 的 REMOTE_DNS 或 TUN_DNS。 local function add_remote_server(server_param) table.insert(config_lines, server_param .. " -group proxy -exclude-default-group") if DEFAULT_DNS_GROUP == AUTO_GROUP then -- 远程副本作为自动组的后备服务器,保留代理、ECS 和传输协议参数。 local auto_param = server_param .. " -group " .. AUTO_GROUP .. " -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_param = make_server_param(w) if not api.is_local_ip(w) then server_param = server_param .. " -proxy " .. proxy_server_name end if SUBNET and SUBNET ~= "" and SUBNET ~= "0" then server_param = server_param .. " -subnet " .. SUBNET end add_remote_server(server_param) end else add_remote_server("server " .. TUN_DNS:gsub("#", ":")) log(" - " .. DNS_MODE:gsub("^%l",string.upper) .. " " .. TUN_DNS .. " -> 分组(proxy)") end 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 merge_array(config_lines, auto_config_lines) end table.insert(config_lines, force_qtype_soa) if DEFAULT_DNS_GROUP then local domain_rules_str = "domain-rules /./ -nameserver " .. DEFAULT_DNS_GROUP if DEFAULT_DNS_GROUP == "proxy" then -- 代理仍禁止写入缓存,去掉会被直连继承且无法反向覆盖的过期缓存限制。 domain_rules_str = domain_rules_str .. " -no-cache -speed-check-mode none -d no" domain_rules_str = domain_rules_str .. " -address " .. ((only_global and IS_SHUNT_NODE) and "-6" or (NO_PROXY_IPV6 == "1" and "#6" or "-6")) end table.insert(config_lines, domain_rules_str) end local setflag = (NFTFLAG == "1") and "inet#passwall#" or "" local set_type = (NFTFLAG == "1") and "-nftset" or "-ipset" --预设排序标签(越往后优先级越高) for i = 1, 10 do table.insert(config_lines, "#--" .. i) end --屏蔽列表 local file_block_host = CACHE_RULES_PATH .. "/block_host" if not fs.access(file_block_host .. "_ok") then api.remove(file_block_host .. "*") end local block_geosite_flag = fs.access(file_block_host) and fs.access(file_block_host .. "_ok") and fs.access(file_block_host .. "_geo") if USE_BLOCK_LIST == "1" and not fs.access(file_block_host) then local block_domain, lookup_block_domain = {}, {} local geosite_arg = "" local f = io.open(USER_RULES_PATH .. "/block_host") if f then for line in f:lines() do if not line:find("#") and line:find("geosite:") then line = string.match(line, ":([^:]+)$") geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line else line = api.get_std_domain(line) if line ~= "" and not line:find("#") then insert_unique(block_domain, line, lookup_block_domain) end end end f:close() end if #block_domain > 0 then local f_out = io.open(file_block_host, "w") for i = 1, #block_domain do f_out:write(block_domain[i] .. "\n") end f_out:close() end local success = true if USE_GEOVIEW == "1" and geosite_arg ~= "" then local code, out = get_geosite(geosite_arg, file_block_host) if code == 0 then log(" - 解析[屏蔽列表] Geosite 到屏蔽域名表(blocklist)完成") sys.call("touch " .. file_block_host .. "_geo") else log(" - 解析[屏蔽列表] Geosite 到屏蔽域名表(blocklist)失败![" .. out .. "]") success = false end end if success and fs.access(file_block_host) then sys.call("touch " .. file_block_host .. "_ok") end end if USE_BLOCK_LIST == "1" and is_file_nonzero(file_block_host) then if block_geosite_flag then log(" - [屏蔽列表] Geosite 使用缓存结果,跳过重复解析") end local domain_set_name = "psw-block" tmp_lines = { string.format("domain-set -name %s -file %s", domain_set_name, file_block_host), string.format("domain-rules /domain-set:%s/ -a #", domain_set_name) } insert_array_after(config_lines, tmp_lines, "#--8") end --始终用国内DNS解析节点域名 local file_vpslist = TMP_PATH .. "/vpslist" if not is_file_nonzero(file_vpslist) then local f_out = io.open(file_vpslist, "w") local written_domains = {} local function process_address(address) address = (address or ""):lower() if api.vps_domain_exclude(address) then return end if datatypes.hostname(address) and not written_domains[address] then f_out:write(address .. "\n") written_domains[address] = true end end api.uci_foreach_c("nodes", function(t) process_address(t.address) process_address(t.download_address) end) f_out:close() end if is_file_nonzero(file_vpslist) then local domain_set_name = "psw-vpslist" tmp_lines = { string.format("domain-set -name %s -file %s", domain_set_name, file_vpslist) } local sets = { "#4:" .. setflag .. "psw_vps", "#6:" .. setflag .. "psw_vps6" } local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver direct', domain_set_name) .. DIRECT_RULE_OPTIONS -- 节点域名最多缓存 30 秒,到期后重新查询,避免继续返回过期的节点 IP。 domain_rules_str = domain_rules_str .. " -rr-ttl-max 30 -no-serve-expired" domain_rules_str = domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",") table.insert(tmp_lines, domain_rules_str) insert_array_after(config_lines, tmp_lines, "#--9") log(" - 节点列表中的域名(vpslist)使用分组:direct") end --直连(白名单)列表 local file_direct_host = CACHE_RULES_PATH .. "/direct_host" if not fs.access(file_direct_host .. "_ok") then api.remove(file_direct_host .. "*") end local direct_geosite_flag = fs.access(file_direct_host) and fs.access(file_direct_host .. "_ok") and fs.access(file_direct_host .. "_geo") if USE_DIRECT_LIST == "1" and not fs.access(file_direct_host) then local direct_domain, lookup_direct_domain = {}, {} local geosite_arg = "" local f = io.open(USER_RULES_PATH .. "/direct_host") if f then for line in f:lines() do if not line:find("#") and line:find("geosite:") then line = string.match(line, ":([^:]+)$") geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line else line = api.get_std_domain(line) if line ~= "" and not line:find("#") and not line:find(":") then insert_unique(direct_domain, line, lookup_direct_domain) end end end f:close() end if #direct_domain > 0 then local f_out = io.open(file_direct_host, "w") for i = 1, #direct_domain do f_out:write(direct_domain[i] .. "\n") end f_out:close() end local success = true if USE_GEOVIEW == "1" and geosite_arg ~= "" then local code, out = get_geosite(geosite_arg, file_direct_host) if code == 0 then log(" - 解析[直连列表] Geosite 到域名白名单(whitelist)完成") sys.call("touch " .. file_direct_host .. "_geo") else log(" - 解析[直连列表] Geosite 到域名白名单(whitelist)失败![" .. out .. "]") success = false end end if success and fs.access(file_direct_host) then sys.call("touch " .. file_direct_host .. "_ok") end end if USE_DIRECT_LIST == "1" and is_file_nonzero(file_direct_host) then if direct_geosite_flag then log(" - [直连列表] Geosite 使用缓存结果,跳过重复解析") end local domain_set_name = "psw-directlist" tmp_lines = { string.format("domain-set -name %s -file %s", domain_set_name, file_direct_host) } local sets = { "#4:" .. setflag .. "psw_white", "#6:" .. setflag .. "psw_white6" } local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver direct', domain_set_name) .. DIRECT_RULE_OPTIONS domain_rules_str = domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",") table.insert(tmp_lines, domain_rules_str) insert_array_after(config_lines, tmp_lines, "#--7") log(" - 域名白名单(whitelist)使用分组:direct") end --代理(黑名单)列表 local file_proxy_host = CACHE_RULES_PATH .. "/proxy_host" if not fs.access(file_proxy_host .. "_ok") then api.remove(file_proxy_host .. "*") end local proxy_geosite_flag = fs.access(file_proxy_host) and fs.access(file_proxy_host .. "_ok") and fs.access(file_proxy_host .. "_geo") if USE_PROXY_LIST == "1" and not fs.access(file_proxy_host) then local proxy_domain, lookup_proxy_domain = {}, {} local geosite_arg = "" local f = io.open(USER_RULES_PATH .. "/proxy_host") if f then for line in f:lines() do if not line:find("#") and line:find("geosite:") then line = string.match(line, ":([^:]+)$") geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line else line = api.get_std_domain(line) if line ~= "" and not line:find("#") and not line:find(":") then insert_unique(proxy_domain, line, lookup_proxy_domain) end end end f:close() end if #proxy_domain > 0 then local f_out = io.open(file_proxy_host, "w") for i = 1, #proxy_domain do f_out:write(proxy_domain[i] .. "\n") end f_out:close() end local success = true if USE_GEOVIEW == "1" and geosite_arg ~= "" then local code, out = get_geosite(geosite_arg, file_proxy_host) if code == 0 then log(" - 解析[代理列表] Geosite 到代理域名表(blacklist)完成") sys.call("touch " .. file_proxy_host .. "_geo") else log(" - 解析[代理列表] Geosite 到代理域名表(blacklist)失败![" .. out .. "]") success = false end end if success and fs.access(file_proxy_host) then sys.call("touch " .. file_proxy_host .. "_ok") end end if USE_PROXY_LIST == "1" and is_file_nonzero(file_proxy_host) then if proxy_geosite_flag then log(" - [代理列表] Geosite 使用缓存结果,跳过重复解析") end local domain_set_name = "psw-proxylist" tmp_lines = { string.format("domain-set -name %s -file %s", domain_set_name, file_proxy_host) } local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver proxy', domain_set_name) domain_rules_str = domain_rules_str .. " -no-cache -speed-check-mode none -d no" local sets = { "#4:" .. setflag .. "psw_black" } if NO_PROXY_IPV6 == "1" then domain_rules_str = domain_rules_str .. " -address #6 " .. set_type .. " " .. table.concat(sets, ",") else table.insert(sets, "#6:" .. setflag .. "psw_black6") domain_rules_str = domain_rules_str .. " -address -6 " .. set_type .. " " .. table.concat(sets, ",") end table.insert(tmp_lines, domain_rules_str) insert_array_after(config_lines, tmp_lines, "#--6") log(" - 代理域名表(blacklist)使用分组:proxy") end --GFW列表 if USE_GFW_LIST == "1" and is_file_nonzero(RULES_PATH .. "/gfwlist") then local domain_set_name = "psw-gfwlist" tmp_lines = { string.format("domain-set -name %s -file %s", domain_set_name, RULES_PATH .. "/gfwlist") } local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver proxy', domain_set_name) domain_rules_str = domain_rules_str .. " -no-cache -speed-check-mode none -d no" local sets = { "#4:" .. setflag .. "psw_gfw" } if NO_PROXY_IPV6 == "1" then domain_rules_str = domain_rules_str .. " -address #6 " .. set_type .. " " .. table.concat(sets, ",") else table.insert(sets, "#6:" .. setflag .. "psw_gfw6") domain_rules_str = domain_rules_str .. " -address -6 " .. set_type .. " " .. table.concat(sets, ",") end table.insert(tmp_lines, domain_rules_str) insert_array_after(config_lines, tmp_lines, "#--5") log(" - 防火墙域名表(gfwlist)使用分组:proxy") end --中国列表 if CHN_LIST ~= "0" and is_file_nonzero(RULES_PATH .. "/chnlist") then local domain_set_name = "psw-chnlist" tmp_lines = { string.format("domain-set -name %s -file %s", domain_set_name, RULES_PATH .. "/chnlist") } if CHN_LIST == "direct" then local sets = { "#4:" .. setflag .. "psw_chn", "#6:" .. setflag .. "psw_chn6" } local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver direct', domain_set_name) .. DIRECT_RULE_OPTIONS domain_rules_str = domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",") table.insert(tmp_lines, domain_rules_str) insert_array_after(config_lines, tmp_lines, "#--4") log(" - 中国域名表(chnlist)使用分组:direct") end --回中国模式 if CHN_LIST == "proxy" then local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver proxy', domain_set_name) domain_rules_str = domain_rules_str .. " -no-cache -speed-check-mode none -d no" local sets = { "#4:" .. setflag .. "psw_chn" } if NO_PROXY_IPV6 == "1" then domain_rules_str = domain_rules_str .. " -address #6 " .. set_type .. " " .. table.concat(sets, ",") else table.insert(sets, "#6:" .. setflag .. "psw_chn6") domain_rules_str = domain_rules_str .. " -address -6 " .. set_type .. " " .. table.concat(sets, ",") end table.insert(tmp_lines, domain_rules_str) insert_array_after(config_lines, tmp_lines, "#--4") log(" - 中国域名表(chnlist)使用分组:proxy") end end --分流规则 if IS_SHUNT_NODE and not only_global then local direct_domain, lookup_direct_domain = {}, {} local proxy_domain, lookup_proxy_domain = {}, {} local black_domain, lookup_black_domain = {}, {} local CACHE_FLAG_PATH = CACHE_RULES_PATH .. "/" .. FLAG local shunt_direct_host = CACHE_FLAG_PATH .. "/shunt_direct_host" local shunt_proxy_host = CACHE_FLAG_PATH .. "/shunt_proxy_host" local shunt_black_host = CACHE_FLAG_PATH .. "/shunt_black_host" local geosite_direct_arg, geosite_proxy_arg, geosite_black_arg = "", "", "" local SHUNT_LIST = "" local t = api.uci_get_c(NODE) local default_node_id = t["default_node"] or "_direct" api.uci_foreach_c("shunt_rules", function(s) local _node_id = t[s[".name"]] if _node_id and t["shunt_group"] == s.group then if _node_id == "_default" then _node_id = default_node_id end local domain_list = s.domain_list or "" for line in string.gmatch(domain_list, "[^\r\n]+") do if line ~= "" and not line:find("#") and not line:find("regexp:") and not line:find("ext:") and not line:find("rule-set:") and not line:find("rs:") then if line:find("geosite:") then line = string.match(line, ":([^:]+)$") if _node_id == "_direct" then geosite_direct_arg = geosite_direct_arg .. (geosite_direct_arg ~= "" and "," or "") .. line elseif _node_id == "_blackhole" then geosite_black_arg = geosite_black_arg .. (geosite_black_arg ~= "" and "," or "") .. line else geosite_proxy_arg = geosite_proxy_arg .. (geosite_proxy_arg ~= "" and "," or "") .. line end else if line:find("domain:") or line:find("full:") then line = string.match(line, ":([^:]+)$") end line = api.get_std_domain(line) if line ~= "" and not line:find("#") then if _node_id == "_direct" then insert_unique(direct_domain, line, lookup_direct_domain) elseif _node_id == "_blackhole" then insert_unique(black_domain, line, lookup_black_domain) else insert_unique(proxy_domain, line, lookup_proxy_domain) end end end end end SHUNT_LIST = SHUNT_LIST .. domain_list .. (_node_id:sub(1, 1) == "_" and "not-node" or "node") if _node_id ~= "_direct" and _node_id ~= "_blackhole" then log(string.format(" - Sing-Box/Xray分流规则(%s)使用分组:proxy", s.remarks)) end end end) local MD5_FILE = CACHE_FLAG_PATH .. "/md5.txt" local cache_md5 = "" local USE_CACHE = true if fs.access(MD5_FILE) then cache_md5 = fs.readfile(MD5_FILE) end local new_md5 = api.md5_string(SHUNT_LIST) if cache_md5 == "" or new_md5 == "" or cache_md5 ~= new_md5 then api.remove(CACHE_FLAG_PATH) sys.call("mkdir -p %s" % CACHE_FLAG_PATH) fs.writefile(MD5_FILE, new_md5) USE_CACHE = false end if not is_file_nonzero(shunt_direct_host) then if #direct_domain > 0 then local f_out = io.open(shunt_direct_host, "w") for i = 1, #direct_domain do f_out:write(direct_domain[i] .. "\n") end f_out:close() end end if not is_file_nonzero(shunt_proxy_host) then if #proxy_domain > 0 then local f_out = io.open(shunt_proxy_host, "w") for i = 1, #proxy_domain do f_out:write(proxy_domain[i] .. "\n") end f_out:close() end end if not is_file_nonzero(shunt_black_host) then if #black_domain > 0 then local f_out = io.open(shunt_black_host, "w") for i = 1, #black_domain do f_out:write(black_domain[i] .. "\n") end f_out:close() end end if not USE_CACHE and USE_GEOVIEW == "1" then local ok = true local function resolve(arg, dest) if arg == "" then return end local code, out = get_geosite(arg, dest) if code ~= 0 then ok = false log(" - 解析[分流节点] Geosite 失败![" .. out .. "]") end end resolve(geosite_direct_arg, shunt_direct_host) resolve(geosite_proxy_arg, shunt_proxy_host) resolve(geosite_black_arg, shunt_black_host) if ok then log(" - 解析[分流节点] Geosite 完成") else api.remove(MD5_FILE) end end if USE_CACHE and USE_GEOVIEW == "1" and (geosite_direct_arg ~= "" or geosite_proxy_arg ~= "" or geosite_black_arg ~= "") then log(" - [分流节点] Geosite 使用缓存结果,跳过重复解析") end if is_file_nonzero(shunt_direct_host) then local domain_set_name = "psw-shunt-direct" tmp_lines = { string.format("domain-set -name %s -file %s", domain_set_name, shunt_direct_host) } local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver proxy', domain_set_name) domain_rules_str = domain_rules_str .. " -no-cache -speed-check-mode none -d no" local sets = { "#4:" .. setflag .. "psw_shunt", "#6:" .. setflag .. "psw_shunt6" } domain_rules_str = domain_rules_str .. " -address -6 " .. set_type .. " " .. table.concat(sets, ",") table.insert(tmp_lines, domain_rules_str) insert_array_after(config_lines, tmp_lines, "#--1") end if is_file_nonzero(shunt_proxy_host) then local domain_set_name = "psw-shunt-proxy" tmp_lines = { string.format("domain-set -name %s -file %s", domain_set_name, shunt_proxy_host) } local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver proxy', domain_set_name) domain_rules_str = domain_rules_str .. " -no-cache -speed-check-mode none -d no" local sets = { "#4:" .. setflag .. "psw_shunt", "#6:" .. setflag .. "psw_shunt6" } domain_rules_str = domain_rules_str .. " -address -6 " .. set_type .. " " .. table.concat(sets, ",") table.insert(tmp_lines, domain_rules_str) insert_array_after(config_lines, tmp_lines, "#--2") end if is_file_nonzero(shunt_black_host) then local domain_set_name = "psw-shunt-black" tmp_lines = { string.format("domain-set -name %s -file %s", domain_set_name, shunt_black_host) } local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver proxy', domain_set_name) domain_rules_str = domain_rules_str .. " -no-cache -speed-check-mode none -d no" local sets = { "#4:" .. setflag .. "psw_shunt", "#6:" .. setflag .. "psw_shunt6" } domain_rules_str = domain_rules_str .. " -address -6 " .. set_type .. " " .. table.concat(sets, ",") table.insert(tmp_lines, domain_rules_str) insert_array_after(config_lines, tmp_lines, "#--3") end end if #config_lines > 0 then local f_out = io.open(TMP_CONF_FILE, "w") for i = 1, #config_lines do line = config_lines[i] if line ~= "" and not line:find("^#--") then f_out:write(line .. "\n") end end f_out:close() end if DEFAULT_DNS_GROUP then log(string.format(" - 默认 DNS 分组:%s", DEFAULT_DNS_GROUP == AUTO_GROUP and "自动" or DEFAULT_DNS_GROUP)) end if DNS_REDIRECT then log(" - DNS 重定向直接使用 SmartDNS,DHCP 租约由 SmartDNS 自动加载。") else log(" - SmartDNS已作为Dnsmasq上游,如果你自行配置了错误的DNS流程,将会导致域名(直连/代理)分流失效!!!") end