luci: shunt added xray balancer support

- optimized pre-proxy settings (https://github.com/xiaorouji/openwrt-passwall/issues/2234)
This commit is contained in:
Tianhe Y 2023-03-31 13:44:00 +08:00 committed by sbwml
parent eadb44a03a
commit ee7ded80e5
7 changed files with 363 additions and 240 deletions

View File

@ -15,7 +15,7 @@ local k, v
local com = require "luci.passwall.com" local com = require "luci.passwall.com"
for k, v in pairs(com) do for k, v in pairs(com) do
o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name)) 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 o.rmempty = false
end end

View File

@ -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 = s:option(ListValue, "shunt_logic", "TCP " .. translate("If the main node is V2ray/Xray shunt"))
o:value("0", translate("Switch it")) o:value("0", translate("Switch it"))
o:value("1", translate("Applying to the default node")) 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")) m:append(Template(appname .. "/auto_switch/footer"))

View File

@ -100,75 +100,107 @@ if current_node and current_node ~= "" and current_node ~= "nil" then
end end
tcp_node:value("nil", translate("Close")) tcp_node:value("nil", translate("Close"))
---- UDP Node
udp_node = s:taboption("Main", ListValue, "udp_node", "<a style='color: red'>" .. translate("UDP Node") .. "</a>")
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 if (has_v2ray or has_xray) and #nodes_table > 0 then
local normal_list = {} local normal_list = {}
local shunt_list = {} local shunt_list = {}
for k, v in pairs(nodes_table) do 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 normal_list[#normal_list + 1] = v
end end
if v.protocol and v.protocol == "_shunt" then if v.protocol and v.protocol == "_shunt" then
shunt_list[#shunt_list + 1] = v shunt_list[#shunt_list + 1] = v
end end
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 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('<a style="color:red">%s</a>', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) 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) uci:foreach(appname, "shunt_rules", function(e)
local id = e[".name"] local id = e[".name"]
local node_option = vid .. "-" .. id .. "_node"
if id and e.remarks then if id and e.remarks then
o = s:taboption("Main", ListValue, v.id .. "." .. id .. "_node", string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", id), e.remarks)) o = s:taboption("Main", ListValue, node_option, string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", id), e.remarks))
o:depends("tcp_node", v.id) o:depends("tcp_node", v.id)
o:value("nil", translate("Close")) o:value("nil", translate("Close"))
o:value("_default", translate("Default")) o:value("_default", translate("Default"))
o:value("_direct", translate("Direct Connection")) o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole")) o:value("_blackhole", translate("Blackhole"))
local pt = s:taboption("Main", ListValue, vid .. "-".. id .. "_proxy_tag", string.format('* <a style="color:red">%s</a>', 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 for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1["remark"]) o:value(v1.id, v1.remark)
end if v1.protocol ~= "_balancing" then
o.cfgvalue = function(self, section) pt:depends(node_option, v1.id)
return m:get(v.id, id) or "nil" end
end
o.write = function(self, section, value)
m:set(v.id, id, value)
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
end) end)
local id = "default_node" local id = "default_node"
o = s:taboption("Main", ListValue, v.id .. "." .. id, string.format('* <a style="color:red">%s</a>', translate("Default"))) o = s:taboption("Main", ListValue, vid .. "-" .. id, string.format('* <a style="color:red">%s</a>', translate("Default")))
o:depends("tcp_node", v.id) o:depends("tcp_node", v.id)
o:value("_direct", translate("Direct Connection")) o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole")) o:value("_blackhole", translate("Blackhole"))
for k1, v1 in pairs(normal_list) do for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1["remark"]) o:value(v1.id, v1["remark"])
end end
o.cfgvalue = function(self, section) o.cfgvalue = get_cfgvalue(v.id, id)
return m:get(v.id, id) or "nil" o.write = get_write(v.id, id)
end
o.write = function(self, section, value)
m:set(v.id, id, value)
end
local id = "main_node" local id = "default_proxy_tag"
o = s:taboption("Main", ListValue, v.id .. "." .. id, string.format('* <a style="color:red">%s</a>', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node.")) o = s:taboption("Main", ListValue, vid .. "-" .. id, string.format('* <a style="color:red">%s</a>', 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"))
for k1, v1 in pairs(normal_list) do for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1["remark"]) if v1.protocol ~= "_balancing" then
end o:depends(vid .. "-default_node", v1.id)
o.cfgvalue = function(self, section) end
return m:get(v.id, id) or "nil"
end
o.write = function(self, section, value)
m:set(v.id, id, value)
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
end end
udp_node = s:taboption("Main", ListValue, "udp_node", "<a style='color: red'>" .. translate("UDP Node") .. "</a>")
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 = 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.default = 1070
tcp_node_socks_port.datatype = "port" tcp_node_socks_port.datatype = "port"

