diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua index 1d3e9addf..4fb6863d2 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/app_update.lua @@ -15,7 +15,7 @@ local k, v local com = require "luci.passwall.com" for k, v in pairs(com) do o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name)) - o.default = v.default_path or ("/usr/bin/"..k) + o.default = v.default_path or ("/usr/bin/" .. k) o.rmempty = false end diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/auto_switch.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/auto_switch.lua index 546170dff..519d0a7af 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/auto_switch.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/auto_switch.lua @@ -59,7 +59,7 @@ o = s:option(Flag, "restore_switch", "TCP " .. translate("Restore Switch"), tran o = s:option(ListValue, "shunt_logic", "TCP " .. translate("If the main node is V2ray/Xray shunt")) o:value("0", translate("Switch it")) o:value("1", translate("Applying to the default node")) -o:value("2", translate("Applying to the default preproxy node")) +o:value("2", translate("Applying to the preproxy node")) m:append(Template(appname .. "/auto_switch/footer")) diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua index a5588576b..e820cc902 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua @@ -100,75 +100,107 @@ if current_node and current_node ~= "" and current_node ~= "nil" then end tcp_node:value("nil", translate("Close")) +---- UDP Node +udp_node = s:taboption("Main", ListValue, "udp_node", "" .. translate("UDP Node") .. "") +udp_node:value("nil", translate("Close")) +udp_node:value("tcp", translate("Same as the tcp node")) + -- 分流 if (has_v2ray or has_xray) and #nodes_table > 0 then local normal_list = {} local shunt_list = {} for k, v in pairs(nodes_table) do - if v.node_type == "normal" then + if v.node_type == "normal" or v.protocol == "_balancing" then normal_list[#normal_list + 1] = v end if v.protocol and v.protocol == "_shunt" then shunt_list[#shunt_list + 1] = v end end + + local function get_cfgvalue(shunt_node_id, rule_id) + return function(self, section) + return m:get(shunt_node_id, rule_id) or "nil" + end + end + local function get_write(shunt_node_id, rule_id) + return function(self, section, value) + m:set(shunt_node_id, rule_id, value) + end + end + for k, v in pairs(shunt_list) do + local vid = v.id:sub(1, 8) + o = s:taboption("Main", ListValue, vid .. "-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("tcp_node", v.id) + o:value("nil", translate("Close")) + for k1, v1 in pairs(normal_list) do + o:value(v1.id, v1.remark) + end + o.cfgvalue = get_cfgvalue(v.id, "main_node") + o.write = get_write(v.id, "main_node") + + local dialerProxy = s:taboption("Main", Flag, vid .. "-dialerProxy", translate("dialerProxy")) + if v.type == "Xray" then + dialerProxy:depends("tcp_node", v.id) + else --主设置界面没有type判断,只能判断本分流节点类型是Xray就添加对本分流节点的依赖,但不是的话就没有依赖,会全部显示,所以添加一个不存在的依赖以达到隐藏的目的 + dialerProxy:depends("tcp_node", "xray_shunt") + end + dialerProxy.cfgvalue = get_cfgvalue(v.id, "dialerProxy") + dialerProxy.write = get_write(v.id, "dialerProxy") + uci:foreach(appname, "shunt_rules", function(e) local id = e[".name"] + local node_option = vid .. "-" .. id .. "_node" if id and e.remarks then - o = s:taboption("Main", ListValue, v.id .. "." .. id .. "_node", string.format('* %s', api.url("shunt_rules", id), e.remarks)) + o = s:taboption("Main", ListValue, node_option, string.format('* %s', api.url("shunt_rules", id), e.remarks)) o:depends("tcp_node", v.id) o:value("nil", translate("Close")) o:value("_default", translate("Default")) o:value("_direct", translate("Direct Connection")) o:value("_blackhole", translate("Blackhole")) + local pt = s:taboption("Main", ListValue, vid .. "-".. id .. "_proxy_tag", string.format('* %s', e.remarks .. " " .. translate("Preproxy"))) + pt:value("nil", translate("Close")) + pt:value("main", translate("Preproxy Node")) + pt.default = "nil" for k1, v1 in pairs(normal_list) do - o:value(v1.id, v1["remark"]) - end - o.cfgvalue = function(self, section) - return m:get(v.id, id) or "nil" - end - o.write = function(self, section, value) - m:set(v.id, id, value) + o:value(v1.id, v1.remark) + if v1.protocol ~= "_balancing" then + pt:depends(node_option, v1.id) + end end + o.cfgvalue = get_cfgvalue(v.id, id) + o.write = get_write(v.id, id) + pt.cfgvalue = get_cfgvalue(v.id, id .. "_proxy_tag") + pt.write = get_write(v.id, id .. "_proxy_tag") end end) local id = "default_node" - o = s:taboption("Main", ListValue, v.id .. "." .. id, string.format('* %s', translate("Default"))) + o = s:taboption("Main", ListValue, vid .. "-" .. id, string.format('* %s', translate("Default"))) o:depends("tcp_node", v.id) o:value("_direct", translate("Direct Connection")) o:value("_blackhole", translate("Blackhole")) for k1, v1 in pairs(normal_list) do o:value(v1.id, v1["remark"]) end - o.cfgvalue = function(self, section) - return m:get(v.id, id) or "nil" - end - o.write = function(self, section, value) - m:set(v.id, id, value) - end + o.cfgvalue = get_cfgvalue(v.id, id) + o.write = get_write(v.id, id) - local id = "main_node" - o = s:taboption("Main", ListValue, v.id .. "." .. id, string.format('* %s', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node.")) - o:depends("tcp_node", v.id) - o:value("nil", translate("Close")) + local id = "default_proxy_tag" + o = s:taboption("Main", ListValue, vid .. "-" .. id, string.format('* %s', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node.")) for k1, v1 in pairs(normal_list) do - o:value(v1.id, v1["remark"]) - end - o.cfgvalue = function(self, section) - return m:get(v.id, id) or "nil" - end - o.write = function(self, section, value) - m:set(v.id, id, value) + if v1.protocol ~= "_balancing" then + o:depends(vid .. "-default_node", v1.id) + end end + o:value("nil", translate("Close")) + o:value("main", translate("Preproxy Node")) + o.cfgvalue = get_cfgvalue(v.id, id) + o.write = get_write(v.id, id) end end -udp_node = s:taboption("Main", ListValue, "udp_node", "" .. translate("UDP Node") .. "") -udp_node:value("nil", translate("Close")) -udp_node:value("tcp", translate("Same as the tcp node")) - tcp_node_socks_port = s:taboption("Main", Value, "tcp_node_socks_port", translate("TCP Node") .. " Socks " .. translate("Listen Port")) tcp_node_socks_port.default = 1070 tcp_node_socks_port.datatype = "port" diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua index df27a5676..0a56a2acc 100644 --- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua +++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua @@ -46,7 +46,7 @@ local x_ss_encrypt_method_list = { "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" } -local security_list = {"none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero"} +local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" } local header_type_list = { "none", "srtp", "utp", "wechat-video", "dtls", "wireguard" @@ -133,9 +133,10 @@ iface:depends("protocol", "_iface") local nodes_table = {} for k, e in ipairs(api.get_valid_nodes()) do - if e.node_type == "normal" then + if e.node_type == "normal" or e.protocol == "_balancing" then nodes_table[#nodes_table + 1] = { id = e[".name"], + protocol = e["protocol"], remarks = e["remark"] } end @@ -145,17 +146,39 @@ end local balancing_node = s:option(DynamicList, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, document")) for k, v in pairs(nodes_table) do balancing_node:value(v.id, v.remarks) end balancing_node:depends("protocol", "_balancing") + local balancingStrategy = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy")) balancingStrategy:depends("protocol", "_balancing") balancingStrategy:value("random") balancingStrategy:value("leastPing") balancingStrategy.default = "random" +-- 探测地址 +local useCustomProbeUrl = s:option(Flag, "useCustomProbeUrl", translate("Use Custome Probe URL")) +useCustomProbeUrl:depends("balancingStrategy", "leastPing") +useCustomProbeUrl.description = "By default the built-in probe URL will be used, enable this option to use a custom probe URL." +local probeUrl = s:option(Value, "probeUrl", translate("Probe URL")) +probeUrl:depends("useCustomProbeUrl", true) +probeUrl.default = "https://www.google.com/generate_204" +probeUrl.description = translate("The URL used to detect the connection status.") +-- 探测间隔 local probeInterval = s:option(Value, "probeInterval", translate("Probe Interval")) probeInterval:depends("balancingStrategy", "leastPing") probeInterval.default = "1m" probeInterval.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.") -- 分流 +if #nodes_table > 0 then + o = s:option(ListValue, "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("protocol", "_shunt") + o:value("nil", translate("Close")) + dialerProxy = s:option(Flag, "dialerProxy", translate("dialerProxy")) + dialerProxy:depends({ type = "Xray", protocol = "_shunt" , }) + for k, v in pairs(nodes_table) do + o:value(v.id, v.remarks) + --dialerProxy:depends({ type = "Xray", main_node = v.id }) + end + o.default = "nil" +end uci:foreach(appname, "shunt_rules", function(e) if e[".name"] and e.remarks then o = s:option(ListValue, e[".name"], string.format('* %s', api.url("shunt_rules", e[".name"]), e.remarks)) @@ -166,15 +189,15 @@ uci:foreach(appname, "shunt_rules", function(e) o:depends("protocol", "_shunt") if #nodes_table > 0 then - _proxy_tag = s:option(ListValue, e[".name"] .. "_proxy_tag", string.format('* %s', e.remarks .. " " .. translate("Preproxy"))) - _proxy_tag:value("nil", translate("Close")) - _proxy_tag:value("default", translate("Default")) - _proxy_tag:value("main", translate("Default Preproxy")) - _proxy_tag.default = "nil" - + local pt = s:option(ListValue, e[".name"] .. "_proxy_tag", string.format('* %s', e.remarks .. " " .. translate("Preproxy"))) + pt:value("nil", translate("Close")) + pt:value("main", translate("Preproxy Node")) + pt.default = "nil" for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) - _proxy_tag:depends(e[".name"], v.id) + if v.protocol ~= "_balancing" then + pt:depends(e[".name"], v.id) + end end end end @@ -194,26 +217,26 @@ for k, v in pairs(nodes_table) do default_node:value(v.id, v.remarks) end default_node:depends("protocol", "_shunt") if #nodes_table > 0 then - o = s:option(ListValue, "main_node", string.format('* %s', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node.")) - o:value("nil", translate("Close")) + local dpt = s:option(ListValue, "default_proxy_tag", string.format('* %s', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node.")) + dpt:value("nil", translate("Close")) + dpt:value("main", translate("Preproxy Node")) + dpt.default = "nil" for k, v in pairs(nodes_table) do - o:value(v.id, v.remarks) - o:depends("default_node", v.id) + if v.protocol ~= "_balancing" then + dpt:depends("default_node", v.id) + end end end -dialerProxy = s:option(Flag, "dialerProxy", translate("dialerProxy")) -dialerProxy:depends({ type = "Xray", protocol = "_shunt"}) - domainStrategy = s:option(ListValue, "domainStrategy", translate("Domain Strategy")) domainStrategy:value("AsIs") domainStrategy:value("IPIfNonMatch") domainStrategy:value("IPOnDemand") domainStrategy.default = "IPOnDemand" domainStrategy.description = "
" + .. "
  • " .. translate("'IPIfNonMatch': When no rule matches current domain, resolves it into IP addresses (A or AAAA records) and try all rules again.") + .. "
  • " .. translate("'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately.") + .. "
  • " domainStrategy:depends("protocol", "_shunt") domainMatcher = s:option(ListValue, "domainMatcher", translate("Domain matcher")) @@ -808,7 +831,7 @@ h2_path:depends("transport", "h2") h2_path:depends("ss_transport", "h2") h2_health_check = s:option(Flag, "h2_health_check", translate("Health check")) -h2_health_check:depends({ type = "Xray", transport = "h2"}) +h2_health_check:depends({ type = "Xray", transport = "h2" }) h2_read_idle_timeout = s:option(Value, "h2_read_idle_timeout", translate("Idle timeout")) h2_read_idle_timeout.default = "10" @@ -843,10 +866,10 @@ grpc_serviceName:depends("transport", "grpc") grpc_mode = s:option(ListValue, "grpc_mode", "gRPC " .. translate("Transfer mode")) grpc_mode:value("gun") grpc_mode:value("multi") -grpc_mode:depends({ type = "Xray", transport = "grpc"}) +grpc_mode:depends({ type = "Xray", transport = "grpc" }) grpc_health_check = s:option(Flag, "grpc_health_check", translate("Health check")) -grpc_health_check:depends({ type = "Xray", transport = "grpc"}) +grpc_health_check:depends({ type = "Xray", transport = "grpc" }) grpc_idle_timeout = s:option(Value, "grpc_idle_timeout", translate("Idle timeout")) grpc_idle_timeout.default = "10" @@ -862,7 +885,7 @@ grpc_permit_without_stream:depends("grpc_health_check", true) grpc_initial_windows_size = s:option(Value, "grpc_initial_windows_size", translate("Initial Windows Size")) grpc_initial_windows_size.default = "0" -grpc_initial_windows_size:depends({ type = "Xray", transport = "grpc"}) +grpc_initial_windows_size:depends({ type = "Xray", transport = "grpc" }) -- [[ Trojan-Go Shadowsocks2 ]] -- ss_aead = s:option(Flag, "ss_aead", translate("Shadowsocks secondary encryption")) diff --git a/luci-app-passwall/luasrc/passwall/server_app.lua b/luci-app-passwall/luasrc/passwall/server_app.lua index d93de0f3f..9d1382599 100644 --- a/luci-app-passwall/luasrc/passwall/server_app.lua +++ b/luci-app-passwall/luasrc/passwall/server_app.lua @@ -44,14 +44,14 @@ local function ln_run(s, d, command, output) end d = TMP_BIN_PATH .. "/" .. d cmd(string.format('[ ! -f "%s" ] && ln -s %s %s 2>/dev/null', d, s, d)) - return string.format("%s >%s 2>&1 &", d .. " " ..command, output) + return string.format("%s >%s 2>&1 &", d .. " " .. command, output) end local function gen_include() cmd(string.format("echo '#!/bin/sh' > /tmp/etc/%s.include", CONFIG)) if nft_flag == "1" then cmd("echo \"\" > " .. CONFIG_PATH .. "/" .. CONFIG .. ".nft") - local nft_cmd="for chain in $(nft -a list chains |grep -E \"chain PSW-SERVER\" |awk -F ' ' '{print$2}'); do\n nft list chain inet fw4 ${chain} >> " .. CONFIG_PATH .. "/" .. CONFIG .. ".nft\n done" + local nft_cmd = "for chain in $(nft -a list chains |grep -E \"chain PSW-SERVER\" |awk -F ' ' '{print$2}'); do\n nft list chain inet fw4 ${chain} >> " .. CONFIG_PATH .. "/" .. CONFIG .. ".nft\n done" cmd(nft_cmd) end local function extract_rules(n, a) @@ -215,7 +215,7 @@ local function stop() ip6t("-F PSW-SERVER 2>/dev/null") ip6t("-X PSW-SERVER 2>/dev/null") else - nft_cmd="handles=$(nft -a list chain inet fw4 input | grep -E \"PSW-SERVER\" | awk -F '# handle ' '{print$2}')\n for handle in $handles; do\n nft delete rule inet fw4 input handle ${handle} 2>/dev/null\n done" + local nft_cmd = "handles=$(nft -a list chain inet fw4 input | grep -E \"PSW-SERVER\" | awk -F '# handle ' '{print$2}')\n for handle in $handles; do\n nft delete rule inet fw4 input handle ${handle} 2>/dev/null\n done" cmd(nft_cmd) cmd("nft flush chain inet fw4 PSW-SERVER 2>/dev/null") cmd("nft delete chain inet fw4 PSW-SERVER 2>/dev/null") diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua index bf3c6c2de..f54feb248 100644 --- a/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -90,9 +90,8 @@ function gen_outbound(flag, node, tag, proxy_table) new_port, --socks port config_file, --config file (proxy == 1 and relay_port) and tostring(relay_port) or "" --relay port - ) ) - ) + )) node = {} node.protocol = "socks" node.transport = "tcp" @@ -257,7 +256,7 @@ function gen_config_server(node) local settings = nil local routing = nil local outbounds = { - {protocol = "freedom", tag = "direct"}, {protocol = "blackhole", tag = "blocked"} + { protocol = "freedom", tag = "direct" }, { protocol = "blackhole", tag = "blocked" } } if node.protocol == "vmess" or node.protocol == "vless" then @@ -596,184 +595,250 @@ function gen_config(var) end end - if node.protocol == "_shunt" then - local rules = {} + local function get_balancer_tag(_node_id) + return "balancer-" .. _node_id:sub(1, 8) + end - local default_node_id = node.default_node or "_direct" - local default_outboundTag - if default_node_id == "_direct" then - default_outboundTag = "direct" - elseif default_node_id == "_blackhole" then - default_outboundTag = "blackhole" - else - local default_node = uci:get_all(appname, default_node_id) - local main_node_id = node.main_node or "nil" - local proxy = 0 - local proxy_tag - if main_node_id ~= "nil" then - local main_node = uci:get_all(appname, main_node_id) - if main_node and api.is_normal_node(main_node) and main_node_id ~= default_node_id then - local main_node_outbound = gen_outbound(flag, main_node, "main") - if main_node_outbound then - table.insert(outbounds, main_node_outbound) - proxy = 1 - proxy_tag = "main" - local pre_proxy = nil - if default_node.type ~= "V2ray" and default_node.type ~= "Xray" then - pre_proxy = true - end - if default_node.type == "Xray" and default_node.tlsflow == "xtls-rprx-vision" then - pre_proxy = true - end - if pre_proxy then - proxy_tag = nil - new_port = get_new_port() - table.insert(inbounds, { - tag = "proxy_default", - listen = "127.0.0.1", - port = new_port, - protocol = "dokodemo-door", - settings = {network = "tcp,udp", address = default_node.address, port = tonumber(default_node.port)} - }) - if default_node.tls_serverName == nil then - default_node.tls_serverName = default_node.address - end - default_node.address = "127.0.0.1" - default_node.port = new_port - table.insert(rules, 1, { - type = "field", - inboundTag = {"proxy_default"}, - outboundTag = "main" - }) - end - end + local function gen_balancer(_node, loopbackTag) + local blc_nodes = _node.balancing_node + local length = #blc_nodes + local valid_nodes = {} + for i = 1, length do + local blc_node_id = blc_nodes[i] + local blc_node_tag = "blc-" .. blc_node_id:sub(1, 8) + local is_new_blc_node = true + for _, outbound in ipairs(outbounds) do + if outbound.tag == blc_node_tag then + is_new_blc_node = false + valid_nodes[#valid_nodes + 1] = blc_node_tag + break end end - if default_node and api.is_normal_node(default_node) then - local default_outbound = gen_outbound(flag, default_node, "default", { proxy = proxy, tag = proxy_tag, dialerProxy = node.dialerProxy }) - if default_outbound then - table.insert(outbounds, default_outbound) - default_outboundTag = "default" + if is_new_blc_node then + local blc_node = uci:get_all(appname, blc_node_id) + local outbound = gen_outbound(flag, blc_node, blc_node_tag) + if outbound then + table.insert(outbounds, outbound) + valid_nodes[#valid_nodes + 1] = blc_node_tag end end end - uci:foreach(appname, "shunt_rules", function(e) - local name = e[".name"] - if name and e.remarks then - local _node_id = node[name] or "nil" - local proxy_tag = node[name .. "_proxy_tag"] or "nil" - local outboundTag - if _node_id == "_direct" then - outboundTag = "direct" - elseif _node_id == "_blackhole" then - outboundTag = "blackhole" - elseif _node_id == "_default" then - outboundTag = "default" - else - if _node_id ~= "nil" then - local _node = uci:get_all(appname, _node_id) - if _node and api.is_normal_node(_node) then - local new_outbound - for index, value in ipairs(outbounds) do - if value["_flag_tag"] == _node_id and value["_flag_proxy_tag"] == proxy_tag then - new_outbound = api.clone(value) - break - end - end - if new_outbound then - new_outbound["tag"] = name - table.insert(outbounds, new_outbound) - outboundTag = name - else - local pre_proxy = nil - if _node.type ~= "V2ray" and _node.type ~= "Xray" then - pre_proxy = true - end - if _node.type == "Xray" and _node.tlsflow == "xtls-rprx-vision" then - pre_proxy = true - end - if pre_proxy then - if proxy_tag ~= "nil" then - new_port = get_new_port() - table.insert(inbounds, { - tag = "proxy_" .. name, - listen = "127.0.0.1", - port = new_port, - protocol = "dokodemo-door", - settings = {network = "tcp,udp", address = _node.address, port = tonumber(_node.port)} - }) - if _node.tls_serverName == nil then - _node.tls_serverName = _node.address - end - _node.address = "127.0.0.1" - _node.port = new_port - table.insert(rules, 1, { - type = "field", - inboundTag = {"proxy_" .. name}, - outboundTag = proxy_tag - }) - end - end - local _outbound = gen_outbound(flag, _node, name, { proxy = (proxy_tag ~= "nil") and 1 or 0, tag = (proxy_tag ~= "nil") and proxy_tag or nil, dialerProxy = node.dialerProxy }) - if _outbound then - table.insert(outbounds, _outbound) - outboundTag = name - end + local balancer, rule + if #valid_nodes > 0 then + local balancerTag = get_balancer_tag(_node[".name"]) + balancer = { + tag = balancerTag, + selector = valid_nodes, + strategy = { type = _node.balancingStrategy or "random" } + } + if _node.balancingStrategy == "leastPing" then + if not observatory then + observatory = { + subjectSelector = { "blc-" }, + probeUrl = _node.useCustomProbeUrl == true and _node.probeUrl or nil, + probeInterval = _node.probeInterval or "1m", + enableConcurrency = node.type == "Xray" and true or nil --这里只判断顶层节点(分流总节点/单独的负载均衡节点)类型为Xray,就可以启用并发 + } + end + end + if loopbackTag and loopbackTag ~= "" then + local inboundTag = loopbackTag .. "-in" + table.insert(outbounds, { + protocol = "loopback", + tag = loopbackTag, + settings = { inboundTag = inboundTag } + }) + rule = { + type = "field", + inboundTag = { inboundTag }, + balancerTag = balancerTag + } + end + end + return balancer, rule + end + + if node.protocol == "_shunt" then + local rules = {} + local balancers = {} + + local preproxy_enabled = false + local preproxy_tag = "main" + local preproxy_node_id = node[preproxy_tag .. "_node"] or "nil" + + local function gen_shunt_node(rule_name, _node_id, as_proxy) + 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" + elseif _node_id == "_blackhole" then + rule_outboundTag = "blackhole" + elseif _node_id == "_default" and rule_name ~= "default" then + rule_outboundTag = "default" + 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 proxy_tag = node[rule_name .. "_proxy_tag"] or "nil" + if proxy_tag == preproxy_tag and not preproxy_enabled then proxy_tag = "nil" end + local proxy_node_id = proxy_tag ~= "nil" and node[proxy_tag .. "_node"] or "nil" --为了适配之前默认节点也可用作前置代理的写法,只设一个的话直接用preproxy_node_id + if _node_id == proxy_node_id then proxy_tag = "nil" end --规则启用了前置代理,但规则本身节点和前置代理节点是同一个,则前置代理设置无效 + local proxy_node = uci:get_all(appname, proxy_node_id) --前置代理节点 + local is_balancing_proxy + if proxy_node and proxy_node.protocol == "_balancing" then + is_balancing_proxy = true + local blc_nodes = proxy_node.balancing_node + for _, blc_node_id in ipairs(blc_nodes) do + if _node_id == blc_node_id then + proxy_tag = "nil" + break end end end - end - if outboundTag then - if outboundTag == "default" then - outboundTag = default_outboundTag + + local copied_outbound + for index, value in ipairs(outbounds) do + if value["_flag_tag"] == _node_id and value["_flag_proxy_tag"] == proxy_tag then + copied_outbound = api.clone(value) + break + end end - local protocols = nil - if e["protocol"] and e["protocol"] ~= "" then - protocols = {} - string.gsub(e["protocol"], '[^' .. " " .. ']+', function(w) - table.insert(protocols, w) - end) + if copied_outbound then + copied_outbound.tag = rule_name + table.insert(outbounds, copied_outbound) + rule_outboundTag = rule_name + else + if proxy_tag ~= "nil" then + local pre_proxy = nil + if _node.type ~= "V2ray" and _node.type ~= "Xray" then + pre_proxy = true + end + if _node.type == "Xray" and _node.tlsflow == "xtls-rprx-vision" then + pre_proxy = true + end + if pre_proxy then + new_port = get_new_port() + table.insert(inbounds, { + tag = "proxy_" .. rule_name, + listen = "127.0.0.1", + port = new_port, + protocol = "dokodemo-door", + settings = {network = "tcp,udp", address = _node.address, port = tonumber(_node.port)} + }) + if _node.tls_serverName == nil then + _node.tls_serverName = _node.address + end + _node.address = "127.0.0.1" + _node.port = new_port + table.insert(rules, 1, { + type = "field", + inboundTag = {"proxy_" .. rule_name}, + outboundTag = is_balancing_proxy and nil or proxy_tag, + balancerTag = is_balancing_proxy and get_balancer_tag(proxy_node_id) or nil + }) + end + end + local _outbound = gen_outbound(flag, _node, rule_name, { proxy = (proxy_tag ~= "nil") and 1 or 0, tag = (proxy_tag ~= "nil") and proxy_tag or nil, dialerProxy = node.dialerProxy }) + if _outbound then + table.insert(outbounds, _outbound) + if proxy_tag == preproxy_tag then preproxy_used = true end + rule_outboundTag = rule_name + end end - if e.domain_list then - local _domain = {} - string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w) - table.insert(_domain, w) - end) - table.insert(rules, { - type = "field", - outboundTag = outboundTag, - domain = _domain, - protocol = protocols - }) + elseif _node.protocol == "_balancing" then + local is_new_balancer = true + for _, v in ipairs(balancers) do + if v["_flag_tag"] == _node_id then + is_new_balancer = false + rule_balancerTag = v.tag + break + end end - if e.ip_list then - local _ip = {} - string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w) - table.insert(_ip, w) - end) - table.insert(rules, { - type = "field", - outboundTag = outboundTag, - ip = _ip, - protocol = protocols - }) - end - if not e.domain_list and not e.ip_list and protocols then - table.insert(rules, { - type = "field", - outboundTag = outboundTag, - protocol = protocols - }) + if is_new_balancer then --注释掉的是给需要用作前置代理的balancer生成等效OutboundTag(loopback + 规则路由至)的代码,可能用上 + --local loopbackTag = as_proxy == true and rule_name or nil + local balancer = gen_balancer(_node) --local balancer, rule = gen_balancer(_node) + if balancer then + table.insert(balancers, balancer) + --if rule then table.insert(rules, rule) end + rule_balancerTag = balancer.tag + end end end end + return rule_outboundTag, rule_balancerTag + end + + --[[此处只要前置代理设置选择了节点,即使全部规则都没使用,仍会先尝试生成,生成有效配置才真正开启功能,会造成配置文件里面会有多余未使用的outbound, + 如果放到最后,判断节点有使用前置代理才生成又不能提前检测前置代理节点能否正确生成配置项,主要是负载均衡类型节点不好处理,生成的配置项多, + 只能先读取分流规则和默认的 proxy_tag,有启用前置代理的,就生成前置代理配置]] + if preproxy_node_id ~= "nil" then + local preproxy_node = uci:get_all(appname, preproxy_node_id) + if preproxy_node and api.is_normal_node(preproxy_node) then + local preproxy_outbound = gen_outbound(flag, preproxy_node, preproxy_tag) + if preproxy_outbound then + table.insert(outbounds, preproxy_outbound) + preproxy_enabled = true + end + elseif preproxy_node and preproxy_node.protocol == "_balancing" then + local preproxy_balancer, preproxy_rule = gen_balancer(preproxy_node, preproxy_tag) + if preproxy_balancer and preproxy_rule then + table.insert(balancers, preproxy_balancer) + table.insert(rules, preproxy_rule) + preproxy_enabled = true + end + end + end + + local default_node_id = node.default_node or "_direct" + local default_outboundTag, default_balancerTag = gen_shunt_node("default", default_node_id) + + uci:foreach(appname, "shunt_rules", function(e) + local outboundTag, balancerTag = gen_shunt_node(e[".name"]) + if outboundTag or balancerTag and e.remarks then + if outboundTag == "default" then + outboundTag = default_outboundTag + end + local protocols = nil + if e["protocol"] and e["protocol"] ~= "" then + protocols = {} + string.gsub(e["protocol"], '[^' .. " " .. ']+', function(w) + table.insert(protocols, w) + end) + end + local _domain = nil + if e.domain_list then + _domain = {} + string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w) + table.insert(_domain, w) + end) + end + local _ip = nil + if e.ip_list then + _ip = {} + string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w) + table.insert(_ip, w) + end) + end + table.insert(rules, { + type = "field", + outboundTag = outboundTag, + balancerTag = balancerTag, + domain = _domain, + ip = _ip, + protocol = protocols + }) + end end) - if default_outboundTag then + if default_outboundTag or default_balancerTag then table.insert(rules, { type = "field", outboundTag = default_outboundTag, + balancerTag = default_balancerTag, network = "tcp,udp" }) end @@ -781,31 +846,16 @@ function gen_config(var) routing = { domainStrategy = node.domainStrategy or "AsIs", domainMatcher = node.domainMatcher or "hybrid", + balancers = #balancers > 0 and balancers or nil, rules = rules } elseif node.protocol == "_balancing" then if node.balancing_node then - local nodes = node.balancing_node - local length = #nodes - for i = 1, length do - local node = uci:get_all(appname, nodes[i]) - local outbound = gen_outbound(flag, node) - if outbound then table.insert(outbounds, outbound) end - end - if node.balancingStrategy == "leastPing" then - observatory = { - subjectSelector = nodes, - probeInterval = node.probeInterval or "1m" - } - end + local balancer = gen_balancer(node) routing = { - balancers = {{ - tag = "balancer", - selector = nodes, - strategy = {type = node.balancingStrategy or "random"} - }}, + balancers = { balancer }, rules = { - {type = "field", network = "tcp,udp", balancerTag = "balancer"} + { type = "field", network = "tcp,udp", balancerTag = balancer.tag } } } end diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po index f525dcfe5..dabe953ba 100644 --- a/luci-app-passwall/po/zh-cn/passwall.po +++ b/luci-app-passwall/po/zh-cn/passwall.po @@ -352,6 +352,18 @@ msgstr "V2ray 负载均衡" msgid "Balancing Strategy" msgstr "负载均衡策略" +msgid "Use Custome Probe URL" +msgstr "使用自定义探测网址" + +msgid "By default the built-in probe URL will be used, enable this option to use a custom probe URL." +msgstr "默认使用内置的探测网址,启用此选项以使用自定义探测网址。" + +msgid "Probe URL" +msgstr "探测网址" + +msgid "The URL used to detect the connection status." +msgstr "用于检测连接状态的网址。" + msgid "Probe Interval" msgstr "探测间隔" @@ -370,6 +382,12 @@ msgstr "V2ray 分流" msgid "Preproxy" msgstr "前置代理" +msgid "Preproxy Node" +msgstr "前置代理节点" + +msgid "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." +msgstr "设置用作前置代理的节点。每条规则(包括默认)都有独立开关控制本规则是否使用前置代理。" + msgid "Direct Connection" msgstr "直连" @@ -661,8 +679,8 @@ msgstr "切掉它" msgid "Applying to the default node" msgstr "应用于默认节点" -msgid "Applying to the default preproxy node" -msgstr "应用于默认前置节点" +msgid "Applying to the preproxy node" +msgstr "应用于前置代理节点" msgid "Add nodes to the standby node list by keywords" msgstr "通过关键字添加节点到备用节点列表"