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"
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

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: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"))

View File

@ -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", "<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
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('<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)
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('* <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: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('* <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
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('* <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: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('* <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"))
local id = "default_proxy_tag"
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."))
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", "<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.default = 1070
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"
}
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, <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
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 <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)
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))
@ -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('* <a style="color:red">%s</a>', 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('* <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 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('* <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"))
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."))
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 = "<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("'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately.")
.. "</li></ul>"
.. "</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></ul>"
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"))

View File

@ -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")

View File

@ -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生成等效OutboundTagloopback + 规则路由至)的代码,可能用上
--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

View File

@ -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 <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."
msgstr "设置用作前置代理的节点。每条规则(包括<code>默认</code>)都有独立开关控制本规则是否使用前置代理。"
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 "通过关键字添加节点到备用节点列表"