View File

@ -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" "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 = { local header_type_list = {
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard" "none", "srtp", "utp", "wechat-video", "dtls", "wireguard"
@ -133,9 +133,10 @@ iface:depends("protocol", "_iface")
local nodes_table = {} local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do 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] = { nodes_table[#nodes_table + 1] = {
id = e[".name"], id = e[".name"],
protocol = e["protocol"],
remarks = e["remark"] remarks = e["remark"]
} }
end end
@ -145,17 +146,39 @@ end
local balancing_node = s:option(DynamicList, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://toutyrater.github.io/routing/balance2.html'>document</a>")) local balancing_node = s:option(DynamicList, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://toutyrater.github.io/routing/balance2.html'>document</a>"))
for k, v in pairs(nodes_table) do balancing_node:value(v.id, v.remarks) end for k, v in pairs(nodes_table) do balancing_node:value(v.id, v.remarks) end
balancing_node:depends("protocol", "_balancing") balancing_node:depends("protocol", "_balancing")
local balancingStrategy = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy")) local balancingStrategy = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy"))
balancingStrategy:depends("protocol", "_balancing") balancingStrategy:depends("protocol", "_balancing")
balancingStrategy:value("random") balancingStrategy:value("random")
balancingStrategy:value("leastPing") balancingStrategy:value("leastPing")
balancingStrategy.default = "random" 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")) local probeInterval = s:option(Value, "probeInterval", translate("Probe Interval"))
probeInterval:depends("balancingStrategy", "leastPing") probeInterval:depends("balancingStrategy", "leastPing")
probeInterval.default = "1m" 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 <code>ns</code>, <code>us</code>, <code>ms</code>, <code>s</code>, <code>m</code>, <code>h</code>, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.") 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 <code>ns</code>, <code>us</code>, <code>ms</code>, <code>s</code>, <code>m</code>, <code>h</code>, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.")
-- 分流 -- 分流
if #nodes_table > 0 then
o = s:option(ListValue, "main_node", string.format('<a style="color:red">%s</a>', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) 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) uci:foreach(appname, "shunt_rules", function(e)
if e[".name"] and e.remarks then if e[".name"] and e.remarks then
o = s:option(ListValue, e[".name"], string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", e[".name"]), e.remarks)) o = s:option(ListValue, e[".name"], string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", e[".name"]), e.remarks))
@ -166,15 +189,15 @@ uci:foreach(appname, "shunt_rules", function(e)
o:depends("protocol", "_shunt") o:depends("protocol", "_shunt")
if #nodes_table > 0 then if #nodes_table > 0 then
_proxy_tag = s:option(ListValue, e[".name"] .. "_proxy_tag", string.format('* <a style="color:red">%s</a>', e.remarks .. " " .. translate("Preproxy"))) local pt = s:option(ListValue, e[".name"] .. "_proxy_tag", string.format('* <a style="color:red">%s</a>', e.remarks .. " " .. translate("Preproxy")))
_proxy_tag:value("nil", translate("Close")) pt:value("nil", translate("Close"))
_proxy_tag:value("default", translate("Default")) pt:value("main", translate("Preproxy Node"))
_proxy_tag:value("main", translate("Default Preproxy")) pt.default = "nil"
_proxy_tag.default = "nil"
for k, v in pairs(nodes_table) do for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks) 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 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") default_node:depends("protocol", "_shunt")
if #nodes_table > 0 then if #nodes_table > 0 then
o = s:option(ListValue, "main_node", string.format('* <a style="color:red">%s</a>', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node.")) local dpt = s:option(ListValue, "default_proxy_tag", string.format('* <a style="color:red">%s</a>', 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")) dpt:value("nil", translate("Close"))
dpt:value("main", translate("Preproxy Node"))
dpt.default = "nil"
for k, v in pairs(nodes_table) do for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks) if v.protocol ~= "_balancing" then
o:depends("default_node", v.id) dpt:depends("default_node", v.id)
end
end 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 = s:option(ListValue, "domainStrategy", translate("Domain Strategy"))
domainStrategy:value("AsIs") domainStrategy:value("AsIs")
domainStrategy:value("IPIfNonMatch") domainStrategy:value("IPIfNonMatch")
domainStrategy:value("IPOnDemand") domainStrategy:value("IPOnDemand")
domainStrategy.default = "IPOnDemand" domainStrategy.default = "IPOnDemand"
domainStrategy.description = "<br /><ul><li>" .. translate("'AsIs': Only use domain for routing. Default value.") domainStrategy.description = "<br /><ul><li>" .. translate("'AsIs': Only use domain for routing. Default value.")
.. "</li><li>" .. translate("'IPIfNonMatch': When no rule matches current domain, resolves it into IP addresses (A or AAAA records) and try all rules again.") .. "</li><li>" .. translate("'IPIfNonMatch': When no rule matches current domain, resolves it into IP addresses (A or AAAA records) and try all rules again.")
.. "</li><li>" .. translate("'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately.") .. "</li><li>" .. translate("'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately.")
.. "</li></ul>" .. "</li></ul>"
domainStrategy:depends("protocol", "_shunt") domainStrategy:depends("protocol", "_shunt")
domainMatcher = s:option(ListValue, "domainMatcher", translate("Domain matcher")) domainMatcher = s:option(ListValue, "domainMatcher", translate("Domain matcher"))
@ -808,7 +831,7 @@ h2_path:depends("transport", "h2")
h2_path:depends("ss_transport", "h2") h2_path:depends("ss_transport", "h2")
h2_health_check = s:option(Flag, "h2_health_check", translate("Health check")) 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 = s:option(Value, "h2_read_idle_timeout", translate("Idle timeout"))
h2_read_idle_timeout.default = "10" 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 = s:option(ListValue, "grpc_mode", "gRPC " .. translate("Transfer mode"))
grpc_mode:value("gun") grpc_mode:value("gun")
grpc_mode:value("multi") 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 = 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 = s:option(Value, "grpc_idle_timeout", translate("Idle timeout"))
grpc_idle_timeout.default = "10" 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 = s:option(Value, "grpc_initial_windows_size", translate("Initial Windows Size"))
grpc_initial_windows_size.default = "0" 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 ]] -- -- [[ Trojan-Go Shadowsocks2 ]] --
ss_aead = s:option(Flag, "ss_aead", translate("Shadowsocks secondary encryption")) ss_aead = s:option(Flag, "ss_aead", translate("Shadowsocks secondary encryption"))

