diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile
index cade7cd17..579aacaf5 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:=24.11.17
-PKG_RELEASE:=1
+PKG_RELEASE:=2
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
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 9765793d6..e41f576f3 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
@@ -147,6 +147,7 @@ if has_xray then
o = s_xray:option(ListValue, "fragment_packets", translate("Fragment Packets"), translate(" \"1-3\" is for segmentation at TCP layer, applying to the beginning 1 to 3 data writes by the client. \"tlshello\" is for TLS client hello packet fragmentation."))
o.default = "tlshello"
o:value("tlshello", "tlshello")
+ o:value("1-1", "1-1")
o:value("1-2", "1-2")
o:value("1-3", "1-3")
o:value("1-5", "1-5")
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 32f72bde4..63ab75fce 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
@@ -334,9 +334,10 @@ o:depends({ [option_name("protocol")] = "shadowsocks" })
o = s:option(Flag, option_name("reality"), translate("REALITY"))
o.default = 0
-o:depends({ [option_name("tls")] = true, [option_name("transport")] = "tcp" })
+o:depends({ [option_name("tls")] = true, [option_name("transport")] = "raw" })
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")] = "xhttp" })
o = s:option(ListValue, option_name("alpn"), translate("alpn"))
o.default = "default"
@@ -392,7 +393,7 @@ o:depends({ [option_name("tls")] = true, [option_name("utls")] = true })
o:depends({ [option_name("tls")] = true, [option_name("reality")] = true })
o = s:option(ListValue, option_name("transport"), translate("Transport"))
-o:value("tcp", "TCP")
+o:value("raw", "RAW")
o:value("mkcp", "mKCP")
o:value("ws", "WebSocket")
o:value("h2", "HTTP/2")
@@ -400,7 +401,7 @@ o:value("ds", "DomainSocket")
o:value("quic", "QUIC")
o:value("grpc", "gRPC")
o:value("httpupgrade", "HttpUpgrade")
-o:value("splithttp", "SplitHTTP")
+o:value("xhttp", "XHTTP")
o:depends({ [option_name("protocol")] = "vmess" })
o:depends({ [option_name("protocol")] = "vless" })
o:depends({ [option_name("protocol")] = "socks" })
@@ -432,13 +433,13 @@ o = s:option(Value, option_name("wireguard_keepAlive"), translate("Keep Alive"))
o.default = "0"
o:depends({ [option_name("protocol")] = "wireguard" })
--- [[ TCP部分 ]]--
+-- [[ RAW部分 ]]--
-- TCP伪装
o = s:option(ListValue, option_name("tcp_guise"), translate("Camouflage Type"))
o:value("none", "none")
o:value("http", "http")
-o:depends({ [option_name("transport")] = "tcp" })
+o:depends({ [option_name("transport")] = "raw" })
-- HTTP域名
o = s:option(DynamicList, option_name("tcp_guise_http_host"), translate("HTTP Host"))
@@ -566,16 +567,115 @@ o = s:option(Value, option_name("httpupgrade_path"), translate("HttpUpgrade Path
o.placeholder = "/"
o:depends({ [option_name("transport")] = "httpupgrade" })
--- [[ SplitHTTP部分 ]]--
-o = s:option(Value, option_name("splithttp_host"), translate("SplitHTTP Host"))
-o:depends({ [option_name("transport")] = "splithttp" })
+-- [[ XHTTP部分 ]]--
+o = s:option(Value, option_name("xhttp_host"), translate("XHTTP Host"))
+o:depends({ [option_name("transport")] = "xhttp" })
-o = s:option(Value, option_name("splithttp_path"), translate("SplitHTTP Path"))
+o = s:option(Value, option_name("xhttp_path"), translate("XHTTP Path"))
o.placeholder = "/"
-o:depends({ [option_name("transport")] = "splithttp" })
+o:depends({ [option_name("transport")] = "xhttp" })
--- [[ Mux ]]--
-o = s:option(Flag, option_name("mux"), translate("Mux"))
+-- 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")] = "xhttp" })
+
+o = s:option(Value, option_name("maxConcurrency"), translate("XMUX Max Concurrency"))
+o:depends({ [option_name("xhttp_xmux")] = true })
+
+o = s:option(Value, option_name("maxConnections"), translate("XMUX Max Connections"))
+o:depends({ [option_name("xhttp_xmux")] = true })
+
+o = s:option(Value, option_name("cMaxReuseTimes"), translate("XMUX Connection Max Reuse Times"))
+o:depends({ [option_name("xhttp_xmux")] = true })
+
+o = s:option(Value, option_name("cMaxLifetimeMs"), translate("XMUX Connection Max Lifetime (ms)"))
+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")] = "xhttp" })
+
+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_host"), string.format('%s', "XHTTP Host"))
+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"))
o:depends({ [option_name("protocol")] = "vmess" })
o:depends({ [option_name("protocol")] = "vless", [option_name("flow")] = "" })
o:depends({ [option_name("protocol")] = "http" })
@@ -588,7 +688,7 @@ o.default = 8
o:depends({ [option_name("mux")] = true })
-- [[ XUDP Mux ]]--
-o = s:option(Flag, option_name("xmux"), translate("xMux"))
+o = s:option(Flag, option_name("xmux"), "XUDP Mux")
o.default = 1
o:depends({ [option_name("protocol")] = "vless", [option_name("flow")] = "xtls-rprx-vision" })
o:depends({ [option_name("protocol")] = "vless", [option_name("flow")] = "xtls-rprx-vision-udp443" })
diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua
index 09853c7b8..816fa4ef6 100644
--- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua
+++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua
@@ -110,6 +110,15 @@ function gen_outbound(flag, node, tag, proxy_table)
end
end
+ if node.type == "Xray" and node.transport == "xhttp" 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
@@ -159,7 +168,7 @@ function gen_outbound(flag, node, tag, proxy_table)
spiderX = node.reality_spiderX or "/",
fingerprint = (node.type == "Xray" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or "chrome"
} or nil,
- tcpSettings = (node.transport == "tcp" and node.protocol ~= "socks") and {
+ rawSettings = ((node.transport == "raw" or node.transport == "tcp") and node.protocol ~= "socks") and {
header = {
type = node.tcp_guise or "none",
request = (node.tcp_guise == "http") and {
@@ -213,9 +222,35 @@ function gen_outbound(flag, node, tag, proxy_table)
path = node.httpupgrade_path or "/",
host = node.httpupgrade_host
} or nil,
- splithttpSettings = (node.transport == "splithttp") and {
- path = node.splithttp_path or "/",
- host = node.splithttp_host
+ xhttpSettings = (node.transport == "xhttp" or node.transport == "splithttp") and {
+ path = node.xhttp_path or node.splithttp_path or "/",
+ host = node.xhttp_host or 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,
+ host = node.xhttp_download_host,
+ },
+ 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 = {
@@ -280,6 +315,41 @@ function gen_outbound(flag, node, tag, proxy_table)
result.streamSettings.tlsSettings.alpn = alpn
end
end
+
+ local alpn_download = {}
+ if node.xhttp_download_alpn and node.xhttp_download_alpn ~= "default" then
+ string.gsub(node.xhttp_download_alpn, '[^' .. "," .. ']+', function(w)
+ table.insert(alpn_download, w)
+ end)
+ end
+ if alpn_download and #alpn_download > 0 then
+ if result.streamSettings.xhttpSettings.downloadSettings.tlsSettings then
+ result.streamSettings.xhttpSettings.downloadSettings.tlsSettings.alpn = alpn_download
+ end
+ end
+
+ local xmux = {}
+ 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
+ xmux.cMaxLifetimeMs = node.cMaxLifetimeMs and (string.find(node.cMaxLifetimeMs, "-") and node.cMaxLifetimeMs or tonumber(node.cMaxLifetimeMs)) or 0
+ if result.streamSettings.xhttpSettings then
+ result.streamSettings.xhttpSettings.xmux = xmux
+ 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.xhttpSettings.downloadSettings.xhttpSettings then
+ result.streamSettings.xhttpSettings.downloadSettings.xhttpSettings.xmux = xmux_download
+ end
+ end
+
end
return result
end
@@ -447,7 +517,7 @@ function gen_config_server(node)
}
}
} or nil,
- tcpSettings = (node.transport == "tcp") and {
+ rawSettings = (node.transport == "raw" or node.transport == "tcp") and {
header = {
type = node.tcp_guise,
request = (node.tcp_guise == "http") and {
@@ -672,9 +742,20 @@ function gen_config(var)
return inboundTag
end
- local function gen_balancer(_node, loopbackTag)
+ local function gen_balancer(_node, loopback_tag)
+ local balancer_id = _node[".name"]
+ local balancer_tag = "balancer-" .. balancer_id
+ local loopback_dst = balancer_id -- route destination for the loopback outbound
+ if not loopback_tag or loopback_tag == "" then loopback_tag = balancer_id end
+ -- existing balancer
+ for _, v in ipairs(balancers) do
+ if v.tag == balancer_tag then
+ gen_loopback(loopback_tag, loopback_dst)
+ return balancer_tag
+ end
+ end
+ -- new balancer
local blc_nodes = _node.balancing_node
- local fallback_node_id = _node.fallback_node
local length = #blc_nodes
local valid_nodes = {}
for i = 1, length do
@@ -698,6 +779,10 @@ function gen_config(var)
end
end
end
+ if #valid_nodes == 0 then return nil end
+
+ -- fallback node
+ local fallback_node_id = _node.fallback_node
if fallback_node_id == "" then fallback_node_id = nil end
if fallback_node_id then
local is_new_node = true
@@ -718,41 +803,33 @@ function gen_config(var)
fallback_node_id = nil
end
else
- local valid = gen_balancer(fallback_node)
- if not valid then
+ if not gen_balancer(fallback_node) then
fallback_node_id = nil
end
end
end
end
-
- local valid = nil
- if #valid_nodes > 0 then
- local balancerTag = get_balancer_tag(_node[".name"])
- table.insert(balancers, {
- tag = balancerTag,
- selector = valid_nodes,
- fallbackTag = fallback_node_id,
- strategy = { type = _node.balancingStrategy or "random" }
- })
- if _node.balancingStrategy == "leastPing" or fallback_node_id then
- if not observatory then
- observatory = {
- subjectSelector = { "blc-" },
- probeUrl = _node.useCustomProbeUrl and _node.probeUrl or nil,
- probeInterval = _node.probeInterval or "1m",
- enableConcurrency = true
- }
- end
+ table.insert(balancers, {
+ tag = balancer_tag,
+ selector = valid_nodes,
+ fallbackTag = fallback_node_id,
+ strategy = { type = _node.balancingStrategy or "random" }
+ })
+ if _node.balancingStrategy == "leastPing" or fallback_node_id then
+ if not observatory then
+ observatory = {
+ subjectSelector = { "blc-" },
+ probeUrl = _node.useCustomProbeUrl and _node.probeUrl or nil,
+ probeInterval = _node.probeInterval or "1m",
+ enableConcurrency = true
+ }
end
- if loopbackTag == nil or loopbackTag =="" then loopbackTag = _node[".name"] end
- local inboundTag = gen_loopback(loopbackTag, _node[".name"])
- table.insert(rules, { inboundTag = { inboundTag }, balancerTag = balancerTag })
- valid = true
end
- return valid
+ local inbound_tag = gen_loopback(loopback_tag, loopback_dst)
+ table.insert(rules, { inboundTag = { inbound_tag }, balancerTag = balancer_tag })
+ return balancer_tag
end
-
+
local function set_outbound_detour(node, outbound, outbounds_table, shunt_rule_name)
if not node or not outbound or not outbounds_table then return nil end
local default_outTag = outbound.tag
@@ -787,31 +864,25 @@ function gen_config(var)
node.port = server_port
end
if node.protocol == "_shunt" then
- local preproxy_enabled = node.preproxy_enabled == "1"
- local preproxy_rule_name = "main"
- local preproxy_tag = "main"
+ local preproxy_rule_name = node.preproxy_enabled == "1" and "main" or nil
+ local preproxy_tag = preproxy_rule_name
local preproxy_node_id = node["main_node"]
- local preproxy_node = preproxy_enabled and preproxy_node_id and uci:get_all(appname, preproxy_node_id) or nil
- if preproxy_node then
- preproxy_tag = preproxy_tag .. ":" .. preproxy_node.remarks
- end
-
- local proxy_outboundTag, proxy_balancerTag
+ local preproxy_outbound_tag, preproxy_balancer_tag
+ local preproxy_nodes
local function gen_shunt_node(rule_name, _node_id)
if not rule_name then return nil, nil end
if not _node_id then _node_id = node[rule_name] or "nil" end
- local rule_outboundTag
- local rule_balancerTag
if _node_id == "_direct" then
- rule_outboundTag = "direct"
+ return "direct", nil
elseif _node_id == "_blackhole" then
- rule_outboundTag = "blackhole"
+ return "blackhole", nil
elseif _node_id == "_default" then
- rule_outboundTag = "default"
+ return "default", nil
elseif _node_id:find("Socks_") then
local socks_id = _node_id:sub(1 + #"Socks_")
local socks_node = uci:get_all(appname, socks_id) or nil
+ local socks_tag
if socks_node then
local _node = {
type = "Xray",
@@ -823,27 +894,25 @@ function gen_config(var)
}
local outbound = gen_outbound(flag, _node, rule_name)
if outbound then
- table.insert(outbounds, outbound)
- rule_outboundTag = outbound.tag
+ if rule_name == "default" then
+ table.insert(outbounds, 1, outbound)
+ else
+ table.insert(outbounds, outbound)
+ end
+ socks_tag = outbound.tag
end
end
+ return socks_tag, nil
elseif _node_id ~= "nil" then
local _node = uci:get_all(appname, _node_id)
if not _node then return nil, nil end
if api.is_normal_node(_node) then
- local use_proxy = preproxy_node and node[rule_name .. "_proxy_tag"] == preproxy_rule_name and _node_id ~= preproxy_node_id
- if use_proxy and proxy_balancerTag then
- for _, blc_node_id in ipairs(preproxy_node.balancing_node) do
- if _node_id == blc_node_id then
- use_proxy = false
- break
- end
- end
- end
+ local use_proxy = preproxy_tag and node[rule_name .. "_proxy_tag"] == preproxy_rule_name and _node_id ~= preproxy_node_id
+ if use_proxy and preproxy_balancer_tag and preproxy_nodes[_node_id] then use_proxy = false end
local copied_outbound
for index, value in ipairs(outbounds) do
- if value["_id"] == _node_id and value["_flag_proxy_tag"] == preproxy_tag then
+ if value["_id"] == _node_id and value["_flag_proxy_tag"] == (use_proxy and preproxy_tag or nil) then
copied_outbound = api.clone(value)
break
end
@@ -851,7 +920,7 @@ function gen_config(var)
if copied_outbound then
copied_outbound.tag = rule_name .. ":" .. _node.remarks
table.insert(outbounds, copied_outbound)
- rule_outboundTag = rule_name
+ return copied_outbound.tag, nil
else
if use_proxy and (_node.type ~= "Xray" or _node.flow == "xtls-rprx-vision") then
new_port = get_new_port()
@@ -869,8 +938,8 @@ function gen_config(var)
_node.port = new_port
table.insert(rules, 1, {
inboundTag = {"proxy_" .. rule_name},
- outboundTag = proxy_outboundTag,
- balancerTag = proxy_balancerTag
+ outboundTag = not preproxy_balancer_tag and preproxy_tag or nil,
+ balancerTag = preproxy_balancer_tag
})
end
local proxy_table = {
@@ -885,33 +954,22 @@ function gen_config(var)
end
end
local outbound = gen_outbound(flag, _node, rule_name, proxy_table)
+ local outbound_tag
if outbound then
outbound.tag = outbound.tag .. ":" .. _node.remarks
- rule_outboundTag = set_outbound_detour(_node, outbound, outbounds, rule_name)
+ outbound_tag = set_outbound_detour(_node, outbound, outbounds, rule_name)
if rule_name == "default" then
table.insert(outbounds, 1, outbound)
else
table.insert(outbounds, outbound)
end
end
+ return outbound_tag, nil
end
elseif _node.protocol == "_balancing" then
- local is_new_balancer = true
- rule_balancerTag = get_balancer_tag(_node_id)
- for _, v in ipairs(balancers) do
- if v.tag == rule_balancerTag then
- is_new_balancer = false
- gen_loopback(rule_name, _node_id)
- break
- end
- end
- if is_new_balancer then
- local valid = gen_balancer(_node, rule_name)
- if not valid then
- rule_balancerTag = nil
- end
- end
+ return nil, gen_balancer(_node, rule_name)
elseif _node.protocol == "_iface" then
+ local outbound_tag
if _node.iface then
local outbound = {
protocol = "freedom",
@@ -923,26 +981,40 @@ function gen_config(var)
}
}
}
+ outbound_tag = outbound.tag
table.insert(outbounds, outbound)
- rule_outboundTag = rule_name
sys.call("touch /tmp/etc/passwall2/iface/" .. _node.iface)
end
+ return outbound_tag, nil
end
end
- return rule_outboundTag, rule_balancerTag
end
- if preproxy_node then
- proxy_outboundTag, proxy_balancerTag = gen_shunt_node(preproxy_rule_name, preproxy_node_id)
- if not proxy_outboundTag and not proxy_balancerTag then
- preproxy_node = nil
+ if preproxy_tag and preproxy_node_id then
+ preproxy_outbound_tag, preproxy_balancer_tag = gen_shunt_node(preproxy_rule_name, preproxy_node_id)
+ if preproxy_balancer_tag then
+ local _node_id = preproxy_node_id
+ preproxy_nodes = {}
+ while _node_id do
+ _node = uci:get_all(appname, _node_id)
+ if not _node then break end
+ if _node.protocol ~= "_balancing" then
+ preproxy_nodes[_node_id] = true
+ break
+ end
+ local _blc_nodes = _node.balancing_node
+ for i = 1, #_blc_nodes do preproxy_nodes[_blc_nodes[i]] = true end
+ _node_id = _node.fallback_node
+ end
+ elseif preproxy_outbound_tag then
+ preproxy_tag = preproxy_outbound_tag
end
end
--default_node
local default_node_id = node.default_node or "_direct"
local default_outboundTag, default_balancerTag = gen_shunt_node("default", default_node_id)
- COMMON.default_outbound_tag = default_outbound_tag
- COMMON.default_balancer_tag = default_balancer_tag
+ COMMON.default_outbound_tag = default_outboundTag
+ COMMON.default_balancer_tag = default_balancerTag
--shunt rule
uci:foreach(appname, "shunt_rules", function(e)
local outboundTag, balancerTag = gen_shunt_node(e[".name"])
@@ -1053,15 +1125,15 @@ function gen_config(var)
}
elseif node.protocol == "_balancing" then
if node.balancing_node then
- local valid = gen_balancer(node)
- if valid then
- table.insert(rules, { network = "tcp,udp", balancerTag = get_balancer_tag(node_id) })
+ local balancer_tag = gen_balancer(node)
+ if balancer_tag then
+ table.insert(rules, { network = "tcp,udp", balancerTag = balancer_tag })
end
routing = {
balancers = balancers,
rules = rules
}
- COMMON.default_balancer_tag = get_balancer_tag(node_id)
+ COMMON.default_balancer_tag = balancer_tag
end
elseif node.protocol == "_iface" then
if node.iface then
@@ -1358,7 +1430,7 @@ function gen_config(var)
local default_rule_index = #routing.rules > 0 and #routing.rules or 1
for index, value in ipairs(routing.rules) do
- if value["_flag"] == "default" then
+ if value.ruleTag == "default" then
default_rule_index = index
break
end
@@ -1461,7 +1533,7 @@ function gen_config(var)
})
end
- table.insert(outbounds, {
+ local direct_outbound = {
protocol = "freedom",
tag = "direct",
settings = {
@@ -1472,11 +1544,23 @@ function gen_config(var)
mark = 255
}
}
- })
- table.insert(outbounds, {
+ }
+ if COMMON.default_outbound_tag == "direct" then
+ table.insert(outbounds, 1, direct_outbound)
+ else
+ table.insert(outbounds, direct_outbound)
+ end
+
+ local blackhole_outbound = {
protocol = "blackhole",
tag = "blackhole"
- })
+ }
+ if COMMON.default_outbound_tag == "blackhole" then
+ table.insert(outbounds, 1, blackhole_outbound)
+ else
+ table.insert(outbounds, blackhole_outbound)
+ end
+
for index, value in ipairs(config.outbounds) do
for k, v in pairs(config.outbounds[index]) do
if k:find("_") == 1 then
diff --git a/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm b/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm
index 4026b2f71..5071216db 100644
--- a/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm
+++ b/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm
@@ -368,6 +368,10 @@ local api = require "luci.passwall2.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");
}
params += "&type=" + v_transport;
@@ -1135,6 +1139,9 @@ local api = require "luci.passwall2.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 || "");
}
if (m.hash) {
diff --git a/luci-app-passwall2/po/zh-cn/passwall2.po b/luci-app-passwall2/po/zh-cn/passwall2.po
index 6f70bcee2..2bf6ca592 100644
--- a/luci-app-passwall2/po/zh-cn/passwall2.po
+++ b/luci-app-passwall2/po/zh-cn/passwall2.po
@@ -1378,6 +1378,30 @@ msgstr "客户端文件不适合当前设备。"
msgid "Can't move new file to path: %s"
msgstr "无法移动新文件到:%s"
+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 连接最大复用流数"
+
+msgid "XMUX Max Connections"
+msgstr "XMUX 最大连接数"
+
+msgid "XMUX Connection Max Reuse Times"
+msgstr "XMUX 连接最多复用次数"
+
+msgid "XMUX Connection Max Lifetime (ms)"
+msgstr "XMUX 连接最大存活时间(ms)"
+
+msgid "Enable Mux.Cool"
+msgstr "启用 Mux.Cool"
+
msgid "Mux concurrency"
msgstr "最大并发连接数"
diff --git a/luci-app-passwall2/root/usr/share/passwall2/iptables.sh b/luci-app-passwall2/root/usr/share/passwall2/iptables.sh
index 73af69df0..ab8ab8db2 100755
--- a/luci-app-passwall2/root/usr/share/passwall2/iptables.sh
+++ b/luci-app-passwall2/root/usr/share/passwall2/iptables.sh
@@ -223,10 +223,10 @@ load_acl() {
}
_acl_list=${TMP_ACL_PATH}/${sid}/rule_list
- [ $use_interface = "1" ] && _acl_list=${TMP_ACL_PATH}/${sid}/interface_list
+ [ "$use_interface" = "1" ] && _acl_list=${TMP_ACL_PATH}/${sid}/interface_list
for i in $(cat $_acl_list); do
- if [ $use_interface = "0" ]; then
+ if [ "$use_interface" = "0" ]; then
if [ -n "$(echo ${i} | grep '^iprange:')" ]; then
_iprange=$(echo ${i} | sed 's#iprange:##g')
_ipt_source=$(factor ${_iprange} "-m iprange --src-range")
diff --git a/luci-app-passwall2/root/usr/share/passwall2/nftables.sh b/luci-app-passwall2/root/usr/share/passwall2/nftables.sh
index 86a27f1bb..db87f2316 100755
--- a/luci-app-passwall2/root/usr/share/passwall2/nftables.sh
+++ b/luci-app-passwall2/root/usr/share/passwall2/nftables.sh
@@ -277,10 +277,10 @@ load_acl() {
}
_acl_list=${TMP_ACL_PATH}/${sid}/rule_list
- [ $use_interface = "1" ] && _acl_list=${TMP_ACL_PATH}/${sid}/interface_list
+ [ "$use_interface" = "1" ] && _acl_list=${TMP_ACL_PATH}/${sid}/interface_list
for i in $(cat $_acl_list); do
- if [ $use_interface = "0" ]; then
+ if [ "$use_interface" = "0" ]; then
if [ -n "$(echo ${i} | grep '^iprange:')" ]; then
_iprange=$(echo ${i} | sed 's#iprange:##g')
_ipt_source=$(factor ${_iprange} "ip saddr")