openwrt-passwall2/luci-app-passwall2/luasrc/passwall2/util_xray.lua

1948 lines
59 KiB
Lua

module("luci.passwall2.util_xray", package.seeall)
local api = require "luci.passwall2.api"
local uci = api.uci
local sys = api.sys
local jsonc = api.jsonc
local appname = api.appname
local fs = api.fs
local CACHE_PATH = api.CACHE_PATH
local new_port
local function get_new_port()
if new_port then
new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port %s tcp)", appname, new_port + 1)))
else
new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port auto tcp)", appname)))
end
return new_port
end
local function get_noise_packets()
local noises = {}
uci:foreach(appname, "xray_noise_packets", function(n)
local noise = (n.enabled == "1") and {
type = n.type,
packet = n.packet,
delay = string.find(n.delay, "-") and n.delay or tonumber(n.delay)
} or nil
table.insert(noises, noise)
end)
if #noises == 0 then noises = nil end
return noises
end
local function get_domain_excluded()
local path = string.format("/usr/share/%s/domains_excluded", appname)
local content = fs.readfile(path)
if not content then return nil end
local hosts = {}
string.gsub(content, '[^' .. "\n" .. ']+', function(w)
local s = w:gsub("^%s*(.-)%s*$", "%1") -- Trim
if s == "" then return end
if s:find("#") and s:find("#") == 1 then return end
if not s:find("#") or s:find("#") ~= 1 then table.insert(hosts, s) end
end)
if #hosts == 0 then hosts = nil end
return hosts
end
function gen_outbound(flag, node, tag, proxy_table)
local result = nil
if node then
local node_id = node[".name"]
if tag == nil then
tag = node_id
end
local proxy_tag = nil
local fragment = nil
local noise = nil
if proxy_table ~= nil and type(proxy_table) == "table" then
proxy_tag = proxy_table.tag or nil
fragment = proxy_table.fragment or nil
noise = proxy_table.noise or nil
end
if node.type ~= "Xray" then
local relay_port = node.port
new_port = get_new_port()
local config_file = string.format("%s_%s_%s.json", flag, tag, new_port)
if tag and node_id and tag ~= node_id then
config_file = string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port)
end
sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null',
appname,
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s relay_port=%s",
new_port, --flag
node_id, --node
"127.0.0.1", --bind
new_port, --socks port
config_file, --config file
(proxy_tag and relay_port) and tostring(relay_port) or "" --relay port
)
)
)
node = {}
node.protocol = "socks"
node.transport = "tcp"
node.address = "127.0.0.1"
node.port = new_port
node.stream_security = "none"
else
if node.flow == "xtls-rprx-vision" then
else
if proxy_tag then
node.proxySettings = {
tag = proxy_tag,
transportLayer = true
}
end
end
end
if node.type == "Xray" then
if node.tls and node.tls == "1" then
node.stream_security = "tls"
if node.reality and node.reality == "1" then
node.stream_security = "reality"
end
end
end
if node.protocol == "wireguard" and node.wireguard_reserved then
local bytes = {}
if not node.wireguard_reserved:match("[^%d,]+") then
node.wireguard_reserved:gsub("%d+", function(b)
bytes[#bytes + 1] = tonumber(b)
end)
else
local result = api.bin.b64decode(node.wireguard_reserved)
for i = 1, #result do
bytes[i] = result:byte(i)
end
end
node.wireguard_reserved = #bytes > 0 and bytes or nil
end
result = {
_id = node_id,
_flag = flag,
_flag_proxy_tag = proxy_tag,
tag = tag,
proxySettings = node.proxySettings or nil,
protocol = node.protocol,
mux = {
enabled = (node.mux == "1" or node.xmux == "1") and true or false,
concurrency = (node.mux == "1" and ((node.mux_concurrency) and tonumber(node.mux_concurrency) or 8)) or ((node.xmux == "1") and -1) or nil,
xudpConcurrency = (node.xmux == "1" and ((node.xudp_concurrency) and tonumber(node.xudp_concurrency) or 8)) or nil
} or nil,
-- 底层传输配置
streamSettings = (node.streamSettings or node.protocol == "vmess" or node.protocol == "vless" or node.protocol == "socks" or node.protocol == "shadowsocks" or node.protocol == "trojan") and {
sockopt = {
mark = 255,
tcpMptcp = (node.tcpMptcp == "1") and true or nil,
tcpNoDelay = (node.tcpNoDelay == "1") and true or nil,
dialerProxy = (fragment or noise) and "dialerproxy" or nil
},
network = node.transport,
security = node.stream_security,
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
allowInsecure = (node.tls_allowInsecure == "1") and true or false,
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil
} or nil,
realitySettings = (node.stream_security == "reality") and {
serverName = node.tls_serverName,
publicKey = node.reality_publicKey,
shortId = node.reality_shortId or "",
spiderX = node.reality_spiderX or "/",
fingerprint = (node.type == "Xray" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or "chrome"
} or nil,
rawSettings = ((node.transport == "raw" or node.transport == "tcp") and node.protocol ~= "socks") and {
header = {
type = node.tcp_guise or "none",
request = (node.tcp_guise == "http") and {
path = node.tcp_guise_http_path or {"/"},
headers = {
Host = node.tcp_guise_http_host or {}
}
} or nil
}
} or nil,
kcpSettings = (node.transport == "mkcp") and {
mtu = tonumber(node.mkcp_mtu),
tti = tonumber(node.mkcp_tti),
uplinkCapacity = tonumber(node.mkcp_uplinkCapacity),
downlinkCapacity = tonumber(node.mkcp_downlinkCapacity),
congestion = (node.mkcp_congestion == "1") and true or false,
readBufferSize = tonumber(node.mkcp_readBufferSize),
writeBufferSize = tonumber(node.mkcp_writeBufferSize),
seed = (node.mkcp_seed and node.mkcp_seed ~= "") and node.mkcp_seed or nil,
header = {type = node.mkcp_guise}
} or nil,
wsSettings = (node.transport == "ws") and {
path = node.ws_path or "/",
host = node.ws_host or nil,
maxEarlyData = tonumber(node.ws_maxEarlyData) or nil,
earlyDataHeaderName = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil,
heartbeatPeriod = tonumber(node.ws_heartbeatPeriod) or nil
} or nil,
dsSettings = (node.transport == "ds") and
{path = node.ds_path} or nil,
quicSettings = (node.transport == "quic") and {
security = node.quic_security,
key = node.quic_key,
header = {type = node.quic_guise}
} or nil,
grpcSettings = (node.transport == "grpc") and {
serviceName = node.grpc_serviceName,
multiMode = (node.grpc_mode == "multi") and true or nil,
idle_timeout = tonumber(node.grpc_idle_timeout) or nil,
health_check_timeout = tonumber(node.grpc_health_check_timeout) or nil,
permit_without_stream = (node.grpc_permit_without_stream == "1") and true or nil,
initial_windows_size = tonumber(node.grpc_initial_windows_size) or nil
} or nil,
httpupgradeSettings = (node.transport == "httpupgrade") and {
path = node.httpupgrade_path or "/",
host = node.httpupgrade_host
} or nil,
xhttpSettings = (node.transport == "xhttp" or node.transport == "splithttp") and {
mode = node.xhttp_mode or "auto",
path = node.xhttp_path or node.splithttp_path or "/",
host = node.xhttp_host or node.splithttp_host,
-- 如果包含 "extra" 节,取 "extra" 内的内容,否则直接赋值给 extra
extra = node.xhttp_extra and (function()
local success, parsed = pcall(jsonc.parse, node.xhttp_extra)
if success then
return parsed.extra or parsed
else
return nil
end
end)() or nil
} or nil,
} or nil,
settings = {
vnext = (node.protocol == "vmess" or node.protocol == "vless") and {
{
address = node.address,
port = tonumber(node.port),
users = {
{
id = node.uuid,
level = 0,
security = (node.protocol == "vmess") and node.security or nil,
encryption = node.encryption or "none",
flow = (node.protocol == "vless" and node.tls == "1" and (node.transport == "raw" or node.transport == "tcp") and node.flow and node.flow ~= "") and node.flow or nil
}
}
}
} or nil,
servers = (node.protocol == "socks" or node.protocol == "http" or node.protocol == "shadowsocks" or node.protocol == "trojan") and {
{
address = node.address,
port = tonumber(node.port),
method = (node.method == "chacha20-ietf-poly1305" and "chacha20-poly1305") or
(node.method == "xchacha20-ietf-poly1305" and "xchacha20-poly1305") or
(node.method ~= "" and node.method) or nil,
ivCheck = (node.protocol == "shadowsocks") and node.iv_check == "1" or nil,
uot = (node.protocol == "shadowsocks") and node.uot == "1" or nil,
password = node.password or "",
users = (node.username and node.password) and {
{
user = node.username,
pass = node.password
}
} or nil
}
} or nil,
address = (node.protocol == "wireguard" and node.wireguard_local_address) and node.wireguard_local_address or nil,
secretKey = (node.protocol == "wireguard") and node.wireguard_secret_key or nil,
peers = (node.protocol == "wireguard") and {
{
publicKey = node.wireguard_public_key,
endpoint = node.address .. ":" .. node.port,
preSharedKey = node.wireguard_preSharedKey,
keepAlive = node.wireguard_keepAlive and tonumber(node.wireguard_keepAlive) or nil
}
} or nil,
mtu = (node.protocol == "wireguard" and node.wireguard_mtu) and tonumber(node.wireguard_mtu) or nil,
reserved = (node.protocol == "wireguard" and node.wireguard_reserved) and node.wireguard_reserved or nil
}
}
if node.protocol == "wireguard" then
result.settings.kernelMode = false
end
local alpn = {}
if node.alpn and node.alpn ~= "default" then
string.gsub(node.alpn, '[^' .. "," .. ']+', function(w)
table.insert(alpn, w)
end)
end
if alpn and #alpn > 0 then
if result.streamSettings.tlsSettings then
result.streamSettings.tlsSettings.alpn = alpn
end
end
end
return result
end
function gen_config_server(node)
local settings = nil
local routing = nil
local outbounds = {
{protocol = "freedom", tag = "direct"}, {protocol = "blackhole", tag = "blocked"}
}
if node.protocol == "vmess" or node.protocol == "vless" then
if node.uuid then
local clients = {}
for i = 1, #node.uuid do
clients[i] = {
id = node.uuid[i],
flow = ("vless" == node.protocol and "1" == node.tls and "raw" == node.transport and node.flow and node.flow ~= "") and node.flow or nil
}
end
settings = {
clients = clients,
decryption = node.decryption or "none"
}
end
elseif node.protocol == "socks" then
settings = {
udp = ("1" == node.udp_forward) and true or false,
auth = ("1" == node.auth) and "password" or "noauth",
accounts = ("1" == node.auth) and {
{
user = node.username,
pass = node.password
}
} or nil
}
elseif node.protocol == "http" then
settings = {
allowTransparent = false,
accounts = ("1" == node.auth) and {
{
user = node.username,
pass = node.password
}
} or nil
}
node.transport = "tcp"
node.tcp_guise = "none"
elseif node.protocol == "shadowsocks" then
settings = {
method = node.method,
password = node.password,
ivCheck = ("1" == node.iv_check) and true or false,
network = node.ss_network or "TCP,UDP"
}
elseif node.protocol == "trojan" then
if node.uuid then
local clients = {}
for i = 1, #node.uuid do
clients[i] = {
password = node.uuid[i]
}
end
settings = {
clients = clients
}
end
elseif node.protocol == "dokodemo-door" then
settings = {
network = node.d_protocol,
address = node.d_address,
port = tonumber(node.d_port)
}
end
if node.fallback and node.fallback == "1" then
local fallbacks = {}
for i = 1, #node.fallback_list do
local fallbackStr = node.fallback_list[i]
if fallbackStr then
local tmp = {}
string.gsub(fallbackStr, '[^,]+', function(w)
table.insert(tmp, w)
end)
local dest = tmp[1] or ""
local path = tmp[2]
local xver = tonumber(tmp[3])
if not dest:find("%.") then
dest = tonumber(dest)
end
fallbacks[i] = {
path = path,
dest = dest,
xver = xver
}
end
end
settings.fallbacks = fallbacks
end
routing = {
domainStrategy = "IPOnDemand",
rules = {
{
ip = {"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"},
outboundTag = (node.accept_lan == nil or node.accept_lan == "0") and "blocked" or "direct"
}
}
}
if node.outbound_node then
local outbound = nil
if node.outbound_node == "_iface" and node.outbound_node_iface then
outbound = {
protocol = "freedom",
tag = "outbound",
streamSettings = {
sockopt = {
mark = 255,
interface = node.outbound_node_iface
}
}
}
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.outbound_node_iface))
else
local outbound_node_t = uci:get_all("passwall2", node.outbound_node)
if node.outbound_node == "_socks" or node.outbound_node == "_http" then
outbound_node_t = {
type = node.type,
protocol = node.outbound_node:gsub("_", ""),
transport = "tcp",
address = node.outbound_node_address,
port = node.outbound_node_port,
username = (node.outbound_node_username and node.outbound_node_username ~= "") and node.outbound_node_username or nil,
password = (node.outbound_node_password and node.outbound_node_password ~= "") and node.outbound_node_password or nil,
}
end
outbound = require("luci.passwall2.util_xray").gen_outbound(nil, outbound_node_t, "outbound")
end
if outbound then
table.insert(outbounds, 1, outbound)
end
end
local config = {
log = {
loglevel = ("1" == node.log) and node.loglevel or "none"
},
-- 传入连接
inbounds = {
{
listen = (node.bind_local == "1") and "127.0.0.1" or nil,
port = tonumber(node.port),
protocol = node.protocol,
settings = settings,
streamSettings = {
network = node.transport,
security = "none",
tlsSettings = ("1" == node.tls) and {
disableSystemRoot = false,
certificates = {
{
certificateFile = node.tls_certificateFile,
keyFile = node.tls_keyFile
}
}
} or nil,
rawSettings = (node.transport == "raw" or node.transport == "tcp") and {
header = {
type = node.tcp_guise,
request = (node.tcp_guise == "http") and {
path = node.tcp_guise_http_path or {"/"},
headers = {
Host = node.tcp_guise_http_host or {}
}
} or nil
}
} or nil,
kcpSettings = (node.transport == "mkcp") and {
mtu = tonumber(node.mkcp_mtu),
tti = tonumber(node.mkcp_tti),
uplinkCapacity = tonumber(node.mkcp_uplinkCapacity),
downlinkCapacity = tonumber(node.mkcp_downlinkCapacity),
congestion = (node.mkcp_congestion == "1") and true or false,
readBufferSize = tonumber(node.mkcp_readBufferSize),
writeBufferSize = tonumber(node.mkcp_writeBufferSize),
seed = (node.mkcp_seed and node.mkcp_seed ~= "") and node.mkcp_seed or nil,
header = {type = node.mkcp_guise}
} or nil,
wsSettings = (node.transport == "ws") and {
host = node.ws_host or nil,
path = node.ws_path
} or nil,
dsSettings = (node.transport == "ds") and {
path = node.ds_path
} or nil,
quicSettings = (node.transport == "quic") and {
security = node.quic_security,
key = node.quic_key,
header = {type = node.quic_guise}
} or nil,
grpcSettings = (node.transport == "grpc") and {
serviceName = node.grpc_serviceName
} or nil,
httpupgradeSettings = (node.transport == "httpupgrade") and {
path = node.httpupgrade_path or "/",
host = node.httpupgrade_host
} or nil,
xhttpSettings = (node.transport == "xhttp") and {
path = node.xhttp_path or "/",
host = node.xhttp_host,
maxUploadSize = node.xhttp_maxuploadsize,
maxConcurrentUploads = node.xhttp_maxconcurrentuploads
} or nil,
sockopt = {
acceptProxyProtocol = (node.acceptProxyProtocol and node.acceptProxyProtocol == "1") and true or false
}
}
}
},
-- 传出连接
outbounds = outbounds,
routing = routing
}
local alpn = {}
if node.alpn then
string.gsub(node.alpn, '[^' .. "," .. ']+', function(w)
table.insert(alpn, w)
end)
end
if alpn and #alpn > 0 then
if config.inbounds[1].streamSettings.tlsSettings then
config.inbounds[1].streamSettings.tlsSettings.alpn = alpn
end
end
if "1" == node.tls then
config.inbounds[1].streamSettings.security = "tls"
if "1" == node.reality then
config.inbounds[1].streamSettings.tlsSettings = nil
config.inbounds[1].streamSettings.security = "reality"
config.inbounds[1].streamSettings.realitySettings = {
show = false,
dest = node.reality_dest,
serverNames = node.reality_serverNames or {},
privateKey = node.reality_private_key,
shortIds = node.reality_shortId or ""
} or nil
end
end
return config
end
function gen_config(var)
local flag = var["-flag"]
local loglevel = var["-loglevel"] or "warning"
local node_id = var["-node"]
local server_host = var["-server_host"]
local server_port = var["-server_port"]
local tcp_proxy_way = var["-tcp_proxy_way"]
local redir_port = var["-redir_port"]
local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
local local_socks_port = var["-local_socks_port"]
local local_socks_username = var["-local_socks_username"]
local local_socks_password = var["-local_socks_password"]
local local_http_address = var["-local_http_address"] or "0.0.0.0"
local local_http_port = var["-local_http_port"]
local local_http_username = var["-local_http_username"]
local local_http_password = var["-local_http_password"]
local dns_listen_port = var["-dns_listen_port"]
local direct_dns_udp_server = var["-direct_dns_udp_server"]
local direct_dns_udp_port = var["-direct_dns_udp_port"]
local direct_dns_query_strategy = var["-direct_dns_query_strategy"]
local direct_ipset = var["-direct_ipset"]
local direct_nftset = var["-direct_nftset"]
local remote_dns_udp_server = var["-remote_dns_udp_server"]
local remote_dns_udp_port = var["-remote_dns_udp_port"]
local remote_dns_tcp_server = var["-remote_dns_tcp_server"]
local remote_dns_tcp_port = var["-remote_dns_tcp_port"]
local remote_dns_doh_url = var["-remote_dns_doh_url"]
local remote_dns_doh_host = var["-remote_dns_doh_host"]
local remote_dns_doh_ip = var["-remote_dns_doh_ip"]
local remote_dns_doh_port = var["-remote_dns_doh_port"]
local remote_dns_fake = var["-remote_dns_fake"]
local remote_dns_query_strategy = var["-remote_dns_query_strategy"]
local remote_dns_detour = var["-remote_dns_detour"]
local dns_cache = var["-dns_cache"]
local dns_domain_rules = {}
local dns = nil
local fakedns = nil
local inbounds = {}
local outbounds = {}
local routing = nil
local burstObservatory = nil
local strategy = nil
local COMMON = {}
local CACHE_TEXT_FILE = CACHE_PATH .. "/cache_" .. flag .. ".txt"
local xray_settings = uci:get_all(appname, "@global_xray[0]") or {}
local node = node_id and uci:get_all(appname, node_id) or nil
local balancers = {}
local rules = {}
if local_socks_port then
local inbound = {
tag = "socks-in",
listen = local_socks_address,
port = tonumber(local_socks_port),
protocol = "socks",
settings = {auth = "noauth", udp = true},
sniffing = {
enabled = xray_settings.sniffing_override_dest == "1" or node.protocol == "_shunt"
}
}
if inbound.sniffing.enabled == true then
inbound.sniffing.destOverride = {"http", "tls", "quic"}
inbound.sniffing.routeOnly = xray_settings.sniffing_override_dest ~= "1" or nil
inbound.sniffing.domainsExcluded = xray_settings.sniffing_override_dest == "1" and get_domain_excluded() or nil
end
if local_socks_username and local_socks_password and local_socks_username ~= "" and local_socks_password ~= "" then
inbound.settings.auth = "password"
inbound.settings.accounts = {
{
user = local_socks_username,
pass = local_socks_password
}
}
end
table.insert(inbounds, inbound)
end
if local_http_port then
local inbound = {
listen = local_http_address,
port = tonumber(local_http_port),
protocol = "http",
settings = {allowTransparent = false}
}
if local_http_username and local_http_password and local_http_username ~= "" and local_http_password ~= "" then
inbound.settings.accounts = {
{
user = local_http_username,
pass = local_http_password
}
}
end
table.insert(inbounds, inbound)
end
if redir_port then
local inbound = {
port = tonumber(redir_port),
protocol = "dokodemo-door",
settings = {network = "tcp,udp", followRedirect = true},
streamSettings = {sockopt = {tproxy = "tproxy"}},
sniffing = {
enabled = xray_settings.sniffing_override_dest == "1" or node.protocol == "_shunt"
}
}
if inbound.sniffing.enabled == true then
inbound.sniffing.destOverride = {"http", "tls", "quic"}
inbound.sniffing.metadataOnly = false
inbound.sniffing.routeOnly = xray_settings.sniffing_override_dest ~= "1" or nil
inbound.sniffing.domainsExcluded = xray_settings.sniffing_override_dest == "1" and get_domain_excluded() or nil
end
if remote_dns_fake then
inbound.sniffing.enabled = true
if not inbound.sniffing.destOverride then
inbound.sniffing.destOverride = {"fakedns"}
inbound.sniffing.metadataOnly = true
else
table.insert(inbound.sniffing.destOverride, "fakedns")
inbound.sniffing.metadataOnly = false
end
end
local tcp_inbound = api.clone(inbound)
tcp_inbound.tag = "tcp_redir"
tcp_inbound.settings.network = "tcp"
tcp_inbound.streamSettings.sockopt.tproxy = tcp_proxy_way
table.insert(inbounds, tcp_inbound)
local udp_inbound = api.clone(inbound)
udp_inbound.tag = "udp_redir"
udp_inbound.settings.network = "udp"
table.insert(inbounds, udp_inbound)
end
local function get_balancer_tag(_node_id)
return "balancer-" .. _node_id
end
local function gen_loopback(outboundTag, dst_node_id)
if not outboundTag then return nil end
local inboundTag = dst_node_id and "loop-in-" .. dst_node_id or outboundTag .. "-lo"
table.insert(outbounds, {
protocol = "loopback",
tag = outboundTag,
settings = { inboundTag = inboundTag }
})
return inboundTag
end
local function gen_balancer(_node, loopback_tag)
local balancer_id = _node[".name"]
local balancer_tag = "balancer-" .. balancer_id
local loopback_dst = balancer_id -- route destination for the loopback outbound
if not loopback_tag or loopback_tag == "" then loopback_tag = balancer_id end
-- existing balancer
for _, v in ipairs(balancers) do
if v.tag == balancer_tag then
gen_loopback(loopback_tag, loopback_dst)
return balancer_tag
end
end
-- new balancer
local blc_nodes = _node.balancing_node
local valid_nodes = {}
for i = 1, #blc_nodes do
local blc_node_id = blc_nodes[i]
local blc_node_tag = "blc-" .. blc_node_id
local is_new_blc_node = true
for _, outbound in ipairs(outbounds) do
if string.sub(outbound.tag, 1, #blc_node_tag) == blc_node_tag then
is_new_blc_node = false
valid_nodes[#valid_nodes + 1] = outbound.tag
break
end
end
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, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil })
if outbound then
outbound.tag = outbound.tag .. ":" .. blc_node.remarks
table.insert(outbounds, outbound)
valid_nodes[#valid_nodes + 1] = outbound.tag
end
end
end
if #valid_nodes == 0 then return nil end
-- fallback node
local fallback_node_tag = nil
local fallback_node_id = _node.fallback_node
if not fallback_node_id or fallback_node_id == "" then fallback_node_id = nil end
if fallback_node_id then
local is_new_node = true
for _, outbound in ipairs(outbounds) do
if string.sub(outbound.tag, 1, #fallback_node_id) == fallback_node_id then
is_new_node = false
fallback_node_tag = outbound.tag
break
end
end
if is_new_node then
local fallback_node = uci:get_all(appname, fallback_node_id)
if fallback_node.protocol ~= "_balancing" then
local outbound = gen_outbound(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil })
if outbound then
outbound.tag = outbound.tag .. ":" .. fallback_node.remarks
table.insert(outbounds, outbound)
fallback_node_tag = outbound.tag
end
end
end
end
if _node.balancingStrategy == "leastLoad" then
strategy = {
type = _node.balancingStrategy,
settings = {
expected = _node.expected and tonumber(_node.expected) and tonumber(_node.expected) or 2,
maxRTT = "1s"
}
}
else
strategy = { type = _node.balancingStrategy or "random" }
end
table.insert(balancers, {
tag = balancer_tag,
selector = valid_nodes,
fallbackTag = fallback_node_tag,
strategy = strategy
})
if _node.balancingStrategy == "leastPing" or _node.balancingStrategy == "leastLoad" or fallback_node_tag then
if not burstObservatory then
burstObservatory = {
subjectSelector = { "blc-" },
pingConfig = {
destination = _node.useCustomProbeUrl and _node.probeUrl or nil,
interval = _node.probeInterval or "1m",
sampling = 3,
timeout = "5s"
}
}
end
end
local inbound_tag = gen_loopback(loopback_tag, loopback_dst)
table.insert(rules, { inboundTag = { inbound_tag }, balancerTag = balancer_tag })
return balancer_tag
end
local function set_outbound_detour(node, outbound, outbounds_table, shunt_rule_name)
if not node or not outbound or not outbounds_table then return nil end
local default_outTag = outbound.tag
local last_insert_outbound
if node.chain_proxy == "1" and node.preproxy_node then
if outbound["_flag_proxy_tag"] then
--Ignore
else
local preproxy_node = uci:get_all(appname, node.preproxy_node)
if preproxy_node then
local preproxy_outbound = gen_outbound(nil, preproxy_node)
if preproxy_outbound then
preproxy_outbound.tag = preproxy_node[".name"] .. ":" .. preproxy_node.remarks
outbound.tag = preproxy_outbound.tag .. " -> " .. outbound.tag
outbound.proxySettings = {
tag = preproxy_outbound.tag,
transportLayer = true
}
last_insert_outbound = preproxy_outbound
default_outTag = outbound.tag
end
end
end
end
if node.chain_proxy == "2" and node.to_node then
local to_node = uci:get_all(appname, node.to_node)
if to_node then
local to_outbound = gen_outbound(nil, to_node)
if to_outbound then
if shunt_rule_name then
to_outbound.tag = outbound.tag
outbound.tag = node[".name"]
else
to_outbound.tag = outbound.tag .. " -> " .. to_outbound.tag
end
to_outbound.proxySettings = {
tag = outbound.tag,
transportLayer = true
}
table.insert(outbounds_table, to_outbound)
default_outTag = to_outbound.tag
end
end
end
return default_outTag, last_insert_outbound
end
if node then
if server_host and server_port then
node.address = server_host
node.port = server_port
end
if node.protocol == "_shunt" then
local preproxy_rule_name = node.preproxy_enabled == "1" and "main" or nil
local preproxy_tag = preproxy_rule_name
local preproxy_node_id = node["main_node"]
local preproxy_outbound_tag, preproxy_balancer_tag
local preproxy_nodes
local function gen_shunt_node(rule_name, _node_id)
if not rule_name then return nil, nil end
if not _node_id then _node_id = node[rule_name] end
if _node_id == "_direct" then
return "direct", nil
elseif _node_id == "_blackhole" then
return "blackhole", nil
elseif _node_id == "_default" then
return "default", nil
elseif _node_id and _node_id:find("Socks_") then
local socks_id = _node_id:sub(1 + #"Socks_")
local socks_node = uci:get_all(appname, socks_id) or nil
local socks_tag
if socks_node then
local _node = {
type = "Xray",
protocol = "socks",
address = "127.0.0.1",
port = socks_node.port,
transport = "tcp",
stream_security = "none"
}
local outbound = gen_outbound(flag, _node, rule_name)
if outbound then
if rule_name == "default" then
table.insert(outbounds, 1, outbound)
else
table.insert(outbounds, outbound)
end
socks_tag = outbound.tag
end
end
return socks_tag, nil
elseif _node_id then
local _node = uci:get_all(appname, _node_id)
if not _node then return nil, nil end
if api.is_normal_node(_node) then
local use_proxy = preproxy_tag and node[rule_name .. "_proxy_tag"] == preproxy_rule_name and _node_id ~= preproxy_node_id
if use_proxy and preproxy_balancer_tag and preproxy_nodes[_node_id] then use_proxy = false end
local copied_outbound
for index, value in ipairs(outbounds) do
if value["_id"] == _node_id and value["_flag_proxy_tag"] == (use_proxy and preproxy_tag or nil) then
copied_outbound = api.clone(value)
break
end
end
if copied_outbound then
copied_outbound.tag = rule_name .. ":" .. _node.remarks
table.insert(outbounds, copied_outbound)
return copied_outbound.tag, nil
else
if use_proxy and (_node.type ~= "Xray" or _node.flow == "xtls-rprx-vision") then
new_port = get_new_port()
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, {
inboundTag = {"proxy_" .. rule_name},
outboundTag = not preproxy_balancer_tag and preproxy_tag or nil,
balancerTag = preproxy_balancer_tag
})
end
local proxy_table = {
tag = use_proxy and preproxy_tag or nil
}
if not proxy_table.tag then
if xray_settings.fragment == "1" then
proxy_table.fragment = true
end
if xray_settings.noise == "1" then
proxy_table.noise = true
end
end
local outbound = gen_outbound(flag, _node, rule_name, proxy_table)
local outbound_tag
if outbound then
outbound.tag = outbound.tag .. ":" .. _node.remarks
outbound_tag, last_insert_outbound = set_outbound_detour(_node, outbound, outbounds, rule_name)
if rule_name == "default" then
table.insert(outbounds, 1, outbound)
else
table.insert(outbounds, outbound)
end
if last_insert_outbound then
table.insert(outbounds, last_insert_outbound)
end
end
return outbound_tag, nil
end
elseif _node.protocol == "_balancing" then
return nil, gen_balancer(_node, rule_name)
elseif _node.protocol == "_iface" then
local outbound_tag
if _node.iface then
local outbound = {
protocol = "freedom",
tag = rule_name,
streamSettings = {
sockopt = {
mark = 255,
interface = _node.iface
}
}
}
outbound_tag = outbound.tag
table.insert(outbounds, outbound)
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, _node.iface))
end
return outbound_tag, nil
end
end
end
if preproxy_tag and preproxy_node_id then
preproxy_outbound_tag, preproxy_balancer_tag = gen_shunt_node(preproxy_rule_name, preproxy_node_id)
if preproxy_balancer_tag then
local _node_id = preproxy_node_id
preproxy_nodes = {}
while _node_id do
_node = uci:get_all(appname, _node_id)
if not _node then break end
if _node.protocol ~= "_balancing" then
preproxy_nodes[_node_id] = true
break
end
local _blc_nodes = _node.balancing_node
for i = 1, #_blc_nodes do preproxy_nodes[_blc_nodes[i]] = true end
_node_id = _node.fallback_node
end
elseif preproxy_outbound_tag then
preproxy_tag = preproxy_outbound_tag
end
end
--default_node
local default_node_id = node.default_node or "_direct"
local default_outboundTag, default_balancerTag = gen_shunt_node("default", default_node_id)
COMMON.default_outbound_tag = default_outboundTag
COMMON.default_balancer_tag = default_balancerTag
--shunt rule
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
balancerTag = default_balancerTag
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 inboundTag = nil
if e["inbound"] and e["inbound"] ~= "" then
inboundTag = {}
if e["inbound"]:find("tproxy") then
if redir_port then
table.insert(inboundTag, "tcp_redir")
table.insert(inboundTag, "udp_redir")
end
end
if e["inbound"]:find("socks") then
if local_socks_port then
table.insert(inboundTag, "socks-in")
end
end
end
local domains = nil
if e.domain_list then
local domain_table = {
shunt_rule_name = e[".name"],
outboundTag = outboundTag,
balancerTag = balancerTag,
domain = {},
}
domains = {}
string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w)
if w:find("#") == 1 then return end
table.insert(domains, w)
table.insert(domain_table.domain, w)
end)
if outboundTag or balancerTag then
table.insert(dns_domain_rules, api.clone(domain_table))
end
if #domains == 0 then domains = nil end
end
local ip = nil
if e.ip_list then
ip = {}
string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w)
if w:find("#") == 1 then return end
table.insert(ip, w)
end)
if #ip == 0 then ip = nil end
end
local source = nil
if e.source then
source = {}
string.gsub(e.source, '[^' .. " " .. ']+', function(w)
table.insert(source, w)
end)
end
local rule = {
ruleTag = e.remarks,
inboundTag = inboundTag,
outboundTag = outboundTag,
balancerTag = balancerTag,
network = e["network"] or "tcp,udp",
source = source,
sourcePort = e["sourcePort"] ~= "" and e["sourcePort"] or nil,
port = e["port"] ~= "" and e["port"] or nil,
protocol = protocols
}
if domains then
local _rule = api.clone(rule)
_rule.ruleTag = _rule.ruleTag .. " Domains"
_rule.domains = domains
table.insert(rules, _rule)
end
if ip then
local _rule = api.clone(rule)
_rule.ruleTag = _rule.ruleTag .. " IP"
_rule.ip = ip
table.insert(rules, _rule)
end
if not domains and not ip then
table.insert(rules, rule)
end
end
end)
if default_balancerTag then
table.insert(rules, {
ruleTag = "default",
balancerTag = default_balancerTag,
network = "tcp,udp"
})
end
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 balancer_tag = gen_balancer(node)
if balancer_tag then
table.insert(rules, { network = "tcp,udp", balancerTag = balancer_tag })
end
routing = {
balancers = balancers,
rules = rules
}
COMMON.default_balancer_tag = balancer_tag
end
elseif node.protocol == "_iface" then
if node.iface then
local outbound = {
protocol = "freedom",
tag = node.remarks or node_id,
streamSettings = {
sockopt = {
mark = 255,
interface = node.iface
}
}
}
table.insert(outbounds, outbound)
COMMON.default_outbound_tag = outbound.tag
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
end
else
local outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.fragment == "1" or nil })
if outbound then
outbound.tag = outbound.tag .. ":" .. node.remarks
COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds)
table.insert(outbounds, outbound)
if last_insert_outbound then
table.insert(outbounds, last_insert_outbound)
end
routing = {
domainStrategy = "AsIs",
domainMatcher = "hybrid",
rules = {}
}
table.insert(routing.rules, {
ruleTag = "default",
outboundTag = COMMON.default_outbound_tag,
network = "tcp,udp"
})
end
end
end
if dns_listen_port then
local rules = {}
local _remote_dns_proto = "tcp"
if not routing then
routing = {
domainStrategy = "IPOnDemand",
rules = {}
}
end
dns = {
tag = "dns-global",
hosts = {},
disableCache = (dns_cache and dns_cache == "0") and true or false,
disableFallback = true,
disableFallbackIfMatch = true,
servers = {},
queryStrategy = "UseIP"
}
local dns_host = ""
if flag == "global" then
dns_host = uci:get(appname, "@global[0]", "dns_hosts") or ""
else
flag = flag:gsub("acl_", "")
local dns_hosts_mode = uci:get(appname, flag, "dns_hosts_mode") or "default"
if dns_hosts_mode == "default" then
dns_host = uci:get(appname, "@global[0]", "dns_hosts") or ""
elseif dns_hosts_mode == "disable" then
dns_host = ""
elseif dns_hosts_mode == "custom" then
dns_host = uci:get(appname, flag, "dns_hosts") or ""
end
end
if #dns_host > 0 then
string.gsub(dns_host, '[^' .. "\r\n" .. ']+', function(w)
local host = sys.exec(string.format("echo -n $(echo %s | awk -F ' ' '{print $1}')", w))
local key = sys.exec(string.format("echo -n $(echo %s | awk -F ' ' '{print $2}')", w))
if host ~= "" and key ~= "" then
dns.hosts[host] = key
end
end)
end
local _remote_dns_ip = nil
local _remote_dns = {
tag = "dns-in-remote",
queryStrategy = (remote_dns_query_strategy and remote_dns_query_strategy ~= "") and remote_dns_query_strategy or "UseIPv4"
}
if remote_dns_udp_server then
_remote_dns.address = remote_dns_udp_server
_remote_dns.port = tonumber(remote_dns_udp_port) or 53
_remote_dns_proto = "udp"
_remote_dns_ip = remote_dns_udp_server
end
if remote_dns_tcp_server then
_remote_dns.address = "tcp://" .. remote_dns_tcp_server .. ":" .. tonumber(remote_dns_tcp_port) or 53
_remote_dns.port = tonumber(remote_dns_tcp_port) or 53
_remote_dns_proto = "tcp"
_remote_dns_ip = remote_dns_tcp_server
end
if remote_dns_doh_url and remote_dns_doh_host then
if remote_dns_doh_ip and remote_dns_doh_host ~= remote_dns_doh_ip and not api.is_ip(remote_dns_doh_host) then
dns.hosts[remote_dns_doh_host] = remote_dns_doh_ip
end
_remote_dns.address = remote_dns_doh_url
_remote_dns.port = tonumber(remote_dns_doh_port) or 443
_remote_dns_ip = remote_dns_doh_ip
end
if _remote_dns.address then
table.insert(dns.servers, _remote_dns)
if remote_dns_detour == "direct" then
table.insert(routing.rules, 1, {
ip = {
_remote_dns_ip
},
port = _remote_dns.port,
network = _remote_dns_proto,
outboundTag = "direct"
})
end
end
local _remote_fakedns = nil
if remote_dns_fake then
fakedns = {}
local fakedns4 = {
ipPool = "198.18.0.0/16",
poolSize = 65535
}
local fakedns6 = {
ipPool = "fc00::/18",
poolSize = 65535
}
if remote_dns_query_strategy == "UseIP" then
table.insert(fakedns, fakedns4)
table.insert(fakedns, fakedns6)
elseif remote_dns_query_strategy == "UseIPv4" then
table.insert(fakedns, fakedns4)
elseif remote_dns_query_strategy == "UseIPv6" then
table.insert(fakedns, fakedns6)
end
_remote_fakedns = {
tag = "dns-in-remote_fakedns",
address = "fakedns",
}
table.insert(dns.servers, _remote_fakedns)
end
local _direct_dns = nil
if direct_dns_udp_server then
local domain = {}
local nodes_domain_text = sys.exec('uci show passwall2 | grep ".address=" | cut -d "\'" -f 2 | grep "[a-zA-Z]$" | sort -u')
string.gsub(nodes_domain_text, '[^' .. "\r\n" .. ']+', function(w)
table.insert(domain, "full:" .. w)
end)
if #domain > 0 then
table.insert(dns_domain_rules, 1, {
outboundTag = "direct",
domain = domain
})
end
_direct_dns = {
tag = "dns-in-direct",
address = direct_dns_udp_server,
port = tonumber(direct_dns_udp_port) or 53,
queryStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP",
}
table.insert(routing.rules, 1, {
ip = {
direct_dns_udp_server
},
port = tonumber(direct_dns_udp_port) or 53,
network = "udp",
outboundTag = "direct"
})
table.insert(dns.servers, _direct_dns)
end
if dns_listen_port then
table.insert(inbounds, {
listen = "127.0.0.1",
port = tonumber(dns_listen_port),
protocol = "dokodemo-door",
tag = "dns-in",
settings = {
address = "0.0.0.0",
network = "tcp,udp"
}
})
local direct_type_dns = {
settings = {
address = direct_dns_udp_server,
port = tonumber(direct_dns_udp_port) or 53,
network = "udp",
nonIPQuery = "skip",
blockTypes = {
65
}
},
proxySettings = {
tag = "direct"
}
}
local remote_type_dns = {
settings = {
address = remote_dns_udp_server,
port = tonumber(remote_dns_udp_port) or 53,
network = _remote_dns_proto or "tcp",
nonIPQuery = "drop"
}
}
local type_dns = direct_type_dns
table.insert(outbounds, {
tag = "dns-out",
protocol = "dns",
proxySettings = type_dns.proxySettings,
settings = type_dns.settings
})
table.insert(routing.rules, 1, {
inboundTag = {
"dns-in"
},
outboundTag = "dns-out"
})
end
local default_dns_tag = "dns-in-remote"
if (not COMMON.default_balancer_tag and not COMMON.default_outbound_tag) or COMMON.default_outbound_tag == "direct" then
default_dns_tag = "dns-in-direct"
end
if dns.servers and #dns.servers > 0 then
local dns_servers = nil
for index, value in ipairs(dns.servers) do
if not dns_servers and value.tag == default_dns_tag then
if value.tag == "dns-in-remote" and remote_dns_fake then
value.tag = "dns-in-default"
break
end
dns_servers = {
tag = "dns-in-default",
address = value.address,
port = value.port,
queryStrategy = value.queryStrategy
}
break
end
end
if dns_servers then
table.insert(dns.servers, 1, dns_servers)
end
--按分流顺序DNS
if dns_domain_rules and #dns_domain_rules > 0 then
for index, value in ipairs(dns_domain_rules) do
if value.domain and (value.outboundTag or value.balancerTag) then
local dns_server = nil
if value.outboundTag == "direct" then
dns_server = api.clone(_direct_dns)
else
if remote_dns_fake then
dns_server = api.clone(_remote_fakedns)
else
dns_server = api.clone(_remote_dns)
end
end
dns_server.domains = value.domain
if value.shunt_rule_name then
dns_server.tag = "dns-in-" .. value.shunt_rule_name
end
if dns_server then
table.insert(dns.servers, dns_server)
table.insert(routing.rules, {
inboundTag = {
dns_server.tag
},
outboundTag = value.outboundTag or nil,
balancerTag = value.balancerTag or nil
})
end
end
end
end
for i = #dns.servers, 1, -1 do
local v = dns.servers[i]
if v.tag ~= "dns-in-default" then
if not v.domains or #v.domains == 0 then
table.remove(dns.servers, i)
end
end
end
end
local default_rule_index = #routing.rules > 0 and #routing.rules or 1
for index, value in ipairs(routing.rules) do
if value.ruleTag == "default" then
default_rule_index = index
break
end
end
for index, value in ipairs(rules) do
local t = rules[#rules + 1 - index]
table.insert(routing.rules, default_rule_index, t)
end
local dns_hosts_len = 0
for key, value in pairs(dns.hosts) do
dns_hosts_len = dns_hosts_len + 1
end
if dns_hosts_len == 0 then
dns.hosts = nil
end
local content = flag .. node_id .. jsonc.stringify(routing.rules)
if api.cacheFileCompareToLogic(CACHE_TEXT_FILE, content) == false then
--clear ipset/nftset
if direct_ipset then
string.gsub(direct_ipset, '[^' .. "," .. ']+', function(w)
sys.call("ipset -q -F " .. w)
end)
local ipset_prefix_name = "passwall2_" .. node_id .. "_"
local ipset_list = sys.exec("ipset list | grep 'Name: ' | grep '" .. ipset_prefix_name .. "' | awk '{print $2}'")
string.gsub(ipset_list, '[^' .. "\r\n" .. ']+', function(w)
sys.call("ipset -q -F " .. w)
end)
end
if direct_nftset then
string.gsub(direct_nftset, '[^' .. "," .. ']+', function(w)
local split = api.split(w, "#")
if #split > 3 then
local ip_type = split[1]
local family = split[2]
local table_name = split[3]
local set_name = split[4]
sys.call(string.format("nft flush set %s %s %s 2>/dev/null", family, table_name, set_name))
end
end)
local family = "inet"
local table_name = "passwall2"
local nftset_prefix_name = "passwall2_" .. node_id .. "_"
local nftset_list = sys.exec("nft -a list sets | grep -E '" .. nftset_prefix_name .. "' | awk -F 'set ' '{print $2}' | awk '{print $1}'")
string.gsub(nftset_list, '[^' .. "\r\n" .. ']+', function(w)
sys.call(string.format("nft flush set %s %s %s 2>/dev/null", family, table_name, w))
end)
end
end
end
if inbounds or outbounds then
local config = {
log = {
--access = string.format("/tmp/etc/%s/%s_access.log", appname, "global"),
--error = string.format("/tmp/etc/%s/%s_error.log", appname, "global"),
--dnsLog = true,
loglevel = loglevel
},
-- DNS
dns = dns,
fakedns = fakedns,
-- 传入连接
inbounds = inbounds,
-- 传出连接
outbounds = outbounds,
-- 连接观测
burstObservatory = burstObservatory,
-- 路由
routing = routing,
-- 本地策略
policy = {
levels = {
[0] = {
-- handshake = 4,
-- connIdle = 300,
-- uplinkOnly = 2,
-- downlinkOnly = 5,
bufferSize = xray_settings.buffer_size and tonumber(xray_settings.buffer_size) or nil,
statsUserUplink = false,
statsUserDownlink = false
}
},
-- system = {
-- statsInboundUplink = false,
-- statsInboundDownlink = false
-- }
}
}
if xray_settings.fragment == "1" or xray_settings.noise == "1" then
table.insert(outbounds, {
protocol = "freedom",
tag = "dialerproxy",
settings = {
domainStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP",
fragment = (xray_settings.fragment == "1") and {
packets = (xray_settings.fragment_packets and xray_settings.fragment_packets ~= "") and xray_settings.fragment_packets,
length = (xray_settings.fragment_length and xray_settings.fragment_length ~= "") and xray_settings.fragment_length,
interval = (xray_settings.fragment_interval and xray_settings.fragment_interval ~= "") and xray_settings.fragment_interval
} or nil,
noises = (xray_settings.noise == "1") and get_noise_packets() or nil
},
streamSettings = {
sockopt = {
mark = 255,
tcpNoDelay = true
}
}
})
end
local direct_outbound = {
protocol = "freedom",
tag = "direct",
settings = {
domainStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP"
},
streamSettings = {
sockopt = {
mark = 255
}
}
}
if COMMON.default_outbound_tag == "direct" then
table.insert(outbounds, 1, direct_outbound)
else
table.insert(outbounds, direct_outbound)
end
local blackhole_outbound = {
protocol = "blackhole",
tag = "blackhole"
}
if COMMON.default_outbound_tag == "blackhole" then
table.insert(outbounds, 1, blackhole_outbound)
else
table.insert(outbounds, blackhole_outbound)
end
for index, value in ipairs(config.outbounds) do
if not value["_flag_proxy_tag"] and value["_id"] and value.server and value.server_port then
sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list"))
end
for k, v in pairs(config.outbounds[index]) do
if k:find("_") == 1 then
config.outbounds[index][k] = nil
end
end
end
return jsonc.stringify(config, 1)
end
end
function gen_proto_config(var)
local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
local local_socks_port = var["-local_socks_port"]
local local_socks_username = var["-local_socks_username"]
local local_socks_password = var["-local_socks_password"]
local local_http_address = var["-local_http_address"] or "0.0.0.0"
local local_http_port = var["-local_http_port"]
local local_http_username = var["-local_http_username"]
local local_http_password = var["-local_http_password"]
local server_proto = var["-server_proto"]
local server_address = var["-server_address"]
local server_port = var["-server_port"]
local server_username = var["-server_username"]
local server_password = var["-server_password"]
local inbounds = {}
local outbounds = {}
local routing = nil
if local_socks_address and local_socks_port then
local inbound = {
listen = local_socks_address,
port = tonumber(local_socks_port),
protocol = "socks",
settings = {
udp = true,
auth = "noauth"
}
}
if local_socks_username and local_socks_password and local_socks_username ~= "" and local_socks_password ~= "" then
inbound.settings.auth = "password"
inbound.settings.accounts = {
{
user = local_socks_username,
pass = local_socks_password
}
}
end
table.insert(inbounds, inbound)
end
if local_http_address and local_http_port then
local inbound = {
listen = local_http_address,
port = tonumber(local_http_port),
protocol = "http",
settings = {
allowTransparent = false
}
}
if local_http_username and local_http_password and local_http_username ~= "" and local_http_password ~= "" then
inbound.settings.accounts = {
{
user = local_http_username,
pass = local_http_password
}
}
end
table.insert(inbounds, inbound)
end
if server_proto ~= "nil" and server_address ~= "nil" and server_port ~= "nil" then
local outbound = {
protocol = server_proto,
streamSettings = {
network = "tcp",
security = "none"
},
settings = {
servers = {
{
address = server_address,
port = tonumber(server_port),
users = (server_username and server_password) and {
{
user = server_username,
pass = server_password
}
} or nil
}
}
}
}
if outbound then table.insert(outbounds, outbound) end
end
-- 额外传出连接
table.insert(outbounds, {
protocol = "freedom", tag = "direct", settings = {keep = ""}
})
local config = {
log = {
loglevel = "warning"
},
-- 传入连接
inbounds = inbounds,
-- 传出连接
outbounds = outbounds,
-- 路由
routing = routing
}
return jsonc.stringify(config, 1)
end
function gen_dns_config(var)
local dns_listen_port = var["-dns_listen_port"]
local dns_out_tag = var["-dns_out_tag"]
local direct_dns_udp_server = var["-direct_dns_udp_server"]
local direct_dns_udp_port = var["-direct_dns_udp_port"]
local direct_dns_tcp_server = var["-direct_dns_tcp_server"]
local direct_dns_tcp_port = var["-direct_dns_tcp_port"]
local direct_dns_doh_url = var["-direct_dns_doh_url"]
local direct_dns_doh_host = var["-direct_dns_doh_host"]
local direct_dns_doh_ip = var["-direct_dns_doh_ip"]
local direct_dns_doh_port = var["-direct_dns_doh_port"]
local direct_dns_query_strategy = var["-direct_dns_query_strategy"]
local remote_dns_udp_server = var["-remote_dns_udp_server"]
local remote_dns_udp_port = var["-remote_dns_udp_port"]
local remote_dns_tcp_server = var["-remote_dns_tcp_server"]
local remote_dns_tcp_port = var["-remote_dns_tcp_port"]
local remote_dns_doh_url = var["-remote_dns_doh_url"]
local remote_dns_doh_host = var["-remote_dns_doh_host"]
local remote_dns_doh_ip = var["-remote_dns_doh_ip"]
local remote_dns_doh_port = var["-remote_dns_doh_port"]
local remote_dns_query_strategy = var["-remote_dns_query_strategy"]
local remote_dns_detour = var["-remote_dns_detour"]
local remote_dns_client_ip = var["-remote_dns_client_ip"]
local remote_dns_outbound_socks_address = var["-remote_dns_outbound_socks_address"]
local remote_dns_outbound_socks_port = var["-remote_dns_outbound_socks_port"]
local dns_cache = var["-dns_cache"]
local loglevel = var["-loglevel"] or "warning"
local inbounds = {}
local outbounds = {}
local dns = nil
local routing = nil
if dns_listen_port then
routing = {
domainStrategy = "IPOnDemand",
rules = {}
}
dns = {
tag = "dns-global",
hosts = {},
disableCache = (dns_cache == "1") and false or true,
disableFallback = true,
disableFallbackIfMatch = true,
servers = {},
clientIp = (remote_dns_client_ip and remote_dns_client_ip ~= "") and remote_dns_client_ip or nil,
}
local other_type_dns_proto, other_type_dns_server, other_type_dns_port
if dns_out_tag == "remote" then
dns.queryStrategy = (remote_dns_query_strategy and remote_dns_query_strategy ~= "") and remote_dns_query_strategy or "UseIPv4"
if remote_dns_detour == "direct" then
dns_out_tag = "direct"
table.insert(outbounds, 1, {
tag = dns_out_tag,
protocol = "freedom",
settings = {
domainStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP"
},
streamSettings = {
sockopt = {
mark = 255
}
}
})
else
if remote_dns_outbound_socks_address and remote_dns_outbound_socks_port then
table.insert(outbounds, 1, {
tag = dns_out_tag,
protocol = "socks",
streamSettings = {
network = "tcp",
security = "none"
},
settings = {
servers = {
{
address = remote_dns_outbound_socks_address,
port = tonumber(remote_dns_outbound_socks_port)
}
}
}
})
end
end
local _remote_dns = {
tag = "dns-in-remote"
}
if remote_dns_udp_server then
_remote_dns.address = remote_dns_udp_server
_remote_dns.port = tonumber(remote_dns_udp_port) or 53
other_type_dns_proto = "udp"
other_type_dns_server = remote_dns_udp_server
other_type_dns_port = _remote_dns.port
end
if remote_dns_tcp_server then
_remote_dns.address = "tcp://" .. remote_dns_tcp_server .. ":" .. tonumber(remote_dns_tcp_port) or 53
_remote_dns.port = tonumber(remote_dns_tcp_port) or 53
other_type_dns_proto = "tcp"
other_type_dns_server = remote_dns_tcp_server
other_type_dns_port = _remote_dns.port
end
if remote_dns_doh_url and remote_dns_doh_host then
if remote_dns_doh_ip and remote_dns_doh_host ~= remote_dns_doh_ip and not api.is_ip(remote_dns_doh_host) then
dns.hosts[remote_dns_doh_host] = remote_dns_doh_ip
end
_remote_dns.address = remote_dns_doh_url
_remote_dns.port = tonumber(remote_dns_doh_port) or 443
end
table.insert(dns.servers, _remote_dns)
elseif dns_out_tag == "direct" then
dns.queryStrategy = (direct_dns_query_strategy and direct_dns_query_strategy ~= "") and direct_dns_query_strategy or "UseIP"
table.insert(outbounds, 1, {
tag = dns_out_tag,
protocol = "freedom",
settings = {
domainStrategy = dns.queryStrategy
},
streamSettings = {
sockopt = {
mark = 255
}
}
})
local _direct_dns = {
tag = "dns-in-direct"
}
if direct_dns_udp_server then
_direct_dns.address = direct_dns_udp_server
_direct_dns.port = tonumber(direct_dns_udp_port) or 53
table.insert(routing.rules, 1, {
ip = {
direct_dns_udp_server
},
port = tonumber(direct_dns_udp_port) or 53,
network = "udp",
outboundTag = "direct"
})
other_type_dns_proto = "udp"
other_type_dns_server = direct_dns_udp_server
other_type_dns_port = _direct_dns.port
end
if direct_dns_tcp_server then
_direct_dns.address = "tcp+local://" .. direct_dns_tcp_server
_direct_dns.port = tonumber(direct_dns_tcp_port) or 53
other_type_dns_proto = "tcp"
other_type_dns_server = direct_dns_tcp_server
other_type_dns_port = _direct_dns.port
end
if direct_dns_doh_url and direct_dns_doh_host then
if direct_dns_doh_ip and direct_dns_doh_host ~= direct_dns_doh_ip and not api.is_ip(direct_dns_doh_host) then
dns.hosts[direct_dns_doh_host] = direct_dns_doh_ip
end
_direct_dns.address = direct_dns_doh_url:gsub("https://", "https+local://")
_direct_dns.port = tonumber(direct_dns_doh_port) or 443
end
table.insert(dns.servers, _direct_dns)
end
local dns_hosts_len = 0
for key, value in pairs(dns.hosts) do
dns_hosts_len = dns_hosts_len + 1
end
if dns_hosts_len == 0 then
dns.hosts = nil
end
table.insert(inbounds, {
listen = "127.0.0.1",
port = tonumber(dns_listen_port),
protocol = "dokodemo-door",
tag = "dns-in",
settings = {
address = other_type_dns_server or "1.1.1.1",
port = other_type_dns_port or 53,
network = "tcp,udp"
}
})
table.insert(outbounds, {
tag = "dns-out",
protocol = "dns",
proxySettings = {
tag = dns_out_tag
},
settings = {
address = other_type_dns_server or "1.1.1.1",
port = other_type_dns_port or 53,
network = other_type_dns_proto or "tcp",
nonIPQuery = "drop"
}
})
table.insert(routing.rules, 1, {
inboundTag = {
"dns-in"
},
outboundTag = "dns-out"
})
table.insert(routing.rules, {
inboundTag = {
"dns-global"
},
outboundTag = dns_out_tag
})
end
if inbounds or outbounds then
local config = {
log = {
--dnsLog = true,
loglevel = loglevel
},
-- DNS
dns = dns,
-- 传入连接
inbounds = inbounds,
-- 传出连接
outbounds = outbounds,
-- 路由
routing = routing
}
return jsonc.stringify(config, 1)
end
end
_G.gen_config = gen_config
_G.gen_proto_config = gen_proto_config
_G.gen_dns_config = gen_dns_config
if arg[1] then
local func =_G[arg[1]]
if func then
print(func(api.get_function_args(arg)))
end
end