View File

@ -44,14 +44,14 @@ local function ln_run(s, d, command, output)
end end
d = TMP_BIN_PATH .. "/" .. d d = TMP_BIN_PATH .. "/" .. d
cmd(string.format('[ ! -f "%s" ] && ln -s %s %s 2>/dev/null', d, s, 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 end
local function gen_include() local function gen_include()
cmd(string.format("echo '#!/bin/sh' > /tmp/etc/%s.include", CONFIG)) cmd(string.format("echo '#!/bin/sh' > /tmp/etc/%s.include", CONFIG))
if nft_flag == "1" then if nft_flag == "1" then
cmd("echo \"\" > " .. CONFIG_PATH .. "/" .. CONFIG .. ".nft") 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) cmd(nft_cmd)
end end
local function extract_rules(n, a) local function extract_rules(n, a)
@ -215,7 +215,7 @@ local function stop()
ip6t("-F PSW-SERVER 2>/dev/null") ip6t("-F PSW-SERVER 2>/dev/null")
ip6t("-X PSW-SERVER 2>/dev/null") ip6t("-X PSW-SERVER 2>/dev/null")
else 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_cmd)
cmd("nft flush chain inet fw4 PSW-SERVER 2>/dev/null") cmd("nft flush chain inet fw4 PSW-SERVER 2>/dev/null")
cmd("nft delete chain inet fw4 PSW-SERVER 2>/dev/null") cmd("nft delete chain inet fw4 PSW-SERVER 2>/dev/null")

