Files
op-packages/luci-app-passwall/root/usr/share/passwall/helper_chinadns_add.lua
T

636 lines
19 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local api = require "luci.passwall.api"
local var = api.get_args(arg)
local FLAG = var["-FLAG"]
local LISTEN_PORT = var["-LISTEN_PORT"]
local DNS_LOCAL = var["-DNS_LOCAL"]
local DNS_TRUST = var["-DNS_TRUST"]
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 GFWLIST = var["-GFWLIST"]
local CHNLIST = var["-CHNLIST"]
local NO_IPV6_TRUST = var["-NO_IPV6_TRUST"]
local DEFAULT_MODE = var["-DEFAULT_MODE"]
local DEFAULT_TAG = var["-DEFAULT_TAG"]
local NO_LOGIC_LOG = var["-NO_LOGIC_LOG"]
local NODE = var["-NODE"]
local NFTFLAG = var["-NFTFLAG"]
local FILTER_HTTPS = var["-FILTER_HTTPS"]
local LOG_FILE = var["-LOG_FILE"]
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 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 then
if sys.exec('[ -s "%s" ] && echo -n 1' % path) == "1" then
return true
end
end
return nil
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)
local setflag = (NFTFLAG == "1") and "inet@passwall@" or ""
local only_global = (DEFAULT_MODE == "proxy" and CHNLIST == "0" and GFWLIST == "0") and 1
config_lines = {
LOG_FILE ~= "/dev/null" and "verbose" or "",
"bind-addr ::",
"bind-port " .. LISTEN_PORT,
"china-dns " .. DNS_LOCAL,
"trust-dns " .. DNS_TRUST,
tonumber(FILTER_HTTPS) == 1 and "filter-qtype 65" or ""
}
for i = 1, 6 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
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)完成")
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
tmp_lines = {
"group null",
"group-dnl " .. file_block_host
}
insert_array_after(config_lines, tmp_lines, "#--5")
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)
local dns, _ = api.get_domain_port_from_url(t.domain_resolver_dns or t.domain_resolver_dns_https or "")
if dns and dns ~= "" then
process_address(dns)
end
end)
api.uci_foreach_c("subscribe_list", function(t) --订阅链接
local url, _ = api.get_domain_port_from_url(t.url or "")
if url and url ~= "" then
process_address(url)
end
end)
f_out:close()
end
if is_file_nonzero(file_vpslist) then
local sets = {
setflag .. "psw_vps",
setflag .. "psw_vps6"
}
tmp_lines = {
"group vpslist",
"group-dnl " .. file_vpslist,
"group-upstream " .. DNS_LOCAL,
"group-ipset " .. table.concat(sets, ",")
}
insert_array_after(config_lines, tmp_lines, "#--6")
log(string.format(" - 节点列表中的域名(vpslist):%s", DNS_LOCAL or "默认"))
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
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)完成")
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
local sets = {
setflag .. "psw_white",
setflag .. "psw_white6"
}
tmp_lines = {
"group directlist",
"group-dnl " .. file_direct_host,
"group-upstream " .. DNS_LOCAL,
"group-ipset " .. table.concat(sets, ",")
}
insert_array_after(config_lines, tmp_lines, "#--4")
log(string.format(" - 域名白名单(whitelist):%s", DNS_LOCAL or "默认"))
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
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)完成")
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
local sets = {
setflag .. "psw_black",
setflag .. "psw_black6"
}
if FLAG ~= "default" then
sets = {
setflag .. "psw_" .. FLAG .. "_black",
setflag .. "psw_" .. FLAG .. "_black6"
}
end
tmp_lines = {
"group proxylist",
"group-dnl " .. file_proxy_host,
"group-upstream " .. DNS_TRUST,
"group-ipset " .. table.concat(sets, ",")
}
if NO_IPV6_TRUST == "1" then table.insert(tmp_lines, "no-ipv6 tag:proxylist") end
insert_array_after(config_lines, tmp_lines, "#--3")
log(string.format(" - 代理域名表(blacklist):%s", DNS_TRUST or "默认"))
end
--内置组(chn/gfw)优先级在自定义组后
--GFW列表
if GFWLIST == "1" and is_file_nonzero(RULES_PATH .. "/gfwlist") then
local sets = {
setflag .. "psw_gfw",
setflag .. "psw_gfw6"
}
if FLAG ~= "default" then
sets = {
setflag .. "psw_" .. FLAG .. "_gfw",
setflag .. "psw_" .. FLAG .. "_gfw6"
}
end
tmp_lines = {
"gfwlist-file " .. RULES_PATH .. "/gfwlist",
"add-taggfw-ip " .. table.concat(sets, ",")
}
if NO_IPV6_TRUST == "1" then table.insert(tmp_lines, "no-ipv6 tag:gfw") end
merge_array(config_lines, tmp_lines)
log(string.format(" - 防火墙域名表(gfwlist):%s", DNS_TRUST or "默认"))
end
--中国列表
if CHNLIST ~= "0" and is_file_nonzero(RULES_PATH .. "/chnlist") then
if CHNLIST == "direct" then
local sets = {
setflag .. "psw_chn",
setflag .. "psw_chn6"
}
local suffix = (NFTFLAG == "1") and "_static" or ""
tmp_lines = {
"chnlist-file " .. RULES_PATH .. "/chnlist",
"ipset-name4 " .. setflag .. "psw_chn" .. suffix,
"ipset-name6 " .. setflag .. "psw_chn6" .. suffix,
"add-tagchn-ip" .. ((NFTFLAG == "1") and (" " .. table.concat(sets, ",")) or "")
}
merge_array(config_lines, tmp_lines)
log(string.format(" - 中国域名表(chnlist):%s", DNS_LOCAL or "默认"))
end
--回中国模式
if CHNLIST == "proxy" then
local sets = {
setflag .. "psw_chn",
setflag .. "psw_chn6"
}
tmp_lines = {
"group chn_proxy",
"group-dnl " .. RULES_PATH .. "/chnlist",
"group-upstream " .. DNS_TRUST,
"group-ipset " .. table.concat(sets, ",")
}
if NO_IPV6_TRUST == "1" then table.insert(tmp_lines, "no-ipv6 tag:chn_proxy") end
insert_array_after(config_lines, tmp_lines, "#--1")
log(string.format(" - 中国域名表(chnlist):%s", DNS_TRUST or "默认"))
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_direct_host_tmp = shunt_direct_host .. "_tmp"
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")
log(string.format(" - Sing-Box/Xray分流规则(%s):%s", s.remarks, DNS_TRUST or "默认"))
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_tmp) then
if #direct_domain > 0 then
local f_out = io.open(shunt_direct_host_tmp, "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_tmp)
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
-- 与 chnlist 比对
if is_file_nonzero(shunt_direct_host_tmp) and CHNLIST ~= "0" and is_file_nonzero(RULES_PATH .. "/chnlist") then
local chn_domain_set = {}
local f_chn = io.open(RULES_PATH .. "/chnlist", "r")
if f_chn then
for line in f_chn:lines() do
if line ~= "" then
chn_domain_set[line] = true
end
end
f_chn:close()
end
local f_in = io.open(shunt_direct_host_tmp, "r")
local f_out = io.open(shunt_direct_host, "w")
if f_in and f_out then
for line in f_in:lines() do
line = string.lower(line)
-- 排除 cn、.cn 等缺少有效域名标签的条目
local valid_domain = line ~= "" and line:find("%.") and line:sub(1,1) ~= "."
if valid_domain and not chn_domain_set[line] then
f_out:write(line .. "\n")
end
end
end
if f_in then f_in:close() end
if f_out then f_out:close() end
api.remove(shunt_direct_host_tmp)
elseif is_file_nonzero(shunt_direct_host_tmp) then
os.rename(shunt_direct_host_tmp, shunt_direct_host)
end
local shunt_file
if is_file_nonzero(shunt_black_host) then
shunt_file = shunt_black_host
end
if is_file_nonzero(shunt_direct_host) then
shunt_file = (shunt_file and shunt_file .. "," or "") .. shunt_direct_host
end
if is_file_nonzero(shunt_proxy_host) then
shunt_file = (shunt_file and shunt_file .. "," or "") .. shunt_proxy_host
end
if shunt_file then
local sets = {
setflag .. "psw_shunt",
setflag .. "psw_shunt6"
}
if FLAG ~= "default" then
sets = {
setflag .. "psw_" .. FLAG .. "_shunt",
setflag .. "psw_" .. FLAG .. "_shunt6"
}
end
tmp_lines = {
"group shuntlist",
"group-dnl " .. shunt_file,
"group-upstream " .. DNS_TRUST,
"group-ipset " .. table.concat(sets, ",")
}
insert_array_after(config_lines, tmp_lines, "#--2")
end
end
--只使用gfwlist模式,GFW列表以外的域名及默认使用本地DNS
if GFWLIST == "1" and CHNLIST == "0" then DEFAULT_TAG = "chn" end
--回中国模式,中国列表以外的域名及默认使用本地DNS
if CHNLIST == "proxy" then DEFAULT_TAG = "chn" end
--全局模式,默认使用远程DNS
if only_global then
DEFAULT_TAG = "gfw"
if NO_IPV6_TRUST == "1" and not IS_SHUNT_NODE then
table.insert(config_lines, "no-ipv6")
end
end
--是否接受直连 DNS 空响应
if DEFAULT_TAG == "none_noip" then table.insert(config_lines, "noip-as-chnip") end
if not DEFAULT_TAG or DEFAULT_TAG == "smart" or DEFAULT_TAG == "none_noip" then DEFAULT_TAG = "none" end
table.insert(config_lines, "default-tag " .. DEFAULT_TAG)
if DEFAULT_TAG == "none" then
table.insert(config_lines, "verdict-cache 5000")
end
table.insert(config_lines, "hosts")
if DEFAULT_TAG == "chn" then
log(string.format(" - 默认 DNS :%s", DNS_LOCAL))
elseif DEFAULT_TAG == "gfw" then
log(string.format(" - 默认 DNS :%s", DNS_TRUST))
else
log(string.format(" - 默认 DNS :%s", "智能匹配"))
end
--输出配置文件
if #config_lines > 0 then
for i = 1, #config_lines do
line = config_lines[i]
if line ~= "" and not line:find("^#--") then
print(line)
end
end
end
log(" - ChinaDNS-NG已作为Dnsmasq上游,如果你自行配置了错误的DNS流程,将会导致域名(直连/代理)分流失效!!!")