@@ -246,6 +246,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
if (s) {
var div = s.parentNode.parentNode.parentNode.parentNode;
div.removeAttribute('onclick');
+ div.title = "<%:HTTP request latency, for reference only%>";
s.innerHTML = '<%:Check...%>';
var sendDate = (new Date()).getTime();
XHR.get('<%=api.url("connect_status")%>', {
@@ -263,6 +264,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
s.className="red";
}
s.innerHTML = use_time + " ms";
+ div.title += "\n<%:Initial handshake time%>: " + rv.handshake_time + " ms";
} else if (rv.status) {
s.className="green";
s.innerHTML = "<%:Working...%>";
diff --git a/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm b/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm
index f2f692b5..c69a075b 100644
--- a/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm
+++ b/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm
@@ -710,6 +710,7 @@ table td, .table .td {
function urltest_node(cbi_id, dom) {
if (dom.dataset.busy == "1" || !cbi_id) return;
dom.dataset.busy = "1";
+ dom.title = "<%:HTTP request latency, for reference only%>";
dom.style.color = "";
dom.innerText = "<%:Check...%>";
XHR.get('<%=api.url("urltest_node")%>', {
@@ -720,7 +721,7 @@ table td, .table .td {
let text = "<%:Error%>";
if (x && x.status == 200) {
if (result.use_time == null || result.use_time.trim() == "") {
- text = "<%:Timeout%>";
+ text = result.http_code >= 400 ? "HTTP " + result.http_code : "<%:Timeout%>";
} else {
let use_time = parseInt(result.use_time);
if (use_time < 1000)
@@ -728,6 +729,7 @@ table td, .table .td {
else if (use_time < 2000)
color = "#fb9a05";
text = use_time + " ms";
+ dom.title += "\n<%:Initial handshake time%>: " + result.handshake_time + " ms";
}
}
dom.innerText = text;
@@ -1417,7 +1419,7 @@ table td, .table .td {
innerHTML = innerHTML.split("{{tcping}}").join('
---');
}
if (o["address"] && _port) {
- innerHTML = innerHTML.split("{{url_test}}").join('
<%:Test%>');
+ innerHTML = innerHTML.split("{{url_test}}").join('
<%:Test%>');
} else {
innerHTML = innerHTML.split("{{url_test}}").join('
---');
}
diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po
index 67df60dd..3b285363 100644
--- a/luci-app-passwall/po/zh-cn/passwall.po
+++ b/luci-app-passwall/po/zh-cn/passwall.po
@@ -22,8 +22,11 @@ msgstr "连接失败"
msgid "Touch Check"
msgstr "点我检测"
-msgid "TLS handshake test, latency for reference only"
-msgstr "TLS握手测试,延时仅供参考"
+msgid "HTTP request latency, for reference only"
+msgstr "HTTP 请求延迟,仅供参考"
+
+msgid "Initial handshake time"
+msgstr "首次握手耗时"
msgid "Kernel Unsupported"
msgstr "内核不支持"
diff --git a/luci-app-passwall/root/usr/share/passwall/haproxy.lua b/luci-app-passwall/root/usr/share/passwall/haproxy.lua
index a2ce23b6..8ac9c3f3 100644
--- a/luci-app-passwall/root/usr/share/passwall/haproxy.lua
+++ b/luci-app-passwall/root/usr/share/passwall/haproxy.lua
@@ -21,7 +21,7 @@ local haproxy_dns = "127.0.0.1"
local cpu_thread = sys.exec('echo -n $(cat /proc/cpuinfo | grep "processor" | wc -l)') or "1"
local health_check_type = api.uci_get_c("@global_haproxy[0]", "health_check_type") or "tcp"
-local health_check_inter = api.uci_get_c("@global_haproxy[0]", "health_check_inter") or "20"
+local health_check_inter = api.uci_get_c("@global_haproxy[0]", "health_check_inter") or "60"
local balancingStrategy = api.uci_get_c("@global_haproxy[0]", "balancingStrategy") or "roundrobin"
local console_port = api.uci_get_c("@global_haproxy[0]", "console_port") or "1188"
local bind_local = api.uci_get_c("@global_haproxy[0]", "bind_local") or "0"
@@ -44,7 +44,7 @@ global
nbthread {{nbthread}}
external-check
insecure-fork-wanted
- spread-checks 50
+ spread-checks 20
tune.max-checks-per-thread 5
defaults
@@ -99,7 +99,7 @@ api.uci_foreach_c("haproxy_config", function(t)
local lbss = t.lbss
local listen_port = tonumber(t.haproxy_port) or 0
local server_node = api.uci_get_c(lbss)
- local hop = (health_check_type == "script_logic") and (server_node.hysteria_hop or server_node.hysteria2_hop) or nil
+ local hop = (health_check_type == "script_logic") and server_node 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)
@@ -153,9 +153,12 @@ api.uci_foreach_c("haproxy_config", function(t)
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 inter = tonumber(health_check_inter:match("^%d+")) or 60
+inter = math.max(inter, 20)
+-- 限制过渡状态的确认间隔,避免正常检测间隔较长时摘除或恢复过慢。
+local fastinter = math.max(10, math.min(15, math.floor(inter / 2)))
+local downinter = math.ceil(inter * 1.5)
+local inter_arg = string.format("inter %ds fastinter %ds downinter %ds", inter, fastinter, downinter)
local sortTable = {}
for i in pairs(listens) do
@@ -165,7 +168,28 @@ for i in pairs(listens) do
end
table.sort(sortTable, function(a,b) return (a < b) end)
-for i, port in pairs(sortTable) do
+local function server_endpoint(o)
+ local address = o.server_address:gsub("^%[(.*)%]$", "%1")
+ if address:find(":", 1, true) then address = "[" .. address .. "]" end
+ return address .. ":" .. o.server_port
+end
+
+local check_counts = {}
+if health_check_type == "script_logic" then
+ for _, port in ipairs(sortTable) do
+ for _, o in ipairs(listens[port]) do
+ -- 域名可能解析到不同 IP,仅对相同的实际代理端点共享检测。
+ if api.is_ip(o.server_address) then
+ local endpoint = server_endpoint(o)
+ check_counts[endpoint] = (check_counts[endpoint] or 0) + 1
+ end
+ end
+ end
+end
+local shared_checks = {}
+local shared_check_names = {}
+
+for i, port in ipairs(sortTable) do
log(" + 入口 %s:%s" % {bind_address, port})
f_out:write("\n" .. string.format([[
@@ -184,7 +208,7 @@ listen %s
local count_M, count_B = 1, 1
for i, o in ipairs(listens[port]) do
- local remark = o.server_remark or ""
+ local remark = (o.server_remark or ""):gsub("[%[%]]", "")
-- 防止重名导致无法运行
if tostring(o.backup) ~= "1" then
remark = "M" .. count_M .. "-" .. remark
@@ -193,8 +217,18 @@ listen %s
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}}"
+ local server = server_endpoint(o)
+ local check_arg = "check " .. inter_arg .. " rise 2 fall 2"
+ if (check_counts[server] or 0) > 1 then
+ local name = shared_check_names[server]
+ if not name then
+ name = "check_" .. (#shared_checks + 1)
+ shared_check_names[server] = name
+ table.insert(shared_checks, {name = name, server = server})
+ end
+ check_arg = "track " .. appname .. "_health_checks/" .. name
+ end
+ local server_conf = "server {{remark}} {{server}} weight {{weight}} {{resolvers}} {{check_arg}} {{backup}}"
server_conf = server_conf:gsub("{{remark}}", remark)
server_conf = server_conf:gsub("{{server}}", server)
server_conf = server_conf:gsub("{{weight}}", o.lbweight)
@@ -203,7 +237,7 @@ listen %s
resolvers = ""
end
server_conf = server_conf:gsub("{{resolvers}}", resolvers)
- server_conf = server_conf:gsub("{{inter_arg}}", inter_arg)
+ server_conf = server_conf:gsub("{{check_arg}}", check_arg)
server_conf = server_conf:gsub("{{backup}}", tostring(o.backup) == "1" and "backup" or "")
f_out:write(" " .. api.trim(server_conf) .. "\n")
@@ -216,6 +250,20 @@ listen %s
end
end
+-- 独立检测 backend,避免入口服务器的手动维护状态传播到其他入口。
+if #shared_checks > 0 then
+ f_out:write(string.format([[
+
+backend %s_health_checks
+ mode tcp
+ option external-check
+ external-check command "/usr/share/%s/haproxy_check.sh"
+]], appname, appname))
+ for _, check in ipairs(shared_checks) do
+ f_out:write(string.format("\tserver %s %s check %s rise 2 fall 2\n", check.name, check.server, inter_arg))
+ end
+end
+
--控制台配置
local console_user = api.uci_get_c("@global_haproxy[0]", "console_user")
local console_password = api.uci_get_c("@global_haproxy[0]", "console_password")
@@ -234,8 +282,7 @@ f_out:close()
--passwall内置健康检查URL
if health_check_type == "script_logic" then
- local probeUrl = api.uci_get_c("@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()
+ local probeUrl = api.trim(api.uci_get_c("@global_haproxy[0]", "health_probe_url") or "")
+ if probeUrl == "" then probeUrl = "https://www.google.com/generate_204" end
+ api.set_cache_var("HAPROXY_PROBE_URL", probeUrl)
end
diff --git a/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh b/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh
index 3b5700f3..4fc3e78a 100755
--- a/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh
+++ b/luci-app-passwall/root/usr/share/passwall/haproxy_check.sh
@@ -1,7 +1,7 @@
#!/bin/sh
export PATH=/usr/sbin:/usr/bin:/sbin:/bin:/root/bin
-CONFIG=passwall
+. /usr/share/passwall/utils.sh
listen_address=$1
listen_port=$2
@@ -13,19 +13,16 @@ busybox pgrep -af "${CONFIG}/" | grep -E 'app\.sh.*(start|stop)|nftables\.sh|ipt
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
+probeUrl=$(get_cache_var "HAPROXY_PROBE_URL")
+probeUrl="${probeUrl:-https://www.google.com/generate_204}"
-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
+case "$server_address" in
+ \[*\]) ;;
+ *:*) server_address="[$server_address]" ;;
+esac
-status=$(/usr/bin/curl -I -o /dev/null -skL ${extra_params} --connect-timeout 3 --retry 1 --max-time 6 -w "%{http_code}" "${probeUrl}")
+# 每轮只探测一次,连续成功/失败由 HAProxy 的 rise/fall 判定。
+status=$(/usr/bin/curl -q -I -o /dev/null -sk --noproxy "" -x "socks5h://${server_address}:${server_port}" --connect-timeout 3 --max-time 6 -w "%{http_code}" "$probeUrl") || exit 1
case "$status" in
200|204)
diff --git a/luci-app-passwall/root/usr/share/passwall/test.sh b/luci-app-passwall/root/usr/share/passwall/test.sh
index 656c700b..141bcc05 100755
--- a/luci-app-passwall/root/usr/share/passwall/test.sh
+++ b/luci-app-passwall/root/usr/share/passwall/test.sh
@@ -73,7 +73,7 @@ url_test_node() {
fi
sleep 2s
probeUrl=$(config_n_get @global_other[0] 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}")
+ result=$(curl --connect-timeout 3 --max-time 5 -o /dev/null -I -sk -w "%{http_code}:%{time_pretransfer}:%{time_starttransfer}" "$@" -x "$curlx" "${probeUrl}") || result=0
# 结束 SS 插件进程
if [ "$_type" != "socks" ]; then
pid_file="${TMP_PATH}/${test_flag}_plugin.pid"