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
-%>