diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
index 1872017f1..fb8846783 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua
@@ -337,6 +337,7 @@ o.default = 0
o:depends({ [option_name("tls")] = true, [option_name("transport")] = "tcp" })
o:depends({ [option_name("tls")] = true, [option_name("transport")] = "h2" })
o:depends({ [option_name("tls")] = true, [option_name("transport")] = "grpc" })
+o:depends({ [option_name("tls")] = true, [option_name("transport")] = "splithttp" })
o = s:option(ListValue, option_name("alpn"), translate("alpn"))
o.default = "default"
@@ -566,7 +567,7 @@ o = s:option(Value, option_name("httpupgrade_path"), translate("HttpUpgrade Path
o.placeholder = "/"
o:depends({ [option_name("transport")] = "httpupgrade" })
--- [[ SplitHTTP部分 ]]--
+-- [[ XHTTP(SplitHTTP)部分 ]]--
o = s:option(Value, option_name("splithttp_host"), translate("SplitHTTP Host"))
o:depends({ [option_name("transport")] = "splithttp" })
@@ -574,20 +575,101 @@ o = s:option(Value, option_name("splithttp_path"), translate("SplitHTTP Path"))
o.placeholder = "/"
o:depends({ [option_name("transport")] = "splithttp" })
-o = s:option(Flag, option_name("splithttp_xmux"), "XMUX", translate("Enable SplitHTTP XMUX. It's not recommended to enable Mux.Cool at the same time."))
+-- XHTTP XMUX
+o = s:option(Flag, option_name("xhttp_xmux"), "XMUX", translate("Enable XHTTP XMUX. It's not recommended to enable Mux.Cool at the same time."))
o:depends({ [option_name("transport")] = "splithttp" })
o = s:option(Value, option_name("maxConcurrency"), translate("XMUX Max Concurrency"))
-o:depends({ [option_name("splithttp_xmux")] = true })
+o:depends({ [option_name("xhttp_xmux")] = true })
o = s:option(Value, option_name("maxConnections"), translate("XMUX Max Connections"))
-o:depends({ [option_name("splithttp_xmux")] = true })
+o:depends({ [option_name("xhttp_xmux")] = true })
o = s:option(Value, option_name("cMaxReuseTimes"), translate("XMUX Connection Max Reuse Times"))
-o:depends({ [option_name("splithttp_xmux")] = true })
+o:depends({ [option_name("xhttp_xmux")] = true })
o = s:option(Value, option_name("cMaxLifetimeMs"), translate("XMUX Connection Max Lifetime (ms)"))
-o:depends({ [option_name("splithttp_xmux")] = true })
+o:depends({ [option_name("xhttp_xmux")] = true })
+
+-- XHTTP 下行
+o = s:option(Flag, option_name("xhttp_download"), string.format('%s', translate("XHTTP download splitting")))
+o:depends({ [option_name("transport")] = "splithttp" })
+
+o = s:option(Value, option_name("xhttp_download_address"), string.format('%s', translate("Address")))
+o:depends({ [option_name("xhttp_download")] = true })
+
+o = s:option(Value, option_name("xhttp_download_port"), string.format('%s', translate("Port")))
+o:depends({ [option_name("xhttp_download")] = true })
+
+o = s:option(Value, option_name("xhttp_download_path"), string.format('%s', "XHTTP Path"), translate("Must be the same as upload path."))
+o.placeholder = "/"
+o:depends({ [option_name("xhttp_download")] = true })
+
+o = s:option(Flag, option_name("xhttp_download_tls"), string.format('%s', "TLS"))
+o:depends({ [option_name("xhttp_download")] = true })
+o.default = 0
+
+o = s:option(Flag, option_name("xhttp_download_reality"), string.format('%s', "REALITY"))
+o.default = 0
+o:depends({ [option_name("xhttp_download_tls")] = true })
+
+o = s:option(ListValue, option_name("xhttp_download_alpn"), string.format('%s', "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({ [option_name("xhttp_download_tls")] = true, [option_name("xhttp_download_reality")] = false })
+
+o = s:option(Value, option_name("xhttp_download_tls_serverName"), string.format('%s', translate("Domain")))
+o:depends({ [option_name("xhttp_download_tls")] = true })
+
+o = s:option(Value, option_name("xhttp_download_reality_publicKey"), string.format('%s', translate("Public Key")))
+o:depends({ [option_name("xhttp_download_tls")] = true, [option_name("xhttp_download_reality")] = true })
+
+o = s:option(Value, option_name("xhttp_download_reality_shortId"), string.format('%s', translate("Short Id")))
+o:depends({ [option_name("xhttp_download_tls")] = true, [option_name("xhttp_download_reality")] = true })
+
+o = s:option(Value, option_name("xhttp_download_reality_spiderX"), string.format('%s', "Spider X"))
+o.placeholder = "/"
+o:depends({ [option_name("xhttp_download_tls")] = true, [option_name("xhttp_download_reality")] = true })
+
+o = s:option(Flag, option_name("xhttp_download_utls"), string.format('%s', "uTLS"))
+o.default = "0"
+o:depends({ [option_name("xhttp_download_tls")] = true, [option_name("xhttp_download_reality")] = false })
+
+o = s:option(ListValue, option_name("xhttp_download_fingerprint"), string.format('%s', translate("Finger Print")))
+o:value("chrome")
+o:value("firefox")
+o:value("edge")
+o:value("safari")
+o:value("360")
+o:value("qq")
+o:value("ios")
+o:value("android")
+o:value("random")
+o:value("randomized")
+o.default = "chrome"
+o:depends({ [option_name("xhttp_download_tls")] = true, [option_name("xhttp_download_utls")] = true })
+o:depends({ [option_name("xhttp_download_tls")] = true, [option_name("xhttp_download_reality")] = true })
+
+o = s:option(Flag, option_name("xhttp_download_xmux"), string.format('%s', "XMUX"), translate("Enable XHTTP XMUX. It's not recommended to enable Mux.Cool at the same time."))
+o:depends({ [option_name("xhttp_download")] = true })
+
+o = s:option(Value, option_name("download_maxConcurrency"), string.format('%s', translate("XMUX Max Concurrency")))
+o:depends({ [option_name("xhttp_download_xmux")] = true })
+
+o = s:option(Value, option_name("download_maxConnections"), string.format('%s', translate("XMUX Max Connections")))
+o:depends({ [option_name("xhttp_download_xmux")] = true })
+
+o = s:option(Value, option_name("download_cMaxReuseTimes"), string.format('%s', translate("XMUX Connection Max Reuse Times")))
+o:depends({ [option_name("xhttp_download_xmux")] = true })
+
+o = s:option(Value, option_name("download_cMaxLifetimeMs"), string.format('%s', translate("XMUX Connection Max Lifetime (ms)")))
+o:depends({ [option_name("xhttp_download_xmux")] = true })
-- [[ Mux.Cool ]]--
o = s:option(Flag, option_name("mux"), "Mux", translate("Enable Mux.Cool"))
diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua
index b07657589..dbcea4e16 100644
--- a/luci-app-passwall/luasrc/passwall/util_xray.lua
+++ b/luci-app-passwall/luasrc/passwall/util_xray.lua
@@ -118,6 +118,15 @@ function gen_outbound(flag, node, tag, proxy_table)
end
end
+ if node.type == "Xray" and node.transport == "splithttp" then
+ if node.xhttp_download_tls and node.xhttp_download_tls == "1" then
+ node.xhttp_download_stream_security = "tls"
+ if node.xhttp_download_reality and node.xhttp_download_reality == "1" then
+ node.xhttp_download_stream_security = "reality"
+ end
+ end
+ end
+
if node.protocol == "wireguard" and node.wireguard_reserved then
local bytes = {}
if not node.wireguard_reserved:match("[^%d,]+") then
@@ -223,7 +232,32 @@ function gen_outbound(flag, node, tag, proxy_table)
} or nil,
splithttpSettings = (node.transport == "splithttp") and {
path = node.splithttp_path or "/",
- host = node.splithttp_host
+ host = node.splithttp_host,
+ downloadSettings = (node.xhttp_download == "1") and {
+ address = node.xhttp_download_address,
+ port = tonumber(node.xhttp_download_port),
+ network = "xhttp",
+ xhttpSettings = {
+ path = node.xhttp_download_path,
+ },
+ security = node.xhttp_download_stream_security,
+ tlsSettings = (node.xhttp_download_stream_security == "tls") and {
+ serverName = node.xhttp_download_tls_serverName,
+ allowInsecure = false,
+ fingerprint = (node.xhttp_download_utls == "1" and
+ node.xhttp_download_fingerprint and
+ node.xhttp_download_fingerprint ~= "") and node.xhttp_download_fingerprint or nil
+ } or nil,
+ realitySettings = (node.xhttp_download_stream_security == "reality") and {
+ serverName = node.xhttp_download_tls_serverName,
+ publicKey = node.xhttp_download_reality_publicKey,
+ shortId = node.xhttp_download_reality_shortId or "",
+ spiderX = node.xhttp_download_reality_spiderX or "/",
+ fingerprint = (
+ node.xhttp_download_fingerprint and
+ node.xhttp_download_fingerprint ~= "") and node.xhttp_download_fingerprint or nil
+ } or nil,
+ } or nil
} or nil,
} or nil,
settings = {
@@ -290,7 +324,7 @@ function gen_outbound(flag, node, tag, proxy_table)
end
local xmux = {}
- if node.splithttp_xmux then
+ if (node.xhttp_xmux == "1") then
xmux.maxConcurrency = node.maxConcurrency and (string.find(node.maxConcurrency, "-") and node.maxConcurrency or tonumber(node.maxConcurrency)) or 0
xmux.maxConnections = node.maxConnections and (string.find(node.maxConnections, "-") and node.maxConnections or tonumber(node.maxConnections)) or 0
xmux.cMaxReuseTimes = node.cMaxReuseTimes and (string.find(node.cMaxReuseTimes, "-") and node.cMaxReuseTimes or tonumber(node.cMaxReuseTimes)) or 0
@@ -300,6 +334,17 @@ function gen_outbound(flag, node, tag, proxy_table)
end
end
+ local xmux_download = {}
+ if (node.xhttp_download_xmux == "1") then
+ xmux_download.maxConcurrency = node.download_maxConcurrency and (string.find(node.download_maxConcurrency, "-") and node.download_maxConcurrency or tonumber(node.download_maxConcurrency)) or 0
+ xmux_download.maxConnections = node.download_maxConnections and (string.find(node.download_maxConnections, "-") and node.download_maxConnections or tonumber(node.download_maxConnections)) or 0
+ xmux_download.cMaxReuseTimes = node.download_cMaxReuseTimes and (string.find(node.download_cMaxReuseTimes, "-") and node.download_cMaxReuseTimes or tonumber(node.download_cMaxReuseTimes)) or 0
+ xmux_download.cMaxLifetimeMs = node.download_cMaxLifetimeMs and (string.find(node.download_cMaxLifetimeMs, "-") and node.download_cMaxLifetimeMs or tonumber(node.download_cMaxLifetimeMs)) or 0
+ if result.streamSettings.splithttpSettings.downloadSettings then
+ result.streamSettings.splithttpSettings.downloadSettings.xmux = xmux_download
+ end
+ end
+
end
return result
end
diff --git a/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm b/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm
index f5b164aa2..ecf4de1bd 100644
--- a/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm
+++ b/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm
@@ -371,6 +371,10 @@ local api = require "luci.passwall.api"
v_transport = "splithttp";
params += opt.query("host", dom_prefix + "splithttp_host");
params += opt.query("path", dom_prefix + "splithttp_path");
+ } else if (v_transport === "xhttp") {
+ v_transport = "xhttp";
+ params += opt.query("host", dom_prefix + "xhttp_host");
+ params += opt.query("path", dom_prefix + "xhttp_path");
} else if (v_transport === "httpupgrade") {
v_transport = "httpupgrade";
params += opt.query("host", dom_prefix + "httpupgrade_host");
@@ -1153,6 +1157,9 @@ local api = require "luci.passwall.api"
} else if (queryParam.type === "splithttp") {
opt.set(dom_prefix + 'splithttp_host', queryParam.host || "");
opt.set(dom_prefix + 'splithttp_path', queryParam.path || "");
+ } else if (queryParam.type === "xhttp") {
+ opt.set(dom_prefix + 'xhttp_host', queryParam.host || "");
+ opt.set(dom_prefix + 'xhttp_path', queryParam.path || "");
} else if (queryParam.type === "httpupgrade") {
opt.set(dom_prefix + 'httpupgrade_host', queryParam.host || "");
opt.set(dom_prefix + 'httpupgrade_path', queryParam.path || "");
diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po
index 34f991d98..97e4d5b15 100644
--- a/luci-app-passwall/po/zh-cn/passwall.po
+++ b/luci-app-passwall/po/zh-cn/passwall.po
@@ -1519,8 +1519,14 @@ msgstr "客户端文件不适合当前设备。"
msgid "Can't move new file to path: %s"
msgstr "无法移动新文件到:%s"
-msgid "Enable SplitHTTP XMUX. It's not recommended to enable Mux.Cool at the same time."
-msgstr "启用 SplitHTTP XMUX。不建议与 Mux.Cool 同时启用。"
+msgid "XHTTP download splitting"
+msgstr "XHTTP 下行分离"
+
+msgid "Must be the same as upload path."
+msgstr "必须与上行 path 相同。"
+
+msgid "Enable XHTTP XMUX. It's not recommended to enable Mux.Cool at the same time."
+msgstr "启用 XHTTP XMUX。不建议与 Mux.Cool 同时启用。"
msgid "XMUX Max Concurrency"
msgstr "XMUX 连接最大复用流数"