mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-10-29 10:25:13 +08:00
update 2026-05-27 19:55:46
This commit is contained in:
1 parent
762a95b5ae
commit
84f707b52b
758 files changed
-362299
No files matched your search
@@ -1,4 +0,0 @@
|
||||
|
||||
config global 'global'
|
||||
option enable '0'
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /usr/share/passwall/utils.sh
|
||||
|
||||
[ "$ACTION" = "ifup" ] || [ "$ACTION" = "ifupdate" ] || exit 0
|
||||
|
||||
([ "$(get_cache_var "ENABLED_DEFAULT_ACL")" = "1" ] || [ "$(get_cache_var "ENABLED_ACLS")" = "1" ]) && [ -f ${LOCK_PATH}/${CONFIG}_ready.lock ] && {
|
||||
|
||||
default_device=$(ip route show default 2>/dev/null | awk -F 'dev ' '{print $2}' | awk '{print $1}' | head -n1)
|
||||
default6_device=$(ip -6 route show default 2>/dev/null | awk -F 'dev ' '{print $2}' | awk '{print $1}' | head -n1)
|
||||
( [ "$default_device" = "$DEVICE" ] || [ "$default6_device" = "$DEVICE" ] ) && {
|
||||
|
||||
if [ "$ACTION" = "ifupdate" ]; then
|
||||
USE_TABLES=$(get_cache_var "USE_TABLES")
|
||||
[ -n "$USE_TABLES" ] && { ${APP_PATH}/${USE_TABLES}.sh update_wan_sets >/dev/null 2>&1 & }
|
||||
exit 0
|
||||
fi
|
||||
|
||||
[ ! -d ${LOCK_PATH} ] && mkdir -p ${LOCK_PATH}
|
||||
LOCK_FILE="${LOCK_PATH}/${CONFIG}_ifup.lock"
|
||||
if [ -s ${LOCK_FILE} ]; then
|
||||
SPID=$(cat ${LOCK_FILE})
|
||||
if [ -e /proc/${SPID}/status ]; then
|
||||
exit 1
|
||||
fi
|
||||
cat /dev/null > ${LOCK_FILE}
|
||||
fi
|
||||
echo $$ > ${LOCK_FILE}
|
||||
|
||||
/etc/init.d/${CONFIG} restart >/dev/null 2>&1 &
|
||||
logger -p notice -t network -s "${CONFIG}: restart when $INTERFACE ifup"
|
||||
|
||||
rm -rf ${LOCK_FILE}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
STOP=15
|
||||
|
||||
ROOT="${IPKG_INSTROOT:-}"
|
||||
. "$ROOT/usr/share/passwall/utils.sh"
|
||||
APP_FILE=${APP_PATH}/app.sh
|
||||
LOCK_FILE_DIR=/var/lock
|
||||
LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}.lock
|
||||
|
||||
set_lock() {
|
||||
[ ! -d "$LOCK_FILE_DIR" ] && mkdir -p $LOCK_FILE_DIR
|
||||
exec 999>"$LOCK_FILE"
|
||||
flock -xn 999
|
||||
}
|
||||
|
||||
unset_lock() {
|
||||
flock -u 999
|
||||
rm -rf "$LOCK_FILE"
|
||||
}
|
||||
|
||||
unlock() {
|
||||
failcount=1
|
||||
while [ "$failcount" -le 10 ]; do
|
||||
if [ -f "$LOCK_FILE" ]; then
|
||||
let "failcount++"
|
||||
sleep 1s
|
||||
[ "$failcount" -ge 10 ] && unset_lock
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
boot_func() {
|
||||
local delay=$(uci -q get ${CONFIG}.@global_delay[0].start_delay || echo 1)
|
||||
if [ "$delay" -gt 0 ]; then
|
||||
echolog "执行启动延时 $delay 秒后再启动!"
|
||||
sleep $delay
|
||||
fi
|
||||
restart boot
|
||||
touch ${LOCK_FILE_DIR}/${CONFIG}_ready.lock
|
||||
}
|
||||
|
||||
boot() {
|
||||
boot_func >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
start() {
|
||||
set_lock
|
||||
[ $? == 1 ] && echolog "脚本已经在运行,不重复运行,退出." && exit 0
|
||||
$APP_FILE start
|
||||
unset_lock
|
||||
}
|
||||
|
||||
stop() {
|
||||
unlock
|
||||
set_lock
|
||||
[ $? == 1 ] && echolog "停止脚本等待超时,不重复运行,退出." && exit 0
|
||||
$APP_FILE stop
|
||||
unset_lock
|
||||
}
|
||||
|
||||
restart() {
|
||||
set_lock
|
||||
[ $? == 1 ] && echolog "脚本已经在运行,不重复运行,退出." && exit 0
|
||||
rule_lock=${LOCK_FILE_DIR}/${CONFIG}_rule_update.lock
|
||||
sub_lock=${LOCK_FILE_DIR}/${CONFIG}_subscribe.lock
|
||||
for i in $(seq 1 300); do
|
||||
if [ ! -f "$rule_lock" ] && [ ! -f "$sub_lock" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
rm -f "$rule_lock" "$sub_lock"
|
||||
$APP_FILE stop
|
||||
$APP_FILE start $1
|
||||
unset_lock
|
||||
}
|
||||
|
||||
reload() {
|
||||
echo "Warning: This service does not support configuration reloading"
|
||||
echo "Performing full restart instead..."
|
||||
restart
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
|
||||
start() {
|
||||
lua /usr/lib/lua/luci/passwall/server_app.lua start
|
||||
}
|
||||
|
||||
stop() {
|
||||
lua /usr/lib/lua/luci/passwall/server_app.lua stop
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
reload() {
|
||||
echo "Warning: This service does not support configuration reloading"
|
||||
echo "Performing full restart instead..."
|
||||
restart
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -e "/etc/config/ucitrack" ]; then
|
||||
uci -q batch <<-EOF
|
||||
delete ucitrack.@passwall[-1]
|
||||
add ucitrack passwall
|
||||
set ucitrack.@passwall[-1].init=passwall
|
||||
delete ucitrack.@passwall_server[-1]
|
||||
add ucitrack passwall_server
|
||||
set ucitrack.@passwall_server[-1].init=passwall_server
|
||||
commit ucitrack
|
||||
EOF
|
||||
fi
|
||||
|
||||
uci -q batch <<-EOF
|
||||
delete firewall.passwall
|
||||
set firewall.passwall=include
|
||||
set firewall.passwall.type='script'
|
||||
set firewall.passwall.path='/var/etc/passwall.include'
|
||||
|
||||
delete firewall.passwall_server
|
||||
set firewall.passwall_server=include
|
||||
set firewall.passwall_server.type='script'
|
||||
set firewall.passwall_server.path='/var/etc/passwall_server.include'
|
||||
|
||||
set dhcp.@dnsmasq[0].localuse=1
|
||||
commit dhcp
|
||||
set uhttpd.main.max_requests=50
|
||||
commit uhttpd
|
||||
EOF
|
||||
|
||||
if [ -x "/sbin/fw3" ]; then
|
||||
uci -q set firewall.passwall.reload='1'
|
||||
uci -q set firewall.passwall_server.reload='1'
|
||||
else
|
||||
uci -q delete firewall.passwall.reload
|
||||
uci -q delete firewall.passwall.fw4_compatible
|
||||
uci -q delete firewall.passwall_server.reload
|
||||
uci -q delete firewall.passwall_server.fw4_compatible
|
||||
fi
|
||||
uci commit firewall
|
||||
|
||||
[ ! -s "/etc/config/passwall" ] && cp -f /usr/share/passwall/0_default_config /etc/config/passwall
|
||||
|
||||
chmod +x /usr/share/passwall/*.sh
|
||||
|
||||
[ "$(uci -q get passwall.@global_xray[0].sniffing)" == "1" ] && [ "$(uci -q get passwall.@global_xray[0].route_only)" != "1" ] && uci -q set passwall.@global_xray[0].sniffing_override_dest=1
|
||||
uci -q delete passwall.@global_xray[0].sniffing
|
||||
uci -q delete passwall.@global_xray[0].route_only
|
||||
uci -q commit passwall
|
||||
|
||||
sed -i "s#add_from#group#g" /etc/config/passwall 2>/dev/null
|
||||
|
||||
rm -f /tmp/luci-indexcache /tmp/luci-indexcache.*
|
||||
rm -rf /tmp/luci-modulecache/
|
||||
killall -HUP rpcd 2>/dev/null
|
||||
|
||||
exit 0
|
||||
@@ -1,255 +0,0 @@
|
||||
|
||||
config global
|
||||
option enabled '0'
|
||||
option socks_enabled '0'
|
||||
option tcp_node_socks_port '1070'
|
||||
option filter_proxy_ipv6 '1'
|
||||
option dns_shunt 'chinadns-ng'
|
||||
option dns_mode 'tcp'
|
||||
option remote_dns '1.1.1.1'
|
||||
list smartdns_remote_dns 'https://1.1.1.1/dns-query'
|
||||
option use_default_dns 'direct'
|
||||
option chinadns_ng_default_tag 'none'
|
||||
option dns_redirect '1'
|
||||
option use_direct_list '1'
|
||||
option use_proxy_list '1'
|
||||
option use_block_list '1'
|
||||
option use_gfw_list '1'
|
||||
option chn_list 'direct'
|
||||
option tcp_proxy_mode 'proxy'
|
||||
option udp_proxy_mode 'proxy'
|
||||
option localhost_proxy '1'
|
||||
option client_proxy '1'
|
||||
option acl_enable '0'
|
||||
option log_tcp '0'
|
||||
option log_udp '0'
|
||||
option loglevel 'error'
|
||||
option log_chinadns_ng '0'
|
||||
|
||||
config global_haproxy
|
||||
option balancing_enable '0'
|
||||
|
||||
config global_delay
|
||||
option start_daemon '1'
|
||||
option start_delay '60'
|
||||
|
||||
config global_forwarding
|
||||
option tcp_no_redir_ports 'disable'
|
||||
option udp_no_redir_ports 'disable'
|
||||
option tcp_proxy_drop_ports 'disable'
|
||||
option udp_proxy_drop_ports '443'
|
||||
option tcp_redir_ports '22,25,53,80,143,443,465,587,853,873,993,995,5222,8080,8443,9418'
|
||||
option udp_redir_ports '1:65535'
|
||||
option accept_icmp '0'
|
||||
option prefer_nft '1'
|
||||
option tcp_proxy_way 'redirect'
|
||||
option ipv6_tproxy '0'
|
||||
|
||||
config global_xray
|
||||
option sniffing_override_dest '0'
|
||||
|
||||
config global_singbox
|
||||
|
||||
config global_other
|
||||
option auto_detection_time 'tcping'
|
||||
option show_node_info '0'
|
||||
|
||||
config global_rules
|
||||
option auto_update '0'
|
||||
option chnlist_update '1'
|
||||
option chnroute_update '1'
|
||||
option chnroute6_update '1'
|
||||
option gfwlist_update '1'
|
||||
option geosite_update '0'
|
||||
option geoip_update '0'
|
||||
list gfwlist_url 'https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt'
|
||||
list chnroute_url 'https://ispip.clang.cn/all_cn.txt'
|
||||
list chnroute_url 'https://cdn.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt'
|
||||
list chnroute6_url 'https://ispip.clang.cn/all_cn_ipv6.txt'
|
||||
list chnroute6_url 'https://cdn.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china6.txt'
|
||||
list chnlist_url 'https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf'
|
||||
list chnlist_url 'https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf'
|
||||
option v2ray_location_asset '/usr/share/v2ray/'
|
||||
option geoip_url 'https://github.com/Loyalsoldier/geoip/releases/latest/download/geoip.dat'
|
||||
option geosite_url 'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat'
|
||||
|
||||
config global_app
|
||||
option geoview_file '/usr/bin/geoview'
|
||||
option sing_box_file '/usr/bin/sing-box'
|
||||
option xray_file '/usr/bin/xray'
|
||||
option hysteria_file '/usr/bin/hysteria'
|
||||
|
||||
config global_subscribe
|
||||
option filter_keyword_mode '1'
|
||||
list filter_discard_list '距离下次重置剩余'
|
||||
list filter_discard_list '套餐到期'
|
||||
list filter_discard_list '过期时间'
|
||||
list filter_discard_list '剩余流量'
|
||||
list filter_discard_list 'QQ群'
|
||||
list filter_discard_list '官网'
|
||||
|
||||
config nodes 'myshunt'
|
||||
option remarks '分流总节点'
|
||||
option type 'Xray'
|
||||
option protocol '_shunt'
|
||||
option DirectGame '_direct'
|
||||
option ProxyGame '_default'
|
||||
option AIGC '_default'
|
||||
option Streaming '_default'
|
||||
option Proxy '_default'
|
||||
option Direct '_direct'
|
||||
option default_node '_direct'
|
||||
option domainStrategy 'IPOnDemand'
|
||||
|
||||
config shunt_rules 'DirectGame'
|
||||
option remarks 'DirectGame'
|
||||
option network 'tcp,udp'
|
||||
option domain_list '# steam直连域名获取国内CDN走国内线路下载
|
||||
cm.steampowered.com
|
||||
steamserver.net
|
||||
|
||||
# steam国内CDN华为云
|
||||
steampipe.steamcontent.tnkjmec.com
|
||||
# steam国内CDN白山云
|
||||
st.dl.eccdnx.com
|
||||
st.dl.bscstorage.net
|
||||
st.dl.pinyuncloud.com
|
||||
# steam国内CDN新流云(原金山云)(支持ipv6)
|
||||
dl.steam.clngaa.com
|
||||
# steam国内CDN网宿
|
||||
cdn.mileweb.cs.steampowered.com.8686c.com
|
||||
cdn-ws.content.steamchina.com
|
||||
# steam国内CDN腾讯云 (蒸汽中国独占)
|
||||
cdn-qc.content.steamchina.com
|
||||
# steam国内CDN阿里云(支持ipv6)
|
||||
cdn-ali.content.steamchina.com
|
||||
xz.pphimalayanrt.com
|
||||
lv.queniujq.cn
|
||||
alibaba.cdn.steampipe.steamcontent.com
|
||||
|
||||
# 国内游戏geosite域名
|
||||
geosite:category-games@cn'
|
||||
option ip_list '# steam直连IP
|
||||
45.121.184.0/24
|
||||
103.10.124.0/23
|
||||
103.28.54.0/24
|
||||
146.66.152.0/24
|
||||
146.66.155.0/24
|
||||
153.254.86.0/24
|
||||
155.133.224.0/22
|
||||
155.133.230.0/24
|
||||
155.133.232.0/23
|
||||
155.133.234.0/24
|
||||
155.133.236.0/22
|
||||
155.133.240.0/23
|
||||
155.133.244.0/23
|
||||
155.133.246.0/24
|
||||
155.133.248.0/21
|
||||
162.254.192.0/21
|
||||
185.25.182.0/23
|
||||
190.217.32.0/22
|
||||
192.69.96.0/22
|
||||
205.196.6.0/24
|
||||
208.64.200.0/22
|
||||
208.78.164.0/22
|
||||
205.185.194.0/24'
|
||||
|
||||
config shunt_rules 'ProxyGame'
|
||||
option remarks 'ProxyGame'
|
||||
option network 'tcp,udp'
|
||||
option domain_list '# steam 商店/客服/聊天/网页布局/API/二维码/Google云同步 代理URL
|
||||
steamcommunity.com
|
||||
www.steamcommunity.com
|
||||
store.steampowered.com
|
||||
checkout.steampowered.com
|
||||
api.steampowered.com
|
||||
help.steampowered.com
|
||||
login.steampowered.com
|
||||
store.akamai.steamstatic.com
|
||||
steambroadcast.akamaized.net
|
||||
steamvideo-a.akamaihd.net
|
||||
steamusercontent-a.akamaihd.net
|
||||
steamstore-a.akamaihd.net
|
||||
steamcommunity-a.akamaihd.net
|
||||
steamcdn-a.akamaihd.net
|
||||
steamuserimages-a.akamaihd.net
|
||||
community.akamai.steamstatic.com
|
||||
avatars.akamai.steamstatic.com
|
||||
community.steamstatic.com
|
||||
cdn.akamai.steamstatic.com
|
||||
avatars.steamstatic.com
|
||||
shared.akamai.steamstatic.com
|
||||
clan.akamai.steamstatic.com
|
||||
cdn.cloudflare.steamstatic.com
|
||||
community.cloudflare.steamstatic.com
|
||||
store.cloudflare.steamstatic.com
|
||||
avatars.cloudflare.steamstatic.com
|
||||
clan.cloudflare.steamstatic.com
|
||||
shared.cloudflare.steamstatic.com
|
||||
steam-chat.com
|
||||
steamcloud-ugc.storage.googleapis.com
|
||||
steamcloud-eu-ams.storage.googleapis.com
|
||||
steamcloud-eu-fra.storage.googleapis.com
|
||||
steamcloud-finland.storage.googleapis.com
|
||||
steamcloud-saopaulo.storage.googleapis.com
|
||||
steamcloud-singapore.storage.googleapis.com
|
||||
steamcloud-sydney.storage.googleapis.com
|
||||
steamcloud-taiwan.storage.googleapis.com
|
||||
steamcloud-eu.storage.googleapis.com
|
||||
domain:blizzard.com
|
||||
geosite:category-games'
|
||||
|
||||
config shunt_rules 'AIGC'
|
||||
option remarks 'AIGC'
|
||||
option network 'tcp,udp'
|
||||
option domain_list 'geosite:category-ai-!cn
|
||||
geosite:apple-intelligence'
|
||||
|
||||
config shunt_rules 'Streaming'
|
||||
option remarks 'Streaming'
|
||||
option network 'tcp,udp'
|
||||
option domain_list 'geosite:netflix
|
||||
geosite:disney'
|
||||
|
||||
config shunt_rules 'Proxy'
|
||||
option remarks 'Proxy'
|
||||
option network 'tcp,udp'
|
||||
option domain_list 'geosite:geolocation-!cn'
|
||||
option ip_list '149.154.160.0/20
|
||||
91.108.4.0/22
|
||||
91.108.56.0/24
|
||||
109.239.140.0/24
|
||||
67.198.55.0/24
|
||||
8.8.4.4
|
||||
8.8.8.8
|
||||
208.67.222.222
|
||||
208.67.220.220
|
||||
1.1.1.1
|
||||
1.1.1.2
|
||||
1.0.0.1
|
||||
9.9.9.9
|
||||
149.112.112.112
|
||||
2001:67c:4e8::/48
|
||||
2001:b28:f23c::/48
|
||||
2001:b28:f23d::/48
|
||||
2001:b28:f23f::/48
|
||||
2001:b28:f242::/48
|
||||
2001:4860:4860::8888
|
||||
2001:4860:4860::8844
|
||||
2606:4700:4700::1111
|
||||
2606:4700:4700::1001'
|
||||
|
||||
config shunt_rules 'Direct'
|
||||
option remarks 'Direct'
|
||||
option network 'tcp,udp'
|
||||
option domain_list 'geosite:cn'
|
||||
option ip_list '223.5.5.5/32
|
||||
223.6.6.6/32
|
||||
119.29.29.29/32
|
||||
180.76.76.76/32
|
||||
114.114.114.114/32
|
||||
114.114.115.115/32
|
||||
1.12.12.12/32
|
||||
120.53.53.53/32
|
||||
geoip:cn
|
||||
geoip:private'
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,560 +0,0 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
-- Copyright(c): lwb1978 2025–2027
|
||||
|
||||
local api = require("luci.passwall.api")
|
||||
local urlencode = api.UrlEncode
|
||||
local base64 = api.base64Encode
|
||||
local json = api.jsonc
|
||||
|
||||
local function host_format(host)
|
||||
if not host then return "" end
|
||||
local str = host:match("%[(.-)%]") or host
|
||||
if api.datatypes.ip6addr(str) then
|
||||
return "[" .. str .. "]"
|
||||
end
|
||||
return host
|
||||
end
|
||||
|
||||
local function build_alpn(alpn) -- 排序+去重
|
||||
if not alpn then return nil end
|
||||
|
||||
local seen = {}
|
||||
local order = { "h3", "h2", "http/1.1" }
|
||||
|
||||
if type(alpn) == "table" then
|
||||
for _, v in ipairs(alpn) do
|
||||
if v then seen[v] = true end
|
||||
end
|
||||
else
|
||||
seen[tostring(alpn)] = true
|
||||
end
|
||||
|
||||
local t = {}
|
||||
for _, v in ipairs(order) do
|
||||
if seen[v] then table.insert(t, v) end
|
||||
end
|
||||
|
||||
if #t == 0 then return nil end
|
||||
return table.concat(t, ",")
|
||||
end
|
||||
|
||||
local function build_common(node)
|
||||
local o = {}
|
||||
|
||||
o.server = host_format(node.server)
|
||||
o.port = node.port
|
||||
o.name = node.name
|
||||
|
||||
-- ===== TLS =====
|
||||
o.tls = {}
|
||||
|
||||
if node["reality-opts"] then
|
||||
o.tls.security = "reality"
|
||||
o.tls.pbk = node["reality-opts"]["public-key"]
|
||||
o.tls.sid = node["reality-opts"]["short-id"]
|
||||
elseif node.tls then
|
||||
o.tls.security = "tls"
|
||||
end
|
||||
|
||||
o.tls.sni = node.servername or node.sni
|
||||
o.tls.fp = node["client-fingerprint"]
|
||||
o.tls.pcs = node.fingerprint
|
||||
o.tls.insecure = node["skip-cert-verify"] == true
|
||||
|
||||
o.tls.alpn = build_alpn(node.alpn)
|
||||
|
||||
local ech_opts = node["ech-opts"]
|
||||
if ech_opts and ech_opts.enable == true then
|
||||
if ech_opts["query-server-name"] then
|
||||
o.tls.ech = ech_opts["query-server-name"] .. "+https://223.5.5.5/dns-query"
|
||||
elseif ech_opts.config then
|
||||
o.tls.ech = ech_opts.config
|
||||
end
|
||||
end
|
||||
|
||||
-- ===== transport =====
|
||||
o.transport = {}
|
||||
local net = node.network or "tcp"
|
||||
o.transport.type = net
|
||||
|
||||
local function get_first(v)
|
||||
if type(v) == "table" then return v[1] end
|
||||
return v
|
||||
end
|
||||
|
||||
if net == "ws" then
|
||||
local opts = node["ws-opts"]
|
||||
if opts then
|
||||
o.transport.path = opts.path
|
||||
o.transport.host = opts.headers and opts.headers.Host
|
||||
end
|
||||
|
||||
elseif net == "grpc" then
|
||||
local opts = node["grpc-opts"]
|
||||
if opts then
|
||||
o.transport.serviceName = opts["grpc-service-name"]
|
||||
end
|
||||
|
||||
elseif net == "http" then
|
||||
local opts = node["http-opts"]
|
||||
if opts then
|
||||
o.transport.host = get_first(opts.host)
|
||||
o.transport.path = get_first(opts.path)
|
||||
end
|
||||
|
||||
elseif net == "h2" then
|
||||
local opts = node["h2-opts"]
|
||||
if opts then
|
||||
o.transport.host = get_first(opts.host)
|
||||
o.transport.path = opts.path
|
||||
end
|
||||
|
||||
elseif net == "xhttp" then
|
||||
local opts = node["xhttp-opts"]
|
||||
if opts then
|
||||
o.transport.host = opts.host
|
||||
o.transport.path = opts.path
|
||||
o.transport.mode = opts.mode
|
||||
|
||||
local extra = {}
|
||||
|
||||
-- headers
|
||||
if opts.headers then
|
||||
extra.headers = opts.headers
|
||||
end
|
||||
|
||||
if opts["x-padding-bytes"] then
|
||||
extra.xPaddingBytes = opts["x-padding-bytes"]
|
||||
end
|
||||
|
||||
if opts["no-grpc-header"] ~= nil then
|
||||
extra.noGRPCHeader = opts["no-grpc-header"]
|
||||
end
|
||||
|
||||
if opts["sc-max-each-post-bytes"] then
|
||||
extra.scMaxEachPostBytes = opts["sc-max-each-post-bytes"]
|
||||
end
|
||||
|
||||
if opts["sc-min-posts-interval-ms"] then
|
||||
extra.scMinPostsIntervalMs = opts["sc-min-posts-interval-ms"]
|
||||
end
|
||||
|
||||
-- xmux
|
||||
if opts["reuse-settings"] then
|
||||
local r = opts["reuse-settings"]
|
||||
local xmux = {}
|
||||
|
||||
if r["max-concurrency"] then xmux.maxConcurrency = r["max-concurrency"] end
|
||||
if r["max-connections"] then xmux.maxConnections = tonumber(r["max-connections"]) end
|
||||
if r["c-max-reuse-times"] then xmux.cMaxReuseTimes = tonumber(r["c-max-reuse-times"]) end
|
||||
if r["h-max-request-times"] then xmux.hMaxRequestTimes = r["h-max-request-times"] end
|
||||
if r["h-max-reusable-secs"] then xmux.hMaxReusableSecs = r["h-max-reusable-secs"] end
|
||||
if r["h-keep-alive-period"] then xmux.hKeepAlivePeriod = tonumber(r["h-keep-alive-period"]) end
|
||||
|
||||
if next(xmux) then extra.xmux = xmux end
|
||||
end
|
||||
|
||||
-- download-settings
|
||||
if opts["download-settings"] then
|
||||
local d = opts["download-settings"]
|
||||
local ds = {}
|
||||
|
||||
if d.server then ds.address = d.server end
|
||||
if d.port then ds.port = d.port end
|
||||
|
||||
ds.network = "xhttp"
|
||||
|
||||
-- TLS
|
||||
if d.tls then
|
||||
ds.security = "tls"
|
||||
ds.tlsSettings = {}
|
||||
|
||||
if d.servername then
|
||||
ds.tlsSettings.serverName = d.servername
|
||||
end
|
||||
if d["skip-cert-verify"] == true then
|
||||
ds.tlsSettings.allowInsecure = true
|
||||
end
|
||||
if d["client-fingerprint"] then
|
||||
ds.tlsSettings.fingerprint = d["client-fingerprint"]
|
||||
end
|
||||
if d.fingerprint then
|
||||
ds.tlsSettings.pinnedPeerCertSha256 = d.fingerprint
|
||||
end
|
||||
if d.alpn then
|
||||
ds.tlsSettings.alpn = d.alpn
|
||||
end
|
||||
end
|
||||
|
||||
-- xhttpSettings
|
||||
local xs = {}
|
||||
|
||||
if d.path then xs.path = d.path end
|
||||
if d.host then xs.host = d.host end
|
||||
|
||||
if next(xs) then
|
||||
ds.xhttpSettings = xs
|
||||
end
|
||||
|
||||
extra.downloadSettings = ds
|
||||
end
|
||||
|
||||
if next(extra) then
|
||||
o.transport.extra = json.stringify(extra)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
-- VLESS
|
||||
local function encode_vless(node)
|
||||
local o = build_common(node)
|
||||
|
||||
local link = "vless://" .. node.uuid .. "@" .. o.server .. ":" .. o.port
|
||||
local p = {}
|
||||
|
||||
if node.flow then table.insert(p, "flow=" .. urlencode(node.flow)) end
|
||||
if node.encryption then table.insert(p, "encryption=" .. urlencode(node.encryption)) end
|
||||
|
||||
-- TLS
|
||||
if o.tls.security then table.insert(p, "security=" .. o.tls.security) end
|
||||
if o.tls.pbk then table.insert(p, "pbk=" .. urlencode(o.tls.pbk)) end
|
||||
if o.tls.sid then table.insert(p, "sid=" .. urlencode(o.tls.sid)) end
|
||||
if o.tls.sni then table.insert(p, "sni=" .. urlencode(o.tls.sni)) end
|
||||
if o.tls.fp then table.insert(p, "fp=" .. urlencode(o.tls.fp)) end
|
||||
if o.tls.alpn then table.insert(p, "alpn=" .. urlencode(o.tls.alpn)) end
|
||||
if o.tls.ech then table.insert(p, "ech=" .. urlencode(o.tls.ech)) end
|
||||
if o.tls.pcs then table.insert(p, "pcs=" .. urlencode(o.tls.pcs)) end
|
||||
table.insert(p, "allowInsecure=" .. (o.tls.insecure and "1" or "0"))
|
||||
|
||||
-- transport
|
||||
table.insert(p, "type=" .. o.transport.type)
|
||||
if o.transport.host then table.insert(p, "host=" .. urlencode(o.transport.host)) end
|
||||
if o.transport.path then table.insert(p, "path=" .. urlencode(o.transport.path)) end
|
||||
if o.transport.serviceName then table.insert(p, "serviceName=" .. urlencode(o.transport.serviceName)) end
|
||||
if o.transport.mode then table.insert(p, "mode=" .. urlencode(o.transport.mode)) end
|
||||
if o.transport.extra then table.insert(p, "extra=" .. urlencode(o.transport.extra)) end
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
end
|
||||
|
||||
return link .. "#" .. urlencode(o.name or "")
|
||||
end
|
||||
|
||||
-- Trojan
|
||||
local function encode_trojan(node)
|
||||
local o = build_common(node)
|
||||
|
||||
local link = "trojan://" .. node.password .. "@" .. o.server .. ":" .. o.port
|
||||
local p = {}
|
||||
|
||||
if o.tls.security then table.insert(p, "security=" .. o.tls.security) end
|
||||
if o.tls.pbk then table.insert(p, "pbk=" .. urlencode(o.tls.pbk)) end
|
||||
if o.tls.sid then table.insert(p, "sid=" .. urlencode(o.tls.sid)) end
|
||||
if o.tls.sni then table.insert(p, "sni=" .. urlencode(o.tls.sni)) end
|
||||
if o.tls.fp then table.insert(p, "fp=" .. urlencode(o.tls.fp)) end
|
||||
if o.tls.alpn then table.insert(p, "alpn=" .. urlencode(o.tls.alpn)) end
|
||||
if o.tls.ech then table.insert(p, "ech=" .. urlencode(o.tls.ech)) end
|
||||
if o.tls.pcs then table.insert(p, "pcs=" .. urlencode(o.tls.pcs)) end
|
||||
table.insert(p, "allowInsecure=" .. (o.tls.insecure and "1" or "0"))
|
||||
|
||||
table.insert(p, "type=" .. o.transport.type)
|
||||
if o.transport.host then table.insert(p, "host=" .. urlencode(o.transport.host)) end
|
||||
if o.transport.path then table.insert(p, "path=" .. urlencode(o.transport.path)) end
|
||||
if o.transport.serviceName then table.insert(p, "serviceName=" .. urlencode(o.transport.serviceName)) end
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
end
|
||||
|
||||
return link .. "#" .. urlencode(o.name or "")
|
||||
end
|
||||
|
||||
-- VMess
|
||||
local function encode_vmess(node)
|
||||
local o = build_common(node)
|
||||
|
||||
local obj = {
|
||||
v = "2",
|
||||
ps = o.name,
|
||||
add = node.server,
|
||||
port = tostring(node.port),
|
||||
id = node.uuid,
|
||||
aid = tostring(node.alterId or 0),
|
||||
net = o.transport.type,
|
||||
security = node.cipher,
|
||||
scy = node.cipher,
|
||||
type = "none",
|
||||
host = o.transport.host or "",
|
||||
path = o.transport.path or "",
|
||||
tls = o.tls.security == "tls" and "tls" or "",
|
||||
sni = o.tls.sni,
|
||||
alpn = o.tls.alpn,
|
||||
fp = o.tls.fp,
|
||||
ech = o.tls.ech,
|
||||
pcs = o.tls.pcs,
|
||||
insecure = o.tls.insecure and "1" or "0",
|
||||
tfo = node.tfo and "1" or "0"
|
||||
}
|
||||
|
||||
if o.transport.type == "grpc" then
|
||||
obj.path = o.transport.serviceName or ""
|
||||
end
|
||||
|
||||
return "vmess://" .. base64(json.stringify(obj))
|
||||
end
|
||||
|
||||
-- SS
|
||||
local function encode_ss(node)
|
||||
local userinfo = node.cipher .. ":" .. node.password
|
||||
local base = userinfo .. "@" .. host_format(node.server) .. ":" .. node.port
|
||||
local link = "ss://" .. base64(base)
|
||||
|
||||
local p = {}
|
||||
|
||||
if node.udp then table.insert(p, "udp=1") end
|
||||
if node["udp-over-tcp"] then table.insert(p, "uot=1") end
|
||||
|
||||
if node.plugin then
|
||||
local plugin = (node.plugin == "obfs") and "obfs-local" or node.plugin
|
||||
if plugin == "shadow-tls" then
|
||||
local shadow_tls = base64(json.stringify(node["plugin-opts"] or {}))
|
||||
table.insert(p, "shadow-tls=" .. urlencode(shadow_tls))
|
||||
else
|
||||
local opts = {}
|
||||
for k, v in pairs(node["plugin-opts"] or {}) do
|
||||
if plugin == "obfs-local" then
|
||||
if k == "mode" then k = "obfs" end
|
||||
if k == "host" then k = "obfs-host" end
|
||||
elseif plugin == "v2ray-plugin" then
|
||||
if k == "mode" and v == "websocket" then
|
||||
v = nil
|
||||
elseif type(v) == "boolean" then
|
||||
if v == true then
|
||||
table.insert(opts, k)
|
||||
end
|
||||
v = nil
|
||||
end
|
||||
elseif plugin == "gost-plugin" then
|
||||
if k == "mode" and v == "websocket" then v = "ws" end
|
||||
if k == "host" then k = "serverName" end
|
||||
if k == "headers" then v = nil end
|
||||
end
|
||||
if v ~= nil then
|
||||
if type(v) == "boolean" then
|
||||
v = v and "1" or "0"
|
||||
end
|
||||
table.insert(opts, k .. "=" .. v)
|
||||
end
|
||||
end
|
||||
if #opts > 0 then plugin = plugin .. ";" .. table.concat(opts, ";") end
|
||||
table.insert(p, "plugin=" .. urlencode(plugin))
|
||||
end
|
||||
end
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
end
|
||||
|
||||
return link .. "#" .. urlencode(node.name or "")
|
||||
end
|
||||
|
||||
-- Hysteria
|
||||
local function encode_hysteria2(node)
|
||||
local link = "hysteria://" .. host_format(node.server) .. ":" .. (node.port or "")
|
||||
local p = {}
|
||||
|
||||
if node["auth-str"] then table.insert(p, "auth=" .. node["auth-str"]) end
|
||||
if node["ports"] then table.insert(p, "mport=" .. node["ports"]) end
|
||||
if node.obfs then table.insert(p, "obfsParam=" .. node.obfs) end
|
||||
if node.sni then table.insert(p, "sni=" .. node.sni) end
|
||||
if node.up then table.insert(p, "upmbps=" .. node.up) end
|
||||
if node.down then table.insert(p, "downmbps=" .. node.down) end
|
||||
if node["skip-cert-verify"] then table.insert(p, "insecure=1") end
|
||||
if node["fingerprint"] then table.insert(p, "pinSHA256=" .. urlencode(node["fingerprint"])) end
|
||||
|
||||
if node.alpn then
|
||||
table.insert(p, "alpn=" .. urlencode(build_alpn(node.alpn)))
|
||||
end
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
end
|
||||
|
||||
return link .. "#" .. urlencode(node.name or "")
|
||||
end
|
||||
|
||||
-- Hysteria2
|
||||
local function encode_hysteria2(node)
|
||||
local link = "hysteria2://" .. (node.password or "") .. "@" .. host_format(node.server) .. ":" .. (node.port or "")
|
||||
local p = {}
|
||||
|
||||
if node["ports"] then table.insert(p, "mport=" .. urlencode(node["ports"])) end
|
||||
if node.obfs then table.insert(p, "obfs=" .. node.obfs) end
|
||||
if node["obfs-password"] then table.insert(p, "obfs-password=" .. node["obfs-password"]) end
|
||||
if node.up then table.insert(p, "upmbps=" .. node.up) end
|
||||
if node.down then table.insert(p, "downmbps=" .. node.down) end
|
||||
|
||||
if node.sni then table.insert(p, "sni=" .. urlencode(node.sni)) end
|
||||
if node["skip-cert-verify"] then table.insert(p, "insecure=1") end
|
||||
if node["fingerprint"] then table.insert(p, "pinSHA256=" .. urlencode(node["fingerprint"])) end
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
end
|
||||
|
||||
return link .. "#" .. urlencode(node.name or "")
|
||||
end
|
||||
|
||||
-- TUIC
|
||||
local function encode_tuic(node)
|
||||
local link = "tuic://" .. node.uuid .. ":" .. node.password .. "@" .. host_format(node.server) .. ":" .. node.port
|
||||
local p = {}
|
||||
|
||||
if node["congestion-controller"] then
|
||||
table.insert(p, "congestion_control=" .. node["congestion-controller"])
|
||||
end
|
||||
|
||||
if node.alpn then
|
||||
table.insert(p, "alpn=" .. urlencode(build_alpn(node.alpn)))
|
||||
end
|
||||
|
||||
if node.sni then table.insert(p, "sni=" .. urlencode(node.sni)) end
|
||||
if node["disable-sni"] then table.insert(p, "disable_sni=1") end
|
||||
if node["skip-cert-verify"] then table.insert(p, "allowInsecure=1") end
|
||||
if node["udp-relay-mode"] then table.insert(p, "udp_relay_mode=" .. node["udp-relay-mode"]) end
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
end
|
||||
|
||||
return link .. "#" .. urlencode(node.name or "")
|
||||
end
|
||||
|
||||
-- AnyTLS
|
||||
local function encode_anytls(node)
|
||||
local o = build_common(node)
|
||||
|
||||
local link = "anytls://" .. (node.password or "") .. "@" .. host_format(node.server) .. ":" .. node.port
|
||||
local p = {}
|
||||
|
||||
if o.tls.sni then table.insert(p, "sni=" .. urlencode(o.tls.sni)) end
|
||||
if o.tls.alpn then table.insert(p, "alpn=" .. urlencode(o.tls.alpn)) end
|
||||
if o.tls.fp then table.insert(p, "fp=" .. urlencode(o.tls.fp)) end
|
||||
if o.tls.ech then table.insert(p, "ech=" .. urlencode(o.tls.ech)) end
|
||||
if o.tls.pcs then
|
||||
table.insert(p, "allowInsecure=1")
|
||||
else
|
||||
table.insert(p, "allowInsecure=" .. (o.tls.insecure and "1" or "0"))
|
||||
end
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
end
|
||||
|
||||
return link .. "#" .. urlencode(node.name or "")
|
||||
end
|
||||
|
||||
-- SSR
|
||||
local function encode_ssr(node)
|
||||
local link = host_format(node.server) .. ":" .. node.port .. ":" .. (node.protocol or "") .. ":" ..
|
||||
(node.cipher or "") .. ":" .. (node.obfs or "") .. ":" .. base64(node.password)
|
||||
local p = {}
|
||||
|
||||
if node["obfs-param"] then table.insert(p, "obfsparam=" .. base64(node["obfs-param"])) end
|
||||
if node["protocol-param"] then table.insert(p, "protoparam=" .. base64(node["protocol-param"])) end
|
||||
table.insert(p, "remarks=" .. base64(node.name))
|
||||
|
||||
if #p > 0 then
|
||||
link = link .. "?" .. table.concat(p, "&")
|
||||
end
|
||||
|
||||
return "ssr://" .. base64(link)
|
||||
end
|
||||
|
||||
local function encode_node(node)
|
||||
if (not node.type) or (not node.name) then return nil end
|
||||
|
||||
local t = node.type
|
||||
|
||||
if t == "vless" then return encode_vless(node)
|
||||
elseif t == "trojan" then return encode_trojan(node)
|
||||
elseif t == "vmess" then return encode_vmess(node)
|
||||
elseif t == "ss" then return encode_ss(node)
|
||||
elseif t == "hysteria" then return encode_hysteria(node)
|
||||
elseif t == "hysteria2" then return encode_hysteria2(node)
|
||||
elseif t == "tuic" then return encode_tuic(node)
|
||||
elseif t == "anytls" then return encode_anytls(node)
|
||||
elseif t == "ssr" then return encode_ssr(node)
|
||||
else api.log("订阅转换 → 丢弃不支持的节点:" .. node.name .. ",节点类型:" .. t)
|
||||
end
|
||||
end
|
||||
|
||||
function parseClashNode(raw, remark)
|
||||
if not raw then return "" end
|
||||
local ok, lyaml = pcall(require, "lyaml")
|
||||
if not ok then return raw end
|
||||
|
||||
local data = lyaml.load(raw)
|
||||
if not data or type(data) ~= "table" then return raw end
|
||||
if not data.proxies then return "" end
|
||||
|
||||
api.log('检测到 Clash 订阅,正在进行转换 ...')
|
||||
|
||||
local links = {}
|
||||
for _, node in ipairs(data.proxies) do
|
||||
local link = encode_node(node)
|
||||
if link then
|
||||
table.insert(links, link)
|
||||
end
|
||||
end
|
||||
|
||||
return #links > 0 and table.concat(links, "\n") or ""
|
||||
end
|
||||
|
||||
function parse_clash_sub_info(headers)
|
||||
local userinfo = headers:match("[Ss]ubscription%-userinfo:%s*([^\r\n]+)")
|
||||
if not userinfo then return nil end
|
||||
|
||||
local upload = tonumber(userinfo:match("upload=(%d+)")) or 0
|
||||
local download = tonumber(userinfo:match("download=(%d+)")) or 0
|
||||
local total = tonumber(userinfo:match("total=(%d+)")) or 0
|
||||
local expire = tonumber(userinfo:match("expire=(%d+)"))
|
||||
local remain = total - (upload + download)
|
||||
if remain < 0 then remain = 0 end
|
||||
|
||||
local function format_size(bytes)
|
||||
local units = { "B", "KB", "MB", "GB", "TB", "PB" }
|
||||
local i = 1
|
||||
while bytes >= 1024 and i < #units do
|
||||
bytes = bytes / 1024
|
||||
i = i + 1
|
||||
end
|
||||
if bytes >= 100 then
|
||||
return string.format("%.0f%s", bytes, units[i])
|
||||
elseif bytes >= 10 then
|
||||
return string.format("%.1f%s", bytes, units[i])
|
||||
else
|
||||
return string.format("%.2f%s", bytes, units[i])
|
||||
end
|
||||
end
|
||||
|
||||
local rem_traffic = format_size(remain)
|
||||
|
||||
local expired_date
|
||||
if expire and expire > 0 then
|
||||
local t = os.date("*t", expire)
|
||||
expired_date = string.format("%d-%d-%d", t.year, t.month, t.day)
|
||||
else
|
||||
expired_date = "长期有效"
|
||||
end
|
||||
|
||||
return {
|
||||
rem_traffic = rem_traffic,
|
||||
expired_date = expired_date
|
||||
}
|
||||
end
|
||||
@@ -1,238 +0,0 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
local api = require ("luci.passwall.api")
|
||||
local appname = api.appname
|
||||
local fs = api.fs
|
||||
local jsonc = api.jsonc
|
||||
local uci = api.uci
|
||||
local sys = api.sys
|
||||
|
||||
local log = function(...)
|
||||
api.log(...)
|
||||
end
|
||||
|
||||
function get_ip_port_from(str)
|
||||
local result_port = sys.exec("echo -n " .. str .. " | sed -n 's/^.*[:#]\\([0-9]*\\)$/\\1/p'")
|
||||
local result_ip = sys.exec(string.format("__host=%s;__varport=%s;", str, result_port) .. "echo -n ${__host%%${__varport:+[:#]${__varport}*}}")
|
||||
return result_ip, result_port
|
||||
end
|
||||
|
||||
local var = api.get_args(arg)
|
||||
local haproxy_path = var["-path"]
|
||||
local haproxy_conf = var["-conf"]
|
||||
local haproxy_dns = var["-dns"] or "119.29.29.29:53,223.5.5.5:53"
|
||||
|
||||
local cpu_thread = sys.exec('echo -n $(cat /proc/cpuinfo | grep "processor" | wc -l)') or "1"
|
||||
local health_check_type = uci:get(appname, "@global_haproxy[0]", "health_check_type") or "tcp"
|
||||
local health_check_inter = uci:get(appname, "@global_haproxy[0]", "health_check_inter") or "20"
|
||||
local balancingStrategy = uci:get(appname, "@global_haproxy[0]", "balancingStrategy") or "roundrobin"
|
||||
local console_port = uci:get(appname, "@global_haproxy[0]", "console_port")
|
||||
local bind_local = uci:get(appname, "@global_haproxy[0]", "bind_local") or "0"
|
||||
local bind_address = "0.0.0.0"
|
||||
if bind_local == "1" then bind_address = "127.0.0.1" end
|
||||
|
||||
log("HAPROXY 负载均衡:")
|
||||
log(string.format(" * 控制台端口:%s", console_port))
|
||||
fs.mkdir(haproxy_path)
|
||||
local haproxy_file = haproxy_path .. "/" .. haproxy_conf
|
||||
|
||||
local f_out = io.open(haproxy_file, "a")
|
||||
|
||||
local haproxy_config = [[
|
||||
global
|
||||
daemon
|
||||
log 127.0.0.1 local2
|
||||
maxconn 60000
|
||||
stats socket {{path}}/haproxy.sock
|
||||
nbthread {{nbthread}}
|
||||
external-check
|
||||
insecure-fork-wanted
|
||||
spread-checks 50
|
||||
tune.max-checks-per-thread 5
|
||||
|
||||
defaults
|
||||
mode tcp
|
||||
log global
|
||||
option tcplog
|
||||
option dontlognull
|
||||
option http-server-close
|
||||
#option forwardfor except 127.0.0.0/8
|
||||
option redispatch
|
||||
retries 2
|
||||
timeout http-request 10s
|
||||
timeout queue 1m
|
||||
timeout connect 10s
|
||||
timeout client 1m
|
||||
timeout server 1m
|
||||
timeout http-keep-alive 10s
|
||||
timeout check 10s
|
||||
maxconn 3000
|
||||
|
||||
resolvers mydns
|
||||
resolve_retries 1
|
||||
timeout resolve 5s
|
||||
hold valid 600s
|
||||
{{dns}}
|
||||
]]
|
||||
|
||||
haproxy_config = haproxy_config:gsub("{{path}}", haproxy_path)
|
||||
haproxy_config = haproxy_config:gsub("{{nbthread}}", cpu_thread)
|
||||
|
||||
local mydns = ""
|
||||
local index = 0
|
||||
string.gsub(haproxy_dns, '[^' .. "," .. ']+', function(w)
|
||||
index = index + 1
|
||||
local s = w:gsub("#", ":")
|
||||
if not s:find(":") then
|
||||
s = s .. ":53"
|
||||
end
|
||||
mydns = mydns .. (index > 1 and "\n" or "") .. " " .. string.format("nameserver dns%s %s", index, s)
|
||||
end)
|
||||
haproxy_config = haproxy_config:gsub("{{dns}}", mydns)
|
||||
|
||||
f_out:write(haproxy_config)
|
||||
|
||||
local listens = {}
|
||||
|
||||
uci:foreach(appname, "haproxy_config", function(t)
|
||||
if t.enabled == "1" then
|
||||
local server_remark
|
||||
local server_address
|
||||
local server_port
|
||||
local lbss = t.lbss
|
||||
local listen_port = tonumber(t.haproxy_port) or 0
|
||||
local server_node = uci:get_all(appname, lbss)
|
||||
local hop = (health_check_type == "passwall_logic") and (server_node.hysteria_hop or server_node.hysteria2_hop) or nil
|
||||
hop = hop and hop:gsub(":", "-") or nil
|
||||
if server_node and server_node.address and (server_node.port or hop) then
|
||||
server_remark = server_node.address .. ":" .. (server_node.port or hop)
|
||||
server_address = server_node.address
|
||||
server_port = server_node.port or hop
|
||||
t.origin_address = server_address
|
||||
t.origin_port = server_port
|
||||
if health_check_type == "passwall_logic" then
|
||||
if server_node.type ~= "Socks" then
|
||||
local relay_port = server_node.port
|
||||
local new_port = api.get_new_port()
|
||||
local config_file = string.format("haproxy_%s_%s.json", t[".name"], new_port)
|
||||
sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null',
|
||||
appname,
|
||||
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s",
|
||||
new_port, --flag
|
||||
server_node[".name"], --node
|
||||
"127.0.0.1", --bind
|
||||
new_port, --socks port
|
||||
config_file --config file
|
||||
)
|
||||
)
|
||||
)
|
||||
server_address = "127.0.0.1"
|
||||
server_port = new_port
|
||||
end
|
||||
end
|
||||
else
|
||||
server_address, server_port = get_ip_port_from(lbss)
|
||||
server_remark = server_address .. ":" .. server_port
|
||||
t.origin_address = server_address
|
||||
t.origin_port = server_port
|
||||
end
|
||||
if server_address and server_port and listen_port > 0 then
|
||||
if not listens[listen_port] then
|
||||
listens[listen_port] = {}
|
||||
end
|
||||
t.server_remark = server_remark
|
||||
t.server_address = server_address
|
||||
t.server_port = server_port
|
||||
table.insert(listens[listen_port], t)
|
||||
else
|
||||
log(" - 丢弃1个明显无效的节点")
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local inter = tonumber((health_check_inter or "20"):match("^%d+"))
|
||||
inter = (inter and inter >= 20) and inter or 20
|
||||
local inter_arg = "inter " .. inter .. "s fastinter " .. inter / 2 .. "s downinter " .. inter * 1.5 .. "s"
|
||||
|
||||
local sortTable = {}
|
||||
for i in pairs(listens) do
|
||||
if i ~= nil then
|
||||
table.insert(sortTable, i)
|
||||
end
|
||||
end
|
||||
table.sort(sortTable, function(a,b) return (a < b) end)
|
||||
|
||||
for i, port in pairs(sortTable) do
|
||||
log(" + 入口 %s:%s" % {bind_address, port})
|
||||
|
||||
f_out:write("\n" .. string.format([[
|
||||
listen %s
|
||||
bind %s:%s
|
||||
mode tcp
|
||||
balance %s
|
||||
]], port, bind_address, port, balancingStrategy))
|
||||
|
||||
if health_check_type == "passwall_logic" then
|
||||
f_out:write(string.format([[
|
||||
option external-check
|
||||
external-check command "/usr/share/%s/haproxy_check.sh"
|
||||
]], appname))
|
||||
end
|
||||
|
||||
local count_M, count_B = 1, 1
|
||||
for i, o in ipairs(listens[port]) do
|
||||
local remark = o.server_remark or ""
|
||||
-- 防止重名导致无法运行
|
||||
if tostring(o.backup) ~= "1" then
|
||||
remark = "M" .. count_M .. "-" .. remark
|
||||
count_M = count_M + 1
|
||||
else
|
||||
remark = "B" .. count_B .. "-" .. remark
|
||||
count_B = count_B + 1
|
||||
end
|
||||
local server = o.server_address .. ":" .. o.server_port
|
||||
local server_conf = "server {{remark}} {{server}} weight {{weight}} {{resolvers}} check {{inter_arg}} rise 2 fall 2 {{backup}}"
|
||||
server_conf = server_conf:gsub("{{remark}}", remark)
|
||||
server_conf = server_conf:gsub("{{server}}", server)
|
||||
server_conf = server_conf:gsub("{{weight}}", o.lbweight)
|
||||
local resolvers = "resolvers mydns"
|
||||
if api.is_ip(o.server_address) then
|
||||
resolvers = ""
|
||||
end
|
||||
server_conf = server_conf:gsub("{{resolvers}}", resolvers)
|
||||
server_conf = server_conf:gsub("{{inter_arg}}", inter_arg)
|
||||
server_conf = server_conf:gsub("{{backup}}", tostring(o.backup) == "1" and "backup" or "")
|
||||
|
||||
f_out:write(" " .. api.trim(server_conf) .. "\n")
|
||||
|
||||
if o.export ~= "0" then
|
||||
sys.call(string.format(". /usr/share/%s/utils.sh ; add_ip2route %s %s", appname, o.origin_address, o.export))
|
||||
end
|
||||
|
||||
log(string.format(" | - 出口节点:%s:%s,权重:%s", o.origin_address, o.origin_port, o.lbweight))
|
||||
end
|
||||
end
|
||||
|
||||
--控制台配置
|
||||
local console_user = uci:get(appname, "@global_haproxy[0]", "console_user")
|
||||
local console_password = uci:get(appname, "@global_haproxy[0]", "console_password")
|
||||
local str = [[
|
||||
listen console
|
||||
bind 0.0.0.0:%s
|
||||
mode http
|
||||
stats refresh 30s
|
||||
stats uri /
|
||||
stats admin if TRUE
|
||||
%s
|
||||
]]
|
||||
f_out:write("\n" .. string.format(str, console_port, (console_user and console_user ~= "" and console_password and console_password ~= "") and "stats auth " .. console_user .. ":" .. console_password or ""))
|
||||
|
||||
f_out:close()
|
||||
|
||||
--passwall内置健康检查URL
|
||||
if health_check_type == "passwall_logic" then
|
||||
local probeUrl = uci:get(appname, "@global_haproxy[0]", "health_probe_url") or "https://www.google.com/generate_204"
|
||||
local f_url = io.open(haproxy_path .. "/Probe_URL", "w")
|
||||
f_url:write(probeUrl)
|
||||
f_url:close()
|
||||
end
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
export PATH=/usr/sbin:/usr/bin:/sbin:/bin:/root/bin
|
||||
CONFIG=passwall
|
||||
|
||||
listen_address=$1
|
||||
listen_port=$2
|
||||
server_address=$3
|
||||
server_port=$4
|
||||
|
||||
busybox pgrep -af "${CONFIG}/" | grep -E 'app\.sh.*(start|stop)|nftables\.sh|iptables\.sh|subscribe\.lua' >/dev/null && {
|
||||
# 特定任务执行中不检测
|
||||
exit 0
|
||||
}
|
||||
|
||||
probe_file="/tmp/etc/passwall/haproxy/Probe_URL"
|
||||
probeUrl="https://www.google.com/generate_204"
|
||||
if [ -f "$probe_file" ]; then
|
||||
firstLine=$(head -n 1 "$probe_file" | tr -d ' \t\n')
|
||||
[ -n "$firstLine" ] && probeUrl="$firstLine"
|
||||
fi
|
||||
|
||||
extra_params="-x socks5h://${server_address}:${server_port}"
|
||||
if /usr/bin/curl --help all | grep -q "\-\-retry-all-errors"; then
|
||||
extra_params="${extra_params} --retry-all-errors"
|
||||
fi
|
||||
|
||||
status=$(/usr/bin/curl -I -o /dev/null -skL ${extra_params} --connect-timeout 3 --retry 1 --max-time 6 -w "%{http_code}" "${probeUrl}")
|
||||
|
||||
case "$status" in
|
||||
200|204)
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -1,538 +0,0 @@
|
||||
local api = require "luci.passwall.api"
|
||||
local appname = api.appname
|
||||
|
||||
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 TCP_NODE = var["-TCP_NODE"]
|
||||
local NFTFLAG = var["-NFTFLAG"]
|
||||
local REMOTE_FAKEDNS = var["-REMOTE_FAKEDNS"]
|
||||
local FILTER_HTTPS = var["-FILTER_HTTPS"]
|
||||
local LOG_FILE = var["-LOG_FILE"]
|
||||
|
||||
local uci = api.uci
|
||||
local sys = api.sys
|
||||
local fs = api.fs
|
||||
local datatypes = api.datatypes
|
||||
|
||||
local TMP_PATH = "/tmp/etc/" .. appname
|
||||
local TMP_ACL_PATH = TMP_PATH .. "/acl"
|
||||
local RULES_PATH = "/usr/share/" .. appname .. "/rules"
|
||||
local FLAG_PATH = TMP_ACL_PATH .. "/" .. FLAG
|
||||
local config_lines = {}
|
||||
local tmp_lines = {}
|
||||
local USE_GEOVIEW = uci:get(appname, "@global_rules[0]", "enable_geoview")
|
||||
local IS_SHUNT_NODE = uci:get(appname, TCP_NODE, "protocol") == "_shunt"
|
||||
|
||||
if IS_SHUNT_NODE then
|
||||
REMOTE_FAKEDNS = uci:get(appname, TCP_NODE, "fakedns") or "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 = uci:get(appname, "@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 end
|
||||
local bin = api.finded_com("geoview")
|
||||
if bin and list_arg and out_path then
|
||||
local cmd = string.format("%q -type geosite -append=true -input %q -list %q -output %q -lowmem=true",
|
||||
bin, geosite_path, list_arg, out_path)
|
||||
sys.call(cmd)
|
||||
return 0
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
if not fs.access(FLAG_PATH) then
|
||||
fs.mkdir(FLAG_PATH)
|
||||
end
|
||||
|
||||
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 = TMP_ACL_PATH .. "/block_host"
|
||||
if USE_BLOCK_LIST == "1" and not fs.access(file_block_host) then
|
||||
local block_domain, lookup_block_domain = {}, {}
|
||||
local geosite_arg = ""
|
||||
for line in io.lines(RULES_PATH .. "/block_host") 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
|
||||
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
|
||||
if USE_GEOVIEW == "1" and geosite_arg ~= "" and api.is_finded("geoview") then
|
||||
if get_geosite(geosite_arg, file_block_host) == 0 then
|
||||
log(" * 解析[屏蔽列表] Geosite 到屏蔽域名表(blocklist)完成")
|
||||
else
|
||||
log(" * 解析[屏蔽列表] Geosite 到屏蔽域名表(blocklist)失败!")
|
||||
end
|
||||
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_ACL_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
|
||||
uci:foreach(appname, "nodes", function(t)
|
||||
process_address(t.address)
|
||||
process_address(t.download_address)
|
||||
end)
|
||||
uci:foreach(appname, "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 = TMP_ACL_PATH .. "/direct_host"
|
||||
if USE_DIRECT_LIST == "1" and not fs.access(file_direct_host) then
|
||||
local direct_domain, lookup_direct_domain = {}, {}
|
||||
local geosite_arg = ""
|
||||
for line in io.lines(RULES_PATH .. "/direct_host") 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(direct_domain, line, lookup_direct_domain)
|
||||
end
|
||||
end
|
||||
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
|
||||
if USE_GEOVIEW == "1" and geosite_arg ~= "" and api.is_finded("geoview") then
|
||||
if get_geosite(geosite_arg, file_direct_host) == 0 then
|
||||
log(" * 解析[直连列表] Geosite 到域名白名单(whitelist)完成")
|
||||
else
|
||||
log(" * 解析[直连列表] Geosite 到域名白名单(whitelist)失败!")
|
||||
end
|
||||
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 = TMP_ACL_PATH .. "/proxy_host"
|
||||
if USE_PROXY_LIST == "1" and not fs.access(file_proxy_host) then
|
||||
local proxy_domain, lookup_proxy_domain = {}, {}
|
||||
local geosite_arg = ""
|
||||
for line in io.lines(RULES_PATH .. "/proxy_host") 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(proxy_domain, line, lookup_proxy_domain)
|
||||
end
|
||||
end
|
||||
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
|
||||
if USE_GEOVIEW == "1" and geosite_arg ~= "" and api.is_finded("geoview") then
|
||||
if get_geosite(geosite_arg, file_proxy_host) == 0 then
|
||||
log(" * 解析[代理列表] Geosite 到代理域名表(blacklist)完成")
|
||||
else
|
||||
log(" * 解析[代理列表] Geosite 到代理域名表(blacklist)失败!")
|
||||
end
|
||||
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,
|
||||
REMOTE_FAKEDNS ~= "1" and "group-ipset " .. table.concat(sets, ",") or ""
|
||||
}
|
||||
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",
|
||||
REMOTE_FAKEDNS ~= "1" and "add-taggfw-ip " .. table.concat(sets, ",") or ""
|
||||
}
|
||||
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
|
||||
tmp_lines = {
|
||||
"chnlist-file " .. RULES_PATH .. "/chnlist",
|
||||
"ipset-name4 " .. setflag .. "psw_chn",
|
||||
"ipset-name6 " .. setflag .. "psw_chn6",
|
||||
"add-tagchn-ip",
|
||||
"chnlist-first"
|
||||
}
|
||||
merge_array(config_lines, tmp_lines)
|
||||
log(string.format(" - 中国域名表(chnroute):%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,
|
||||
REMOTE_FAKEDNS ~= "1" and "group-ipset " .. table.concat(sets, ",") or ""
|
||||
}
|
||||
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(" - 中国域名表(chnroute):%s", DNS_TRUST or "默认"))
|
||||
end
|
||||
end
|
||||
|
||||
--分流规则
|
||||
if IS_SHUNT_NODE then
|
||||
local white_domain, lookup_white_domain = {}, {}
|
||||
local shunt_domain, lookup_shunt_domain = {}, {}
|
||||
local file_white_host = FLAG_PATH .. "/shunt_direct_host"
|
||||
local file_shunt_host = FLAG_PATH .. "/shunt_proxy_host"
|
||||
local geosite_white_arg, geosite_shunt_arg = "", ""
|
||||
|
||||
local t = uci:get_all(appname, TCP_NODE)
|
||||
local default_node_id = t["default_node"] or "_direct"
|
||||
uci:foreach(appname, "shunt_rules", function(s)
|
||||
local _node_id = t[s[".name"]]
|
||||
if _node_id and _node_id ~= "_blackhole" 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:") then
|
||||
if line:find("geosite:") then
|
||||
line = string.match(line, ":([^:]+)$")
|
||||
if _node_id == "_direct" then
|
||||
geosite_white_arg = geosite_white_arg .. (geosite_white_arg ~= "" and "," or "") .. line
|
||||
else
|
||||
geosite_shunt_arg = geosite_shunt_arg .. (geosite_shunt_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(white_domain, line, lookup_white_domain)
|
||||
else
|
||||
insert_unique(shunt_domain, line, lookup_shunt_domain)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if _node_id ~= "_direct" then
|
||||
log(string.format(" - Sing-Box/Xray分流规则(%s):%s", s.remarks, DNS_TRUST or "默认"))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
if is_file_nonzero(file_white_host) == nil then
|
||||
if #white_domain > 0 then
|
||||
local f_out = io.open(file_white_host, "w")
|
||||
for i = 1, #white_domain do
|
||||
f_out:write(white_domain[i] .. "\n")
|
||||
end
|
||||
f_out:close()
|
||||
end
|
||||
end
|
||||
|
||||
if is_file_nonzero(file_shunt_host) == nil then
|
||||
if #shunt_domain > 0 then
|
||||
local f_out = io.open(file_shunt_host, "w")
|
||||
for i = 1, #shunt_domain do
|
||||
f_out:write(shunt_domain[i] .. "\n")
|
||||
end
|
||||
f_out:close()
|
||||
end
|
||||
end
|
||||
|
||||
if GFWLIST == "1" and CHNLIST == "0" and USE_GEOVIEW == "1" and api.is_finded("geoview") then --仅GFW模式解析geosite
|
||||
local return_white, return_shunt
|
||||
if geosite_white_arg ~= "" then
|
||||
return_white = get_geosite(geosite_white_arg, file_white_host)
|
||||
end
|
||||
if geosite_shunt_arg ~= "" then
|
||||
return_shunt = get_geosite(geosite_shunt_arg, file_shunt_host)
|
||||
end
|
||||
if (return_white == nil or return_white == 0) and (return_shunt == nil or return_shunt == 0) then
|
||||
log(" * 解析[分流节点] Geosite 完成")
|
||||
else
|
||||
log(" * 解析[分流节点] Geosite 失败!")
|
||||
end
|
||||
end
|
||||
|
||||
local sets = {
|
||||
setflag .. "psw_shunt",
|
||||
setflag .. "psw_shunt6"
|
||||
}
|
||||
if FLAG ~= "default" then
|
||||
sets = {
|
||||
setflag .. "psw_" .. FLAG .. "_shunt",
|
||||
setflag .. "psw_" .. FLAG .. "_shunt6"
|
||||
}
|
||||
end
|
||||
|
||||
if is_file_nonzero(file_white_host) then
|
||||
if USE_DIRECT_LIST == "1" then
|
||||
--当白名单启用时,添加到白名单组一同处理
|
||||
for i, v in ipairs(config_lines) do
|
||||
if v == "group-dnl " .. file_direct_host then
|
||||
config_lines[i] = "group-dnl " .. file_direct_host .. "," .. file_white_host
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
--当白名单不启用时,创建新组,ipset到shuntlist
|
||||
tmp_lines = {
|
||||
"group whitelist",
|
||||
"group-dnl " .. file_white_host,
|
||||
"group-upstream " .. DNS_LOCAL,
|
||||
"group-ipset " .. table.concat(sets, ",")
|
||||
}
|
||||
insert_array_after(config_lines, tmp_lines, "#--4")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if is_file_nonzero(file_shunt_host) then
|
||||
tmp_lines = {
|
||||
"group shuntlist",
|
||||
"group-dnl " .. file_shunt_host,
|
||||
"group-upstream " .. DNS_TRUST,
|
||||
(not only_global and REMOTE_FAKEDNS == "1") and "" or ("group-ipset " .. table.concat(sets, ","))
|
||||
}
|
||||
if NO_IPV6_TRUST == "1" then table.insert(tmp_lines, "no-ipv6 tag:shuntlist") end
|
||||
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 DEFAULT_TAG == nil 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流程,将会导致域名(直连/代理域名)分流失效!!!")
|
||||
@@ -1,737 +0,0 @@
|
||||
local api = require "luci.passwall.api"
|
||||
local appname = "passwall"
|
||||
local uci = api.uci
|
||||
local sys = api.sys
|
||||
local fs = api.fs
|
||||
local datatypes = api.datatypes
|
||||
local TMP = {}
|
||||
|
||||
local function tinsert(table_name, val)
|
||||
if table_name and type(table_name) == "table" then
|
||||
if not TMP[table_name] then
|
||||
TMP[table_name] = {}
|
||||
end
|
||||
if TMP[table_name][val] then
|
||||
return false
|
||||
end
|
||||
table.insert(table_name, val)
|
||||
TMP[table_name][val] = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function backup_servers()
|
||||
local DNSMASQ_DNS = uci:get("dhcp", "@dnsmasq[0]", "server")
|
||||
if DNSMASQ_DNS and #DNSMASQ_DNS > 0 then
|
||||
uci:set(appname, "@global[0]", "dnsmasq_servers", DNSMASQ_DNS)
|
||||
api.uci_save(uci, appname, true)
|
||||
end
|
||||
end
|
||||
|
||||
local function restore_servers()
|
||||
local dns_table = {}
|
||||
local DNSMASQ_DNS = uci:get("dhcp", "@dnsmasq[0]", "server")
|
||||
if DNSMASQ_DNS and #DNSMASQ_DNS > 0 then
|
||||
for k, v in ipairs(DNSMASQ_DNS) do
|
||||
tinsert(dns_table, v)
|
||||
end
|
||||
end
|
||||
local OLD_SERVER = uci:get(appname, "@global[0]", "dnsmasq_servers")
|
||||
if OLD_SERVER and #OLD_SERVER > 0 then
|
||||
for k, v in ipairs(OLD_SERVER) do
|
||||
tinsert(dns_table, v)
|
||||
end
|
||||
uci:delete(appname, "@global[0]", "dnsmasq_servers")
|
||||
api.uci_save(uci, appname, true)
|
||||
end
|
||||
if dns_table and #dns_table > 0 then
|
||||
uci:set_list("dhcp", "@dnsmasq[0]", "server", dns_table)
|
||||
api.uci_save(uci, "dhcp", true)
|
||||
end
|
||||
end
|
||||
|
||||
function stretch()
|
||||
local dnsmasq_server = uci:get("dhcp", "@dnsmasq[0]", "server")
|
||||
local dnsmasq_noresolv = uci:get("dhcp", "@dnsmasq[0]", "noresolv")
|
||||
local _flag
|
||||
if dnsmasq_server and #dnsmasq_server > 0 then
|
||||
for k, v in ipairs(dnsmasq_server) do
|
||||
if not v:find("/") then
|
||||
_flag = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if not _flag and dnsmasq_noresolv == "1" then
|
||||
uci:delete("dhcp", "@dnsmasq[0]", "noresolv")
|
||||
local RESOLVFILE = "/tmp/resolv.conf.d/resolv.conf.auto"
|
||||
local file = io.open(RESOLVFILE, "r")
|
||||
if not file then
|
||||
RESOLVFILE = "/tmp/resolv.conf.auto"
|
||||
else
|
||||
local size = file:seek("end")
|
||||
file:close()
|
||||
if size == 0 then
|
||||
RESOLVFILE = "/tmp/resolv.conf.auto"
|
||||
end
|
||||
end
|
||||
uci:set("dhcp", "@dnsmasq[0]", "resolvfile", RESOLVFILE)
|
||||
api.uci_save(uci, "dhcp", true)
|
||||
end
|
||||
end
|
||||
|
||||
function restart(var)
|
||||
local LOG = var["-LOG"]
|
||||
sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1")
|
||||
if LOG == "1" then
|
||||
api.log("重启 dnsmasq 服务")
|
||||
end
|
||||
end
|
||||
|
||||
function logic_restart(var)
|
||||
local LOG = var["-LOG"]
|
||||
local DEFAULT_DNS = api.get_cache_var("DEFAULT_DNS")
|
||||
if DEFAULT_DNS then
|
||||
backup_servers()
|
||||
--sys.call("sed -i '/list server/d' /etc/config/dhcp >/dev/null 2>&1")
|
||||
local dns_table = {}
|
||||
local dnsmasq_server = uci:get("dhcp", "@dnsmasq[0]", "server")
|
||||
if dnsmasq_server and #dnsmasq_server > 0 then
|
||||
for k, v in ipairs(dnsmasq_server) do
|
||||
if v:find("/") then
|
||||
tinsert(dns_table, v)
|
||||
end
|
||||
end
|
||||
uci:set_list("dhcp", "@dnsmasq[0]", "server", dns_table)
|
||||
api.uci_save(uci, "dhcp", true)
|
||||
end
|
||||
sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1")
|
||||
restore_servers()
|
||||
else
|
||||
sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1")
|
||||
end
|
||||
if LOG == "1" then
|
||||
api.log("重启 dnsmasq 服务")
|
||||
end
|
||||
end
|
||||
|
||||
function copy_instance(var)
|
||||
local LISTEN_PORT = var["-LISTEN_PORT"]
|
||||
local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"]
|
||||
local conf_lines = {}
|
||||
local DEFAULT_DNSMASQ_CFGID = sys.exec("echo -n $(uci -q show dhcp.@dnsmasq[0] | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}')")
|
||||
for line in io.lines("/tmp/etc/dnsmasq.conf." .. DEFAULT_DNSMASQ_CFGID) do
|
||||
local filter
|
||||
if line:find("passwall") then filter = true end
|
||||
if line:find("ubus") then filter = true end
|
||||
if line:find("dhcp") then filter = true end
|
||||
if line:find("server=") == 1 then filter = true end
|
||||
if line:find("port=") == 1 then filter = true end
|
||||
if line:find("conf%-dir=") == 1 then
|
||||
filter = true
|
||||
if TMP_DNSMASQ_PATH then
|
||||
local tmp_path = line:sub(1 + #"conf-dir=")
|
||||
sys.call(string.format("cp -r %s/* %s/ 2>/dev/null", tmp_path, TMP_DNSMASQ_PATH))
|
||||
end
|
||||
end
|
||||
if line:find("address=") == 1 or (line:find("server=") == 1 and line:find("/")) then filter = nil end
|
||||
if not filter then
|
||||
tinsert(conf_lines, line)
|
||||
end
|
||||
end
|
||||
tinsert(conf_lines, "port=" .. LISTEN_PORT)
|
||||
if TMP_DNSMASQ_PATH then
|
||||
sys.call("rm -rf " .. TMP_DNSMASQ_PATH .. "/*passwall*")
|
||||
end
|
||||
if var["-return"] == "1" then
|
||||
return conf_lines
|
||||
end
|
||||
if #conf_lines > 0 then
|
||||
local DNSMASQ_CONF = var["-DNSMASQ_CONF"]
|
||||
local conf_out = io.open(DNSMASQ_CONF, "a")
|
||||
conf_out:write(table.concat(conf_lines, "\n"))
|
||||
conf_out:write("\n")
|
||||
conf_out:close()
|
||||
end
|
||||
end
|
||||
|
||||
function add_rule(var)
|
||||
local FLAG = var["-FLAG"]
|
||||
local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"]
|
||||
local DNSMASQ_CONF_FILE = var["-DNSMASQ_CONF_FILE"]
|
||||
local LISTEN_PORT = var["-LISTEN_PORT"]
|
||||
local DEFAULT_DNS = var["-DEFAULT_DNS"]
|
||||
local LOCAL_DNS = var["-LOCAL_DNS"]
|
||||
local TUN_DNS = var["-TUN_DNS"]
|
||||
local REMOTE_FAKEDNS = var["-REMOTE_FAKEDNS"]
|
||||
local USE_DEFAULT_DNS = var["-USE_DEFAULT_DNS"]
|
||||
local CHINADNS_DNS = var["-CHINADNS_DNS"]
|
||||
local TCP_NODE = var["-TCP_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 CACHE_PATH = api.CACHE_PATH
|
||||
local CACHE_FLAG = "dnsmasq_" .. FLAG
|
||||
local CACHE_DNS_PATH = CACHE_PATH .. "/" .. CACHE_FLAG
|
||||
local CACHE_TEXT_FILE = CACHE_DNS_PATH .. ".txt"
|
||||
local USE_CHINADNS_NG = "0"
|
||||
local IS_SHUNT_NODE = uci:get(appname, TCP_NODE, "protocol") == "_shunt"
|
||||
|
||||
if IS_SHUNT_NODE then
|
||||
REMOTE_FAKEDNS = uci:get(appname, TCP_NODE, "fakedns") or "0"
|
||||
end
|
||||
|
||||
local list1 = {}
|
||||
local excluded_domain = {}
|
||||
|
||||
local function log(...)
|
||||
if NO_LOGIC_LOG == "1" then
|
||||
return
|
||||
end
|
||||
api.log(...)
|
||||
end
|
||||
|
||||
local function check_dns(domain, dns)
|
||||
if domain == "" or domain:find("#") then
|
||||
return false
|
||||
end
|
||||
if not dns then
|
||||
return
|
||||
end
|
||||
for k,v in ipairs(list1[domain].dns) do
|
||||
if dns == v then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function check_ipset(domain, ipset)
|
||||
if domain == "" or domain:find("#") then
|
||||
return false
|
||||
end
|
||||
if not ipset then
|
||||
return
|
||||
end
|
||||
for k,v in ipairs(list1[domain].ipsets) do
|
||||
if ipset == v then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function set_domain_address(domain, address)
|
||||
if domain == "" or domain:find("#") then
|
||||
return
|
||||
end
|
||||
if not list1[domain] then
|
||||
list1[domain] = {
|
||||
dns = {},
|
||||
ipsets = {}
|
||||
}
|
||||
end
|
||||
if not list1[domain].address then
|
||||
list1[domain].address = address
|
||||
end
|
||||
end
|
||||
|
||||
local function set_domain_dns(domain, dns)
|
||||
if domain == "" or domain:find("#") then
|
||||
return
|
||||
end
|
||||
if not dns then
|
||||
return
|
||||
end
|
||||
if not list1[domain] then
|
||||
list1[domain] = {
|
||||
dns = {},
|
||||
ipsets = {}
|
||||
}
|
||||
end
|
||||
for line in string.gmatch(dns, '[^' .. "," .. ']+') do
|
||||
if not check_dns(domain, line) then
|
||||
table.insert(list1[domain].dns, line)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function set_domain_ipset(domain, ipset)
|
||||
if domain == "" or domain:find("#") then
|
||||
return
|
||||
end
|
||||
if not ipset then
|
||||
return
|
||||
end
|
||||
if not list1[domain] then
|
||||
list1[domain] = {
|
||||
dns = {},
|
||||
ipsets = {}
|
||||
}
|
||||
end
|
||||
for line in string.gmatch(ipset, '[^' .. "," .. ']+') do
|
||||
if not check_ipset(domain, line) then
|
||||
table.insert(list1[domain].ipsets, line)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function add_excluded_domain(domain)
|
||||
if domain == "" or domain:find("#") then
|
||||
return
|
||||
end
|
||||
excluded_domain[domain] = true
|
||||
end
|
||||
|
||||
local function check_excluded_domain(domain)
|
||||
if domain == "" or domain:find("#") then
|
||||
return false
|
||||
end
|
||||
if excluded_domain[domain] then
|
||||
return true
|
||||
end
|
||||
local pos = domain:find(".", 1, true)
|
||||
while pos do
|
||||
if excluded_domain[domain:sub(pos + 1)] then
|
||||
return true
|
||||
end
|
||||
pos = domain:find(".", pos + 1, true)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local cache_text = ""
|
||||
local nodes_address_md5 = sys.exec("echo -n $(uci show passwall | grep '\\.address') | md5sum")
|
||||
local new_rules = sys.exec("echo -n $(find /usr/share/passwall/rules -type f | xargs md5sum)")
|
||||
local new_text = TMP_DNSMASQ_PATH .. DNSMASQ_CONF_FILE .. DEFAULT_DNS .. LOCAL_DNS .. TUN_DNS .. REMOTE_FAKEDNS .. USE_DEFAULT_DNS .. CHINADNS_DNS .. USE_DIRECT_LIST .. USE_PROXY_LIST .. USE_BLOCK_LIST .. USE_GFW_LIST .. CHN_LIST .. DEFAULT_PROXY_MODE .. NO_PROXY_IPV6 .. nodes_address_md5 .. new_rules .. NFTFLAG
|
||||
if fs.access(CACHE_TEXT_FILE) then
|
||||
for line in io.lines(CACHE_TEXT_FILE) do
|
||||
cache_text = line
|
||||
end
|
||||
end
|
||||
|
||||
if cache_text ~= new_text then
|
||||
api.remove(CACHE_DNS_PATH .. "*")
|
||||
end
|
||||
|
||||
local dnsmasq_default_dns
|
||||
if USE_DEFAULT_DNS ~= "nil" then
|
||||
if USE_DEFAULT_DNS == "direct" then
|
||||
dnsmasq_default_dns = LOCAL_DNS
|
||||
end
|
||||
if USE_DEFAULT_DNS == "remote" then
|
||||
dnsmasq_default_dns = TUN_DNS
|
||||
end
|
||||
if USE_DEFAULT_DNS == "remote" and CHN_LIST == "direct" then
|
||||
dnsmasq_default_dns = TUN_DNS
|
||||
end
|
||||
end
|
||||
|
||||
local only_global
|
||||
if DEFAULT_PROXY_MODE == "proxy" and CHN_LIST == "0" and USE_GFW_LIST == "0" then
|
||||
--没有启用中国列表和GFW列表时
|
||||
dnsmasq_default_dns = TUN_DNS
|
||||
only_global = 1
|
||||
end
|
||||
if USE_DEFAULT_DNS == "chinadns_ng" and CHINADNS_DNS ~= "0" then
|
||||
dnsmasq_default_dns = CHINADNS_DNS
|
||||
USE_CHINADNS_NG = "1"
|
||||
end
|
||||
|
||||
local setflag_4= (NFTFLAG == "1") and "4#inet#passwall#" or ""
|
||||
local setflag_6= (NFTFLAG == "1") and "6#inet#passwall#" or ""
|
||||
|
||||
if not fs.access(CACHE_DNS_PATH) then
|
||||
fs.mkdir(CACHE_DNS_PATH)
|
||||
|
||||
--屏蔽列表
|
||||
if USE_CHINADNS_NG == "0" then
|
||||
if USE_BLOCK_LIST == "1" then
|
||||
for line in io.lines("/usr/share/passwall/rules/block_host") do
|
||||
line = api.get_std_domain(line)
|
||||
if line ~= "" and not line:find("#") and not line:find(":") then
|
||||
set_domain_address(line, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local fwd_dns
|
||||
local no_ipv6
|
||||
|
||||
--始终用国内DNS解析节点域名
|
||||
if true then
|
||||
fwd_dns = LOCAL_DNS
|
||||
if USE_CHINADNS_NG == "1" then
|
||||
fwd_dns = nil
|
||||
else
|
||||
local sets = {
|
||||
setflag_4 .. "psw_vps",
|
||||
setflag_6 .. "psw_vps6"
|
||||
}
|
||||
local function process_address(address)
|
||||
address = (address or ""):lower()
|
||||
if api.vps_domain_exclude(address) then return end
|
||||
if datatypes.hostname(address) then
|
||||
set_domain_dns(address, fwd_dns)
|
||||
set_domain_ipset(address, table.concat(sets, ","))
|
||||
end
|
||||
end
|
||||
uci:foreach(appname, "nodes", function(t)
|
||||
process_address(t.address)
|
||||
process_address(t.download_address)
|
||||
end)
|
||||
uci:foreach(appname, "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)
|
||||
log(string.format(" - 节点列表中的域名(vpslist):%s", fwd_dns or "默认"))
|
||||
end
|
||||
end
|
||||
|
||||
--直连(白名单)列表
|
||||
if USE_DIRECT_LIST == "1" then
|
||||
if fs.access("/usr/share/passwall/rules/direct_host") then
|
||||
fwd_dns = LOCAL_DNS
|
||||
if USE_CHINADNS_NG == "1" then
|
||||
fwd_dns = nil
|
||||
end
|
||||
if fwd_dns then
|
||||
local sets = {
|
||||
setflag_4 .. "psw_white",
|
||||
setflag_6 .. "psw_white6"
|
||||
}
|
||||
--始终用国内DNS解析直连(白名单)列表
|
||||
for line in io.lines("/usr/share/passwall/rules/direct_host") do
|
||||
line = api.get_std_domain(line)
|
||||
if line ~= "" and not line:find("#") and not line:find(":") then
|
||||
add_excluded_domain(line)
|
||||
set_domain_dns(line, fwd_dns)
|
||||
set_domain_ipset(line, table.concat(sets, ","))
|
||||
end
|
||||
end
|
||||
log(string.format(" - 域名白名单(whitelist):%s", fwd_dns or "默认"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--代理(黑名单)列表
|
||||
if USE_PROXY_LIST == "1" then
|
||||
if fs.access("/usr/share/passwall/rules/proxy_host") then
|
||||
fwd_dns = TUN_DNS
|
||||
if USE_CHINADNS_NG == "1" then
|
||||
fwd_dns = nil
|
||||
end
|
||||
if fwd_dns then
|
||||
local set_name = "psw_black"
|
||||
local set6_name = "psw_black6"
|
||||
if FLAG ~= "default" then
|
||||
set_name = "psw_" .. FLAG .. "_black"
|
||||
set6_name = "psw_" .. FLAG .. "_black6"
|
||||
end
|
||||
local sets = {
|
||||
setflag_4 .. set_name
|
||||
}
|
||||
if NO_PROXY_IPV6 ~= "1" then
|
||||
table.insert(sets, setflag_6 .. set6_name)
|
||||
end
|
||||
if REMOTE_FAKEDNS == "1" then
|
||||
sets = {}
|
||||
end
|
||||
--始终使用远程DNS解析代理(黑名单)列表
|
||||
for line in io.lines("/usr/share/passwall/rules/proxy_host") do
|
||||
line = api.get_std_domain(line)
|
||||
if line ~= "" and not line:find("#") and not line:find(":") then
|
||||
add_excluded_domain(line)
|
||||
if NO_PROXY_IPV6 == "1" then
|
||||
set_domain_address(line, "::")
|
||||
end
|
||||
set_domain_dns(line, fwd_dns)
|
||||
set_domain_ipset(line, table.concat(sets, ","))
|
||||
end
|
||||
end
|
||||
log(string.format(" - 代理域名表(blacklist):%s", fwd_dns or "默认"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--GFW列表
|
||||
if USE_GFW_LIST == "1" then
|
||||
if fs.access("/usr/share/passwall/rules/gfwlist") then
|
||||
fwd_dns = TUN_DNS
|
||||
if USE_CHINADNS_NG == "1" then
|
||||
fwd_dns = nil
|
||||
end
|
||||
if fwd_dns then
|
||||
local set_name = "psw_gfw"
|
||||
local set6_name = "psw_gfw6"
|
||||
if FLAG ~= "default" then
|
||||
set_name = "psw_" .. FLAG .. "_gfw"
|
||||
set6_name = "psw_" .. FLAG .. "_gfw6"
|
||||
end
|
||||
local sets = {
|
||||
setflag_4 .. set_name
|
||||
}
|
||||
if NO_PROXY_IPV6 ~= "1" then
|
||||
table.insert(sets, setflag_6 .. set6_name)
|
||||
end
|
||||
if REMOTE_FAKEDNS == "1" then
|
||||
sets = {}
|
||||
end
|
||||
for line in io.lines("/usr/share/passwall/rules/gfwlist") do
|
||||
if line ~= "" and not line:find("#") and not check_excluded_domain(line) then
|
||||
if NO_PROXY_IPV6 == "1" then
|
||||
set_domain_address(line, "::")
|
||||
end
|
||||
if dnsmasq_default_dns == fwd_dns then
|
||||
fwd_dns = nil
|
||||
else
|
||||
set_domain_dns(line, fwd_dns)
|
||||
end
|
||||
set_domain_ipset(line, table.concat(sets, ","))
|
||||
end
|
||||
end
|
||||
log(string.format(" - 防火墙域名表(gfwlist):%s", fwd_dns or "默认"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--中国列表
|
||||
if CHN_LIST ~= "0" then
|
||||
if fs.access("/usr/share/passwall/rules/chnlist") then
|
||||
fwd_dns = nil
|
||||
if CHN_LIST == "direct" then
|
||||
fwd_dns = LOCAL_DNS
|
||||
end
|
||||
if CHN_LIST == "proxy" then
|
||||
fwd_dns = TUN_DNS
|
||||
end
|
||||
if USE_CHINADNS_NG == "1" then
|
||||
fwd_dns = nil
|
||||
end
|
||||
if fwd_dns then
|
||||
local sets = {
|
||||
setflag_4 .. "psw_chn",
|
||||
setflag_6 .. "psw_chn6"
|
||||
}
|
||||
if CHN_LIST == "proxy" then
|
||||
if NO_PROXY_IPV6 == "1" then
|
||||
sets = {
|
||||
setflag_4 .. "psw_chn"
|
||||
}
|
||||
end
|
||||
if REMOTE_FAKEDNS == "1" then
|
||||
sets = {}
|
||||
end
|
||||
end
|
||||
for line in io.lines("/usr/share/passwall/rules/chnlist") do
|
||||
if line ~= "" and not line:find("#") and not check_excluded_domain(line) then
|
||||
if CHN_LIST == "proxy" and NO_PROXY_IPV6 == "1" then
|
||||
set_domain_address(line, "::")
|
||||
end
|
||||
if dnsmasq_default_dns == fwd_dns then
|
||||
fwd_dns = nil
|
||||
else
|
||||
set_domain_dns(line, fwd_dns)
|
||||
end
|
||||
set_domain_ipset(line, table.concat(sets, ","))
|
||||
end
|
||||
end
|
||||
log(string.format(" - 中国域名表(chnroute):%s", fwd_dns or "默认"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--分流规则
|
||||
if IS_SHUNT_NODE and USE_CHINADNS_NG == "0" then
|
||||
local t = uci:get_all(appname, TCP_NODE)
|
||||
local default_node_id = t["default_node"] or "_direct"
|
||||
uci:foreach(appname, "shunt_rules", function(s)
|
||||
local _node_id = t[s[".name"]]
|
||||
if _node_id and _node_id ~= "_blackhole" then
|
||||
if _node_id == "_default" then
|
||||
_node_id = default_node_id
|
||||
end
|
||||
|
||||
fwd_dns = nil
|
||||
no_ipv6 = nil
|
||||
|
||||
local sets = {}
|
||||
|
||||
if _node_id == "_direct" then
|
||||
fwd_dns = LOCAL_DNS
|
||||
if USE_DIRECT_LIST == "1" then
|
||||
table.insert(sets, setflag_4 .. "psw_white")
|
||||
table.insert(sets, setflag_6 .. "psw_white6")
|
||||
else
|
||||
local set_name = "psw_shunt"
|
||||
local set6_name = "psw_shunt6"
|
||||
if FLAG ~= "default" then
|
||||
set_name = "psw_" .. FLAG .. "_shunt"
|
||||
set6_name = "psw_" .. FLAG .. "_shunt6"
|
||||
end
|
||||
table.insert(sets, setflag_4 .. set_name)
|
||||
table.insert(sets, setflag_6 .. set6_name)
|
||||
end
|
||||
else
|
||||
local set_name = "psw_shunt"
|
||||
local set6_name = "psw_shunt6"
|
||||
if FLAG ~= "default" then
|
||||
set_name = "psw_" .. FLAG .. "_shunt"
|
||||
set6_name = "psw_" .. FLAG .. "_shunt6"
|
||||
end
|
||||
fwd_dns = TUN_DNS
|
||||
table.insert(sets, setflag_4 .. set_name)
|
||||
if NO_PROXY_IPV6 ~= "1" then
|
||||
table.insert(sets, setflag_6 .. set6_name)
|
||||
else
|
||||
no_ipv6 = true
|
||||
end
|
||||
if not only_global then
|
||||
if REMOTE_FAKEDNS == "1" then
|
||||
sets = {}
|
||||
end
|
||||
end
|
||||
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("geosite:") and not line:find("ext:") then
|
||||
if line:find("domain:") or line:find("full:") then
|
||||
line = string.match(line, ":([^:]+)$")
|
||||
end
|
||||
line = api.get_std_domain(line)
|
||||
add_excluded_domain(line)
|
||||
|
||||
if no_ipv6 then
|
||||
set_domain_address(line, "::")
|
||||
end
|
||||
set_domain_dns(line, fwd_dns)
|
||||
set_domain_ipset(line, table.concat(sets, ","))
|
||||
end
|
||||
end
|
||||
if _node_id ~= "_direct" then
|
||||
log(string.format(" - Sing-Box/Xray分流规则(%s):%s", s.remarks, fwd_dns or "默认"))
|
||||
end
|
||||
end
|
||||
end)
|
||||
elseif only_global == 1 and NO_PROXY_IPV6 == "1" then
|
||||
--节点:固定节点
|
||||
--代理模式:全局模式
|
||||
--过滤代理域名 IPv6:启用
|
||||
--禁止解析所有IPv6记录
|
||||
list1["#"] = {
|
||||
dns = {},
|
||||
ipsets = {},
|
||||
address = "::"
|
||||
}
|
||||
end
|
||||
|
||||
if list1 and next(list1) then
|
||||
local address_out = io.open(CACHE_DNS_PATH .. "/000-address.conf", "a")
|
||||
local server_out = io.open(CACHE_DNS_PATH .. "/001-server.conf", "a")
|
||||
local ipset_out = io.open(CACHE_DNS_PATH .. "/ipset.conf", "a")
|
||||
local set_name = "ipset"
|
||||
if NFTFLAG == "1" then
|
||||
set_name = "nftset"
|
||||
end
|
||||
for key, value in pairs(list1) do
|
||||
if value.address then
|
||||
local domain = "." .. key
|
||||
if key == "#" then
|
||||
domain = key
|
||||
end
|
||||
address_out:write(string.format("address=/%s/%s", domain, value.address) .. "\n")
|
||||
end
|
||||
if value.dns and #value.dns > 0 then
|
||||
for i, dns in ipairs(value.dns) do
|
||||
server_out:write(string.format("server=/.%s/%s", key, dns) .. "\n")
|
||||
end
|
||||
end
|
||||
if value.ipsets and #value.ipsets > 0 then
|
||||
local ipsets_str = ""
|
||||
for i, ipset in ipairs(value.ipsets) do
|
||||
ipsets_str = ipsets_str .. ipset .. ","
|
||||
end
|
||||
ipsets_str = ipsets_str:sub(1, #ipsets_str - 1)
|
||||
ipset_out:write(string.format("%s=/.%s/%s", set_name, key, ipsets_str) .. "\n")
|
||||
end
|
||||
end
|
||||
address_out:close()
|
||||
server_out:close()
|
||||
ipset_out:close()
|
||||
end
|
||||
|
||||
local f_out = io.open(CACHE_TEXT_FILE, "a")
|
||||
f_out:write(new_text)
|
||||
f_out:close()
|
||||
end
|
||||
|
||||
if USE_CHINADNS_NG == "0" then
|
||||
api.remove(TMP_DNSMASQ_PATH)
|
||||
fs.symlink(CACHE_DNS_PATH, TMP_DNSMASQ_PATH)
|
||||
end
|
||||
|
||||
if DNSMASQ_CONF_FILE ~= "nil" then
|
||||
local conf_lines = {}
|
||||
if LISTEN_PORT then
|
||||
--Copy dnsmasq instance
|
||||
conf_lines = copy_instance({["-LISTEN_PORT"] = LISTEN_PORT, ["-TMP_DNSMASQ_PATH"] = TMP_DNSMASQ_PATH, ["-return"] = "1"})
|
||||
--dhcp.leases to hosts
|
||||
local hosts = "/tmp/etc/" .. appname .. "_tmp/dhcp-hosts"
|
||||
sys.call("touch " .. hosts)
|
||||
tinsert(conf_lines, "addn-hosts=" .. hosts)
|
||||
else
|
||||
--Modify the default dnsmasq service
|
||||
end
|
||||
if USE_CHINADNS_NG == "0" then
|
||||
tinsert(conf_lines, string.format("conf-dir=%s", TMP_DNSMASQ_PATH))
|
||||
end
|
||||
if dnsmasq_default_dns then
|
||||
for s in string.gmatch(dnsmasq_default_dns, '[^' .. "," .. ']+') do
|
||||
tinsert(conf_lines, string.format("server=%s", s))
|
||||
end
|
||||
tinsert(conf_lines, "all-servers")
|
||||
tinsert(conf_lines, "no-poll")
|
||||
tinsert(conf_lines, "no-resolv")
|
||||
if USE_CHINADNS_NG == "0" then
|
||||
log(string.format(" - 默认:%s", dnsmasq_default_dns))
|
||||
end
|
||||
|
||||
if FLAG == "default" then
|
||||
api.set_cache_var("DEFAULT_DNS", DEFAULT_DNS)
|
||||
end
|
||||
end
|
||||
if #conf_lines > 0 then
|
||||
local conf_out = io.open(DNSMASQ_CONF_FILE, "a")
|
||||
conf_out:write(table.concat(conf_lines, "\n"))
|
||||
conf_out:write("\n")
|
||||
conf_out:close()
|
||||
end
|
||||
end
|
||||
|
||||
if USE_CHINADNS_NG == "0" then
|
||||
log(" - PassWall必须依赖于Dnsmasq,如果你自行配置了错误的DNS流程,将会导致域名(直连/代理域名)分流失效!!!")
|
||||
end
|
||||
end
|
||||
|
||||
_G.stretch = stretch
|
||||
_G.restart = restart
|
||||
_G.logic_restart = logic_restart
|
||||
_G.copy_instance = copy_instance
|
||||
_G.add_rule = add_rule
|
||||
|
||||
if arg[1] then
|
||||
local func =_G[arg[1]]
|
||||
if func then
|
||||
func(api.get_function_args(arg))
|
||||
end
|
||||
end
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
restart() {
|
||||
local no_log
|
||||
eval_set_val $@
|
||||
_LOG_FILE=$LOG_FILE
|
||||
[ -n "$no_log" ] && LOG_FILE="/dev/null"
|
||||
rm -rf /tmp/smartdns.cache
|
||||
/etc/init.d/smartdns reload >/dev/null 2>&1
|
||||
LOG_FILE=${_LOG_FILE}
|
||||
}
|
||||
|
||||
del() {
|
||||
rm -rf /tmp/etc/smartdns/passwall.conf
|
||||
sed -i "/passwall/d" /etc/smartdns/custom.conf >/dev/null 2>&1
|
||||
rm -rf /tmp/smartdns.cache
|
||||
/etc/init.d/smartdns reload >/dev/null 2>&1
|
||||
}
|
||||
|
||||
arg1=$1
|
||||
shift
|
||||
case $arg1 in
|
||||
del)
|
||||
del $@
|
||||
;;
|
||||
restart)
|
||||
restart $@
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
||||
@@ -1,696 +0,0 @@
|
||||
local api = require "luci.passwall.api"
|
||||
local appname = api.appname
|
||||
|
||||
local var = api.get_args(arg)
|
||||
local FLAG = var["-FLAG"]
|
||||
local SMARTDNS_CONF = var["-SMARTDNS_CONF"]
|
||||
local LOCAL_GROUP = var["-LOCAL_GROUP"]
|
||||
local REMOTE_GROUP = var["-REMOTE_GROUP"]
|
||||
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 REMOTE_FAKEDNS = var["-REMOTE_FAKEDNS"]
|
||||
local TCP_NODE = var["-TCP_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 LOCAL_PORT = var["-LOCAL_PORT"]
|
||||
|
||||
local uci = api.uci
|
||||
local sys = api.sys
|
||||
local fs = api.fs
|
||||
local datatypes = api.datatypes
|
||||
|
||||
local TMP_PATH = "/tmp/etc/" .. appname
|
||||
local TMP_ACL_PATH = TMP_PATH .. "/acl"
|
||||
local RULES_PATH = "/usr/share/" .. appname .. "/rules"
|
||||
local FLAG_PATH = TMP_ACL_PATH .. "/" .. FLAG
|
||||
local TMP_CONF_FILE = FLAG_PATH .. "/smartdns.conf"
|
||||
local config_lines = {}
|
||||
local tmp_lines = {}
|
||||
local USE_GEOVIEW = uci:get(appname, "@global_rules[0]", "enable_geoview")
|
||||
local IS_SHUNT_NODE = uci:get(appname, TCP_NODE, "protocol") == "_shunt"
|
||||
|
||||
if IS_SHUNT_NODE then
|
||||
REMOTE_FAKEDNS = uci:get(appname, TCP_NODE, "fakedns") or "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 = uci:get(appname, "@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 end
|
||||
local bin = api.finded_com("geoview")
|
||||
if bin and list_arg and out_path then
|
||||
local cmd = string.format("%q -type geosite -append=true -input %q -list %q -output %q -lowmem=true",
|
||||
bin, geosite_path, list_arg, out_path)
|
||||
sys.call(cmd)
|
||||
return 0
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
if not fs.access(FLAG_PATH) then
|
||||
fs.mkdir(FLAG_PATH)
|
||||
end
|
||||
|
||||
local LOCAL_EXTEND_ARG = ""
|
||||
if LOCAL_GROUP == "null" or LOCAL_GROUP == "" then
|
||||
log(" * 注意:SmartDNS 国内分组名未设置,DNS 将无法正常工作!!!")
|
||||
os.exit(1)
|
||||
else
|
||||
--从smartdns配置中读取参数
|
||||
local custom_conf_path = "/etc/smartdns/custom.conf"
|
||||
local options = {
|
||||
{key = "dualstack_ip_selection", config_key = "dualstack-ip-selection", yes_no = true, arg_yes = "-d yes", arg_no = "-d no", default = "yes"},
|
||||
{key = "speed_check_mode", config_key = "speed-check-mode", prefix = "-c ", default = "ping,tcp:80,tcp:443"},
|
||||
{key = "serve_expired", config_key = "serve-expired", yes_no = true, arg_yes = "", arg_no = "-no-serve-expired", default = "yes"},
|
||||
{key = "response_mode", config_key = "response-mode", prefix = "-r ", default = "first-ping"},
|
||||
{key = "rr_ttl", config_key = "rr-ttl", prefix = "-rr-ttl "},
|
||||
{key = "rr_ttl_min", config_key = "rr-ttl-min", prefix = "-rr-ttl-min "},
|
||||
{key = "rr_ttl_max", config_key = "rr-ttl-max", prefix = "-rr-ttl-max "},
|
||||
{key = "rr_ttl_reply_max", config_key = "rr-ttl-reply-max", prefix = "-rr-ttl-reply-max "},
|
||||
{
|
||||
key = "force_aaaa_soa",
|
||||
config_key = "force-qtype-SOA",
|
||||
prefix = "-address ",
|
||||
get_value = function(custom_config)
|
||||
local soa = custom_config["force-qtype-SOA"]
|
||||
return ((soa and soa:match("(^|%s)28(%s|$)"))
|
||||
or custom_config["force-AAAA-SOA"] == "yes"
|
||||
or uci:get("smartdns", "@smartdns[0]", "force_aaaa_soa") == "1")
|
||||
and "#6" or "-6"
|
||||
end
|
||||
}
|
||||
}
|
||||
-- 从 custom.conf 中读取值,以最后出现的值为准
|
||||
local custom_config = {}
|
||||
local f_in = io.open(custom_conf_path, "r")
|
||||
if f_in then
|
||||
for line in f_in:lines() do
|
||||
line = api.trim(line)
|
||||
if line ~= "" and not line:match("^#") then
|
||||
local param, value = line:match("^(%S+)%s+(.+)$")
|
||||
if param and value then custom_config[param] = value end
|
||||
end
|
||||
end
|
||||
f_in:close()
|
||||
end
|
||||
-- 从 smartdns 配置中读取值,优先级以 custom.conf 为准
|
||||
for _, opt in ipairs(options) do
|
||||
local val
|
||||
if opt.get_value then
|
||||
val = opt.get_value(custom_config)
|
||||
else
|
||||
val = custom_config[opt.config_key] or uci:get("smartdns", "@smartdns[0]", opt.key) or opt.default
|
||||
end
|
||||
if val == "yes" then val = "1" elseif val == "no" then val = "0" end
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not REMOTE_GROUP or REMOTE_GROUP == "nil" then
|
||||
REMOTE_GROUP = "passwall_proxy"
|
||||
if REMOTE_DNS then
|
||||
REMOTE_DNS = REMOTE_DNS:gsub("#", ":")
|
||||
end
|
||||
sys.call('sed -i "/passwall/d" /etc/smartdns/custom.conf >/dev/null 2>&1')
|
||||
end
|
||||
|
||||
local force_https_soa = uci:get(appname, "@global[0]", "force_https_soa") or 0
|
||||
local proxy_server_name = "passwall-proxy-server"
|
||||
config_lines = {
|
||||
tonumber(LISTEN_PORT) ~= 0 and "bind [::]:" .. LISTEN_PORT .. "@lo" or "",
|
||||
(tonumber(LOCAL_PORT) ~= 0 and LOCAL_GROUP) and "bind [::]:" .. 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",
|
||||
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 DNS_MODE == "socks" then
|
||||
for w in string.gmatch(REMOTE_DNS, '[^|]+') do
|
||||
local server_dns = api.trim(w)
|
||||
local server_param
|
||||
|
||||
local dnsType = string.match(server_dns, "^(.-)://")
|
||||
dnsType = dnsType and string.lower(dnsType) or nil
|
||||
local dnsServer = string.match(server_dns, "://(.+)") or server_dns
|
||||
|
||||
if dnsType and dnsType ~= "" and dnsType ~= "udp" then
|
||||
if dnsType == "tcp" then
|
||||
server_param = "server-tcp " .. dnsServer
|
||||
elseif dnsType == "tls" then
|
||||
server_param = "server-tls " .. dnsServer
|
||||
elseif dnsType == "quic" then
|
||||
server_param = "server-quic " .. dnsServer
|
||||
elseif dnsType == "https" or dnsType == "h3" then
|
||||
local http_host = nil
|
||||
local url = w
|
||||
local port = 443
|
||||
local s = api.split(w, ",")
|
||||
if s and #s > 1 then
|
||||
url = s[1]
|
||||
local dns_ip = s[2]
|
||||
local host_port = api.get_domain_from_url(s[1])
|
||||
if host_port and #host_port > 0 then
|
||||
http_host = host_port
|
||||
local s2 = api.split(host_port, ":")
|
||||
if s2 and #s2 > 1 then
|
||||
http_host = s2[1]
|
||||
port = s2[2]
|
||||
end
|
||||
url = url:gsub(http_host, dns_ip)
|
||||
end
|
||||
end
|
||||
server_dns = url
|
||||
if http_host then
|
||||
server_dns = server_dns .. " -http-host " .. http_host
|
||||
end
|
||||
server_param = (dnsType == "https" and "server-https " or "server-h3 ") .. server_dns
|
||||
end
|
||||
else
|
||||
server_param = "server " .. dnsServer
|
||||
|
||||
end
|
||||
|
||||
if not api.is_local_ip(w) 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)
|
||||
end
|
||||
if not IS_SHUNT_NODE then REMOTE_FAKEDNS = "0" 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)
|
||||
log(" - " .. DNS_MODE:gsub("^%l",string.upper) .. " " .. TUN_DNS .. " -> " .. REMOTE_GROUP)
|
||||
end
|
||||
|
||||
--设置默认 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
|
||||
end
|
||||
if DEFAULT_DNS_GROUP then
|
||||
local domain_rules_str = "domain-rules /./ -nameserver " .. 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 " .. (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 "")
|
||||
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, 8 do
|
||||
table.insert(config_lines, "#--" .. i)
|
||||
end
|
||||
|
||||
--屏蔽列表
|
||||
local file_block_host = TMP_ACL_PATH .. "/block_host"
|
||||
if USE_BLOCK_LIST == "1" and not fs.access(file_block_host) then
|
||||
local block_domain, lookup_block_domain = {}, {}
|
||||
local geosite_arg = ""
|
||||
for line in io.lines(RULES_PATH .. "/block_host") 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
|
||||
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
|
||||
if USE_GEOVIEW == "1" and geosite_arg ~= "" and api.is_finded("geoview") then
|
||||
if get_geosite(geosite_arg, file_block_host) == 0 then
|
||||
log(" * 解析[屏蔽列表] Geosite 到屏蔽域名表(blocklist)完成")
|
||||
else
|
||||
log(" * 解析[屏蔽列表] Geosite 到屏蔽域名表(blocklist)失败!")
|
||||
end
|
||||
end
|
||||
end
|
||||
if USE_BLOCK_LIST == "1" and is_file_nonzero(file_block_host) then
|
||||
local domain_set_name = "passwall-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, "#--7")
|
||||
end
|
||||
|
||||
--始终用国内DNS解析节点域名
|
||||
local file_vpslist = TMP_ACL_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
|
||||
uci:foreach(appname, "nodes", function(t)
|
||||
process_address(t.address)
|
||||
process_address(t.download_address)
|
||||
end)
|
||||
uci:foreach(appname, "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 domain_set_name = "passwall-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/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
|
||||
domain_rules_str = domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",")
|
||||
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
|
||||
table.insert(tmp_lines, domain_rules_str)
|
||||
insert_array_after(config_lines, tmp_lines, "#--8")
|
||||
log(string.format(" - 节点列表中的域名(vpslist)使用分组:%s", LOCAL_GROUP or "默认"))
|
||||
end
|
||||
|
||||
--直连(白名单)列表
|
||||
local file_direct_host = TMP_ACL_PATH .. "/direct_host"
|
||||
if USE_DIRECT_LIST == "1" and not fs.access(file_direct_host) then
|
||||
local direct_domain, lookup_direct_domain = {}, {}
|
||||
local geosite_arg = ""
|
||||
for line in io.lines(RULES_PATH .. "/direct_host") 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(direct_domain, line, lookup_direct_domain)
|
||||
end
|
||||
end
|
||||
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
|
||||
if USE_GEOVIEW == "1" and geosite_arg ~= "" and api.is_finded("geoview") then
|
||||
if get_geosite(geosite_arg, file_direct_host) == 0 then
|
||||
log(" * 解析[直连列表] Geosite 到域名白名单(whitelist)完成")
|
||||
else
|
||||
log(" * 解析[直连列表] Geosite 到域名白名单(whitelist)失败!")
|
||||
end
|
||||
end
|
||||
end
|
||||
if USE_DIRECT_LIST == "1" and is_file_nonzero(file_direct_host) then
|
||||
local domain_set_name = "passwall-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/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
|
||||
domain_rules_str = domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",")
|
||||
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
|
||||
table.insert(tmp_lines, domain_rules_str)
|
||||
insert_array_after(config_lines, tmp_lines, "#--6")
|
||||
log(string.format(" - 域名白名单(whitelist)使用分组:%s", LOCAL_GROUP or "默认"))
|
||||
end
|
||||
|
||||
--代理(黑名单)列表
|
||||
local file_proxy_host = TMP_ACL_PATH .. "/proxy_host"
|
||||
if USE_PROXY_LIST == "1" and not fs.access(file_proxy_host) then
|
||||
local proxy_domain, lookup_proxy_domain = {}, {}
|
||||
local geosite_arg = ""
|
||||
for line in io.lines(RULES_PATH .. "/proxy_host") 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(proxy_domain, line, lookup_proxy_domain)
|
||||
end
|
||||
end
|
||||
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
|
||||
if USE_GEOVIEW == "1" and geosite_arg ~= "" and api.is_finded("geoview") then
|
||||
if get_geosite(geosite_arg, file_proxy_host) == 0 then
|
||||
log(" * 解析[代理列表] Geosite 到代理域名表(blacklist)完成")
|
||||
else
|
||||
log(" * 解析[代理列表] Geosite 到代理域名表(blacklist)失败!")
|
||||
end
|
||||
end
|
||||
end
|
||||
if USE_PROXY_LIST == "1" and is_file_nonzero(file_proxy_host) then
|
||||
local domain_set_name = "passwall-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 %s', domain_set_name, REMOTE_GROUP)
|
||||
domain_rules_str = domain_rules_str .. " -speed-check-mode none"
|
||||
domain_rules_str = domain_rules_str .. " -no-serve-expired"
|
||||
local sets = {
|
||||
"#4:" .. setflag .. "psw_black"
|
||||
}
|
||||
if NO_PROXY_IPV6 == "1" then
|
||||
domain_rules_str = domain_rules_str .. " -address #6"
|
||||
domain_rules_str = REMOTE_FAKEDNS ~= "1" and (domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",")) or domain_rules_str
|
||||
else
|
||||
domain_rules_str = domain_rules_str .. " -address -6"
|
||||
table.insert(sets, "#6:" .. setflag .. "psw_black6")
|
||||
domain_rules_str = REMOTE_FAKEDNS ~= "1" and (domain_rules_str .. " -d no " .. set_type .. " " .. table.concat(sets, ",")) or domain_rules_str
|
||||
end
|
||||
table.insert(tmp_lines, domain_rules_str)
|
||||
insert_array_after(config_lines, tmp_lines, "#--5")
|
||||
log(string.format(" - 代理域名表(blacklist)使用分组:%s", REMOTE_GROUP or "默认"))
|
||||
end
|
||||
|
||||
--GFW列表
|
||||
if USE_GFW_LIST == "1" and is_file_nonzero(RULES_PATH .. "/gfwlist") then
|
||||
local domain_set_name = "passwall-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 %s', domain_set_name, REMOTE_GROUP)
|
||||
domain_rules_str = domain_rules_str .. " -speed-check-mode none"
|
||||
domain_rules_str = domain_rules_str .. " -no-serve-expired"
|
||||
local sets = {
|
||||
"#4:" .. setflag .. "psw_gfw"
|
||||
}
|
||||
if NO_PROXY_IPV6 == "1" then
|
||||
domain_rules_str = domain_rules_str .. " -address #6"
|
||||
domain_rules_str = REMOTE_FAKEDNS ~= "1" and (domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",")) or domain_rules_str
|
||||
else
|
||||
domain_rules_str = domain_rules_str .. " -address -6"
|
||||
table.insert(sets, "#6:" .. setflag .. "psw_gfw6")
|
||||
domain_rules_str = REMOTE_FAKEDNS ~= "1" and (domain_rules_str .. " -d no " .. set_type .. " " .. table.concat(sets, ",")) or domain_rules_str
|
||||
end
|
||||
table.insert(tmp_lines, domain_rules_str)
|
||||
insert_array_after(config_lines, tmp_lines, "#--1")
|
||||
log(string.format(" - 防火墙域名表(gfwlist)使用分组:%s", REMOTE_GROUP or "默认"))
|
||||
end
|
||||
|
||||
--中国列表
|
||||
if CHN_LIST ~= "0" and is_file_nonzero(RULES_PATH .. "/chnlist") then
|
||||
local domain_set_name = "passwall-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/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
|
||||
domain_rules_str = domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",")
|
||||
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
|
||||
table.insert(tmp_lines, domain_rules_str)
|
||||
insert_array_after(config_lines, tmp_lines, "#--2")
|
||||
log(string.format(" - 中国域名表(chnroute)使用分组:%s", LOCAL_GROUP or "默认"))
|
||||
end
|
||||
|
||||
--回中国模式
|
||||
if CHN_LIST == "proxy" then
|
||||
local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver %s', domain_set_name, REMOTE_GROUP)
|
||||
domain_rules_str = domain_rules_str .. " -speed-check-mode none"
|
||||
domain_rules_str = domain_rules_str .. " -no-serve-expired"
|
||||
local sets = {
|
||||
"#4:" .. setflag .. "psw_chn"
|
||||
}
|
||||
if NO_PROXY_IPV6 == "1" then
|
||||
domain_rules_str = domain_rules_str .. " -address #6"
|
||||
domain_rules_str = REMOTE_FAKEDNS ~= "1" and (domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",")) or domain_rules_str
|
||||
else
|
||||
domain_rules_str = domain_rules_str .. " -address -6"
|
||||
table.insert(sets, "#6:" .. setflag .. "psw_chn6")
|
||||
domain_rules_str = REMOTE_FAKEDNS ~= "1" and (domain_rules_str .. " -d no " .. set_type .. " " .. table.concat(sets, ",")) or domain_rules_str
|
||||
end
|
||||
table.insert(tmp_lines, domain_rules_str)
|
||||
insert_array_after(config_lines, tmp_lines, "#--2")
|
||||
log(string.format(" - 中国域名表(chnroute)使用分组:%s", REMOTE_GROUP or "默认"))
|
||||
end
|
||||
end
|
||||
|
||||
--分流规则
|
||||
if IS_SHUNT_NODE then
|
||||
local white_domain, lookup_white_domain = {}, {}
|
||||
local shunt_domain, lookup_shunt_domain = {}, {}
|
||||
local file_white_host = FLAG_PATH .. "/shunt_direct_host"
|
||||
local file_shunt_host = FLAG_PATH .. "/shunt_proxy_host"
|
||||
local geosite_white_arg, geosite_shunt_arg = "", ""
|
||||
|
||||
local t = uci:get_all(appname, TCP_NODE)
|
||||
local default_node_id = t["default_node"] or "_direct"
|
||||
uci:foreach(appname, "shunt_rules", function(s)
|
||||
local _node_id = t[s[".name"]]
|
||||
if _node_id and _node_id ~= "_blackhole" 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:") then
|
||||
if line:find("geosite:") then
|
||||
line = string.match(line, ":([^:]+)$")
|
||||
if _node_id == "_direct" then
|
||||
geosite_white_arg = geosite_white_arg .. (geosite_white_arg ~= "" and "," or "") .. line
|
||||
else
|
||||
geosite_shunt_arg = geosite_shunt_arg .. (geosite_shunt_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(white_domain, line, lookup_white_domain)
|
||||
else
|
||||
insert_unique(shunt_domain, line, lookup_shunt_domain)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if _node_id ~= "_direct" then
|
||||
log(string.format(" - Sing-Box/Xray分流规则(%s)使用分组:%s", s.remarks, REMOTE_GROUP or "默认"))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
if is_file_nonzero(file_white_host) == nil then
|
||||
if #white_domain > 0 then
|
||||
local f_out = io.open(file_white_host, "w")
|
||||
for i = 1, #white_domain do
|
||||
f_out:write(white_domain[i] .. "\n")
|
||||
end
|
||||
f_out:close()
|
||||
end
|
||||
end
|
||||
|
||||
if is_file_nonzero(file_shunt_host) == nil then
|
||||
if #shunt_domain > 0 then
|
||||
local f_out = io.open(file_shunt_host, "w")
|
||||
for i = 1, #shunt_domain do
|
||||
f_out:write(shunt_domain[i] .. "\n")
|
||||
end
|
||||
f_out:close()
|
||||
end
|
||||
end
|
||||
|
||||
if USE_GFW_LIST == "1" and CHN_LIST == "0" and USE_GEOVIEW == "1" and api.is_finded("geoview") then --仅GFW模式解析geosite
|
||||
local return_white, return_shunt
|
||||
if geosite_white_arg ~= "" then
|
||||
return_white = get_geosite(geosite_white_arg, file_white_host)
|
||||
end
|
||||
if geosite_shunt_arg ~= "" then
|
||||
return_shunt = get_geosite(geosite_shunt_arg, file_shunt_host)
|
||||
end
|
||||
if (return_white == nil or return_white == 0) and (return_shunt == nil or return_shunt == 0) then
|
||||
log(" * 解析[分流节点] Geosite 完成")
|
||||
else
|
||||
log(" * 解析[分流节点] Geosite 失败!")
|
||||
end
|
||||
end
|
||||
|
||||
if is_file_nonzero(file_white_host) then
|
||||
local domain_set_name = "passwall-whitehost"
|
||||
tmp_lines = {
|
||||
string.format("domain-set -name %s -file %s", domain_set_name, file_white_host)
|
||||
}
|
||||
local domain_rules_str = string.format('domain-rules /domain-set:%s/ %s', domain_set_name, LOCAL_GROUP and "-nameserver " .. LOCAL_GROUP or "")
|
||||
if USE_DIRECT_LIST == "1" then
|
||||
local sets = {
|
||||
"#4:" .. setflag .. "psw_white",
|
||||
"#6:" .. setflag .. "psw_white6"
|
||||
}
|
||||
domain_rules_str = domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",")
|
||||
else
|
||||
local sets = {
|
||||
"#4:" .. setflag .. "psw_shunt",
|
||||
"#6:" .. setflag .. "psw_shunt6"
|
||||
}
|
||||
domain_rules_str = domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ",")
|
||||
end
|
||||
domain_rules_str = domain_rules_str .. (LOCAL_EXTEND_ARG ~= "" and " " .. LOCAL_EXTEND_ARG or "")
|
||||
table.insert(tmp_lines, domain_rules_str)
|
||||
insert_array_after(config_lines, tmp_lines, "#--4")
|
||||
end
|
||||
|
||||
if is_file_nonzero(file_shunt_host) then
|
||||
local domain_set_name = "passwall-shuntlist"
|
||||
tmp_lines = {
|
||||
string.format("domain-set -name %s -file %s", domain_set_name, file_shunt_host)
|
||||
}
|
||||
local domain_rules_str = string.format('domain-rules /domain-set:%s/ -nameserver %s', domain_set_name, REMOTE_GROUP)
|
||||
domain_rules_str = domain_rules_str .. " -speed-check-mode none"
|
||||
domain_rules_str = domain_rules_str .. " -no-serve-expired"
|
||||
local sets = {
|
||||
"#4:" .. setflag .. "psw_shunt"
|
||||
}
|
||||
if NO_PROXY_IPV6 == "1" then
|
||||
domain_rules_str = domain_rules_str .. " -address #6"
|
||||
domain_rules_str = (not only_global and REMOTE_FAKEDNS == "1")
|
||||
and domain_rules_str
|
||||
or (domain_rules_str .. " " .. set_type .. " " .. table.concat(sets, ","))
|
||||
else
|
||||
domain_rules_str = domain_rules_str .. " -address -6"
|
||||
table.insert(sets, "#6:" .. setflag .. "psw_shunt6")
|
||||
domain_rules_str = (not only_global and REMOTE_FAKEDNS == "1")
|
||||
and domain_rules_str
|
||||
or (domain_rules_str .. " -d no " .. set_type .. " " .. table.concat(sets, ","))
|
||||
end
|
||||
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))
|
||||
end
|
||||
|
||||
fs.symlink(TMP_CONF_FILE, SMARTDNS_CONF)
|
||||
sys.call(string.format('echo "conf-file %s" >> /etc/smartdns/custom.conf', string.gsub(SMARTDNS_CONF, appname, appname .. "*")))
|
||||
log(" - SmartDNS已作为Dnsmasq上游,如果你自行配置了错误的DNS流程,将会导致域名(直连/代理域名)分流失效!!!")
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,51 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# dhcp.leases to hosts
|
||||
|
||||
. /usr/share/passwall/utils.sh
|
||||
LOCK_FILE=${LOCK_PATH}/${CONFIG}_lease2hosts.lock
|
||||
LEASE_FILE="/tmp/dhcp.leases"
|
||||
HOSTS_FILE="$TMP_PATH2/dhcp-hosts"
|
||||
TMP_FILE="/tmp/dhcp-hosts.tmp"
|
||||
|
||||
exec 99>"$LOCK_FILE"
|
||||
flock -n 99
|
||||
if [ "$?" != 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
reload_dnsmasq_pids() {
|
||||
local pidfile pid
|
||||
find $TMP_PATH/acl -type f -name 'dnsmasq.pid' 2>/dev/null | while read pidfile; do
|
||||
if [ -s "$pidfile" ]; then
|
||||
read pid < "$pidfile"
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
kill -HUP "$pid"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
while true; do
|
||||
|
||||
if [ -f "$LEASE_FILE" ]; then
|
||||
awk 'NF >= 4 && $4 != "*" {print $3" "$4}' "$LEASE_FILE" | sort > "$TMP_FILE"
|
||||
if [ -s "$TMP_FILE" ]; then
|
||||
if [ ! -f "$HOSTS_FILE" ] || ! cmp -s "$TMP_FILE" "$HOSTS_FILE"; then
|
||||
mv "$TMP_FILE" "$HOSTS_FILE"
|
||||
reload_dnsmasq_pids
|
||||
else
|
||||
rm -f "$TMP_FILE"
|
||||
fi
|
||||
else
|
||||
if [ -s "$HOSTS_FILE" ]; then
|
||||
: > "$HOSTS_FILE"
|
||||
reload_dnsmasq_pids
|
||||
fi
|
||||
rm -f "$TMP_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep 60
|
||||
|
||||
done 2>/dev/null
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. $DIR/utils.sh
|
||||
LOCK_FILE=${LOCK_PATH}/${CONFIG}_monitor.lock
|
||||
|
||||
MAX_RESTART_COUNT=10
|
||||
RESTART_STATS_DIR="${TMP_PATH}/script_rstats"
|
||||
mkdir -p "$RESTART_STATS_DIR"
|
||||
|
||||
sleep 58s
|
||||
last_cleanup_date=$(date +%Y%m%d)
|
||||
while [ 1 -eq 1 ]; do
|
||||
[ -f "$LOCK_FILE" ] && {
|
||||
sleep 6s
|
||||
continue
|
||||
}
|
||||
touch $LOCK_FILE
|
||||
|
||||
for file in "$TMP_SCRIPT_FUNC_PATH"/*; do
|
||||
[ -f "$file" ] || continue
|
||||
IFS= read -r cmd < "$file"
|
||||
[ -z "$cmd" ] && continue
|
||||
cmd_check=$(printf '%s' "$cmd" | sed 's/>.*$//;s/[[:space:]]*$//')
|
||||
|
||||
case "$cmd_check" in
|
||||
*dns2socks*) cmd_check=${cmd_check//:/ } ;;
|
||||
esac
|
||||
|
||||
filename=$(basename "$file")
|
||||
stats_file="${RESTART_STATS_DIR}/${filename}.count"
|
||||
if [ -s "$stats_file" ]; then
|
||||
read restart_count < "$stats_file"
|
||||
[ -z "$restart_count" ] && restart_count=0
|
||||
else
|
||||
restart_count=0
|
||||
fi
|
||||
# 检查是否超过最大重启次数
|
||||
[ "$restart_count" -ge "$MAX_RESTART_COUNT" ] && continue
|
||||
|
||||
if ! busybox pgrep -f "$cmd_check" >/dev/null; then
|
||||
restart_count=$((restart_count + 1))
|
||||
echo "$restart_count" > "$stats_file"
|
||||
#echo "${cmd} 进程挂掉,重启" >> /tmp/log/passwall.log
|
||||
sh -c "nohup $cmd 2>&1 &"
|
||||
sleep 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 每天清理一次统计文件(跨天后执行一次)
|
||||
current_date=$(date +%Y%m%d)
|
||||
if [ "$current_date" != "$last_cleanup_date" ]; then
|
||||
rm -f "${RESTART_STATS_DIR:?}"/* 2>/dev/null
|
||||
last_cleanup_date="$current_date"
|
||||
fi
|
||||
|
||||
rm -f $LOCK_FILE
|
||||
sleep 58s
|
||||
done
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,801 +0,0 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
local api = require ("luci.passwall.api")
|
||||
local name = api.appname
|
||||
local uci = api.uci
|
||||
local sys = api.sys
|
||||
local jsonc = api.jsonc
|
||||
local fs = api.fs
|
||||
|
||||
local arg1 = arg[1]
|
||||
local arg2 = arg[2]
|
||||
local arg3 = arg[3]
|
||||
|
||||
local nftable_name = "inet passwall"
|
||||
local rule_path = "/usr/share/" .. name .. "/rules"
|
||||
local reboot = 0
|
||||
local gfwlist_update = "0"
|
||||
local chnroute_update = "0"
|
||||
local chnroute6_update = "0"
|
||||
local chnlist_update = "0"
|
||||
local geoip_update = "0"
|
||||
local geosite_update = "0"
|
||||
|
||||
local excluded_domain = {"apple.com","sina.cn","sina.com.cn","baidu.com","byr.cn","jlike.com","weibo.com","zhongsou.com","youdao.com","sogou.com","so.com","soso.com","aliyun.com","taobao.com","jd.com","qq.com","bing.com"}
|
||||
|
||||
local gfwlist_url = uci:get(name, "@global_rules[0]", "gfwlist_url") or {"https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt"}
|
||||
local chnroute_url = uci:get(name, "@global_rules[0]", "chnroute_url") or {"https://ispip.clang.cn/all_cn.txt"}
|
||||
local chnroute6_url = uci:get(name, "@global_rules[0]", "chnroute6_url") or {"https://ispip.clang.cn/all_cn_ipv6.txt"}
|
||||
local chnlist_url = uci:get(name, "@global_rules[0]", "chnlist_url") or {"https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf","https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf","https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf"}
|
||||
local geoip_url = uci:get(name, "@global_rules[0]", "geoip_url") or "https://github.com/Loyalsoldier/geoip/releases/latest/download/geoip.dat"
|
||||
local geosite_url = uci:get(name, "@global_rules[0]", "geosite_url") or "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||
local asset_location = uci:get(name, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
|
||||
local geo2rule = uci:get(name, "@global_rules[0]", "geo2rule") or "0"
|
||||
local geoip_update_ok, geosite_update_ok = false, false
|
||||
asset_location = asset_location:match("/$") and asset_location or (asset_location .. "/")
|
||||
local backup_path = "/tmp/bak_v2ray/"
|
||||
local rollback = false
|
||||
|
||||
if arg3 == "cron" then
|
||||
arg2 = nil
|
||||
elseif arg3 == "rollback" then
|
||||
rollback, geoip_update_ok, geosite_update_ok = true, true, true
|
||||
end
|
||||
|
||||
local log = function(...)
|
||||
if arg1 then
|
||||
if arg1 == "log" then
|
||||
api.log(...)
|
||||
elseif arg1 == "print" then
|
||||
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
|
||||
print(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--gen cache for nftset from file
|
||||
local function gen_cache(set_name, ip_type, input_file, output_file)
|
||||
local tmp_set_name = set_name .. "_tmp_" .. os.time()
|
||||
local f_in = io.open(input_file, "r")
|
||||
if not f_in then return false end
|
||||
local nft_pipe = io.popen("nft -f -", "w")
|
||||
if not nft_pipe then
|
||||
f_in:close()
|
||||
return false
|
||||
end
|
||||
nft_pipe:write('#!/usr/sbin/nft -f\n')
|
||||
nft_pipe:write(string.format('add table %s\n', nftable_name))
|
||||
nft_pipe:write(string.format('add set %s %s { type %s; flags interval, timeout; timeout 2d; gc-interval 1h; auto-merge; }\n', nftable_name, tmp_set_name, ip_type))
|
||||
nft_pipe:write(string.format('add element %s %s { ', nftable_name, tmp_set_name))
|
||||
local count = 0
|
||||
local batch_size = 500
|
||||
for line in f_in:lines() do
|
||||
local ip = line:match("^%s*(.-)%s*$")
|
||||
if ip and ip ~= "" then
|
||||
nft_pipe:write(ip, "timeout 365d, ")
|
||||
count = count + 1
|
||||
if count % batch_size == 0 then
|
||||
nft_pipe:write("}\n")
|
||||
nft_pipe:write(string.format('add element %s %s { ', nftable_name, tmp_set_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
nft_pipe:write("}\n")
|
||||
f_in:close()
|
||||
|
||||
local success = nft_pipe:close()
|
||||
if not (success == true or success == 0) then
|
||||
os.execute(string.format('nft delete set %s %s 2>/dev/null', nftable_name, tmp_set_name))
|
||||
return false
|
||||
end
|
||||
os.execute(string.format('nft list set %s %s | sed "s/%s/%s/g" > %s', nftable_name, tmp_set_name, tmp_set_name, set_name, output_file))
|
||||
os.execute(string.format('nft delete set %s %s 2>/dev/null', nftable_name, tmp_set_name))
|
||||
end
|
||||
|
||||
-- curl
|
||||
local function curl(url, file)
|
||||
local http_code = 0
|
||||
local header_str = ""
|
||||
local args = {
|
||||
"-fskL",
|
||||
"--retry 3",
|
||||
"--connect-timeout 3",
|
||||
"--max-time 300",
|
||||
"--speed-limit 51200 --speed-time 15",
|
||||
'-A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"',
|
||||
"--dump-header -",
|
||||
"-w '\\n%{http_code}'"
|
||||
}
|
||||
if file then
|
||||
args[#args + 1] = "-o " .. file
|
||||
end
|
||||
local return_code, result = api.curl_auto(url, nil, args)
|
||||
if result and result ~= "" then
|
||||
local body, code = result:match("^(.-)%s*([0-9]+)$")
|
||||
if code then
|
||||
http_code = tonumber(code) or 0
|
||||
header_str = body
|
||||
else
|
||||
http_code = tonumber(result:match("(%d+)%s*$")) or 0
|
||||
end
|
||||
end
|
||||
if header_str ~= "" then
|
||||
header_str = header_str:gsub("\r", "")
|
||||
end
|
||||
return return_code, http_code, header_str
|
||||
end
|
||||
|
||||
--check excluded domain
|
||||
local excluded_map = {}
|
||||
for _, d in ipairs(excluded_domain) do
|
||||
excluded_map[d] = true
|
||||
end
|
||||
local function check_excluded_domain(value)
|
||||
if not value or value == "" then return false end
|
||||
value = value:lower()
|
||||
local eq_pos = value:find("=", 1, true)
|
||||
if eq_pos then
|
||||
value = value:sub(eq_pos + 1)
|
||||
end
|
||||
if value:sub(1,1) == "/" then
|
||||
value = value:sub(2)
|
||||
end
|
||||
local slash_pos = value:find("/", 1, true)
|
||||
local colon_pos = value:find(":", 1, true)
|
||||
local cut_pos
|
||||
if slash_pos and colon_pos then
|
||||
cut_pos = (slash_pos < colon_pos) and slash_pos or colon_pos
|
||||
else
|
||||
cut_pos = slash_pos or colon_pos
|
||||
end
|
||||
if cut_pos then
|
||||
value = value:sub(1, cut_pos - 1)
|
||||
end
|
||||
value = value:gsub("^%.*", ""):gsub("%.*$", "")
|
||||
while value do
|
||||
if excluded_map[value] then
|
||||
return true
|
||||
end
|
||||
local dot_pos = value:find(".", 1, true)
|
||||
if not dot_pos then
|
||||
break
|
||||
end
|
||||
value = value:sub(dot_pos + 1)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- 替代 string.find 查找 "^[#!\\[@]+"
|
||||
local function is_comment_line(s)
|
||||
if not s or s == "" then return false end
|
||||
local b = s:byte(1)
|
||||
-- '#' = 35, '!' = 33, '\' = 92, '[' = 91, '@' = 64
|
||||
if b == 35 or b == 33 or b == 92 or b == 91 or b == 64 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- IPv4 检测,替代 string.find "^%d+%.%d+%.%d+%.%d+"
|
||||
-- IPv4 cidr检测,替代 string.find "^%d+%.%d+%.%d+%.%d+[%/][%d]+$"
|
||||
local function is_ipv4(s, check_cidr)
|
||||
local dot = 0
|
||||
local seg_start = 1
|
||||
local len = #s
|
||||
local mask_start = nil
|
||||
local i = 1
|
||||
while i <= len do
|
||||
local b = s:byte(i)
|
||||
if b >= 48 and b <= 57 then
|
||||
-- 数字,继续
|
||||
elseif b == 46 then -- "."
|
||||
dot = dot + 1
|
||||
if dot > 3 or i == seg_start then return false end
|
||||
local seg = tonumber(s:sub(seg_start, i - 1))
|
||||
if not seg or seg > 255 then return false end
|
||||
seg_start = i + 1
|
||||
elseif b == 47 then -- "/"
|
||||
if not check_cidr then return false end
|
||||
if dot ~= 3 or i == seg_start then return false end
|
||||
local seg = tonumber(s:sub(seg_start, i - 1))
|
||||
if not seg or seg > 255 then return false end
|
||||
mask_start = i + 1
|
||||
break
|
||||
else
|
||||
return false
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
-- 如果没有 CIDR,则检查最后一段即可
|
||||
if not check_cidr or not mask_start then
|
||||
if dot ~= 3 or seg_start > len then return false end
|
||||
local seg = tonumber(s:sub(seg_start))
|
||||
return seg and seg <= 255 or false
|
||||
end
|
||||
-- CIDR 掩码检查
|
||||
if mask_start > len then return false end
|
||||
local mask = tonumber(s:sub(mask_start))
|
||||
return mask and mask >= 0 and mask <= 32 or false
|
||||
end
|
||||
|
||||
local function is_ipv4_cidr(s)
|
||||
return is_ipv4(s, true)
|
||||
end
|
||||
|
||||
local function is_ipv6(s, check_cidr)
|
||||
local first = s:byte(1)
|
||||
local last = s:byte(#s)
|
||||
if first == 91 and last == 93 then -- "[" and "]"
|
||||
s = s:sub(2, -2)
|
||||
end
|
||||
local len = #s
|
||||
local i = 1
|
||||
local seg_len = 0
|
||||
local segs = 0
|
||||
local saw_dc = false -- 是否出现 "::"
|
||||
local b
|
||||
while i <= len do
|
||||
b = s:byte(i)
|
||||
-- CIDR 部分
|
||||
if b == 47 then -- '/'
|
||||
if not check_cidr then
|
||||
return false
|
||||
end
|
||||
-- 处理 "/" 之前的段
|
||||
if seg_len > 0 then segs = segs + 1 end
|
||||
if (not saw_dc and segs ~= 8) or (saw_dc and segs > 8) then return false end
|
||||
-- 解析掩码
|
||||
i = i + 1
|
||||
if i > len then return false end
|
||||
local mask = 0
|
||||
while i <= len do
|
||||
b = s:byte(i)
|
||||
if b < 48 or b > 57 then return false end
|
||||
mask = mask * 10 + (b - 48)
|
||||
if mask > 128 then return false end
|
||||
i = i + 1
|
||||
end
|
||||
-- CIDR 解析成功
|
||||
return true
|
||||
end
|
||||
-- 冒号处理(: 或 ::)
|
||||
if b == 58 then
|
||||
local nextb = (i+1 <= len) and s:byte(i+1) or 0
|
||||
-- "::"
|
||||
if nextb == 58 then
|
||||
if saw_dc then return false end
|
||||
saw_dc = true
|
||||
if seg_len > 0 then segs = segs + 1 end
|
||||
seg_len = 0
|
||||
i = i + 2
|
||||
else
|
||||
-- 普通 ":"
|
||||
if seg_len == 0 then return false end
|
||||
segs = segs + 1
|
||||
seg_len = 0
|
||||
i = i + 1
|
||||
end
|
||||
else
|
||||
-- hex 数字
|
||||
local is_hex =
|
||||
(b >= 48 and b <= 57) or -- 0-9
|
||||
(b >= 65 and b <= 70) or -- A-F
|
||||
(b >= 97 and b <= 102) -- a-f
|
||||
if not is_hex then return false end
|
||||
seg_len = seg_len + 1
|
||||
if seg_len > 4 then return false end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
if seg_len > 0 then segs = segs + 1 end
|
||||
if not saw_dc then return segs == 8 end
|
||||
return segs <= 8
|
||||
end
|
||||
|
||||
-- IPv6 cidr检测,替代 string.find ":-[%x]+%:+[%x]-[%/][%d]+$"
|
||||
local function is_ipv6_cidr(s)
|
||||
return is_ipv6(s, true)
|
||||
end
|
||||
|
||||
-- 检测是否含有冒号,替代 string.find(line, ":")
|
||||
local function has_colon(s)
|
||||
for i = 1, #s do
|
||||
if s:byte(i) == 58 then -- ':'
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- 域名提取,替代 string.match "([%w%-]+%.[%w%.%-]+)[%/%*]*"
|
||||
local function extract_domain(s)
|
||||
if not s or s == "" then return nil end
|
||||
local len = #s
|
||||
local start = nil
|
||||
local last_dot = nil
|
||||
for i = 1, len do
|
||||
local b = s:byte(i)
|
||||
-- 允许的域名字符:a-zA-Z0-9.-
|
||||
if (b >= 48 and b <= 57) or (b >= 65 and b <= 90) or (b >= 97 and b <= 122) or b == 45 or b == 46 then
|
||||
if not start then start = i end
|
||||
if b == 46 then last_dot = i end
|
||||
else
|
||||
if start then
|
||||
if last_dot and last_dot > start then
|
||||
local domain = s:sub(start, i - 1)
|
||||
while domain:byte(1) == 46 do
|
||||
domain = domain:sub(2)
|
||||
end
|
||||
return domain
|
||||
else
|
||||
start = nil
|
||||
last_dot = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if start and last_dot and last_dot > start then
|
||||
local domain = s:sub(start)
|
||||
while domain:byte(1) == 46 do
|
||||
domain = domain:sub(2)
|
||||
end
|
||||
return domain
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function non_file_check(file_path, header_content)
|
||||
local remote_file_size = nil
|
||||
local local_file_size = tonumber(fs.stat(file_path, "size") or 0)
|
||||
if local_file_size == 0 then
|
||||
log("下载文件为空或读取出错。")
|
||||
return true
|
||||
end
|
||||
if header_content and header_content ~= "" then
|
||||
for size in header_content:gmatch("[Cc]ontent%-[Ll]ength:%s*(%d+)") do
|
||||
local s = tonumber(size)
|
||||
if s and s > 0 then
|
||||
remote_file_size = s
|
||||
end
|
||||
end
|
||||
end
|
||||
if remote_file_size and remote_file_size ~= local_file_size then
|
||||
log(string.format("校验出错:远程 %dB, 下载 %dB", remote_file_size, local_file_size))
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function GeoToRule(rule_name, rule_type, out_path)
|
||||
local bin = api.finded_com("geoview")
|
||||
if not (bin and api.compare_versions(api.get_app_version("geoview"), ">=", "0.1.10")) then
|
||||
log("[警告] Geoview 组件缺失或版本过低,规则生成流程已被跳过。")
|
||||
return false
|
||||
end
|
||||
local geosite_path = asset_location .. "geosite.dat"
|
||||
local geoip_path = asset_location .. "geoip.dat"
|
||||
local file_path = (rule_type == "domain") and geosite_path or geoip_path
|
||||
local geo_arg
|
||||
if rule_type == "domain" then
|
||||
if rule_name == "gfwlist" then
|
||||
geo_arg = "-type geosite -list gfw"
|
||||
else
|
||||
geo_arg = "-type geosite -list cn"
|
||||
end
|
||||
elseif rule_type == "ip4" then
|
||||
geo_arg = "-type geoip -list cn -ipv6=false"
|
||||
elseif rule_type == "ip6" then
|
||||
geo_arg = "-type geoip -list cn -ipv4=false"
|
||||
end
|
||||
local cmd = string.format(bin .. " -input '%s' %s -lowmem=true -output '%s'", file_path, geo_arg, out_path)
|
||||
sys.exec(cmd)
|
||||
local local_file_size = tonumber(fs.stat(out_path, "size") or 0)
|
||||
if local_file_size == 0 then
|
||||
os.remove(out_path)
|
||||
log(rule_name .. " 生成失败,请确保 Geo 文件正确且包含目标规则。")
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--fetch rule
|
||||
local function fetch_rule(rule_name, rule_type, url, exclude_domain, max_retries)
|
||||
local sret = 0
|
||||
local max_attempts = max_retries or 2
|
||||
local rule_dataset = {}
|
||||
local file_tmp = "/tmp/" .. rule_name .. "_tmp"
|
||||
local rule_final_path = rule_path .. "/" .. rule_name
|
||||
if geo2rule == "1" then
|
||||
url = {"geo2rule"}
|
||||
log(rule_name.. " 开始生成...")
|
||||
else
|
||||
log(rule_name.. " 开始更新...")
|
||||
end
|
||||
|
||||
for k, v in ipairs(url) do
|
||||
local current_file = "/tmp/" .. rule_name .. "_dl" .. k
|
||||
local success = false
|
||||
|
||||
if v ~= "geo2rule" then
|
||||
for i = 1, max_attempts do
|
||||
local return_code, http_code, header = curl(v, current_file)
|
||||
if return_code == 0 and not non_file_check(current_file, header) then
|
||||
success = true
|
||||
break
|
||||
end
|
||||
os.remove(current_file)
|
||||
log(string.format("%s 第%d条规则下载失败 (HTTP:%s),正在进行第%d次尝试...", rule_name, k, tostring(http_code), i))
|
||||
end
|
||||
else
|
||||
if not GeoToRule(rule_name, rule_type, current_file) then return 1 end
|
||||
success = true
|
||||
end
|
||||
|
||||
if success then
|
||||
local f = io.open(current_file, "r")
|
||||
if f then
|
||||
if rule_name == "gfwlist" and geo2rule == "0" then
|
||||
local decode = api.base64Decode(f:read("*all"))
|
||||
for line in string.gmatch(decode, "[^\r\n]+") do
|
||||
line = line:gsub("full:", "")
|
||||
if not (is_comment_line(line) or is_ipv4(line) or has_colon(line) or (exclude_domain and check_excluded_domain(line))) then
|
||||
local match = extract_domain(line)
|
||||
if match and not is_ipv4(match) then
|
||||
rule_dataset[match] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
for line in f:lines() do
|
||||
if rule_type == "domain" then
|
||||
line = line:gsub("full:", "")
|
||||
if not (is_comment_line(line) or is_ipv4(line) or has_colon(line) or (exclude_domain and check_excluded_domain(line))) then
|
||||
local match = extract_domain(line)
|
||||
if match and not is_ipv4(match) then
|
||||
rule_dataset[match] = true
|
||||
end
|
||||
end
|
||||
elseif rule_type == "ip4" then
|
||||
local function is_0dot(s) -- "^0%..*"
|
||||
return s and s:byte(1)==48 and s:byte(2)==46
|
||||
end
|
||||
if is_ipv4_cidr(line) and not is_0dot(line) then
|
||||
rule_dataset[line] = true
|
||||
end
|
||||
elseif rule_type == "ip6" then
|
||||
local function is_double_colon_cidr(s) -- "^::(/%d+)?$"
|
||||
if not s or s:byte(1)~=58 or s:byte(2)~=58 then return false end
|
||||
local l = #s
|
||||
if l==2 then return true end
|
||||
if l==3 or s:byte(3)~=47 then return false end
|
||||
for i=4,l do
|
||||
local b=s:byte(i)
|
||||
if b<48 or b>57 then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
if is_ipv6_cidr(line) and not is_double_colon_cidr(line) then
|
||||
rule_dataset[line] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
end
|
||||
else
|
||||
sret = 1
|
||||
log(string.format("%s 第%d条规则: %s 下载失败!", rule_name, k, v))
|
||||
end
|
||||
os.remove(current_file)
|
||||
end
|
||||
|
||||
if sret == 0 then
|
||||
local result_list = {}
|
||||
for line, _ in pairs(rule_dataset) do table.insert(result_list, line) end
|
||||
table.sort(result_list)
|
||||
|
||||
local out = io.open(file_tmp, "w")
|
||||
if out then
|
||||
for _, line in ipairs(result_list) do out:write(line .. "\n") end
|
||||
out:close()
|
||||
end
|
||||
|
||||
local old_md5 = sys.exec(string.format("md5sum %s 2>/dev/null | awk '{print $1}'", rule_final_path)):gsub("\n", "")
|
||||
local new_md5 = sys.exec(string.format("md5sum %s 2>/dev/null | awk '{print $1}'", file_tmp)):gsub("\n", "")
|
||||
|
||||
if old_md5 ~= new_md5 then
|
||||
if api.is_finded("fw4") and (rule_type == "ip4" or rule_type == "ip6") then
|
||||
local nft_file = file_tmp .. ".nft"
|
||||
local set_name = "psw_" .. rule_name
|
||||
if rule_name == "chnroute" then set_name = "psw_chn"
|
||||
elseif rule_name == "chnroute6" then set_name = "psw_chn6" end
|
||||
|
||||
local addr_type = (rule_type == "ip4") and "ipv4_addr" or "ipv6_addr"
|
||||
gen_cache(set_name, addr_type, file_tmp, nft_file)
|
||||
os.execute(string.format("mv -f %s %s.nft", nft_file, rule_final_path))
|
||||
end
|
||||
os.execute(string.format("mv -f %s %s", file_tmp, rule_final_path))
|
||||
if not rollback then reboot = 1 end
|
||||
log(string.format("%s 更新成功,总规则数 %d 条。", rule_name, #result_list))
|
||||
else
|
||||
log(rule_name .. " 版本一致,无需更新。")
|
||||
os.remove(file_tmp)
|
||||
end
|
||||
else
|
||||
log(rule_name .. " 更新失败(部分或全部资源无法下载)。")
|
||||
os.remove(file_tmp)
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function fetch_geofile(geo_name, geo_type, url)
|
||||
local tmp_path = "/tmp/" .. geo_name
|
||||
local asset_path = asset_location .. geo_name
|
||||
local down_filename = url:match("^.*/([^/?#]+)")
|
||||
local sha_url = url:gsub(down_filename, down_filename .. ".sha256sum")
|
||||
local sha_path = tmp_path .. ".sha256sum"
|
||||
|
||||
local function verify_sha256(sha_file)
|
||||
return sys.call("sha256sum -c " .. sha_file .. " > /dev/null 2>&1") == 0
|
||||
end
|
||||
|
||||
local sha_verify = select(1, curl(sha_url, sha_path)) == 0
|
||||
if sha_verify then
|
||||
local f = io.open(sha_path, "r")
|
||||
if f then
|
||||
local content = f:read("*l")
|
||||
f:close()
|
||||
if content then
|
||||
content = content:gsub("(%x+)%s+.+", "%1 " .. tmp_path)
|
||||
f = io.open(sha_path, "w")
|
||||
if f then
|
||||
f:write(content)
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
if fs.access(asset_path) then
|
||||
sys.call(string.format("cp -f %s %s", asset_path, tmp_path))
|
||||
if verify_sha256(sha_path) then
|
||||
log(geo_type .. " 版本一致,无需更新。")
|
||||
return 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local sret_tmp, _, header = curl(url, tmp_path)
|
||||
if sret_tmp == 0 and non_file_check(tmp_path, header) then
|
||||
log(geo_type .. " 下载文件过程出错,尝试重新下载。")
|
||||
os.remove(tmp_path)
|
||||
sret_tmp, _, header= curl(url, tmp_path)
|
||||
if sret_tmp == 0 and non_file_check(tmp_path, header) then
|
||||
sret_tmp = 1
|
||||
log(geo_type .. " 下载文件过程出错,请检查网络或下载链接后重试!")
|
||||
end
|
||||
end
|
||||
if sret_tmp == 0 then
|
||||
if sha_verify then
|
||||
if verify_sha256(sha_path) then
|
||||
sys.call(string.format("mkdir -p %s && mv -f %s %s", backup_path, asset_path, backup_path))
|
||||
sys.call(string.format("mkdir -p %s && mv -f %s %s", asset_location, tmp_path, asset_path))
|
||||
reboot = 1
|
||||
log(geo_type .. " 更新成功。")
|
||||
if geo_type == "geoip" then
|
||||
geoip_update_ok = true
|
||||
else
|
||||
geosite_update_ok = true
|
||||
end
|
||||
else
|
||||
log(geo_type .. " 更新失败,请稍后重试或更换更新URL。")
|
||||
return 1
|
||||
end
|
||||
else
|
||||
if fs.access(asset_path) and sys.call(string.format("cmp -s %s %s", tmp_path, asset_path)) == 0 then
|
||||
log(geo_type .. " 版本一致,无需更新。")
|
||||
return 0
|
||||
end
|
||||
sys.call(string.format("mkdir -p %s && mv -f %s %s", backup_path, asset_path, backup_path))
|
||||
sys.call(string.format("mkdir -p %s && mv -f %s %s", asset_location, tmp_path, asset_path))
|
||||
reboot = 1
|
||||
log(geo_type .. " 更新成功。")
|
||||
if geo_type == "geoip" then
|
||||
geoip_update_ok = true
|
||||
else
|
||||
geosite_update_ok = true
|
||||
end
|
||||
end
|
||||
else
|
||||
log(geo_type .. " 更新失败,请稍后重试或更换更新URL。")
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function fetch_gfwlist()
|
||||
fetch_rule("gfwlist","domain",gfwlist_url,true)
|
||||
end
|
||||
|
||||
local function fetch_chnroute()
|
||||
fetch_rule("chnroute","ip4",chnroute_url,false)
|
||||
end
|
||||
|
||||
local function fetch_chnroute6()
|
||||
fetch_rule("chnroute6","ip6",chnroute6_url,false)
|
||||
end
|
||||
|
||||
local function fetch_chnlist()
|
||||
fetch_rule("chnlist","domain",chnlist_url,false)
|
||||
end
|
||||
|
||||
local function fetch_geoip()
|
||||
fetch_geofile("geoip.dat","geoip",geoip_url)
|
||||
end
|
||||
|
||||
local function fetch_geosite()
|
||||
fetch_geofile("geosite.dat","geosite",geosite_url)
|
||||
end
|
||||
|
||||
if arg2 then
|
||||
string.gsub(arg2, '[^' .. "," .. ']+', function(w)
|
||||
if w == "gfwlist" then
|
||||
gfwlist_update = "1"
|
||||
end
|
||||
if w == "chnroute" then
|
||||
chnroute_update = "1"
|
||||
end
|
||||
if w == "chnroute6" then
|
||||
chnroute6_update = "1"
|
||||
end
|
||||
if w == "chnlist" then
|
||||
chnlist_update = "1"
|
||||
end
|
||||
if w == "geoip" then
|
||||
geoip_update = "1"
|
||||
end
|
||||
if w == "geosite" then
|
||||
geosite_update = "1"
|
||||
end
|
||||
end)
|
||||
if rollback then arg2 = nil end
|
||||
else
|
||||
gfwlist_update = uci:get(name, "@global_rules[0]", "gfwlist_update") or "1"
|
||||
chnroute_update = uci:get(name, "@global_rules[0]", "chnroute_update") or "1"
|
||||
chnroute6_update = uci:get(name, "@global_rules[0]", "chnroute6_update") or "1"
|
||||
chnlist_update = uci:get(name, "@global_rules[0]", "chnlist_update") or "1"
|
||||
geoip_update = uci:get(name, "@global_rules[0]", "geoip_update") or "1"
|
||||
geosite_update = uci:get(name, "@global_rules[0]", "geosite_update") or "1"
|
||||
end
|
||||
if geo2rule ~= "1" and gfwlist_update == "0" and chnroute_update == "0" and chnroute6_update == "0" and chnlist_update == "0" and geoip_update == "0" and geosite_update == "0" then
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
local function check_instance(action)
|
||||
local rule_lock = "/var/lock/" .. name .. "_rule_update.lock"
|
||||
local sub_lock = "/var/lock/" .. name .. "_subscribe.lock"
|
||||
|
||||
if action == "start" then
|
||||
math.randomseed(os.time() + math.floor(os.clock() * 1000))
|
||||
api.nixio.nanosleep(0, math.random(100, 1000) * 1000000)
|
||||
if fs.access(rule_lock) then
|
||||
log("有[规则更新]实例正在运行,请稍后再试...\n")
|
||||
os.exit(0)
|
||||
else
|
||||
luci.sys.call("touch " .. rule_lock)
|
||||
end
|
||||
elseif action == "end" then
|
||||
luci.sys.call("rm -f " .. rule_lock)
|
||||
return
|
||||
end
|
||||
|
||||
if fs.access(sub_lock) then
|
||||
log("[订阅]实例正在运行,[规则更新]进入队列等待...\n")
|
||||
end
|
||||
while fs.access(sub_lock) do
|
||||
api.nixio.nanosleep(2, 0)
|
||||
end
|
||||
end
|
||||
|
||||
check_instance("start")
|
||||
|
||||
log("开始更新规则...")
|
||||
local function safe_call(func, err_msg)
|
||||
xpcall(func, function(e)
|
||||
log(e)
|
||||
log(debug.traceback())
|
||||
log(err_msg)
|
||||
end)
|
||||
end
|
||||
|
||||
local function remove_tmp_geofile(name)
|
||||
os.remove("/tmp/" .. name .. ".dat")
|
||||
os.remove("/tmp/" .. name .. ".dat.sha256sum")
|
||||
end
|
||||
|
||||
if geo2rule == "1" then
|
||||
if geoip_update == "1" and not rollback then
|
||||
log("geoip 开始更新...")
|
||||
safe_call(fetch_geoip, "更新geoip发生错误...")
|
||||
remove_tmp_geofile("geoip")
|
||||
end
|
||||
|
||||
if geosite_update == "1" and not rollback then
|
||||
log("geosite 开始更新...")
|
||||
safe_call(fetch_geosite, "更新geosite发生错误...")
|
||||
remove_tmp_geofile("geosite")
|
||||
end
|
||||
|
||||
-- 如果是手动更新(arg2存在)始终生成规则
|
||||
if arg2 then geoip_update_ok, geosite_update_ok = true, true end
|
||||
chnroute_update, chnroute6_update, gfwlist_update, chnlist_update = "1", "1", "1", "1"
|
||||
|
||||
if geoip_update_ok then
|
||||
if fs.access(asset_location .. "geoip.dat") then
|
||||
safe_call(fetch_chnroute, "生成chnroute发生错误...")
|
||||
safe_call(fetch_chnroute6, "生成chnroute6发生错误...")
|
||||
else
|
||||
log("geoip.dat 文件不存在,跳过规则生成。")
|
||||
end
|
||||
end
|
||||
|
||||
if geosite_update_ok then
|
||||
if fs.access(asset_location .. "geosite.dat") then
|
||||
safe_call(fetch_gfwlist, "生成gfwlist发生错误...")
|
||||
safe_call(fetch_chnlist, "生成chnlist发生错误...")
|
||||
else
|
||||
log("geosite.dat 文件不存在,跳过规则生成。")
|
||||
end
|
||||
end
|
||||
else
|
||||
if gfwlist_update == "1" then
|
||||
safe_call(fetch_gfwlist, "更新gfwlist发生错误...")
|
||||
end
|
||||
|
||||
if chnroute_update == "1" then
|
||||
safe_call(fetch_chnroute, "更新chnroute发生错误...")
|
||||
end
|
||||
|
||||
if chnroute6_update == "1" then
|
||||
safe_call(fetch_chnroute6, "更新chnroute6发生错误...")
|
||||
end
|
||||
|
||||
if chnlist_update == "1" then
|
||||
safe_call(fetch_chnlist, "更新chnlist发生错误...")
|
||||
end
|
||||
|
||||
if geoip_update == "1" then
|
||||
log("geoip 开始更新...")
|
||||
safe_call(fetch_geoip, "更新geoip发生错误...")
|
||||
remove_tmp_geofile("geoip")
|
||||
end
|
||||
|
||||
if geosite_update == "1" then
|
||||
log("geosite 开始更新...")
|
||||
safe_call(fetch_geosite, "更新geosite发生错误...")
|
||||
remove_tmp_geofile("geosite")
|
||||
end
|
||||
end
|
||||
|
||||
if not rollback then
|
||||
uci:set(name, "@global_rules[0]", "gfwlist_update", gfwlist_update)
|
||||
uci:set(name, "@global_rules[0]", "chnroute_update", chnroute_update)
|
||||
uci:set(name, "@global_rules[0]", "chnroute6_update", chnroute6_update)
|
||||
uci:set(name, "@global_rules[0]", "chnlist_update", chnlist_update)
|
||||
uci:set(name, "@global_rules[0]", "geoip_update", geoip_update)
|
||||
uci:set(name, "@global_rules[0]", "geosite_update", geosite_update)
|
||||
api.uci_save(uci, name, true)
|
||||
end
|
||||
|
||||
if reboot == 1 then
|
||||
if arg3 == "cron" then
|
||||
if not fs.access("/var/lock/" .. name .. ".lock") then
|
||||
sys.call("touch /tmp/lock/" .. name .. "_cron.lock")
|
||||
end
|
||||
end
|
||||
|
||||
log("重启服务,应用新的规则。")
|
||||
uci:set(name, "@global[0]", "flush_set", "1")
|
||||
api.uci_save(uci, name, true, true)
|
||||
end
|
||||
log("规则更新完毕...\n")
|
||||
|
||||
check_instance("end")
|
||||
Whitespace-only changes.
Whitespace-only changes.
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,27 +0,0 @@
|
||||
cn
|
||||
apple.com
|
||||
microsoft.com
|
||||
dyndns.com
|
||||
douyucdn.cn
|
||||
douyucdn2.cn
|
||||
location.services.mozilla.com
|
||||
|
||||
#steam
|
||||
steamcontent.com
|
||||
dl.steam.clngaa.com
|
||||
dl.steam.ksyna.com
|
||||
st.dl.bscstorage.net
|
||||
st.dl.eccdnx.com
|
||||
st.dl.pinyuncloud.com
|
||||
cdn.mileweb.cs.steampowered.com.8686c.com
|
||||
cdn-ws.content.steamchina.com
|
||||
cdn-qc.content.steamchina.com
|
||||
cdn-ali.content.steamchina.com
|
||||
epicgames-download1-1251447533.file.myqcloud.com
|
||||
|
||||
#DoT/DoH
|
||||
dns.alidns.com
|
||||
doh.pub
|
||||
dot.pub
|
||||
doh.360.cn
|
||||
dot.360.cn
|
||||
@@ -1,10 +0,0 @@
|
||||
114.114.114.114
|
||||
114.114.115.115
|
||||
223.5.5.5
|
||||
223.6.6.6
|
||||
119.29.29.29
|
||||
180.76.76.76
|
||||
1.12.12.12
|
||||
120.53.53.53
|
||||
180.184.1.1
|
||||
180.184.2.2
|
||||
@@ -1,26 +0,0 @@
|
||||
courier.push.apple.com
|
||||
rbsxbxp-mim.vivox.com
|
||||
rbsxbxp.www.vivox.com
|
||||
rbsxbxp-ws.vivox.com
|
||||
rbspsxp.www.vivox.com
|
||||
rbspsxp-mim.vivox.com
|
||||
rbspsxp-ws.vivox.com
|
||||
rbswxp.www.vivox.com
|
||||
rbswxp-mim.vivox.com
|
||||
disp-rbspsp-5-1.vivox.com
|
||||
disp-rbsxbp-5-1.vivox.com
|
||||
proxy.rbsxbp.vivox.com
|
||||
proxy.rbspsp.vivox.com
|
||||
proxy.rbswp.vivox.com
|
||||
rbswp.vivox.com
|
||||
rbsxbp.vivox.com
|
||||
rbspsp.vivox.com
|
||||
rbspsp.www.vivox.com
|
||||
rbswp.www.vivox.com
|
||||
rbsxbp.www.vivox.com
|
||||
rbsxbxp.vivox.com
|
||||
rbspsxp.vivox.com
|
||||
rbswxp.vivox.com
|
||||
Mijia Cloud
|
||||
dlg.io.mi.com
|
||||
marscdn.c2c.wechat.com
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,16 +0,0 @@
|
||||
0.0.0.0/8
|
||||
10.0.0.0/8
|
||||
100.64.0.0/10
|
||||
127.0.0.0/8
|
||||
169.254.0.0/16
|
||||
172.16.0.0/12
|
||||
192.0.0.0/24
|
||||
192.0.2.0/24
|
||||
192.88.99.0/24
|
||||
192.168.0.0/16
|
||||
198.51.100.0/24
|
||||
203.0.113.0/24
|
||||
224.0.0.0/4
|
||||
233.252.0.0/24
|
||||
240.0.0.0/4
|
||||
255.255.255.255/32
|
||||
@@ -1,16 +0,0 @@
|
||||
::/128
|
||||
::1/128
|
||||
::ffff:0:0/96
|
||||
::ffff:0:0:0/96
|
||||
64:ff9b::/96
|
||||
64:ff9b:1::/48
|
||||
100::/64
|
||||
2001::/32
|
||||
2001:20::/28
|
||||
2001:db8::/32
|
||||
2002::/16
|
||||
3fff::/20
|
||||
5f00::/16
|
||||
fc00::/7
|
||||
fe80::/64
|
||||
ff00::/8
|
||||
@@ -1,13 +0,0 @@
|
||||
engage.cloudflareclient.com
|
||||
github.com
|
||||
bing.com
|
||||
c.mi.com
|
||||
apple-relay.apple.com
|
||||
|
||||
#google
|
||||
googleapis.cn
|
||||
googleapis.com
|
||||
google.com.tw
|
||||
google.com.hk
|
||||
gstatic.com
|
||||
xn--ngstr-lra8j.com
|
||||
@@ -1,21 +0,0 @@
|
||||
149.154.160.0/20
|
||||
91.108.4.0/22
|
||||
91.108.56.0/24
|
||||
109.239.140.0/24
|
||||
67.198.55.0/24
|
||||
8.8.4.4
|
||||
8.8.8.8
|
||||
208.67.222.222
|
||||
208.67.220.220
|
||||
104.16.249.249
|
||||
104.16.248.249
|
||||
1.1.1.1
|
||||
1.1.1.2
|
||||
1.0.0.1
|
||||
9.9.9.9
|
||||
149.112.112.112
|
||||
2001:67c:4e8::/48
|
||||
2001:b28:f23c::/48
|
||||
2001:b28:f23d::/48
|
||||
2001:b28:f23f::/48
|
||||
2001:b28:f242::/48
|
||||
@@ -1,190 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /usr/share/passwall/utils.sh
|
||||
APP_FILE=${APP_PATH}/app.sh
|
||||
|
||||
flag=0
|
||||
|
||||
check_process() {
|
||||
while busybox pgrep -af "${CONFIG}/" | grep -E 'app\.sh.*(start|stop)|nftables\.sh|iptables\.sh|subscribe\.lua' >/dev/null; do
|
||||
sleep 6s
|
||||
done
|
||||
}
|
||||
|
||||
test_url() {
|
||||
local url=$1
|
||||
local try=1
|
||||
[ -n "$2" ] && try=$2
|
||||
local timeout=2
|
||||
[ -n "$3" ] && timeout=$3
|
||||
local extra_params=$4
|
||||
if /usr/bin/curl --help all | grep -q "\-\-retry-all-errors"; then
|
||||
extra_params="--retry-all-errors ${extra_params}"
|
||||
fi
|
||||
local status=$(/usr/bin/curl -I -o /dev/null -skL ${extra_params} --connect-timeout ${timeout} --retry ${try} -w %{http_code} "$url")
|
||||
case "$status" in
|
||||
204)
|
||||
status=200
|
||||
;;
|
||||
esac
|
||||
echo $status
|
||||
}
|
||||
|
||||
test_proxy() {
|
||||
local result=0
|
||||
local status=$(test_url "${probe_url}" ${retry_num} ${connect_timeout} "-x socks5h://127.0.0.1:${socks_port}")
|
||||
if [ "$status" = "200" ]; then
|
||||
result=0
|
||||
else
|
||||
local status2=$(test_url "https://www.baidu.com" ${retry_num} ${connect_timeout})
|
||||
if [ "$status2" = "200" ]; then
|
||||
result=1
|
||||
else
|
||||
result=2
|
||||
ping -c 3 -W 1 223.5.5.5 > /dev/null 2>&1
|
||||
[ $? -eq 0 ] && {
|
||||
result=1
|
||||
}
|
||||
fi
|
||||
fi
|
||||
echo $result
|
||||
}
|
||||
|
||||
test_node() {
|
||||
local node_id=$1
|
||||
local _type=$(echo $(config_n_get ${node_id} type) | tr 'A-Z' 'a-z')
|
||||
[ -n "${_type}" ] && {
|
||||
check_process
|
||||
local _tmp_port=$(get_new_port 48800 tcp,udp)
|
||||
NO_REC_PROCESS=1 $APP_FILE run_socks flag="test_node_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=test_node_${node_id}.json
|
||||
sleep 2s
|
||||
local curlx="socks5h://127.0.0.1:${_tmp_port}"
|
||||
local _proxy_status=$(test_url "${probe_url}" ${retry_num} ${connect_timeout} "-x $curlx")
|
||||
# 结束 SS 插件进程
|
||||
local pid_file="/tmp/etc/${CONFIG}/test_node_${node_id}_plugin.pid"
|
||||
[ -s "$pid_file" ] && kill -9 "$(head -n 1 "$pid_file")" >/dev/null 2>&1
|
||||
busybox pgrep -af "test_node_${node_id}" | awk '! /socks_auto_switch\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||
rm -rf /tmp/etc/${CONFIG}/test_node_${node_id}*.*
|
||||
if [ "${_proxy_status}" -eq 200 ]; then
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
test_auto_switch() {
|
||||
flag=$((flag + 1))
|
||||
local b_nodes=$1
|
||||
local now_node=$2
|
||||
[ -z "$now_node" ] && {
|
||||
if [ -n "$(get_cache_var "socks_${id}")" ]; then
|
||||
now_node=$(get_cache_var "socks_${id}")
|
||||
else
|
||||
#echolog "Socks切换检测:未知错误"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
[ $flag -le 1 ] && {
|
||||
main_node=$now_node
|
||||
}
|
||||
|
||||
local status=$(test_proxy)
|
||||
if [ "$status" = "2" ]; then
|
||||
echolog "Socks切换检测:无法连接到网络,请检查网络是否正常!"
|
||||
return 2
|
||||
fi
|
||||
|
||||
#检测主节点是否能使用
|
||||
if [ "$restore_switch" = "1" ] && [ -n "$main_node" ] && [ "$now_node" != "$main_node" ]; then
|
||||
test_node ${main_node}
|
||||
[ $? -eq 0 ] && {
|
||||
check_process
|
||||
#主节点正常,切换到主节点
|
||||
echolog "Socks切换检测:端口[${socks_port}] 主节点【$(config_n_get $main_node type):[$(config_n_get $main_node remarks)]】正常,切换到主节点!"
|
||||
NO_REC_PROCESS=1 $APP_FILE socks_node_switch flag=${id} new_node=${main_node}
|
||||
[ $? -eq 0 ] && {
|
||||
echolog "Socks切换检测:端口[${socks_port}] 节点切换完毕!"
|
||||
}
|
||||
return 0
|
||||
}
|
||||
fi
|
||||
|
||||
if [ "$status" = "0" ]; then
|
||||
#echolog "Socks切换检测:${id}【$(config_n_get $now_node type):[$(config_n_get $now_node remarks)]】正常。"
|
||||
return 0
|
||||
elif [ "$status" = "1" ]; then
|
||||
local new_node msg
|
||||
if [ "$backup_node_num" -gt 1 ]; then
|
||||
# 有多个后备节点时
|
||||
local first_node found node
|
||||
for node in $b_nodes; do
|
||||
[ -z "$first_node" ] && first_node="$node" # 记录第一个节点
|
||||
[ "$found" = "1" ] && { new_node="$node"; break; } # 找到当前节点后取下一个
|
||||
[ "$node" = "$now_node" ] && found=1 # 标记找到当前节点
|
||||
done
|
||||
# 如果没找到当前节点,或者当前节点是最后一个,就取第一个节点
|
||||
[ -z "$new_node" ] && new_node="$first_node"
|
||||
msg="切换到$([ "$now_node" = "$main_node" ] && echo 备用节点 || echo 下一个备用节点)检测!"
|
||||
else
|
||||
# 只有一个后备节点时,与主节点轮询
|
||||
new_node=$([ "$now_node" = "$main_node" ] && echo "$b_nodes" || echo "$main_node")
|
||||
msg="切换到$([ "$now_node" = "$main_node" ] && echo 备用节点 || echo 主节点)检测!"
|
||||
fi
|
||||
echolog "Socks切换检测:端口[${socks_port}]【$(config_n_get $now_node type):[$(config_n_get $now_node remarks)]】异常,$msg"
|
||||
test_node ${new_node}
|
||||
if [ $? -eq 0 ]; then
|
||||
# [ "$restore_switch" = "0" ] && {
|
||||
# uci set $CONFIG.${id}.node=$new_node
|
||||
# [ -z "$(echo $b_nodes | grep $main_node)" ] && uci add_list $CONFIG.${id}.autoswitch_backup_node=$main_node
|
||||
# uci commit $CONFIG
|
||||
# }
|
||||
check_process
|
||||
echolog "Socks切换检测:端口[${socks_port}]【$(config_n_get $new_node type):[$(config_n_get $new_node remarks)]】正常,切换到此节点!"
|
||||
NO_REC_PROCESS=1 $APP_FILE socks_node_switch flag=${id} new_node=${new_node}
|
||||
[ $? -eq 0 ] && {
|
||||
echolog "Socks切换检测:端口[${socks_port}] 节点切换完毕!"
|
||||
}
|
||||
return 0
|
||||
else
|
||||
test_auto_switch "${b_nodes}" ${new_node}
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
start() {
|
||||
id=$1
|
||||
LOCK_FILE=${LOCK_PATH}/${CONFIG}_socks_auto_switch_${id}.lock
|
||||
LOG_EVENT_FILTER=$(uci -q get "${CONFIG}.global[0].log_event_filter" 2>/dev/null)
|
||||
LOG_EVENT_CMD=$(uci -q get "${CONFIG}.global[0].log_event_cmd" 2>/dev/null)
|
||||
main_node=$(config_n_get $id node)
|
||||
socks_port=$(config_n_get $id port 0)
|
||||
delay=$(config_n_get $id autoswitch_testing_time 30)
|
||||
connect_timeout=$(config_n_get $id autoswitch_connect_timeout 3)
|
||||
retry_num=$(config_n_get $id autoswitch_retry_num 1)
|
||||
restore_switch=$(config_n_get $id autoswitch_restore_switch 0)
|
||||
probe_url=$(config_n_get $id autoswitch_probe_url "https://www.google.com/generate_204")
|
||||
backup_node=$(config_n_get $id autoswitch_backup_node)
|
||||
if [ -n "$backup_node" ]; then
|
||||
backup_node=$(echo "$backup_node" | tr -s ' ' '\n' | uniq | tr -s '\n' ' ')
|
||||
backup_node_num=$(printf "%s\n" "$backup_node" | wc -w)
|
||||
if [ "$backup_node_num" -eq 1 ]; then
|
||||
[ "$main_node" = "$backup_node" ] && return
|
||||
fi
|
||||
else
|
||||
return
|
||||
fi
|
||||
while [ -n "$backup_node" ]; do
|
||||
[ -f "$LOCK_FILE" ] && {
|
||||
sleep 6s
|
||||
continue
|
||||
}
|
||||
check_process
|
||||
touch $LOCK_FILE
|
||||
test_auto_switch "$backup_node"
|
||||
rm -f $LOCK_FILE
|
||||
sleep ${delay}
|
||||
done
|
||||
}
|
||||
|
||||
start $@
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,90 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
## 循环更新脚本
|
||||
|
||||
. /usr/share/passwall/utils.sh
|
||||
LOCK_FILE=${LOCK_PATH}/${CONFIG}_tasks.lock
|
||||
|
||||
CFG_UPDATE_INT=0
|
||||
|
||||
exec 99>"$LOCK_FILE"
|
||||
flock -n 99
|
||||
if [ "$?" != 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
while true
|
||||
do
|
||||
|
||||
if [ "$CFG_UPDATE_INT" -ne 0 ]; then
|
||||
|
||||
stop_week_mode=$(config_t_get global_delay stop_week_mode)
|
||||
stop_interval_mode=$(config_t_get global_delay stop_interval_mode)
|
||||
stop_interval_mode=$(expr "$stop_interval_mode" \* 60)
|
||||
if [ -n "$stop_week_mode" ]; then
|
||||
[ "$stop_week_mode" = "8" ] && {
|
||||
[ "$(expr "$CFG_UPDATE_INT" % "$stop_interval_mode")" -eq 0 ] && { /etc/init.d/$CONFIG stop > /dev/null 2>&1 & }
|
||||
}
|
||||
fi
|
||||
|
||||
start_week_mode=$(config_t_get global_delay start_week_mode)
|
||||
start_interval_mode=$(config_t_get global_delay start_interval_mode)
|
||||
start_interval_mode=$(expr "$start_interval_mode" \* 60)
|
||||
if [ -n "$start_week_mode" ]; then
|
||||
[ "$start_week_mode" = "8" ] && {
|
||||
[ "$(expr "$CFG_UPDATE_INT" % "$start_interval_mode")" -eq 0 ] && { /etc/init.d/$CONFIG start > /dev/null 2>&1 & }
|
||||
}
|
||||
fi
|
||||
|
||||
restart_week_mode=$(config_t_get global_delay restart_week_mode)
|
||||
restart_interval_mode=$(config_t_get global_delay restart_interval_mode)
|
||||
restart_interval_mode=$(expr "$restart_interval_mode" \* 60)
|
||||
if [ -n "$restart_week_mode" ]; then
|
||||
[ "$restart_week_mode" = "8" ] && {
|
||||
[ "$(expr "$CFG_UPDATE_INT" % "$restart_interval_mode")" -eq 0 ] && { /etc/init.d/$CONFIG restart > /dev/null 2>&1 & }
|
||||
}
|
||||
fi
|
||||
|
||||
autoupdate=$(config_t_get global_rules auto_update)
|
||||
weekupdate=$(config_t_get global_rules week_update)
|
||||
hourupdate=$(config_t_get global_rules interval_update)
|
||||
hourupdate=$(expr "$hourupdate" \* 60)
|
||||
if [ "$autoupdate" = "1" ]; then
|
||||
[ "$weekupdate" = "8" ] && {
|
||||
[ "$(expr "$CFG_UPDATE_INT" % "$hourupdate")" -eq 0 ] && { lua $APP_PATH/rule_update.lua log all cron > /dev/null 2>&1 & }
|
||||
}
|
||||
fi
|
||||
|
||||
TMP_SUB_PATH=$TMP_PATH/sub_tasks
|
||||
mkdir -p $TMP_SUB_PATH
|
||||
for item in $(uci show ${CONFIG} | grep "=subscribe_list" | cut -d '.' -sf 2 | cut -d '=' -sf 1); do
|
||||
if [ "$(config_n_get $item auto_update 0)" = "1" ]; then
|
||||
cfgid=$(uci show ${CONFIG}.$item | head -n 1 | cut -d '.' -sf 2 | cut -d '=' -sf 1)
|
||||
remark=$(config_n_get $item remark)
|
||||
week_update=$(config_n_get $item week_update)
|
||||
hour_update=$(config_n_get $item interval_update)
|
||||
echo "$cfgid" >> $TMP_SUB_PATH/${week_update}_${hour_update}
|
||||
fi
|
||||
done
|
||||
|
||||
[ -d "${TMP_SUB_PATH}" ] && {
|
||||
for name in $(ls ${TMP_SUB_PATH}); do
|
||||
week_update=$(echo $name | awk -F '_' '{print $1}')
|
||||
hour_update=$(echo $name | awk -F '_' '{print $2}')
|
||||
hour_update=$(expr "$hour_update" \* 60)
|
||||
cfgids=$(echo -n $(cat ${TMP_SUB_PATH}/${name}) | sed 's# #,#g')
|
||||
[ "$week_update" = "8" ] && {
|
||||
[ "$(expr "$CFG_UPDATE_INT" % "$hour_update")" -eq 0 ] && { lua $APP_PATH/subscribe.lua start $cfgids cron > /dev/null 2>&1 & }
|
||||
}
|
||||
|
||||
done
|
||||
rm -rf $TMP_SUB_PATH
|
||||
}
|
||||
|
||||
fi
|
||||
|
||||
CFG_UPDATE_INT=$(expr "$CFG_UPDATE_INT" + 10)
|
||||
|
||||
sleep 600
|
||||
|
||||
done 2>/dev/null
|
||||
@@ -1,84 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /usr/share/passwall/utils.sh
|
||||
|
||||
test_url() {
|
||||
local url=$1
|
||||
local try=1
|
||||
[ -n "$2" ] && try=$2
|
||||
local timeout=2
|
||||
[ -n "$3" ] && timeout=$3
|
||||
local extra_params=$4
|
||||
curl --help all | grep "\-\-retry-all-errors" > /dev/null
|
||||
[ $? == 0 ] && extra_params="--retry-all-errors ${extra_params}"
|
||||
status=$(/usr/bin/curl -I -o /dev/null -skL $extra_params --connect-timeout ${timeout} --retry ${try} -w %{http_code} "$url")
|
||||
case "$status" in
|
||||
204|\
|
||||
200)
|
||||
status=200
|
||||
;;
|
||||
esac
|
||||
echo $status
|
||||
}
|
||||
|
||||
test_proxy() {
|
||||
result=0
|
||||
status=$(test_url "https://www.google.com/generate_204" ${retry_num} ${connect_timeout})
|
||||
if [ "$status" = "200" ]; then
|
||||
result=0
|
||||
else
|
||||
status2=$(test_url "https://www.baidu.com" ${retry_num} ${connect_timeout})
|
||||
if [ "$status2" = "200" ]; then
|
||||
result=1
|
||||
else
|
||||
result=2
|
||||
ping -c 3 -W 1 223.5.5.5 > /dev/null 2>&1
|
||||
[ $? -eq 0 ] && {
|
||||
result=1
|
||||
}
|
||||
fi
|
||||
fi
|
||||
echo $result
|
||||
}
|
||||
|
||||
url_test_node() {
|
||||
result=0
|
||||
local node_id=$1
|
||||
local _type=$(echo $(config_n_get ${node_id} type) | tr 'A-Z' 'a-z')
|
||||
[ -n "${_type}" ] && {
|
||||
if [ "${_type}" == "socks" ]; then
|
||||
local _address=$(config_n_get ${node_id} address)
|
||||
local _port=$(config_n_get ${node_id} port)
|
||||
[ -n "${_address}" ] && [ -n "${_port}" ] && {
|
||||
local curlx="socks5h://${_address}:${_port}"
|
||||
local _username=$(config_n_get ${node_id} username)
|
||||
local _password=$(config_n_get ${node_id} password)
|
||||
[ -n "${_username}" ] && [ -n "${_password}" ] && curlx="socks5h://${_username}:${_password}@${_address}:${_port}"
|
||||
}
|
||||
else
|
||||
local _tmp_port=$(get_new_port 48900 tcp,udp)
|
||||
NO_REC_PROCESS=1 /usr/share/${CONFIG}/app.sh run_socks flag="url_test_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=url_test_${node_id}.json
|
||||
local curlx="socks5h://127.0.0.1:${_tmp_port}"
|
||||
fi
|
||||
sleep 2s
|
||||
local probeUrl=$(config_t_get global_other url_test_url https://www.google.com/generate_204)
|
||||
result=$(curl --connect-timeout 3 --max-time 5 -o /dev/null -I -skL -w "%{http_code}:%{time_pretransfer}" -x ${curlx} "${probeUrl}")
|
||||
# 结束 SS 插件进程
|
||||
local pid_file="/tmp/etc/${CONFIG}/url_test_${node_id}_plugin.pid"
|
||||
[ -s "$pid_file" ] && kill -9 "$(head -n 1 "$pid_file")" >/dev/null 2>&1
|
||||
busybox pgrep -af "url_test_${node_id}" | awk '! /test\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||
rm -rf /tmp/etc/${CONFIG}/*url_test_${node_id}*.*
|
||||
}
|
||||
echo $result
|
||||
}
|
||||
|
||||
arg1=$1
|
||||
shift
|
||||
case $arg1 in
|
||||
test_url)
|
||||
test_url $@
|
||||
;;
|
||||
url_test_node)
|
||||
url_test_node $@
|
||||
;;
|
||||
esac
|
||||
@@ -1,551 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Copyright (C) 2022-2025 xiaorouji
|
||||
# Copyright (C) 2026 Openwrt-Passwall Organization
|
||||
|
||||
CONFIG=passwall
|
||||
APP_PATH=/usr/share/${CONFIG}
|
||||
TMP_PATH=/tmp/etc/${CONFIG}
|
||||
TMP_PATH2=${TMP_PATH}_tmp
|
||||
LOCK_PATH=/tmp/lock
|
||||
LOG_FILE=/tmp/log/${CONFIG}.log
|
||||
TMP_ACL_PATH=${TMP_PATH}/acl
|
||||
TMP_BIN_PATH=${TMP_PATH}/bin
|
||||
TMP_IFACE_PATH=${TMP_PATH}/iface
|
||||
TMP_ROUTE_PATH=${TMP_PATH}/route
|
||||
TMP_SCRIPT_FUNC_PATH=${TMP_PATH}/script_func
|
||||
RULES_PATH=/usr/share/${CONFIG}/rules
|
||||
|
||||
. /lib/functions/network.sh
|
||||
|
||||
echolog() {
|
||||
local d="$(date "+%Y-%m-%d %H:%M:%S")"
|
||||
echo -e "$d: $*" >>$LOG_FILE
|
||||
}
|
||||
|
||||
clean_log() {
|
||||
logsnum=$(cat $LOG_FILE 2>/dev/null | wc -l)
|
||||
[ "$logsnum" -gt 1000 ] && {
|
||||
echo "" > $LOG_FILE
|
||||
echolog "日志文件过长,清空处理!"
|
||||
}
|
||||
}
|
||||
|
||||
config_get_type() {
|
||||
local ret=$(uci -q get "${CONFIG}.${1}" 2>/dev/null)
|
||||
echo "${ret:=$2}"
|
||||
}
|
||||
|
||||
config_n_get() {
|
||||
local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null)
|
||||
echo "${ret:=$3}"
|
||||
}
|
||||
|
||||
config_t_get() {
|
||||
local index=${4:-0}
|
||||
local ret=$(uci -q get "${CONFIG}.@${1}[${index}].${2}" 2>/dev/null)
|
||||
echo "${ret:=${3}}"
|
||||
}
|
||||
|
||||
config_t_set() {
|
||||
local index=${4:-0}
|
||||
local ret=$(uci -q set "${CONFIG}.@${1}[${index}].${2}=${3}" 2>/dev/null)
|
||||
}
|
||||
|
||||
first_type() {
|
||||
[ "${1#/}" != "$1" ] && [ -x "$1" ] && echo "$1" && return
|
||||
for p in "/bin/$1" "/usr/bin/$1" "${TMP_BIN_PATH:-/tmp}/$1"; do
|
||||
[ -x "$p" ] && echo "$p" && return
|
||||
done
|
||||
command -v "$1" 2>/dev/null || command -v "$2" 2>/dev/null
|
||||
}
|
||||
|
||||
get_enabled_anonymous_secs() {
|
||||
uci -q show "${CONFIG}" | grep "${1}\[.*\.enabled='1'" | cut -d '.' -sf2
|
||||
}
|
||||
|
||||
get_geoip() {
|
||||
local geo_output_path="$TMP_PATH2/geo_output"
|
||||
mkdir -p ${geo_output_path}
|
||||
local geoip_code="$1"
|
||||
local geoip_type_flag=""
|
||||
local output_path="${geo_output_path}/geoip-${geoip_code}-$2"
|
||||
[ ! -s "${output_path}" ] && {
|
||||
local geoip_path="$(config_t_get global_rules v2ray_location_asset)"
|
||||
geoip_path="${geoip_path%*/}/geoip.dat"
|
||||
local bin="$(first_type $(config_t_get global_app geoview_file) geoview)"
|
||||
[ -n "$bin" ] && [ -s "$geoip_path" ] || { echo ""; return; }
|
||||
case "$2" in
|
||||
"ipv4") geoip_type_flag="-ipv6=false" ;;
|
||||
"ipv6") geoip_type_flag="-ipv4=false" ;;
|
||||
esac
|
||||
"$bin" -input "$geoip_path" -list "$geoip_code" $geoip_type_flag -lowmem=true -output ${output_path}
|
||||
}
|
||||
[ -s "${output_path}" ] && cat "${output_path}"
|
||||
}
|
||||
|
||||
get_host_ip() {
|
||||
local host=$2
|
||||
local count=$3
|
||||
[ -z "$count" ] && count=3
|
||||
local isip=""
|
||||
local ip=""
|
||||
if [ "$1" == "ipv6" ]; then
|
||||
isip=$(echo $host | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}")
|
||||
if [ -n "$isip" ]; then
|
||||
ip=$(echo "$host" | tr -d '[]')
|
||||
fi
|
||||
else
|
||||
isip=$(echo $host | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}")
|
||||
[ -n "$isip" ] && ip=$isip
|
||||
fi
|
||||
[ -z "$isip" ] && {
|
||||
local t=4
|
||||
[ "$1" == "ipv6" ] && t=6
|
||||
local vpsrip=$(resolveip -$t -t $count $host | awk 'NR==1{print}')
|
||||
ip=$vpsrip
|
||||
}
|
||||
[ -n "$ip" ] && echo "$ip"
|
||||
}
|
||||
|
||||
get_node_host_ip() {
|
||||
local ip
|
||||
local address=$(config_n_get $1 address)
|
||||
[ -n "$address" ] && {
|
||||
local use_ipv6=$(config_n_get $1 use_ipv6)
|
||||
local network_type="ipv4"
|
||||
[ "$use_ipv6" == "1" ] && network_type="ipv6"
|
||||
ip=$(get_host_ip $network_type $address)
|
||||
}
|
||||
echo $ip
|
||||
}
|
||||
|
||||
get_ip_port_from() {
|
||||
local __host=${1}; shift 1
|
||||
local __ipv=${1}; shift 1
|
||||
local __portv=${1}; shift 1
|
||||
local __ucipriority=${1}; shift 1
|
||||
|
||||
local val1 val2
|
||||
val2=$(echo "$__host" | sed -n '
|
||||
s/^[^#]*[#]\([0-9]*\)$/\1/p; t;
|
||||
s/^\(\[[^]]*\]\)[:]\([0-9]*\)$/\2/p; t;
|
||||
s/^.*[:#]\([0-9]*\)$/\1/p
|
||||
')
|
||||
if [ -n "${__ucipriority}" ]; then
|
||||
val2=$(config_n_get ${__host} port "${val2}")
|
||||
val1=$(config_n_get ${__host} address "${__host%%${val2:+[:#]${val2}*}}")
|
||||
else
|
||||
val1="${__host%%${val2:+[:#]${val2}*}}"
|
||||
fi
|
||||
eval "${__ipv}=\"$val1\"; ${__portv}=\"$val2\""
|
||||
}
|
||||
|
||||
parse_doh() {
|
||||
local __doh=$1 __url_var=$2 __host_var=$3 __port_var=$4 __bootstrap_var=$5
|
||||
__doh=$(printf '%s' "$__doh" | tr -d ' \t\n')
|
||||
local __url=${__doh%%,*}
|
||||
local __bootstrap=${__doh#*,}
|
||||
[ "$__bootstrap" = "$__doh" ] && __bootstrap=""
|
||||
local __host_port=$(lua_api "get_domain_from_url(\"${__url}\")")
|
||||
local __host __port
|
||||
if echo "${__host_port}" | grep -q '^\[.*\]:[0-9]\+$'; then
|
||||
__host=${__host_port%%]:*}]
|
||||
__port=${__host_port##*:}
|
||||
elif echo "${__host_port}" | grep -q ':[0-9]\+$'; then
|
||||
__host=${__host_port%:*}
|
||||
__port=${__host_port##*:}
|
||||
else
|
||||
__host=${__host_port}
|
||||
__port=443
|
||||
fi
|
||||
__host=${__host#[}
|
||||
__host=${__host%]}
|
||||
if [ "$(lua_api "is_ip(\"${__host}\")")" = "true" ]; then
|
||||
__bootstrap=${__host}
|
||||
fi
|
||||
__bootstrap=${__bootstrap#[}
|
||||
__bootstrap=${__bootstrap%]}
|
||||
eval "${__url_var}='${__url}' ${__host_var}='${__host}' ${__port_var}='${__port}' ${__bootstrap_var}='${__bootstrap}'"
|
||||
}
|
||||
|
||||
host_from_url(){
|
||||
local f="${1}"
|
||||
|
||||
## Remove protocol part of url ##
|
||||
f="${f##http://}"
|
||||
f="${f##https://}"
|
||||
f="${f##ftp://}"
|
||||
f="${f##sftp://}"
|
||||
|
||||
## Remove username and/or username:password part of URL ##
|
||||
f="${f##*:*@}"
|
||||
f="${f##*@}"
|
||||
|
||||
## Remove rest of urls ##
|
||||
f="${f%%/*}"
|
||||
echo "${f%%:*}"
|
||||
}
|
||||
|
||||
hosts_foreach() {
|
||||
local __hosts
|
||||
eval "__hosts=\$${1}"; shift 1
|
||||
local __func=${1}; shift 1
|
||||
local __default_port=${1}; shift 1
|
||||
local __ret=1
|
||||
|
||||
[ -z "${__hosts}" ] && return 0
|
||||
local __ip __port
|
||||
for __host in $(echo $__hosts | sed 's/[ ,]/\n/g'); do
|
||||
get_ip_port_from "$__host" "__ip" "__port"
|
||||
eval "$__func \"${__host}\" \"\${__ip}\" \"\${__port:-${__default_port}}\" \"$@\""
|
||||
__ret=$?
|
||||
[ ${__ret} -ge ${ERROR_NO_CATCH:-1} ] && return ${__ret}
|
||||
done
|
||||
}
|
||||
|
||||
check_host() {
|
||||
local f=${1}
|
||||
a=$(echo $f | grep "\/")
|
||||
[ -n "$a" ] && return 1
|
||||
# 判断是否包含汉字~
|
||||
local tmp=$(echo -n $f | awk '{print gensub(/[!-~]/,"","g",$0)}')
|
||||
[ -n "$tmp" ] && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
get_first_dns() {
|
||||
local __hosts_val=${1}; shift 1
|
||||
__first() {
|
||||
[ -z "${2}" ] && return 0
|
||||
echo "${2}#${3}"
|
||||
return 1
|
||||
}
|
||||
eval "hosts_foreach \"${__hosts_val}\" __first \"$@\""
|
||||
}
|
||||
|
||||
get_last_dns() {
|
||||
local __hosts_val=${1}; shift 1
|
||||
local __first __last
|
||||
__every() {
|
||||
[ -z "${2}" ] && return 0
|
||||
__last="${2}#${3}"
|
||||
__first=${__first:-${__last}}
|
||||
}
|
||||
eval "hosts_foreach \"${__hosts_val}\" __every \"$@\""
|
||||
[ "${__first}" == "${__last}" ] || echo "${__last}"
|
||||
}
|
||||
|
||||
check_port_exists() {
|
||||
local port=$1
|
||||
local protocol=$2
|
||||
[ -n "$protocol" ] || protocol="tcp,udp"
|
||||
local result=
|
||||
if [ "$protocol" = "tcp" ]; then
|
||||
result=$(netstat -tln | grep -c ":$port ")
|
||||
elif [ "$protocol" = "udp" ]; then
|
||||
result=$(netstat -uln | grep -c ":$port ")
|
||||
elif [ "$protocol" = "tcp,udp" ]; then
|
||||
result=$(netstat -tuln | grep -c ":$port ")
|
||||
fi
|
||||
echo "${result}"
|
||||
}
|
||||
|
||||
get_new_port() {
|
||||
local default_start_port=2001
|
||||
local min_port=1025
|
||||
local max_port=49151
|
||||
local port="$1"
|
||||
local protocol=$(echo "$2" | tr 'A-Z' 'a-z')
|
||||
local LOCK_FILE="${LOCK_PATH}/${CONFIG}_get_prot.lock"
|
||||
while ! mkdir "$LOCK_FILE" 2>/dev/null; do
|
||||
sleep 0.05
|
||||
done
|
||||
if [ "$port" = "auto" ]; then
|
||||
local now last_time diff last_port
|
||||
now=$(date +%s 2>/dev/null)
|
||||
last_time=$(get_cache_var "last_get_new_port_time")
|
||||
if [ -n "$now" ] && [ -n "$last_time" ]; then
|
||||
diff=$(expr "$now" - "$last_time")
|
||||
[ "$diff" -lt 0 ] && diff=$(expr 0 - "$diff")
|
||||
else
|
||||
diff=999
|
||||
fi
|
||||
if [ "$diff" -gt 10 ]; then
|
||||
port=$default_start_port
|
||||
else
|
||||
last_port=$(get_cache_var "last_get_new_port_auto")
|
||||
if [ -n "$last_port" ]; then
|
||||
port=$(expr "$last_port" + 1)
|
||||
else
|
||||
port=$default_start_port
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
[ "$port" -lt $min_port -o "$port" -gt $max_port ] && port=$default_start_port
|
||||
local start_port="$port"
|
||||
while :; do
|
||||
if [ "$(check_port_exists "$port" "$protocol")" = 0 ]; then
|
||||
break
|
||||
fi
|
||||
port=$(expr "$port" + 1)
|
||||
if [ "$port" -gt $max_port ]; then
|
||||
port=$min_port
|
||||
fi
|
||||
[ "$port" = "$start_port" ] && {
|
||||
rmdir "$LOCK_FILE" 2>/dev/null
|
||||
return 1
|
||||
}
|
||||
done
|
||||
if [ "$1" = "auto" ]; then
|
||||
set_cache_var "last_get_new_port_auto" "$port"
|
||||
[ -n "$now" ] && set_cache_var "last_get_new_port_time" "$now"
|
||||
fi
|
||||
rmdir "$LOCK_FILE" 2>/dev/null
|
||||
echo "$port"
|
||||
}
|
||||
|
||||
check_ver() {
|
||||
local version1="$1"
|
||||
local version2="$2"
|
||||
local i v1 v1_1 v1_2 v1_3 v2 v2_1 v2_2 v2_3
|
||||
IFS='.'; set -- $version1; v1_1=${1:-0}; v1_2=${2:-0}; v1_3=${3:-0}
|
||||
IFS='.'; set -- $version2; v2_1=${1:-0}; v2_2=${2:-0}; v2_3=${3:-0}
|
||||
IFS=
|
||||
for i in 1 2 3; do
|
||||
eval v1=\$v1_$i
|
||||
eval v2=\$v2_$i
|
||||
if [ "$v1" -gt "$v2" ]; then
|
||||
# $1 大于 $2
|
||||
echo 0
|
||||
return
|
||||
elif [ "$v1" -lt "$v2" ]; then
|
||||
# $1 小于 $2
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
done
|
||||
# $1 等于 $2
|
||||
echo 255
|
||||
}
|
||||
|
||||
eval_set_val() {
|
||||
for i in $@; do
|
||||
for j in $i; do
|
||||
eval $j
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
eval_unset_val() {
|
||||
for i in $@; do
|
||||
for j in $i; do
|
||||
eval unset $j
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
lua_api() {
|
||||
local func=${1}
|
||||
[ -z "${func}" ] && {
|
||||
echo "nil"
|
||||
return
|
||||
}
|
||||
echo $(lua -e "local api = require 'luci.passwall.api' print(api.${func})")
|
||||
}
|
||||
|
||||
set_cache_var() {
|
||||
local key="${1}"
|
||||
shift 1
|
||||
local val="$@"
|
||||
[ -n "${key}" ] && [ -n "${val}" ] && {
|
||||
[ ! -d $TMP_PATH ] && mkdir -p $TMP_PATH
|
||||
sed -i "/${key}=/d" $TMP_PATH/var >/dev/null 2>&1
|
||||
echo "${key}=\"${val}\"" >> $TMP_PATH/var
|
||||
eval ${key}=\"${val}\"
|
||||
}
|
||||
}
|
||||
|
||||
get_cache_var() {
|
||||
local key="${1}"
|
||||
[ -n "${key}" ] && [ -s "$TMP_PATH/var" ] && {
|
||||
echo $(cat $TMP_PATH/var | grep "^${key}=" | awk -F '=' '{print $2}' | tail -n 1 | awk -F'"' '{print $2}')
|
||||
}
|
||||
}
|
||||
|
||||
eval_cache_var() {
|
||||
[ -s "$TMP_PATH/var" ] && eval $(cat "$TMP_PATH/var")
|
||||
}
|
||||
|
||||
has_1_65535() {
|
||||
local val="$1"
|
||||
val=${val//:/-}
|
||||
case ",$val," in
|
||||
*,1-65535,*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
add_ip2route() {
|
||||
local ip=$(get_host_ip "ipv4" $1)
|
||||
[ -z "$ip" ] && {
|
||||
echolog " - 无法解析[${1}],路由表添加失败!"
|
||||
return 1
|
||||
}
|
||||
local remarks="${1}"
|
||||
[ "$remarks" != "$ip" ] && remarks="${1}(${ip})"
|
||||
|
||||
local gateway device
|
||||
network_get_gateway gateway "$2"
|
||||
network_get_device device "$2"
|
||||
[ -z "${device}" ] && device=$(ubus call "network.interface.$2" status 2>/dev/null | jsonfilter -e '@.device' 2>/dev/null)
|
||||
[ -z "${device}" ] && [ -d "/sys/class/net/$2" ] && device="$2"
|
||||
[ -z "${device}" ] && device="$2"
|
||||
|
||||
if [ -n "${gateway}" ]; then
|
||||
route add -host ${ip} gw ${gateway} dev ${device} >/dev/null 2>&1
|
||||
echo "$ip" >> $TMP_ROUTE_PATH/${device}
|
||||
echolog " - [${remarks}]添加到接口[${device}]路由表成功!"
|
||||
else
|
||||
echolog " - [${remarks}]添加到接口[${device}]路由表失功!原因是找不到[${device}]网关。"
|
||||
fi
|
||||
}
|
||||
|
||||
delete_ip2route() {
|
||||
[ -d "${TMP_ROUTE_PATH}" ] && {
|
||||
local interface
|
||||
for interface in $(ls ${TMP_ROUTE_PATH}); do
|
||||
for ip in $(cat ${TMP_ROUTE_PATH}/${interface}); do
|
||||
route del -host ${ip} dev ${interface} >/dev/null 2>&1
|
||||
done
|
||||
done
|
||||
}
|
||||
}
|
||||
|
||||
ln_run() {
|
||||
local file_func=${1}
|
||||
local ln_name=${2}
|
||||
local output=${3}
|
||||
|
||||
shift 3;
|
||||
if [ "${file_func%%/*}" != "${file_func}" ]; then
|
||||
[ ! -L "${file_func}" ] && {
|
||||
ln -s "${file_func}" "${TMP_BIN_PATH}/${ln_name}" >/dev/null 2>&1
|
||||
file_func="${TMP_BIN_PATH}/${ln_name}"
|
||||
}
|
||||
[ -x "${file_func}" ] || echolog " - $(readlink ${file_func}) 没有执行权限,无法启动:${file_func} $*"
|
||||
fi
|
||||
#echo "${file_func} $*" >&2
|
||||
[ -n "${file_func}" ] || echolog " - 找不到 ${ln_name},无法启动..."
|
||||
[ "${output}" != "/dev/null" ] && [ "${ln_name}" != "chinadns-ng" ] && {
|
||||
local persist_log_path=$(config_t_get global persist_log_path)
|
||||
local sys_log=$(config_t_get global sys_log "0")
|
||||
}
|
||||
if [ -z "$persist_log_path" ] && [ "$sys_log" != "1" ]; then
|
||||
${file_func:-echolog " - ${ln_name}"} "$@" >${output} 2>&1 &
|
||||
else
|
||||
[ "${output: -1, -7}" == "TCP.log" ] && local protocol="TCP"
|
||||
[ "${output: -1, -7}" == "UDP.log" ] && local protocol="UDP"
|
||||
if [ -n "${persist_log_path}" ]; then
|
||||
mkdir -p ${persist_log_path}
|
||||
local log_file=${persist_log_path}/passwall_${protocol}_${ln_name}_$(date '+%F').log
|
||||
echolog "记录到持久性日志文件:${log_file}"
|
||||
${file_func:-echolog " - ${ln_name}"} "$@" >> ${log_file} 2>&1 &
|
||||
sys_log=0
|
||||
fi
|
||||
if [ "${sys_log}" == "1" ]; then
|
||||
echolog "记录 ${ln_name}_${protocol} 到系统日志"
|
||||
${file_func:-echolog " - ${ln_name}"} "$@" 2>&1 | logger -t PASSWALL_${protocol}_${ln_name} &
|
||||
fi
|
||||
fi
|
||||
[ "$NO_REC_PROCESS" = "1" ] && return
|
||||
process_count=$(ls $TMP_SCRIPT_FUNC_PATH | wc -l)
|
||||
process_count=$((process_count + 1))
|
||||
echo "${file_func:-echolog " - ${ln_name}"} $@ >${output}" > $TMP_SCRIPT_FUNC_PATH/$process_count
|
||||
}
|
||||
|
||||
is_socks_wrap() {
|
||||
case "$1" in
|
||||
Socks_*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
kill_all() {
|
||||
kill -9 $(pidof "$@") >/dev/null 2>&1
|
||||
}
|
||||
|
||||
get_subscribe_host(){
|
||||
local line
|
||||
uci show "${CONFIG}" | grep "=subscribe_list" | while read -r line; do
|
||||
local section="$(echo "$line" | cut -d '.' -sf 2 | cut -d '=' -sf 1)"
|
||||
local url="$(config_n_get $section url)"
|
||||
[ -n "$url" ] || continue
|
||||
url="$(host_from_url "$url")"
|
||||
echo "$url"
|
||||
done
|
||||
}
|
||||
|
||||
gen_lanlist() {
|
||||
cat $RULES_PATH/lanlist_ipv4 | tr -s '\n' | grep -v "^#"
|
||||
}
|
||||
|
||||
gen_lanlist_6() {
|
||||
cat $RULES_PATH/lanlist_ipv6 | tr -s '\n' | grep -v "^#"
|
||||
}
|
||||
|
||||
get_wan_ips() {
|
||||
local family="$1"
|
||||
local NET_ADDR
|
||||
local iface
|
||||
local INTERFACES=$(ubus call network.interface dump | jsonfilter -e \
|
||||
'@.interface[!(@.interface ~ /lan/) && !(@.l3_device ~ /\./) && @.route[0]].interface')
|
||||
for iface in $INTERFACES; do
|
||||
local addr
|
||||
if [ "$family" = "ip6" ]; then
|
||||
network_get_ipaddr6 addr "$iface"
|
||||
case "$addr" in
|
||||
""|fe80*) continue ;;
|
||||
esac
|
||||
else
|
||||
network_get_ipaddr addr "$iface"
|
||||
case "$addr" in
|
||||
""|"0.0.0.0") continue ;;
|
||||
esac
|
||||
fi
|
||||
case " $NET_ADDR " in
|
||||
*" $addr "*) ;;
|
||||
*) NET_ADDR="${NET_ADDR:+$NET_ADDR }$addr" ;;
|
||||
esac
|
||||
done
|
||||
echo "$NET_ADDR"
|
||||
}
|
||||
|
||||
get_local_ips() {
|
||||
local family="$1"
|
||||
local ALL_IPS WAN_IPS ip NET_ADDR
|
||||
if [ "$family" = "ip6" ]; then
|
||||
ALL_IPS=$(ip -o -6 addr show scope global | awk '{print $4}' | cut -d/ -f1)
|
||||
WAN_IPS=$(get_wan_ips ip6)
|
||||
else
|
||||
ALL_IPS=$(ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1)
|
||||
WAN_IPS=$(get_wan_ips ip4)
|
||||
fi
|
||||
# 补充回环(scope global 不包含)
|
||||
[ "$family" = "ip6" ] && ALL_IPS="$ALL_IPS ::1"
|
||||
[ "$family" != "ip6" ] && ALL_IPS="$ALL_IPS 127.0.0.1"
|
||||
for ip in $ALL_IPS; do
|
||||
case "$ip" in
|
||||
""|0.0.0.0|::) continue ;;
|
||||
esac
|
||||
case " $WAN_IPS " in
|
||||
*" $ip "*) continue ;;
|
||||
esac
|
||||
case " $NET_ADDR " in
|
||||
*" $ip "*) ;;
|
||||
*) NET_ADDR="${NET_ADDR:+$NET_ADDR }$ip" ;;
|
||||
esac
|
||||
done
|
||||
for ip in $NET_ADDR; do
|
||||
echo "$ip"
|
||||
done
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"luci-app-passwall": {
|
||||
"description": "Grant UCI access for luci-app-passwall",
|
||||
"read": {
|
||||
"uci": [ "passwall", "passwall_server" ]
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "passwall", "passwall_server" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"config": "passwall_server",
|
||||
"init": "passwall_server"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"config": "passwall",
|
||||
"init": "passwall"
|
||||
}
|
||||
Reference in new issue
Block a user