From 133898bee4c17d0649e33272523a9b52d784e511 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 2 Apr 2026 23:48:06 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A6=84=20Sync=202026-04-02=2023:48:06?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../luci-static/resources/view/fchomo/node.js | 1 + luci-app-fchomo/po/zh_Hans/fchomo.po | 2 +- luci-app-fchomo/po/zh_Hant/fchomo.po | 2 +- .../passwall/node_config/link_share_man.htm | 2 +- .../root/usr/share/passwall/subscribe.lua | 2 +- luci-app-passwall2/Makefile | 2 +- .../luasrc/controller/passwall2.lua | 12 +- .../model/cbi/passwall2/client/global.lua | 7 +- .../cbi/passwall2/client/node_config.lua | 11 +- .../client/node_subscribe_config.lua | 6 +- .../model/cbi/passwall2/client/other.lua | 13 +- .../cbi/passwall2/client/type/hysteria2.lua | 8 +- .../model/cbi/passwall2/client/type/ray.lua | 14 +- .../cbi/passwall2/client/type/sing-box.lua | 46 ++- .../model/cbi/passwall2/client/type/tuic.lua | 12 +- .../model/cbi/passwall2/server/type/ray.lua | 33 ++- .../cbi/passwall2/server/type/sing-box.lua | 37 ++- luci-app-passwall2/luasrc/passwall2/api.lua | 10 + .../luasrc/passwall2/util_hysteria2.lua | 16 +- .../luasrc/passwall2/util_sing-box.lua | 155 ++++++---- .../luasrc/passwall2/util_tuic.lua | 17 +- .../luasrc/passwall2/util_xray.lua | 275 +++++++++--------- .../view/passwall2/node_config/footer.htm | 13 +- .../view/passwall2/node_config/header.htm | 9 +- .../passwall2/node_config/link_share_man.htm | 144 ++++++--- .../passwall2/node_list/link_add_node.htm | 38 ++- .../view/passwall2/node_list/node_list.htm | 109 ++++--- luci-app-passwall2/po/fa/passwall2.po | 40 ++- .../po/ru/{passwall.po => passwall2.po} | 42 ++- luci-app-passwall2/po/zh-cn/passwall2.po | 38 ++- luci-app-passwall2/po/zh-tw/passwall2.po | 40 ++- .../root/usr/share/passwall2/iptables.sh | 69 +++-- .../root/usr/share/passwall2/nftables.sh | 51 ++-- .../root/usr/share/passwall2/subscribe.lua | 217 +++++++------- .../root/usr/share/passwall2/utils.sh | 2 +- 35 files changed, 958 insertions(+), 537 deletions(-) rename luci-app-passwall2/po/ru/{passwall.po => passwall2.po} (98%) diff --git a/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js b/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js index c25eda71..8554a0f6 100644 --- a/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js +++ b/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js @@ -1244,6 +1244,7 @@ return view.extend({ so.modalonly = true; 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-up', _('stream-up')); so.value('packet-up', _('packet-up')); diff --git a/luci-app-fchomo/po/zh_Hans/fchomo.po b/luci-app-fchomo/po/zh_Hans/fchomo.po index 7b75facc..359a8051 100644 --- a/luci-app-fchomo/po/zh_Hans/fchomo.po +++ b/luci-app-fchomo/po/zh_Hans/fchomo.po @@ -1953,7 +1953,7 @@ msgstr "混淆类型" #: htdocs/luci-static/resources/view/fchomo/node.js:408 #: htdocs/luci-static/resources/view/fchomo/node.js:409 msgid "Obfuscated as %s" -msgstr "混淆为" +msgstr "混淆为%s" #: htdocs/luci-static/resources/view/fchomo/global.js:880 msgid "One or more numbers in the range 0-63 separated by commas" diff --git a/luci-app-fchomo/po/zh_Hant/fchomo.po b/luci-app-fchomo/po/zh_Hant/fchomo.po index 9bfcb2f5..7426cc83 100644 --- a/luci-app-fchomo/po/zh_Hant/fchomo.po +++ b/luci-app-fchomo/po/zh_Hant/fchomo.po @@ -1953,7 +1953,7 @@ msgstr "混淆類型" #: htdocs/luci-static/resources/view/fchomo/node.js:408 #: htdocs/luci-static/resources/view/fchomo/node.js:409 msgid "Obfuscated as %s" -msgstr "混淆為" +msgstr "混淆為%s" #: htdocs/luci-static/resources/view/fchomo/global.js:880 msgid "One or more numbers in the range 0-63 separated by commas" diff --git a/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm b/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm index 133b72f9..17c35949 100644 --- a/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm +++ b/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm @@ -1714,7 +1714,7 @@ local current_node = map:get(section) 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"; } if (queryParam.security) { diff --git a/luci-app-passwall/root/usr/share/passwall/subscribe.lua b/luci-app-passwall/root/usr/share/passwall/subscribe.lua index 09cd5564..6d8e928c 100755 --- a/luci-app-passwall/root/usr/share/passwall/subscribe.lua +++ b/luci-app-passwall/root/usr/share/passwall/subscribe.lua @@ -1490,7 +1490,7 @@ local function processData(szType, content, add_mode, group) result.address = host_port end 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" end if params.security == "tls" or params.security == "reality" then diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 2e99ee97..746779fb 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 -PKG_VERSION:=26.3.5 +PKG_VERSION:=26.4.2 PKG_RELEASE:=1 PKG_PO_VERSION:=$(PKG_VERSION) diff --git a/luci-app-passwall2/luasrc/controller/passwall2.lua b/luci-app-passwall2/luasrc/controller/passwall2.lua index 35f47a1d..66307151 100644 --- a/luci-app-passwall2/luasrc/controller/passwall2.lua +++ b/luci-app-passwall2/luasrc/controller/passwall2.lua @@ -459,17 +459,11 @@ function copy_node() local uuid = api.gen_short_uuid() uci:section(appname, "nodes", uuid) for k, v in pairs(uci:get_all(appname, section)) do - local filter = k:find("%.") - if filter and filter == 1 then - else - xpcall(function() - uci:set(appname, uuid, k, v) - end, - function(e) - end) + if not k:match("^%.") and k ~= "group" then + if k == "remarks" then v = (v or "") .. "(1)" end + uci:set(appname, uuid, k, v) end end - uci:delete(appname, uuid, "group") uci:set(appname, uuid, "add_mode", 1) api.uci_save(uci, appname) http.redirect(api.url("node_config", uuid)) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua index 085575ef..32a585fc 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua @@ -95,7 +95,7 @@ current_node = current_node_id and m.uci:get_all(appname, current_node_id) or {} -- Shunt Start 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 local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua") setfenv(shunt_lua, getfenv(1))(m, s, { @@ -112,7 +112,7 @@ if (has_singbox or has_xray) and #nodes_table > 0 then }) end else - local tips = s:taboption("Main", DummyValue, "tips", " ") + local tips = s:taboption("Main", DummyValue, "tips", " ") tips.rawhtml = true tips.cfgvalue = function(t, n) return string.format('%s', 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_socks = s2.fields["node"] 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.group[#o_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default") o_socks:value(v.id, v["remark"]) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua index 6b9db8ab..28e3aa7d 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua @@ -9,6 +9,9 @@ if not arg[1] or not m:get(arg[1]) then luci.http.redirect(api.url("node_list")) end +fs = require "nixio.fs" +formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "." + local header = Template(appname .. "/node_config/header") header.api = api header.section = arg[1] @@ -61,12 +64,18 @@ o.write = function(self, section, value) m:set(section, self.option, value) end -local fs = require "nixio.fs" local types_dir = "/usr/lib/lua/luci/model/cbi/passwall2/client/type/" s.val = {} s.val["type"] = m.uci:get(appname, arg[1], "type") 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")) local type_table = {} diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua index 1841d7a6..31690099 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua @@ -127,9 +127,11 @@ o.validate = function(self, value) return value:gsub("%s+", ""):gsub("%z", "") end -o = s:option(Flag, "allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.")) -o.default = "1" +o = s:option(Flag, "allowInsecure", translate("allowInsecure")) +o.default = "0" o.rmempty = false +o.description = translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.") .. "
" .. + translate("Used when the node link does not include this parameter.") o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode")) o.default = "5" diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua index edd466fe..99adab06 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua @@ -168,14 +168,17 @@ if has_xray then o:depends("fragment", true) 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: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:depends("fragment", true) 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:depends("fragment", true) @@ -225,22 +228,18 @@ if has_xray then o = s_xray_noise:option(ListValue, "type", translate("Type")) o:value("rand", "rand") + o:value("array", "array") o:value("str", "str") o:value("hex", "hex") 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.rmempty = false o = s_xray_noise:option(Value, "delay", translate("Delay (ms)")) o.datatype = "or(uinteger,portrange)" 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 if has_singbox then diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/hysteria2.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/hysteria2.lua index a77c154f..7a0e38f2 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/hysteria2.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/hysteria2.lua @@ -20,9 +20,6 @@ local function _n(name) return option_prefix .. name 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("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.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 = s:option(Flag, _n("disable_mtu_discovery"), translate("Disable MTU detection")) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua index af2cd1d3..804b270e 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua @@ -25,7 +25,6 @@ local function _n(name) return option_prefix .. name end -local formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "." local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol")) 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: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 = 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: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("protocol")] = "hysteria2" }) @@ -404,7 +406,7 @@ o.validate = function(self, value) 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.default = "none" +o.default = "full" o:value("none") o:value("half") o:value("full") @@ -687,10 +689,12 @@ o:depends({ [_n("protocol")] = "shadowsocks" }) o:depends({ [_n("protocol")] = "wireguard" }) 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.rows = 10 o.wrap = "off" +o.description = translate("An FinalMaskObject in JSON format, used for sharing.") .. "
" .. + translate("Custom finalmask overrides mkcp, hysteria2, fragment, noise, and related settings.") o.custom_cfgvalue = function(self, section, value) local raw = m:get(section, "finalmask") if raw then diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua index 1a8933c5..4524e81a 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua @@ -25,7 +25,6 @@ local function _n(name) return option_prefix .. name end -local formvalue_key = "cbid." .. appname .. "." .. arg[1] .. "." local formvalue_proto = luci.http.formvalue(formvalue_key .. _n("protocol")) if formvalue_proto then s.val["protocol"] = formvalue_proto end @@ -68,6 +67,9 @@ if singbox_tags:find("with_quic") then end o:value("anytls", "AnyTLS") 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("_shunt", translate("Shunt")) 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")] = "socks" }) o:depends({ [_n("protocol")] = "ssh" }) +o:depends({ [_n("protocol")] = "naive" }) o = s:option(Value, _n("password"), translate("Password")) o.password = true @@ -238,6 +241,7 @@ o:depends({ [_n("protocol")] = "trojan" }) o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "anytls" }) o:depends({ [_n("protocol")] = "ssh" }) +o:depends({ [_n("protocol")] = "naive" }) o = s:option(ListValue, _n("security"), translate("Encrypt Method")) 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:depends({ [_n("protocol")] = "socks" }) o:depends({ [_n("protocol")] = "shadowsocks" }) +o:depends({ [_n("protocol")] = "naive" }) o = s:option(Value, _n("alter_id"), "Alter ID") 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:depends({ [_n("protocol")] = "hysteria" }) - - o = s:option(Value, _n("hysteria_alpn"), translate("QUIC TLS ALPN")) - o:depends({ [_n("protocol")] = "hysteria" }) end 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("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" }) end @@ -437,6 +441,26 @@ o = s:option(Value, _n("ssh_client_version"), translate("Client Version"), trans o:depends({ [_n("protocol")] = "ssh" }) -- [[ 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.default = 0 o:depends({ [_n("protocol")] = "vmess" }) @@ -446,7 +470,7 @@ o:depends({ [_n("protocol")] = "trojan" }) o:depends({ [_n("protocol")] = "shadowsocks" }) 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:value("default", translate("Default")) o:value("h3") @@ -456,6 +480,7 @@ o:value("http/1.1") o:value("h2,http/1.1") o:value("h3,h2,http/1.1") 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.default = "0" @@ -463,14 +488,13 @@ o:depends({ [_n("tls")] = true }) o:depends({ [_n("protocol")] = "hysteria"}) o:depends({ [_n("protocol")] = "tuic" }) 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("protocol")] = "hysteria"}) o:depends({ [_n("protocol")] = "tuic" }) 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.default = "0" @@ -478,7 +502,6 @@ o:depends({ [_n("tls")] = true }) o:depends({ [_n("protocol")] = "hysteria"}) o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "hysteria2" }) -o:depends({ [_n("protocol")] = "shadowsocks" }) o = s:option(Flag, _n("ech"), translate("ECH")) 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")] = "hysteria" }) o:depends({ [_n("protocol")] = "hysteria2" }) +o:depends({ [_n("protocol")] = "naive" }) o = s:option(TextValue, _n("ech_config"), translate("ECH Config")) o.default = "" @@ -501,6 +525,9 @@ o.validate = function(self, value) return value 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 o = s:option(Flag, _n("utls"), translate("uTLS")) o.default = "0" @@ -666,6 +693,7 @@ o:depends({ [_n("tcp_guise")] = "http" }) o:depends({ [_n("transport")] = "http" }) o:depends({ [_n("transport")] = "ws" }) o:depends({ [_n("transport")] = "httpupgrade" }) +o:depends({ [_n("protocol")] = "naive" }) -- [[ Mux ]]-- o = s:option(Flag, _n("mux"), translate("Mux")) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/tuic.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/tuic.lua index 5d6974d0..fb7f91ac 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/tuic.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/tuic.lua @@ -128,8 +128,18 @@ o.default = 0 o.rmempty = true 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.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 api.luci_types(arg[1], m, s, type_name, option_prefix) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua index 29d39d00..17a441e3 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua @@ -38,6 +38,7 @@ o:value("http", "HTTP") o:value("socks", "Socks") o:value("shadowsocks", "Shadowsocks") o:value("trojan", "Trojan") +o:value("hysteria2", translate("Hysteria2")) o:value("dokodemo-door", "dokodemo-door") o:depends({ [_n("custom")] = false }) @@ -123,6 +124,30 @@ o:value("", translate("Disable")) o:value("xtls-rprx-vision") 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.default = 0 o.validate = function(self, value, t) @@ -191,6 +216,7 @@ 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")] = "hysteria2"}) o = s:option(Flag, _n("use_mldsa65Seed"), translate("ML-DSA-65")) 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" 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("protocol")] = "hysteria2"}) o.validate = function(self, value, t) if value and 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" 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("protocol")] = "hysteria2"}) o.validate = function(self, value, t) if value and value ~= "" then if not nixio.fs.access(value) then @@ -402,7 +430,10 @@ o:depends({ [_n("custom")] = false }) --[[Fast Open]] o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open")) 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 ]]-- o = s:option(Flag, _n("fallback"), translate("Fallback")) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua index 7dce431d..3455bc8b 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua @@ -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:depends({ [_n("protocol")] = "hysteria" }) - - o = s:option(Value, _n("hysteria_alpn"), translate("QUIC TLS ALPN")) - o:depends({ [_n("protocol")] = "hysteria" }) end if singbox_tags:find("with_quic") then @@ -145,12 +142,26 @@ if singbox_tags:find("with_quic") then o.default = "3" 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" }) end 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: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: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 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 }) 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 ]] -- o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") diff --git a/luci-app-passwall2/luasrc/passwall2/api.lua b/luci-app-passwall2/luasrc/passwall2/api.lua index 09e01329..169892c9 100644 --- a/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/luci-app-passwall2/luasrc/passwall2/api.lua @@ -1551,3 +1551,13 @@ function get_core(field, candidates) end return nil 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 diff --git a/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua b/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua index 8d0a6473..51d310d6 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_hysteria2.lua @@ -87,14 +87,14 @@ function gen_config(var) quic = { 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, - 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 tostring(timeout) .. "s" - end - return nil - end)(), + maxIdleTimeout = (function(t) + t = tonumber(tostring(t or "30"):match("^%d+")) + return (t and t >= 4 and t <= 120) and t .. "s" or "30s" + 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 .. "s" or nil + end)(node.hysteria2_keep_alive_period), disablePathMTUDiscovery = (node.hysteria2_disable_mtu_discovery) and true or false, }, bandwidth = (node.hysteria2_up_mbps or node.hysteria2_down_mbps) and { diff --git a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua index f032f36b..2e930da4 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua @@ -7,6 +7,7 @@ local appname = api.appname local fs = api.fs local CACHE_PATH = api.CACHE_PATH local split = api.split +local ech_domain = {} 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") @@ -159,6 +160,9 @@ function gen_outbound(flag, node, tag, proxy_table) } 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 local alpn = nil if node.alpn and node.alpn ~= "default" then @@ -176,10 +180,35 @@ function gen_outbound(flag, node, tag, proxy_table) --max_version = "1.3", 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, + ech = (node.ech == "1") and (function() + local function get_ech_domain(s) --兼容xray "域名+DNS" 格式ech + local domain, dns = s:match("^([^+]+)%+(.+)$") + 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 { enabled = true, 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 = tonumber(node.hysteria_recv_window), disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false, - 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 - } + tls = tls } end @@ -452,22 +468,15 @@ function gen_outbound(flag, node, tag, proxy_table) udp_over_stream = false, zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false, heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s", - 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 - } + tls = tls } + 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 if node.protocol == "hysteria2" then @@ -494,17 +503,7 @@ function gen_outbound(flag, node, tag, proxy_table) password = node.hysteria2_obfs_password } or nil, password = node.hysteria2_auth_password or nil, - 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 - } + tls = tls } end @@ -530,6 +529,23 @@ function gen_outbound(flag, node, tag, proxy_table) } 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 for key, value in pairs(protocol_table) do result[key] = value @@ -548,6 +564,14 @@ function gen_config_server(node) enabled = true, certificate_path = node.tls_certificateFile, 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 @@ -570,7 +594,7 @@ function gen_config_server(node) if node.tls == "1" and node.ech == "1" then tls.ech = { 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 @@ -751,9 +775,6 @@ function gen_config_server(node) end if node.protocol == "hysteria" then - tls.alpn = (node.hysteria_alpn and node.hysteria_alpn ~= "") and { - node.hysteria_alpn - } or nil protocol_table = { up = node.hysteria_up_mbps .. " Mbps", down = node.hysteria_down_mbps .. " Mbps", @@ -776,9 +797,14 @@ function gen_config_server(node) end if node.protocol == "tuic" then - tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "") and { - node.tuic_alpn - } or nil + tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "default") and (function() + local alpn = {} + 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 = { users = { { @@ -789,7 +815,7 @@ function gen_config_server(node) }, congestion_control = node.tuic_congestion_control or "cubic", 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 } end @@ -1913,15 +1939,28 @@ function gen_config(var) if not dns then dns = { - servers = { - { - type = "local", - tag = "direct" - } - } + servers = {{ + type = "local", + tag = "direct" + }} } 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 route.final = nil table.insert(route.rules, { diff --git a/luci-app-passwall2/luasrc/passwall2/util_tuic.lua b/luci-app-passwall2/luasrc/passwall2/util_tuic.lua index 5defed23..a1ccce45 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_tuic.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_tuic.lua @@ -25,11 +25,18 @@ function gen_config(var) -- certificates = node.tuic_certificate and { node.tuic_certpath } or nil, udp_relay_mode = node.tuic_udp_relay_mode, congestion_control = node.tuic_congestion_control, - heartbeat = node.tuic_heartbeat .. "s", - timeout = node.tuic_timeout .. "s", - gc_interval = node.tuic_gc_interval .. "s", - gc_lifetime = node.tuic_gc_lifetime .. "s", - alpn = node.tuic_tls_alpn, + heartbeat = (tonumber(node.tuic_heartbeat) or 3) .. "s", + timeout = (tonumber(node.tuic_timeout) or 8) .. "s", + gc_interval = (tonumber(node.tuic_gc_interval) or 3) .. "s", + gc_lifetime = (tonumber(node.tuic_gc_lifetime) or 15) .. "s", + 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"), zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1"), send_window = tonumber(node.tuic_send_window), diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua index 206ba563..ce9d713a 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -9,21 +9,6 @@ local CACHE_PATH = api.CACHE_PATH 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 path = string.format("/usr/share/%s/domains_excluded", appname) local content = fs.readfile(path) @@ -146,8 +131,7 @@ function gen_outbound(flag, node, tag, proxy_table) sockopt = { mark = 255, tcpFastOpen = (node.tcp_fast_open == "1") and true or nil, - tcpMptcp = (node.tcpMptcp == "1") and true or nil, - dialerProxy = (fragment or noise) and "dialerproxy" or nil + tcpMptcp = (node.tcpMptcp == "1") and true or nil }, network = node.transport, security = node.stream_security, @@ -169,7 +153,7 @@ function gen_outbound(flag, node, tag, proxy_table) return node.tls_CertByName end)(), 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, realitySettings = (node.stream_security == "reality") and { serverName = node.tls_serverName, @@ -237,58 +221,31 @@ function gen_outbound(flag, node, tag, proxy_table) path = node.xhttp_path or "/", host = node.xhttp_host, extra = (function() - local extra_tbl = {} - -- Parse xhttp_extra and perform simple fault tolerance. + local extra = {} if node.xhttp_extra then - local success, parsed = pcall(jsonc.parse, api.base64Decode(node.xhttp_extra)) - if success and parsed then - extra_tbl = 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 + local ok, parsed = pcall(jsonc.parse, api.base64Decode(node.xhttp_extra)) + if ok and type(parsed) == "table" then + extra = parsed.extra or parsed end end -- Handling User-Agent if node.user_agent and node.user_agent ~= "" then - extra_tbl.headers = extra_tbl.headers or {} - if not extra_tbl.headers["User-Agent"] and not extra_tbl.headers["user-agent"] then - extra_tbl.headers["User-Agent"] = node.user_agent + extra.headers = extra.headers or {} + if not extra.headers["User-Agent"] and not extra.headers["user-agent"] then + extra.headers["User-Agent"] = node.user_agent end end - -- Clean up empty headers - 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 + return api.cleanEmptyTables(extra) end)() } or nil, hysteriaSettings = (node.transport == "hysteria") and { version = 2, - 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 + auth = node.hysteria2_auth_password } or nil, finalmask = (function() - local finalmask - if node.transport == "mkcp" then + local finalmask = {} + local TP = node.transport + if TP == "mkcp" then local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat", dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"} local udp = {} @@ -304,36 +261,55 @@ function gen_outbound(flag, node, tag, proxy_table) c.settings = { password = node.mkcp_seed } end udp[#udp+1] = c - finalmask = { udp = udp } - elseif node.transport == "hysteria" and node.hysteria2_obfs_type and node.hysteria2_obfs_type ~= "" then - finalmask = { - udp = {{ + finalmask.udp = udp + elseif TP == "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 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 + 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 local ok, fm = pcall(jsonc.parse, api.base64Decode(node.finalmask)) if ok and type(fm) == "table" then - if not finalmask or not next(finalmask) then - 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 + finalmask = fm end end - return (finalmask and next(finalmask)) and finalmask or nil + return api.cleanEmptyTables(finalmask) end)() } or nil, settings = { @@ -475,6 +451,13 @@ function gen_config_server(node) clients = clients } 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 settings = { network = node.d_protocol, @@ -552,6 +535,12 @@ function gen_config_server(node) end end + if node.protocol == "hysteria2" then + node.protocol = "hysteria" + node.transport = "hysteria" + node.tls = "1" + end + local config = { log = { loglevel = ("1" == node.log) and node.loglevel or "none" @@ -618,8 +607,11 @@ function gen_config_server(node) maxUploadSize = node.xhttp_maxuploadsize, maxConcurrentUploads = node.xhttp_maxconcurrentuploads } or nil, + hysteriaSettings = (node.transport == "hysteria") and { + version = 2 + } or nil, finalmask = (function() - local finalmask + local finalmask = {} if node.transport == "mkcp" then local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat", dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"} @@ -636,27 +628,32 @@ function gen_config_server(node) c.settings = { password = node.mkcp_seed } end 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 if node.finalmask and node.finalmask ~= "" then local ok, fm = pcall(jsonc.parse, api.base64Decode(node.finalmask)) if ok and type(fm) == "table" then - if not finalmask or not next(finalmask) then - 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 + finalmask = fm end end - return (finalmask and next(finalmask)) and finalmask or nil + return api.cleanEmptyTables(finalmask) end)(), sockopt = { 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 {} + 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 balancers = {} local rules = {} @@ -909,7 +938,7 @@ function gen_config(var) end end 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 valid_nodes[#valid_nodes + 1] = outboundTag end @@ -934,7 +963,7 @@ function gen_config(var) local fallback_node = get_node_by_id(fallback_node_id) if fallback_node 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 fallback_node_tag = outboundTag end @@ -964,27 +993,29 @@ function gen_config(var) strategy = strategy }) 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 not burstObservatory then - burstObservatory = { - subjectSelector = { "blc-" }, - pingConfig = { - destination = _node.useCustomProbeUrl and _node.probeUrl or nil, - interval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m", - sampling = 3, - timeout = "5s" - } + burstObservatory = burstObservatory or { + subjectSelector = { "blc-" }, + pingConfig = { + destination = _node.useCustomProbeUrl and _node.probeUrl or nil, + interval = t, + sampling = 3, + timeout = "5s" } - end + } else - if not observatory then - observatory = { - subjectSelector = { "blc-" }, - probeUrl = _node.useCustomProbeUrl and _node.probeUrl or nil, - probeInterval = (api.format_go_time(_node.probeInterval) ~= "0s") and api.format_go_time(_node.probeInterval) or "1m", - enableConcurrency = true - } - end + observatory = observatory or { + subjectSelector = { "blc-" }, + probeUrl = _node.useCustomProbeUrl and _node.probeUrl or nil, + probeInterval = t, + enableConcurrency = true + } end end 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 = { protocol = "freedom", tag = "direct", diff --git a/luci-app-passwall2/luasrc/view/passwall2/node_config/footer.htm b/luci-app-passwall2/luasrc/view/passwall2/node_config/footer.htm index ef8cf345..2d9e3c77 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/node_config/footer.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/node_config/footer.htm @@ -2,8 +2,6 @@ local api = self.api -%>