From 91c7cc9ee4b858f3024a8cc22144f6d6226f8a82 Mon Sep 17 00:00:00 2001 From: kenzok8 Date: Tue, 29 Apr 2025 09:45:01 +0800 Subject: [PATCH] update 2025-04-29 09:45:01 --- .../model/cbi/passwall/client/node_list.lua | 2 + .../cbi/passwall/client/type/sing-box.lua | 10 ++ .../cbi/passwall/server/type/sing-box.lua | 10 ++ luci-app-passwall/luasrc/passwall/api.lua | 4 + .../luasrc/passwall/util_sing-box.lua | 19 ++++ .../passwall/node_list/link_share_man.htm | 95 +++++++++++++++++++ .../root/usr/share/passwall/subscribe.lua | 64 +++++++++++++ 7 files changed, 204 insertions(+) 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 33587b183..05d701908 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 @@ -121,6 +121,8 @@ o.cfgvalue = function(t, n) protocol = "HY" elseif protocol == "hysteria2" then protocol = "HY2" + elseif protocol == "anytls" then + protocol = "AnyTLS" else protocol = protocol:gsub("^%l",string.upper) end 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 23ac5ed90..0fb4acc6a 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 @@ -8,6 +8,9 @@ if not singbox_bin then return end +local local_version = api.get_app_version("sing-box") +local version_ge_1_12_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.12.0") + local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'") local appname = "passwall" @@ -56,6 +59,9 @@ end if singbox_tags:find("with_quic") then o:value("hysteria2", "Hysteria2") end +if version_ge_1_12_0 then + o:value("anytls", "AnyTLS") +end o:value("_urltest", translate("URLTest")) o:value("_shunt", translate("Shunt")) o:value("_iface", translate("Custom Interface")) @@ -248,6 +254,7 @@ o:depends({ [_n("protocol")] = "shadowsocks" }) o:depends({ [_n("protocol")] = "shadowsocksr" }) o:depends({ [_n("protocol")] = "trojan" }) o:depends({ [_n("protocol")] = "tuic" }) +o:depends({ [_n("protocol")] = "anytls" }) o = s:option(ListValue, _n("security"), translate("Encrypt Method")) for a, t in ipairs(security_list) do o:value(t) end @@ -428,6 +435,7 @@ o:depends({ [_n("protocol")] = "vless" }) o:depends({ [_n("protocol")] = "http" }) o:depends({ [_n("protocol")] = "trojan" }) o:depends({ [_n("protocol")] = "shadowsocks" }) +o:depends({ [_n("protocol")] = "anytls" }) o = s:option(ListValue, _n("alpn"), translate("alpn")) o.default = "default" @@ -513,6 +521,7 @@ if singbox_tags:find("with_utls") then o:depends({ [_n("protocol")] = "shadowsocks", [_n("utls")] = true }) o:depends({ [_n("protocol")] = "socks", [_n("utls")] = true }) o:depends({ [_n("protocol")] = "trojan", [_n("utls")] = true }) + o:depends({ [_n("protocol")] = "anytls", [_n("utls")] = true }) o = s:option(Value, _n("reality_publicKey"), translate("Public Key")) o:depends({ [_n("utls")] = true, [_n("reality")] = true }) @@ -731,6 +740,7 @@ o:depends({ [_n("protocol")] = "hysteria" }) o:depends({ [_n("protocol")] = "vless" }) o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "hysteria2" }) +o:depends({ [_n("protocol")] = "anytls" }) o = s:option(ListValue, _n("chain_proxy"), translate("Chain Proxy")) o:value("", translate("Close(Not use)")) diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua index df52d1f45..8b640598b 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/server/type/sing-box.lua @@ -8,6 +8,9 @@ if not singbox_bin then return end +local local_version = api.get_app_version("sing-box") +local version_ge_1_12_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.12.0") + local fs = api.fs local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'") @@ -47,6 +50,9 @@ end if singbox_tags:find("with_quic") then o:value("hysteria2", "Hysteria2") end +if version_ge_1_12_0 then + o:value("anytls", "AnyTLS") +end o:value("direct", "Direct") o = s:option(Value, _n("port"), translate("Listen Port")) @@ -70,6 +76,7 @@ o:depends({ [_n("protocol")] = "http" }) o = s:option(Value, _n("username"), translate("Username")) o:depends({ [_n("auth")] = true }) o:depends({ [_n("protocol")] = "naive" }) +o:depends({ [_n("protocol")] = "anytls" }) o = s:option(Value, _n("password"), translate("Password")) o.password = true @@ -77,6 +84,7 @@ o:depends({ [_n("auth")] = true }) o:depends({ [_n("protocol")] = "shadowsocks" }) o:depends({ [_n("protocol")] = "naive" }) o:depends({ [_n("protocol")] = "tuic" }) +o:depends({ [_n("protocol")] = "anytls" }) if singbox_tags:find("with_quic") then o = s:option(Value, _n("hysteria_up_mbps"), translate("Max upload Mbps")) @@ -220,6 +228,7 @@ o:depends({ [_n("protocol")] = "http" }) o:depends({ [_n("protocol")] = "vmess" }) o:depends({ [_n("protocol")] = "vless" }) o:depends({ [_n("protocol")] = "trojan" }) +o:depends({ [_n("protocol")] = "anytls" }) if singbox_tags:find("with_reality_server") then -- [[ REALITY部分 ]] -- @@ -229,6 +238,7 @@ if singbox_tags:find("with_reality_server") then o:depends({ [_n("protocol")] = "vmess", [_n("tls")] = true }) o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true }) o:depends({ [_n("protocol")] = "trojan", [_n("tls")] = true }) + o:depends({ [_n("protocol")] = "anytls", [_n("tls")] = true }) o = s:option(Value, _n("reality_private_key"), translate("Private Key")) o:depends({ [_n("reality")] = true }) diff --git a/luci-app-passwall/luasrc/passwall/api.lua b/luci-app-passwall/luasrc/passwall/api.lua index 99a949343..3c86dc8b5 100644 --- a/luci-app-passwall/luasrc/passwall/api.lua +++ b/luci-app-passwall/luasrc/passwall/api.lua @@ -522,6 +522,8 @@ function get_valid_nodes() protocol = "HY" elseif protocol == "hysteria2" then protocol = "HY2" + elseif protocol == "anytls" then + protocol = "AnyTLS" else protocol = protocol:gsub("^%l",string.upper) end @@ -566,6 +568,8 @@ function get_node_remarks(n) protocol = "HY" elseif protocol == "hysteria2" then protocol = "HY2" + elseif protocol == "anytls" then + protocol = "AnyTLS" else protocol = protocol:gsub("^%l",string.upper) end diff --git a/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/luci-app-passwall/luasrc/passwall/util_sing-box.lua index 5372fbea6..20bdd42a1 100644 --- a/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -441,6 +441,13 @@ function gen_outbound(flag, node, tag, proxy_table) } end + if node.protocol == "anytls" then + protocol_table = { + password = (node.password and node.password ~= "") and node.password or "", + tls = tls + } + end + if protocol_table then for key, value in pairs(protocol_table) do result[key] = value @@ -730,6 +737,18 @@ function gen_config_server(node) } end + if node.protocol == "anytls" then + protocol_table = { + users = { + { + name = (node.username and node.username ~= "") and node.username or "sekai", + password = node.password + } + }, + tls = tls, + } + end + if node.protocol == "direct" then protocol_table = { network = (node.d_protocol ~= "TCP,UDP") and node.d_protocol or nil, diff --git a/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm b/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm index 9b650af00..0b90a451e 100644 --- a/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm +++ b/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm @@ -546,6 +546,41 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin params += opt.query("congestion_control", dom_prefix + "tuic_congestion_control"); params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure"); + params += "#" + encodeURI(v_alias.value); + if (params[0] == "&") { + params = params.substring(1); + } + url += params; + } else if (v_type === "sing-box" && opt.get(dom_prefix + "protocol").value === "anytls") { + protocol = "anytls"; + var v_password = opt.get(dom_prefix + "password"); + var v_port = opt.get(dom_prefix + "port"); + url = encodeURIComponent(v_password.value) + + "@" + _address + + ":" + v_port.value + "?"; + + var params = ""; + if (opt.get(dom_prefix + "tls").checked) { + var v_security = "tls"; + if (opt.get(dom_prefix + "fingerprint") && opt.get(dom_prefix + "fingerprint").value != "") { + let v_fp = opt.get(dom_prefix + "fingerprint").value; + params += "&fp=" + v_fp; + } + if (opt.get(dom_prefix + "reality") && opt.get(dom_prefix + "reality").checked) { + v_security = "reality"; + if (opt.get(dom_prefix + "fingerprint") && opt.get(dom_prefix + "fingerprint").value != "") { + let v_fp = opt.get(dom_prefix + "fingerprint").value; + params += "&fp=" + v_fp; + } + params += opt.query("pbk", dom_prefix + "reality_publicKey"); + params += opt.query("sid", dom_prefix + "reality_shortId"); + } + params += "&security=" + v_security; + params += opt.query("alpn", dom_prefix + "alpn"); + params += opt.query("sni", dom_prefix + "tls_serverName"); + params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure"); + } + params += "#" + encodeURI(v_alias.value); if (params[0] == "&") { params = params.substring(1); @@ -1373,6 +1408,66 @@ local hysteria2_type = map:get("@global_subscribe[0]", "hysteria2_type") or "sin opt.set('remarks', decodeURIComponent(hash.substr(1))); } } + if (ssu[0] === "anytls") { + if (has_singbox) { + dom_prefix = "singbox_" + opt.set('type', "sing-box"); + } + opt.set(dom_prefix + 'protocol', "anytls"); + var m = parseNodeUrl(ssrurl); + var password = m.passwd; + if (password === "") { + s.innerHTML = "<%:Invalid Share URL Format%>"; + return false; + } + opt.set(dom_prefix + 'password', password); + opt.set(dom_prefix + 'address', m.hostname); + opt.set(dom_prefix + 'port', m.port || "443"); + var queryParam = {}; + if (m.search.length > 1) { + var query = m.search.replace('/?', '?').split('?') + var queryParams = query[1]; + var queryArray = queryParams.split('&'); + var params; + for (i = 0; i < queryArray.length; i++) { + params = queryArray[i].split('='); + queryParam[decodeURIComponent(params[0])] = decodeURIComponent(params[1] || ''); + } + } + if (queryParam.security) { + if (queryParam.security == "tls") { + opt.set(dom_prefix + 'tls', true); + opt.set(dom_prefix + 'reality', false); + opt.set(dom_prefix + 'flow', queryParam.flow || ''); + opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); + opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); + opt.set(dom_prefix + 'tls_allowInsecure', true); + if (queryParam.allowinsecure === '0' || queryParam.insecure === '0') { + opt.set(dom_prefix + 'tls_allowInsecure', false); + } + if (queryParam.fp && queryParam.fp.trim() != "") { + opt.set(dom_prefix + 'utls', true); + opt.set(dom_prefix + 'fingerprint', queryParam.fp); + } + } + if (queryParam.security == "reality") { + opt.set(dom_prefix + 'tls', true); + opt.set(dom_prefix + 'reality', true); + opt.set(dom_prefix + 'flow', queryParam.flow || ''); + opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); + opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); + if (queryParam.fp && queryParam.fp.trim() != "") { + opt.set(dom_prefix + 'utls', true); + opt.set(dom_prefix + 'fingerprint', queryParam.fp); + } + opt.set(dom_prefix + 'reality_publicKey', queryParam.pbk || ''); + opt.set(dom_prefix + 'reality_shortId', queryParam.sid || ''); + } + } + if (m.hash) { + opt.set('remarks', decodeURIComponent(m.hash.substr(1))); + } + } if (dom_prefix && dom_prefix != null) { if (opt.get(dom_prefix + 'port').value) { opt.get(dom_prefix + 'port').focus(); diff --git a/luci-app-passwall/root/usr/share/passwall/subscribe.lua b/luci-app-passwall/root/usr/share/passwall/subscribe.lua index 60a786429..0226529f8 100755 --- a/luci-app-passwall/root/usr/share/passwall/subscribe.lua +++ b/luci-app-passwall/root/usr/share/passwall/subscribe.lua @@ -1290,6 +1290,70 @@ local function processData(szType, content, add_mode, add_from) end result.type = 'sing-box' result.protocol = "tuic" + elseif szType == "anytls" then + result.type = 'sing-box' + result.protocol = "anytls" + local alias = "" + if content:find("#") then + local idx_sp = content:find("#") + alias = content:sub(idx_sp + 1, -1) + content = content:sub(0, idx_sp - 1) + end + result.remarks = UrlDecode(alias) + if content:find("@") then + local Info = split(content, "@") + result.password = UrlDecode(Info[1]) + local port = "443" + Info[2] = (Info[2] or ""):gsub("/%?", "?") + local query = split(Info[2], "?") + local host_port = query[1] + local params = {} + for _, v in pairs(split(query[2], '&')) do + local t = split(v, '=') + params[t[1]] = UrlDecode(t[2]) + end + -- [2001:4860:4860::8888]:443 + -- 8.8.8.8:443 + if host_port:find(":") then + local sp = split(host_port, ":") + port = sp[#sp] + if api.is_ipv6addrport(host_port) then + result.address = api.get_ipv6_only(host_port) + else + result.address = sp[1] + end + else + result.address = host_port + end + result.tls = "0" + if params.security == "tls" or params.security == "reality" then + result.tls = "1" + result.tls_serverName = (params.sni and params.sni ~= "") and params.sni or params.host + result.alpn = params.alpn + if params.fp and params.fp ~= "" then + result.utls = "1" + result.fingerprint = params.fp + end + if params.security == "reality" then + result.reality = "1" + result.reality_publicKey = params.pbk or nil + result.reality_shortId = params.sid or nil + end + end + result.port = port + params.allowinsecure = params.allowinsecure or params.insecure + if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then + result.tls_allowInsecure = params.allowinsecure + else + result.tls_allowInsecure = allowInsecure_default and "1" or "0" + end + local singbox_version = api.get_app_version("sing-box") + local version_ge_1_12 = api.compare_versions(singbox_version:match("[^v]+"), ">=", "1.12.0") + if not has_singbox or not version_ge_1_12 then + log("跳过节点:" .. result.remarks ..",因" .. szType .. "类型的节点需要 Sing-Box 1.12 以上版本支持。") + return nil + end + end else log('暂时不支持' .. szType .. "类型的节点订阅,跳过此节点。") return nil