diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
index 174df8b2e..d33a67969 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
@@ -19,6 +19,7 @@ end
local normal_list = {}
local balancing_list = {}
+local urltest_list = {}
local shunt_list = {}
local iface_list = {}
for k, v in pairs(nodes_table) do
@@ -28,6 +29,9 @@ for k, v in pairs(nodes_table) do
if v.protocol and v.protocol == "_balancing" then
balancing_list[#balancing_list + 1] = v
end
+ if v.protocol and v.protocol == "_urltest" then
+ urltest_list[#urltest_list + 1] = v
+ end
if v.protocol and v.protocol == "_shunt" then
shunt_list[#shunt_list + 1] = v
end
@@ -185,6 +189,9 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
for k1, v1 in pairs(balancing_list) do
o:value(v1.id, v1.remark)
end
+ for k1, v1 in pairs(urltest_list) do
+ o:value(v1.id, v1.remark)
+ end
for k1, v1 in pairs(iface_list) do
o:value(v1.id, v1.remark)
end
@@ -226,6 +233,9 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
for k1, v1 in pairs(balancing_list) do
o:value(v1.id, v1.remark)
end
+ for k1, v1 in pairs(urltest_list) do
+ o:value(v1.id, v1.remark)
+ end
for k1, v1 in pairs(iface_list) do
o:value(v1.id, v1.remark)
end
@@ -250,6 +260,9 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
for k1, v1 in pairs(balancing_list) do
o:value(v1.id, v1.remark)
end
+ for k1, v1 in pairs(urltest_list) do
+ o:value(v1.id, v1.remark)
+ end
for k1, v1 in pairs(iface_list) do
o:value(v1.id, v1.remark)
end
@@ -265,7 +278,7 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
o:value("", translate("Close"))
o:value("main", translate("Preproxy Node"))
for k1, v1 in pairs(normal_list) do
- if v1.protocol ~= "_balancing" then
+ if v1.protocol ~= "_balancing" and v1.protocol ~= "_urltest" then
o:depends({ [vid .. "-default_node"] = v1.id, [vid .. "-preproxy_enabled"] = "1" })
end
end
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
index cdde9ba3d..33587b183 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
@@ -103,6 +103,8 @@ o.cfgvalue = function(t, n)
local protocol = m:get(n, "protocol")
if protocol == "_balancing" then
protocol = translate("Balancing")
+ elseif protocol == "_urltest" then
+ protocol = "URLTest"
elseif protocol == "_shunt" then
protocol = translate("Shunt")
elseif protocol == "vmess" then
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
index 7e5ee34a6..d826f3360 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua
@@ -56,6 +56,7 @@ end
if singbox_tags:find("with_quic") then
o:value("hysteria2", "Hysteria2")
end
+o:value("_urltest", translate("URLTest"))
o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface"))
@@ -65,6 +66,7 @@ o:depends({ [_n("protocol")] = "_iface" })
local nodes_table = {}
local iface_table = {}
+local urltest_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" then
nodes_table[#nodes_table + 1] = {
@@ -79,6 +81,12 @@ for k, e in ipairs(api.get_valid_nodes()) do
remark = e["remark"]
}
end
+ if e.protocol == "_urltest" then
+ urltest_table[#urltest_table + 1] = {
+ id = e[".name"],
+ remark = e["remark"]
+ }
+ end
end
local socks_list = {}
@@ -91,6 +99,44 @@ m.uci:foreach(appname, "socks", function(s)
end
end)
+--[[ URLTest ]]
+o = s:option(DynamicList, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, document"))
+o:depends({ [_n("protocol")] = "_urltest" })
+for k, v in pairs(nodes_table) do o:value(v.id, v.remark) end
+
+o = s:option(Value, _n("urltest_url"), translate("Probe URL"))
+o:depends({ [_n("protocol")] = "_urltest" })
+o:value("https://cp.cloudflare.com/", "Cloudflare")
+o:value("https://www.gstatic.com/generate_204", "Gstatic")
+o:value("https://www.google.com/generate_204", "Google")
+o:value("https://www.youtube.com/generate_204", "YouTube")
+o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)")
+o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)")
+o.default = "https://www.gstatic.com/generate_204"
+o.description = translate("The URL used to detect the connection status.")
+
+o = s:option(Value, _n("urltest_interval"), translate("Test interval"))
+o:depends({ [_n("protocol")] = "_urltest" })
+o.datatype = "uinteger"
+o.default = "180"
+o.description = translate("The test interval in seconds.") .. "
" ..
+ translate("Test interval must be less or equal than idle timeout.")
+
+o = s:option(Value, _n("urltest_tolerance"), translate("Test tolerance"), translate("The test tolerance in milliseconds."))
+o:depends({ [_n("protocol")] = "_urltest" })
+o.datatype = "uinteger"
+o.default = "50"
+
+o = s:option(Value, _n("urltest_idle_timeout"), translate("Idle timeout"), translate("The idle timeout in seconds."))
+o:depends({ [_n("protocol")] = "_urltest" })
+o.datatype = "uinteger"
+o.default = "1800"
+
+o = s:option(Flag, _n("urltest_interrupt_exist_connections"), translate("Interrupt existing connections"))
+o:depends({ [_n("protocol")] = "_urltest" })
+o.default = "0"
+o.description = translate("Interrupt existing connections when the selected outbound has changed.")
+
-- [[ 分流模块 ]]
if #nodes_table > 0 then
o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy"))
@@ -101,6 +147,9 @@ if #nodes_table > 0 then
for k, v in pairs(socks_list) do
o:value(v.id, v.remark)
end
+ for k, v in pairs(urltest_table) do
+ o:value(v.id, v.remark)
+ end
for k, v in pairs(iface_table) do
o:value(v.id, v.remark)
end
@@ -121,6 +170,9 @@ m.uci:foreach(appname, "shunt_rules", function(e)
for k, v in pairs(socks_list) do
o:value(v.id, v.remark)
end
+ for k, v in pairs(urltest_table) do
+ o:value(v.id, v.remark)
+ end
for k, v in pairs(iface_table) do
o:value(v.id, v.remark)
end
@@ -152,6 +204,9 @@ if #nodes_table > 0 then
for k, v in pairs(socks_list) do
o:value(v.id, v.remark)
end
+ for k, v in pairs(urltest_table) do
+ o:value(v.id, v.remark)
+ end
for k, v in pairs(iface_table) do
o:value(v.id, v.remark)
end
diff --git a/luci-app-passwall/luasrc/passwall/api.lua b/luci-app-passwall/luasrc/passwall/api.lua
index ca3611cdd..f08c7019c 100644
--- a/luci-app-passwall/luasrc/passwall/api.lua
+++ b/luci-app-passwall/luasrc/passwall/api.lua
@@ -384,7 +384,7 @@ function strToTable(str)
end
function is_normal_node(e)
- if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface") then
+ if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
return false
end
return true
@@ -494,7 +494,7 @@ function get_valid_nodes()
uci:foreach(appname, "nodes", function(e)
e.id = e[".name"]
if e.type and e.remarks then
- if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface") then
+ if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface" or e.protocol == "_urltest") then
local type = e.type
if type == "sing-box" then type = "Sing-Box" end
e["remark"] = "%s:[%s] " % {type .. " " .. i18n.translatef(e.protocol), e.remarks}
@@ -544,7 +544,7 @@ end
function get_node_remarks(n)
local remarks = ""
if n then
- if n.protocol and (n.protocol == "_balancing" or n.protocol == "_shunt" or n.protocol == "_iface") then
+ if n.protocol and (n.protocol == "_balancing" or n.protocol == "_shunt" or n.protocol == "_iface" or n.protocol == "_urltest") then
remarks = "%s:[%s] " % {n.type .. " " .. i18n.translatef(n.protocol), n.remarks}
else
local type2 = n.type
diff --git a/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/luci-app-passwall/luasrc/passwall/util_sing-box.lua
index 671974548..bd3a2688c 100644
--- a/luci-app-passwall/luasrc/passwall/util_sing-box.lua
+++ b/luci-app-passwall/luasrc/passwall/util_sing-box.lua
@@ -901,6 +901,54 @@ function gen_config(var)
table.insert(inbounds, inbound)
end
+ local function gen_urltest(_node)
+ local urltest_id = _node[".name"]
+ local urltest_tag = "urltest-" .. urltest_id
+ -- existing urltest
+ for _, v in ipairs(outbounds) do
+ if v.tag == urltest_tag then
+ return urltest_tag
+ end
+ end
+ -- new urltest
+ local ut_nodes = _node.urltest_node
+ local valid_nodes = {}
+ for i = 1, #ut_nodes do
+ local ut_node_id = ut_nodes[i]
+ local ut_node_tag = "ut-" .. ut_node_id
+ local is_new_ut_node = true
+ for _, outbound in ipairs(outbounds) do
+ if string.sub(outbound.tag, 1, #ut_node_tag) == ut_node_tag then
+ is_new_ut_node = false
+ valid_nodes[#valid_nodes + 1] = outbound.tag
+ break
+ end
+ end
+ if is_new_ut_node then
+ local ut_node = uci:get_all(appname, ut_node_id)
+ local outbound = gen_outbound(flag, ut_node, ut_node_tag)
+ if outbound then
+ outbound.tag = outbound.tag .. ":" .. ut_node.remarks
+ table.insert(outbounds, outbound)
+ valid_nodes[#valid_nodes + 1] = outbound.tag
+ end
+ end
+ end
+ if #valid_nodes == 0 then return nil end
+ local outbound = {
+ type = "urltest",
+ tag = urltest_tag,
+ outbounds = valid_nodes,
+ url = _node.urltest_url or "https://www.gstatic.com/generate_204",
+ interval = _node.urltest_interval and tonumber(_node.urltest_interval) and string.format("%dm", tonumber(_node.urltest_interval) / 60) or "3m",
+ tolerance = _node.urltest_tolerance and tonumber(_node.urltest_tolerance) and tonumber(_node.urltest_tolerance) or 50,
+ idle_timeout = _node.urltest_idle_timeout and tonumber(_node.urltest_idle_timeout) and string.format("%dm", tonumber(_node.urltest_idle_timeout) / 60) or "30m",
+ interrupt_exist_connections = (_node.urltest_interrupt_exist_connections == "true" or _node.urltest_interrupt_exist_connections == "1") and true or false
+ }
+ table.insert(outbounds, outbound)
+ return urltest_tag
+ end
+
local function set_outbound_detour(node, outbound, outbounds_table, shunt_rule_name)
if not node or not outbound or not outbounds_table then return nil end
local default_outTag = outbound.tag
@@ -1055,6 +1103,8 @@ function gen_config(var)
end
end
end
+ elseif _node.protocol == "_urltest" then
+ rule_outboundTag = gen_urltest(_node)
elseif _node.protocol == "_iface" then
if _node.iface then
local _outbound = {
@@ -1231,6 +1281,10 @@ function gen_config(var)
for index, value in ipairs(rules) do
table.insert(route.rules, rules[index])
end
+ elseif node.protocol == "_urltest" then
+ if node.urltest_node then
+ COMMON.default_outbound_tag = gen_urltest(node)
+ end
elseif node.protocol == "_iface" then
if node.iface then
local outbound = {
diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po
index 29998faf8..4c93e477c 100644
--- a/luci-app-passwall/po/zh-cn/passwall.po
+++ b/luci-app-passwall/po/zh-cn/passwall.po
@@ -1579,9 +1579,6 @@ msgstr "最大并发连接数"
msgid "XUDP Mux concurrency"
msgstr "XUDP 最大并发连接数"
-msgid "Mux idle timeout"
-msgstr "最大闲置时间"
-
msgid "Padding"
msgstr "填充"
@@ -1600,9 +1597,6 @@ msgstr "推荐值:Sec-WebSocket-Protocol"
msgid "Health check"
msgstr "健康检查"
-msgid "Idle timeout"
-msgstr "闲置时间"
-
msgid "Health check timeout"
msgstr "检查超时时间"
@@ -1803,3 +1797,39 @@ msgstr "是否真的要恢复客户端默认配置?"
msgid "Settings have been successfully saved and applied!"
msgstr "设置已成功保存并应用!"
+
+msgid "_urltest"
+msgstr "URLTest"
+
+msgid "URLTest node list"
+msgstr "URLTest 节点列表"
+
+msgid "List of nodes to test, document"
+msgstr "要测试的节点列表,文档原理"
+
+msgid "Test interval"
+msgstr "测试间隔"
+
+msgid "The test interval in seconds."
+msgstr "测试间隔时间(单位:秒)。"
+
+msgid "Test interval must be less or equal than idle timeout."
+msgstr "测试间隔时间必须小于或等于空闲超时时间。"
+
+msgid "Test tolerance"
+msgstr "测试容差"
+
+msgid "The test tolerance in milliseconds."
+msgstr "测试容差时间(单位:毫秒)。"
+
+msgid "Idle timeout"
+msgstr "空闲超时"
+
+msgid "The idle timeout in seconds."
+msgstr "空闲超时时间(单位:秒)。"
+
+msgid "Interrupt existing connections"
+msgstr "中断现有连接"
+
+msgid "Interrupt existing connections when the selected outbound has changed."
+msgstr "当选择的出站发生变化时中断现有连接。"
diff --git a/luci-app-passwall/root/usr/share/passwall/app.sh b/luci-app-passwall/root/usr/share/passwall/app.sh
index c7f3346c9..aea09e627 100755
--- a/luci-app-passwall/root/usr/share/passwall/app.sh
+++ b/luci-app-passwall/root/usr/share/passwall/app.sh
@@ -621,7 +621,7 @@ run_socks() {
if [ "$type" == "sing-box" ] || [ "$type" == "xray" ]; then
local protocol=$(config_n_get $node protocol)
- if [ "$protocol" == "_balancing" ] || [ "$protocol" == "_shunt" ] || [ "$protocol" == "_iface" ]; then
+ if [ "$protocol" == "_balancing" ] || [ "$protocol" == "_shunt" ] || [ "$protocol" == "_iface" ] || [ "$protocol" == "_urltest" ]; then
unset error_msg
fi
fi
diff --git a/luci-app-passwall/root/usr/share/passwall/subscribe.lua b/luci-app-passwall/root/usr/share/passwall/subscribe.lua
index f89ab7ba7..7533743d0 100755
--- a/luci-app-passwall/root/usr/share/passwall/subscribe.lua
+++ b/luci-app-passwall/root/usr/share/passwall/subscribe.lua
@@ -318,6 +318,36 @@ do
end
}
end
+ elseif node.protocol and node.protocol == '_urltest' then
+ local flag = "Sing-Box URLTest节点[" .. node_id .. "]列表"
+ local currentNodes = {}
+ local newNodes = {}
+ if node.urltest_node then
+ for k, node in pairs(node.urltest_node) do
+ currentNodes[#currentNodes + 1] = {
+ log = false,
+ node = node,
+ currentNode = node and uci:get_all(appname, node) or nil,
+ remarks = node,
+ set = function(o, server)
+ if o and server and server ~= "nil" then
+ table.insert(o.newNodes, server)
+ end
+ end
+ }
+ end
+ end
+ CONFIG[#CONFIG + 1] = {
+ remarks = flag,
+ currentNodes = currentNodes,
+ newNodes = newNodes,
+ set = function(o, newNodes)
+ if o then
+ if not newNodes then newNodes = o.newNodes end
+ uci:set_list(appname, node_id, "urltest_node", newNodes or {})
+ end
+ end
+ }
else
--前置代理节点
local currentNode = uci:get_all(appname, node_id) or nil
@@ -1691,7 +1721,7 @@ local execute = function()
f:close()
raw = trim(stdout)
local old_md5 = value.md5 or ""
- local new_md5 = luci.sys.exec("[ -f " .. tmp_file .. " ] && md5sum " .. tmp_file .. " | awk '{print $1}' || echo 0")
+ local new_md5 = luci.sys.exec("[ -f " .. tmp_file .. " ] && md5sum " .. tmp_file .. " | awk '{print $1}' || echo 0"):gsub("\n", "")
os.remove(tmp_file)
if old_md5 == new_md5 then
log('订阅:【' .. remark .. '】没有变化,无需更新。')
diff --git a/luci-app-passwall/root/usr/share/passwall/test.sh b/luci-app-passwall/root/usr/share/passwall/test.sh
index 83335b80e..2da810894 100755
--- a/luci-app-passwall/root/usr/share/passwall/test.sh
+++ b/luci-app-passwall/root/usr/share/passwall/test.sh
@@ -95,7 +95,7 @@ url_test_node() {
[ "${chn_list}" = "proxy" ] && probeUrl="www.baidu.com"
result=$(${_curl} --max-time 5 -o /dev/null -I -skL -x ${curlx} ${curl_arg}${probeUrl})
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}.json"
+ rm -rf "/tmp/etc/${CONFIG}/url_test_${node_id}"*.json
}
echo $result
}
diff --git a/patch-luci-app-passwall.patch b/patch-luci-app-passwall.patch
index 6243de609..06fb4fa96 100644
--- a/patch-luci-app-passwall.patch
+++ b/patch-luci-app-passwall.patch
@@ -20,10 +20,10 @@ index af6e04a..709fba2 100644
define Package/$(PKG_NAME)/postrm
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
-index 5687101..174df8b 100644
+index 908dc46..d33a679 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
-@@ -493,6 +493,12 @@ o:value("9.9.9.9", "9.9.9.9 (Quad9)")
+@@ -506,6 +506,12 @@ o:value("9.9.9.9", "9.9.9.9 (Quad9)")
o:value("149.112.112.112", "149.112.112.112 (Quad9)")
o:value("208.67.220.220", "208.67.220.220 (OpenDNS)")
o:value("208.67.222.222", "208.67.222.222 (OpenDNS)")
@@ -36,7 +36,7 @@ index 5687101..174df8b 100644
o:depends({dns_mode = "dns2socks"})
o:depends({dns_mode = "tcp"})
o:depends({dns_mode = "udp"})
-@@ -592,7 +598,7 @@ if api.is_finded("smartdns") then
+@@ -605,7 +611,7 @@ if api.is_finded("smartdns") then
end
o = s:taboption("DNS", Flag, "dns_redirect", translate("DNS Redirect"), translate("Force special DNS server to need proxy devices."))