diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index c45a059b..4ff6e386 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall -PKG_VERSION:=4.74-4 +PKG_VERSION:=4.75-1 PKG_RELEASE:= PKG_CONFIG_DEPENDS:= \ 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 7d113d76..99bf4363 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 @@ -54,7 +54,8 @@ for k, e in ipairs(api.get_valid_nodes()) do if e.node_type == "normal" then nodes_table[#nodes_table + 1] = { id = e[".name"], - remarks = e["remark"] + remarks = e["remark"], + type = e["type"] } end if e.protocol == "_balancing" then @@ -80,11 +81,13 @@ local o = s:option(ListValue, option_name("balancingStrategy"), translate("Balan o:depends({ [option_name("protocol")] = "_balancing" }) o:value("random") o:value("leastPing") -o.default = "random" +o:value("leastLoad") +o.default = "leastLoad" -- 探测地址 local o = s:option(Flag, option_name("useCustomProbeUrl"), translate("Use Custome Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL.")) o:depends({ [option_name("balancingStrategy")] = "leastPing" }) +o:depends({ [option_name("balancingStrategy")] = "leastLoad" }) local o = s:option(Value, option_name("probeUrl"), translate("Probe URL")) o:depends({ [option_name("useCustomProbeUrl")] = true }) @@ -94,6 +97,7 @@ o.description = translate("The URL used to detect the connection status.") -- 探测间隔 local o = s:option(Value, option_name("probeInterval"), translate("Probe Interval")) o:depends({ [option_name("balancingStrategy")] = "leastPing" }) +o:depends({ [option_name("balancingStrategy")] = "leastLoad" }) o.default = "1m" o.description = translate("The interval between initiating probes. Every time this time elapses, a server status check is performed on a server. The time format is numbers + units, such as '10s', '2h45m', and the supported time units are ns, us, ms, s, m, h, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.") @@ -522,4 +526,21 @@ o.default = 0 o = s:option(Flag, option_name("tcpNoDelay"), "tcpNoDelay") o.default = 0 +o = s:option(ListValue, option_name("to_node"), translate("Landing node"), translate("Only support a layer of proxy.")) +o.default = "" +o:value("", translate("Close(Not use)")) +for k, v in pairs(nodes_table) do + if v.type == "Xray" then + o:value(v.id, v.remarks) + end +end + +for i, v in ipairs(s.fields[option_name("protocol")].keylist) do + if not v:find("_") then + s.fields[option_name("tcpMptcp")]:depends({ [option_name("protocol")] = v }) + s.fields[option_name("tcpNoDelay")]:depends({ [option_name("protocol")] = v }) + s.fields[option_name("to_node")]:depends({ [option_name("protocol")] = v }) + end +end + api.luci_types(arg[1], m, s, type_name, option_prefix) diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua index 8514b3c1..7e8ba6d5 100644 --- a/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -248,6 +248,11 @@ function gen_outbound(flag, node, tag, proxy_table) reserved = (node.protocol == "wireguard" and node.wireguard_reserved) and node.wireguard_reserved or nil } } + + if node.protocol == "wireguard" then + result.settings.kernelMode = false + end + local alpn = {} if node.alpn and node.alpn ~= "default" then string.gsub(node.alpn, '[^' .. "," .. ']+', function(w) @@ -665,7 +670,7 @@ function gen_config(var) selector = valid_nodes, strategy = { type = _node.balancingStrategy or "random" } } - if _node.balancingStrategy == "leastPing" then + if _node.balancingStrategy == "leastPing" or _node.balancingStrategy == "leastLoad" then if not observatory then observatory = { subjectSelector = { "blc-" }, @@ -692,6 +697,34 @@ function gen_config(var) return balancer, rule 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 + + if node.to_node then + local to_node = uci:get_all(appname, node.to_node) + if to_node then + local to_outbound = gen_outbound(nil, to_node) + if to_outbound then + if shunt_rule_name then + to_outbound.tag = outbound.tag + outbound.tag = node[".name"] + else + to_outbound.tag = outbound.tag .. " -> " .. to_outbound.tag + end + + to_outbound.proxySettings = { + tag = outbound.tag, + transportLayer = true + } + table.insert(outbounds_table, to_outbound) + default_outTag = to_outbound.tag + end + end + end + return default_outTag + end + if node.protocol == "_shunt" then local rules = {} local balancers = {} @@ -723,6 +756,7 @@ function gen_config(var) elseif preproxy_node and api.is_normal_node(preproxy_node) then local preproxy_outbound = gen_outbound(flag, preproxy_node, preproxy_tag, { fragment = xray_settings.fragment == "1" or nil }) if preproxy_outbound then + set_outbound_detour(preproxy_node, preproxy_outbound, outbounds, preproxy_tag) table.insert(outbounds, preproxy_outbound) else preproxy_enabled = false @@ -738,7 +772,7 @@ function gen_config(var) end end - local function gen_shunt_node(rule_name, _node_id, as_proxy) + 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 @@ -832,6 +866,7 @@ function gen_config(var) end local _outbound = gen_outbound(flag, _node, rule_name, proxy_table) if _outbound then + set_outbound_detour(_node, _outbound, outbounds, rule_name) table.insert(outbounds, _outbound) if proxy then preproxy_used = true end rule_outboundTag = rule_name @@ -984,26 +1019,27 @@ function gen_config(var) } } end - else - local outbound = nil - if node.protocol == "_iface" then - if node.iface then - outbound = { - protocol = "freedom", - tag = "outbound", - streamSettings = { - sockopt = { - mark = 255, - interface = node.iface - } + elseif node.protocol == "_iface" then + if node.iface then + local outbound = { + protocol = "freedom", + tag = "outbound", + streamSettings = { + sockopt = { + mark = 255, + interface = node.iface } } - sys.call("touch /tmp/etc/passwall/iface/" .. node.iface) - end - else - outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil }) + } + table.insert(outbounds, outbound) + sys.call("touch /tmp/etc/passwall/iface/" .. node.iface) + end + else + local outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil }) + if outbound then + set_outbound_detour(node, outbound, outbounds) + table.insert(outbounds, outbound) end - if outbound then table.insert(outbounds, outbound) end routing = { domainStrategy = "AsIs", domainMatcher = "hybrid", 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 2540f664..22be258e 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 @@ -225,6 +225,10 @@ local api = require "luci.passwall.api" if (v_transport === "ws") { info.host = opt.get(dom_prefix + "ws_host").value; info.path = opt.get(dom_prefix + "ws_path").value; + if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) { + var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value; + info.path = info.path + "?ed=" + ws_maxEarlyData; + } } else if (v_transport === "h2") { info.host = opt.get(dom_prefix + "h2_host").value; info.path = opt.get(dom_prefix + "h2_path").value; @@ -269,6 +273,10 @@ local api = require "luci.passwall.api" if (v_transport === "ws") { params += opt.query("host", dom_prefix + "ws_host"); params += opt.query("path", dom_prefix + "ws_path"); + if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) { + var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value; + params += "?ed=" + ws_maxEarlyData; + } } else if (v_transport === "h2") { v_transport = "http"; params += opt.query("host", dom_prefix + "h2_host"); @@ -704,6 +712,24 @@ local api = require "luci.passwall.api" } else if (ssm.net === "ws") { opt.set(dom_prefix + 'ws_host', ssm.host); opt.set(dom_prefix + 'ws_path', ssm.path); + if (dom_prefix == "singbox_" && ssm.path && ssm.path.length > 1) { + var ws_path_params = {}; + var ws_path_dat = ssm.path.split('?'); + var ws_path = ws_path_dat[0]; + var ws_path_params = {}; + var ws_path_params_array = ws_path_dat[1].split('&'); + for (i = 0; i < ws_path_params_array.length; i++) { + var kv = ws_path_params_array[i].split('='); + ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || ''); + } + + if (ws_path_params.ed) { + opt.set(dom_prefix + 'ws_path', ws_path); + opt.set(dom_prefix + 'ws_enableEarlyData', true); + opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed); + opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol'); + } + } } else if (ssm.net === "h2") { opt.set(dom_prefix + 'h2_host', ssm.host); opt.set(dom_prefix + 'h2_path', ssm.path); @@ -793,6 +819,24 @@ local api = require "luci.passwall.api" } else if (queryParam.type === "ws") { opt.set(dom_prefix + 'ws_host', queryParam.host || ""); opt.set(dom_prefix + 'ws_path', queryParam.path || ""); + if (dom_prefix == "singbox_" && queryParam.path && queryParam.path.length > 1) { + var ws_path_params = {}; + var ws_path_dat = queryParam.path.split('?'); + var ws_path = ws_path_dat[0]; + var ws_path_params = {}; + var ws_path_params_array = ws_path_dat[1].split('&'); + for (i = 0; i < ws_path_params_array.length; i++) { + var kv = ws_path_params_array[i].split('='); + ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || ''); + } + + if (ws_path_params.ed) { + opt.set(dom_prefix + 'ws_path', ws_path); + opt.set(dom_prefix + 'ws_enableEarlyData', true); + opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed); + opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol'); + } + } } else if (queryParam.type === "h2" || queryParam.type === "http") { opt.set(dom_prefix + 'h2_host', queryParam.host || ""); opt.set(dom_prefix + 'h2_path', queryParam.path || ""); diff --git a/luci-app-passwall/root/usr/share/passwall/rules/proxy_ip b/luci-app-passwall/root/usr/share/passwall/rules/proxy_ip index 39be0de2..88309f8d 100644 --- a/luci-app-passwall/root/usr/share/passwall/rules/proxy_ip +++ b/luci-app-passwall/root/usr/share/passwall/rules/proxy_ip @@ -7,6 +7,8 @@ 8.8.8.8 208.67.222.222 208.67.220.220 +104.16.249.249 +104.16.248.249 1.1.1.1 1.1.1.2 1.0.0.1 @@ -16,4 +18,4 @@ 2001:b28:f23c::/48 2001:b28:f23d::/48 2001:b28:f23f::/48 -2001:b28:f242::/48 \ No newline at end of file +2001:b28:f242::/48 diff --git a/luci-app-passwall/root/usr/share/passwall/subscribe.lua b/luci-app-passwall/root/usr/share/passwall/subscribe.lua index 3be410bc..bc68928d 100755 --- a/luci-app-passwall/root/usr/share/passwall/subscribe.lua +++ b/luci-app-passwall/root/usr/share/passwall/subscribe.lua @@ -406,6 +406,21 @@ local function processData(szType, content, add_mode, add_from) if info.net == 'ws' then result.ws_host = info.host result.ws_path = info.path + if result.type == "sing-box" and info.path then + local ws_path_dat = split(info.path, "?") + local ws_path = ws_path_dat[1] + local ws_path_params = {} + for _, v in pairs(split(ws_path_dat[2], '&')) do + local t = split(v, '=') + ws_path_params[t[1]] = t[2] + end + if ws_path_params.ed and tonumber(ws_path_params.ed) then + result.ws_path = ws_path + result.ws_enableEarlyData = "1" + result.ws_maxEarlyData = tonumber(ws_path_params.ed) + result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol" + end + end end if info.net == 'h2' then result.h2_host = info.host @@ -693,6 +708,21 @@ local function processData(szType, content, add_mode, add_from) if params.type == 'ws' then result.ws_host = params.host result.ws_path = params.path + if result.type == "sing-box" and params.path then + local ws_path_dat = split(params.path, "?") + local ws_path = ws_path_dat[1] + local ws_path_params = {} + for _, v in pairs(split(ws_path_dat[2], '&')) do + local t = split(v, '=') + ws_path_params[t[1]] = t[2] + end + if ws_path_params.ed and tonumber(ws_path_params.ed) then + result.ws_path = ws_path + result.ws_enableEarlyData = "1" + result.ws_maxEarlyData = tonumber(ws_path_params.ed) + result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol" + end + end end if params.type == 'h2' or params.type == 'http' then params.type = "h2" diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 95b9a9c4..40b6b169 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 -PKG_VERSION:=1.26-3 +PKG_VERSION:=1.27-1 PKG_RELEASE:= PKG_CONFIG_DEPENDS:= \ 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 4953985d..19afae3a 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 @@ -54,7 +54,8 @@ for k, e in ipairs(api.get_valid_nodes()) do if e.node_type == "normal" then nodes_table[#nodes_table + 1] = { id = e[".name"], - remarks = e["remark"] + remarks = e["remark"], + type = e["type"] } end if e.protocol == "_balancing" then @@ -80,11 +81,13 @@ local o = s:option(ListValue, option_name("balancingStrategy"), translate("Balan o:depends({ [option_name("protocol")] = "_balancing" }) o:value("random") o:value("leastPing") -o.default = "random" +o:value("leastLoad") +o.default = "leastLoad" -- 探测地址 local o = s:option(Flag, option_name("useCustomProbeUrl"), translate("Use Custome Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL.")) o:depends({ [option_name("balancingStrategy")] = "leastPing" }) +o:depends({ [option_name("balancingStrategy")] = "leastLoad" }) local o = s:option(Value, option_name("probeUrl"), translate("Probe URL")) o:depends({ [option_name("useCustomProbeUrl")] = true }) @@ -94,6 +97,7 @@ o.description = translate("The URL used to detect the connection status.") -- 探测间隔 local o = s:option(Value, option_name("probeInterval"), translate("Probe Interval")) o:depends({ [option_name("balancingStrategy")] = "leastPing" }) +o:depends({ [option_name("balancingStrategy")] = "leastLoad" }) o.default = "1m" o.description = translate("The interval between initiating probes. Every time this time elapses, a server status check is performed on a server. The time format is numbers + units, such as '10s', '2h45m', and the supported time units are ns, us, ms, s, m, h, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.") @@ -523,4 +527,21 @@ o.default = 0 o = s:option(Flag, option_name("tcpNoDelay"), "tcpNoDelay") o.default = 0 +o = s:option(ListValue, option_name("to_node"), translate("Landing node"), translate("Only support a layer of proxy.")) +o.default = "" +o:value("", translate("Close(Not use)")) +for k, v in pairs(nodes_table) do + if v.type == "Xray" then + o:value(v.id, v.remarks) + end +end + +for i, v in ipairs(s.fields[option_name("protocol")].keylist) do + if not v:find("_") then + s.fields[option_name("tcpMptcp")]:depends({ [option_name("protocol")] = v }) + s.fields[option_name("tcpNoDelay")]:depends({ [option_name("protocol")] = v }) + s.fields[option_name("to_node")]:depends({ [option_name("protocol")] = v }) + end +end + api.luci_types(arg[1], m, s, type_name, option_prefix) 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 89c0c0b3..611a884a 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 @@ -65,13 +65,13 @@ o.default = "eth1" o:depends({ [option_name("protocol")] = "_iface" }) local nodes_table = {} -local balancers_table = {} local iface_table = {} for k, e in ipairs(api.get_valid_nodes()) do if e.node_type == "normal" then nodes_table[#nodes_table + 1] = { id = e[".name"], - remarks = e["remark"] + remarks = e["remark"], + type = e["type"] } end if e.protocol == "_iface" then @@ -89,9 +89,6 @@ if #nodes_table > 0 then o = s:option(Value, option_name("main_node"), string.format('%s', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including Default) has a separate switch that controls whether this rule uses the pre-proxy or not.")) o:depends({ [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true }) - for k, v in pairs(balancers_table) do - o:value(v.id, v.remarks) - end for k, v in pairs(iface_table) do o:value(v.id, v.remarks) end @@ -110,9 +107,6 @@ uci:foreach(appname, "shunt_rules", function(e) o:depends({ [option_name("protocol")] = "_shunt" }) if #nodes_table > 0 then - for k, v in pairs(balancers_table) do - o:value(v.id, v.remarks) - end for k, v in pairs(iface_table) do o:value(v.id, v.remarks) end @@ -142,9 +136,6 @@ o:value("_direct", translate("Direct Connection")) o:value("_blackhole", translate("Blackhole")) if #nodes_table > 0 then - for k, v in pairs(balancers_table) do - o:value(v.id, v.remarks) - end for k, v in pairs(iface_table) do o:value(v.id, v.remarks) end @@ -631,4 +622,18 @@ o:value("prefer_ipv6") o:value("ipv4_only") o:value("ipv6_only") +o = s:option(ListValue, option_name("to_node"), translate("Landing node"), translate("Only support a layer of proxy.")) +o.default = "" +o:value("", translate("Close(Not use)")) +for k, v in pairs(nodes_table) do + if v.type == "sing-box" then + o:value(v.id, v.remarks) + end +end +for i, v in ipairs(s.fields[option_name("protocol")].keylist) do + if not v:find("_") then + o:depends({ [option_name("protocol")] = v }) + end +end + api.luci_types(arg[1], m, s, type_name, option_prefix) diff --git a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua index 32371a47..77c1e220 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua @@ -872,6 +872,54 @@ function gen_config(var) node.address = server_host node.port = server_port 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 + + if node.shadowtls == "1" then + local _node = { + type = "sing-box", + protocol = "shadowtls", + shadowtls_version = node.shadowtls_version, + password = (node.shadowtls_version == "2" or node.shadowtls_version == "3") and node.shadowtls_password or nil, + address = node.address, + port = node.port, + tls = "1", + tls_serverName = node.shadowtls_serverName, + utls = node.shadowtls_utls, + fingerprint = node.shadowtls_fingerprint + } + local shadowtls_outbound = gen_outbound(nil, _node, outbound.tag .. "_shadowtls") + if shadowtls_outbound then + table.insert(outbounds_table, shadowtls_outbound) + outbound.detour = outbound.tag .. "_shadowtls" + outbound.server = nil + outbound.server_port = nil + end + end + + if node.to_node then + local to_node = uci:get_all(appname, node.to_node) + if to_node then + local to_outbound = gen_outbound(nil, to_node) + if to_outbound then + if shunt_rule_name then + to_outbound.tag = outbound.tag + outbound.tag = node[".name"] + else + to_outbound.tag = outbound.tag .. " -> " .. to_outbound.tag + end + + to_outbound.detour = outbound.tag + table.insert(outbounds_table, to_outbound) + default_outTag = to_outbound.tag + end + end + end + return default_outTag + end + if node.protocol == "_shunt" then local rules = {} @@ -900,34 +948,14 @@ function gen_config(var) elseif preproxy_node and api.is_normal_node(preproxy_node) then local preproxy_outbound = gen_outbound(flag, preproxy_node, preproxy_tag) if preproxy_outbound then - if preproxy_node.shadowtls == "1" then - local _node = { - type = "sing-box", - protocol = "shadowtls", - shadowtls_version = preproxy_node.shadowtls_version, - password = (preproxy_node.shadowtls_version == "2" or preproxy_node.shadowtls_version == "3") and preproxy_node.shadowtls_password or nil, - address = preproxy_node.address, - port = preproxy_node.port, - tls = "1", - tls_serverName = preproxy_node.shadowtls_serverName, - utls = preproxy_node.shadowtls_utls, - fingerprint = preproxy_node.shadowtls_fingerprint - } - local shadowtls_outbound = gen_outbound(flag, _node, preproxy_tag .. "_shadowtls") - if shadowtls_outbound then - table.insert(outbounds, shadowtls_outbound) - preproxy_outbound.detour = preproxy_outbound.tag .. "_shadowtls" - preproxy_outbound.server = nil - preproxy_outbound.server_port = nil - end - end + set_outbound_detour(preproxy_node, preproxy_outbound, outbounds, preproxy_tag) table.insert(outbounds, preproxy_outbound) else preproxy_enabled = false end end - local function gen_shunt_node(rule_name, _node_id, as_proxy) + 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 @@ -999,27 +1027,7 @@ function gen_config(var) end local _outbound = gen_outbound(flag, _node, rule_name, { proxy = proxy and 1 or 0, tag = proxy and preproxy_tag or nil }) if _outbound then - if _node.shadowtls == "1" then - local shadowtls_node = { - type = "sing-box", - protocol = "shadowtls", - shadowtls_version = _node.shadowtls_version, - password = (_node.shadowtls_version == "2" or _node.shadowtls_version == "3") and _node.shadowtls_password or nil, - address = _node.address, - port = _node.port, - tls = "1", - tls_serverName = _node.shadowtls_serverName, - utls = _node.shadowtls_utls, - fingerprint = _node.shadowtls_fingerprint - } - local shadowtls_outbound = gen_outbound(flag, shadowtls_node, rule_name .. "_shadowtls", { proxy = proxy and 1 or 0, tag = proxy and preproxy_tag or nil }) - if shadowtls_outbound then - table.insert(outbounds, shadowtls_outbound) - _outbound.detour = _outbound.tag .. "_shadowtls" - _outbound.server = nil - _outbound.server_port = nil - end - end + set_outbound_detour(_node, _outbound, outbounds, rule_name) table.insert(outbounds, _outbound) rule_outboundTag = rule_name end @@ -1196,46 +1204,23 @@ function gen_config(var) for index, value in ipairs(rules) do table.insert(route.rules, rules[index]) end - else - local outbound = nil - if node.protocol == "_iface" then - if node.iface then - outbound = { - type = "direct", - tag = node_id, - bind_interface = node.iface, - routing_mark = 255, - } - sys.call("touch /tmp/etc/passwall2/iface/" .. node.iface) - end - else - outbound = gen_outbound(flag, node) - if outbound then - if node.shadowtls == "1" then - local shadowtls_node = { - type = "sing-box", - protocol = "shadowtls", - shadowtls_version = node.shadowtls_version, - password = (node.shadowtls_version == "2" or node.shadowtls_version == "3") and node.shadowtls_password or nil, - address = node.address, - port = node.port, - tls = "1", - tls_serverName = node.shadowtls_serverName, - utls = node.shadowtls_utls, - fingerprint = node.shadowtls_fingerprint - } - local shadowtls_outbound = gen_outbound(flag, shadowtls_node, outbound.tag .. "_shadowtls") - if shadowtls_outbound then - table.insert(outbounds, shadowtls_outbound) - outbound.detour = outbound.tag .. "_shadowtls" - outbound.server = nil - outbound.server_port = nil - end - end - end - end - if outbound then + elseif node.protocol == "_iface" then + if node.iface then + local outbound = { + type = "direct", + tag = node_id, + bind_interface = node.iface, + routing_mark = 255, + } + table.insert(outbounds, outbound) default_outTag = outbound.tag + route.final = default_outTag + sys.call("touch /tmp/etc/passwall2/iface/" .. node.iface) + end + else + local outbound = gen_outbound(flag, node) + if outbound then + default_outTag = set_outbound_detour(node, outbound, outbounds) table.insert(outbounds, outbound) route.final = default_outTag end diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua index 67627130..8004731c 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -245,6 +245,11 @@ function gen_outbound(flag, node, tag, proxy_table) reserved = (node.protocol == "wireguard" and node.wireguard_reserved) and node.wireguard_reserved or nil } } + + if node.protocol == "wireguard" then + result.settings.kernelMode = false + end + local alpn = {} if node.alpn and node.alpn ~= "default" then string.gsub(node.alpn, '[^' .. "," .. ']+', function(w) @@ -658,7 +663,7 @@ function gen_config(var) selector = valid_nodes, strategy = { type = _node.balancingStrategy or "random" } } - if _node.balancingStrategy == "leastPing" then + if _node.balancingStrategy == "leastPing" or _node.balancingStrategy == "leastLoad" then if not observatory then observatory = { subjectSelector = { "blc-" }, @@ -685,6 +690,34 @@ function gen_config(var) return balancer, rule 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 + + if node.to_node then + local to_node = uci:get_all(appname, node.to_node) + if to_node then + local to_outbound = gen_outbound(nil, to_node) + if to_outbound then + if shunt_rule_name then + to_outbound.tag = outbound.tag + outbound.tag = node[".name"] + else + to_outbound.tag = outbound.tag .. " -> " .. to_outbound.tag + end + + to_outbound.proxySettings = { + tag = outbound.tag, + transportLayer = true + } + table.insert(outbounds_table, to_outbound) + default_outTag = to_outbound.tag + end + end + end + return default_outTag + end + for k, v in pairs(nodes) do if server_host and server_port then v.address = server_host @@ -722,6 +755,7 @@ function gen_config(var) elseif preproxy_node and api.is_normal_node(preproxy_node) then local preproxy_outbound = gen_outbound(flag, preproxy_node, preproxy_tag, { fragment = xray_settings.fragment == "1" or nil }) if preproxy_outbound then + set_outbound_detour(preproxy_node, preproxy_outbound, outbounds, preproxy_tag) table.insert(outbounds, preproxy_outbound) else preproxy_enabled = false @@ -737,7 +771,7 @@ function gen_config(var) end end - local function gen_shunt_node(rule_name, _node_id, as_proxy) + 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 @@ -831,6 +865,7 @@ function gen_config(var) end local _outbound = gen_outbound(flag, _node, rule_name, proxy_table) if _outbound then + set_outbound_detour(_node, _outbound, outbounds, rule_name) table.insert(outbounds, _outbound) if proxy then preproxy_used = true end rule_outboundTag = rule_name @@ -991,39 +1026,39 @@ function gen_config(var) } } end - else - local outbound = nil - if node.protocol == "_iface" then - if node.iface then - outbound = { - protocol = "freedom", - tag = "outbound", - streamSettings = { - sockopt = { - mark = 255, - interface = node.iface - } + elseif node.protocol == "_iface" then + if node.iface then + local outbound = { + protocol = "freedom", + tag = "outbound", + streamSettings = { + sockopt = { + mark = 255, + interface = node.iface } } - sys.call("touch /tmp/etc/passwall2/iface/" .. node.iface) - end - else - outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil }) + } + table.insert(outbounds, outbound) + sys.call("touch /tmp/etc/passwall2/iface/" .. node.iface) + end + else + local outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil }) + if outbound then + local default_outTag = set_outbound_detour(node, outbound, outbounds) + table.insert(outbounds, outbound) + routing = { + domainStrategy = "AsIs", + domainMatcher = "hybrid", + rules = {} + } + table.insert(routing.rules, { + _flag = "default", + type = "field", + outboundTag = default_outTag, + network = "tcp,udp" + }) end - if outbound then table.insert(outbounds, outbound) end - routing = { - domainStrategy = "AsIs", - domainMatcher = "hybrid", - rules = {} - } - table.insert(routing.rules, { - _flag = "default", - type = "field", - outboundTag = node_id, - network = "tcp,udp" - }) end - end if remote_dns_udp_server 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 fb8fb776..f2c324a3 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 @@ -225,6 +225,10 @@ local api = require "luci.passwall2.api" if (v_transport === "ws") { info.host = opt.get(dom_prefix + "ws_host").value; info.path = opt.get(dom_prefix + "ws_path").value; + if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) { + var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value; + info.path = info.path + "?ed=" + ws_maxEarlyData; + } } else if (v_transport === "h2") { info.host = opt.get(dom_prefix + "h2_host").value; info.path = opt.get(dom_prefix + "h2_path").value; @@ -269,6 +273,10 @@ local api = require "luci.passwall2.api" if (v_transport === "ws") { params += opt.query("host", dom_prefix + "ws_host"); params += opt.query("path", dom_prefix + "ws_path"); + if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) { + var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value; + params += "?ed=" + ws_maxEarlyData; + } } else if (v_transport === "h2") { v_transport = "http"; params += opt.query("host", dom_prefix + "h2_host"); @@ -720,6 +728,24 @@ local api = require "luci.passwall2.api" } else if (ssm.net === "ws") { opt.set(dom_prefix + 'ws_host', ssm.host); opt.set(dom_prefix + 'ws_path', ssm.path); + if (dom_prefix == "singbox_" && ssm.path && ssm.path.length > 1) { + var ws_path_params = {}; + var ws_path_dat = ssm.path.split('?'); + var ws_path = ws_path_dat[0]; + var ws_path_params = {}; + var ws_path_params_array = ws_path_dat[1].split('&'); + for (i = 0; i < ws_path_params_array.length; i++) { + var kv = ws_path_params_array[i].split('='); + ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || ''); + } + + if (ws_path_params.ed) { + opt.set(dom_prefix + 'ws_path', ws_path); + opt.set(dom_prefix + 'ws_enableEarlyData', true); + opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed); + opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol'); + } + } } else if (ssm.net === "h2") { opt.set(dom_prefix + 'h2_host', ssm.host); opt.set(dom_prefix + 'h2_path', ssm.path); @@ -809,6 +835,24 @@ local api = require "luci.passwall2.api" } else if (queryParam.type === "ws") { opt.set(dom_prefix + 'ws_host', queryParam.host || ""); opt.set(dom_prefix + 'ws_path', queryParam.path || ""); + if (dom_prefix == "singbox_" && queryParam.path && queryParam.path.length > 1) { + var ws_path_params = {}; + var ws_path_dat = queryParam.path.split('?'); + var ws_path = ws_path_dat[0]; + var ws_path_params = {}; + var ws_path_params_array = ws_path_dat[1].split('&'); + for (i = 0; i < ws_path_params_array.length; i++) { + var kv = ws_path_params_array[i].split('='); + ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || ''); + } + + if (ws_path_params.ed) { + opt.set(dom_prefix + 'ws_path', ws_path); + opt.set(dom_prefix + 'ws_enableEarlyData', true); + opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed); + opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol'); + } + } } else if (queryParam.type === "h2" || queryParam.type === "http") { opt.set(dom_prefix + 'h2_host', queryParam.host || ""); opt.set(dom_prefix + 'h2_path', queryParam.path || ""); diff --git a/luci-app-passwall2/po/zh-cn/passwall2.po b/luci-app-passwall2/po/zh-cn/passwall2.po index 4cec1a69..7b6d6f0f 100644 --- a/luci-app-passwall2/po/zh-cn/passwall2.po +++ b/luci-app-passwall2/po/zh-cn/passwall2.po @@ -1482,3 +1482,9 @@ msgstr "分片间隔(ms)" msgid "If is domain name, The requested domain name will be resolved to IP before connect." msgstr "如果是域名,域名将在请求发出之前解析为 IP。" + +msgid "Landing node" +msgstr "落地节点" + +msgid "Only support a layer of proxy." +msgstr "仅支持一层代理。" diff --git a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua index 8627c643..b3a1217d 100755 --- a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua +++ b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua @@ -401,6 +401,21 @@ local function processData(szType, content, add_mode, add_from) if info.net == 'ws' then result.ws_host = info.host result.ws_path = info.path + if result.type == "sing-box" and info.path then + local ws_path_dat = split(info.path, "?") + local ws_path = ws_path_dat[1] + local ws_path_params = {} + for _, v in pairs(split(ws_path_dat[2], '&')) do + local t = split(v, '=') + ws_path_params[t[1]] = t[2] + end + if ws_path_params.ed and tonumber(ws_path_params.ed) then + result.ws_path = ws_path + result.ws_enableEarlyData = "1" + result.ws_maxEarlyData = tonumber(ws_path_params.ed) + result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol" + end + end end if info.net == 'h2' then result.h2_host = info.host @@ -672,6 +687,21 @@ local function processData(szType, content, add_mode, add_from) if params.type == 'ws' then result.ws_host = params.host result.ws_path = params.path + if result.type == "sing-box" and params.path then + local ws_path_dat = split(params.path, "?") + local ws_path = ws_path_dat[1] + local ws_path_params = {} + for _, v in pairs(split(ws_path_dat[2], '&')) do + local t = split(v, '=') + ws_path_params[t[1]] = t[2] + end + if ws_path_params.ed and tonumber(ws_path_params.ed) then + result.ws_path = ws_path + result.ws_enableEarlyData = "1" + result.ws_maxEarlyData = tonumber(ws_path_params.ed) + result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol" + end + end end if params.type == 'h2' or params.type == 'http' then params.type = "h2" diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/check.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/check.htm index 5f6a673a..4a16adce 100644 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/check.htm +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/check.htm @@ -13,9 +13,9 @@ if (s) { if (rv.ret=="0") - s.innerHTML =""+"<%:Connect OK%>"+""; + s.innerHTML =""+"<%:Connect OK%>"+""; else - s.innerHTML =""+"<%:Connect Error%>"+""; + s.innerHTML =""+"<%:Connect Error%>"+""; } btn.disabled = false; btn.value = '<%:Check Connect%>'; diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm index da89fd53..ea4113d8 100644 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/refresh.htm @@ -15,13 +15,13 @@ switch (rv.ret) { case 0: - s.innerHTML =""+"<%:Refresh OK!%> "+"<%:Total Records:%>"+rv.retcount+""; + s.innerHTML =""+"<%:Refresh OK!%> "+"<%:Total Records:%>"+rv.retcount+""; break; case 1: - s.innerHTML =""+"<%:No new data!%> "+""; + s.innerHTML =""+"<%:No new data!%> "+""; break; default: - s.innerHTML =""+"<%:Refresh Error!%> "+""; + s.innerHTML =""+"<%:Refresh Error!%> "+""; break; } } diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm index 1882ac56..ff0c4860 100644 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/reset.htm @@ -7,7 +7,7 @@ return false; } if (reset != "reset") { - s.innerHTML = "<%:The content entered is incorrect!%>"; + s.innerHTML = "<%:The content entered is incorrect!%>"; return false; } btn.disabled = true; @@ -15,7 +15,7 @@ murl=dataname; XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr","reset")%>', { set:murl }, function(x,rv) { btn.value = '<%:Reset complete%>'; - s.innerHTML = "<%:Reset complete%>"; + s.innerHTML = "<%:Reset complete%>"; }); return false; } diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm index 259cb7ff..d0b77f10 100644 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/server_list.htm @@ -18,7 +18,7 @@ const wsPath = wsPaths[index]; const tls = tlss[index]; if (!dom) res() - port.innerHTML = 'connect'; + port.innerHTML = 'connect'; XHR.get('<%=luci.dispatcher.build_url("admin/services/shadowsocksr/ping")%>', { index, domain: dom.getAttribute("hint"), @@ -34,11 +34,11 @@ if (result.ping < 200) col = '#ff7700'; if (result.ping < 100) col = '#249400'; } - dom.innerHTML = `${(result.ping ? result.ping : "--") + " ms"}` + dom.innerHTML = `${(result.ping ? result.ping : "--") + " ms"}` if (result.socket) { - port.innerHTML = 'ok'; + port.innerHTML = 'ok'; } else { - port.innerHTML = 'fail'; + port.innerHTML = 'fail'; } res(); }); diff --git a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm index 5eecbee6..479d7e2f 100644 --- a/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm +++ b/luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm @@ -69,9 +69,9 @@ function export_ssr_url(btn, urlname, sid) { textarea.select(); try { document.execCommand("copy"); // Security exception may be thrown by some browsers. - s.innerHTML = "<%:Copy SSR to clipboard successfully.%>"; + s.innerHTML = "<%:Copy SSR to clipboard successfully.%>"; } catch (ex) { - s.innerHTML = "<%:Unable to copy SSR to clipboard.%>"; + s.innerHTML = "<%:Unable to copy SSR to clipboard.%>"; } finally { document.body.removeChild(textarea); } @@ -83,7 +83,7 @@ function import_ssr_url(btn, urlname, sid) { if (!s) return false; var ssrurl = prompt("<%:Paste sharing link here%>", ""); if (ssrurl == null || ssrurl == "") { - s.innerHTML = "<%:User cancelled.%>"; + s.innerHTML = "<%:User cancelled.%>"; return false; } s.innerHTML = ""; @@ -118,7 +118,7 @@ function import_ssr_url(btn, urlname, sid) { document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = params.get("insecure") ? true : false; document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : ""; - s.innerHTML = "<%:Import configuration information successfully.%>"; + s.innerHTML = "<%:Import configuration information successfully.%>"; return false; case "ss": var url0, param = ""; @@ -164,7 +164,7 @@ function import_ssr_url(btn, urlname, sid) { if (param != undefined) { document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = decodeURI(param); } - s.innerHTML = "<%:Import configuration information successfully.%>"; + s.innerHTML = "<%:Import configuration information successfully.%>"; } else { var sstr = b64decsafe(url0); document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = ssu[0]; @@ -179,7 +179,7 @@ function import_ssr_url(btn, urlname, sid) { if (param != undefined) { document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = decodeURI(param); } - s.innerHTML = "<%:Import configuration information successfully.%>"; + s.innerHTML = "<%:Import configuration information successfully.%>"; } return false; case "ssr": @@ -212,7 +212,7 @@ function import_ssr_url(btn, urlname, sid) { document.getElementsByName('cbid.shadowsocksr.' + sid + '.protocol_param')[0].value = dictvalue(pdict, 'protoparam'); var rem = pdict['remarks']; if (typeof (rem) != 'undefined' && rem != '' && rem.length > 0) document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = b64decutf8safe(rem); - s.innerHTML = "<%:Import configuration information successfully.%>"; + s.innerHTML = "<%:Import configuration information successfully.%>"; return false; case "trojan": try { @@ -234,7 +234,7 @@ function import_ssr_url(btn, urlname, sid) { document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event); document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = url.searchParams.get("sni"); - s.innerHTML = "<%:Import configuration information successfully.%>"; + s.innerHTML = "<%:Import configuration information successfully.%>"; return false; case "vmess": var sstr = b64DecodeUnicode(ssu[1]); @@ -287,7 +287,7 @@ function import_ssr_url(btn, urlname, sid) { } document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].checked = true; document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].dispatchEvent(event); - s.innerHTML = "<%:Import configuration information successfully.%>"; + s.innerHTML = "<%:Import configuration information successfully.%>"; return false; case "vless": try { @@ -357,10 +357,10 @@ function import_ssr_url(btn, urlname, sid) { } break; } - s.innerHTML = "<%:Import configuration information successfully.%>"; + s.innerHTML = "<%:Import configuration information successfully.%>"; return false; default: - s.innerHTML = "<%:Invalid format.%>"; + s.innerHTML = "<%:Invalid format.%>"; return false; } }