🦄 Sync 2026-04-02 23:48:06

This commit is contained in:
github-actions[bot] committed 2026-04-02 23:48:06 +08:00
1 parent a794220143
commit c361065f64
35 files changed
+958 -537

No files matched your search

@@ -1244,6 +1244,7 @@ return view.extend({
so.modalonly = true; so.modalonly = true;
so = ss.taboption('field_transport', form.ListValue, 'transport_xhttp_mode', _('XHTTP mode')); so = ss.taboption('field_transport', form.ListValue, 'transport_xhttp_mode', _('XHTTP mode'));
so.value('auto', _('Auto'));
so.value('stream-one', _('stream-one')); so.value('stream-one', _('stream-one'));
so.value('stream-up', _('stream-up')); so.value('stream-up', _('stream-up'));
so.value('packet-up', _('packet-up')); so.value('packet-up', _('packet-up'));
+1 -1
View File
@@ -1953,7 +1953,7 @@ msgstr "混淆类型"
#: htdocs/luci-static/resources/view/fchomo/node.js:408 #: htdocs/luci-static/resources/view/fchomo/node.js:408
#: htdocs/luci-static/resources/view/fchomo/node.js:409 #: htdocs/luci-static/resources/view/fchomo/node.js:409
msgid "Obfuscated as %s" msgid "Obfuscated as %s"
msgstr "混淆为" msgstr "混淆为%s"
#: htdocs/luci-static/resources/view/fchomo/global.js:880 #: htdocs/luci-static/resources/view/fchomo/global.js:880
msgid "One or more numbers in the range 0-63 separated by commas" msgid "One or more numbers in the range 0-63 separated by commas"
+1 -1
View File
@@ -1953,7 +1953,7 @@ msgstr "混淆類型"
#: htdocs/luci-static/resources/view/fchomo/node.js:408 #: htdocs/luci-static/resources/view/fchomo/node.js:408
#: htdocs/luci-static/resources/view/fchomo/node.js:409 #: htdocs/luci-static/resources/view/fchomo/node.js:409
msgid "Obfuscated as %s" msgid "Obfuscated as %s"
msgstr "混淆為" msgstr "混淆為%s"
#: htdocs/luci-static/resources/view/fchomo/global.js:880 #: htdocs/luci-static/resources/view/fchomo/global.js:880
msgid "One or more numbers in the range 0-63 separated by commas" msgid "One or more numbers in the range 0-63 separated by commas"
@@ -1714,7 +1714,7 @@ local current_node = map:get(section)
queryParam[decodeURIComponent(params[0]).toLowerCase()] = decodeURIComponent(params[1] || ''); queryParam[decodeURIComponent(params[0]).toLowerCase()] = decodeURIComponent(params[1] || '');
} }
} }
if ((!queryParam.security || queryParam.security == "") && queryParam.sni && queryParam.sni != "") { if (!queryParam.security || queryParam.security == "") {
queryParam.security = "tls"; queryParam.security = "tls";
} }
if (queryParam.security) { if (queryParam.security) {
@@ -1490,7 +1490,7 @@ local function processData(szType, content, add_mode, group)
result.address = host_port result.address = host_port
end end
result.tls = "0" result.tls = "0"
if (not params.security or params.security == "") and params.sni and params.sni ~= "" then if not params.security or params.security == "" then
params.security = "tls" params.security = "tls"
end end
if params.security == "tls" or params.security == "reality" then if params.security == "tls" or params.security == "reality" then
+1 -1
View File
@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2 PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.3.5 PKG_VERSION:=26.4.2
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)
@@ -459,17 +459,11 @@ function copy_node()
local uuid = api.gen_short_uuid() local uuid = api.gen_short_uuid()
uci:section(appname, "nodes", uuid) uci:section(appname, "nodes", uuid)
for k, v in pairs(uci:get_all(appname, section)) do for k, v in pairs(uci:get_all(appname, section)) do
local filter = k:find("%.") if not k:match("^%.") and k ~= "group" then
if filter and filter == 1 then if k == "remarks" then v = (v or "") .. "(1)" end
else uci:set(appname, uuid, k, v)
xpcall(function()
uci:set(appname, uuid, k, v)
end,
function(e)
end)
end end
end end
uci:delete(appname, uuid, "group")
uci:set(appname, uuid, "add_mode", 1) uci:set(appname, uuid, "add_mode", 1)
api.uci_save(uci, appname) api.uci_save(uci, appname)
http.redirect(api.url("node_config", uuid)) http.redirect(api.url("node_config", uuid))
@@ -95,7 +95,7 @@ current_node = current_node_id and m.uci:get_all(appname, current_node_id) or {}
-- Shunt Start -- Shunt Start
if (has_singbox or has_xray) and #nodes_table > 0 then if (has_singbox or has_xray) and #nodes_table > 0 then
if #normal_list > 0 then if #normal_list > 0 or #iface_list > 0 then
if current_node.protocol == "_shunt" then if current_node.protocol == "_shunt" then
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua") local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s, { setfenv(shunt_lua, getfenv(1))(m, s, {
@@ -112,7 +112,7 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
}) })
end end
else else
local tips = s:taboption("Main", DummyValue, "tips", " ") local tips = s:taboption("Main", DummyValue, "tips", " ")
tips.rawhtml = true tips.rawhtml = true
tips.cfgvalue = function(t, n) tips.cfgvalue = function(t, n)
return string.format('<a style="color: red">%s</a>', translate("There are no available nodes, please add or subscribe nodes first.")) return string.format('<a style="color: red">%s</a>', translate("There are no available nodes, please add or subscribe nodes first."))
@@ -351,6 +351,9 @@ end
local o_node = s.fields["node"] local o_node = s.fields["node"]
local o_socks = s2.fields["node"] local o_socks = s2.fields["node"]
for k, v in pairs(nodes_table) do for k, v in pairs(nodes_table) do
if #normal_list == 0 and #iface_list == 0 then
break
end
o_node:value(v.id, v["remark"]) o_node:value(v.id, v["remark"])
o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") o_node.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
o_socks:value(v.id, v["remark"]) o_socks:value(v.id, v["remark"])
@@ -9,6 +9,9 @@ if not arg[1] or not m:get(arg[1]) then
luci.http.redirect(api.url("node_list")) luci.http.redirect(api.url("node_list"))
end end
fs = require "nixio.fs"
formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "."
local header = Template(appname .. "/node_config/header") local header = Template(appname .. "/node_config/header")
header.api = api header.api = api
header.section = arg[1] header.section = arg[1]
@@ -61,12 +64,18 @@ o.write = function(self, section, value)
m:set(section, self.option, value) m:set(section, self.option, value)
end end
local fs = require "nixio.fs"
local types_dir = "/usr/lib/lua/luci/model/cbi/passwall2/client/type/" local types_dir = "/usr/lib/lua/luci/model/cbi/passwall2/client/type/"
s.val = {} s.val = {}
s.val["type"] = m.uci:get(appname, arg[1], "type") s.val["type"] = m.uci:get(appname, arg[1], "type")
s.val["protocol"] = m.uci:get(appname, arg[1], "protocol") s.val["protocol"] = m.uci:get(appname, arg[1], "protocol")
if luci.http.formvalue("cbi.submit") == "1" then
local formvalue_type = luci.http.formvalue(formvalue_key .. "type")
if formvalue_type then
s.val["type"] = formvalue_type
end
end
o = s:option(ListValue, "type", translate("Type")) o = s:option(ListValue, "type", translate("Type"))
local type_table = {} local type_table = {}
@@ -127,9 +127,11 @@ o.validate = function(self, value)
return value:gsub("%s+", ""):gsub("%z", "") return value:gsub("%s+", ""):gsub("%z", "")
end end
o = s:option(Flag, "allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.")) o = s:option(Flag, "allowInsecure", translate("allowInsecure"))
o.default = "1" o.default = "0"
o.rmempty = false o.rmempty = false
o.description = translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.") .. "<br>" ..
translate("Used when the node link does not include this parameter.")
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode")) o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o.default = "5" o.default = "5"
@@ -168,14 +168,17 @@ if has_xray then
o:depends("fragment", true) o:depends("fragment", true)
o = s_xray:option(Value, "fragment_length", translate("Fragment Length"), translate("Fragmented packet length (byte)")) o = s_xray:option(Value, "fragment_length", translate("Fragment Length"), translate("Fragmented packet length (byte)"))
o.datatype = "or(uinteger,portrange)"
o.default = "100-200" o.default = "100-200"
o:depends("fragment", true) o:depends("fragment", true)
o = s_xray:option(Value, "fragment_interval", translate("Fragment Interval"), translate("Fragmentation interval (ms)")) o = s_xray:option(Value, "fragment_delay", translate("Fragment Delay"), translate("Fragmentation interval (ms)"))
o.datatype = "or(uinteger,portrange)"
o.default = "10-20" o.default = "10-20"
o:depends("fragment", true) o:depends("fragment", true)
o = s_xray:option(Value, "fragment_maxSplit", translate("Max Split"), translate("Limit the maximum number of splits.")) o = s_xray:option(Value, "fragment_maxSplit", translate("Max Split"), translate("Limit the maximum number of splits."))
o.datatype = "or(uinteger,portrange)"
o.default = "100-200" o.default = "100-200"
o:depends("fragment", true) o:depends("fragment", true)
@@ -225,22 +228,18 @@ if has_xray then
o = s_xray_noise:option(ListValue, "type", translate("Type")) o = s_xray_noise:option(ListValue, "type", translate("Type"))
o:value("rand", "rand") o:value("rand", "rand")
o:value("array", "array")
o:value("str", "str") o:value("str", "str")
o:value("hex", "hex") o:value("hex", "hex")
o:value("base64", "base64") o:value("base64", "base64")
o = s_xray_noise:option(Value, "packet", translate("Packet")) o = s_xray_noise:option(Value, "packet", translate("Packet | Rand Length"))
o.datatype = "minlength(1)" o.datatype = "minlength(1)"
o.rmempty = false o.rmempty = false
o = s_xray_noise:option(Value, "delay", translate("Delay (ms)")) o = s_xray_noise:option(Value, "delay", translate("Delay (ms)"))
o.datatype = "or(uinteger,portrange)" o.datatype = "or(uinteger,portrange)"
o.rmempty = false o.rmempty = false
o = s_xray_noise:option(ListValue, "applyTo", translate("IP Type"))
o:value("ip", "ALL")
o:value("ipv4", "IPv4")
o:value("ipv6", "IPv6")
end end
if has_singbox then if has_singbox then
@@ -20,9 +20,6 @@ local function _n(name)
return option_prefix .. name return option_prefix .. name
end end
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
o:value("udp", "UDP")
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
o = s:option(Value, _n("port"), translate("Port")) o = s:option(Value, _n("port"), translate("Port"))
@@ -67,7 +64,10 @@ o.rewrite_option = o.option
o = s:option(Value, _n("recv_window_conn"), translate("QUIC connection receive window")) o = s:option(Value, _n("recv_window_conn"), translate("QUIC connection receive window"))
o.rewrite_option = o.option o.rewrite_option = o.option
o = s:option(Value, _n("idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s-120s)") o = s:option(Value, _n("idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s~120s)")
o.rewrite_option = o.option
o = s:option(Value, _n("keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Example:") .. "10s (2s~60s)")
o.rewrite_option = o.option o.rewrite_option = o.option
o = s:option(Flag, _n("disable_mtu_discovery"), translate("Disable MTU detection")) o = s:option(Flag, _n("disable_mtu_discovery"), translate("Disable MTU detection"))
@@ -25,7 +25,6 @@ local function _n(name)
return option_prefix .. name return option_prefix .. name
end end
local formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "."
local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol")) local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol"))
if formvalue_proto then s.val["protocol"] = formvalue_proto end if formvalue_proto then s.val["protocol"] = formvalue_proto end
@@ -326,7 +325,10 @@ o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
o.password = true o.password = true
o:depends({ [_n("protocol")] = "hysteria2"}) o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s-120s)") o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s~120s)")
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Value, _n("hysteria2_keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Example:") .. "10s (2s~60s)")
o:depends({ [_n("protocol")] = "hysteria2"}) o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Flag, _n("hysteria2_disable_mtu_discovery"), translate("Disable MTU detection")) o = s:option(Flag, _n("hysteria2_disable_mtu_discovery"), translate("Disable MTU detection"))
@@ -368,7 +370,7 @@ o:depends({ [_n("protocol")] = "hysteria2" })
-- o:value("1.3") -- o:value("1.3")
-- o:depends({ [_n("tls")] = true }) -- o:depends({ [_n("tls")] = true })
o = s:option(Value, _n("tls_serverName"), translate("Domain")) o = s:option(Value, _n("tls_serverName"), "SNI " .. translate("Domain"))
o:depends({ [_n("tls")] = true }) o:depends({ [_n("tls")] = true })
o:depends({ [_n("protocol")] = "hysteria2" }) o:depends({ [_n("protocol")] = "hysteria2" })
@@ -404,7 +406,7 @@ o.validate = function(self, value)
end end
o = s:option(ListValue, _n("ech_ForceQuery"), translate("ECH Query Policy"), translate("Controls the policy used when performing DNS queries for ECH configuration.")) o = s:option(ListValue, _n("ech_ForceQuery"), translate("ECH Query Policy"), translate("Controls the policy used when performing DNS queries for ECH configuration."))
o.default = "none" o.default = "full"
o:value("none") o:value("none")
o:value("half") o:value("half")
o:value("full") o:value("full")
@@ -687,10 +689,12 @@ o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "wireguard" }) o:depends({ [_n("protocol")] = "wireguard" })
o:depends({ [_n("protocol")] = "hysteria2" }) o:depends({ [_n("protocol")] = "hysteria2" })
o = s:option(TextValue, _n("finalmask"), " ", translate("An FinalMaskObject in JSON format, used for sharing.")) o = s:option(TextValue, _n("finalmask"), " ")
o:depends({ [_n("use_finalmask")] = true }) o:depends({ [_n("use_finalmask")] = true })
o.rows = 10 o.rows = 10
o.wrap = "off" o.wrap = "off"
o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "<br>" ..
translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.")
o.custom_cfgvalue = function(self, section, value) o.custom_cfgvalue = function(self, section, value)
local raw = m:get(section, "finalmask") local raw = m:get(section, "finalmask")
if raw then if raw then
@@ -25,7 +25,6 @@ local function _n(name)
return option_prefix .. name return option_prefix .. name
end end
local formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "."
local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol")) local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol"))
if formvalue_proto then s.val["protocol"] = formvalue_proto end if formvalue_proto then s.val["protocol"] = formvalue_proto end
@@ -68,6 +67,9 @@ if singbox_tags:find("with_quic") then
end end
o:value("anytls", "AnyTLS") o:value("anytls", "AnyTLS")
o:value("ssh", "SSH") o:value("ssh", "SSH")
if singbox_tags:find("with_naive_outbound") then
o:value("naive", "NaïveProxy")
end
o:value("_urltest", translate("URLTest")) o:value("_urltest", translate("URLTest"))
o:value("_shunt", translate("Shunt")) o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface")) o:value("_iface", translate("Custom Interface"))
@@ -227,6 +229,7 @@ o = s:option(Value, _n("username"), translate("Username"))
o:depends({ [_n("protocol")] = "http" }) o:depends({ [_n("protocol")] = "http" })
o:depends({ [_n("protocol")] = "socks" }) o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "ssh" }) o:depends({ [_n("protocol")] = "ssh" })
o:depends({ [_n("protocol")] = "naive" })
o = s:option(Value, _n("password"), translate("Password")) o = s:option(Value, _n("password"), translate("Password"))
o.password = true o.password = true
@@ -238,6 +241,7 @@ o:depends({ [_n("protocol")] = "trojan" })
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "anytls" }) o:depends({ [_n("protocol")] = "anytls" })
o:depends({ [_n("protocol")] = "ssh" }) o:depends({ [_n("protocol")] = "ssh" })
o:depends({ [_n("protocol")] = "naive" })
o = s:option(ListValue, _n("security"), translate("Encrypt Method")) o = s:option(ListValue, _n("security"), translate("Encrypt Method"))
for a, t in ipairs(security_list) do o:value(t) end for a, t in ipairs(security_list) do o:value(t) end
@@ -285,6 +289,7 @@ end
o = s:option(Flag, _n("uot"), translate("UDP over TCP")) o = s:option(Flag, _n("uot"), translate("UDP over TCP"))
o:depends({ [_n("protocol")] = "socks" }) o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "shadowsocks" }) o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "naive" })
o = s:option(Value, _n("alter_id"), "Alter ID") o = s:option(Value, _n("alter_id"), "Alter ID")
o.datatype = "uinteger" o.datatype = "uinteger"
@@ -345,9 +350,6 @@ if singbox_tags:find("with_quic") then
o = s:option(Flag, _n("hysteria_disable_mtu_discovery"), translate("Disable MTU detection")) o = s:option(Flag, _n("hysteria_disable_mtu_discovery"), translate("Disable MTU detection"))
o:depends({ [_n("protocol")] = "hysteria" }) o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(Value, _n("hysteria_alpn"), translate("QUIC TLS ALPN"))
o:depends({ [_n("protocol")] = "hysteria" })
end end
if singbox_tags:find("with_quic") then if singbox_tags:find("with_quic") then
@@ -387,6 +389,8 @@ if singbox_tags:find("with_quic") then
o:value("http/1.1") o:value("http/1.1")
o:value("h2,http/1.1") o:value("h2,http/1.1")
o:value("h3,h2,http/1.1") o:value("h3,h2,http/1.1")
o:value("spdy/3.1")
o:value("h3,spdy/3.1")
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
end end
@@ -437,6 +441,26 @@ o = s:option(Value, _n("ssh_client_version"), translate("Client Version"), trans
o:depends({ [_n("protocol")] = "ssh" }) o:depends({ [_n("protocol")] = "ssh" })
-- [[ SSH config end ]] -- -- [[ SSH config end ]] --
-- [[ naive start ]] --
o = s:option(Value, _n("naive_insecure_concurrency"), translate("Concurrent Tunnels"))
o.datatype = "uinteger"
o.placeholder = "0"
o.default = "0"
o:depends({ [_n("protocol")] = "naive" })
o = s:option(Flag, _n("naive_quic"), translate("QUIC"))
o.default = 0
o:depends({ [_n("protocol")] = "naive" })
o = s:option(ListValue, _n("naive_congestion_control"), translate("Congestion control algorithm"))
o.default = "bbr"
o:value("bbr", translate("BBR"))
o:value("bbr2", translate("BBRv2"))
o:value("cubic", translate("CUBIC"))
o:value("reno", translate("New Reno"))
o:depends({ [_n("naive_quic")] = "1" })
-- [[ naive end ]] --
o = s:option(Flag, _n("tls"), translate("TLS")) o = s:option(Flag, _n("tls"), translate("TLS"))
o.default = 0 o.default = 0
o:depends({ [_n("protocol")] = "vmess" }) o:depends({ [_n("protocol")] = "vmess" })
@@ -446,7 +470,7 @@ o:depends({ [_n("protocol")] = "trojan" })
o:depends({ [_n("protocol")] = "shadowsocks" }) o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "anytls" }) o:depends({ [_n("protocol")] = "anytls" })
o = s:option(ListValue, _n("alpn"), translate("alpn")) o = s:option(ListValue, _n("alpn"), translate("ALPN"))
o.default = "default" o.default = "default"
o:value("default", translate("Default")) o:value("default", translate("Default"))
o:value("h3") o:value("h3")
@@ -456,6 +480,7 @@ o:value("http/1.1")
o:value("h2,http/1.1") o:value("h2,http/1.1")
o:value("h3,h2,http/1.1") o:value("h3,h2,http/1.1")
o:depends({ [_n("tls")] = true }) o:depends({ [_n("tls")] = true })
o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(Flag, _n("tls_disable_sni"), translate("Disable SNI"), translate("Do not send server name in ClientHello.")) o = s:option(Flag, _n("tls_disable_sni"), translate("Disable SNI"), translate("Do not send server name in ClientHello."))
o.default = "0" o.default = "0"
@@ -463,14 +488,13 @@ o:depends({ [_n("tls")] = true })
o:depends({ [_n("protocol")] = "hysteria"}) o:depends({ [_n("protocol")] = "hysteria"})
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "hysteria2" }) o:depends({ [_n("protocol")] = "hysteria2" })
o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(Value, _n("tls_serverName"), translate("Domain")) o = s:option(Value, _n("tls_serverName"), "SNI " .. translate("Domain"))
o:depends({ [_n("tls")] = true }) o:depends({ [_n("tls")] = true })
o:depends({ [_n("protocol")] = "hysteria"}) o:depends({ [_n("protocol")] = "hysteria"})
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "hysteria2" }) o:depends({ [_n("protocol")] = "hysteria2" })
o:depends({ [_n("protocol")] = "shadowsocks" }) o:depends({ [_n("protocol")] = "naive" })
o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.")) o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
o.default = "0" o.default = "0"
@@ -478,7 +502,6 @@ o:depends({ [_n("tls")] = true })
o:depends({ [_n("protocol")] = "hysteria"}) o:depends({ [_n("protocol")] = "hysteria"})
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "hysteria2" }) o:depends({ [_n("protocol")] = "hysteria2" })
o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(Flag, _n("ech"), translate("ECH")) o = s:option(Flag, _n("ech"), translate("ECH"))
o.default = "0" o.default = "0"
@@ -486,6 +509,7 @@ o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "hysteria" }) o:depends({ [_n("protocol")] = "hysteria" })
o:depends({ [_n("protocol")] = "hysteria2" }) o:depends({ [_n("protocol")] = "hysteria2" })
o:depends({ [_n("protocol")] = "naive" })
o = s:option(TextValue, _n("ech_config"), translate("ECH Config")) o = s:option(TextValue, _n("ech_config"), translate("ECH Config"))
o.default = "" o.default = ""
@@ -501,6 +525,9 @@ o.validate = function(self, value)
return value return value
end end
o = s:option(Value, _n("ech_query_server_name"), translate("ECH Query Domain"), translate("Overrides the domain name used for ECH HTTPS record queries."))
o:depends({ [_n("ech")] = true })
if singbox_tags:find("with_utls") then if singbox_tags:find("with_utls") then
o = s:option(Flag, _n("utls"), translate("uTLS")) o = s:option(Flag, _n("utls"), translate("uTLS"))
o.default = "0" o.default = "0"
@@ -666,6 +693,7 @@ o:depends({ [_n("tcp_guise")] = "http" })
o:depends({ [_n("transport")] = "http" }) o:depends({ [_n("transport")] = "http" })
o:depends({ [_n("transport")] = "ws" }) o:depends({ [_n("transport")] = "ws" })
o:depends({ [_n("transport")] = "httpupgrade" }) o:depends({ [_n("transport")] = "httpupgrade" })
o:depends({ [_n("protocol")] = "naive" })
-- [[ Mux ]]-- -- [[ Mux ]]--
o = s:option(Flag, _n("mux"), translate("Mux")) o = s:option(Flag, _n("mux"), translate("Mux"))
@@ -128,8 +128,18 @@ o.default = 0
o.rmempty = true o.rmempty = true
o.rewrite_option = o.option o.rewrite_option = o.option
o = s:option(DynamicList, _n("tls_alpn"), translate("TLS ALPN")) o = s:option(ListValue, _n("tls_alpn"), translate("TLS ALPN"))
o.rmempty = true o.rmempty = true
o.default = ""
o:value("", translate("Default"))
o:value("h3")
o:value("h2")
o:value("h3,h2")
o:value("http/1.1")
o:value("h2,http/1.1")
o:value("h3,h2,http/1.1")
o:value("spdy/3.1")
o:value("h3,spdy/3.1")
o.rewrite_option = o.option o.rewrite_option = o.option
api.luci_types(arg[1], m, s, type_name, option_prefix) api.luci_types(arg[1], m, s, type_name, option_prefix)
@@ -38,6 +38,7 @@ o:value("http", "HTTP")
o:value("socks", "Socks") o:value("socks", "Socks")
o:value("shadowsocks", "Shadowsocks") o:value("shadowsocks", "Shadowsocks")
o:value("trojan", "Trojan") o:value("trojan", "Trojan")
o:value("hysteria2", translate("Hysteria2"))
o:value("dokodemo-door", "dokodemo-door") o:value("dokodemo-door", "dokodemo-door")
o:depends({ [_n("custom")] = false }) o:depends({ [_n("custom")] = false })
@@ -123,6 +124,30 @@ o:value("", translate("Disable"))
o:value("xtls-rprx-vision") o:value("xtls-rprx-vision")
o:depends({ [_n("protocol")] = "vless" }) o:depends({ [_n("protocol")] = "vless" })
---- [[ hysteria2 ]]
o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
o.password = true
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Flag, _n("hysteria2_ignore_client_bandwidth"), translate("Client BBR Flow Control"))
o.default = 0
o:depends({ [_n("protocol")] = "hysteria2" })
o = s:option(Value, _n("hysteria2_up_mbps"), translate("Max upload Mbps"))
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_ignore_client_bandwidth")] = false })
o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
o:depends({ [_n("protocol")] = "hysteria2", [_n("hysteria2_ignore_client_bandwidth")] = false })
o = s:option(ListValue, _n("hysteria2_obfs_type"), translate("Obfs Type"))
o:value("", translate("Disable"))
o:value("salamander")
o:depends({ [_n("protocol")] = "hysteria2" })
o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password"))
o:depends({ [_n("hysteria2_obfs_type")] = "salamander" })
---- [[ TLS ]]
o = s:option(Flag, _n("tls"), translate("TLS")) o = s:option(Flag, _n("tls"), translate("TLS"))
o.default = 0 o.default = 0
o.validate = function(self, value, t) o.validate = function(self, value, t)
@@ -191,6 +216,7 @@ o:value("http/1.1")
o:value("h2,http/1.1") o:value("h2,http/1.1")
o:value("h3,h2,http/1.1") o:value("h3,h2,http/1.1")
o:depends({ [_n("tls")] = true, [_n("reality")] = false }) o:depends({ [_n("tls")] = true, [_n("reality")] = false })
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Flag, _n("use_mldsa65Seed"), translate("ML-DSA-65")) o = s:option(Flag, _n("use_mldsa65Seed"), translate("ML-DSA-65"))
o.default = "0" o.default = "0"
@@ -215,6 +241,7 @@ o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolu
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem" o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
o:depends({ [_n("tls")] = true, [_n("reality")] = false }) o:depends({ [_n("tls")] = true, [_n("reality")] = false })
o:depends({ [_n("protocol")] = "hysteria2"})
o.validate = function(self, value, t) o.validate = function(self, value, t)
if value and value ~= "" then if value and value ~= "" then
if not nixio.fs.access(value) then if not nixio.fs.access(value) then
@@ -230,6 +257,7 @@ o = s:option(FileUpload, _n("tls_keyFile"), translate("Private key absolute path
o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key" o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key"
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
o:depends({ [_n("tls")] = true, [_n("reality")] = false }) o:depends({ [_n("tls")] = true, [_n("reality")] = false })
o:depends({ [_n("protocol")] = "hysteria2"})
o.validate = function(self, value, t) o.validate = function(self, value, t)
if value and value ~= "" then if value and value ~= "" then
if not nixio.fs.access(value) then if not nixio.fs.access(value) then
@@ -402,7 +430,10 @@ o:depends({ [_n("custom")] = false })
--[[Fast Open]] --[[Fast Open]]
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open")) o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
o.default = "0" o.default = "0"
o:depends({ [_n("custom")] = false }) o:depends({ [_n("protocol")] = "vmess", [_n("custom")] = false })
o:depends({ [_n("protocol")] = "vless", [_n("custom")] = false })
o:depends({ [_n("protocol")] = "shadowsocks", [_n("custom")] = false })
o:depends({ [_n("protocol")] = "trojan", [_n("custom")] = false })
-- [[ Fallback ]]-- -- [[ Fallback ]]--
o = s:option(Flag, _n("fallback"), translate("Fallback")) o = s:option(Flag, _n("fallback"), translate("Fallback"))
@@ -123,9 +123,6 @@ if singbox_tags:find("with_quic") then
o = s:option(Flag, _n("hysteria_disable_mtu_discovery"), translate("Disable MTU detection")) o = s:option(Flag, _n("hysteria_disable_mtu_discovery"), translate("Disable MTU detection"))
o:depends({ [_n("protocol")] = "hysteria" }) o:depends({ [_n("protocol")] = "hysteria" })
o = s:option(Value, _n("hysteria_alpn"), translate("QUIC TLS ALPN"))
o:depends({ [_n("protocol")] = "hysteria" })
end end
if singbox_tags:find("with_quic") then if singbox_tags:find("with_quic") then
@@ -145,12 +142,26 @@ if singbox_tags:find("with_quic") then
o.default = "3" o.default = "3"
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
o = s:option(Value, _n("tuic_alpn"), translate("QUIC TLS ALPN")) o = s:option(ListValue, _n("tuic_alpn"), translate("QUIC TLS ALPN"))
o.default = "default"
o:value("default", translate("Default"))
o:value("h3")
o:value("h2")
o:value("h3,h2")
o:value("http/1.1")
o:value("h2,http/1.1")
o:value("h3,h2,http/1.1")
o:value("spdy/3.1")
o:value("h3,spdy/3.1")
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
end end
if singbox_tags:find("with_quic") then if singbox_tags:find("with_quic") then
o = s:option(Flag, _n("hysteria2_ignore_client_bandwidth"), translate("Commands the client to use the BBR flow control algorithm")) o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
o.password = true
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Flag, _n("hysteria2_ignore_client_bandwidth"), translate("Client BBR Flow Control"), translate("Commands the client to use the BBR flow control algorithm"))
o.default = 0 o.default = 0
o:depends({ [_n("protocol")] = "hysteria2" }) o:depends({ [_n("protocol")] = "hysteria2" })
@@ -167,10 +178,6 @@ if singbox_tags:find("with_quic") then
o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password")) o = s:option(Value, _n("hysteria2_obfs_password"), translate("Obfs Password"))
o:depends({ [_n("hysteria2_obfs_type")] = "salamander" }) o:depends({ [_n("hysteria2_obfs_type")] = "salamander" })
o = s:option(Value, _n("hysteria2_auth_password"), translate("Auth Password"))
o.password = true
o:depends({ [_n("protocol")] = "hysteria2"})
end end
o = s:option(ListValue, _n("d_protocol"), translate("Destination protocol")) o = s:option(ListValue, _n("d_protocol"), translate("Destination protocol"))
@@ -259,6 +266,18 @@ if singbox_tags:find("with_utls") then
o:depends({ [_n("reality")] = true }) o:depends({ [_n("reality")] = true })
end end
o = s:option(ListValue, _n("alpn"), translate("ALPN"))
o.default = "default"
o:value("default", translate("Default"))
o:value("h3")
o:value("h2")
o:value("h3,h2")
o:value("http/1.1")
o:value("h2,http/1.1")
o:value("h3,h2,http/1.1")
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
o:depends({ [_n("protocol")] = "hysteria" })
-- [[ TLS ]] -- -- [[ TLS ]] --
o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
@@ -1551,3 +1551,13 @@ function get_core(field, candidates)
end end
return nil return nil
end end
function cleanEmptyTables(t)
if type(t) ~= "table" then return nil end
for k, v in pairs(t) do
if type(v) == "table" then
t[k] = cleanEmptyTables(v)
end
end
return next(t) and t or nil
end
@@ -87,14 +87,14 @@ function gen_config(var)
quic = { quic = {
initStreamReceiveWindow = (node.hysteria2_recv_window) and tonumber(node.hysteria2_recv_window) or nil, initStreamReceiveWindow = (node.hysteria2_recv_window) and tonumber(node.hysteria2_recv_window) or nil,
initConnReceiveWindow = (node.hysteria2_recv_window_conn) and tonumber(node.hysteria2_recv_window_conn) or nil, initConnReceiveWindow = (node.hysteria2_recv_window_conn) and tonumber(node.hysteria2_recv_window_conn) or nil,
maxIdleTimeout = (function() maxIdleTimeout = (function(t)
local timeoutStr = tostring(node.hysteria2_idle_timeout or "") t = tonumber(tostring(t or "30"):match("^%d+"))
local timeout = tonumber(timeoutStr:match("^%d+")) return (t and t >= 4 and t <= 120) and t .. "s" or "30s"
if timeout and timeout >= 4 and timeout <= 120 then end)(node.hysteria2_idle_timeout),
return tostring(timeout) .. "s" keepAlivePeriod = (function(t)
end t = tonumber(tostring(t or "0"):match("^%d+"))
return nil return (t and t >= 2 and t <= 60) and t .. "s" or nil
end)(), end)(node.hysteria2_keep_alive_period),
disablePathMTUDiscovery = (node.hysteria2_disable_mtu_discovery) and true or false, disablePathMTUDiscovery = (node.hysteria2_disable_mtu_discovery) and true or false,
}, },
bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and { bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and {
@@ -7,6 +7,7 @@ local appname = api.appname
local fs = api.fs local fs = api.fs
local CACHE_PATH = api.CACHE_PATH local CACHE_PATH = api.CACHE_PATH
local split = api.split local split = api.split
local ech_domain = {}
local local_version = api.get_app_version("sing-box"):match("[^v]+") local local_version = api.get_app_version("sing-box"):match("[^v]+")
local version_ge_1_13_0 = api.compare_versions(local_version, ">=", "1.13.0") local version_ge_1_13_0 = api.compare_versions(local_version, ">=", "1.13.0")
@@ -159,6 +160,9 @@ function gen_outbound(flag, node, tag, proxy_table)
} }
local tls = nil local tls = nil
if node.protocol == "hysteria" or node.protocol == "hysteria2" or node.protocol == "tuic" or node.protocol == "naive" then
node.tls = "1"
end
if node.tls == "1" then if node.tls == "1" then
local alpn = nil local alpn = nil
if node.alpn and node.alpn ~= "default" then if node.alpn and node.alpn ~= "default" then
@@ -176,10 +180,35 @@ function gen_outbound(flag, node, tag, proxy_table)
--max_version = "1.3", --max_version = "1.3",
fragment = fragment, fragment = fragment,
record_fragment = record_fragment, record_fragment = record_fragment,
ech = (node.ech == "1") and { ech = (node.ech == "1") and (function()
enabled = true, local function get_ech_domain(s) --兼容xray "域名+DNS" 格式ech
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {} local domain, dns = s:match("^([^+]+)%+(.+)$")
} or nil, if not domain or not dns then return nil end
if not (dns:match("^https?://") or dns:match("^tcp://") or dns:match("^udp://") or dns:match("^h2c://")) then
return nil
end
if not domain:match("^[%w%-%.]+%.[%a]+$") then return nil end
return domain
end
local ech = { enabled = true }
local config = node.ech_config
local qname = node.ech_query_server_name
if config and not qname then
qname = get_ech_domain(config)
if not qname and not (config:match("%-+%s*BEGIN") and config:match("%-+%s*END")) then
config = "-----BEGIN ECH CONFIGS-----\n" .. config:gsub("%s+", "") .. "\n-----END ECH CONFIGS-----"
end
end
if qname then
ech.query_server_name = qname
ech_domain[qname] = true
elseif config then
ech.config = { config }
elseif node.tls_serverName and node.tls_serverName ~= "" then
ech_domain[node.tls_serverName] = true
end
return ech
end)() or nil,
utls = (node.utls == "1" or node.reality == "1") and { utls = (node.utls == "1" or node.reality == "1") and {
enabled = true, enabled = true,
fingerprint = node.fingerprint or "chrome" fingerprint = node.fingerprint or "chrome"
@@ -418,20 +447,7 @@ function gen_outbound(flag, node, tag, proxy_table)
recv_window_conn = tonumber(node.hysteria_recv_window_conn), recv_window_conn = tonumber(node.hysteria_recv_window_conn),
recv_window = tonumber(node.hysteria_recv_window), recv_window = tonumber(node.hysteria_recv_window),
disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false, disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
tls = { tls = tls
enabled = true,
server_name = node.tls_serverName,
insecure = (node.tls_allowInsecure == "1") and true or false,
fragment = fragment,
record_fragment = record_fragment,
alpn = (node.hysteria_alpn and node.hysteria_alpn ~= "") and {
node.hysteria_alpn
} or nil,
ech = (node.ech == "1") and {
enabled = true,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {}
} or nil
}
} }
end end
@@ -452,22 +468,15 @@ function gen_outbound(flag, node, tag, proxy_table)
udp_over_stream = false, udp_over_stream = false,
zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false, zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s", heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s",
tls = { tls = tls
enabled = true,
disable_sni = (node.tls_disable_sni == "1") and true or false,
server_name = node.tls_serverName,
insecure = (node.tls_allowInsecure == "1") and true or false,
fragment = fragment,
record_fragment = record_fragment,
alpn = (node.tuic_alpn and node.tuic_alpn ~= "") and {
node.tuic_alpn
} or nil,
ech = (node.ech == "1") and {
enabled = true,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {},
} or nil
}
} }
if node.tuic_alpn and node.tuic_alpn ~= "default" then
local alpn = {}
string.gsub(node.tuic_alpn, '[^,]+', function(w)
table.insert(alpn, w)
end)
if #alpn > 0 then protocol_table.tls.alpn = alpn end
end
end end
if node.protocol == "hysteria2" then if node.protocol == "hysteria2" then
@@ -494,17 +503,7 @@ function gen_outbound(flag, node, tag, proxy_table)
password = node.hysteria2_obfs_password password = node.hysteria2_obfs_password
} or nil, } or nil,
password = node.hysteria2_auth_password or nil, password = node.hysteria2_auth_password or nil,
tls = { tls = tls
enabled = true,
server_name = node.tls_serverName,
insecure = (node.tls_allowInsecure == "1") and true or false,
fragment = fragment,
record_fragment = record_fragment,
ech = (node.ech == "1") and {
enabled = true,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {}
} or nil
}
} }
end end
@@ -530,6 +529,23 @@ function gen_outbound(flag, node, tag, proxy_table)
} }
end end
if node.protocol == "naive" then
protocol_table = {
username = (node.username and node.username ~= "") and node.username or "",
password = (node.password and node.password ~= "") and node.password or "",
insecure_concurrency = tonumber(node.naive_insecure_concurrency or 0) > 0 and tonumber(node.naive_insecure_concurrency) or 0,
udp_over_tcp = node.uot == "1" and {
enabled = true,
version = 2
} or false,
extra_headers = node.user_agent and {
["User-Agent"] = node.user_agent
} or nil,
quic = node.naive_quic == "1" and true or false,
quic_congestion_control = (node.naive_quic == "1" and node.naive_congestion_control) and node.naive_congestion_control or nil
}
end
if protocol_table then if protocol_table then
for key, value in pairs(protocol_table) do for key, value in pairs(protocol_table) do
result[key] = value result[key] = value
@@ -548,6 +564,14 @@ function gen_config_server(node)
enabled = true, enabled = true,
certificate_path = node.tls_certificateFile, certificate_path = node.tls_certificateFile,
key_path = node.tls_keyFile, key_path = node.tls_keyFile,
alpn = (node.alpn and node.alpn ~= "default") and (function()
local alpn = {}
string.gsub(node.alpn, '[^,]+', function(w)
table.insert(alpn, w)
end)
if #alpn > 0 then return alpn end
return nil
end)() or nil
} }
if node.tls == "1" and node.reality == "1" then if node.tls == "1" and node.reality == "1" then
@@ -570,7 +594,7 @@ function gen_config_server(node)
if node.tls == "1" and node.ech == "1" then if node.tls == "1" and node.ech == "1" then
tls.ech = { tls.ech = {
enabled = true, enabled = true,
key = node.ech_key and split(node.ech_key:gsub("\\n", "\n"), "\n") or {}, key = node.ech_key and { node.ech_key } or nil
} }
end end
@@ -751,9 +775,6 @@ function gen_config_server(node)
end end
if node.protocol == "hysteria" then if node.protocol == "hysteria" then
tls.alpn = (node.hysteria_alpn and node.hysteria_alpn ~= "") and {
node.hysteria_alpn
} or nil
protocol_table = { protocol_table = {
up = node.hysteria_up_mbps .. " Mbps", up = node.hysteria_up_mbps .. " Mbps",
down = node.hysteria_down_mbps .. " Mbps", down = node.hysteria_down_mbps .. " Mbps",
@@ -776,9 +797,14 @@ function gen_config_server(node)
end end
if node.protocol == "tuic" then if node.protocol == "tuic" then
tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "") and { tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "default") and (function()
node.tuic_alpn local alpn = {}
} or nil string.gsub(node.tuic_alpn, '[^,]+', function(w)
table.insert(alpn, w)
end)
if #alpn > 0 then return alpn end
return nil
end)() or nil
protocol_table = { protocol_table = {
users = { users = {
{ {
@@ -789,7 +815,7 @@ function gen_config_server(node)
}, },
congestion_control = node.tuic_congestion_control or "cubic", congestion_control = node.tuic_congestion_control or "cubic",
zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false, zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
heartbeat = node.tuic_heartbeat .. "s", heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s",
tls = tls tls = tls
} }
end end
@@ -1913,15 +1939,28 @@ function gen_config(var)
if not dns then if not dns then
dns = { dns = {
servers = { servers = {{
{ type = "local",
type = "local", tag = "direct"
tag = "direct" }}
}
}
} }
end end
if next(ech_domain) ~= nil then
table.insert(dns.servers, {
tag = "ech-dns",
type = "https",
server = "223.5.5.5"
})
if not dns.rules then dns.rules = {} end
local domain = {}
for line, _ in pairs(ech_domain) do domain[#domain+1] = line end
table.insert(dns.rules, 1, {
domain = domain,
server = "ech-dns"
})
end
if COMMON.default_outbound_tag == "block" then if COMMON.default_outbound_tag == "block" then
route.final = nil route.final = nil
table.insert(route.rules, { table.insert(route.rules, {
@@ -25,11 +25,18 @@ function gen_config(var)
-- certificates = node.tuic_certificate and { node.tuic_certpath } or nil, -- certificates = node.tuic_certificate and { node.tuic_certpath } or nil,
udp_relay_mode = node.tuic_udp_relay_mode, udp_relay_mode = node.tuic_udp_relay_mode,
congestion_control = node.tuic_congestion_control, congestion_control = node.tuic_congestion_control,
heartbeat = node.tuic_heartbeat .. "s", heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s",
timeout = node.tuic_timeout .. "s", timeout = (tonumber(node.tuic_timeout) or 8) .. "s",
gc_interval = node.tuic_gc_interval .. "s", gc_interval = (tonumber(node.tuic_gc_interval) or 3) .. "s",
gc_lifetime = node.tuic_gc_lifetime .. "s", gc_lifetime = (tonumber(node.tuic_gc_lifetime) or 15) .. "s",
alpn = node.tuic_tls_alpn, alpn = (node.tuic_tls_alpn and node.tuic_tls_alpn ~= "") and (function()
local alpn = {}
string.gsub(node.tuic_tls_alpn, '[^,]+', function(w)
table.insert(alpn, w)
end)
if #alpn > 0 then return alpn end
return nil
end)() or nil,
disable_sni = (node.tuic_disable_sni == "1"), disable_sni = (node.tuic_disable_sni == "1"),
zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1"), zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1"),
send_window = tonumber(node.tuic_send_window), send_window = tonumber(node.tuic_send_window),
+142 -133
View File
@@ -9,21 +9,6 @@ local CACHE_PATH = api.CACHE_PATH
local xray_version = api.get_app_version("xray") local xray_version = api.get_app_version("xray")
local function get_noise_packets()
local noises = {}
uci:foreach(appname, "xray_noise_packets", function(n)
local noise = (n.enabled == "1") and {
type = n.type,
packet = n.packet,
delay = string.find(n.delay, "-") and n.delay or tonumber(n.delay),
applyTo = n.applyTo
} or nil
table.insert(noises, noise)
end)
if #noises == 0 then noises = nil end
return noises
end
local function get_domain_excluded() local function get_domain_excluded()
local path = string.format("/usr/share/%s/domains_excluded", appname) local path = string.format("/usr/share/%s/domains_excluded", appname)
local content = fs.readfile(path) local content = fs.readfile(path)
@@ -146,8 +131,7 @@ function gen_outbound(flag, node, tag, proxy_table)
sockopt = { sockopt = {
mark = 255, mark = 255,
tcpFastOpen = (node.tcp_fast_open == "1") and true or nil, tcpFastOpen = (node.tcp_fast_open == "1") and true or nil,
tcpMptcp = (node.tcpMptcp == "1") and true or nil, tcpMptcp = (node.tcpMptcp == "1") and true or nil
dialerProxy = (fragment or noise) and "dialerproxy" or nil
}, },
network = node.transport, network = node.transport,
security = node.stream_security, security = node.stream_security,
@@ -169,7 +153,7 @@ function gen_outbound(flag, node, tag, proxy_table)
return node.tls_CertByName return node.tls_CertByName
end)(), end)(),
echConfigList = (node.ech == "1") and node.ech_config or nil, echConfigList = (node.ech == "1") and node.ech_config or nil,
echForceQuery = (node.ech == "1") and (node.ech_ForceQuery or "none") or nil echForceQuery = (node.ech == "1") and (node.ech_ForceQuery or "full") or nil
} or nil, } or nil,
realitySettings = (node.stream_security == "reality") and { realitySettings = (node.stream_security == "reality") and {
serverName = node.tls_serverName, serverName = node.tls_serverName,
@@ -237,58 +221,31 @@ function gen_outbound(flag, node, tag, proxy_table)
path = node.xhttp_path or "/", path = node.xhttp_path or "/",
host = node.xhttp_host, host = node.xhttp_host,
extra = (function() extra = (function()
local extra_tbl = {} local extra = {}
-- Parse xhttp_extra and perform simple fault tolerance.
if node.xhttp_extra then if node.xhttp_extra then
local success, parsed = pcall(jsonc.parse, api.base64Decode(node.xhttp_extra)) local ok, parsed = pcall(jsonc.parse, api.base64Decode(node.xhttp_extra))
if success and parsed then if ok and type(parsed) == "table" then
extra_tbl = parsed.extra or parsed extra = parsed.extra or parsed
for k, v in pairs(extra_tbl) do
if (type(v) == "table" and next(v) == nil) or v == nil then
extra_tbl[k] = nil
end
end
end end
end end
-- Handling User-Agent -- Handling User-Agent
if node.user_agent and node.user_agent ~= "" then if node.user_agent and node.user_agent ~= "" then
extra_tbl.headers = extra_tbl.headers or {} extra.headers = extra.headers or {}
if not extra_tbl.headers["User-Agent"] and not extra_tbl.headers["user-agent"] then if not extra.headers["User-Agent"] and not extra.headers["user-agent"] then
extra_tbl.headers["User-Agent"] = node.user_agent extra.headers["User-Agent"] = node.user_agent
end end
end end
-- Clean up empty headers return api.cleanEmptyTables(extra)
if extra_tbl.headers and next(extra_tbl.headers) == nil then
extra_tbl.headers = nil
end
return next(extra_tbl) ~= nil and extra_tbl or nil
end)() end)()
} or nil, } or nil,
hysteriaSettings = (node.transport == "hysteria") and { hysteriaSettings = (node.transport == "hysteria") and {
version = 2, version = 2,
auth = node.hysteria2_auth_password, auth = node.hysteria2_auth_password
up = (node.hysteria2_up_mbps and tonumber(node.hysteria2_up_mbps)) and tonumber(node.hysteria2_up_mbps) .. "mbps" or nil,
down = (node.hysteria2_down_mbps and tonumber(node.hysteria2_down_mbps)) and tonumber(node.hysteria2_down_mbps) .. "mbps" or nil,
udphop = (node.hysteria2_hop) and {
port = string.gsub(node.hysteria2_hop, ":", "-"),
interval = (function()
local v = tonumber((node.hysteria2_hop_interval or "30s"):match("^%d+"))
return (v and v >= 5) and v or 30
end)()
} or nil,
maxIdleTimeout = (function()
local timeoutStr = tostring(node.hysteria2_idle_timeout or "")
local timeout = tonumber(timeoutStr:match("^%d+"))
if timeout and timeout >= 4 and timeout <= 120 then
return timeout
end
return 30
end)(),
disablePathMTUDiscovery = (node.hysteria2_disable_mtu_discovery) and true or false
} or nil, } or nil,
finalmask = (function() finalmask = (function()
local finalmask local finalmask = {}
if node.transport == "mkcp" then local TP = node.transport
if TP == "mkcp" then
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat", local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"} dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
local udp = {} local udp = {}
@@ -304,36 +261,55 @@ function gen_outbound(flag, node, tag, proxy_table)
c.settings = { password = node.mkcp_seed } c.settings = { password = node.mkcp_seed }
end end
udp[#udp+1] = c udp[#udp+1] = c
finalmask = { udp = udp } finalmask.udp = udp
elseif node.transport == "hysteria" and node.hysteria2_obfs_type and node.hysteria2_obfs_type ~= "" then elseif TP == "hysteria" then
finalmask = { if node.hysteria2_obfs_type and node.hysteria2_obfs_type ~= "" then
udp = {{ finalmask.udp = {{
type = node.hysteria2_obfs_type, type = node.hysteria2_obfs_type,
settings = node.hysteria2_obfs_password and { settings = node.hysteria2_obfs_password and {
password = node.hysteria2_obfs_password password = node.hysteria2_obfs_password
} or nil } or nil
}} }}
end
local up = tonumber(node.hysteria2_up_mbps) or 0
local down = tonumber(node.hysteria2_down_mbps) or 0
finalmask.quicParams = {
congestion = (up <= 0 and down <= 0) and "bbr" or "brutal",
brutalUp = up > 0 and (up .. "mbps") or nil,
brutalDown = down > 0 and (down .. "mbps") or nil,
udpHop = (node.hysteria2_hop) and {
ports = string.gsub(node.hysteria2_hop, ":", "-"),
interval = (function(v)
v = tonumber((v or "30s"):match("^%d+"))
return (v and v >= 5) and v or 30
end)(node.hysteria2_hop_interval)
} or nil,
maxIdleTimeout = (function(t)
t = tonumber(tostring(t or "30"):match("^%d+"))
return (t and t >= 4 and t <= 120) and t or 30
end)(node.hysteria2_idle_timeout),
keepAlivePeriod = (function(t)
t = tonumber(tostring(t or "0"):match("^%d+"))
return (t and t >= 2 and t <= 60) and t or nil
end)(node.hysteria2_keep_alive_period),
disablePathMTUDiscovery = tonumber(node.hysteria2_disable_mtu_discovery) == 1
} }
end end
if fragment and fragment_table and ({raw=1, ws=1, httpupgrade=1, grpc=1, xhttp=1})[TP] then
finalmask.tcp = finalmask.tcp or {}
finalmask.tcp[#finalmask.tcp+1] = api.clone(fragment_table)
end
if noise and noise_table and (TP == "mkcp" or (TP == "xhttp" and node.alpn == "h3")) then
finalmask.udp = finalmask.udp or {}
finalmask.udp[#finalmask.udp+1] = api.clone(noise_table)
end
if node.finalmask and node.finalmask ~= "" then if node.finalmask and node.finalmask ~= "" then
local ok, fm = pcall(jsonc.parse, api.base64Decode(node.finalmask)) local ok, fm = pcall(jsonc.parse, api.base64Decode(node.finalmask))
if ok and type(fm) == "table" then if ok and type(fm) == "table" then
if not finalmask or not next(finalmask) then finalmask = fm
finalmask = fm
else
if type(fm.udp) == "table" then
finalmask.udp = finalmask.udp or {}
for i = 1, #fm.udp do
finalmask.udp[#finalmask.udp+1] = fm.udp[i]
end
end
if type(fm.tcp) == "table" then
finalmask.tcp = fm.tcp
end
end
end end
end end
return (finalmask and next(finalmask)) and finalmask or nil return api.cleanEmptyTables(finalmask)
end)() end)()
} or nil, } or nil,
settings = { settings = {
@@ -475,6 +451,13 @@ function gen_config_server(node)
clients = clients clients = clients
} }
end end
elseif node.protocol == "hysteria2" then
settings = {
version = 2,
clients = node.hysteria2_auth_password and {
{ auth = node.hysteria2_auth_password }
}
}
elseif node.protocol == "dokodemo-door" then elseif node.protocol == "dokodemo-door" then
settings = { settings = {
network = node.d_protocol, network = node.d_protocol,
@@ -552,6 +535,12 @@ function gen_config_server(node)
end end
end end
if node.protocol == "hysteria2" then
node.protocol = "hysteria"
node.transport = "hysteria"
node.tls = "1"
end
local config = { local config = {
log = { log = {
loglevel = ("1" == node.log) and node.loglevel or "none" loglevel = ("1" == node.log) and node.loglevel or "none"
@@ -618,8 +607,11 @@ function gen_config_server(node)
maxUploadSize = node.xhttp_maxuploadsize, maxUploadSize = node.xhttp_maxuploadsize,
maxConcurrentUploads = node.xhttp_maxconcurrentuploads maxConcurrentUploads = node.xhttp_maxconcurrentuploads
} or nil, } or nil,
hysteriaSettings = (node.transport == "hysteria") and {
version = 2
} or nil,
finalmask = (function() finalmask = (function()
local finalmask local finalmask = {}
if node.transport == "mkcp" then if node.transport == "mkcp" then
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat", local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"} dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
@@ -636,27 +628,32 @@ function gen_config_server(node)
c.settings = { password = node.mkcp_seed } c.settings = { password = node.mkcp_seed }
end end
udp[#udp+1] = c udp[#udp+1] = c
finalmask = { udp = udp } finalmask.udp = udp
elseif node.transport == "hysteria" then
if node.hysteria2_obfs_type and node.hysteria2_obfs_type ~= "" then
finalmask.udp = {{
type = node.hysteria2_obfs_type,
settings = node.hysteria2_obfs_password and {
password = node.hysteria2_obfs_password
} or nil
}}
end
local ignore = tonumber(node.hysteria2_ignore_client_bandwidth) == 1
local up = (not ignore) and tonumber(node.hysteria2_up_mbps) or 0
local down = (not ignore) and tonumber(node.hysteria2_down_mbps) or 0
finalmask.quicParams = {
congestion = (up <= 0 and down <= 0) and "bbr" or "brutal",
brutalUp = up > 0 and (up .. "mbps") or nil,
brutalDown = down > 0 and (down .. "mbps") or nil
}
end end
if node.finalmask and node.finalmask ~= "" then if node.finalmask and node.finalmask ~= "" then
local ok, fm = pcall(jsonc.parse, api.base64Decode(node.finalmask)) local ok, fm = pcall(jsonc.parse, api.base64Decode(node.finalmask))
if ok and type(fm) == "table" then if ok and type(fm) == "table" then
if not finalmask or not next(finalmask) then finalmask = fm
finalmask = fm
else
if type(fm.udp) == "table" then
finalmask.udp = finalmask.udp or {}
for i = 1, #fm.udp do
finalmask.udp[#finalmask.udp+1] = fm.udp[i]
end
end
if type(fm.tcp) == "table" then
finalmask.tcp = fm.tcp
end
end
end end
end end
return (finalmask and next(finalmask)) and finalmask or nil return api.cleanEmptyTables(finalmask)
end)(), end)(),
sockopt = { sockopt = {
tcpFastOpen = (node.tcp_fast_open == "1") and true or nil, tcpFastOpen = (node.tcp_fast_open == "1") and true or nil,
@@ -751,6 +748,38 @@ function gen_config(var)
local xray_settings = uci:get_all(appname, "@global_xray[0]") or {} local xray_settings = uci:get_all(appname, "@global_xray[0]") or {}
if xray_settings.fragment == "1" then
local delay = xray_settings.fragment_delay
fragment_table = {
type = "fragment",
settings = {
packets = xray_settings.fragment_packets,
length = xray_settings.fragment_length,
delay = delay and (delay:find("-", 1, true) and delay or tonumber(delay)) or nil,
maxSplit = xray_settings.fragment_maxSplit
}
}
end
if xray_settings.noise == "1" then
local noises = {}
uci:foreach(appname, "xray_noise_packets", function(n)
if n.enabled == "1" then
local noise = {
rand = (n.type == "rand" and n.packet) and (n.packet:find("-", 1, true) and n.packet or tonumber(n.packet)) or nil,
type = (n.type ~= "rand") and n.type or nil,
packet = (n.type ~= "rand") and n.packet or nil,
delay = n.delay and (n.delay:find("-", 1, true) and n.delay or tonumber(n.delay)) or nil
}
table.insert(noises, noise)
end
end)
noise_table = #noises > 0 and {
type = "noise",
settings = { reset = 0, noise = noises }
} or nil
end
local node = node_id and uci:get_all(appname, node_id) or nil local node = node_id and uci:get_all(appname, node_id) or nil
local balancers = {} local balancers = {}
local rules = {} local rules = {}
@@ -909,7 +938,7 @@ function gen_config(var)
end end
end end
if is_new_blc_node then if is_new_blc_node then
local outboundTag = gen_outbound_get_tag(flag, blc_node_id, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) local outboundTag = gen_outbound_get_tag(flag, blc_node_id, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run })
if outboundTag then if outboundTag then
valid_nodes[#valid_nodes + 1] = outboundTag valid_nodes[#valid_nodes + 1] = outboundTag
end end
@@ -934,7 +963,7 @@ function gen_config(var)
local fallback_node = get_node_by_id(fallback_node_id) local fallback_node = get_node_by_id(fallback_node_id)
if fallback_node then if fallback_node then
if fallback_node.protocol ~= "_balancing" then if fallback_node.protocol ~= "_balancing" then
local outboundTag = gen_outbound_get_tag(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.record_fragment == "1" or nil, run_socks_instance = not no_run }) local outboundTag = gen_outbound_get_tag(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run })
if outboundTag then if outboundTag then
fallback_node_tag = outboundTag fallback_node_tag = outboundTag
end end
@@ -964,27 +993,29 @@ function gen_config(var)
strategy = strategy strategy = strategy
}) })
if _node.balancingStrategy == "leastPing" or _node.balancingStrategy == "leastLoad" or fallback_node_tag then if _node.balancingStrategy == "leastPing" or _node.balancingStrategy == "leastLoad" or fallback_node_tag then
local t = api.format_go_time(_node.probeInterval)
if t == "0s" then
t = "60s"
elseif not t:find("[hm]") and tonumber(t:match("%d+")) < 10 then
t = "10s"
end
if _node.balancingStrategy == "leastLoad" then if _node.balancingStrategy == "leastLoad" then
if not burstObservatory then burstObservatory = burstObservatory or {
burstObservatory = { subjectSelector = { "blc-" },
subjectSelector = { "blc-" }, pingConfig = {
pingConfig = { destination = _node.useCustomProbeUrl and _node.probeUrl or nil,
destination = _node.useCustomProbeUrl and _node.probeUrl or nil, interval = t,
interval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m", sampling = 3,
sampling = 3, timeout = "5s"
timeout = "5s"
}
} }
end }
else else
if not observatory then observatory = observatory or {
observatory = { subjectSelector = { "blc-" },
subjectSelector = { "blc-" }, probeUrl = _node.useCustomProbeUrl and _node.probeUrl or nil,
probeUrl = _node.useCustomProbeUrl and _node.probeUrl or nil, probeInterval = t,
probeInterval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m", enableConcurrency = true
enableConcurrency = true }
}
end
end end
end end
local loopback_outbound = gen_loopback(loopback_tag, loopback_dst) local loopback_outbound = gen_loopback(loopback_tag, loopback_dst)
@@ -1731,28 +1762,6 @@ function gen_config(var)
} }
} }
if xray_settings.fragment == "1" or xray_settings.noise == "1" then
table.insert(outbounds, {
protocol = "freedom",
tag = "dialerproxy",
settings = {
domainStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP",
fragment = (xray_settings.fragment == "1") and {
packets = (xray_settings.fragment_packets and xray_settings.fragment_packets ~= "") and xray_settings.fragment_packets,
length = (xray_settings.fragment_length and xray_settings.fragment_length ~= "") and xray_settings.fragment_length,
interval = (xray_settings.fragment_interval and xray_settings.fragment_interval ~= "") and xray_settings.fragment_interval,
maxSplit = (xray_settings.fragment_maxSplit and xray_settings.fragment_maxSplit ~= "") and xray_settings.fragment_maxSplit
} or nil,
noises = (xray_settings.noise == "1") and get_noise_packets() or nil
},
streamSettings = {
sockopt = {
mark = 255
}
}
})
end
local direct_outbound = { local direct_outbound = {
protocol = "freedom", protocol = "freedom",
tag = "direct", tag = "direct",
@@ -2,8 +2,6 @@
local api = self.api local api = self.api
-%> -%>
<script type="text/javascript"> <script type="text/javascript">
let node_id = "<%=self.section%>";
let url = '<%=api.url("node_config")%>/' + node_id;
//<![CDATA[ //<![CDATA[
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
waitForElement('select[name*="<%=api.appname%>"][name*="type"]', function(el) { waitForElement('select[name*="<%=api.appname%>"][name*="type"]', function(el) {
@@ -13,8 +11,12 @@ local api = self.api
if (o_val != el.value) { if (o_val != el.value) {
let save = true; let save = true;
if (save) { if (save) {
//document.getElementsByClassName('cbi-button-save')[0].click();
update_node({ update_node({
type: getOption("type").value remarks: getOption("remarks").value,
group: getOption("group").value,
type: getOption("type").value,
protocol: ''
}); });
} }
} }
@@ -29,11 +31,14 @@ local api = self.api
let name = name_split[name_split.length - 1]; let name = name_split[name_split.length - 1];
let save = true; let save = true;
if (save) { if (save) {
//document.getElementsByClassName('cbi-button-save')[0].click();
update_node({ update_node({
remarks: getOption("remarks").value,
group: getOption("group").value,
protocol: getOption(name).value protocol: getOption(name).value
}); });
} else { } else {
window.location.href = url + "?select_proto=" + el.value; window.location.href = node_config_url + "?select_proto=" + el.value;
} }
} }
}); });
@@ -3,6 +3,9 @@ local api = self.api
-%> -%>
<script type="text/javascript"> <script type="text/javascript">
//<![CDATA[ //<![CDATA[
let node_id = "<%=self.section%>";
let node_config_url = '<%=api.url("node_config")%>/' + node_id;
function waitForElement(selector, callback) { function waitForElement(selector, callback) {
const el = document.querySelector(selector); const el = document.querySelector(selector);
if (el) return callback(el); if (el) return callback(el);
@@ -18,7 +21,7 @@ local api = self.api
function getOption(opt) { function getOption(opt) {
var obj; var obj;
var id = 'cbid.<%=api.appname%>.<%=self.section%>.' + opt; var id = 'cbid.<%=api.appname%>.' + node_id + '.' + opt;
obj = document.getElementsByName(id)[0] || document.getElementById(id); obj = document.getElementsByName(id)[0] || document.getElementById(id);
if (obj) { if (obj) {
var combobox = document.getElementById('cbi.combobox.' + id); var combobox = document.getElementById('cbi.combobox.' + id);
@@ -37,12 +40,12 @@ local api = self.api
function update_node(data) { function update_node(data) {
XHR.get('<%=api.url("update_node")%>', { XHR.get('<%=api.url("update_node")%>', {
id: "<%=self.section%>", id: node_id,
data: JSON.stringify(data) data: JSON.stringify(data)
}, },
function(x, data) { function(x, data) {
if (x && x.status == 200 && data.code == 1) { if (x && x.status == 200 && data.code == 1) {
window.location.reload(); window.location.href = node_config_url;
} }
else { else {
alert("<%:Error%>"); alert("<%:Error%>");
@@ -675,6 +675,31 @@ local current_node = map:get(section)
params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure"); 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 === "naive") {
protocol = "naive+https";
var v_username = opt.get(dom_prefix + "username");
var v_password = opt.get(dom_prefix + "password");
var v_port = opt.get(dom_prefix + "port");
url = encodeURIComponent(v_username.value) +
":" + encodeURIComponent(v_password.value) +
"@" + _address +
":" + v_port.value + "?";
var params = "security=tls";
params += opt.query("sni", dom_prefix + "tls_serverName");
params += opt.query("insecure-concurrency", dom_prefix + "naive_insecure_concurrency");
params += opt.query("ech", dom_prefix + "ech_config");
params += opt.query("uot", dom_prefix + "uot");
if (opt.get(dom_prefix + "naive_quic")?.checked) {
protocol = "naive+quic";
params += opt.query("congestion_control", dom_prefix + "naive_congestion_control");
}
params += "#" + encodeURI(v_alias.value); params += "#" + encodeURI(v_alias.value);
if (params[0] == "&") { if (params[0] == "&") {
params = params.substring(1); params = params.substring(1);
@@ -1034,10 +1059,10 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'reality', false); opt.set(dom_prefix + 'reality', false);
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
opt.set(dom_prefix + 'tls_allowInsecure', true); opt.set(
if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { dom_prefix + 'tls_allowInsecure',
opt.set(dom_prefix + 'tls_allowInsecure', false); !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.allowInsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0')
} );
if (queryParam.fp && queryParam.fp.trim() != "") { if (queryParam.fp && queryParam.fp.trim() != "") {
opt.set(dom_prefix + 'utls', true); opt.set(dom_prefix + 'utls', true);
opt.set(dom_prefix + 'fingerprint', queryParam.fp); opt.set(dom_prefix + 'fingerprint', queryParam.fp);
@@ -1205,7 +1230,7 @@ local current_node = map:get(section)
var params; var params;
for (var i = 0; i < queryArray.length; i++) { for (var i = 0; i < queryArray.length; i++) {
params = queryArray[i].split('='); params = queryArray[i].split('=');
queryParam[decodeURIComponent(params[0]).toLowerCase()] = decodeURIComponent(params[1] || ''); queryParam[decodeURIComponent(params[0])] = decodeURIComponent(params[1] || '');
} }
} }
opt.set(dom_prefix + 'mux', queryParam.mux === '1'); opt.set(dom_prefix + 'mux', queryParam.mux === '1');
@@ -1281,21 +1306,15 @@ local current_node = map:get(section)
queryParam.security = queryParam.security || "tls"; queryParam.security = queryParam.security || "tls";
opt.set(dom_prefix + 'tls', queryParam.security === "tls"); opt.set(dom_prefix + 'tls', queryParam.security === "tls");
if (queryParam.security === "tls") { if (queryParam.security === "tls") {
var tls_serverName = queryParam.peer; opt.set(dom_prefix + 'tls_serverName', queryParam.peer || queryParam.sni || "");
if (queryParam.sni) {
tls_serverName = queryParam.sni
}
tls_serverName = tls_serverName || "";
opt.set(dom_prefix + 'tls_serverName', tls_serverName);
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
opt.set(dom_prefix + 'tls_CertSha', queryParam.pcs || ''); opt.set(dom_prefix + 'tls_CertSha', queryParam.pcs || '');
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || ''); opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
} }
opt.set(dom_prefix + 'tls_allowInsecure', true); opt.set(
if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { dom_prefix + 'tls_allowInsecure',
opt.set(dom_prefix + 'tls_allowInsecure', false); !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.allowInsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0')
} );
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo); opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
opt.set(dom_prefix + 'use_finalmask', !!queryParam.fm); opt.set(dom_prefix + 'use_finalmask', !!queryParam.fm);
opt.set(dom_prefix + 'finalmask', queryParam.fm || ""); opt.set(dom_prefix + 'finalmask', queryParam.fm || "");
@@ -1327,11 +1346,11 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'uuid', ssm.id); opt.set(dom_prefix + 'uuid', ssm.id);
opt.set(dom_prefix + 'tls', ssm.tls === "tls"); opt.set(dom_prefix + 'tls', ssm.tls === "tls");
if (ssm.tls === "tls") { if (ssm.tls === "tls") {
var tls_serverName = ssm.host; opt.set(dom_prefix + 'tls_serverName', ssm.sni || ssm.host);
if (ssm.sni) { opt.set(
tls_serverName = ssm.sni dom_prefix + 'tls_allowInsecure',
} !((ssm.allowinsecure ?? '0') === '0' && (ssm.allowInsecure ?? '0') === '0' && (ssm.insecure ?? '0') === '0')
opt.set(dom_prefix + 'tls_serverName', tls_serverName); );
} }
ssm.net = ssm.net.toLowerCase(); ssm.net = ssm.net.toLowerCase();
if (ssm.net === "kcp" || ssm.net === "mkcp") if (ssm.net === "kcp" || ssm.net === "mkcp")
@@ -1443,10 +1462,10 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'reality', false); opt.set(dom_prefix + 'reality', false);
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
opt.set(dom_prefix + 'tls_allowInsecure', true); opt.set(
if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { dom_prefix + 'tls_allowInsecure',
opt.set(dom_prefix + 'tls_allowInsecure', false); !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.allowInsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0')
} );
if (queryParam.fp && queryParam.fp.trim() != "") { if (queryParam.fp && queryParam.fp.trim() != "") {
opt.set(dom_prefix + 'utls', true); opt.set(dom_prefix + 'utls', true);
opt.set(dom_prefix + 'fingerprint', queryParam.fp); opt.set(dom_prefix + 'fingerprint', queryParam.fp);
@@ -1604,9 +1623,10 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'port', m.port || "443"); opt.set(dom_prefix + 'port', m.port || "443");
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ""); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || "");
if (queryParam.insecure && queryParam.insecure == "1") { opt.set(
opt.set(dom_prefix + 'tls_allowInsecure', true); dom_prefix + 'tls_allowInsecure',
} !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.allowInsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0')
);
opt.set(dom_prefix + 'tls_CertSha', queryParam.pcs || ''); opt.set(dom_prefix + 'tls_CertSha', queryParam.pcs || '');
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || ''); opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
if (m.hash) { if (m.hash) {
@@ -1646,7 +1666,7 @@ local current_node = map:get(section)
var params; var params;
for (i = 0; i < queryArray.length; i++) { for (i = 0; i < queryArray.length; i++) {
params = queryArray[i].split('='); params = queryArray[i].split('=');
queryParam[decodeURIComponent(params[0])] = decodeURIComponent(params[1] || ''); queryParam[decodeURIComponent(params[0]).toLowerCase()] = decodeURIComponent(params[1] || '');
} }
} }
opt.set(dom_prefix + 'tuic_congestion_control', queryParam.congestion_control || 'cubic'); opt.set(dom_prefix + 'tuic_congestion_control', queryParam.congestion_control || 'cubic');
@@ -1654,10 +1674,10 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'tuic_alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'tuic_alpn', queryParam.alpn || 'default');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
opt.set(dom_prefix + 'tls_disable_sni', queryParam.disable_sni === "1"); opt.set(dom_prefix + 'tls_disable_sni', queryParam.disable_sni === "1");
opt.set(dom_prefix + 'tls_allowInsecure', true); opt.set(
if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') { dom_prefix + 'tls_allowInsecure',
opt.set(dom_prefix + 'tls_allowInsecure', false); !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0' && (queryParam.allow_insecure ?? '0') === '0')
} );
if (hash) { if (hash) {
opt.set('remarks', decodeURIComponent(hash.substr(1))); opt.set('remarks', decodeURIComponent(hash.substr(1)));
} }
@@ -1685,10 +1705,10 @@ local current_node = map:get(section)
var params; var params;
for (i = 0; i < queryArray.length; i++) { for (i = 0; i < queryArray.length; i++) {
params = queryArray[i].split('='); params = queryArray[i].split('=');
queryParam[decodeURIComponent(params[0])] = decodeURIComponent(params[1] || ''); queryParam[decodeURIComponent(params[0]).toLowerCase()] = decodeURIComponent(params[1] || '');
} }
} }
if ((!queryParam.security || queryParam.security == "") && queryParam.sni && queryParam.sni != "") { if (!queryParam.security || queryParam.security == "") {
queryParam.security = "tls"; queryParam.security = "tls";
} }
if (queryParam.security) { if (queryParam.security) {
@@ -1697,10 +1717,7 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'reality', false); opt.set(dom_prefix + 'reality', false);
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default'); opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || ''); opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
opt.set(dom_prefix + 'tls_allowInsecure', true); opt.set(dom_prefix + 'tls_allowInsecure', !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0'));
if ((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0') {
opt.set(dom_prefix + 'tls_allowInsecure', false);
}
if (queryParam.fp && queryParam.fp.trim() != "") { if (queryParam.fp && queryParam.fp.trim() != "") {
opt.set(dom_prefix + 'utls', true); opt.set(dom_prefix + 'utls', true);
opt.set(dom_prefix + 'fingerprint', queryParam.fp); opt.set(dom_prefix + 'fingerprint', queryParam.fp);
@@ -1723,6 +1740,55 @@ local current_node = map:get(section)
opt.set('remarks', decodeURIComponent(m.hash.substr(1))); opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
} }
} }
if (ssu[0] === "naive+https" || ssu[0] === "naive+quic") {
if (has_singbox) {
dom_prefix = "singbox_"
opt.set('type', "sing-box");
}
opt.set(dom_prefix + 'protocol', "naive");
var _parsedUrl = new URL("http://" + ssu[1]);
var username = _parsedUrl.username;
var password = _parsedUrl.password;
var hostname = _parsedUrl.hostname;
var port = _parsedUrl.port;
var search = _parsedUrl.search;
var hash = _parsedUrl.hash;
if (!username || !password) { //某些链接会把username和password之间的:进行编码
const decoded = decodeURIComponent(username || password || "");
const i = decoded.indexOf(":");
if (i > -1) {
username = decoded.slice(0, i);
password = decoded.slice(i + 1);
}
}
opt.set(dom_prefix + 'username', decodeURIComponent(username));
opt.set(dom_prefix + 'password', decodeURIComponent(password));
opt.set(dom_prefix + 'address', unbracketIP(hostname));
opt.set(dom_prefix + 'port', port || "443");
var queryParam = {};
if (search.length > 1) {
var query = search.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] || '');
}
}
opt.set(dom_prefix + 'naive_insecure_concurrency', queryParam['insecure-concurrency'] || '0');
opt.set(dom_prefix + 'uot', (queryParam.uot ?? '0') === '1');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
opt.set(dom_prefix + 'ech', !!queryParam.ech);
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
if (ssu[0] === "naive+quic") {
opt.set(dom_prefix + 'naive_quic', true);
opt.set(dom_prefix + 'naive_congestion_control', queryParam.congestion_control || 'bbr');
}
if (hash) {
opt.set('remarks', decodeURIComponent(hash.substr(1)));
}
}
if (dom_prefix && dom_prefix != null) { if (dom_prefix && dom_prefix != null) {
if (opt.get(dom_prefix + 'port').value) { if (opt.get(dom_prefix + 'port').value) {
opt.get(dom_prefix + 'port').focus(); opt.get(dom_prefix + 'port').focus();
@@ -5,6 +5,10 @@ local api = require "luci.passwall2.api"
<script type="text/javascript"> <script type="text/javascript">
//<![CDATA[ //<![CDATA[
function ajax_add_node(link, group) { function ajax_add_node(link, group) {
const dom = document.getElementById('modal-mask');
dom.style.display = 'block';
dom.innerHTML = '<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;"><%:Processing, please wait…%></div>';
const chunkSize = 1000; // Fragmented sending to overcome uhttpd's limitations, with each fragment containing 1000 characters. const chunkSize = 1000; // Fragmented sending to overcome uhttpd's limitations, with each fragment containing 1000 characters.
const totalChunks = Math.ceil(link.length / chunkSize); const totalChunks = Math.ceil(link.length / chunkSize);
let currentChunk = 0; let currentChunk = 0;
@@ -19,11 +23,15 @@ local api = require "luci.passwall2.api"
currentChunk++; currentChunk++;
sendNextChunk(); sendNextChunk();
} else { } else {
dom.innerHTML = '';
dom.style.display = 'none';
alert("<%:Error%>"); alert("<%:Error%>");
return; return;
} }
}; };
xhr.onerror = function () { xhr.onerror = function () {
dom.innerHTML = '';
dom.style.display = 'none';
alert("<%:Network Error%>"); alert("<%:Network Error%>");
return; return;
}; };
@@ -40,6 +48,22 @@ local api = require "luci.passwall2.api"
sendNextChunk(); sendNextChunk();
} }
function decodeIfBase64(str) {
try {
let s = str.replace(/-/g, '+').replace(/_/g, '/');
while (s.length % 4) s += '=';
const decoded = decodeURIComponent(
atob(s).split('').map(c =>
'%' + c.charCodeAt(0).toString(16).padStart(2, '0')
).join('')
);
if (btoa(unescape(encodeURIComponent(decoded))).replace(/=+$/, '') === s.replace(/=+$/, '')) {
return decoded;
}
} catch (e) {}
return str;
}
function getBg(el) { function getBg(el) {
if (!el) return null; if (!el) return null;
const style = getComputedStyle(el); const style = getComputedStyle(el);
@@ -74,15 +98,15 @@ local api = require "luci.passwall2.api"
const group = (document.querySelector('#addlink_group_custom input[type="hidden"]')?.value || "default"); const group = (document.querySelector('#addlink_group_custom input[type="hidden"]')?.value || "default");
nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").replace(/<[^>]*>/g, '').trim(); nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").replace(/<[^>]*>/g, '').trim();
if (nodes_link != "") { if (nodes_link != "") {
nodes_link = decodeIfBase64(nodes_link);
let s = nodes_link.split('://'); let s = nodes_link.split('://');
if (s.length > 1) { if (s.length > 1) {
close_add_link_div();
ajax_add_node(nodes_link, group); ajax_add_node(nodes_link, group);
} } else {
else {
alert("<%:Please enter the correct link.%>"); alert("<%:Please enter the correct link.%>");
} }
} } else {
else {
document.getElementById("nodes_link").focus(); document.getElementById("nodes_link").focus();
} }
} }
@@ -93,8 +117,7 @@ local api = require "luci.passwall2.api"
function(x, data) { function(x, data) {
if(x && x.status == 200) { if(x && x.status == 200) {
window.location.href = '<%=api.url("node_list")%>'; window.location.href = '<%=api.url("node_list")%>';
} } else {
else {
alert("<%:Error%>"); alert("<%:Error%>");
} }
}); });
@@ -154,8 +177,7 @@ local api = require "luci.passwall2.api"
function(x, data) { function(x, data) {
if (x && x.status == 200) { if (x && x.status == 200) {
window.location.href = '<%=api.url("node_list")%>'; window.location.href = '<%=api.url("node_list")%>';
} } else {
else {
alert("<%:Error%>"); alert("<%:Error%>");
} }
}); });
@@ -48,8 +48,11 @@ table td, .table .td {
background: #5e72e445 !important; background: #5e72e445 !important;
} }
.ping a:hover{ .ping a,
text-decoration : underline; .ping_value a,
.tcping_value a {
text-decoration: none;
cursor: pointer;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@@ -413,11 +416,16 @@ table td, .table .td {
} }
} }
if (ids.length > 0) { if (ids.length > 0) {
if (confirm('<%:Are you sure to delete select nodes?%>') == true){ const dom = document.getElementById('modal-mask');
if (confirm('<%:Are you sure to delete select nodes?%>') == true) {
dom.style.display = 'block';
dom.innerHTML = '<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;"><%:Processing, please wait…%></div>';
XHR.get('<%=api.url("delete_select_nodes")%>', { XHR.get('<%=api.url("delete_select_nodes")%>', {
ids: ids.join() ids: ids.join()
}, },
function(x, data) { function(x, data) {
dom.innerHTML = '';
dom.style.display = 'none';
if (x && x.status == 200) { if (x && x.status == 200) {
/* /*
for (var i = 0 ; i < ids.length; i++) { for (var i = 0 ; i < ids.length; i++) {
@@ -427,8 +435,7 @@ table td, .table .td {
*/ */
ajaxAbortAll(); ajaxAbortAll();
window.location.href = '<%=api.url("node_list")%>'; window.location.href = '<%=api.url("node_list")%>';
} } else {
else {
alert("<%:Error%>"); alert("<%:Error%>");
} }
}); });
@@ -553,11 +560,10 @@ table td, .table .td {
text = "<%:Timeout%>"; text = "<%:Timeout%>";
} else { } else {
let use_time = parseInt(result.use_time); let use_time = parseInt(result.use_time);
if (use_time < 1000) { if (use_time < 1000)
color = "green"; color = "green";
} else if (use_time < 2000) { else if (use_time < 2000)
color = "#fb9a05"; color = "#fb9a05";
}
text = use_time + " ms"; text = use_time + " ms";
} }
} }
@@ -569,9 +575,11 @@ table td, .table .td {
} }
function ping_node(cbi_id, dom, type) { function ping_node(cbi_id, dom, type) {
var full = get_address_full(cbi_id); if (dom.dataset.busy == "1" || !cbi_id) return;
const full = get_address_full(cbi_id);
if ((type == "icmp" && full.address != "" ) || (type == "tcping" && full.address != "" && full.port != "")) { if ((type == "icmp" && full.address != "" ) || (type == "tcping" && full.address != "" && full.port != "")) {
dom.onclick = null dom.dataset.busy = "1";
dom.style.color = "";
dom.innerText = "<%:Check...%>"; dom.innerText = "<%:Check...%>";
XHR.get('<%=api.url("ping_node")%>', { XHR.get('<%=api.url("ping_node")%>', {
address: full.address, address: full.address,
@@ -579,19 +587,23 @@ table td, .table .td {
type: type type: type
}, },
function(x, result) { function(x, result) {
let color = "red";
let text = "<%:Error%>";
if(x && x.status == 200) { if(x && x.status == 200) {
if (result.ping == null || result.ping.trim() == "") { if (result.ping == null || result.ping.trim() == "") {
dom.outerHTML = "<font style='color:red'><%:Timeout%></font>"; text = "<%:Timeout%>";
} else { } else {
var ping = parseInt(result.ping); var ping = parseInt(result.ping);
if (ping < 100) if (ping < 100)
dom.outerHTML = "<font style='color:green'>" + result.ping + " ms" + "</font>"; color = "green";
else if (ping < 200) else if (ping < 200)
dom.outerHTML = "<font style='color:#fb9a05'>" + result.ping + " ms" + "</font>"; color = "#fb9a05";
else if (ping >= 200) text = result.ping + " ms";
dom.outerHTML = "<font style='color:red'>" + result.ping + " ms" + "</font>";
} }
} }
dom.innerText = text;
dom.style.color = color;
dom.dataset.busy = "0";
} }
); );
} }
@@ -601,15 +613,15 @@ table td, .table .td {
function pingAllNodes() { function pingAllNodes() {
if (auto_detection_time == "icmp" || auto_detection_time == "tcping") { if (auto_detection_time == "icmp" || auto_detection_time == "tcping") {
const now = Date.now(); const now = Date.now();
var nodes = []; const nodes = [];
const ping_value = document.getElementsByClassName(auto_detection_time == "tcping" ? 'tcping_value' : 'ping_value'); const ping_value = document.getElementsByClassName(auto_detection_time == "tcping" ? 'tcping_value' : 'ping_value');
for (var i = 0; i < ping_value.length; i++) { for (let i = 0; i < ping_value.length; i++) {
var cbi_id = ping_value[i].getAttribute("cbiid"); const cbi_id = ping_value[i].getAttribute("cbiid");
var full = get_address_full(cbi_id); const full = get_address_full(cbi_id);
if ((auto_detection_time == "icmp" && full.address != "" ) || (auto_detection_time == "tcping" && full.address != "" && full.port != "")) { if ((auto_detection_time == "icmp" && full.address != "" ) || (auto_detection_time == "tcping" && full.address != "" && full.port != "")) {
var flag = false; let flag = false;
// Merge duplicates // Merge duplicates
for (var j = 0; j < nodes.length; j++) { for (let j = 0; j < nodes.length; j++) {
if (nodes[j].address == full.address && nodes[j].port == full.port) { if (nodes[j].address == full.address && nodes[j].port == full.port) {
nodes[j].indexs = nodes[j].indexs + "," + i; nodes[j].indexs = nodes[j].indexs + "," + i;
flag = true; flag = true;
@@ -621,12 +633,17 @@ table td, .table .td {
const cacheData = JSON.parse(localStorage.getItem(auto_detection_time + ":" + full.address + ":" + full.port)); const cacheData = JSON.parse(localStorage.getItem(auto_detection_time + ":" + full.address + ":" + full.port));
if (cacheData && cacheData.savetime && (now - cacheData.timestamp) < cacheData.savetime) { if (cacheData && cacheData.savetime && (now - cacheData.timestamp) < cacheData.savetime) {
const a = ping_value[i].firstElementChild;
let color = "red";
if (cacheData.value < 100) if (cacheData.value < 100)
ping_value[i].innerHTML = "<font style='color:green'>" + cacheData.value + " ms" + "</font>"; color = "green";
else if (cacheData.value < 200) else if (cacheData.value < 200)
ping_value[i].innerHTML = "<font style='color:#fb9a05'>" + cacheData.value + " ms" + "</font>"; color = "#fb9a05";
else if (cacheData.value >= 200) a.innerText = cacheData.value + " ms";
ping_value[i].innerHTML = "<font style='color:red'>" + cacheData.value + " ms" + "</font>"; a.style.color = color;
a.onclick = function () {
ping_node(cbi_id, this, auto_detection_time);
};
} else { } else {
localStorage.removeItem(auto_detection_time + ":" + full.address + ":" + full.port); localStorage.removeItem(auto_detection_time + ":" + full.address + ":" + full.port);
nodes.push({ nodes.push({
@@ -650,12 +667,14 @@ table td, .table .td {
}, },
function(x, result) { function(x, result) {
if (x && x.status == 200) { if (x && x.status == 200) {
var strs = dom.indexs.split(","); let strs = dom.indexs.split(",");
for (var i = 0; i < strs.length; i++) { for (let i = 0; i < strs.length; i++) {
if (result.ping == null || result.ping.trim() == "") { const a = ping_value[strs[i]].firstElementChild;
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>"; const cbi_id = ping_value[strs[i]].getAttribute("cbiid");
} else { let color = "red";
var ping = parseInt(result.ping); let text = "<%:Timeout%>";
if (result.ping !== null && result.ping.trim() !== "") {
let ping = parseInt(result.ping);
//save to cache //save to cache
const cache_data = { const cache_data = {
dom_id: strs[i], dom_id: strs[i],
@@ -663,23 +682,33 @@ table td, .table .td {
savetime: 60 * 1000, savetime: 60 * 1000,
value: ping value: ping
}; };
text = result.ping + " ms";
localStorage.setItem(auto_detection_time + ":" + dom.address + ":" + dom.port, JSON.stringify(cache_data)); localStorage.setItem(auto_detection_time + ":" + dom.address + ":" + dom.port, JSON.stringify(cache_data));
if (ping < 100) if (ping < 100)
ping_value[strs[i]].innerHTML = "<font style='color:green'>" + result.ping + " ms" + "</font>"; color = "green";
else if (ping < 200) else if (ping < 200)
ping_value[strs[i]].innerHTML = "<font style='color:#fb9a05'>" + result.ping + " ms" + "</font>"; color = "#fb9a05";
else if (ping >= 200)
ping_value[strs[i]].innerHTML = "<font style='color:red'>" + result.ping + " ms" + "</font>";
} }
a.innerText = text;
a.style.color = color;
a.onclick = function () {
ping_node(cbi_id, this, auto_detection_time);
};
} }
} }
res(); res();
}, },
5000, 5000,
function(x) { function(x) {
var strs = dom.indexs.split(","); let strs = dom.indexs.split(",");
for (var i = 0; i < strs.length; i++) { for (let i = 0; i < strs.length; i++) {
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>"; const a = ping_value[strs[i]].firstElementChild;
const cbi_id = ping_value[strs[i]].getAttribute("cbiid");
a.innerText = "<%:Timeout%>";
a.style.color = "red";
a.onclick = function () {
ping_node(cbi_id, this, auto_detection_time);
};
} }
res(); res();
} }
@@ -927,12 +956,12 @@ table td, .table .td {
if (auto_detection_time != "icmp" && o["address"] && o["port"]) { if (auto_detection_time != "icmp" && o["address"] && o["port"]) {
innerHTML = innerHTML.split("{{ping}}").join('<span class="ping"><a href="javascript:void(0)" onclick="javascript:ping_node(\'{{id}}\', this, \'icmp\')"><%:Test%></a></span>'); innerHTML = innerHTML.split("{{ping}}").join('<span class="ping"><a href="javascript:void(0)" onclick="javascript:ping_node(\'{{id}}\', this, \'icmp\')"><%:Test%></a></span>');
} else { } else {
innerHTML = innerHTML.split("{{ping}}").join('<span class="ping_value" cbiid="{{id}}">---</span>'); innerHTML = innerHTML.split("{{ping}}").join('<span class="ping_value" cbiid="{{id}}"><a href="javascript:void(0)" style="color:inherit">---</a></span>');
} }
if (auto_detection_time != "tcping" && o["address"] && o["port"]) { if (auto_detection_time != "tcping" && o["address"] && o["port"]) {
innerHTML = innerHTML.split("{{tcping}}").join('<span class="ping"><a href="javascript:void(0)" onclick="javascript:ping_node(\'{{id}}\', this, \'tcping\')"><%:Test%></a></span>'); innerHTML = innerHTML.split("{{tcping}}").join('<span class="ping"><a href="javascript:void(0)" onclick="javascript:ping_node(\'{{id}}\', this, \'tcping\')"><%:Test%></a></span>');
} else { } else {
innerHTML = innerHTML.split("{{tcping}}").join('<span class="tcping_value" cbiid="{{id}}">---</span>'); innerHTML = innerHTML.split("{{tcping}}").join('<span class="tcping_value" cbiid="{{id}}"><a href="javascript:void(0)" style="color:inherit">---</a></span>');
} }
innerHTML = innerHTML.split("{{url_test}}").join('<span class="ping"><a href="javascript:void(0)" onclick="javascript:urltest_node(\'{{id}}\', this)"><%:Test%></a></span>'); innerHTML = innerHTML.split("{{url_test}}").join('<span class="ping"><a href="javascript:void(0)" onclick="javascript:urltest_node(\'{{id}}\', this)"><%:Test%></a></span>');
innerHTML = innerHTML.split("{{id}}").join(o[".name"]); innerHTML = innerHTML.split("{{id}}").join(o[".name"]);
+32 -8
View File
@@ -567,6 +567,9 @@ msgstr "نوع احراز هویت"
msgid "Auth Password" msgid "Auth Password"
msgstr "رمز عبور احراز هویت" msgstr "رمز عبور احراز هویت"
msgid "Client BBR Flow Control"
msgstr "کنترل BBR مشتری"
msgid "Commands the client to use the BBR flow control algorithm" msgid "Commands the client to use the BBR flow control algorithm"
msgstr "به کلاینت دستور می‌دهد تا از الگوریتم کنترل جریان BBR استفاده کند" msgstr "به کلاینت دستور می‌دهد تا از الگوریتم کنترل جریان BBR استفاده کند"
@@ -591,6 +594,9 @@ msgstr "پنجره دریافت اتصال QUIC"
msgid "QUIC concurrent bidirectional streams" msgid "QUIC concurrent bidirectional streams"
msgstr "جریان‌های دوطرفه همزمان QUIC" msgstr "جریان‌های دوطرفه همزمان QUIC"
msgid "QUIC KeepAlive interval"
msgstr "فاصله QUIC KeepAlive"
msgid "Disable MTU detection" msgid "Disable MTU detection"
msgstr "غیرفعال کردن تشخیص MTU" msgstr "غیرفعال کردن تشخیص MTU"
@@ -1335,6 +1341,9 @@ msgstr "اجازه ناامن (allowInsecure)"
msgid "Whether unsafe connections are allowed. When checked, Certificate validation will be skipped." msgid "Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."
msgstr "آیا اتصالات ناامن مجاز هستند. در صورت تیک خوردن، اعتبارسنجی گواهی نادیده گرفته می‌شود." msgstr "آیا اتصالات ناامن مجاز هستند. در صورت تیک خوردن، اعتبارسنجی گواهی نادیده گرفته می‌شود."
msgid "Used when the node link does not include this parameter."
msgstr "زمانی استفاده می‌شود که لینک گره شامل این پارامتر نباشد."
msgid "%s Node Use Type" msgid "%s Node Use Type"
msgstr "نوع استفاده گره %s" msgstr "نوع استفاده گره %s"
@@ -1353,6 +1362,9 @@ msgstr "رمز عبور TUIC برای Socks محلی"
msgid "UDP relay mode" msgid "UDP relay mode"
msgstr "حالت بازپخش UDP" msgstr "حالت بازپخش UDP"
msgid "Concurrent Tunnels"
msgstr "تونل‌های همزمان"
msgid "Congestion control algorithm" msgid "Congestion control algorithm"
msgstr "الگوریتم کنترل ازدحام" msgstr "الگوریتم کنترل ازدحام"
@@ -1674,12 +1686,21 @@ msgstr "سیاست پرس‌وجوی ECH"
msgid "Controls the policy used when performing DNS queries for ECH configuration." msgid "Controls the policy used when performing DNS queries for ECH configuration."
msgstr "سیاستی که هنگام انجام پرس‌وجوهای DNS برای پیکربندی ECH استفاده می‌شود را کنترل می‌کند." msgstr "سیاستی که هنگام انجام پرس‌وجوهای DNS برای پیکربندی ECH استفاده می‌شود را کنترل می‌کند."
msgid "ECH Query Domain"
msgstr "دامنه پرس و جو ECH"
msgid "Overrides the domain name used for ECH HTTPS record queries."
msgstr "نام دامنه مورد استفاده برای پرس‌وجوهای رکورد ECH HTTPS را لغو می‌کند."
msgid "Enable Multipath TCP, need to be enabled in both server and client configuration." msgid "Enable Multipath TCP, need to be enabled in both server and client configuration."
msgstr "فعال‌سازی Multipath TCP، باید در تنظیمات سرور و کلاینت فعال باشد." msgstr "فعال‌سازی Multipath TCP، باید در تنظیمات سرور و کلاینت فعال باشد."
msgid "An FinalMaskObject in JSON format, used for sharing." msgid "An FinalMaskObject in JSON format, used for sharing."
msgstr "یک شیء FinalMaskObject با فرمت JSON که برای اشتراک‌گذاری استفاده می‌شود." msgstr "یک شیء FinalMaskObject با فرمت JSON که برای اشتراک‌گذاری استفاده می‌شود."
msgid "Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings."
msgstr "ماسک نهایی سفارشی، تنظیمات mkcp، hysteria2، fragment، noise و تنظیمات مرتبط را نادیده می‌گیرد."
msgid "Fragment" msgid "Fragment"
msgstr "قطعه‌بندی (Fragment)" msgstr "قطعه‌بندی (Fragment)"
@@ -1698,8 +1719,8 @@ msgstr "طول قطعه"
msgid "Fragmented packet length (byte)" msgid "Fragmented packet length (byte)"
msgstr "طول بسته قطعه‌بندی شده (بایت)" msgstr "طول بسته قطعه‌بندی شده (بایت)"
msgid "Fragment Interval" msgid "Fragment Delay"
msgstr "بازه قطعه‌بندی" msgstr "تأخیر قطعه"
msgid "Fragmentation interval (ms)" msgid "Fragmentation interval (ms)"
msgstr "بازه قطعه‌بندی (میلی‌ثانیه)" msgstr "بازه قطعه‌بندی (میلی‌ثانیه)"
@@ -1708,7 +1729,7 @@ msgid "Max Split"
msgstr "حداکثر تقسیم" msgstr "حداکثر تقسیم"
msgid "Limit the maximum number of splits." msgid "Limit the maximum number of splits."
msgstr "محدود کردن حداکثر تعداد تقسیم‌ها." msgstr "حداکثر تعداد تقسیم‌بندی‌ها را محدود کنید."
msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first." msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first."
msgstr "تقسیم داده‌های دست تکان دادن به چندین رکورد TLS برای فرار بهتر از سانسور. سربار کم. توصیه می‌شود ابتدا فعال شود." msgstr "تقسیم داده‌های دست تکان دادن به چندین رکورد TLS برای فرار بهتر از سانسور. سربار کم. توصیه می‌شود ابتدا فعال شود."
@@ -1728,15 +1749,12 @@ msgstr "برای ارسال بسته‌های نویز، \"Noise\" را در ت
msgid "Xray Noise Packets" msgid "Xray Noise Packets"
msgstr "بسته‌های نویز Xray" msgstr "بسته‌های نویز Xray"
msgid "Packet" msgid "Packet | Rand Length"
msgstr "بسته" msgstr "طول بسته | رند"
msgid "Delay (ms)" msgid "Delay (ms)"
msgstr "تاخیر (میلی‌ثانیه)" msgstr "تاخیر (میلی‌ثانیه)"
msgid "IP Type"
msgstr "نوع IP"
msgid "If is domain name, The requested domain name will be resolved to IP before connect." msgid "If is domain name, The requested domain name will be resolved to IP before connect."
msgstr "اگر نام دامنه است، نام دامنه درخواست شده قبل از اتصال به IP حل می‌شود." msgstr "اگر نام دامنه است، نام دامنه درخواست شده قبل از اتصال به IP حل می‌شود."
@@ -2275,6 +2293,9 @@ msgstr "گزینه‌های افزونه ناقص است."
msgid "shadowsocks-libev unsupport 2022 encryption." msgid "shadowsocks-libev unsupport 2022 encryption."
msgstr "shadowsocks-libev از رمزنگاری 2022 پشتیبانی نمی‌کند." msgstr "shadowsocks-libev از رمزنگاری 2022 پشتیبانی نمی‌کند."
msgid "Please replace %s to support %s transmission method."
msgstr "لطفا برای پشتیبانی از روش انتقال %s، %s را جایگزین کنید."
msgid "Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks." msgid "Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks."
msgstr "لطفاً برای پشتیبانی از روش‌های انتقال بیشتر در Shadowsocks، از Xray یا Sing-Box استفاده کنید." msgstr "لطفاً برای پشتیبانی از روش‌های انتقال بیشتر در Shadowsocks، از Xray یا Sing-Box استفاده کنید."
@@ -2361,3 +2382,6 @@ msgstr "خطا، بازیابی سرویس."
msgid "Subscription complete..." msgid "Subscription complete..."
msgstr "اشتراک کامل شد..." msgstr "اشتراک کامل شد..."
msgid "Processing, please wait…"
msgstr "در حال پردازش، لطفا صبر کنید…"
@@ -568,6 +568,9 @@ msgstr "Тип аутентификации"
msgid "Auth Password" msgid "Auth Password"
msgstr "Пароль аутентификации" msgstr "Пароль аутентификации"
msgid "Client BBR Flow Control"
msgstr "Управление клиентом BBR"
msgid "Commands the client to use the BBR flow control algorithm" msgid "Commands the client to use the BBR flow control algorithm"
msgstr "Включает использование алгоритма управления потоком BBR на клиенте" msgstr "Включает использование алгоритма управления потоком BBR на клиенте"
@@ -592,6 +595,9 @@ msgstr "Размер окна приёма соединения QUIC"
msgid "QUIC concurrent bidirectional streams" msgid "QUIC concurrent bidirectional streams"
msgstr "Макс. количество одновременных двунаправленных потоков QUIC" msgstr "Макс. количество одновременных двунаправленных потоков QUIC"
msgid "QUIC KeepAlive interval"
msgstr "Интервал QUIC KeepAlive"
msgid "Disable MTU detection" msgid "Disable MTU detection"
msgstr "Отключить определение MTU" msgstr "Отключить определение MTU"
@@ -1336,6 +1342,9 @@ msgstr "Пропускать проверку сертификата"
msgid "Whether unsafe connections are allowed. When checked, Certificate validation will be skipped." msgid "Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."
msgstr "Разрешить соединения без проверки сертификата. При включении валидация будет пропущена." msgstr "Разрешить соединения без проверки сертификата. При включении валидация будет пропущена."
msgid "Used when the node link does not include this parameter."
msgstr "Используется, если в ссылке на узел этот параметр отсутствует."
msgid "%s Node Use Type" msgid "%s Node Use Type"
msgstr "Тип использования сервера %s" msgstr "Тип использования сервера %s"
@@ -1354,6 +1363,9 @@ msgstr "Пароль для локального SOCKS TUIC"
msgid "UDP relay mode" msgid "UDP relay mode"
msgstr "Режим ретрансляции UDP" msgstr "Режим ретрансляции UDP"
msgid "Concurrent Tunnels"
msgstr "Одновременные туннели"
msgid "Congestion control algorithm" msgid "Congestion control algorithm"
msgstr "Алгоритм контроля перегрузок" msgstr "Алгоритм контроля перегрузок"
@@ -1675,12 +1687,21 @@ msgstr "Политика запросов ECH"
msgid "Controls the policy used when performing DNS queries for ECH configuration." msgid "Controls the policy used when performing DNS queries for ECH configuration."
msgstr "Управляет политикой DNS-запросов для получения конфигурации ECH." msgstr "Управляет политикой DNS-запросов для получения конфигурации ECH."
msgid "ECH Query Domain"
msgstr "Домен запроса ECH"
msgid "Overrides the domain name used for ECH HTTPS record queries."
msgstr "Переопределяет доменное имя, используемое для запросов записей ECH HTTPS."
msgid "Enable Multipath TCP, need to be enabled in both server and client configuration." msgid "Enable Multipath TCP, need to be enabled in both server and client configuration."
msgstr "Включить Multipath TCP. Должно быть активировано и на сервере, и на клиенте." msgstr "Включить Multipath TCP. Должно быть активировано и на сервере, и на клиенте."
msgid "An FinalMaskObject in JSON format, used for sharing." msgid "An FinalMaskObject in JSON format, used for sharing."
msgstr "Объект FinalMaskObject в формате JSON, используемый для обмена данными." msgstr "Объект FinalMaskObject в формате JSON, используемый для обмена данными."
msgid "Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings."
msgstr "Пользовательская настройка finalmask переопределяет параметры mkcp, hysteria2, fragment, noise и связанные с ними настройки."
msgid "Fragment" msgid "Fragment"
msgstr "Фрагментация" msgstr "Фрагментация"
@@ -1699,17 +1720,17 @@ msgstr "Длина фрагмента"
msgid "Fragmented packet length (byte)" msgid "Fragmented packet length (byte)"
msgstr "Длина фрагментированного пакета (байт)" msgstr "Длина фрагментированного пакета (байт)"
msgid "Fragment Interval" msgid "Fragment Delay"
msgstr "Интервал фрагментации" msgstr "Задержка фрагмента"
msgid "Fragmentation interval (ms)" msgid "Fragmentation interval (ms)"
msgstr "Интервал между фрагментами (мс)" msgstr "Интервал между фрагментами (мс)"
msgid "Max Split" msgid "Max Split"
msgstr "Макс. число фрагментов" msgstr "Максимальное разделение"
msgid "Limit the maximum number of splits." msgid "Limit the maximum number of splits."
msgstr "Ограничить максимальное количество фрагментов." msgstr "Ограничьте максимальное количество разделений."
msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first." msgid "Split handshake data into multiple TLS records for better censorship evasion. Low overhead. Recommended to enable first."
msgstr "Разбивать данные рукопожатия на несколько TLS-записей для лучшего обхода цензуры. Малая нагрузка. Рекомендуется включить в первую очередь." msgstr "Разбивать данные рукопожатия на несколько TLS-записей для лучшего обхода цензуры. Малая нагрузка. Рекомендуется включить в первую очередь."
@@ -1729,15 +1750,12 @@ msgstr "Чтобы отправлять шумовые пакеты, включ
msgid "Xray Noise Packets" msgid "Xray Noise Packets"
msgstr "Пакеты шума Xray" msgstr "Пакеты шума Xray"
msgid "Packet" msgid "Packet | Rand Length"
msgstr "Пакет" msgstr "Пакет | Длина ранда"
msgid "Delay (ms)" msgid "Delay (ms)"
msgstr "Задержка (мс)" msgstr "Задержка (мс)"
msgid "IP Type"
msgstr "Тип IP"
msgid "If is domain name, The requested domain name will be resolved to IP before connect." msgid "If is domain name, The requested domain name will be resolved to IP before connect."
msgstr "Если указано доменное имя, оно будет разрешено в IP-адрес перед подключением." msgstr "Если указано доменное имя, оно будет разрешено в IP-адрес перед подключением."
@@ -2273,6 +2291,9 @@ msgstr "Опции плагина указаны не полностью."
msgid "shadowsocks-libev unsupport 2022 encryption." msgid "shadowsocks-libev unsupport 2022 encryption."
msgstr "shadowsocks-libev не поддерживает шифрование 2022." msgstr "shadowsocks-libev не поддерживает шифрование 2022."
msgid "Please replace %s to support %s transmission method."
msgstr "Пожалуйста, замените %s, чтобы поддержать метод передачи %s."
msgid "Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks." msgid "Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks."
msgstr "Пожалуйста, используйте Xray или Sing-Box для поддержки дополнительных методов передачи в Shadowsocks." msgstr "Пожалуйста, используйте Xray или Sing-Box для поддержки дополнительных методов передачи в Shadowsocks."
@@ -2359,3 +2380,6 @@ msgstr "Ошибка, выполняется восстановление слу
msgid "Subscription complete..." msgid "Subscription complete..."
msgstr "Подписка завершена..." msgstr "Подписка завершена..."
msgid "Processing, please wait…"
msgstr "Обработка, пожалуйста, подождите…"
+31 -7
View File
@@ -559,6 +559,9 @@ msgstr "认证类型"
msgid "Auth Password" msgid "Auth Password"
msgstr "认证密码" msgstr "认证密码"
msgid "Client BBR Flow Control"
msgstr "客户端 BBR 流控"
msgid "Commands the client to use the BBR flow control algorithm" msgid "Commands the client to use the BBR flow control algorithm"
msgstr "命令客户端使用 BBR 流量控制算法" msgstr "命令客户端使用 BBR 流量控制算法"
@@ -583,6 +586,9 @@ msgstr "QUIC 连接接收窗口"
msgid "QUIC concurrent bidirectional streams" msgid "QUIC concurrent bidirectional streams"
msgstr "QUIC 并发双向流的最大数量" msgstr "QUIC 并发双向流的最大数量"
msgid "QUIC KeepAlive interval"
msgstr "QUIC KeepAlive 间隔"
msgid "Disable MTU detection" msgid "Disable MTU detection"
msgstr "禁用 MTU 检测" msgstr "禁用 MTU 检测"
@@ -1327,6 +1333,9 @@ msgstr "允许不安全连接"
msgid "Whether unsafe connections are allowed. When checked, Certificate validation will be skipped." msgid "Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."
msgstr "是否允许不安全连接。当勾选时,将跳过证书验证。" msgstr "是否允许不安全连接。当勾选时,将跳过证书验证。"
msgid "Used when the node link does not include this parameter."
msgstr "当节点链接未包含该参数时,将使用此设置。"
msgid "%s Node Use Type" msgid "%s Node Use Type"
msgstr "%s 节点使用类型" msgstr "%s 节点使用类型"
@@ -1345,6 +1354,9 @@ msgstr "用于本地Socks服务器连接的密码"
msgid "UDP relay mode" msgid "UDP relay mode"
msgstr "UDP中继模式" msgstr "UDP中继模式"
msgid "Concurrent Tunnels"
msgstr "并发隧道连接数"
msgid "Congestion control algorithm" msgid "Congestion control algorithm"
msgstr "拥塞控制算法" msgstr "拥塞控制算法"
@@ -1666,12 +1678,21 @@ msgstr "ECH 查询策略"
msgid "Controls the policy used when performing DNS queries for ECH configuration." msgid "Controls the policy used when performing DNS queries for ECH configuration."
msgstr "控制使用 DNS 查询 ECH 配置时的策略。" msgstr "控制使用 DNS 查询 ECH 配置时的策略。"
msgid "ECH Query Domain"
msgstr "ECH 查询域名"
msgid "Overrides the domain name used for ECH HTTPS record queries."
msgstr "覆盖用于 ECH HTTPS 记录查询的域名。"
msgid "Enable Multipath TCP, need to be enabled in both server and client configuration." msgid "Enable Multipath TCP, need to be enabled in both server and client configuration."
msgstr "启用 Multipath TCP,需在服务端和客户端配置中同时启用。" msgstr "启用 Multipath TCP,需在服务端和客户端配置中同时启用。"
msgid "An FinalMaskObject in JSON format, used for sharing." msgid "An FinalMaskObject in JSON format, used for sharing."
msgstr "JSON 格式的 FinalMaskObject,用来实现分享。" msgstr "JSON 格式的 FinalMaskObject,用来实现分享。"
msgid "Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings."
msgstr "自定义 finalmask 将覆盖 mkcp、hysteria2、fragment、noise 等相关配置。"
msgid "Fragment" msgid "Fragment"
msgstr "分片" msgstr "分片"
@@ -1690,8 +1711,8 @@ msgstr "分片包长"
msgid "Fragmented packet length (byte)" msgid "Fragmented packet length (byte)"
msgstr "分片包长 (byte)" msgstr "分片包长 (byte)"
msgid "Fragment Interval" msgid "Fragment Delay"
msgstr "分片间隔" msgstr "分片延迟"
msgid "Fragmentation interval (ms)" msgid "Fragmentation interval (ms)"
msgstr "分片间隔(ms)" msgstr "分片间隔(ms)"
@@ -1720,15 +1741,12 @@ msgstr "在 Xray 设置中勾选 “噪声” 以发送噪声包。"
msgid "Xray Noise Packets" msgid "Xray Noise Packets"
msgstr "Xray 噪声数据包" msgstr "Xray 噪声数据包"
msgid "Packet" msgid "Packet | Rand Length"
msgstr "数据包" msgstr "数据包 | Rand 长度"
msgid "Delay (ms)" msgid "Delay (ms)"
msgstr "延迟(ms)" msgstr "延迟(ms)"
msgid "IP Type"
msgstr "IP 类型"
msgid "If is domain name, The requested domain name will be resolved to IP before connect." msgid "If is domain name, The requested domain name will be resolved to IP before connect."
msgstr "如果是域名,域名将在请求发出之前解析为 IP。" msgstr "如果是域名,域名将在请求发出之前解析为 IP。"
@@ -2267,6 +2285,9 @@ msgstr "插件选项不完整。"
msgid "shadowsocks-libev unsupport 2022 encryption." msgid "shadowsocks-libev unsupport 2022 encryption."
msgstr "shadowsocks-libev 不支持2022加密。" msgstr "shadowsocks-libev 不支持2022加密。"
msgid "Please replace %s to support %s transmission method."
msgstr "请更换 %s 以支持 %s 传输方式。"
msgid "Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks." msgid "Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks."
msgstr "请更换 Xray 或 Sing-Box 来支持 Shadowsocks 更多的传输方式。" msgstr "请更换 Xray 或 Sing-Box 来支持 Shadowsocks 更多的传输方式。"
@@ -2353,3 +2374,6 @@ msgstr "发生错误, 正在恢复服务。"
msgid "Subscription complete..." msgid "Subscription complete..."
msgstr "订阅完毕..." msgstr "订阅完毕..."
msgid "Processing, please wait…"
msgstr "正在处理,请稍候。"
+32 -8
View File
@@ -559,6 +559,9 @@ msgstr "認證類型"
msgid "Auth Password" msgid "Auth Password"
msgstr "認證密碼" msgstr "認證密碼"
msgid "Client BBR Flow Control"
msgstr "客戶端 BBR 流控"
msgid "Commands the client to use the BBR flow control algorithm" msgid "Commands the client to use the BBR flow control algorithm"
msgstr "命令客戶端使用 BBR 流量控制算法" msgstr "命令客戶端使用 BBR 流量控制算法"
@@ -583,6 +586,9 @@ msgstr "QUIC 連接接收窗口"
msgid "QUIC concurrent bidirectional streams" msgid "QUIC concurrent bidirectional streams"
msgstr "QUIC 並發双向流的最大數量" msgstr "QUIC 並發双向流的最大數量"
msgid "QUIC KeepAlive interval"
msgstr "QUIC KeepAlive 間隔"
msgid "Disable MTU detection" msgid "Disable MTU detection"
msgstr "禁用 MTU 檢測" msgstr "禁用 MTU 檢測"
@@ -1327,6 +1333,9 @@ msgstr "允許不安全連接"
msgid "Whether unsafe connections are allowed. When checked, Certificate validation will be skipped." msgid "Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."
msgstr "是否允許不安全連接。當勾選時,將跳過證書驗證。" msgstr "是否允許不安全連接。當勾選時,將跳過證書驗證。"
msgid "Used when the node link does not include this parameter."
msgstr "當節點連結未包含該參數時,將使用此設定。"
msgid "%s Node Use Type" msgid "%s Node Use Type"
msgstr "%s 節點使用類型" msgstr "%s 節點使用類型"
@@ -1345,6 +1354,9 @@ msgstr "用於本地Socks服務器連接的密碼"
msgid "UDP relay mode" msgid "UDP relay mode"
msgstr "UDP中继模式" msgstr "UDP中继模式"
msgid "Concurrent Tunnels"
msgstr "並發隧道連接數"
msgid "Congestion control algorithm" msgid "Congestion control algorithm"
msgstr "拥塞控制算法" msgstr "拥塞控制算法"
@@ -1666,12 +1678,21 @@ msgstr "ECH 查詢策略"
msgid "Controls the policy used when performing DNS queries for ECH configuration." msgid "Controls the policy used when performing DNS queries for ECH configuration."
msgstr "控制使用 DNS 查詢 ECH 配置時的策略。" msgstr "控制使用 DNS 查詢 ECH 配置時的策略。"
msgid "ECH Query Domain"
msgstr "ECH 查詢網域名"
msgid "Overrides the domain name used for ECH HTTPS record queries."
msgstr "覆蓋用於 ECH HTTPS 記錄查詢的網域名。"
msgid "Enable Multipath TCP, need to be enabled in both server and client configuration." msgid "Enable Multipath TCP, need to be enabled in both server and client configuration."
msgstr "啟用 Multipath TCP,需在服務端和客戶端配置中同時啟用。" msgstr "啟用 Multipath TCP,需在服務端和客戶端配置中同時啟用。"
msgid "An FinalMaskObject in JSON format, used for sharing." msgid "An FinalMaskObject in JSON format, used for sharing."
msgstr "JSON 格式的 FinalMaskObject,用來實現分享。" msgstr "JSON 格式的 FinalMaskObject,用來實現分享。"
msgid "Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings."
msgstr "自定義 finalmask 將涵蓋 mkcp、hysteria2、fragment、noise 等相關配置。"
msgid "Fragment" msgid "Fragment"
msgstr "分片" msgstr "分片"
@@ -1690,8 +1711,8 @@ msgstr "分片包长"
msgid "Fragmented packet length (byte)" msgid "Fragmented packet length (byte)"
msgstr "分片包长 (byte)" msgstr "分片包长 (byte)"
msgid "Fragment Interval" msgid "Fragment Delay"
msgstr "分片間隔" msgstr "分片延遲"
msgid "Fragmentation interval (ms)" msgid "Fragmentation interval (ms)"
msgstr "分片間隔(ms)" msgstr "分片間隔(ms)"
@@ -1720,15 +1741,12 @@ msgstr "在 Xray 設置中勾選 “噪聲” 以發送噪聲包。"
msgid "Xray Noise Packets" msgid "Xray Noise Packets"
msgstr "Xray 噪聲數据包" msgstr "Xray 噪聲數据包"
msgid "Packet" msgid "Packet | Rand Length"
msgstr "數据包" msgstr "數據包 | Rand 長度"
msgid "Delay (ms)" msgid "Delay (ms)"
msgstr "延迟(ms)" msgstr "延迟(ms)"
msgid "IP Type"
msgstr "IP 類型"
msgid "If is domain name, The requested domain name will be resolved to IP before connect." msgid "If is domain name, The requested domain name will be resolved to IP before connect."
msgstr "如果是域名,域名將在請求發出之前解析為 IP。" msgstr "如果是域名,域名將在請求發出之前解析為 IP。"
@@ -2267,8 +2285,11 @@ msgstr "插件選項不完整。"
msgid "shadowsocks-libev unsupport 2022 encryption." msgid "shadowsocks-libev unsupport 2022 encryption."
msgstr "shadowsocks-libev 不支持2022加密。" msgstr "shadowsocks-libev 不支持2022加密。"
msgid "Please replace %s to support %s transmission method."
msgstr "請更換 %s 以支援 %s 傳輸方式。"
msgid "Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks." msgid "Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks."
msgstr "請更換 Xray 或 Sing-Box 來支持 Shadowsocks 更多的傳輸方式。" msgstr "請更換 Xray 或 Sing-Box 來支援 Shadowsocks 更多的傳輸方式。"
msgid "unsupport %s plugin." msgid "unsupport %s plugin."
msgstr "不支持 %s 插件。" msgstr "不支持 %s 插件。"
@@ -2353,3 +2374,6 @@ msgstr "發生錯誤, 正在恢復服務。"
msgid "Subscription complete..." msgid "Subscription complete..."
msgstr "訂閱完毕..." msgstr "訂閱完毕..."
msgid "Processing, please wait…"
msgstr "正在處理,請稍候。"
@@ -13,6 +13,8 @@ IPSET_WAN6="passwall2_wan6"
IPSET_LAN6="passwall2_lan6" IPSET_LAN6="passwall2_lan6"
IPSET_VPS6="passwall2_vps6" IPSET_VPS6="passwall2_vps6"
FWMARK="0x50535732"
ipt=$(command -v iptables-legacy || command -v iptables) ipt=$(command -v iptables-legacy || command -v iptables)
ip6t=$(command -v ip6tables-legacy || command -v ip6tables) ip6t=$(command -v ip6tables-legacy || command -v ip6tables)
@@ -150,10 +152,9 @@ REDIRECT() {
local s="-j REDIRECT" local s="-j REDIRECT"
[ -n "$1" ] && { [ -n "$1" ] && {
local s="$s --to-ports $1" local s="$s --to-ports $1"
[ "$2" == "MARK" ] && s="-j MARK --set-mark $1"
[ "$2" == "TPROXY" ] && { [ "$2" == "TPROXY" ] && {
local mark="-m mark --mark 1" local mark="-m mark --mark ${FWMARK}"
s="${mark} -j TPROXY --tproxy-mark 0x1/0x1 --on-port $1" s="${mark} -j TPROXY --on-port $1"
} }
} }
echo $s echo $s
@@ -688,7 +689,7 @@ add_firewall_rule() {
$ipt_n -N PSW2_OUTPUT $ipt_n -N PSW2_OUTPUT
$ipt_n -A PSW2_OUTPUT $(dst $IPSET_LAN) -j RETURN $ipt_n -A PSW2_OUTPUT $(dst $IPSET_LAN) -j RETURN
$ipt_n -A PSW2_OUTPUT $(dst $IPSET_VPS) -j RETURN $ipt_n -A PSW2_OUTPUT $(dst $IPSET_VPS) -j RETURN
$ipt_n -A PSW2_OUTPUT -m mark --mark 0xff -j RETURN $ipt_n -A PSW2_OUTPUT -m mark --mark 255 -j RETURN
$ipt_n -N PSW2_DNS $ipt_n -N PSW2_DNS
if [ $(config_t_get global dns_redirect "1") = "0" ]; then if [ $(config_t_get global dns_redirect "1") = "0" ]; then
@@ -697,12 +698,16 @@ add_firewall_rule() {
else else
$ipt_n -I PREROUTING -j PSW2_DNS $ipt_n -I PREROUTING -j PSW2_DNS
fi fi
$ipt_m -N PSW2_DIVERT
$ipt_m -A PSW2_DIVERT -j MARK --set-mark ${FWMARK}
$ipt_m -A PSW2_DIVERT -j ACCEPT
$ipt_m -N PSW2_RULE $ipt_m -N PSW2_RULE
$ipt_m -A PSW2_RULE -j CONNMARK --restore-mark $ipt_m -A PSW2_RULE -j CONNMARK --restore-mark
$ipt_m -A PSW2_RULE -m mark --mark 0x1 -j RETURN $ipt_m -A PSW2_RULE -m mark --mark ${FWMARK} -j RETURN
$ipt_m -A PSW2_RULE -p tcp -m tcp --syn -j MARK --set-xmark 1 $ipt_m -A PSW2_RULE -p tcp -m tcp --syn -j MARK --set-xmark ${FWMARK}
$ipt_m -A PSW2_RULE -p udp -m conntrack --ctstate NEW,RELATED -j MARK --set-xmark 1 $ipt_m -A PSW2_RULE -p udp -m conntrack --ctstate NEW,RELATED -j MARK --set-xmark ${FWMARK}
$ipt_m -A PSW2_RULE -j CONNMARK --save-mark $ipt_m -A PSW2_RULE -j CONNMARK --save-mark
$ipt_m -N PSW2 $ipt_m -N PSW2
@@ -722,6 +727,8 @@ add_firewall_rule() {
unset WAN_IP wan_ip unset WAN_IP wan_ip
insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW2" insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW2"
insert_rule_before "$ipt_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT"
insert_rule_before "$ipt_m" "PREROUTING" "PSW2" "-p udp -m socket -j PSW2_DIVERT"
$ipt_m -N PSW2_OUTPUT $ipt_m -N PSW2_OUTPUT
$ipt_m -A PSW2_OUTPUT $(dst $IPSET_LAN) -j RETURN $ipt_m -A PSW2_OUTPUT $(dst $IPSET_LAN) -j RETURN
@@ -735,10 +742,10 @@ add_firewall_rule() {
log_i18n 1 "$(i18n "Add direct DNS to %s: %s" "iptables" "${dns_address}:${dns_port:-53}")" log_i18n 1 "$(i18n "Add direct DNS to %s: %s" "iptables" "${dns_address}:${dns_port:-53}")"
done done
} }
$ipt_m -A PSW2_OUTPUT -m mark --mark 0xff -j RETURN $ipt_m -A PSW2_OUTPUT -m mark --mark 255 -j RETURN
ip rule add fwmark 1 lookup 100 ip rule add fwmark ${FWMARK} lookup 999 priority 999
ip route add local 0.0.0.0/0 dev lo table 100 ip route add local 0.0.0.0/0 dev lo table 999
[ "$accept_icmpv6" = "1" ] && { [ "$accept_icmpv6" = "1" ] && {
$ip6t_n -N PSW2 $ip6t_n -N PSW2
@@ -749,7 +756,7 @@ add_firewall_rule() {
$ip6t_n -N PSW2_OUTPUT $ip6t_n -N PSW2_OUTPUT
$ip6t_n -A PSW2_OUTPUT $(dst $IPSET_LAN6) -j RETURN $ip6t_n -A PSW2_OUTPUT $(dst $IPSET_LAN6) -j RETURN
$ip6t_n -A PSW2_OUTPUT $(dst $IPSET_VPS6) -j RETURN $ip6t_n -A PSW2_OUTPUT $(dst $IPSET_VPS6) -j RETURN
$ip6t_n -A PSW2_OUTPUT -m mark --mark 0xff -j RETURN $ip6t_n -A PSW2_OUTPUT -m mark --mark 255 -j RETURN
} }
$ip6t_n -N PSW2_DNS $ip6t_n -N PSW2_DNS
@@ -759,12 +766,16 @@ add_firewall_rule() {
else else
$ip6t_n -I PREROUTING -j PSW2_DNS $ip6t_n -I PREROUTING -j PSW2_DNS
fi fi
$ip6t_m -N PSW2_DIVERT
$ip6t_m -A PSW2_DIVERT -j MARK --set-mark ${FWMARK}
$ip6t_m -A PSW2_DIVERT -j ACCEPT
$ip6t_m -N PSW2_RULE $ip6t_m -N PSW2_RULE
$ip6t_m -A PSW2_RULE -j CONNMARK --restore-mark $ip6t_m -A PSW2_RULE -j CONNMARK --restore-mark
$ip6t_m -A PSW2_RULE -m mark --mark 0x1 -j RETURN $ip6t_m -A PSW2_RULE -m mark --mark ${FWMARK} -j RETURN
$ip6t_m -A PSW2_RULE -p tcp -m tcp --syn -j MARK --set-xmark 1 $ip6t_m -A PSW2_RULE -p tcp -m tcp --syn -j MARK --set-xmark ${FWMARK}
$ip6t_m -A PSW2_RULE -p udp -m conntrack --ctstate NEW,RELATED -j MARK --set-xmark 1 $ip6t_m -A PSW2_RULE -p udp -m conntrack --ctstate NEW,RELATED -j MARK --set-xmark ${FWMARK}
$ip6t_m -A PSW2_RULE -j CONNMARK --save-mark $ip6t_m -A PSW2_RULE -j CONNMARK --save-mark
$ip6t_m -N PSW2 $ip6t_m -N PSW2
@@ -783,15 +794,17 @@ add_firewall_rule() {
unset WAN6_IP wan6_ip unset WAN6_IP wan6_ip
insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW2" insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW2"
insert_rule_before "$ip6t_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT"
insert_rule_before "$ip6t_m" "PREROUTING" "PSW2" "-p udp -m socket -j PSW2_DIVERT"
$ip6t_m -N PSW2_OUTPUT $ip6t_m -N PSW2_OUTPUT
$ip6t_m -A PSW2_OUTPUT -m mark --mark 0xff -j RETURN $ip6t_m -A PSW2_OUTPUT -m mark --mark 255 -j RETURN
$ip6t_m -A PSW2_OUTPUT $(dst $IPSET_LAN6) -j RETURN $ip6t_m -A PSW2_OUTPUT $(dst $IPSET_LAN6) -j RETURN
$ip6t_m -A PSW2_OUTPUT $(dst $IPSET_VPS6) -j RETURN $ip6t_m -A PSW2_OUTPUT $(dst $IPSET_VPS6) -j RETURN
$ip6t_m -A PSW2_OUTPUT -m conntrack --ctdir REPLY -j RETURN $ip6t_m -A PSW2_OUTPUT -m conntrack --ctdir REPLY -j RETURN
ip -6 rule add fwmark 1 table 100 ip -6 rule add fwmark ${FWMARK} table 999 priority 999
ip -6 route add local ::/0 dev lo table 100 ip -6 route add local ::/0 dev lo table 999
[ "$ENABLED_DEFAULT_ACL" == 1 ] && { [ "$ENABLED_DEFAULT_ACL" == 1 ] && {
local ipt_tmp=$ipt_n local ipt_tmp=$ipt_n
@@ -910,10 +923,10 @@ add_firewall_rule() {
fi fi
$ipt_m -I OUTPUT $(comment "mangle-OUTPUT-PSW2") -o lo -j RETURN $ipt_m -I OUTPUT $(comment "mangle-OUTPUT-PSW2") -o lo -j RETURN
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -m mark --mark 1 -j RETURN" insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -m mark --mark ${FWMARK} -j RETURN"
$ip6t_m -I OUTPUT $(comment "mangle-OUTPUT-PSW2") -o lo -j RETURN $ip6t_m -I OUTPUT $(comment "mangle-OUTPUT-PSW2") -o lo -j RETURN
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -m mark --mark 1 -j RETURN" insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -m mark --mark ${FWMARK} -j RETURN"
$ipt_m -A PSW2 -p udp --dport 53 -j RETURN $ipt_m -A PSW2 -p udp --dport 53 -j RETURN
$ip6t_m -A PSW2 -p udp --dport 53 -j RETURN $ip6t_m -A PSW2 -p udp --dport 53 -j RETURN
@@ -934,17 +947,17 @@ del_firewall_rule() {
$ipt -D $chain $index 2>/dev/null $ipt -D $chain $index 2>/dev/null
done done
done done
for chain in "PSW2" "PSW2_OUTPUT" "PSW2_DNS" "PSW2_RULE"; do for chain in "PSW2" "PSW2_OUTPUT" "PSW2_DIVERT" "PSW2_DNS" "PSW2_RULE"; do
$ipt -F $chain 2>/dev/null $ipt -F $chain 2>/dev/null
$ipt -X $chain 2>/dev/null $ipt -X $chain 2>/dev/null
done done
done done
ip rule del fwmark 1 lookup 100 2>/dev/null ip rule del fwmark ${FWMARK} lookup 999 priority 999 2>/dev/null
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null ip route del local 0.0.0.0/0 dev lo table 999 2>/dev/null
ip -6 rule del fwmark 1 table 100 2>/dev/null ip -6 rule del fwmark ${FWMARK} table 999 priority 999 2>/dev/null
ip -6 route del local ::/0 dev lo table 100 2>/dev/null ip -6 route del local ::/0 dev lo table 999 2>/dev/null
log_i18n 0 "Delete %s rules is complete." "iptables" log_i18n 0 "Delete %s rules is complete." "iptables"
} }
@@ -968,7 +981,7 @@ gen_include() {
[ -z "${_ipt}" ] && return [ -z "${_ipt}" ] && return
echo "*$2" echo "*$2"
${_ipt}-save -t $2 | grep "PSW2" | grep -v "\-j PSW2$" | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/" ${_ipt}-save -t $2 | grep "PSW2" | grep -v "\-j PSW2$" | grep -v "socket \-j PSW2_DIVERT$" | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/"
echo 'COMMIT' echo 'COMMIT'
} }
local __ipt="" local __ipt=""
@@ -985,6 +998,8 @@ gen_include() {
[ -z "${is_tproxy}" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p tcp -j PSW2") [ -z "${is_tproxy}" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p tcp -j PSW2")
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW2") \$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW2")
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT")
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "PSW2" "-p udp -m socket -j PSW2_DIVERT")
WAN_IP=\$(get_wan_ips ip4) WAN_IP=\$(get_wan_ips ip4)
[ ! -z "\${WAN_IP}" ] && { [ ! -z "\${WAN_IP}" ] && {
@@ -1009,6 +1024,8 @@ gen_include() {
[ "$accept_icmpv6" = "1" ] && $ip6t_n -A PREROUTING -p ipv6-icmp -j PSW2 [ "$accept_icmpv6" = "1" ] && $ip6t_n -A PREROUTING -p ipv6-icmp -j PSW2
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW2") \$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW2")
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT")
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "PSW2" "-p udp -m socket -j PSW2_DIVERT")
WAN6_IP=\$(get_wan_ips ip6) WAN6_IP=\$(get_wan_ips ip6)
[ ! -z "\${WAN6_IP}" ] && { [ ! -z "\${WAN6_IP}" ] && {
@@ -14,6 +14,8 @@ NFTSET_WAN6="passwall2_wan6"
NFTSET_LAN6="passwall2_lan6" NFTSET_LAN6="passwall2_lan6"
NFTSET_VPS6="passwall2_vps6" NFTSET_VPS6="passwall2_vps6"
FWMARK="0x50535732"
FWI=$(uci -q get firewall.passwall2.path 2>/dev/null) FWI=$(uci -q get firewall.passwall2.path 2>/dev/null)
FAKE_IP="198.18.0.0/16" FAKE_IP="198.18.0.0/16"
FAKE_IP_6="fc00::/18" FAKE_IP_6="fc00::/18"
@@ -103,15 +105,14 @@ REDIRECT() {
local s="counter redirect" local s="counter redirect"
[ -n "$1" ] && { [ -n "$1" ] && {
local s="$s to :$1" local s="$s to :$1"
[ "$2" == "MARK" ] && s="counter meta mark set $1"
[ "$2" == "TPROXY" ] && { [ "$2" == "TPROXY" ] && {
s="counter meta mark 1 tproxy to :$1" s="counter meta mark ${FWMARK} tproxy to :$1"
} }
[ "$2" == "TPROXY4" ] && { [ "$2" == "TPROXY4" ] && {
s="counter meta mark 1 tproxy ip to :$1" s="counter meta mark ${FWMARK} tproxy ip to :$1"
} }
[ "$2" == "TPROXY6" ] && { [ "$2" == "TPROXY6" ] && {
s="counter meta mark 1 tproxy ip6 to :$1" s="counter meta mark ${FWMARK} tproxy ip6 to :$1"
} }
} }
@@ -500,7 +501,7 @@ load_acl() {
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && { [ "$UDP_NO_REDIR_PORTS" != "disable" ] && {
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return comment \"${comment}\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 counter meta l4proto udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return comment \"${comment}\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return comment \"${comment}\""
if ! has_1_65535 "$UDP_NO_REDIR_PORTS"; then if ! has_1_65535 "$UDP_NO_REDIR_PORTS"; then
log 2 "${msg}$(i18n "not proxy %s port [%s]" "UDP" "${UDP_NO_REDIR_PORTS}")" log 2 "${msg}$(i18n "not proxy %s port [%s]" "UDP" "${UDP_NO_REDIR_PORTS}")"
else else
@@ -712,6 +713,11 @@ add_firewall_rule() {
nft_output_chain="PSW2_OUTPUT_MANGLE" nft_output_chain="PSW2_OUTPUT_MANGLE"
fi fi
nft "add chain $NFTABLE_NAME PSW2_DIVERT"
nft "flush chain $NFTABLE_NAME PSW2_DIVERT"
nft "add rule $NFTABLE_NAME PSW2_DIVERT meta l4proto tcp socket transparent 1 mark set ${FWMARK} counter accept"
nft "add rule $NFTABLE_NAME PSW2_DIVERT meta l4proto udp socket transparent 1 mark set ${FWMARK} counter accept"
nft "add chain $NFTABLE_NAME PSW2_DNS" nft "add chain $NFTABLE_NAME PSW2_DNS"
nft "flush chain $NFTABLE_NAME PSW2_DNS" nft "flush chain $NFTABLE_NAME PSW2_DNS"
if [ $(config_t_get global dns_redirect "1") = "0" ]; then if [ $(config_t_get global dns_redirect "1") = "0" ]; then
@@ -725,11 +731,11 @@ add_firewall_rule() {
# for ipv4 ipv6 tproxy mark # for ipv4 ipv6 tproxy mark
nft "add chain $NFTABLE_NAME PSW2_RULE" nft "add chain $NFTABLE_NAME PSW2_RULE"
nft "flush chain $NFTABLE_NAME PSW2_RULE" nft "flush chain $NFTABLE_NAME PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_RULE meta mark set ct mark counter" nft "add rule $NFTABLE_NAME PSW2_RULE counter meta mark set ct mark"
nft "add rule $NFTABLE_NAME PSW2_RULE meta mark 1 counter return" nft "add rule $NFTABLE_NAME PSW2_RULE meta mark ${FWMARK} counter return"
nft "add rule $NFTABLE_NAME PSW2_RULE tcp flags syn meta mark set mark and 0x0 xor 0x1 counter" nft "add rule $NFTABLE_NAME PSW2_RULE tcp flags & (fin|syn|rst|ack) == syn counter meta mark set ${FWMARK}"
nft "add rule $NFTABLE_NAME PSW2_RULE meta l4proto udp ct state new,related meta mark set mark and 0x0 xor 0x1 counter" nft "add rule $NFTABLE_NAME PSW2_RULE meta l4proto udp ct state new,related counter meta mark set ${FWMARK}"
nft "add rule $NFTABLE_NAME PSW2_RULE ct mark set mark counter" nft "add rule $NFTABLE_NAME PSW2_RULE counter ct mark set mark"
#ipv4 tproxy mode and udp #ipv4 tproxy mode and udp
nft "add chain $NFTABLE_NAME PSW2_MANGLE" nft "add chain $NFTABLE_NAME PSW2_MANGLE"
@@ -751,9 +757,10 @@ add_firewall_rule() {
log_i18n 1 "$(i18n "Add direct DNS to %s: %s" "nftables" "${dns_address}:${dns_port:-53}")" log_i18n 1 "$(i18n "Add direct DNS to %s: %s" "nftables" "${dns_address}:${dns_port:-53}")"
done done
} }
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE meta mark 0xff counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE meta mark 255 counter return"
# jump chains # jump chains
nft "add rule $NFTABLE_NAME mangle_prerouting counter jump PSW2_DIVERT"
nft "add rule $NFTABLE_NAME mangle_prerouting ip protocol udp counter jump PSW2_MANGLE" nft "add rule $NFTABLE_NAME mangle_prerouting ip protocol udp counter jump PSW2_MANGLE"
[ -n "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME mangle_prerouting ip protocol tcp counter jump PSW2_MANGLE" [ -n "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME mangle_prerouting ip protocol tcp counter jump PSW2_MANGLE"
@@ -769,7 +776,7 @@ add_firewall_rule() {
nft "flush chain $NFTABLE_NAME PSW2_OUTPUT_NAT" nft "flush chain $NFTABLE_NAME PSW2_OUTPUT_NAT"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_NAT ip daddr @$NFTSET_LAN counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_NAT ip daddr @$NFTSET_LAN counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_NAT ip daddr @$NFTSET_VPS counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_NAT ip daddr @$NFTSET_VPS counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_NAT meta mark 0xff counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_NAT meta mark 255 counter return"
} }
#icmp ipv6-icmp redirect #icmp ipv6-icmp redirect
@@ -797,8 +804,8 @@ add_firewall_rule() {
} }
unset WAN_IP unset WAN_IP
ip rule add fwmark 1 lookup 100 ip rule add fwmark ${FWMARK} lookup 999 priority 999
ip route add local 0.0.0.0/0 dev lo table 100 ip route add local 0.0.0.0/0 dev lo table 999
#ipv6 tproxy mode and udp #ipv6 tproxy mode and udp
nft "add chain $NFTABLE_NAME PSW2_MANGLE_V6" nft "add chain $NFTABLE_NAME PSW2_MANGLE_V6"
@@ -812,7 +819,7 @@ add_firewall_rule() {
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_LAN6 counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_LAN6 counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_VPS6 counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_VPS6 counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 ct direction reply counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 ct direction reply counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta mark 0xff counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta mark 255 counter return"
# jump chains # jump chains
[ "$PROXY_IPV6" == "1" ] && { [ "$PROXY_IPV6" == "1" ] && {
@@ -827,8 +834,8 @@ add_firewall_rule() {
} }
unset WAN6_IP unset WAN6_IP
ip -6 rule add fwmark 1 table 100 ip -6 rule add fwmark ${FWMARK} table 999 priority 999
ip -6 route add local ::/0 dev lo table 100 ip -6 route add local ::/0 dev lo table 999
} }
[ "$ENABLED_DEFAULT_ACL" == 1 ] && { [ "$ENABLED_DEFAULT_ACL" == 1 ] && {
@@ -945,7 +952,7 @@ add_firewall_rule() {
fi fi
nft "add rule $NFTABLE_NAME mangle_output oif lo counter return comment \"PSW2_OUTPUT_MANGLE\"" nft "add rule $NFTABLE_NAME mangle_output oif lo counter return comment \"PSW2_OUTPUT_MANGLE\""
nft "add rule $NFTABLE_NAME mangle_output meta mark 1 counter return comment \"PSW2_OUTPUT_MANGLE\"" nft "add rule $NFTABLE_NAME mangle_output meta mark ${FWMARK} counter return comment \"PSW2_OUTPUT_MANGLE\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp udp dport 53 counter return" nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp udp dport 53 counter return"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp udp dport 53 counter return" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp udp dport 53 counter return"
@@ -973,11 +980,11 @@ del_firewall_rule() {
# Need to be removed at the end, otherwise it will show "Resource busy" # Need to be removed at the end, otherwise it will show "Resource busy"
nft delete chain $NFTABLE_NAME handle $(nft -a list chains | grep -E "PSW2_RULE" | awk -F '# handle ' '{print$2}') 2>/dev/null nft delete chain $NFTABLE_NAME handle $(nft -a list chains | grep -E "PSW2_RULE" | awk -F '# handle ' '{print$2}') 2>/dev/null
ip rule del fwmark 1 lookup 100 2>/dev/null ip rule del fwmark ${FWMARK} lookup 999 priority 999 2>/dev/null
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null ip route del local 0.0.0.0/0 dev lo table 999 2>/dev/null
ip -6 rule del fwmark 1 table 100 2>/dev/null ip -6 rule del fwmark ${FWMARK} table 999 priority 999 2>/dev/null
ip -6 route del local ::/0 dev lo table 100 2>/dev/null ip -6 route del local ::/0 dev lo table 999 2>/dev/null
destroy_nftset $NFTSET_LOCAL destroy_nftset $NFTSET_LOCAL
destroy_nftset $NFTSET_WAN destroy_nftset $NFTSET_WAN
@@ -453,7 +453,7 @@ local function processData(szType, content, add_mode, group)
end end
result.type = "SSR" result.type = "SSR"
local dat = split(content, "/%?") local dat = split(content:gsub("/%?", "?"), "%?")
local hostInfo = split(dat[1], ':') local hostInfo = split(dat[1], ':')
if dat[1]:match('%[(.*)%]') then if dat[1]:match('%[(.*)%]') then
result.address = dat[1]:match('%[(.*)%]') result.address = dat[1]:match('%[(.*)%]')
@@ -467,8 +467,10 @@ local function processData(szType, content, add_mode, group)
result.password = base64Decode(hostInfo[#hostInfo]) result.password = base64Decode(hostInfo[#hostInfo])
local params = {} local params = {}
for _, v in pairs(split(dat[2], '&')) do for _, v in pairs(split(dat[2], '&')) do
local t = split(v, '=') local s = v:find("=", 1, true)
params[t[1]] = t[2] if s and s > 1 then
params[v:sub(1, s - 1)] = v:sub(s + 1)
end
end end
result.obfs_param = base64Decode(params.obfsparam) result.obfs_param = base64Decode(params.obfsparam)
result.protocol_param = base64Decode(params.protoparam) result.protocol_param = base64Decode(params.protoparam)
@@ -587,14 +589,10 @@ local function processData(szType, content, add_mode, group)
if info.tls == "tls" or info.tls == "1" then if info.tls == "tls" or info.tls == "1" then
result.tls = "1" result.tls = "1"
result.tls_serverName = (info.sni and info.sni ~= "") and info.sni or info.host result.tls_serverName = (info.sni and info.sni ~= "") and info.sni or info.host
info.allowinsecure = info.allowinsecure or info.insecure
if info.allowinsecure and (info.allowinsecure == "1" or info.allowinsecure == "0") then
result.tls_allowInsecure = info.allowinsecure
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
result.tls_CertSha = info.pcs result.tls_CertSha = info.pcs
result.tls_CertByName = info.vcn result.tls_CertByName = info.vcn
local insecure = info.allowinsecure or info.allowInsecure or info.insecure
result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (allowInsecure_default and "1" or "0")
else else
result.tls = "0" result.tls = "0"
end end
@@ -633,8 +631,10 @@ local function processData(szType, content, add_mode, group)
local find_index = info:find("%?") local find_index = info:find("%?")
local query = split(info, "%?") local query = split(info, "%?")
for _, v in pairs(split(query[2], '&')) do for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=') local s = v:find("=", 1, true)
if #t >= 2 then params[t[1]] = UrlDecode(t[2]) end if s and s > 1 then
params[v:sub(1, s - 1)] = UrlDecode(v:sub(s + 1))
end
end end
if params.plugin then if params.plugin then
local plugin_info = params.plugin local plugin_info = params.plugin
@@ -838,7 +838,7 @@ local function processData(szType, content, add_mode, group)
end end
if params.type == 'xhttp' then if params.type == 'xhttp' then
if result.type ~= "Xray" then if result.type ~= "Xray" then
result.error_msg = "请更换 Xray 以支持 xhttp 传输方式." result.error_msg = i18n.translatef("Please replace %s to support %s transmission method.", "Xray", "xhttp")
end end
result.xhttp_host = params.host result.xhttp_host = params.host
result.xhttp_path = params.path result.xhttp_path = params.path
@@ -876,12 +876,8 @@ local function processData(szType, content, add_mode, group)
result.reality_mldsa65Verify = params.pqv or nil result.reality_mldsa65Verify = params.pqv or nil
end end
end end
params.allowinsecure = params.allowinsecure or params.insecure local insecure = params.allowinsecure or params.allowInsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (allowInsecure_default and "1" or "0")
result.tls_allowInsecure = params.allowinsecure
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
result.uot = params.udp result.uot = params.udp
elseif (params.type ~= "tcp" and params.type ~= "raw") and (params.headerType and params.headerType ~= "none") then elseif (params.type ~= "tcp" and params.type ~= "raw") and (params.headerType and params.headerType ~= "none") then
result.error_msg = i18n.translatef("Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks.") result.error_msg = i18n.translatef("Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks.")
@@ -959,9 +955,9 @@ local function processData(szType, content, add_mode, group)
local host_port = query[1] local host_port = query[1]
local params = {} local params = {}
for _, v in pairs(split(query[2], '&')) do for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=') local s = v:find("=", 1, true)
if #t > 1 then if s and s > 1 then
params[string.lower(t[1])] = UrlDecode(t[2]) params[v:sub(1, s - 1)] = UrlDecode(v:sub(s + 1))
end end
end end
-- [2001:4860:4860::8888]:443 -- [2001:4860:4860::8888]:443
@@ -978,32 +974,14 @@ local function processData(szType, content, add_mode, group)
result.address = host_port result.address = host_port
end end
local peer, sni = nil, ""
if params.peer then peer = params.peer end
sni = params.sni and params.sni or ""
if params.ws and params.ws == "1" then
result.trojan_transport = "ws"
if params.wshost then result.ws_host = params.wshost end
if params.wspath then result.ws_path = params.wspath end
if sni == "" and params.wshost then sni = params.wshost end
end
result.port = port result.port = port
result.tls = '1' result.tls = '1'
result.tls_serverName = peer and peer or sni result.tls_serverName = params.peer or params.sni or ""
result.tls_CertSha = params.pcs result.tls_CertSha = params.pcs
result.tls_CertByName = params.vcn result.tls_CertByName = params.vcn
local insecure = params.allowinsecure or params.allowInsecure or params.insecure
params.allowinsecure = params.allowinsecure or params.insecure result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (allowInsecure_default and "1" or "0")
if params.allowinsecure then
if params.allowinsecure == "1" or params.allowinsecure == "0" then
result.tls_allowInsecure = params.allowinsecure
else
result.tls_allowInsecure = string.lower(params.allowinsecure) == "true" and "1" or "0"
end
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
if not params.type then params.type = "tcp" end if not params.type then params.type = "tcp" end
params.type = string.lower(params.type) params.type = string.lower(params.type)
@@ -1071,8 +1049,7 @@ local function processData(szType, content, add_mode, group)
result.quic_security = params.quicSecurity or "none" result.quic_security = params.quicSecurity or "none"
end end
if params.type == 'grpc' then if params.type == 'grpc' then
if params.path then result.grpc_serviceName = params.path end result.grpc_serviceName = params.serviceName or params.path
if params.serviceName then result.grpc_serviceName = params.serviceName end
result.grpc_mode = params.mode or "gun" result.grpc_mode = params.mode or "gun"
end end
if params.type == 'xhttp' then if params.type == 'xhttp' then
@@ -1131,8 +1108,10 @@ local function processData(szType, content, add_mode, group)
local host_port = query[1] local host_port = query[1]
local params = {} local params = {}
for _, v in pairs(split(query[2], '&')) do for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=') local s = v:find("=", 1, true)
params[t[1]] = UrlDecode(t[2]) if s and s > 1 then
params[v:sub(1, s - 1)] = UrlDecode(v:sub(s + 1))
end
end end
-- [2001:4860:4860::8888]:443 -- [2001:4860:4860::8888]:443
-- 8.8.8.8:443 -- 8.8.8.8:443
@@ -1265,17 +1244,11 @@ local function processData(szType, content, add_mode, group)
result.use_mldsa65Verify = (params.pqv and params.pqv ~= "") and "1" or nil result.use_mldsa65Verify = (params.pqv and params.pqv ~= "") and "1" or nil
result.reality_mldsa65Verify = params.pqv or nil result.reality_mldsa65Verify = params.pqv or nil
end end
local insecure = params.allowinsecure or params.allowInsecure or params.insecure
result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (allowInsecure_default and "1" or "0")
end end
result.port = port 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
result.tcp_fast_open = params.tfo result.tcp_fast_open = params.tfo
result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil
result.finalmask = (params.fm and params.fm ~= "") and api.base64Encode(params.fm) or nil result.finalmask = (params.fm and params.fm ~= "") and api.base64Encode(params.fm) or nil
@@ -1302,13 +1275,13 @@ local function processData(szType, content, add_mode, group)
end end
result.remarks = UrlDecode(alias) result.remarks = UrlDecode(alias)
local dat = split(content:gsub("/%?", "?"), '%?') local query = split(content:gsub("/%?", "?"), '%?')
local host_port = dat[1] local host_port = query[1]
local params = {} local params = {}
for _, v in pairs(split(dat[2], '&')) do for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=') local s = v:find("=", 1, true)
if #t > 0 then if s and s > 1 then
params[t[1]] = t[2] params[v:sub(1, s - 1)] = v:sub(s + 1)
end end
end end
-- [2001:4860:4860::8888]:443 -- [2001:4860:4860::8888]:443
@@ -1327,14 +1300,10 @@ local function processData(szType, content, add_mode, group)
result.hysteria_obfs = params.obfsParam result.hysteria_obfs = params.obfsParam
result.hysteria_auth_type = "string" result.hysteria_auth_type = "string"
result.hysteria_auth_password = params.auth result.hysteria_auth_password = params.auth
result.tls_serverName = params.peer result.tls_serverName = params.peer or params.sni or ""
params.allowinsecure = params.allowinsecure or params.insecure local insecure = params.allowinsecure or params.allowInsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (allowInsecure_default and "1" or "0")
result.tls_allowInsecure = params.allowinsecure result.alpn = params.alpn
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
result.hysteria_alpn = params.alpn
result.hysteria_up_mbps = params.upmbps result.hysteria_up_mbps = params.upmbps
result.hysteria_down_mbps = params.downmbps result.hysteria_down_mbps = params.downmbps
result.hysteria_hop = params.mport result.hysteria_hop = params.mport
@@ -1357,9 +1326,9 @@ local function processData(szType, content, add_mode, group)
local host_port = query[1] local host_port = query[1]
local params = {} local params = {}
for _, v in pairs(split(query[2], '&')) do for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=') local s = v:find("=", 1, true)
if #t > 1 then if s and s > 1 then
params[string.lower(t[1])] = UrlDecode(t[2]) params[v:sub(1, s - 1):lower()] = UrlDecode(v:sub(s + 1))
end end
end end
-- [2001:4860:4860::8888]:443 -- [2001:4860:4860::8888]:443
@@ -1378,12 +1347,8 @@ local function processData(szType, content, add_mode, group)
result.tls_serverName = params.sni result.tls_serverName = params.sni
result.tls_CertSha = params.pcs result.tls_CertSha = params.pcs
result.tls_CertByName = params.vcn result.tls_CertByName = params.vcn
params.allowinsecure = params.allowinsecure or params.insecure local insecure = params.allowinsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (allowInsecure_default and "1" or "0")
result.tls_allowInsecure = params.allowinsecure
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
result.hysteria2_tls_pinSHA256 = params.pinSHA256 result.hysteria2_tls_pinSHA256 = params.pinSHA256
result.hysteria2_hop = params.mport result.hysteria2_hop = params.mport
@@ -1441,9 +1406,9 @@ local function processData(szType, content, add_mode, group)
local host_port = query[1] local host_port = query[1]
local params = {} local params = {}
for _, v in pairs(split(query[2], '&')) do for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=') local s = v:find("=", 1, true)
if #t > 1 then if s and s > 1 then
params[string.lower(t[1])] = UrlDecode(t[2]) params[v:sub(1, s - 1):lower()] = UrlDecode(v:sub(s + 1))
end end
end end
if host_port:find(":") then if host_port:find(":") then
@@ -1462,16 +1427,8 @@ local function processData(szType, content, add_mode, group)
result.tuic_alpn = params.alpn or "default" result.tuic_alpn = params.alpn or "default"
result.tuic_congestion_control = params.congestion_control or "cubic" result.tuic_congestion_control = params.congestion_control or "cubic"
result.tuic_udp_relay_mode = params.udp_relay_mode or "native" result.tuic_udp_relay_mode = params.udp_relay_mode or "native"
params.allowinsecure = params.allowinsecure or params.insecure local insecure = params.allowinsecure or params.insecure or params.allow_insecure
if params.allowinsecure then result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (allowInsecure_default and "1" or "0")
if params.allowinsecure == "1" or params.allowinsecure == "0" then
result.tls_allowInsecure = params.allowinsecure
else
result.tls_allowInsecure = string.lower(params.allowinsecure) == "true" and "1" or "0"
end
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
elseif szType == "anytls" then elseif szType == "anytls" then
if has_singbox then if has_singbox then
result.type = 'sing-box' result.type = 'sing-box'
@@ -1497,8 +1454,10 @@ local function processData(szType, content, add_mode, group)
local host_port = query[1] local host_port = query[1]
local params = {} local params = {}
for _, v in pairs(split(query[2], '&')) do for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=') local s = v:find("=", 1, true)
params[t[1]] = UrlDecode(t[2]) if s and s > 1 then
params[v:sub(1, s - 1):lower()] = UrlDecode(v:sub(s + 1))
end
end end
-- [2001:4860:4860::8888]:443 -- [2001:4860:4860::8888]:443
-- 8.8.8.8:443 -- 8.8.8.8:443
@@ -1514,7 +1473,7 @@ local function processData(szType, content, add_mode, group)
result.address = host_port result.address = host_port
end end
result.tls = "0" result.tls = "0"
if (not params.security or params.security == "") and params.sni and params.sni ~= "" then if not params.security or params.security == "" then
params.security = "tls" params.security = "tls"
end end
if params.security == "tls" or params.security == "reality" then if params.security == "tls" or params.security == "reality" then
@@ -1532,18 +1491,70 @@ local function processData(szType, content, add_mode, group)
end end
end end
result.port = port result.port = port
params.allowinsecure = params.allowinsecure or params.insecure local insecure = params.allowinsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (allowInsecure_default and "1" or "0")
result.tls_allowInsecure = params.allowinsecure end
elseif szType == 'naive+https' or szType == 'naive+quic' then
if has_singbox then
result.type = 'sing-box'
result.protocol = "naive"
else
log(2, i18n.translatef("Skip the %s node because the %s core program is not installed.", "NaïveProxy", "Sing-Box"))
return nil
end
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)
local Info = content
if content:find("@", 1, true) then
local contents = split(content, "@")
local auth = contents[1] or ""
local idx = auth:find(":", 1, true)
if not idx then -- Fixed some links that encode the colon between username and password.
auth = UrlDecode(auth)
idx = auth:find(":", 1, true)
end
if idx then
result.username = UrlDecode(auth:sub(1, idx - 1))
result.password = UrlDecode(auth:sub(idx + 1))
end
Info = (contents[2] or ""):gsub("/%?", "?")
end
local query = split(Info, "%?")
local host_port = query[1]
local params = {}
for _, v in pairs(split(query[2], '&')) do
local s = v:find("=", 1, true)
if s and s > 1 then
params[v:sub(1, s - 1)] = UrlDecode(v:sub(s + 1))
end
end
if host_port:find(":") then
local sp = split(host_port, ":")
result.port = sp[#sp]
if api.is_ipv6addrport(host_port) then
result.address = api.get_ipv6_only(host_port)
else else
result.tls_allowInsecure = allowInsecure_default and "1" or "0" result.address = sp[1]
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(2, i18n.translatef("Skip the %s node, as %s type nodes require Sing-Box version 1.12 or higher.", result.remarks, szType))
return nil
end end
else
result.address = host_port
end
result.tls_serverName = params.sni
result.uot = params.uot
result.naive_insecure_concurrency = params["insecure-concurrency"] or "0"
if params.ech and params.ech ~= "" then
result.ech = "1"
result.ech_config = params.ech
end
if szType == "naive+quic" then
result.naive_quic = "1"
result.naive_congestion_control = params.congestion_control or "bbr"
end end
else else
log(2, i18n.translatef("%s type node subscriptions are not currently supported, skip this node.", szType)) log(2, i18n.translatef("%s type node subscriptions are not currently supported, skip this node.", szType))
@@ -48,7 +48,7 @@ eval_set_val() {
eval_unset_val() { eval_unset_val() {
for i in $@; do for i in $@; do
for j in $i; do for j in $i; do
eval unset j eval unset $j
done done
done done
} }