View File

@ -90,9 +90,8 @@ function gen_outbound(flag, node, tag, proxy_table)
new_port, --socks port new_port, --socks port
config_file, --config file config_file, --config file
(proxy == 1 and relay_port) and tostring(relay_port) or "" --relay port (proxy == 1 and relay_port) and tostring(relay_port) or "" --relay port
)
) )
) ))
node = {} node = {}
node.protocol = "socks" node.protocol = "socks"
node.transport = "tcp" node.transport = "tcp"
@ -257,7 +256,7 @@ function gen_config_server(node)
local settings = nil local settings = nil
local routing = nil local routing = nil
local outbounds = { 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 if node.protocol == "vmess" or node.protocol == "vless" then
@ -596,184 +595,250 @@ function gen_config(var)
end end
end end
if node.protocol == "_shunt" then local function get_balancer_tag(_node_id)
local rules = {} return "balancer-" .. _node_id:sub(1, 8)
end
local default_node_id = node.default_node or "_direct" local function gen_balancer(_node, loopbackTag)
local default_outboundTag local blc_nodes = _node.balancing_node
if default_node_id == "_direct" then local length = #blc_nodes
default_outboundTag = "direct" local valid_nodes = {}
elseif default_node_id == "_blackhole" then for i = 1, length do
default_outboundTag = "blackhole" local blc_node_id = blc_nodes[i]
else local blc_node_tag = "blc-" .. blc_node_id:sub(1, 8)
local default_node = uci:get_all(appname, default_node_id) local is_new_blc_node = true
local main_node_id = node.main_node or "nil" for _, outbound in ipairs(outbounds) do
local proxy = 0 if outbound.tag == blc_node_tag then
local proxy_tag is_new_blc_node = false
if main_node_id ~= "nil" then valid_nodes[#valid_nodes + 1] = blc_node_tag
local main_node = uci:get_all(appname, main_node_id) break
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
end end
end end
if default_node and api.is_normal_node(default_node) then if is_new_blc_node then
local default_outbound = gen_outbound(flag, default_node, "default", { proxy = proxy, tag = proxy_tag, dialerProxy = node.dialerProxy }) local blc_node = uci:get_all(appname, blc_node_id)
if default_outbound then local outbound = gen_outbound(flag, blc_node, blc_node_tag)
table.insert(outbounds, default_outbound) if outbound then
default_outboundTag = "default" table.insert(outbounds, outbound)
valid_nodes[#valid_nodes + 1] = blc_node_tag
end end
end end
end end
uci:foreach(appname, "shunt_rules", function(e) local balancer, rule
local name = e[".name"] if #valid_nodes > 0 then
if name and e.remarks then local balancerTag = get_balancer_tag(_node[".name"])
local _node_id = node[name] or "nil" balancer = {
local proxy_tag = node[name .. "_proxy_tag"] or "nil" tag = balancerTag,
local outboundTag selector = valid_nodes,
if _node_id == "_direct" then strategy = { type = _node.balancingStrategy or "random" }
outboundTag = "direct" }
elseif _node_id == "_blackhole" then if _node.balancingStrategy == "leastPing" then
outboundTag = "blackhole" if not observatory then
elseif _node_id == "_default" then observatory = {
outboundTag = "default" subjectSelector = { "blc-" },
else probeUrl = _node.useCustomProbeUrl == true and _node.probeUrl or nil,
if _node_id ~= "nil" then probeInterval = _node.probeInterval or "1m",
local _node = uci:get_all(appname, _node_id) enableConcurrency = node.type == "Xray" and true or nil --这里只判断顶层节点(分流总节点/单独的负载均衡节点)类型为Xray就可以启用并发
if _node and api.is_normal_node(_node) then }
local new_outbound end
for index, value in ipairs(outbounds) do end
if value["_flag_tag"] == _node_id and value["_flag_proxy_tag"] == proxy_tag then if loopbackTag and loopbackTag ~= "" then
new_outbound = api.clone(value) local inboundTag = loopbackTag .. "-in"
break table.insert(outbounds, {
end protocol = "loopback",
end tag = loopbackTag,
if new_outbound then settings = { inboundTag = inboundTag }
new_outbound["tag"] = name })
table.insert(outbounds, new_outbound) rule = {
outboundTag = name type = "field",
else inboundTag = { inboundTag },
local pre_proxy = nil balancerTag = balancerTag
if _node.type ~= "V2ray" and _node.type ~= "Xray" then }
pre_proxy = true end
end end
if _node.type == "Xray" and _node.tlsflow == "xtls-rprx-vision" then return balancer, rule
pre_proxy = true end
end
if pre_proxy then if node.protocol == "_shunt" then
if proxy_tag ~= "nil" then local rules = {}
new_port = get_new_port() local balancers = {}
table.insert(inbounds, {
tag = "proxy_" .. name, local preproxy_enabled = false
listen = "127.0.0.1", local preproxy_tag = "main"
port = new_port, local preproxy_node_id = node[preproxy_tag .. "_node"] or "nil"
protocol = "dokodemo-door",
settings = {network = "tcp,udp", address = _node.address, port = tonumber(_node.port)} local function gen_shunt_node(rule_name, _node_id, as_proxy)
}) if not rule_name then return nil, nil end
if _node.tls_serverName == nil then if not _node_id then _node_id = node[rule_name] or "nil" end
_node.tls_serverName = _node.address local rule_outboundTag
end local rule_balancerTag
_node.address = "127.0.0.1" if _node_id == "_direct" then
_node.port = new_port rule_outboundTag = "direct"
table.insert(rules, 1, { elseif _node_id == "_blackhole" then
type = "field", rule_outboundTag = "blackhole"
inboundTag = {"proxy_" .. name}, elseif _node_id == "_default" and rule_name ~= "default" then
outboundTag = proxy_tag rule_outboundTag = "default"
}) elseif _node_id ~= "nil" then
end local _node = uci:get_all(appname, _node_id)
end if not _node then return nil, nil 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 if api.is_normal_node(_node) then --这一块根据代理设置的修改方向还需要修改
table.insert(outbounds, _outbound) local proxy_tag = node[rule_name .. "_proxy_tag"] or "nil"
outboundTag = name if proxy_tag == preproxy_tag and not preproxy_enabled then proxy_tag = "nil" end
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
end end
end
if outboundTag then local copied_outbound
if outboundTag == "default" then for index, value in ipairs(outbounds) do
outboundTag = default_outboundTag if value["_flag_tag"] == _node_id and value["_flag_proxy_tag"] == proxy_tag then
copied_outbound = api.clone(value)
break
end
end end
local protocols = nil if copied_outbound then
if e["protocol"] and e["protocol"] ~= "" then copied_outbound.tag = rule_name
protocols = {} table.insert(outbounds, copied_outbound)
string.gsub(e["protocol"], '[^' .. " " .. ']+', function(w) rule_outboundTag = rule_name
table.insert(protocols, w) else
end) 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 end
if e.domain_list then elseif _node.protocol == "_balancing" then
local _domain = {} local is_new_balancer = true
string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w) for _, v in ipairs(balancers) do
table.insert(_domain, w) if v["_flag_tag"] == _node_id then
end) is_new_balancer = false
table.insert(rules, { rule_balancerTag = v.tag
type = "field", break
outboundTag = outboundTag, end
domain = _domain,
protocol = protocols
})
end end
if e.ip_list then if is_new_balancer then --注释掉的是给需要用作前置代理的balancer生成等效OutboundTagloopback + 规则路由至)的代码,可能用上
local _ip = {} --local loopbackTag = as_proxy == true and rule_name or nil
string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w) local balancer = gen_balancer(_node) --local balancer, rule = gen_balancer(_node)
table.insert(_ip, w) if balancer then
end) table.insert(balancers, balancer)
table.insert(rules, { --if rule then table.insert(rules, rule) end
type = "field", rule_balancerTag = balancer.tag
outboundTag = outboundTag, end
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
})
end end
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) end)
if default_outboundTag then if default_outboundTag or default_balancerTag then
table.insert(rules, { table.insert(rules, {
type = "field", type = "field",
outboundTag = default_outboundTag, outboundTag = default_outboundTag,
balancerTag = default_balancerTag,
network = "tcp,udp" network = "tcp,udp"
}) })
end end
@ -781,31 +846,16 @@ function gen_config(var)
routing = { routing = {
domainStrategy = node.domainStrategy or "AsIs", domainStrategy = node.domainStrategy or "AsIs",
domainMatcher = node.domainMatcher or "hybrid", domainMatcher = node.domainMatcher or "hybrid",
balancers = #balancers > 0 and balancers or nil,
rules = rules rules = rules
} }
elseif node.protocol == "_balancing" then elseif node.protocol == "_balancing" then
if node.balancing_node then if node.balancing_node then
local nodes = node.balancing_node local balancer = gen_balancer(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
routing = { routing = {
balancers = {{ balancers = { balancer },
tag = "balancer",
selector = nodes,
strategy = {type = node.balancingStrategy or "random"}
}},
rules = { rules = {
{type = "field", network = "tcp,udp", balancerTag = "balancer"} { type = "field", network = "tcp,udp", balancerTag = balancer.tag }
} }
} }
end end

View File

@ -352,6 +352,18 @@ msgstr "V2ray 负载均衡"
msgid "Balancing Strategy" msgid "Balancing Strategy"
msgstr "负载均衡策略" 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" msgid "Probe Interval"
msgstr "探测间隔" msgstr "探测间隔"
@ -370,6 +382,12 @@ msgstr "V2ray 分流"
msgid "Preproxy" msgid "Preproxy"
msgstr "前置代理" msgstr "前置代理"
msgid "Preproxy Node"
msgstr "前置代理节点"
msgid "Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."
msgstr "设置用作前置代理的节点。每条规则(包括<code>默认</code>)都有独立开关控制本规则是否使用前置代理。"
msgid "Direct Connection" msgid "Direct Connection"
msgstr "直连" msgstr "直连"
@ -661,8 +679,8 @@ msgstr "切掉它"
msgid "Applying to the default node" msgid "Applying to the default node"
msgstr "应用于默认节点" msgstr "应用于默认节点"
msgid "Applying to the default preproxy node" msgid "Applying to the preproxy node"
msgstr "应用于默认前置节点" msgstr "应用于前置代理节点"
msgid "Add nodes to the standby node list by keywords" msgid "Add nodes to the standby node list by keywords"
msgstr "通过关键字添加节点到备用节点列表" msgstr "通过关键字添加节点到备用节点列表"